diff --git a/src/content/blog/2022-03-11-firstdiary.md b/src/content/blog/2022-03-11-firstdiary.md new file mode 100644 index 0000000..353c60e --- /dev/null +++ b/src/content/blog/2022-03-11-firstdiary.md @@ -0,0 +1,20 @@ +--- +title: 일상 기록 첫 번째 이야기 +date: 2022-03-11 +tags: 일상 +excerpt: 일상 기록 첫 번째 이야기 +--- + +# 일상 기록 첫 번째 이야기 + +~~깃~~ 블로그를 만들면서 앞으로 써 내려갈 일상 디렉토리의 첫 번째 포스트를 작성해 봅니다. + +어려서부터 내 생각을 정리하거나 메모를 할 때에는 간단히 들고 다니는 메모장이나 노트에 많이 끄적여 봤는데, + +이처럼 누구나 볼 수 있는 페이지에 나의 일상을 기록하고 내 생각을 정리하는 포스트를 만든다는게 너무 어색하기만 하다 :) + +평소에 글을 쓰는걸 별로 좋아하지 않았기 때문에 글을 작성하는데에도 많은 시간이 소요되고 내용도 알차지는 못하겠지만, 점점 나의 글을 쓰면서 어제보다 발전해가는 모습을 볼 수 있지 않을까?! + +때문에 일상 포스트를 꼭 해보고 싶었다. + +~~깃~~ 블로그 일상 포스트 시작합니다. \ No newline at end of file diff --git a/src/content/blog/2022-03-13-transferring-git-storage.md b/src/content/blog/2022-03-13-transferring-git-storage.md new file mode 100644 index 0000000..1926e09 --- /dev/null +++ b/src/content/blog/2022-03-13-transferring-git-storage.md @@ -0,0 +1,20 @@ +--- +title: Transferring git storage +date: 2022-03-13 +tags: 일상 +excerpt: 깃 저장소 변경하면서 잠깐 쉬는 타임에 끄적여보는 일상 기록 +--- + +# 일상 기록 - 깃 저장소 변경 + +기존 학부시절부터 사용했던 깃 저장소([git_storage_gahu](https://github.com/gahu))에서 했었던 프로젝트와 공부 했던 기록들을 정리해서 +새로운 깃 저장소([git_storage_gahusb](https://github.com/gahusb))로 변경하였다. + +기존의 깃이 공부하면서 썻던 내용들이라... 정리도 되어 있지 않았고 그래서 뭔가 지저분하게 되어 있는데 어디서부터 손을 대야 할지 감이 잡히지 않았다. + +때문에 새로운 [저장소](https://github.com/gahusb)를 만들고, github io를 이용하여 나의 [블로그](https://gahusb.github.io/)도 작성하면서 +기존 프로젝트들의 내용 정리와 블로그 정리 두가지를 같이 하면서 옮기게 되었다. + +블로그 작업 준비도 하면서 기존 프로젝트들을 옮기고 정리한다는게 쉬운 일은 아니지만 +지금 목표로 하고 있는 일이 있기 때문에 시작을 할 수 있었던것 같고, +무엇보다도 새로 산 Macbook M1 Max를 잘 활용해 볼 수 있는 기회가 되지 않을까 생각하면서 작업하고 있다. diff --git a/src/content/blog/2022-03-15-samgyub.md b/src/content/blog/2022-03-15-samgyub.md new file mode 100644 index 0000000..e7bd647 --- /dev/null +++ b/src/content/blog/2022-03-15-samgyub.md @@ -0,0 +1,18 @@ +--- +title: 오랜만에 삼겹살집! +date: 2022-03-15 +tags: 일상 +excerpt: 오랜만에 삼겹살집에 가서 먹부림한 하루 기록. +--- + +# 일상 기록 - 삼겹살 먹부림 + +오늘 오랜만에 집 근처에 있는 삼겹살집에 갔다. +퇴근 후 평소와는 다른 방향으로 걸어가면서 삼겹살이 먹고 싶다던 승토리와 함께 돼지고기 뿌시러 갔다. + +해바라기집이라는 이곳은 이 집에 온지 얼마 안 지나서 방문하고 이번이 두 번째 방문이다. +![image](/assets/img/daily/20220315_191531.jpg) + +돼지 반마리를 시켰는데, 위에 사진 처럼 두꺼운 삼겹살과 항정살, 갈비살, 목살 등이 500g 나오는데 우리 둘이서는 이정도가 딱 정당히 배부르고 좋은 것 같다. + +최근에 감기 기운이 있어서 목도 칼칼하니 별로 좋지 않았는데 기름진 고기를 먹으며 힐링했던 시간 diff --git a/src/content/blog/2022-04-02-warehouse43.md b/src/content/blog/2022-04-02-warehouse43.md new file mode 100644 index 0000000..f6c27b9 --- /dev/null +++ b/src/content/blog/2022-04-02-warehouse43.md @@ -0,0 +1,49 @@ +--- +title: 소고기 먹부림 +date: 2022-04-02 +tags: 일상 +excerpt: 여의도 창고 43에서 오랜만에 소고기와 와인을 즐긴 기록. +--- + +# 일상 기록 - 소는 누가~~ 키우나?! + +오랜만에 승토리와 몸보신(?)을 하기 위해서 여의도에 있는 고깃집을 탐색했다. + +여의도에는 소고기 오마카세를 하는 집도 있었지만 오랜만에 소고기를 소금에 찍어서 먹어보고 싶다는 의견에 나도 적극적으로 동의했고, 오마카세가 아닌 구이집을 가기로 했다. + +약속시간, 7시 반 이라는 조금 늦은 저녁이 되어서 배고픔을 안고 여의도로 넘어왔고, 가깝지만 괜찮다고 생각되는 집으로 향했다. + +## 창고 43 본점 + +여의도 맨하탄빌딩 2층에 있는 창고 43으로 갔다. + +![image](/assets/img/daily/%EC%B0%BD%EA%B3%A043-1%20Medium.jpeg) + +입구에 가까히 갈수록 소고기 특유의 냄새가 풍겼고, 발걸음을 더 빨리해서 입성! + +예약을 하지 않았지만 다행히도 조금 늦은 시간에 가서 그런지 한 타임의 폭풍이 지나가고 난 뒤의 모습이었다. +덕분에 바로 자리를 안내 받을 수 있었다. + +가장 괜찮아보이는 한우 세트를 주문하고 함께 어울릴만한 와인도 하나 먹고 싶었다! +오늘은 FLEX 하는 날!! +무난한 평의 칠레 산 와인을 시켜보았다. + +![image](/assets/img/daily/%EC%B0%BD%EA%B3%A043-2%20Medium.jpeg) + +살짝 무게감이 있으면서도 향이 아주 좋아서 우리 둘다 만족할 수 있었다. + +그렇게 와인을 한 잔 따라놓고 가볍게 향을 즐기고 있을 때쯤! + +드디어!!! 소고기님 입장하셨다. + +![image](/assets/img/daily/%EC%B0%BD%EA%B3%A043-3%20Medium.jpeg) + +사진으로 보기에는 양이 적어 보일 순 있지만 +맞다.😥 뭔가 평소 먹었던 양보다는 확실히 적긴 했지만 마블링이 살아 있었다... + +투쁠 안심, 새우살, 투쁠 등심 이렇게 세트로 하는데 600g이었나..? +그래도 처음으로 소에서 가장 적게 나와서 귀하다는 새우살을 먹어 볼 수 있다는 생각에 들떠 있었고, 그 맛은 너무 훌륭했다.😋 + +입에 넣자마자 사라지는 마법을 느끼며 정말 괜찮은 소고기를 먹어본 것 같다. + +고기와 와인을 먹고 냉면, 깍두기 볶음밥까지 완벽하게 마무리를 지으며 배 터지게 잘 먹고서 나왔다. :) diff --git a/src/content/blog/2022-04-07-slicknlist.md b/src/content/blog/2022-04-07-slicknlist.md new file mode 100644 index 0000000..f642047 --- /dev/null +++ b/src/content/blog/2022-04-07-slicknlist.md @@ -0,0 +1,263 @@ +--- +title: 블로그 기능 추가 +date: 2022-04-07 +tags: 개발 +excerpt: slick 슬라이더, 목차, 페이지 버튼, 유튜브 임베드 추가 정리. +--- + +# 게시글 테스트 + +* toc +{:toc .large-only} + +--- + +## slick image +[Slick](https://kenwheeler.github.io/slick/) +slick 사이트에 들어가서 `get it now`를 눌러 다운로드 받아주어 slick 폴더를 `/assets/css/slick`에 복사>붙여넣기 해준다. + + +게시글 원하는 위치에 아래와 같이 넣어주면 된다. + +```html +
+
+
+
+
+ +``` + +.slick() 안의 옵션을 원하는대로 설정하여 사용 할 수 있다. + +
+
+
+
+
+ + + +그리고 scss를 수정하여 좌우로 화살표를 넣어서 표출 하는것도 가능하도록 변경 할 수 있다. +이미지는 별도로 구해서 사용하시면 됩니다. + +> assests/css/slick/slick-theme.css + +```css +.slick-prev:before +{ + content: url(left.png); +} +[dir='rtl'] .slick-prev:before +{ + content: url(right.png); +} + +.slick-next:before +{ + content: url(right.png); +} +[dir='rtl'] .slick-next:before +{ + content: url(left.png); +} +``` + +--- +## 게시글 목차 만들기 +h1 타이틀 바로 아래에 + +``` +* toc +{:toc .large-only} +``` + +를 추가하여 헤더를 기준으로 목차 생성 + +--- +## 페이지 버튼 만들기 + +> _include/components/page-button.html + +```html +
+
+ {% if page.previous.url %} + + {% endif %} +
+
+ {% if page.next.url %} + + {% endif %} +
+
+``` + +> _layouts/post.html + +```console +{% include components/page-button.html %} +``` + +About 위에 위치하여 작성자 소개 위에 위치하도록 한다. + +원하는 스타일대로 변경을 위해서 다음과 같이 작업해준다. + +> _sass/my-style.scss + +```scss +.page-control { + height: 100%; + display: flex; + align-items: center; + justify-content: space-between; +} + +.page-control > div { + max-width: 50%; +}.page-control { + height: 100%; + display: flex; + align-items: center; + justify-content: space-between; +} + +.page-control > div { + max-width: 50%; +} + +.w-btn-outline { + position: relative; + padding: 15px 30px; + border-radius: 15px; + font-family: "paybooc-Light", sans-serif; + box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2); + text-decoration: none; + font-weight: 600; + transition: 0.25s; +} + +.w-btn-outline:hover { + letter-spacing: 2px; + transform: scale(1.2); + cursor: pointer; +} + +.w-btn-outline:active { + transform: scale(1.5); +} + +.w-btn-gray-outline { + border: 3px solid #a3a1a1; + color: #6e6e6e; +} + +.w-btn-gray-outline:hover { + background-color: #a3a1a1; + color: #e3dede; +} +``` + +--- + +## 게시글에 유튜브플레이어 보기 + +> _includes/components/youtubePlayer.html + +```html + + +
+ +
+``` + +위 처럼 youtube 영상의 id를 가져와서 표출 할 수 있도록 만들고 + +```console +`{% include components/youtubePlayer.html id="{youtube ID}" %}` +``` + +게시글 원하는 위치에 youtube id를 넣어주면 된다. + +![image](https://likelion.notion.site/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F72612a68-022b-424b-8ee1-4600408d2b93%2FUntitled.png?table=block&id=b894acab-60c6-4de6-b0c2-7e0c15a7c844&spaceId=c69962b0-3951-485b-b10a-5bb29576bba8&width=1220&userId=&cache=v2) + +위와 같이 `watch?v=` 이후에 오는 id를 가져다가 사용한다. + +{% include components/youtubePlayer.html id='0TNFb5zgpbg' %} + +--- + +## 게시물 조회수 보이기 +Hits 이용 +> [Hits](https://hits.seeyoufarm.com/) + +TARGET URL에 나의 블로그 주소를 입력하고, Options에서 여러 설정들을 조정하여 내가 원하는 스타일의 hits를 만들 수 있다. 미리보기를 통해 표시되는것을 볼 수 있으니 참고해서 만들면 된다. + +그렇게 생성된 아래의 HTML Link를 복사해서 + +> _layouts/post.html + +에 넣어서 각 게시물 별로 조회수가 보이는 위치를 조정할 수 있다. + +> _includes/body/sidebar-sticky.html + +에 넣으면 사이드바는 어떤 게시물을 봐도 같이 보이는것이기 때문에 전체 조회수를 체크할 수 있다. + +만약 조회수 가장 오른쪽에 생성되는 링크모양 아이콘이 싫다면 + +> _sass/my-style.scss + +```scss +a.external::after, a::after { + display: none; +} +``` + +를 추가하여 제거 할 수 있다. + +--- + diff --git a/src/content/blog/2022-04-09-oh_codingtest.md b/src/content/blog/2022-04-09-oh_codingtest.md new file mode 100644 index 0000000..72702e2 --- /dev/null +++ b/src/content/blog/2022-04-09-oh_codingtest.md @@ -0,0 +1,24 @@ +--- +title: 코딩테스트 +date: 2022-04-09 +tags: 공부 +excerpt: 오늘의집 코딩테스트를 치르며 느낀 점을 정리한 기록. +--- + +# 코딩 테스트 기록 + +오늘의집 개발자 대규모 채용 코딩테스트 + +오늘은 오늘의집이라는 유니콘 기업에 프론트엔드 개발자 포지션으로 코딩테스트를 했다. + +문제는 3문제 180분이었고, 나는 평소대로 JAVA로 치뤘다. + +3문제 모두 문자열과 관련된 알고리즘을 사용해 푸는 문제였고, 1번 문제는 단순히 문자열에서 다음에 나오는 문자를 보고 방향전환 및 직진 거리에 대한 명령을 알려주는 프로그램이라 단순 구현력을 묻는 문제였다. + +2번 문제는 반복 문자열을 제거하는 알고리즘 문제로, 이 문제와 유사한 문제를 이전에 풀었었는데.. 이게 참 내 생각처럼 쉽게 해결되지 않아서 이 문제에만 1시간 반 가까이 소비한 것 같다. + +3번 문제는 문자열에 특정 변수를 두고 그것을 치환하는 문제였는데, HashSet을 이용해서 반복 유무 검사와 무한 반복인지를 체크하도록 하여 해결하였다. + +지금까지 알고리즘을 이렇게 꾸준히 한 경험이 많지 않아서인지 생각보다 구현에는 어려움이 없었고, 평소에 얼마나 하느냐가 크게 다가왔었다. ~~(심지어 전날 새벽까지 놀고 집중해서 할 수 있었다는게 놀라울 정도)~~ + +목표인 IT 대장급이라고 불리는 기업의 코테 올솔할 수 있는 실력을 갖출때까지 멈추지 않고 계속 할 것이다. diff --git a/src/content/blog/2022-04-25-dairyhard.md b/src/content/blog/2022-04-25-dairyhard.md new file mode 100644 index 0000000..2510dfb --- /dev/null +++ b/src/content/blog/2022-04-25-dairyhard.md @@ -0,0 +1,16 @@ +--- +title: 일상기록 - 04월25일 +date: 2022-04-25 +tags: 일상 +excerpt: 바쁜 일정 속에서 다시 루틴을 잡아가려는 마음 기록. +--- + +# 일상의 기록 - 04월25일 + +최근에는 업무도 바빠서 늦게까지 야근하고, 그러지 않은 날에는 미뤄진 약속을 가느라고 목표로 했던 1일 1commit 100일 목표에서 많이 떨어지게 된 것 같다. + +물론 채우기 위해서 중간중간 알고리즘 한 문제라도 풀어서 올리고는 있지만 예전만큼 활발하지 못한것 같아서 거의 반성의 기록을 남긴다. + +처음 1일 1commit을 목표로 했을 때에는 공부도 흥미롭고, 일도 여유가 있었기 때문에 공부에 투자하는 시간도 많았는데 그러지 못하는게 너무 불편한 상황이 되었다. + +이번주만 지나면 그래도 여유를 찾을 수 있을것 같으니 좀 더 공부하고 목표를 채워가는 일상을 기록 할 수 있도록 노력할 것이다. diff --git a/src/content/blog/2022-06-09-publicdatacontest.md b/src/content/blog/2022-06-09-publicdatacontest.md new file mode 100644 index 0000000..fb2d225 --- /dev/null +++ b/src/content/blog/2022-06-09-publicdatacontest.md @@ -0,0 +1,19 @@ +--- +title: 식의약 공공데이터 활용 공모전 준비 +date: 2022-06-09 +tags: 개발, 아이디어 +excerpt: 공공데이터 공모전 준비 과정과 아이디어 선정 기록. +--- + +# 식의약 공공데이터 활용 공모전 준비 + +이번에 같은 팀의 동료와 동기 3명이서 식의약 공공데이터 활용을 해서 웹/앱 개발을 하는 공모전에 참가하려고 한다.
+그래서 사전 Notion을 이용하여 일정 관리 및 아이디어 모집을 했고, 오늘에서 모여 최종 아이디어 회의를 시작했다. + +다양한 아이디어들 중에서 3가지 아이디어가 가장 괜찮을것 같았고, 그 중 내가 제시한 아이디어가 가장 참신성에서 괜찮다고 투표 결과가 나와서 채택하게 되었다.
+아이디어가 정해지고 나서도 활용할 공공데이터의 데이터 내용 및 배경, 기대효과, Mock-up design 등 해야 할 것들이 많이 있어서 정리하는데만 시간이 더 걸리기도 했다.
+ +퇴근 후에 다들 힘든 상태에서 진행하기도 했고, 이번 주.. 너무 바빴어서 잠도 제대로 잔 적이 없긴 하지만 오랜만에 다시 두근거리는 프로젝트를 진행하는것 같아서 기대가 된다.
+(개발하느라 고생할거 생각하면 아찔하긴 하다..)
+ +8월 초까지 개발이 어느정도 완료가 되어야 하니 스케줄 정해진대로 완료해서 좋은 성과를 냈으면 한다.
diff --git a/src/content/blog/2022-07-06-publicdatacontest.md b/src/content/blog/2022-07-06-publicdatacontest.md new file mode 100644 index 0000000..dded096 --- /dev/null +++ b/src/content/blog/2022-07-06-publicdatacontest.md @@ -0,0 +1,17 @@ +--- +title: 식의약 공공데이터 활용 공모전 1차 평가 통과 +date: 2022-07-06 +tags: 개발, 아이디어 +excerpt: 공모전 1차 통과 후 일정과 해야 할 일 정리. +--- + +# 식의약 공공데이터 활용 공모전 1차 평가 통과 + +'그린라이트'라는 장기 기증 활성화를 주제로 하는 캠페인의 이름을 따서 식의약 공공데이터 활용 공모전에 지난 6월에 참가를 했었다.
+아이디어만큼은 자신이 있었으나 마이너한듯한 분야라서 걱정을 하고 있었는데, 오늘 1차 평가 통과 메세지가 왔다!! + +67개의 팀이 참가해서 아이디어부문 8개 팀, 개발부문 4개 팀이 선정 되었는데 그 중 개발부문 한 팀으로 선정이 된 것이다!!!
+기쁨도 잠시... 2차 평가의 날이 보름정도 남은 상태라 발표자료와 앱 개발을 해야하는 상황이 온 것이다.
+공공데이터도 사용 신청도 미흡한 상태라 걱정이 되긴 하지만 팀원들과 모여서 합의하고 일정 및 업무 분담을 하기로 했다. + +이번에도 좋은 결과를 거둘 수 있는 공모전이 되었으면 좋겠다고 생각하고 있다. diff --git a/src/content/blog/2023-04-29-daily.md b/src/content/blog/2023-04-29-daily.md new file mode 100644 index 0000000..e800e6b --- /dev/null +++ b/src/content/blog/2023-04-29-daily.md @@ -0,0 +1,31 @@ +--- +title: daily diary +date: 2023-04-29 +tags: 일상 +excerpt: 오랜만에 기록을 다시 시작하며 하루를 정리한 글. +--- + +# 일상의 기록 + +오랫만에 일상의 기록 +23년에는 한 번도 기록을 하지 않았더라구.. +물론 23년에는 승토리와 처음으로 해외 여행도 다녀오고 연휴도 있었고 이직을 하기 때문에 회사일에 집중하기도 했다. +그래도 너무 오랜만에 MacBook Pro를 켜면서 켜져 있는 VScode 창을 보면서 끝내지 못한 포스팅도 있었고, 기록이 22년 12월을 마지막으로 멈춰 있는 상태를 발견하고나니 반성하자 나 자신.. + +오늘은 승토리와 함께 아침 일찍부터 낙성대역 근처 카페에 자리잡고 각자 해야 할 일을 하고서, +저녁에는 이사가야 할 집 구경도 하고 가구의 치수도 재는 등 하려고 나왔다. +물론 내 생각보다는 너무 늦게 나온편이긴 하지만 이 정도면 선방했지! + +5월 19일에 이사도 가고 새로운 직장에 조금씩 익숙해지면 다시 본격적으로 꾸준히 업데이트하며 공부할 계획을 다시 세워보겠다! +서버 개설 및 유지, 관리와 같은 부분에서 많이 부족하다는 것을 느끼고 있고, 확실히 그동안 해왔던 임베디드와는 전혀 다른 성격을 지나고 있기 때문에 서버 모니터링 & VOC와 같은 내용들을 따라가는게 어렵긴 하다. +그와 관련해서 내 NAS 서버를 관리하면서 그런 부족한 부분을 채워가보면 어떨까 지금은 생각하고 있다. + +우선은 이사에 집중하고 워크스페이스 관리를 좀 더 신경써서 주기적으로 내 스스로의 가치를 올릴 수 있도록 해보겠다. + +그리고 요새는 부업과 관련된 내용을 나름대로 정리하고 있다. +세상에는 내가 모르던 분야에서 다양한 부업으로 제 2의 수익을 내는 방법이 많은것을 알고서 깜짝 놀랐고, +그런 방법을 습득하고 소소하게라도 제 2의 수입을 안정적으로 만들 수 있는 노력을 할 예정이다. + +또한 승토리와 6월에 결혼을 준비하는 단계에 도입하기로 했다. +이것은 엄청나게 의미 있는 것이며, 인생에서 전환기가 될 수 있다고 생각한다. +지금 이사가는 집에서 같이 시작하기에는 내가 생각했던 부분보다는 소소할 수 있겠지만, 차근차근 준비해보려고 한다. 💪 diff --git a/src/content/blog/2026-01-lotto-lab.md b/src/content/blog/2026-01-lotto-lab.md index ef7af23..0324ed8 100644 --- a/src/content/blog/2026-01-lotto-lab.md +++ b/src/content/blog/2026-01-lotto-lab.md @@ -1,7 +1,7 @@ --- title: 로또 실험실을 조금 더 재미있게 date: 2026-01-12 -tags: product, lotto +tags: 개발, product, lotto excerpt: 작은 실험으로 시작한 로또 페이지를 앞으로 어떻게 발전시키려는지 정리했습니다. --- diff --git a/src/content/blog/2026-01-welcome.md b/src/content/blog/2026-01-welcome.md index c996528..ddf65df 100644 --- a/src/content/blog/2026-01-welcome.md +++ b/src/content/blog/2026-01-welcome.md @@ -1,7 +1,7 @@ --- title: 새 블로그를 열었습니다 date: 2026-01-18 -tags: intro, blog +tags: 일상, intro, blog excerpt: 이제부터 개발 기록과 여행 기록을 이곳에 차곡차곡 쌓아갑니다. --- diff --git a/src/pages/blog/Blog.css b/src/pages/blog/Blog.css index 1637ca9..61e6f93 100644 --- a/src/pages/blog/Blog.css +++ b/src/pages/blog/Blog.css @@ -48,7 +48,7 @@ .blog-grid { display: grid; - grid-template-columns: minmax(0, 0.45fr) minmax(0, 0.55fr); + grid-template-columns: minmax(0, 0.3fr) minmax(0, 0.7fr); gap: 22px; align-items: start; } @@ -58,6 +58,27 @@ gap: 12px; } +.blog-category-filter { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.blog-category-chip { + border: 1px solid var(--line); + background: rgba(255, 255, 255, 0.04); + color: var(--text); + border-radius: 999px; + padding: 6px 12px; + font-size: 12px; + cursor: pointer; +} + +.blog-category-chip.is-active { + border-color: rgba(247, 168, 165, 0.6); + background: rgba(247, 168, 165, 0.2); +} + .blog-list__item { border: 1px solid var(--line); background: var(--surface); @@ -79,6 +100,34 @@ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); } +.blog-pagination { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding-top: 10px; +} + +.blog-page-btn { + border: 1px solid var(--line); + background: rgba(255, 255, 255, 0.05); + color: var(--text); + border-radius: 999px; + padding: 6px 12px; + font-size: 12px; + cursor: pointer; +} + +.blog-page-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.blog-page-indicator { + color: var(--muted); + font-size: 12px; +} + .blog-list__title { margin: 0; font-weight: 600; @@ -170,6 +219,31 @@ font-size: 0.85em; } +.blog-article__body a { + color: #f7d4c9; +} + +.md-image { + width: 100%; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.12); + margin: 12px 0; +} + +.md-quote { + margin: 0 0 14px; + padding: 12px 16px; + border-left: 3px solid rgba(247, 168, 165, 0.6); + background: rgba(255, 255, 255, 0.03); + color: var(--muted); +} + +.md-hr { + border: none; + border-top: 1px solid rgba(255, 255, 255, 0.14); + margin: 18px 0; +} + .md-code { padding: 14px; border-radius: 12px; @@ -183,6 +257,68 @@ color: var(--muted); } +.blog-categories { + display: grid; + gap: 18px; +} + +.blog-categories__head h2 { + margin: 0 0 6px; + font-size: 24px; + font-family: var(--font-display); +} + +.blog-categories__head p { + margin: 0; + color: var(--muted); +} + +.blog-categories__grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 14px; +} + +.blog-category-card { + border: 1px solid var(--line); + background: var(--surface); + border-radius: 18px; + padding: 16px; + text-align: left; + color: inherit; + cursor: pointer; + display: grid; + gap: 12px; + transition: border-color 0.2s ease; +} + +.blog-category-card:hover { + border-color: rgba(255, 255, 255, 0.25); +} + +.blog-category-card__head { + display: flex; + justify-content: space-between; + align-items: center; + font-weight: 600; +} + +.blog-category-card__count { + font-size: 12px; + color: var(--muted); +} + +.blog-category-card__list { + display: grid; + gap: 6px; + color: var(--muted); + font-size: 13px; +} + +.blog-category-card__empty { + color: var(--muted); +} + @media (max-width: 900px) { .blog-header, .blog-grid { diff --git a/src/pages/blog/Blog.jsx b/src/pages/blog/Blog.jsx index 3e11a6d..7651ce8 100644 --- a/src/pages/blog/Blog.jsx +++ b/src/pages/blog/Blog.jsx @@ -1,24 +1,64 @@ -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { getBlogPosts } from '../../data/blog'; import './Blog.css'; const renderInline = (text) => { - const pattern = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g; - const parts = text.split(pattern).filter(Boolean); + const normalized = text.replace(//gi, '\n'); + const pattern = + /(!\[[^\]]*\]\([^)]+\)|\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g; + const segments = normalized.split('\n'); - return parts.map((part, index) => { - if (part.startsWith('**')) { - return ( - {part.replace(/\*\*/g, '')} - ); + return segments.flatMap((segment, segmentIndex) => { + const parts = segment.split(pattern).filter(Boolean); + const rendered = parts.map((part, index) => { + if (part.startsWith('![')) { + const match = part.match(/!\[([^\]]*)\]\(([^)]+)\)/); + if (!match) return {part}; + const [, alt, src] = match; + return ( + {alt} + ); + } + if (part.startsWith('[')) { + const match = part.match(/\[([^\]]+)\]\(([^)]+)\)/); + if (!match) return {part}; + const [, label, href] = match; + return ( + + {label} + + ); + } + if (part.startsWith('**')) { + return ( + {part.replace(/\*\*/g, '')} + ); + } + if (part.startsWith('*')) { + return {part.replace(/\*/g, '')}; + } + if (part.startsWith('`')) { + return {part.replace(/`/g, '')}; + } + return {part}; + }); + + if (segmentIndex < segments.length - 1) { + rendered.push(
); } - if (part.startsWith('*')) { - return {part.replace(/\*/g, '')}; - } - if (part.startsWith('`')) { - return {part.replace(/`/g, '')}; - } - return {part}; + + return rendered; }); }; @@ -60,6 +100,18 @@ const renderMarkdown = (body) => { return; } + if (/^---$/.test(line.trim())) { + flushList(); + blocks.push({ type: 'hr' }); + return; + } + + if (/^>\s+/.test(line)) { + flushList(); + blocks.push({ type: 'quote', value: line.replace(/^>\s+/, '') }); + return; + } + if (/^[-*]\s+/.test(line)) { list.push(line.replace(/^[-*]\s+/, '')); return; @@ -108,6 +160,13 @@ const renderMarkdown = (body) => { {block.value} ); + if (block.type === 'quote') + return ( +
+ {renderInline(block.value)} +
+ ); + if (block.type === 'hr') return
; return (

{renderInline(block.value)} @@ -118,8 +177,59 @@ const renderMarkdown = (body) => { const Blog = () => { const posts = useMemo(() => getBlogPosts(), []); - const [activeSlug, setActiveSlug] = useState(posts[0]?.slug); - const activePost = posts.find((post) => post.slug === activeSlug) || posts[0]; + const categoryNames = ['일상', '개발', '공부', '아이디어']; + const categorized = useMemo(() => { + const map = new Map(categoryNames.map((name) => [name, []])); + const misc = []; + + posts.forEach((post) => { + const matched = categoryNames.find((name) => post.tags.includes(name)); + if (matched) { + map.get(matched).push(post); + } else { + misc.push(post); + } + }); + + return { + categories: categoryNames.map((name) => ({ + name, + items: map.get(name), + })), + misc, + }; + }, [posts]); + + const [selectedCategory, setSelectedCategory] = useState('전체'); + const [page, setPage] = useState(1); + const pageSize = 10; + const filteredPosts = useMemo(() => { + if (selectedCategory === '전체') return posts; + if (selectedCategory === '기타') return categorized.misc; + return posts.filter((post) => post.tags.includes(selectedCategory)); + }, [posts, categorized.misc, selectedCategory]); + const totalPages = Math.max(1, Math.ceil(filteredPosts.length / pageSize)); + const pagedPosts = filteredPosts.slice((page - 1) * pageSize, page * pageSize); + + const [activeSlug, setActiveSlug] = useState(pagedPosts[0]?.slug); + const activePost = + pagedPosts.find((post) => post.slug === activeSlug) || pagedPosts[0]; + + useEffect(() => { + if (page > totalPages) { + setPage(1); + } + }, [page, totalPages]); + + useEffect(() => { + if (!pagedPosts.find((post) => post.slug === activeSlug)) { + setActiveSlug(pagedPosts[0]?.slug); + } + }, [pagedPosts, activeSlug]); + + useEffect(() => { + setPage(1); + }, [selectedCategory]); return (

@@ -141,7 +251,21 @@ const Blog = () => {
{activePost ? ( @@ -183,6 +330,64 @@ const Blog = () => { )}
+ +
+
+

카테고리

+

태그 기준으로 글을 묶어 한눈에 확인할 수 있습니다.

+
+
+ {categorized.categories.map((group) => ( + + ))} + +
+
); };