-
Notifications
You must be signed in to change notification settings - Fork 0
Feature#385 메인페이지 youtube embed 영상 삽입 #386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { useEffect, useState } from "react"; | ||
|
|
||
| export const useFontLoadState = () => { | ||
| const [fontLoaded, setFontLoaded] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| if (document.fonts && document.fonts.ready) { | ||
| document.fonts.ready.then(() => { | ||
| setFontLoaded(true); | ||
| }); | ||
| } else { | ||
| setTimeout(() => setFontLoaded(true), 100); | ||
| } | ||
| }, []); | ||
|
|
||
| return { fontLoaded }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -110,8 +110,6 @@ | |
|
|
||
| margin: 0px auto; | ||
|
|
||
| @media (max-width: 1023px) { | ||
| } | ||
| @media (max-width: 768px) { | ||
| padding: 0.5rem; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| .yt_hero_section { | ||
| &__wrapper { | ||
| position: relative; | ||
|
|
||
| display: flex; | ||
| flex-direction: column; | ||
| justify-content: center; | ||
|
|
||
| height: calc(100vh - 100px - 70px); | ||
| // 100px: header | ||
| // 70px: banner | ||
|
|
||
| color: #fff; | ||
| background-color: #000; | ||
|
|
||
| overflow: clip; | ||
|
|
||
| @media (max-width: 1023px) { | ||
| height: calc(100vh - 60px - 70px); | ||
| } | ||
| @media (max-width: 768px) { | ||
| height: calc(100vh - 60px - 70px); | ||
| } | ||
| } | ||
|
|
||
| &__video { | ||
| width: 100%; | ||
| margin: 0px auto; | ||
| } | ||
|
|
||
| &__title { | ||
| display: flex; | ||
| flex-direction: row; | ||
| justify-content: center; | ||
|
|
||
| margin: 0px auto; | ||
| text-align: center; | ||
|
|
||
| overflow: hidden; | ||
|
|
||
| font-weight: bold; | ||
| font-size: 3.75rem; | ||
|
|
||
| @media (max-width: 1023px) { | ||
| flex-direction: column; | ||
| gap: 0; | ||
|
|
||
| font-size: 3rem; | ||
| } | ||
|
|
||
| @media (max-width: 767px) { | ||
| gap: 0; | ||
| font-size: 2rem; | ||
|
|
||
| line-height: 1.2; | ||
| } | ||
| } | ||
|
|
||
| &__content { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 1rem; | ||
|
|
||
| width: 100%; | ||
|
|
||
| margin: 0px auto; | ||
|
|
||
| @media (max-width: 768px) { | ||
| padding: 0.5rem; | ||
| } | ||
|
|
||
| p { | ||
| display: flex; | ||
| flex-direction: column; | ||
| text-align: center; | ||
|
|
||
| width: 100%; | ||
|
|
||
| margin-left: auto; | ||
| margin-right: auto; | ||
|
|
||
| font-size: 1.125rem; | ||
|
|
||
| @media (max-width: 767px) { | ||
| font-size: 1rem; | ||
| } | ||
| } | ||
|
|
||
| button { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 0.75rem; | ||
|
|
||
| width: fit-content; | ||
|
|
||
| margin: 0.5rem auto; | ||
| border-radius: 9999px; | ||
| padding: 0.5rem 1rem; | ||
|
|
||
| background-color: #fff; | ||
| transition: background-color 1s; | ||
|
|
||
| font-size: 1rem; | ||
|
|
||
| @media (max-width: 1023px) { | ||
| padding: 0.5rem 1rem; | ||
| font-size: 1rem; | ||
| } | ||
| @media (max-width: 768px) { | ||
| padding: 0.5rem 1rem; | ||
| font-size: 0.875rem; | ||
| } | ||
|
|
||
| &:hover { | ||
| cursor: pointer; | ||
| background-color: #f3f3f3; | ||
| } | ||
|
|
||
| span:first-child { | ||
| color: #000; | ||
| } | ||
| span:last-child { | ||
| aspect-ratio: 1 / 1; | ||
| height: fit-content; | ||
|
|
||
| padding: 0.25rem; | ||
| border-radius: 9999px; | ||
|
|
||
| background-color: #000; | ||
| } | ||
| } | ||
|
|
||
| footer { | ||
| display: flex; | ||
| justify-content: center; | ||
|
|
||
| width: 100%; | ||
| height: fit-content; | ||
|
|
||
| margin: 0px auto; | ||
|
|
||
| animation: bounceWithOpacity 1.5s infinite; | ||
|
|
||
| @keyframes bounceWithOpacity { | ||
| 0% { | ||
| transform: translateY(0); | ||
| opacity: 1; | ||
| } | ||
| 50% { | ||
| transform: translateY(-10px); | ||
| opacity: 0.5; | ||
| } | ||
| 100% { | ||
| transform: translateY(0); | ||
| opacity: 1; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,56 @@ | ||||||||||||||||||||||||||||||
| import { useGSAP } from "@gsap/react"; | ||||||||||||||||||||||||||||||
| import { YouTubeEmbed } from "@next/third-parties/google"; | ||||||||||||||||||||||||||||||
| import gsap from "gsap"; | ||||||||||||||||||||||||||||||
| import { ChevronDown, ChevronRight } from "lucide-react"; | ||||||||||||||||||||||||||||||
| import Link from "next/link"; | ||||||||||||||||||||||||||||||
| import { useRef } from "react"; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import { useFontLoadState } from "@/shared/hooks/useFontLoadState"; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import styles from "./YoutubeHeroSection.module.scss"; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export const YoutubeHeroSection = () => { | ||||||||||||||||||||||||||||||
| const titleRef = useRef<HTMLHeadingElement>(null); | ||||||||||||||||||||||||||||||
| const textRef = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||
| const registerButtonRef = useRef<HTMLButtonElement>(null); | ||||||||||||||||||||||||||||||
| const { fontLoaded } = useFontLoadState(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| useGSAP(() => { | ||||||||||||||||||||||||||||||
| if (!fontLoaded) return; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const timeline = gsap.timeline(); | ||||||||||||||||||||||||||||||
| timeline.fromTo(titleRef.current, { opacity: 0, y: 50 }, { opacity: 1, y: 0, duration: 2, ease: "expo.out" }); | ||||||||||||||||||||||||||||||
| }, [fontLoaded]); | ||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainGuard null ref and verify useGSAP dependency signature Prevent null target errors and use the current - useGSAP(() => {
- if (!fontLoaded) return;
-
- const timeline = gsap.timeline();
- timeline.fromTo(titleRef.current, { opacity: 0, y: 50 }, { opacity: 1, y: 0, duration: 2, ease: "expo.out" });
- }, [fontLoaded]);
+ useGSAP(() => {
+ if (!fontLoaded || !titleRef.current) return;
+ gsap.timeline().fromTo(
+ titleRef.current,
+ { opacity: 0, y: 50 },
+ { opacity: 1, y: 0, duration: 2, ease: "expo.out" }
+ );
+ }, { dependencies: [fontLoaded] });If you’re on an older To confirm plugin version and whether 🏁 Script executed: #!/bin/bash
# Show @gsap/react version and check for registration
fd -a package.json | xargs -I {} jq -r '.dependencies["@gsap/react"] // .devDependencies["@gsap/react"] // empty + " (file: {})"' {}
rg -nP 'registerPlugin\(\s*useGSAP\s*\)' -C2 --type ts --type tsxLength of output: 241 Guard null refs and migrate to v2 useGSAP signature useGSAP(() => {
- if (!fontLoaded) return;
-
- const timeline = gsap.timeline();
- timeline.fromTo(titleRef.current, { opacity: 0, y: 50 }, { opacity: 1, y: 0, duration: 2, ease: "expo.out" });
-}, [fontLoaded]);
+ if (!fontLoaded || !titleRef.current) return;
+ gsap.timeline().fromTo(
+ titleRef.current,
+ { opacity: 0, y: 50 },
+ { opacity: 1, y: 0, duration: 2, ease: "expo.out" }
+ );
+}, { dependencies: [fontLoaded] });Aligns with @gsap/react 2.1.2’s object‐based deps API and prevents null‐target errors. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||
| <section className={styles.yt_hero_section__wrapper}> | ||||||||||||||||||||||||||||||
| <h1 className={styles.yt_hero_section__title} ref={titleRef} style={{ opacity: fontLoaded ? 1 : 0 }}> | ||||||||||||||||||||||||||||||
| <span>당신의 수능을 바꿀 </span> | ||||||||||||||||||||||||||||||
| <span>마지막 기회</span> | ||||||||||||||||||||||||||||||
| </h1> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| <article className={styles.yt_hero_section__video}> | ||||||||||||||||||||||||||||||
| <YouTubeEmbed videoid={"sy0h73_DO5M"} style="margin: 0px auto;" /> | ||||||||||||||||||||||||||||||
| </article> | ||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix invalid React style prop (string passed instead of object) Passing a string to - <article className={styles.yt_hero_section__video}>
- <YouTubeEmbed videoid={"sy0h73_DO5M"} style="margin: 0px auto;" />
- </article>
+ <article className={styles.yt_hero_section__video}>
+ <YouTubeEmbed
+ videoid="sy0h73_DO5M"
+ style={{ margin: "0 auto" }}
+ />
+ </article>Optional hardening (keeps viewers on your content): + <YouTubeEmbed
+ videoid="sy0h73_DO5M"
+ style={{ margin: "0 auto" }}
+ params="rel=0&modestbranding=1&playsinline=1"
+ />📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| <div ref={textRef} className={styles.yt_hero_section__content} style={{ opacity: fontLoaded ? 1 : 0 }}> | ||||||||||||||||||||||||||||||
| <p> | ||||||||||||||||||||||||||||||
| <span>모의가 아닌 진짜 수능 이용 가이드입니다.</span> | ||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| <Link href="/apply" scroll> | ||||||||||||||||||||||||||||||
| <button ref={registerButtonRef}> | ||||||||||||||||||||||||||||||
| <span>지금 바로 신청하기</span> | ||||||||||||||||||||||||||||||
| <span> | ||||||||||||||||||||||||||||||
| <ChevronRight strokeWidth={1} /> | ||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||
| </Link> | ||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid interactive-in-interactive: don’t nest a button inside Link Anchor-with-button is invalid and harms a11y. Make the Link’s anchor the CTA. - <Link href="/apply" scroll>
- <button ref={registerButtonRef}>
- <span>지금 바로 신청하기</span>
- <span>
- <ChevronRight strokeWidth={1} />
- </span>
- </button>
- </Link>
+ <Link href="/apply" scroll>
+ <span>지금 바로 신청하기</span>
+ <span>
+ <ChevronRight strokeWidth={1} />
+ </span>
+ </Link>Pair with the SCSS change that styles 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| <footer className={styles.yt_hero_section__footer}> | ||||||||||||||||||||||||||||||
| <ChevronDown size={30} /> | ||||||||||||||||||||||||||||||
| </footer> | ||||||||||||||||||||||||||||||
|
Comment on lines
+50
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CSS module class not defined
- <footer className={styles.yt_hero_section__footer}>
+ <footer>
<ChevronDown size={30} />
</footer>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| </section> | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Align styles with Link-as-anchor (not button) and improve a11y focus
The CTA is an anchor via Next.js Link; style the anchor instead of a button and add focus-visible.
📝 Committable suggestion