LIGHT
공부/AI-POT 2026. 6. 12. 23:03

AI-POT 2급 자격증 공부(N샷프롬프팅_CoT_자기성찰_멀티모달)

시험 99% 출제! N샷·CoT·멀티모달 프롬프트 완전 정리

AI-POT 시험에서 출제율이 가장 높은, 무려 99% 이상 나온다는 핵심 챕터입니다. 예시를 몇 개 주느냐로 갈리는 N샷, 단계적으로 사고하게 만드는 CoT, 그리고 텍스트와 이미지를 함께 넣는 멀티모달까지 — 이 셋만 확실히 잡으면 점수가 보입니다. 이 글에서 비유와 함께 한 번에 정리해드릴게요.


① 예시 개수가 전부 — N샷 프롬프팅

N샷 프롬프팅은 AI에게 예시(샷, shot)를 들어주며 시키는 기법입니다. 핵심은 단 하나, 예시를 몇 개 주느냐예요.

기법 예시 개수 한마디로
제로샷(Zero-shot) 0개 예시 없이 그냥 시킴
원샷(One-shot) 1개 예시 딱 하나
퓨샷(Few-shot) 2개 이상 예시 여러 개

🔑 헷갈릴 게 없어요. 0개=제로샷, 1개=원샷, 2개 이상=퓨샷. 개수만 세면 끝.


② 단계적으로 생각하게 하라 — CoT와 자기 성찰

CoT(Chain of Thought, 체인 오브 소트) 는 복잡한 문제를 단계적으로 나눠 풀게 하는 방식입니다. 그런데 이게 세 가지 유형으로 나뉘고, 비유로 외우면 절대 안 잊어버려요.

유형 방식 비유
일반 CoT 정해진 순서대로, 수정 없이 쭉 진행 🍳 요리 레시피
자기 성찰형 CoT 스스로 의심하고 반론하며 재검토 🩺 의사 선생님
피드백 루프 설계 수행을 관찰해 즉각 피드백, 반복 개선 🏋️ 운동 코치

비유로 직관을 잡아볼까요. 요리 레시피는 적힌 순서대로 따라가면 끝(일반 CoT). 의사는 "정말 이 진단이 맞나?" 스스로 의심하며 재검토하죠(자기 성찰형). 운동 코치는 내 자세를 보고 그때그때 교정해줍니다(피드백 루프).

🔑 구분 한 줄: 단일 방향 순서=CoT / 의심·자기평가=자기 성찰형 / 반복 개선=피드백 루프


③ 텍스트 + 이미지 + 음성 — 멀티모달 프롬프트

멀티모달 프롬프트둘 이상의 데이터 유형(텍스트·이미지·음성 등)을 합쳐서 AI에 넣는 겁니다. 여기서 중요한 함정 하나!

⚠️ 텍스트만, 또는 이미지만 넣으면 멀티모달이 아닙니다. 반드시 2종 이상을 결합해야 멀티모달이에요.

멀티모달의 장점은 분명합니다. 정보를 여러 형태로 주니 AI가 딴소리(환각)를 덜 하고, 정확도가 높아집니다.

🔑 핵심: 2개 이상 데이터 유형 결합 = 멀티모달 (단일 유형은 멀티모달 X)


한 걸음 더 — 1급 향상 기법

조금 더 깊이 들어가면, 위 기법들을 한 단계 끌어올린 버전이 나옵니다.

기법 핵심 언제 쓰나
대조적 N샷 좋은 예 + 나쁜 예를 함께 제공 AI 혼동을 줄이고 부적절한 응답을 걸러낼 때
다중 관점 CoT 여러 페르소나(재무 분석가·마케터·운영 관리자 등)로 한 문제를 다각도 해석 복잡한 정책 문제, 갑론을박 상황

💡 대조적 N샷은 '이건 맞고 저건 틀려'를 같이 보여주는 것, 다중 관점 CoT는 '여러 전문가를 한 테이블에 앉히는 것'이라 생각하면 쉬워요.


마치며

오늘 내용을 한 문장으로 정리하면 이렇습니다.

예시 개수로 나뉘는 N샷(0·1·2+), 단계적 사고의 CoT 3유형(레시피·의사·코치), 그리고 2종 이상 결합의 멀티모달 — 이 셋이 출제율 99%의 핵심이다.

출제율이 가장 높은 만큼, 비유까지 함께 외워두면 어떤 변형 문제가 나와도 흔들리지 않습니다. N샷·CoT·멀티모달, 이 세 단어만큼은 꼭 챙기세요!


📇 핵심만 빠르게 — 한 줄 암기 카드

  • N샷: 예시 0개=제로샷 / 1개=원샷 / 2개+=퓨샷
  • CoT 3유형: 레시피=일반 CoT(단일방향) / 의사=자기 성찰형(의심) / 코치=피드백 루프(반복)
  • 멀티모달 = 텍스트+이미지+음성 등 2종 이상 결합 (단일 유형은 ❌)
  • 멀티모달 효과 = 딴소리 억제 + 정확도↑
  • 1급: 대조적 N샷(긍정+부정 예시) / 다중 관점 CoT(여러 페르소나·정책·갑론을박)

이 글이 도움이 되셨다면 공감과 댓글 부탁드려요. AI 개념 정리 시리즈, 다음 편에서 계속됩니다! 🙌

h

hyos

IT · 스타트업 · 개인기록. 만들고, 실패하고, 기록합니다.

공부/AI-POT 2026. 6. 12. 22:35

AI-POT 2급 자격증 공부(프롬프트전략_목적별구조_역설계)

결과물만 보고 프롬프트를 알아낸다? 프롬프트 전략과 역설계 완전 정리

멋진 AI 그림을 보고 "이건 대체 뭐라고 입력한 거지?" 궁금했던 적 있으신가요? 놀랍게도 결과물만 보고 원래 프롬프트를 거꾸로 알아내는 '역설계' 기술이 있습니다. 이 글에서는 프롬프트 5대 구성요소를 실전에서 적용하는 법, 이미지 AI별 작성 규칙, 그리고 역설계와 1급 고급 전략까지 한 번에 정리합니다.


프롬프트 5대 구성요소, 실전에서 이렇게 쓴다

좋은 프롬프트의 뼈대는 다섯 가지입니다 — 지시·맥락·입력·출력 형식·제약 조건. 이걸 자기소개서 작성 예시로 보면 감이 확 옵니다.

구성 요소 실전 적용 예시
지시 행동 동사로 명령 "분석하라", "요약할 것"
맥락 배경 정보 제공 대기업 공채, 컴공 4학년
입력 수행할 작업·데이터 자기소개서 문항
출력 형식 결과의 구조 지정 상황 소개 → 해결 과정 → 결과·배운 점
제약 조건 지킬 규칙 설정 과장 표현 지양, 500자 초과 금지

시험에서는 "이 문장이 어떤 구성요소냐"를 자주 묻습니다. 두 가지 패턴만 기억하세요.

🔑 "중립적 관점", "개인정보 미포함", "전문용어 최소화", "500단어 초과 금지" → 전부 제약 조건.
🔑 "친절한 수학 튜터"에 출력 형식·답변 수준·제한사항은 다 있는데 풀어야 할 수학 문제(데이터)가 없다입력이 누락된 것.


텍스트 vs 이미지 — 작성 규칙이 다르다

흥미로운 차이가 있습니다. 텍스트 프롬프트는 용도에 따라 요소를 유연하게 바꿔도 되지만, 이미지 프롬프트는 작성 순서와 파라미터가 어느 정도 고정돼 있어요. 도구별로 정리하면:

도구 파라미터 작성법
Midjourney 프롬프트 뒤에 하이픈 두 개(--) + 호출 기호와 값 사이 띄어쓰기
Stable Diffusion 메인 프롬프트 + 네거티브 프롬프트 칸 + 파라미터는 선택 버튼
DALL-E (달리) 파라미터 없이 프롬프트 칸에 묘사

🔑 기출: 하이픈 두 개(--)로 파라미터를 쓰는 방식미드저니의 특징입니다.


거꾸로 추적하기 — 프롬프트 역설계

이제 이번 글의 하이라이트입니다. 프롬프트 역설계(Reverse Engineering)완성된 결과물(텍스트·이미지)을 보고, 그걸 만든 프롬프트를 거꾸로 추론하는 기술이에요.

왜 쓸까요? 두 가지 목적이 있습니다.

  • ✍️ 효과적인 프롬프트 작성법 학습 — 잘 나온 결과물을 분해해 노하우를 배움
  • 🛡️ 규칙 위반/우회 탐지 — AI 서비스의 정책을 어기거나 우회한 프롬프트를 잡아냄

역설계를 잘하려면 원하는 방식을 명확히 요청하고, 첫 결과가 별로여도 계속 개선을 요구해야 합니다. 유형별 방법은:

유형 방법
텍스트 역설계 분석 대상을 명확히 + 하이픈 세 개(---)로 문단을 끊어 입력
이미지 역설계 이미지와 함께 어떤 생성 AI 기반인지(미드저니/SD) 명시

🔑 텍스트는 --- 세 개로 구분, 이미지는 기반 AI를 반드시 명시하는 게 포인트.


한 걸음 더 — 1급 고급 전략 3종

1급에서는 이름부터 묵직한 세 가지 전략이 나옵니다.

전략 한마디로
내쉬 균형 프롬프트 모든 이해관계자의 전략을 고려해 모두 만족할 중간 지점 찾기
베이지안 프롬프트 불확실한 데이터를 모델링해 예측하고 불확실성 해소
거꾸로 프롬프트 원하는 답을 먼저 제시하고, 거기 도달할 질문을 역으로 생성

💡 헷갈리지 않게: 모두 만족 중간점 = 내쉬 균형 / 불확실성 예측 = 베이지안 / 답 먼저 = 거꾸로.


마치며

오늘 내용을 한 문장으로 정리하면 이렇습니다.

프롬프트는 5대 구성요소로 설계하고, 이미지는 도구별 규칙을 따르며, 잘 만든 결과물은 역설계로 분해해 배운다.

좋은 프롬프트를 '쓰는' 능력만큼, 잘 된 결과물을 '뜯어보는' 역설계 능력도 실력입니다. 두 방향을 모두 익히면 AI를 다루는 시야가 확 넓어져요.


📇 핵심만 빠르게 — 한 줄 암기 카드

  • 5요소: 지시(행동 동사)·맥락(배경)·입력(데이터)·출력 형식(구조)·제약 조건(규칙)
  • "중립·개인정보X·500자 금지" → 제약 조건 / 풀 문제·데이터 없음 → 입력 누락
  • 이미지: 미드저니(--) / 스테이블 디퓨전(메인+네거티브+버튼) / DALL-E(묘사만)
  • 역설계 = 결과물→프롬프트 역추론 (학습 + 규칙 위반 탐지)
  • 텍스트 역설계 = --- 3개 / 이미지 역설계 = 기반 AI 명시
  • 1급: 내쉬균형(중간점)·베이지안(불확실성)·거꾸로(답 먼저)

이 글이 도움이 되셨다면 공감과 댓글 부탁드려요. AI 개념 정리 시리즈, 다음 편에서 계속됩니다! 🙌

h

hyos

IT · 스타트업 · 개인기록. 만들고, 실패하고, 기록합니다.

공부/AI-POT 2026. 6. 12. 22:07

AI-POT 2급 자격증 공부(프롬프트작성원칙_파라미터_Temperature_TopP)

AI의 '성격'은 숫자로 정한다 — 프롬프트 원칙과 Temperature·Top P 완전 정리

똑같은 질문인데 어떤 AI는 딱딱한 보고서처럼, 어떤 AI는 시인처럼 답합니다. 이 '성격' 차이가 사실은 TemperatureTop P라는 숫자 두 개로 조절된다는 걸 아셨나요? 이 글에서는 좋은 프롬프트의 2대 원칙부터, 출제율 1위 파라미터 Temperature·Top P, 이미지 AI별 파라미터 입력법, 그리고 1급 고급 기법까지 한 번에 정리합니다.


좋은 프롬프트의 두 기둥 — 명확성과 구체성

화려한 기법보다 먼저 잡아야 할 기본이 있습니다. 바로 명확성구체성이에요.

① 명확성 — 모호함을 없애고 직접적으로 지시하는 겁니다. AI가 딴생각을 못 하게요. 한 가지 팁은 같은 개념엔 같은 용어를 쓰는 것. '고객'과 '유저'를 섞어 쓰면 AI가 헷갈립니다.

② 구체성상세한 정보, 특히 정량적 표현이 핵심입니다. "짧게 써줘" 대신 "300자 이내로", "몇 개 정도" 대신 "5개"처럼 숫자와 단위로 측정 가능하게 제시하세요.

여기에 맥락 정보(배경·역할 설정)와 제약 조건(분량·개수·용어 제한)까지 더하면 금상첨화입니다.

💡 예를 들어 AI 비서에게 *"내일 미팅 준비해 줘"* 라고만 하면? 무슨 미팅인지, 뭘 준비할지 맥락 정보가 부족해 제대로 응답하기 어렵습니다.


출제율 1위 — Temperature와 Top P

여기가 이번 글의 핵심이자 시험 단골입니다. 텍스트 생성 AI에서 가장 중요한 두 파라미터예요.

파라미터 무엇을 조절? 값이 높으면 값이 낮으면
Temperature 결과의 다양성·창의성 창의적·무작위적 일관적·안정적
Top P 단어 선택의 다양성 다양한 단어 예측 가능·안정적

외우는 법은 의외로 간단합니다.

🔑 둘 다 "높으면 창의적·다양, 낮으면 일관·안정" 입니다.
차이는 대상이에요 — Temperature는 결과 전체의 창의성, Top P는 단어 선택의 다양성.


그림 AI는 어떻게 파라미터를 넣을까?

이미지 생성 AI는 도구마다 파라미터 입력 방식이 꽤 다릅니다.

도구 파라미터 넣는 법 원치 않는 요소 빼기
Midjourney 프롬프트 뒤에 하이픈 두 개(--) --no 로 제외
Stable Diffusion 선택 버튼이 많음 네거티브 프롬프트 칸이 따로 있음
DALL-E 정해진 양식 없이 프롬프트에 직접 서술 문장으로 직접 풀어씀

🔑 기출 포인트: 미드저니는 ----no, 스테이블 디퓨전은 네거티브 프롬프트 칸, DALL-E는 스타일·퀄리티·사이즈를 그냥 문장으로 적습니다.


한 걸음 더 — 1급 고급 기법

① 전략적 모호성 (의도적 모호함) — 보통은 명확하게 쓰라고 하지만, 1급에선 반대 기법도 나옵니다. 일부러 모호하게 써서 AI에게 창의적 자유를 주는 거죠. 핵심은 *"중간까지는 구체적, 이후엔 자율권"* 입니다.

표현 예시: *"하되", "식으로"* — "전체 분위기는 차분하게 하되, 색감은 자유롭게 표현해줘"

② 파라미터로 성격 만들기 — 가장 흥미로운 부분입니다. Temperature와 Top P를 조합하면 AI에게 인격을 부여할 수 있어요.

설정 탄생하는 인격
낮은 Temperature + 낮은 Top P 정확하고 예측 가능한 교육자·전문가
높은 Temperature + 높은 Top P 자유분방한 창의적 작가

마치며

오늘 내용을 한 문장으로 정리하면 이렇습니다.

좋은 프롬프트의 기본은 명확성·구체성이고, AI의 창의성은 Temperature(결과)와 Top P(단어)로 조절한다. 둘 다 높이면 작가가, 낮추면 전문가가 된다.

프롬프트의 '말'을 다듬는 것이 절반이라면, 나머지 절반은 이 파라미터라는 손잡이를 돌리는 감각입니다. 두 개를 함께 익히면 AI를 내 의도대로 부리는 힘이 생깁니다.


📇 핵심만 빠르게 — 한 줄 암기 카드

  • 좋은 프롬프트 2원칙: 명확성(모호 제거·동일 용어) + 구체성(정량적·숫자·단위)
  • 배경·역할 → 맥락 정보 / 분량·개수 제한 → 제약 조건
  • "내일 미팅 준비해줘" → 맥락 정보 부족
  • Temperature = 결과 창의성 / Top P = 단어 다양성 → 둘 다 ↑창의·다양, ↓일관·안정
  • 이미지: 미드저니(--,--no) / SD(네거티브 프롬프트 칸) / DALL-E(자유 서술)
  • 일부러 모호("하되","식으로") → 전략적 모호성
  • 낮은 Temp+TopP → 전문가·교육자 / 높은 Temp+TopP → 작가

이 글이 도움이 되셨다면 공감과 댓글 부탁드려요. AI 개념 정리 시리즈, 다음 편에서 계속됩니다! 🙌

h

hyos

IT · 스타트업 · 개인기록. 만들고, 실패하고, 기록합니다.

IT 2026. 6. 12. 21:44

블로그 꽃단장 파일 공유(w/claude)

skin-share.html 코드 가이드

— 파일 구조, 동작 원리, 커스터마이징 지점

tistory-skin-guide.md(제작 과정/이슈 해결기)와 함께 보는 코드 레퍼런스입니다.
이 문서는 "코드가 어떻게 생겼고, 어디를 고치면 무엇이 바뀌는가"에 집중합니다.


1. 파일 한 장 요약

skin-share.html 하나에 HTML + CSS + JS가 전부 들어있습니다. (약 1,300줄)

skin-share.html
├─ <head>
│   ├─ Google Fonts 로드 (Nanum Pen Script / Noto Serif KR / Noto Sans KR)
│   └─ <style>  ······································ 약 10~590행
│       ├─ ① 디자인 토큰 (:root / body.dk)
│       ├─ ② 베이스 & 레이아웃 (헤더, 그리드, 사이드바)
│       ├─ ③ 글 카드 + 미리보기 클램프
│       ├─ ④ 페이지네이션
│       ├─ ⑤ 사이드바 위젯 (검색/카테고리/태그/RSS)
│       ├─ ⑥ Tistory 자동 삽입 요소 덮어쓰기 (!important 지대)
│       ├─ ⑦ 첨부파일 재스타일 + 확장자 칩
│       └─ ⑧ 상세 페이지 모드 (body.detail …)
├─ <body class="tt-body-index">
│   ├─ 헤더 (블로그명, 카테고리 내비, 다크모드 토글)
│   ├─ 메인 컬럼: <s_list> 글 목록 + <s_article_rep> 글 상세
│   │   └─ 카드 내부에 태그/명함/댓글 등 "상세 전용 요소" 동봉
│   ├─ 사이드바: 검색 / 카테고리 / 태그 클라우드 / RSS
│   └─ 푸터
└─ <script>  ········································ 약 724~1330행
    ├─ (a) localStorage 캐시 정리
    ├─ (b) 첨부파일 DOM 재구성
    ├─ (c) 페이지네이션 재구성
    ├─ (d) 페이지 타입 감지 + 상세 모드 토글  ← 가장 큰 블록
    │   ├─ 사이드바 카테고리 재구성
    │   ├─ 글 태그 정리(멀티 태그 분리) + 폴백
    │   ├─ author-box / atags 중복 제거
    │   └─ 사이드바 태그 클라우드 정리
    ├─ (e) 다크모드 토글 + 복원
    └─ (f) 스크롤 진행 바

2. 디자인 토큰 — 색/스타일을 바꾸려면 여기만

모든 색상은 CSS 변수로 선언되어 있고, 라이트/다크가 같은 변수명을 공유합니다.
변수만 바꾸면 전체 테마가 일괄 변경됩니다.

:root {                                  /* 라이트 모드 */
  --ac:#B07828;                          /* 액센트 (브론즈) — 링크 호버, 현재 페이지, 칩 */
  --ac-a:rgba(176,120,40,.13);           /* 액센트 연한 배경 */
  --bg:#F8F6F3;  --nav:#EFE9E2;          /* 페이지 배경 / 내비 배경 */
  --tx:#1A1814;  --sub:#4A4238;  --mt:#6E665E;  /* 본문 / 보조 / 메타 텍스트 */
  --bd:#E4DDD5;  --tb:#EDE7DF;  --cb:#FFFEFB;   /* 보더 / 태그 배경 / 카드 배경 */
}
body.dk { /* 다크 모드 — 같은 변수를 어두운 값으로 재정의 */ }
바꾸고 싶은 것 수정 위치
포인트 컬러 --ac, --ac-a (라이트/다크 각각)
전체 배경 톤 --bg, --cb, --bd
본문 폰트 body{font-family:...} + <head>의 Google Fonts 링크
로고 손글씨체 Nanum Pen Script 사용처 (헤더 로고, 아바타 이니셜)

규칙: 색을 하드코딩하지 말 것. 새 요소를 추가할 때도 var(--ac) 등 변수를 쓰면
다크모드가 공짜로 따라옵니다. (예외: 확장자 칩 색상은 파일 형식 고유색이라 하드코딩)


3. 한 장 두 모드: 목록 ↔ 상세

이 스킨의 핵심 트릭입니다. 카드(.card) 마크업은 하나인데,
body 클래스에 따라 두 가지 모습으로 렌더됩니다.

목록 모드 (기본)                상세 모드 (body.detail)
┌─ .card ──────────┐           .card → 보더/배경 제거, 아티클화
│ 제목 (.pt)        │           .pt   → 28px 대제목
│ 미리보기 2줄 (.pe) │           .pe   → 클램프 해제, 전문 표시
│ 태그 (compact)    │           .rep-extra → 표시 (명함, 댓글 등)
└──────────────────┘           + "← 목록으로" 링크 JS 삽입

동작 순서:

  1. <body class="tt-body-index"> → Tistory가 tt-body-page(상세) 등으로 치환
  2. JS(d블록)가 클래스를 읽어 body.detail 클래스를 추가/생략
  3. CSS의 body.detail ... 섹션(⑧)이 카드를 아티클로 변환
// 정식 판별 우선, 변수 미처리 시 휴리스틱 폴백
if (/tt-body-/.test(bodyCls)) {
  isDetail = /tt-body-page(\s|$)/.test(bodyCls);
} else {
  isDetail = cards.length === 1;
}

상세 전용 요소 패턴: 명함·댓글처럼 상세에서만 보일 요소는 카드 안에
class="rep-extra"로 넣어두고 기본 display:none, body.detail에서만 표시합니다.
새 상세 전용 요소를 추가할 때 이 클래스만 붙이면 됩니다.


4. 미리보기 클램프 — .pe의 방어 설계

목록의 본문 미리보기는 Tistory가 어떤 HTML이든 넣을 수 있는 영역입니다.
그래서 3중 방어를 합니다:

.pe{ max-height:53px !important; overflow:hidden !important;
     display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
.pe img,.pe figure,.pe video,.pe iframe,.pe table{ display:none !important; }  /* 미디어 제거 */
.pe *{ font-size:14px !important; margin:0 !important; ... }                   /* 평탄화 */

상세 모드에서는 이걸 전부 해제합니다 (body.detail .pe{max-height:none !important;...}).
미리보기 줄 수를 바꾸려면 -webkit-line-clamp 값과 max-height(줄수 × line-height)를 함께 수정하세요.


5. !important 지대 — Tistory 덮어쓰기 레이어

⑥~⑦ 섹션은 거의 모든 선언에 !important가 붙어 있습니다. 버그가 아니라 의도입니다.
Tistory가 런타임에 자체 CSS와 인라인 스타일을 주입하기 때문에, 이를 이기려면 필요합니다.

주요 덮어쓰기 대상:

대상 처리
공감/구독/프로필 위젯 display:none (스킨 자체 디자인으로 대체)
another_category (카테고리의 다른 글) 테이블 → 이전 글/다음 글 2칸 카드 그리드로 변환. tr:first-child::before{content:'← 이전 글'} 같은 가상요소로 라벨링
첨부파일 블록 기본 아이콘 숨기고 확장자 칩 + DOWNLOAD 텍스트 링크로 재구성
본문 표 border-collapse + 변수 기반 보더로 통일
댓글 React 앱 [class*="tt_"] 셀렉터로 폰트/색만 덮어씀 (DOM은 건드리지 않음)

여기를 수정할 때 주의: 셀렉터가 div[class*="another_category"]처럼 부분 일치인 이유는
Tistory가 클래스명을 바꾸는 경우가 있어서입니다. 정확한 클래스명에 의존하지 마세요.


6. JS 모듈별 가이드

전부 IIFE(즉시실행함수)로 독립되어 있어 하나가 죽어도 나머지는 동작합니다.
중요 로직은 try/catch로 감싸고, 실패 시 console.warn('[hyos] ...')을 남깁니다.

(b) 첨부파일 재구성

  • .tt_attachment 등에서 파일명/크기/링크를 파싱
  • 확장자를 추출해 <span class="file-ext-ico" data-ext="pdf"> 칩 삽입
  • 새 확장자 색을 추가하려면: CSS의 .file-ext-ico[data-ext="..."] 규칙 한 줄 추가

(c) 페이지네이션 재구성

  • 서버가 렌더한 번호 링크에서 현재 페이지(href 없는 항목), 전체 페이지, URL 패턴을 수집
  • ‹ 1 … 4 5 6 … 23 › 형태로 다시 그림
  • 윈도우 크기 변경: var win = 2 (현재 ±2) 값 수정
  • 변수 미치환([##_ 잔존) 시 페이지네이션 전체 숨김 — raw 텍스트 노출 방지

(d) 페이지 타입 감지 블록 (메인)

가장 큰 블록. 내부 순서가 중요합니다:

1. body_id로 상세/목록 판별 → body.detail 토글
2. 상세면: "← 목록으로" 링크 삽입
3. 사이드바 카테고리 재구성  ← try/catch (실패해도 원본 노출)
4. 글 태그 정리: '#' 멀티 태그 분리 → 칩 재구성
   └ 폴백 사다리: s_tag_label 결과 → 본문 /tag/ 링크 → 페이지 JSON
     → 모바일 페이지(/m/...) fetch → 전부 실패 시 숨김
5. author-box / atags 중복 제거
6. 사이드바 태그 클라우드 정리 (멀티 태그 분리 동일 적용)

태그 폴백은 0ms / 800ms / 2500ms 3회 재시도합니다 — Tistory React가
비동기로 마운트되기 때문. 이 패턴은 React 영역을 읽는 모든 코드에 필요합니다.

(e) 다크모드

document.getElementById('b').classList.toggle('dk', dk);   // className= 금지!
localStorage.setItem('hyos-dark', dk ? '1' : '0');
  • classList.toggle을 쓰는 이유: className =으로 덮어쓰면 Tistory의
    tt-body-page 클래스가 날아가 레이아웃이 깨집니다 (실제 겪은 버그)
  • 새로고침 시 localStorage에서 복원

(f) 스크롤 진행 바

헤더 위 2px 바(#prog)의 너비를 스크롤 비율로 갱신. 필요 없으면
#prog 요소와 마지막 scroll 리스너만 지우면 됩니다.


7. 공통 패턴 (새 기능 추가 시 따라할 것)

패턴 1 — 미치환 변수 가드

if (/\[##_/.test(el.innerHTML)) { /* 변수 미처리 → 폴백 or 숨김 */ }

모든 동적 영역의 첫 줄. raw 텍스트가 사용자에게 노출되는 것을 막습니다.

패턴 2 — 폴백 사다리

정식 치환자 → DOM 정규화 → 대체 소스(RSS, /m/ 페이지) → 깔끔하게 숨김

어느 단계에서 성공하든 결과 UI는 동일하게. 전부 실패하면 빈 껍데기 대신 섹션 자체를 숨깁니다.

패턴 3 — 비동기 재시도

if (!run()) {
  setTimeout(function(){ run(); }, 800);
  setTimeout(function(){ run(); }, 2500);
}

Tistory React 마운트를 기다리는 표준 방법. MutationObserver보다 단순하고 충분합니다.

패턴 4 — 네임스페이스

  • CSS 클래스: hyos-file, hyos-cmt 처럼 접두사 사용 (Tistory 클래스와 충돌 방지)
  • 콘솔 로그: console.log('[hyos] ...') — 필터링 가능
  • localStorage 키: hyos-dark, hyosTagCache_v1 — 버전 붙여서 마이그레이션 대비

8. 커스터마이징 빠른 레시피

하고 싶은 것 방법
포인트 컬러 변경 :rootbody.dk--ac, --ac-a 수정
미리보기 3줄로 .pe-webkit-line-clamp:3 + max-height 재계산
사이드바 위젯 순서 변경 <aside class="sb"> 안의 .ss 블록 순서 바꾸기
사이드바 위젯 제거 해당 .ss 블록 삭제 (JS는 요소 없으면 자동 skip)
페이지네이션 윈도우 JS의 var win = 2 수정
확장자 칩 색 추가 .file-ext-ico[data-ext="확장자"]{background:...} 추가
다크모드 기본값 ON (e) 복원 IIFE에서 localStorage 체크 앞에 dk=true;... 분기 추가
명함(author-box) 수정 class="author-box" 블록 — 이름/소개/이니셜은 플레이스홀더 검색
댓글 영역 스타일 #hyos-cmt 하위 CSS — DOM 구조는 Tistory React 소유, CSS만 건드릴 것

9. 수정 시 지켜야 할 것 (안 깨뜨리는 법)

  1. <s_...> 블록 구조를 옮기지 마세요. 치환자는 자기 블록 안에서만 동작합니다.
    특히 <s_tag_label>, <s_random_tags>(반복 단위 주의), <s_paging_rep>.
  2. href="..."로 감싸면 안 됩니다. 속성 전체를 출력하는 변수입니다.
  3. body 클래스는 classList로만 조작하세요.
  4. React 마운트 포인트(data-tistory-react-app) 내부 DOM을 JS로 수정하지 마세요.
    리렌더 시 덮어써집니다. CSS 덮어쓰기만 허용.
  5. JS 추가는 새 IIFE로. 기존 블록에 끼워 넣지 말고 독립 블록 + try/catch로 추가하면
    실패해도 다른 기능이 살아 있습니다.
  6. 수정 후에는 5종 페이지를 모두 확인: 홈 / 글 상세 / 카테고리 / 태그 검색 / 마지막 페이지(글 1개).

 

깃허브로 따로 관리를 하고있지 않아서 파일로 전달 드리니 참고바랍니다~

 

h

hyos

IT · 스타트업 · 개인기록. 만들고, 실패하고, 기록합니다.

IT 2026. 6. 12. 21:39

티스토리 블로그 꽃단장 가이드 및 이슈 노트(w/ Claude Design)

Tistory 커스텀 스킨 제작 가이드

— 디자인 시안부터 실서비스 적용까지, 삽질 기록과 해결법

대상 독자: HTML/CSS/JS 기본기가 있는 1~3년차 개발자.
이 문서는 실제로 블로그(hyos.blog) 스킨을 디자인 시안부터 만들어 Tistory에 적용하면서
겪은 이슈와 해결 과정을 정리한 것입니다.


0. 전체 흐름 요약

1) HTML/CSS로 디자인 시안 제작 (홈 + 상세 2종)
2) 시안을 Tistory 스킨(skin.html) 구조로 변환
3) 치환자(템플릿 변수) 적용 → 업로드 → 깨지는 부분 디버깅 (대부분의 시간이 여기)
4) JS 폴백/정규화 레이어 추가로 마무리

핵심 교훈을 먼저 말하면:

  • Tistory 치환자는 문서대로 동작하지 않는 경우가 많다. 변수가 빈값으로 치환되거나, 아예 치환되지 않고 텍스트가 그대로 남기도 한다.
  • 그래서 최종 구조는 "치환자 우선 + JS 폴백"의 2중 구조가 됐다. 서버가 제대로 렌더하면 그대로 쓰고, 실패하면 JS가 DOM/RSS/모바일 페이지에서 데이터를 긁어와 채운다.
  • Tistory가 주입하는 기본 스타일과 싸우려면 !important가 생각보다 자주 필요하다.

1. 스킨 파일 구조

Tistory 스킨은 최소 두 파일이다.

파일 역할
skin.html 페이지 전체 구조. 치환자 포함
style.css 스타일 (skin.html 안에 <style>로 넣어도 동작함)

우리는 관리 편의상 skin.html 하나에 CSS/JS를 전부 인라인으로 넣었다.
업로드는 블로그 관리 → 꾸미기 → 스킨 편집 → html 편집에서 한다.

페이지 1장으로 모든 화면을 처리한다

Tistory는 홈/카테고리/태그/검색/글상세가 전부 같은 skin.html로 렌더링된다.
<s_list>(목록)와 <s_article_rep>(글)을 같은 파일에 두고, 현재 페이지 타입에 따라
Tistory가 알아서 한쪽만 채워준다. 우리는 여기에 더해 JS로 상세/목록 레이아웃을 토글했다.


2. 치환자(템플릿 변수) 기초

치환자는 형태이고, 반복/조건 블록은 <s_블록명>...</s_블록명> 태그다.

<s_list>                          <!-- 목록 페이지에서만 렌더 -->
  <s_list_rep>                    <!-- 글 개수만큼 반복 -->
    <a href=""></a>
  </s_list_rep>
</s_list>

중요한 규칙: 변수는 지정된 블록 안에서만 치환된다.
블록 밖에 변수를 쓰면 치환되지 않고 raw 텍스트가 그대로 노출된다. (아래 이슈 4 참고)


3. 겪은 이슈와 해결법

이슈 1 — 홈 목록에서 글 전문이 다 보임

증상: 홈 화면 카드에 미리보기 2줄이 아니라 본문 전체가 출력됨.

원인: 목록에서 본문 변수를 그대로 쓰면 전문이 들어온다.

해결: CSS line-clamp로 강제 제한. Tistory가 어떤 HTML을 넣어주든 2줄로 자른다.

.preview{
  display:-webkit-box;
  -webkit-line-clamp:2;
  -webkit-box-orient:vertical;
  overflow:hidden;
}
/* 본문 안의 어떤 태그가 와도 스타일 통일 */
.preview *{
  font-size:14px !important;
  margin:0 !important;
  background:none !important;
}

포인트: 미리보기 안에는 <p>, <h2>, <figure> 등 무엇이든 들어올 수 있으므로
* 셀렉터 + !important로 전부 평탄화해야 한다.

이슈 2 — 상세 페이지와 목록 페이지 구분

증상: "마지막 페이지에 글이 1개만 있으면 상세 페이지로 보임."

원인(우리 실수): 처음에 "카드가 1개면 상세"라는 휴리스틱으로 판별했다.
목록 페이지의 마지막 페이지에 글이 1개 남자 상세로 오인.

해결: Tistory 공식 변수 tt-body-index를 사용한다.

<body id="b" class="tt-body-index">

렌더되면 tt-body-index(홈), tt-body-page(글 상세), tt-body-category,
tt-body-tag, tt-body-search 같은 클래스가 붙는다.

var bodyCls = document.body.className || '';
var isDetail;
if (/tt-body-/.test(bodyCls)) {
  isDetail = /tt-body-page(\s|$)/.test(bodyCls);   // 정식 판별
} else {
  isDetail = cards.length === 1;                    // 변수 미처리 시 폴백
}

교훈: 휴리스틱을 쓰더라도 항상 "정식 방법 우선, 휴리스틱은 폴백"으로 겹쳐라.

이슈 3 — 다크모드 토글이 페이지 판별을 깨뜨림

증상: 다크모드를 켜면 상세 페이지 레이아웃이 풀림.

원인: 토글 코드가 body.className = 'dk'클래스를 통째로 덮어써서
tt-body-page 클래스가 날아갔다.

해결: classList.toggle 사용.

// BAD
document.body.className = dk ? 'dk' : '';
// GOOD
document.body.classList.toggle('dk', dk);

사소해 보이지만 실제로 한참 헤맨 버그. 남의 플랫폼 위에서는 body/root 요소의
클래스를 절대 통째로 덮어쓰지 말 것.

이슈 4 — 태그가 절대 안 나옴 (최대 난관)

증상: 글 하단 태그 영역이 항상 빈값. ,
어떤 변수를 써도 안 나옴.

원인이 3겹이었다:

  1. 블록 누락: 태그 변수는 반드시 <s_tag_label> 블록 안에 있어야 치환된다.

    <s_tag_label>
      <div class="atags"></div>
    </s_tag_label>

    블록 없이 변수만 쓰면 영원히 빈값/raw 텍스트.

  2. 길이 필터에 걸림: JS 정리 로직에서 "50자 넘는 태그는 쓰레기값"으로 버렸는데,
    실제로는 사용자가 태그 입력란에 NLP #자연어처리 #프롬프트엔지니어링 #...처럼
    #로 이어 적은 80자짜리 태그 1개가 등록되어 있었다. 통째로 필터에 걸려 0개가 됨.

  3. 입력 방식 자체의 문제: Tistory 에디터 하단 태그 입력란에서는
    태그 하나 입력 후 엔터로 구분해야 개별 태그가 된다.
    #로 이어 적으면 전부 한 덩어리 태그로 저장되고, /tag/ 검색도 덩어리째로만 잡힌다.

해결:

// '#'로 이어 적은 멀티 태그를 분리해서 개별 칩으로 표시
var add = function (t) {
  t = (t || '').trim();
  if (!t) return;
  if (t.indexOf('#') > 0 || /^#.+#/.test(t)) {
    t.split('#').forEach(function (p) {
      p = p.trim();
      if (p && !seen[p] && p.length <= 80) { seen[p] = 1; tagSet.push(p); }
    });
    return;
  }
  t = t.replace(/^#/, '');
  if (!t || seen[t] || t.length > 80) return;
  seen[t] = 1; tagSet.push(t);
};

교훈:

  • 변수가 안 나오면 먼저 해당 변수의 필수 블록(<s_...>)을 확인하라.
  • "데이터가 안 온다"고 단정하기 전에 내 필터 로직이 데이터를 버리고 있는지 의심하라.
  • 디버깅할 땐 기존(잘 동작하는) 스킨의 렌더 결과 HTML을 직접 까보는 게 가장 빠르다.
    개발자도구에서 동작하는 스킨의 태그 영역 HTML을 복사해 비교하면서 원인을 찾았다.

이슈 5 — 사이드바 태그 클라우드의 반복 단위

증상: <s_random_tags>로 감싼 TAGS 섹션이 뜨긴 뜨는데 구조가 깨지거나,
섹션 제목까지 태그 개수만큼 복제됨.

원인: <s_random_tags>반복 블록이다. 섹션 전체를 감싸면 섹션이 통째로 반복된다.

<!-- BAD: 섹션 전체가 태그 수만큼 반복됨 -->
<s_random_tags>
  <div class="ss">
    <h3>TAGS</h3>
    <div class="tc"><a href=""></a></div>
  </div>
</s_random_tags>

<!-- GOOD: 반복시킬 <a> 하나만 감싼다 -->
<div class="ss">
  <h3>TAGS</h3>
  <div class="tc">
    <s_random_tags><a href=""></a></s_random_tags>
  </div>
</div>

교훈: <s_..._rep> 류 블록을 만나면 "이 블록의 반복 단위가 무엇인가"를 먼저 정하라.

이슈 6 — 페이지네이션 링크가 href= 로 깨짐

증상: 페이지 번호를 누르면 https://blog.com/href= 같은 이상한 URL로 이동.

원인: 는 URL이 아니라 href="..." 속성 전체를 출력하는
변수다. href="[##_...]"처럼 감싸면 href="href=..."로 깨진다.

<!-- BAD -->
<a href=""></a>
<!-- GOOD -->
<a ></a>

교훈: 치환자마다 출력 형식(URL만 vs 속성 전체)이 다르다. 깨지면
렌더된 HTML을 개발자도구로 열어 변수가 뭘 뱉었는지 확인하는 게 정답.

이슈 7 — ‹ › 버튼이 없는 페이지로 이동

증상: 이전/다음 화살표를 누르면 "없는 페이지" 에러.

해결: 서버 변수에 의존하지 않고 JS로 페이지네이션을 재구성했다.

  1. 서버가 렌더한 번호 링크에서 현재 페이지(href 없는 번호), 전체 페이지 수, URL 패턴을 수집
  2. ‹ 1 … 4 5 6 … 23 › 형태로 다시 그림 (현재 ±2 윈도우 + 첫/끝 + 줄임표)
  3. 첫 페이지의 , 마지막 페이지의 pointer-events:none + 흐림 처리
var win = 2, lo = Math.max(1, cur - win), hi = Math.min(total, cur + win);
var pages = [1];
if (lo > 2) pages.push('…');
for (var i = lo; i <= hi; i++) { if (i > 1 && i < total) pages.push(i); }
if (hi < total - 1) pages.push('…');
if (total > 1) pages.push(total);

이슈 8 — 댓글 수가 항상 0

증상: 목록 카드의 댓글 카운트 변수가 4종
(rp_cnt, comment_count, comment_cnt, reply_cnt) 전부 빈값/0.

해결: RSS 폴백. Tistory RSS(/rss)의 각 <item>에는 댓글 수가 들어있다.

fetch('/rss').then(r => r.text()).then(xml => {
  var doc = new DOMParser().parseFromString(xml, 'text/xml');
  doc.querySelectorAll('item').forEach(it => {
    var link = it.querySelector('link').textContent;
    var slash = it.getElementsByTagNameNS(
      'http://purl.org/rss/1.0/modules/slash/', 'comments')[0];
    var cnt = slash ? parseInt(slash.textContent, 10) : 0;
    // link의 pathname으로 카드와 매칭해서 카운트 표시
  });
});

같은 도메인이라 CORS 문제 없음. 단, RSS 공개 설정(블로그 관리 → 콘텐츠)이 켜져 있어야 하고
RSS에 노출되는 글 수 제한이 있다는 점은 감안할 것.

이슈 9 — 신형 댓글/프로필은 React 마운트 방식

증상: 댓글 영역이 아예 비어 있음. 직접 <form>을 그려도 동작 안 함.

원인: 최신 Tistory는 댓글·프로필 카드를 React 앱으로 클라이언트에서 마운트한다.
스킨은 마운트 포인트만 제공해야 한다.

<div data-tistory-react-app="Comment"></div>

div를 두면 Tistory 스크립트가 댓글 목록+입력창을 통째로 렌더해준다.
디자인 통일은 마운트된 DOM을 CSS로 덮는 방식으로 처리:

#hyos-cmt [class*="tt_"] { font-family:'Noto Sans KR',sans-serif !important; }

주의: React가 비동기로 마운트되므로, 관련 DOM을 읽는 JS는
setTimeout 재시도(예: 0ms / 800ms / 2500ms)를 걸어야 한다.

이슈 10 — 첨부파일 영역 깨짐 + 확장자 아이콘

증상: <s_attachment> 영역이 Tistory 기본 스타일과 충돌해 깨짐.

해결:

  1. 파일명에서 확장자를 JS로 파싱해 data-ext 속성 부여
  2. 확장자별 색상 칩(PDF=빨강, HWP=파랑, XLS=초록...)을 CSS로 표시
  3. Tistory가 background를 덮어써서 칩 색상에 !important 필수였다
.file-ext-ico[data-ext="pdf"]{ background:#c0392b !important; color:#fff !important; }
.file-ext-ico[data-ext="hwp"]{ background:#2e6ab0 !important; color:#fff !important; }

이슈 11 — 본문 표(table)가 안 보임

증상: 에디터에서 만든 표가 본문에서 투명/무스타일로 렌더됨.

원인: 에디터는 <table data-ke-align="..."> 형태로 저장하는데, 스킨 CSS에
table 스타일이 전혀 없었고 리셋 CSS가 보더를 다 지워버렸다.

해결: 본문 영역(.pe) 한정으로 table 스타일을 명시.

.pe table{border-collapse:collapse;width:100%;margin:20px 0;}
.pe th,.pe td{border:1px solid var(--bd);padding:10px 14px;text-align:left;}
.pe thead th{background:var(--tb);font-weight:600;}

교훈: 본문에는 에디터가 생성하는 모든 마크업(표, 인용, 코드, 이미지 캡션...)이
들어올 수 있다. 스킨 만들 때 에디터로 온갖 요소를 넣은 테스트 글을 하나 만들어두면
디버깅이 훨씬 빨라진다.


4. 최종 아키텍처: "치환자 우선 + JS 폴백"

여러 이슈를 거치며 정착한 패턴. 모든 동적 데이터에 동일하게 적용했다.

┌─ 1순위: Tistory 치환자가 정상 렌더 → 그대로 사용
├─ 2순위: DOM에 남은 흔적(미처리 변수, 링크 등)을 JS로 정규화
├─ 3순위: 다른 소스에서 페치 (RSS, 모바일 페이지 /m/...)
└─ 실패: 해당 섹션을 깔끔하게 숨김 (빈 껍데기 노출 금지)

체크 코드 패턴:

var raw = el.innerHTML || '';
if (/\[##_/.test(raw)) {
  // 치환자가 처리되지 않음 → 폴백 또는 숨김
}

[##_ 문자열이 남아있으면 변수 미처리라는 뜻이다. 이 체크 하나로
"raw 변수 텍스트가 사용자에게 노출되는" 최악의 상황을 방지할 수 있다.


5. 디버깅 워크플로우 (시간 아끼는 법)

  1. 개발자도구 → Elements가 진실이다. 스킨 코드가 아니라 렌더된 결과를 봐라.
    변수가 뭘로 치환됐는지, 빈값인지, raw로 남았는지 바로 보인다.
  2. console.log를 스킨 JS에 심어라. 우리는 [hyos] 접두사로 통일해서
    [hyos] 태그 추출 결과: [] 처럼 각 폴백 단계의 결과를 찍었다.
  3. 잘 동작하는 다른 스킨과 비교하라. 같은 블로그에 기본 스킨을 잠깐 적용해보고
    해당 영역의 렌더 HTML을 복사해두면, 내 스킨에서 뭐가 다른지 즉시 비교된다.
  4. 테스트용 글을 만들어라. 표, 코드블록, 이미지, 첨부파일, 태그 여러 개,
    댓글 있는 글 — 전부 들어간 글 하나면 회귀 테스트가 된다.
  5. 스킨 저장 후 강력 새로고침(Ctrl+Shift+R). Tistory는 캐시가 꽤 끈질기다.

6. 치환자 빠른 참조 (이번에 실제로 쓴 것들)

치환자 / 블록 용도 함정
tt-body-index 페이지 타입 (tt-body-page 등) body class에 넣고 classList로만 조작
<s_list> / <s_list_rep> 글 목록 / 반복 반복 단위에 주의
<s_article_rep> 글 상세
<s_tag_label> + 글 태그 블록 필수. 변수 단독으론 절대 안 나옴
<s_random_tags> + 사이드바 태그 클라우드 반복 블록임 — <a> 하나만 감싸기
페이지 링크 href= 포함 속성 전체 출력. href="..."로 감싸면 깨짐
<s_attachment> 첨부파일 Tistory 기본 스타일과 충돌 → !important
data-tistory-react-app="Comment" 댓글 (신형) React 비동기 마운트. JS 재시도 필요
/rss 댓글 수 폴백 slash:comments 네임스페이스 파싱
/m/{글번호} 모바일 렌더 페이지 태그 등 데이터 폴백 소스로 활용 가능

7. 마지막 조언

  • 처음부터 Tistory 구조에 맞춰 디자인하지 마라. 순수 HTML 시안을 먼저 완성하고
    (디자인 의사결정이 자유로움), 그 다음 스킨으로 이식하는 편이 결과물이 훨씬 좋다.
  • 이식은 한 섹션씩. 헤더 → 목록 → 상세 → 사이드바 → 댓글 순으로 하나씩 옮기고
    매번 업로드해서 확인해라. 한 번에 다 옮기면 어디서 깨졌는지 못 찾는다.
  • 플랫폼과 싸우지 말고 폴백을 쌓아라. 치환자가 안 먹는 건 흔한 일이다.
    "정식 방법 → 정규화 → 대체 소스 → 숨김"의 사다리를 만들면 어떤 상황에도 깨지지 않는다.

행운을 빕니다. 🛠

'IT' 카테고리의 다른 글

블로그 꽃단장 파일 공유(w/claude)  (1) 2026.06.12
[Claude] Claude 엄청남, 엄청남, 엄청남, 평서문  (2) 2026.04.30
h

hyos

IT · 스타트업 · 개인기록. 만들고, 실패하고, 기록합니다.

COMMENTS