Skip to content

Commit

Permalink
🎨 style: 絵文字のアニメーションを使った背景の実装 #185
Browse files Browse the repository at this point in the history
* 🎨 style: 背景グラデーションの実装 #185

* 🎨 style: 絵文字アニメーションの実装

* 🎨 style: ヘッダーの重ね合わせを修正

* 🎨 style: 絵文字の色を半透明に変更

* ♻️ refactor: 細かい表記の修正
  • Loading branch information
kazzyfrog authored May 22, 2024
1 parent 53dfd23 commit a3be167
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 59 deletions.
140 changes: 84 additions & 56 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@ export const BASE_PATH = basePath ? basePath : "";
import GitHubIcon from "@mui/icons-material/GitHub";
import { SITE_NAME } from "../../lib/constants";
import KeyboardDoubleArrowDownSharpIcon from "@mui/icons-material/KeyboardDoubleArrowDownSharp";
import GradientBackground from "@/components/gradient-background";
import ScreenEmojis from "@/components/screen-emojis";

import contributors from "../../Contributors.json";
import {
groupContributorsBySection,
latestContributorsColor,
} from "@/utils/contributors-grouping";

export default function Home() {
const contributorsNumber = contributors.length;
const contributorsGroups = groupContributorsBySection(contributors, 3);

return (
<>
Expand Down Expand Up @@ -63,65 +71,85 @@ export default function Home() {
</p>
</section>

<section className="bg-red-600 p-6 pb-0 pt-8 md:px-10 md:pt-10">
<div className="rounded-md bg-white px-5 pb-16 pt-12 md:p-20 md:pt-8">
<h2 className="text-center text-xl font-bold tracking-tighter text-red-600 md:pb-4 md:pt-12 md:text-3xl">
簡単 8 STEP でコントリビューション!
</h2>
<ul className="mx-auto mt-8 w-fit list-inside rounded-md px-2 text-lg font-bold md:text-xl md:leading-7 lg:mb-8 lg:mt-10 lg:flex lg:gap-16 lg:bg-stone-100 lg:px-12 lg:pb-4 lg:pt-8">
<div>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 1</p>
プロジェクトをフォーク
</li>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 2</p>
ローカルマシンへクローン
</li>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 3</p>
作業用ブランチを作成
</li>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 4</p>
変更を加える
</li>
<GradientBackground mainColor={latestContributorsColor}>
<ScreenEmojis contributors={contributorsGroups[0]} isTopSection />
<section className="mx-auto h-screen max-w-screen-xl">
セクション1
</section>

<ScreenEmojis contributors={contributorsGroups[1]} />
<section className="mx-auto h-screen max-w-screen-xl">
セクション2
</section>

<ScreenEmojis contributors={contributorsGroups[2]} />
<section className="bg-red-600 p-6 pb-0 pt-8 md:px-10 md:pt-10">
<div className="rounded-md bg-white px-5 pb-16 pt-12 md:p-20 md:pt-8">
<h2 className="text-center text-xl font-bold tracking-tighter text-red-600 md:pb-4 md:pt-12 md:text-3xl">
簡単 8 STEP でコントリビューション!
</h2>
<ul className="mx-auto mt-8 w-fit list-inside rounded-md px-2 text-lg font-bold md:text-xl md:leading-7 lg:mb-8 lg:mt-10 lg:flex lg:gap-16 lg:bg-stone-100 lg:px-12 lg:pb-4 lg:pt-8">
<div>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 1</p>
プロジェクトをフォーク
</li>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 2</p>
ローカルマシンへクローン
</li>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 3</p>
作業用ブランチを作成
</li>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 4</p>
変更を加える
</li>
</div>
<div>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 5</p>
変更をコミット
</li>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 6</p>
変更をプッシュ
</li>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 7</p>
プルリクエストを作成
</li>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 8</p>
レビューとフィードバックに対応
</li>
</div>
</ul>
<div className="mb-8 text-center font-bold leading-7 md:text-xl md:leading-8">
<KeyboardDoubleArrowDownSharpIcon className="mb-4 text-6xl text-red-600" />
<p>プルリクエストが承認されると</p>
<p>あなたの変更がメインプロジェクトに反映されます 🎉</p>
</div>
<div>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 5</p>
変更をコミット
</li>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 6</p>
変更をプッシュ
</li>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 7</p>
プルリクエストを作成
</li>
<li className="pb-4 md:pb-6">
<p className="mr-6 text-sm text-red-600 md:text-lg">STEP 8</p>
レビューとフィードバックに対応
</li>
<div className="text-center">
<a
href="https://github.com/first-contributions-ja/first-contributions-ja.github.io"
className="rounded-md bg-red-600 px-3 py-3 text-sm text-white transition hover:opacity-70 md:px-5 md:py-4 md:text-lg"
>
<GitHubIcon className="mr-3 -translate-y-0.5 text-3xl md:text-4xl" />
詳しい手順はこちら
</a>
</div>
</ul>
<div className="mb-8 text-center font-bold leading-7 md:text-xl md:leading-8">
<KeyboardDoubleArrowDownSharpIcon className="mb-4 text-6xl text-red-600" />
<p>プルリクエストが承認されると</p>
<p>あなたの変更がメインプロジェクトに反映されます 🎉</p>
</div>
<div className="text-center">
<a
href="https://github.com/first-contributions-ja/first-contributions-ja.github.io"
className="rounded-md bg-red-600 px-3 py-3 text-sm text-white transition hover:opacity-70 md:px-5 md:py-4 md:text-lg"
>
<GitHubIcon className="mr-3 -translate-y-0.5 text-3xl md:text-4xl" />
詳しい手順はこちら
</a>
</div>
</div>
</section>
</section>
</GradientBackground>
{/* tailwind のclassをCSSに含める */}
<div className="hidden animate-[horizontal_3s_ease-in-out_infinite_alternate_both]">
horizontal
</div>
<div className="hidden animate-[vertical_3s_ease-in-out_infinite_alternate_both]">
vertical
</div>
</>
);
}
61 changes: 61 additions & 0 deletions src/components/animated-emoji.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
emojiToUnicodeHex,
getAnimationStyles,
getEmojiSize,
} from "@/utils/animated-emoji";
import contributors from "../../Contributors.json";
import { latestContributorsColor } from "@/utils/contributors-grouping";
import { hexToRgb } from "@/utils/background-color";

type AnimatedEmojiProps = {
index: number;
contributor: (typeof contributors)[number];
size?: "small" | "medium" | "large";
isTopSection?: boolean;
};

const AnimatedEmoji: React.FC<AnimatedEmojiProps> = ({
index,
contributor,
isTopSection = false,
}) => {
const emojiSize = getEmojiSize(
isTopSection && index === 0 ? "large" : "medium",
);
const speed = Math.floor(Math.random() * 65 + 35) / 10;
const styles = getAnimationStyles(index, speed);
const notoEmoji = emojiToUnicodeHex(contributor.favoriteEmoji);
const emojiColor = hexToRgb(latestContributorsColor, 0.5);

return (
<>
<div
className="group absolute flex h-20 w-20 cursor-pointer items-center justify-center rounded-full bg-transparent [perspective:1000px]"
style={styles as React.CSSProperties}
>
<div className="relative h-full w-full rounded-full duration-1000 [transform-style:preserve-3d] group-hover:[transform:rotateY(180deg)]">
<div className="absolute h-full w-full rounded-full [backface-visibility:hidden]">
<div
className={`flex h-full w-full flex-col items-center justify-center gap-0.5 ${emojiSize}`}
style={{ color: emojiColor }}
dangerouslySetInnerHTML={{ __html: notoEmoji }}
></div>
</div>
<div
style={{ borderColor: contributor.favoriteColor }}
className="absolute h-full w-full rounded-full border-4 shadow-xl [backface-visibility:hidden] [transform:rotateY(180deg)]"
>
<img
src={`${contributor.github}.png`}
loading="lazy"
alt="contributor's icon"
className="h-full w-full rounded-full object-cover object-top"
/>
</div>
</div>
</div>
</>
);
};

export default AnimatedEmoji;
48 changes: 48 additions & 0 deletions src/components/gradient-background.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { getGradientStyles } from "@/utils/background-color";
import { ReactNode } from "react";

type GradientBackgroundProps = {
mainColor: string;
children: ReactNode;
};

const GradientBackground: React.FC<GradientBackgroundProps> = ({
mainColor,
children,
}) => {
const gradientStyles = getGradientStyles(mainColor);

return (
<main
className="bg-gradient-to-b from-[var(--background-start-rgb)] via-[var(--background-middle-rgb)] to-[var(--background-end-rgb)]"
style={gradientStyles as React.CSSProperties}
>
<div className=" inset-0 top-0">
<svg
preserveAspectRatio="none"
viewBox="0 0 1200 120"
xmlns="http://www.w3.org/2000/svg"
style={{
fill: "#ffffff",
width: "100%",
height: 137,
transform: "scaleX(-1)",
}}
>
<path
d="M0 0v46.29c47.79 22.2 103.59 32.17 158 28 70.36-5.37 136.33-33.31 206.8-37.5 73.84-4.36 147.54 16.88 218.2 35.26 69.27 18 138.3 24.88 209.4 13.08 36.15-6 69.85-17.84 104.45-29.34C989.49 25 1113-14.29 1200 52.47V0z"
opacity=".25"
/>
<path
d="M0 0v15.81c13 21.11 27.64 41.05 47.69 56.24C99.41 111.27 165 111 224.58 91.58c31.15-10.15 60.09-26.07 89.67-39.8 40.92-19 84.73-46 130.83-49.67 36.26-2.85 70.9 9.42 98.6 31.56 31.77 25.39 62.32 62 103.63 73 40.44 10.79 81.35-6.69 119.13-24.28s75.16-39 116.92-43.05c59.73-5.85 113.28 22.88 168.9 38.84 30.2 8.66 59 6.17 87.09-7.5 22.43-10.89 48-26.93 60.65-49.24V0z"
opacity=".5"
/>
<path d="M0 0v5.63C149.93 59 314.09 71.32 475.83 42.57c43-7.64 84.23-20.12 127.61-26.46 59-8.63 112.48 12.24 165.56 35.4C827.93 77.22 886 95.24 951.2 90c86.53-7 172.46-45.71 248.8-84.81V0z" />
</svg>
</div>
{children}
</main>
);
};

export default GradientBackground;
6 changes: 3 additions & 3 deletions src/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { BASE_PATH } from "../app/page";

export default function Header() {
return (
<header className="fixed left-0 top-0 w-full py-3 backdrop-blur-sm lg:py-4">
<header className="fixed left-0 top-0 z-10 w-full py-3 backdrop-blur-sm lg:py-4">
<Link href="/">
<h1 className="ml-3 inline text-lg font-bold text-red-600 transition hover:opacity-70 lg:ml-5">
<span className="ml-3 inline text-lg font-bold text-red-600 transition hover:opacity-70 lg:ml-5">
<Image
src={`${BASE_PATH}/logo.svg`}
width={640}
Expand All @@ -16,7 +16,7 @@ export default function Header() {
className="mr-2 inline-block w-[40px] lg:mr-4"
/>
{SITE_NAME}
</h1>
</span>
</Link>
</header>
);
Expand Down
36 changes: 36 additions & 0 deletions src/components/screen-emojis.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import AnimatedEmoji from "./animated-emoji";
import contributors from "../../Contributors.json";
import { Noto_Emoji } from "next/font/google";

const emoji = Noto_Emoji({
subsets: ["emoji"],
weight: "300",
display: "swap",
});

type ScreenEmojisProps = {
contributors: typeof contributors;
isTopSection?: boolean;
};

const ScreenEmojis: React.FC<ScreenEmojisProps> = ({
contributors,
isTopSection,
}) => {
return (
<>
<div className={`${emoji.className} relative`}>
{contributors.map((contributor, index) => (
<AnimatedEmoji
key={index}
index={index}
contributor={contributor}
isTopSection={isTopSection}
/>
))}
</div>
</>
);
};

export default ScreenEmojis;
39 changes: 39 additions & 0 deletions src/utils/animated-emoji.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const getEmojiSize = (type: "small" | "medium" | "large") => {
switch (type) {
case "small":
return "text-xl";
case "medium":
return "text-7xl";
case "large":
return "text-9xl";
}
};

const getAnimationDirection = (index: number) =>
(index + 1) % 2 === 0 ? "vertical" : "horizontal";
const getAnimationAlternate = (index: number) =>
index < 5 ? "alternate-reverse" : "alternate";

const getAnimationStyles = (index: number, speed: number) => {
const animationDirection = getAnimationDirection(index);
const isHorizontal = animationDirection === "horizontal";
const alternate = getAnimationAlternate(index);
const left = isHorizontal ? 0 : `calc(100vw / 10 * ${index})`;
const top = isHorizontal ? `calc(80vh / 10 * ${index})` : 0;

return {
animation: `${animationDirection} ${speed}s ease-in-out infinite ${alternate} both`,
left,
top,
};
};

const emojiToUnicodeHex = (emoji: string) => {
const codePoint = emoji.codePointAt(0);
if (codePoint === undefined) {
throw new Error("Invalid emoji input");
}
return `&#x${codePoint.toString(16).toUpperCase()};&#xfe0f;`;
};

export { getEmojiSize, getAnimationStyles, emojiToUnicodeHex };
21 changes: 21 additions & 0 deletions src/utils/background-color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const hexToRgb = (color: string, transparency = 1) => {
const rgbValues = Object.fromEntries(
(
(color.match(/^#?[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/) ? color : "000")
.replace(/^#?(.*)$/, (_, hex) =>
hex.length == 3 ? hex.replace(/./g, "$&$&") : hex,
)
.match(/../g) ?? []
).map((c, i) => ["rgb".charAt(i), parseInt("0x" + c)]),
);

return `rgb(${rgbValues.r} ${rgbValues.g} ${rgbValues.b} / ${transparency})`;
};

export const getGradientStyles = (color: string) => {
return {
"--background-start-rgb": hexToRgb(color, 0.05),
"--background-middle-rgb": hexToRgb(color, 0.4),
"--background-end-rgb": hexToRgb(color),
};
};
Loading

0 comments on commit a3be167

Please sign in to comment.