diff --git a/app/entities/common/Footer.tsx b/app/entities/common/Footer.tsx index 3afed5b..861c494 100644 --- a/app/entities/common/Footer.tsx +++ b/app/entities/common/Footer.tsx @@ -39,18 +39,14 @@ const Footer = () => { }); if (response.data.success) { - toast.success( - response.data.message || '인증 이메일이 발송되었습니다.' - ); + toast.success(response.data.message || '인증 이메일이 발송되었습니다.'); setIsSubmitted(true); setNickname(''); setEmail(''); } } catch (error) { if (axios.isAxiosError(error) && error.response) { - toast.error( - error.response.data.error || '구독 신청에 실패했습니다.' - ); + toast.error(error.response.data.error || '구독 신청에 실패했습니다.'); } else { toast.error('구독 신청 중 오류가 발생했습니다.'); } @@ -71,8 +67,9 @@ const Footer = () => {
BLOG
-

- a developer who never stops growing. +

+ 개발과 기술에 대한 이야기를 공유하는 공간입니다.
+ 문제 해결과 성장의 기록을 만듭니다.

diff --git a/app/entities/common/SectionHeading.tsx b/app/entities/common/SectionHeading.tsx new file mode 100644 index 0000000..2148928 --- /dev/null +++ b/app/entities/common/SectionHeading.tsx @@ -0,0 +1,16 @@ +interface SectionHeadingProps { + title: string; +} + +const SectionHeading = ({ title }: SectionHeadingProps) => { + return ( +
+

+ {title} +

+
+
+ ); +}; + +export default SectionHeading; diff --git a/app/entities/common/Toast/Toast.tsx b/app/entities/common/Toast/Toast.tsx index 1de0308..349c430 100644 --- a/app/entities/common/Toast/Toast.tsx +++ b/app/entities/common/Toast/Toast.tsx @@ -1,33 +1,42 @@ -import { CiCircleCheck, CiCircleRemove } from 'react-icons/ci'; +import { CiCircleCheck, CiCircleRemove, CiMail } from 'react-icons/ci'; + +type ToastType = 'success' | 'error' | 'info'; interface ToastProps { message: string; - type: 'success' | 'error'; + title?: string; + type: ToastType; removeToast: () => void; } -const Toast = ({ message, type, removeToast }: ToastProps) => { - const iconRender = (type: 'success' | 'error') => { - if (type === 'success') { - return ; - } else { - return ; - } - }; +const iconMap: Record = { + success: , + error: , + info: , +}; + +const Toast = ({ message, title, type, removeToast }: ToastProps) => { return (
removeToast()} className={` transform transition-all duration-300 ease-out animate-slideUp - bg-gray-200/90 text-black px-3 py-2 rounded-lg flex items-center gap-3 - backdrop-blur-sm w-full max-w-md origin-center cursor-pointer + bg-gray-200/90 text-black px-3 py-2 rounded-lg flex items-center gap-3 + backdrop-blur-sm w-full max-w-md origin-center cursor-pointer hover:bg-gray-300/90 hover:shadow-lg `} >
- {iconRender(type)} + {iconMap[type]} +
+
+ {title && ( +

{title}

+ )} +

+ {message} +

-

{message}

); }; diff --git a/app/entities/common/Toast/ToastProvider.tsx b/app/entities/common/Toast/ToastProvider.tsx index e7a2e6e..a12b36c 100644 --- a/app/entities/common/Toast/ToastProvider.tsx +++ b/app/entities/common/Toast/ToastProvider.tsx @@ -5,7 +5,8 @@ import useToastStore from '@/app/stores/useToastStore'; interface Toast { id: number; message: string; - type: 'success' | 'error'; + title?: string; + type: 'success' | 'error' | 'info'; } const ToastProvider = () => { @@ -24,6 +25,7 @@ const ToastProvider = () => { key={toast.id} removeToast={() => removeToast(toast.id)} message={toast.message} + title={toast.title} type={toast.type} /> ); diff --git a/app/entities/portfolio/Carousel.tsx b/app/entities/portfolio/Carousel.tsx index 833d4ff..238f927 100644 --- a/app/entities/portfolio/Carousel.tsx +++ b/app/entities/portfolio/Carousel.tsx @@ -175,7 +175,7 @@ const Carousel = ({ slides }: CarouselProps) => { ] as ReactElement, { hideTags: true, - hoverEffect: false, + } )}
@@ -197,7 +197,7 @@ const Carousel = ({ slides }: CarouselProps) => { ] as ReactElement, { hideTags: true, - hoverEffect: false, + } )} diff --git a/app/entities/portfolio/PortfolioPreview.tsx b/app/entities/portfolio/PortfolioPreview.tsx index 0f48b1b..caa8aaf 100644 --- a/app/entities/portfolio/PortfolioPreview.tsx +++ b/app/entities/portfolio/PortfolioPreview.tsx @@ -1,178 +1,67 @@ 'use client'; import Image from 'next/image'; import Link from 'next/link'; -import { useState } from 'react'; import { FaGithub, FaGlobe } from 'react-icons/fa'; +import { MdArrowForward } from 'react-icons/md'; import { Project } from '@/app/types/Portfolio'; interface PortfolioPreviewProps { project: Project; hideTags?: boolean; - hoverEffect?: boolean; } -const PortfolioPreview = ({ - project, - hideTags, - hoverEffect = true, -}: PortfolioPreviewProps) => { - const [isHovered, setIsHovered] = useState(false); - const [isTouched, setIsTouched] = useState(false); - - const handleTouchStart = () => { - if (hoverEffect) { - setIsTouched(true); - } - }; - - const handleTouchEnd = () => { - if (hoverEffect) { - setTimeout(() => setIsTouched(false), 3000); - } - }; - - const showOverlay = hoverEffect && (isHovered || isTouched); - +const PortfolioPreview = ({ project, hideTags }: PortfolioPreviewProps) => { return ( -
setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > -
- {`${project.title} - - {/* 데스크톱 호버 오버레이 */} - - - {/* 모바일 터치 오버레이 */} -
-
- {project.demoUrl && ( - - - - )} - {project.githubUrl && ( - - - - )} - {project.slug && ( - - - - )} -
-
-
- -
+
+ {/* 이미지 영역 */} +
-

- {project.title} -

+ {`${project.title} -

- {project.description} -

+
- {!hideTags && project.tags && project.tags.length > 0 && ( -
- {project.tags.map((tag, index) => ( - - {tag} - - ))} -
- )} + {/* 정보 + 버튼 영역 */} +
+ {/* 텍스트 정보 */} +
+ +

+ {project.title} +

+ +

+ {project.description} +

- {/* 모바일 하단 액션 버튼들 */} -
+ {!hideTags && project.tags && project.tags.length > 0 && ( +
+ {project.tags.map((tag, index) => ( + + {tag} + + ))} +
+ )} +
+
{project.demoUrl && ( - @@ -183,9 +72,10 @@ const PortfolioPreview = ({ href={project.githubUrl} target="_blank" rel="noopener noreferrer" - className="flex-1" + className="flex-1 md:flex-none" + title="GitHub 코드 보기" > - @@ -195,9 +85,11 @@ const PortfolioPreview = ({ - diff --git a/app/entities/portfolio/PortfolioStone.tsx b/app/entities/portfolio/PortfolioStone.tsx index e984598..1f9e76e 100644 --- a/app/entities/portfolio/PortfolioStone.tsx +++ b/app/entities/portfolio/PortfolioStone.tsx @@ -69,11 +69,11 @@ const PortfolioStone = ({
-
-

+
+

{project.title}

-

+

{project.description}

@@ -82,7 +82,7 @@ const PortfolioStone = ({ {project.tags.slice(0, 3).map((tag, tagIndex) => ( {tag} @@ -94,7 +94,7 @@ const PortfolioStone = ({ {project.demoUrl && ( 배포 @@ -103,7 +103,7 @@ const PortfolioStone = ({ {project.githubUrl && ( 코드 @@ -112,7 +112,7 @@ const PortfolioStone = ({ {project.slug && ( 자세히 diff --git a/app/entities/portfolio/PortfolioStoneGrid.tsx b/app/entities/portfolio/PortfolioStoneGrid.tsx index 71b9074..8bf0268 100644 --- a/app/entities/portfolio/PortfolioStoneGrid.tsx +++ b/app/entities/portfolio/PortfolioStoneGrid.tsx @@ -8,12 +8,12 @@ const PortfolioStoneGrid = ({ projects }: { projects: Project[] }) => { const [windowWidth, setWindowWidth] = useState(1024); // 기본값 설정 const pastelColors = [ - 'bg-emerald-200', - 'bg-blue-200', - 'bg-amber-200', - 'bg-purple-200', - 'bg-teal-200', - 'bg-rose-200', + 'bg-emerald-200 dark:bg-emerald-900', + 'bg-blue-200 dark:bg-blue-900', + 'bg-amber-200 dark:bg-amber-900', + 'bg-purple-200 dark:bg-purple-900', + 'bg-teal-200 dark:bg-teal-900', + 'bg-rose-200 dark:bg-rose-900', ]; // 클라이언트 사이드에서만 window 크기 감지 @@ -46,11 +46,11 @@ const PortfolioStoneGrid = ({ projects }: { projects: Project[] }) => {

포트폴리오

-

+

참여한 프로젝트 모아보기

{!projects || projects.length === 0 ? ( -

프로젝트가 없습니다.

+

프로젝트가 없습니다.

) : null}
{projects.map((project, index) => { @@ -101,7 +101,7 @@ const PortfolioStoneGrid = ({ projects }: { projects: Project[] }) => { })}
-

+

프로젝트 카드에 마우스를 올려보세요 - 주변 카드들이 밀려납니다

diff --git a/app/entities/post/detail/SubscribeToast.tsx b/app/entities/post/detail/SubscribeToast.tsx new file mode 100644 index 0000000..2e1402e --- /dev/null +++ b/app/entities/post/detail/SubscribeToast.tsx @@ -0,0 +1,43 @@ +'use client'; + +import { useEffect, useRef } from 'react'; +import useToast from '@/app/hooks/useToast'; + +const STORAGE_KEY = 'subscribe-toast-shown'; + +const SubscribeToast = () => { + const toast = useToast(); + const sentinelRef = useRef(null); + + useEffect(() => { + if (sessionStorage.getItem(STORAGE_KEY)) return; + + const sentinel = sentinelRef.current; + if (!sentinel) return; + + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + sessionStorage.setItem(STORAGE_KEY, 'true'); + toast.info( + '하단에서 이메일 구독을 신청하면 새 글이 올라올 때 알림을 받을 수 있어요.', + { + title: '글을 읽어주셔서 감사합니다!', + duration: 10000, + } + ); + observer.disconnect(); + } + }, + { threshold: 0.1 } + ); + + observer.observe(sentinel); + + return () => observer.disconnect(); + }, [toast]); + + return
; +}; + +export default SubscribeToast; diff --git a/app/entities/profile/AboutMe.tsx b/app/entities/profile/AboutMe.tsx index 099365a..4199d0f 100644 --- a/app/entities/profile/AboutMe.tsx +++ b/app/entities/profile/AboutMe.tsx @@ -4,11 +4,11 @@ import { githubLink, linkedinLink } from '@/app/lib/constants/landingPageData'; const AboutMe = () => { return ( -
+
-
+
-
+
{ priority={true} src={'/images/profile/profile.jpg'} alt="About image" - className="absolute inset-0 object-cover w-full h-full bg-gray-500 transition-opacity duration-700 group-hover:opacity-0" + className="absolute inset-0 object-cover w-full h-full bg-gray-500 transition-opacity duration-700 group-hover/duck:opacity-0" />
+
+
+ 저는 커피☕와 사진 📸을 좋아하는 개발자입니다~ +
+
+
-

+

About Me

-

- 프론트엔드 개발자로서 React, Next.js, TypeScript를 주로 사용합니다. - 항상 사용자 입장에서 생각하고, 성능 최적화에 관심이 많으며, 지속적인 +

+ Software Engineer로서 React, TypeScript를 주로 사용합니다. 항상 + 확장성에 대해서 고민하고, 성능 최적화에 관심이 많으며, 지속적인 학습과 성장을 추구합니다.

diff --git a/app/entities/profile/Experience.tsx b/app/entities/profile/Experience.tsx new file mode 100644 index 0000000..84afc8f --- /dev/null +++ b/app/entities/profile/Experience.tsx @@ -0,0 +1,72 @@ +import { HiOutlineAcademicCap, HiOutlineBriefcase } from 'react-icons/hi'; +import SectionHeading from '../common/SectionHeading'; + +type ExperienceType = 'work' | 'education'; + +interface ExperienceItem { + company: string; + role: string; + period: string; + type: ExperienceType; + current?: boolean; +} + +const experiences: ExperienceItem[] = [ + { + company: 'CJ올리브영', + role: 'AI플랫폼팀 Intern', + period: '2025.09 ~', + type: 'work', + current: true, + }, + { + company: '네이버 부스트캠프 9기 웹·모바일', + role: '웹풀스택 챌린지, 멤버십 수료', + period: '2024.07 ~ 2024.12', + type: 'education', + }, +]; + +const Experience = () => { + return ( +
+ +
+ {experiences.map((exp) => ( +
+
+ {exp.type === 'work' ? ( + + ) : ( + + )} +
+
+
+

+ {exp.company} +

+ {exp.current && ( + + 현재 + + )} +
+

+ {exp.role} +

+
+ + {exp.period} + +
+ ))} +
+
+ ); +}; + +export default Experience; diff --git a/app/entities/profile/FeaturedProjects.tsx b/app/entities/profile/FeaturedProjects.tsx index 90cfc1f..1cdfe06 100644 --- a/app/entities/profile/FeaturedProjects.tsx +++ b/app/entities/profile/FeaturedProjects.tsx @@ -1,4 +1,5 @@ import { Project } from '@/app/types/Portfolio'; +import SectionHeading from '../common/SectionHeading'; import PortfolioPreview from '../portfolio/PortfolioPreview'; interface FeaturedProjectsProps { @@ -7,14 +8,9 @@ interface FeaturedProjectsProps { const FeaturedProjects = ({ projects }: FeaturedProjectsProps) => { return ( -
-
-

- Featured Projects -

-
-
-
+
+ +
{projects.map((project) => { return ; })} diff --git a/app/entities/profile/HeroBanner.tsx b/app/entities/profile/HeroBanner.tsx index 0a2a555..2f59b0d 100644 --- a/app/entities/profile/HeroBanner.tsx +++ b/app/entities/profile/HeroBanner.tsx @@ -1,10 +1,11 @@ import Image from 'next/image'; +import { RiDoubleQuotesR } from 'react-icons/ri'; import DecryptedText from '../bits/DecryptedText'; const HeroBanner = () => { return (
-
+
{ className="object-cover bg-gray-100 w-full h-full transition-transform duration-700 group-hover:scale-105" />
-
+
-

+

{ encryptedClassName="text-neutral-200/90" />

-

+

Jeongwoo Seo

@@ -46,14 +47,20 @@ const HeroBanner = () => {
- -

- 안녕하세요, 서정우입니다. -

-

- 깔끔한 코드 작성에 중점을 두고, 확장성에 대해 고민하며 멈추지 않는 - 기술의 변화를 즐깁니다. -

+
+
+ +
+
+

+ 안녕하세요, 서정우입니다. +

+

+ 깔끔한 코드 작성에 중점을 두고, 확장성에 대해 고민하며 멈추지 않는 + 기술의 변화를 즐깁니다. +

+
+
); }; diff --git a/app/entities/profile/LatestArticles.tsx b/app/entities/profile/LatestArticles.tsx index 75dd3f5..ca1bf09 100644 --- a/app/entities/profile/LatestArticles.tsx +++ b/app/entities/profile/LatestArticles.tsx @@ -3,6 +3,7 @@ import Image from 'next/image'; import Link from 'next/link'; import { Post } from '@/app/types/Post'; import ErrorBox from '../common/Error/ErrorBox'; +import SectionHeading from '../common/SectionHeading'; import Skeleton from '../common/Skeleton/Skeleton'; interface LatestArticlesProps { @@ -17,13 +18,8 @@ const LatestArticles = ({ error = null, }: LatestArticlesProps) => { return ( -
-
-

- Latest Articles -

-
-
+
+
{loading ? ( <> @@ -39,7 +35,7 @@ const LatestArticles = ({ key={post._id} className="group cursor-pointer bg-gradient-to-br from-gray-50 to-gray-100 dark:from-primary-rich rounded-2xl overflow-hidden shadow transition-all duration-300 border border-gray-200 dark:border-gray-700 hover:scale-[1.02]" > -
+
-
+

{post.title}

@@ -75,7 +71,7 @@ const LatestArticleSkeleton = () => { 'flex flex-col gap-3 bg-gradient-to-br from-gray-50 to-gray-100 dark:from-primary-rich rounded-2xl overflow-hidden shadow-2xl border border-gray-200 dark:border-gray-700' } > - +
diff --git a/app/favicon.ico b/app/favicon.ico index 5e7df3c..b7555a8 100644 Binary files a/app/favicon.ico and b/app/favicon.ico differ diff --git a/app/hooks/useToast.ts b/app/hooks/useToast.ts index b8c4662..f0583e8 100644 --- a/app/hooks/useToast.ts +++ b/app/hooks/useToast.ts @@ -1,25 +1,33 @@ import { useCallback, useEffect, useRef } from 'react'; import useToastStore from '@/app/stores/useToastStore'; +const DEFAULT_DURATION = 5000; + +interface ToastOptions { + title?: string; + duration?: number; +} + const useToast = () => { const { createToast, removeToast } = useToastStore(); - const DURATION = 5000; const timerIDs = useRef[]>([]); const toast = useCallback( - (message: string, type: 'success' | 'error') => { + (message: string, type: 'success' | 'error' | 'info', options?: ToastOptions) => { + const duration = options?.duration ?? DEFAULT_DURATION; const newToast = { id: new Date().getTime() + Math.floor(Math.random() * 200), message, + title: options?.title, type, - duration: DURATION || 5000, + duration, }; createToast(newToast); const id = setTimeout(() => { removeToast(newToast.id); timerIDs.current = timerIDs.current.filter((timerId) => timerId !== id); - }, DURATION); + }, duration); }, [createToast, removeToast] ); @@ -31,20 +39,27 @@ const useToast = () => { }, []); const success = useCallback( - (message: string) => { - toast(message, 'success'); + (message: string, options?: ToastOptions) => { + toast(message, 'success', options); }, [toast] ); const error = useCallback( - (message: string) => { - toast(message, 'error'); + (message: string, options?: ToastOptions) => { + toast(message, 'error', options); + }, + [toast] + ); + + const info = useCallback( + (message: string, options?: ToastOptions) => { + toast(message, 'info', options); }, [toast] ); - return { success, error }; + return { success, error, info }; }; export default useToast; diff --git a/app/layout.tsx b/app/layout.tsx index 226df5a..935e7b2 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -12,6 +12,22 @@ export const metadata: Metadata = { description: '문제 해결 경험과 개발 지식을 공유하는 개발 블로그입니다.', icons: { icon: '/favicon.ico', + apple: '/assets/apple-touch-icon.png', + shortcut: '/favicon-16.png', + other: [ + { + rel: 'icon', + url: '/favicon-32.png', + sizes: '32x32', + type: 'image/png', + }, + { + rel: 'icon', + url: '/favicon-16.png', + sizes: '16x16', + type: 'image/png', + }, + ], }, keywords: [ 'ShipFriend', diff --git a/app/lib/constants/landingPageData.ts b/app/lib/constants/landingPageData.ts index 618fd8a..59d802f 100644 --- a/app/lib/constants/landingPageData.ts +++ b/app/lib/constants/landingPageData.ts @@ -7,9 +7,10 @@ export const githubLink = 'https://github.com/ShipFriend0516'; export const projects: Project[] = [ { title: 'PREVIEW', - description: 'WebRTC 기반 화상 면접 스터디 플랫폼', + description: + '네이버 부스트캠프 9기 최종 프로젝트 WebRTC 기반 화상 면접 스터디 플랫폼', image: '/images/logo/preview-logo.png', - tags: ['React', 'WebRTC', 'Fullstack'], + tags: ['React', 'WebRTC'], githubUrl: 'https://github.com/boostcampwm-2024/web27-Preview', slug: 'preview', }, @@ -17,7 +18,7 @@ export const projects: Project[] = [ title: 'ShipFriend TechBlog', description: '개발 관련 글을 작성하고 공유하는 기술 블로그 플랫폼', image: '/images/logo/shipfriend-logo.webp', - tags: ['Next.js', 'TypeScript', 'MongoDB'], + tags: ['Next.js', 'TypeScript', 'MongoDB', 'Fullstack'], demoUrl: 'https://shipfriend.dev', githubUrl: 'https://github.com/ShipFriend0516/TechBlog', slug: 'shipfriend', diff --git a/app/page.tsx b/app/page.tsx index b76b27e..99b1264 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,13 +1,12 @@ 'use client'; -import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; import useDataFetch from '@/app/hooks/common/useDataFetch'; -import useShortcut from '@/app/hooks/common/useShortcut'; import useFingerprint from '@/app/hooks/useFingerprint'; import useToast from '@/app/hooks/useToast'; import { projects } from '@/app/lib/constants/landingPageData'; import { Post } from '@/app/types/Post'; import AboutMe from './entities/profile/AboutMe'; +import Experience from './entities/profile/Experience'; import FeaturedProjects from './entities/profile/FeaturedProjects'; import HeroBanner from './entities/profile/HeroBanner'; import LatestArticles from './entities/profile/LatestArticles'; @@ -16,7 +15,6 @@ import MoreExplore from './entities/profile/MoreExplore'; export default function Home() { const { fingerprint } = useFingerprint(); const toast = useToast(); - const router = useRouter(); const fetchConfig = { method: 'GET' as const, @@ -38,23 +36,11 @@ export default function Home() { } }, [fingerprint]); - const goToWritePage = () => { - toast.success('글쓰기 페이지로 이동합니다...'); - router.push('/admin/write'); - }; - - const goToPostsPage = () => { - toast.success('모든 글 목록 페이지로 이동합니다...'); - router.push('/posts'); - }; - - useShortcut(goToWritePage, ['Alt', 'N'], true); - useShortcut(goToPostsPage, ['Ctrl', ';'], true); - return ( -
+
+ diff --git a/app/posts/[slug]/page.tsx b/app/posts/[slug]/page.tsx index 96f2944..7324240 100644 --- a/app/posts/[slug]/page.tsx +++ b/app/posts/[slug]/page.tsx @@ -5,6 +5,7 @@ import PostActionSection from '@/app/entities/post/detail/PostActionSection'; import PostDetail from '@/app/entities/post/detail/PostDetail'; import PostJSONLd from '@/app/entities/post/detail/PostJSONLd'; import PostRecommendation from '@/app/entities/post/detail/PostRecommendation'; +import SubscribeToast from '@/app/entities/post/detail/SubscribeToast'; import dbConnect from '@/app/lib/dbConnect'; import Post from '@/app/models/Post'; @@ -83,6 +84,7 @@ const BlogDetailPage = async ({ params }: { params: { slug: string } }) => { currentPostId={post?._id} seriesId={post?.seriesId} /> +
diff --git a/app/stores/useToastStore.ts b/app/stores/useToastStore.ts index d10645e..3e229bb 100644 --- a/app/stores/useToastStore.ts +++ b/app/stores/useToastStore.ts @@ -1,9 +1,13 @@ import { create } from "zustand"; +type ToastType = "success" | "error" | "info"; + interface Toast { id: number; message: string; - type: "success" | "error"; + title?: string; + type: ToastType; + duration?: number; } interface ToastState { diff --git a/public/assets/android-chrome-192x192.png b/public/assets/android-chrome-192x192.png new file mode 100644 index 0000000..792aeba Binary files /dev/null and b/public/assets/android-chrome-192x192.png differ diff --git a/public/assets/android-chrome-512x512.png b/public/assets/android-chrome-512x512.png new file mode 100644 index 0000000..2dd49d2 Binary files /dev/null and b/public/assets/android-chrome-512x512.png differ diff --git a/public/assets/apple-touch-icon.png b/public/assets/apple-touch-icon.png new file mode 100644 index 0000000..2f9986b Binary files /dev/null and b/public/assets/apple-touch-icon.png differ diff --git a/public/assets/favicon-16x16.png b/public/assets/favicon-16x16.png new file mode 100644 index 0000000..6625442 Binary files /dev/null and b/public/assets/favicon-16x16.png differ diff --git a/public/assets/favicon-32x32.png b/public/assets/favicon-32x32.png new file mode 100644 index 0000000..e8986cb Binary files /dev/null and b/public/assets/favicon-32x32.png differ diff --git a/public/assets/favicon.ico b/public/assets/favicon.ico new file mode 100644 index 0000000..b7555a8 Binary files /dev/null and b/public/assets/favicon.ico differ