Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 15 additions & 27 deletions apps/landing/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
import { useRef } from 'react';
import './App.css';
import HeroSection from './components/HeroSection';
import FeatureBookmarkSection from './components/FeatureBookmarkSection';
import FeatureReminderSection from './components/FeatureReminderSection';
import FeatureRewardSection from './components/FeatureRewardSection';
import FinalCTASection from './components/FinalCTASection';
import Header from './components/Header';
import Contents from './components/contents/Contents';
import { useKeyboardScroll } from './hooks/useKeyboardScroll';

function App() {
return (
<div className="h-dvh snap-y snap-mandatory overflow-y-scroll scroll-smooth">
<Header />

{/* 각 섹션들 */}
<section className="h-dvh snap-start">
<HeroSection />
</section>

<section className="h-dvh snap-start" id="bookmark-section">
<FeatureBookmarkSection />
</section>
const scrollRef = useRef<HTMLDivElement | null>(null);

<section className="h-dvh snap-start">
<FeatureReminderSection />
</section>
useKeyboardScroll(scrollRef);

<section className="h-dvh snap-start">
<FeatureRewardSection />
</section>

<section className="h-dvh snap-start">
<FinalCTASection />
</section>
return (
<div
ref={scrollRef}
className="h-dvh snap-y snap-mandatory overflow-y-scroll scroll-smooth outline-none"
tabIndex={-1}
>
<Header />
<main>
<Contents />
</main>
</div>
);
}
Expand Down
3,255 changes: 3,254 additions & 1 deletion apps/landing/src/assets/1_landingmain.json

Large diffs are not rendered by default.

298 changes: 0 additions & 298 deletions apps/landing/src/assets/2_bookmark.json

This file was deleted.

1 change: 0 additions & 1 deletion apps/landing/src/assets/3_bell.json

This file was deleted.

1 change: 0 additions & 1 deletion apps/landing/src/assets/4_up.json

This file was deleted.

886 changes: 885 additions & 1 deletion apps/landing/src/assets/5_chippiface.json

Large diffs are not rendered by default.

Binary file added apps/landing/src/assets/Bookmark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions apps/landing/src/assets/Dotori.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 117 additions & 0 deletions apps/landing/src/assets/JobBookmark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
177 changes: 177 additions & 0 deletions apps/landing/src/assets/Remind.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
26 changes: 0 additions & 26 deletions apps/landing/src/components/FeatureBookmarkSection.tsx

This file was deleted.

25 changes: 0 additions & 25 deletions apps/landing/src/components/FeatureReminderSection.tsx

This file was deleted.

27 changes: 0 additions & 27 deletions apps/landing/src/components/FeatureRewardSection.tsx

This file was deleted.

21 changes: 21 additions & 0 deletions apps/landing/src/components/contents/Contents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import FeatureBookmarkSection from './FeatureBookmarkSection';
import FeatureReminderSection from './FeatureReminderSection';
import FeatureRewardSection from './FeatureRewardSection';
import FinalCTASection from './FinalCTASection';
import HeroSection from './HeroSection';
import ShareBookmarkSection from './ShareBookmarkSection';

const Contents = () => {
return (
<>
<HeroSection />
<ShareBookmarkSection />
<FeatureBookmarkSection />
<FeatureReminderSection />
<FeatureRewardSection />
<FinalCTASection />
</>
);
};

export default Contents;
30 changes: 30 additions & 0 deletions apps/landing/src/components/contents/FeatureBookmarkSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Bookmark from '../../assets/Bookmark.png';

const FeatureBookmarkSection = () => {
return (
<section className="flex h-dvh snap-start items-center justify-center bg-white">
<div className="flex items-start gap-[7rem]">
{/* 글씨 영역 */}
<div className="flex flex-col items-start gap-[2.4rem] pl-[3rem] pt-[14rem] text-left">
<p className="head1">손쉽게 북마크하고 메모까지</p>
<p className="sub2-sb text-font-gray-3">
기억에 남기고 싶은 정보를 <br />
빠르게 북마크하세요.
</p>
</div>

{/* 이미지 영역 */}
<div className="flex items-center justify-center">
<img
src={Bookmark}
alt="Bookmark 기능 이미지"
width={447}
height={490}
/>
</div>
</div>
</section>
);
};

export default FeatureBookmarkSection;
27 changes: 27 additions & 0 deletions apps/landing/src/components/contents/FeatureReminderSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Remind from '../../assets/Remind.svg';

const FeatureReminderSection = () => {
return (
<section className="flex h-dvh snap-start items-center justify-center bg-white">
<div className="flex items-start gap-[7rem]">
{/* 이미지 영역 */}
<div className="flex items-center justify-center">
<img src={Remind} alt="Remind 기능 이미지" />
</div>
{/* 글씨 영역 */}
<div className="relative flex flex-1 flex-col items-end pr-[10rem] pt-[5rem] text-right">
<h1 className="head1 text-font-black mb-[2.4rem]">
잊지 않도록 리마인드
</h1>
<p className="sub2-sb text-font-gray-3">
첫 실행 시 리마인드 주기를 설정해 놓치지 않고
<br />
읽을 수 있어요.
</p>
</div>
</div>
</section>
);
};

export default FeatureReminderSection;
26 changes: 26 additions & 0 deletions apps/landing/src/components/contents/FeatureRewardSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Dotori from '../../assets/Dotori.svg';

const FeatureRewardSection = () => {
return (
<section className="flex h-dvh snap-start items-center justify-center bg-white">
<div className="flex items-start gap-[24rem]">
{/* 글씨 영역 */}
<div className="flex flex-col items-start gap-[2.4rem] pl-[3rem] pt-[14rem] text-left">
<h1 className="head1">도토리 보상 루프</h1>
<p className="sub2-sb text-font-gray-3">
내가 저장했던 지식을 활용할 때마다
<br />
도토리를 모아 성장해보세요.
</p>
</div>

{/* 이미지 영역 */}
<div className="flex items-center justify-center">
<img src={Dotori} alt="도토리 수집 보상 이미지" />
</div>
</div>
</section>
);
};

export default FeatureRewardSection;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button, sendGAEvent } from '@pinback/design-system/ui';
import Lottie from 'lottie-react';
import Chippiface from '../assets/5_chippiface.json';
import { sendGAEvent, Button } from '@pinback/design-system/ui';
import Footer from './Footer';
import Chippiface from '../../assets/5_chippiface.json';
import Footer from '../Footer';
const FinalCTASection = () => {
const handleInstallClick = () => {
sendGAEvent('landing', 'landing', '2-landing-bottomBtn');
Expand All @@ -10,7 +10,7 @@ const FinalCTASection = () => {
};

return (
<div className="relative flex h-dvh flex-col">
<div className="relative flex h-dvh snap-start flex-col">
<section className="bg-white-bg mt-[10.3rem] flex w-full flex-col items-center justify-center gap-[2rem] overflow-hidden px-[17.2rem] text-center">
<Lottie
animationData={Chippiface}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useEffect } from 'react';
import { Button } from '@pinback/design-system/ui';
import landingmain from '../assets/1_landingmain.json';
import Lottie from 'lottie-react';
import landing_bell from '../assets/landing_bell.svg';
import landing_icon from '../assets/landing_icon.svg';
import { useEffect } from 'react';
import landingmain from '../../assets/1_landingmain.json';
import landing_bell from '../../assets/landing_bell.svg';
import landing_icon from '../../assets/landing_icon.svg';

const floatAnimationStyle = `
@keyframes floatY {
Expand All @@ -26,7 +26,7 @@ const HeroSection = () => {
}, []);

return (
<section className="bg-white-bg flex h-dvh w-full flex-col items-center justify-center px-[17.2rem] text-center">
<section className="bg-white-bg flex h-dvh w-full snap-start flex-col items-center justify-center px-[17.2rem] text-center">
<div className="flex flex-col items-center justify-center">
<div className="flex flex-row flex-nowrap items-end justify-center gap-[3rem]">
<img src={landing_icon} alt="Landing Icon" />
Expand Down
31 changes: 31 additions & 0 deletions apps/landing/src/components/contents/ShareBookmarkSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import JobBookmark from '../../assets/JobBookmark.svg';

const ShareBookmarkSection = () => {
return (
<section className="flex h-dvh snap-start items-center justify-center bg-white">
<div className="flex items-start gap-[7rem]">
{/* 이미지 영역 */}
<div className="flex items-center justify-center">
<img src={JobBookmark} alt="북마크 공유 기능 이미지" />
</div>
{/* 글씨 영역 */}
<div className="relative flex flex-1 flex-col items-end pr-[10rem] pt-[5rem] text-right">
<div className="bg-main500 sub3-sb text-white-bg inline-flex items-center gap-[0.8rem] rounded-full px-[1.6rem] py-[0.8rem]">
<span>새 기능이 출시됐어요!</span>
</div>

<h1 className="head1 text-font-black mb-[2.4rem] mt-[1.6rem]">
나와 같은 IT 분야 사람들이
<br />
저장한 글을 확인해요
</h1>
<p className="sub2-sb text-font-gray-3">
유용한 정보나 아티클을 쉽게 추천받을 수 있어요
</p>
</div>
</div>
</section>
);
};

export default ShareBookmarkSection;
Empty file removed apps/landing/src/hooks/.gitkeep
Empty file.
41 changes: 41 additions & 0 deletions apps/landing/src/hooks/useKeyboardScroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { RefObject, useEffect } from 'react';

export const useKeyboardScroll = <T extends HTMLElement>(
scrollRef: RefObject<T | null>
) => {
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const container = scrollRef.current;
if (!container) return;

const activeElement = document.activeElement as HTMLElement;
const isInputFocused =
['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'A'].includes(
activeElement.tagName
) || activeElement.isContentEditable;

if (isInputFocused) return;

const pageHeight = window.innerHeight;

switch (e.key) {
case 'ArrowDown':
case 'PageDown':
case ' ':
e.preventDefault();
container.scrollBy({ top: pageHeight, behavior: 'smooth' });
break;
case 'ArrowUp':
case 'PageUp':
e.preventDefault();
container.scrollBy({ top: -pageHeight, behavior: 'smooth' });
Comment on lines +19 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Shift+Space와 반복 입력을 처리하지 않아 페이지 전환이 과하게 발생할 수 있습니다.

지금 구현은 Space를 항상 아래로만 보내서 기본 브라우저 동작인 Shift+Space 위로 이동을 깨고, keydown 반복도 그대로 받아서 키를 길게 누르면 여러 섹션이 연속으로 넘어갑니다. PR 목표가 “브라우저 표준 동작 + 한 페이지씩 전환”이라면 여기서 방향과 반복을 같이 제어해야 합니다.

🔧 제안 수정안
-      const pageHeight = window.innerHeight;
+      const pageHeight = container.clientHeight;
+      if (e.repeat) return;

       switch (e.key) {
         case 'ArrowDown':
         case 'PageDown':
         case ' ':
           e.preventDefault();
-          container.scrollBy({ top: pageHeight, behavior: 'smooth' });
+          container.scrollBy({
+            top: e.key === ' ' && e.shiftKey ? -pageHeight : pageHeight,
+            behavior: 'smooth',
+          });
           break;
         case 'ArrowUp':
         case 'PageUp':
           e.preventDefault();
           container.scrollBy({ top: -pageHeight, behavior: 'smooth' });
           break;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const pageHeight = window.innerHeight;
switch (e.key) {
case 'ArrowDown':
case 'PageDown':
case ' ':
e.preventDefault();
container.scrollBy({ top: pageHeight, behavior: 'smooth' });
break;
case 'ArrowUp':
case 'PageUp':
e.preventDefault();
container.scrollBy({ top: -pageHeight, behavior: 'smooth' });
const pageHeight = container.clientHeight;
if (e.repeat) return;
switch (e.key) {
case 'ArrowDown':
case 'PageDown':
case ' ':
e.preventDefault();
container.scrollBy({
top: e.key === ' ' && e.shiftKey ? -pageHeight : pageHeight,
behavior: 'smooth',
});
break;
case 'ArrowUp':
case 'PageUp':
e.preventDefault();
container.scrollBy({ top: -pageHeight, behavior: 'smooth' });
break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/landing/src/hooks/useKeyboardScroll.ts` around lines 20 - 32, 현재 핸들러는
Space를 항상 아래로만 처리하고 키 반복을 막지 않아 Shift+Space의 기본 위로 이동을 깨고 길게 누르면 여러 섹션이 연속
전환됩니다; 수정 방법은 useKeyboardScroll 내에서 key 이벤트 처리부(e.key cases)를 변경해 Space(' ')일 때
e.shiftKey가 true이면 위로(scrollBy top: -pageHeight) 아니면 아래로(scrollBy top:
pageHeight) 이동하도록 분기하고, 반복 키 입력을 막기 위해 핸들러 초기에 e.repeat를 검사해 true면 바로 반환하거나 반복 시
동작을 무시하도록 하며, preventDefault는 실제로 스크롤 동작을 수행할 때만 호출하게 하십시오 (참조: e.key,
e.shiftKey, e.repeat, container.scrollBy, pageHeight).

break;
default:
break;
}
};

window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [scrollRef]);
};
Loading