TOC테스트중

Sur3원, h3r3하 y0u는 ar3대 g00d로 s3r해.
Casper Post 페이지, “인젝션”으로 바로 체감나는 4가지 핵심 패치
Casper의 포스트 본문 컨테이너는 .gh-content
를 쓰고(테마의 post.hbs
기반), Code Injection으로 덮어씌우는 건 표준 전술이다. 아래 4가지는 UI/UX 효용 대비 리스크가 낮고, 최신 Casper 흐름과도 호환된다. (유령, GitHub)
스텝-바이-스텝(요약)
- Ghost Admin → Settings → Code injection → Site header에 아래 “인젝션 번들” 통째로 붙여넣기.
- 새로고침 후 포스트 하나 열고 확인.
- 필요 시 변수/옵션만 미세 조정.
4가지 코드, 왜 쓰는가
- 가독 리듬 & 열 폭 정렬(typography grid)
- 목표: 줄길이 66~75자, 행간 1.7대, 문단·리스트 간격을 정규화해 읽기 피로 감소.
- 포인트:
.post-template .gh-content
범위만 타깃팅해 홈/태그 레이아웃과 충돌 없음.
- 자동 목차(TOC) + 앵커링
- 목표: H2/H3에서 자동으로 목차 생성, 데스크톱에선 우측 스티키, 모바일에선 상단 블록.
- 포인트: Casper가 쓰는
.gh-content
기준으로 헤딩을 스캔해 목차를 비침투식으로 삽입. (유령)
- 스크롤 리딩 프로그레스 바
- 목표: 긴 글에서 현재 진행률을 1픽셀도 안 되는 얇은 바에 시각화 → 몰입감 증가.
- 포인트: 본문 시작~끝 기준으로 계산, 헤더 겹침 방지(z-index) 처리.
- 미디어(이미지/갤러리) & 코드블록 가독 보강
- 목표: 이미지 라운딩/캡션 정리, 갤러리 거터 균일화, 코드블록 복사 버튼으로 실용성 업.
- 포인트:
.kg-image-card / .kg-gallery-card / pre > code
등 Ghost 기본 클래스를 보수적 선택자로 스타일링.
참고: Casper는 Code Injection으로 레이아웃·스타일을 보강하는 사례가 널려 있고(포스트 헤더 높이/피처드 이미지 등), 굳이 테마 파일을 뜯지 않아도 충분히 간다. (Ghost Forum)
인젝션 번들(그냥 복붙)
위치: Settings → Code injection → Site header
역할: ①가독/열폭 ②자동 목차 ③프로그레스 바 ④미디어·코드 보강
<style>
/* ===== 0) 공통: 포스트 페이지 한정 스코프 ===== */
.post-template :root { --accent: var(--ghost-accent-color, #5e81ac); }
/* ===== 1) 가독 리듬 & 열폭 ===== */
.post-template .gh-content{
max-width: 72ch; /* 열 폭 */
line-height: 1.75; /* 행간 */
font-size: clamp(1rem, 0.96rem + 0.3vw, 1.08rem);
word-break: keep-all;
}
.post-template .gh-content p { margin: 0 0 1.05em; }
.post-template .gh-content h2,
.post-template .gh-content h3,
.post-template .gh-content h4{
line-height: 1.25;
margin: 2.1em 0 .65em;
scroll-margin-top: 96px; /* 앵커 점프시 헤더 가림 방지 */
}
.post-template .gh-content li{ margin: .35em 0; }
.post-template .gh-content blockquote{
border-left: 3px solid var(--accent);
padding-left: 1rem; opacity:.95;
}
/* ===== 2) 자동 TOC 스타일 ===== */
.post-template .gh-toc{
border: 1px solid rgba(0,0,0,.08);
border-radius: 10px; padding: .9rem 1rem;
background: rgba(127,127,127,.06);
font-size: .95rem;
margin: 1rem 0 1.5rem;
}
.post-template .gh-toc .gh-toc-title{ font-weight: 700; margin-bottom: .4rem; }
.post-template .gh-toc ol{ margin: 0; padding-left: 1.1rem; }
.post-template .gh-toc li{ margin: .25rem 0; }
@media (min-width: 1100px){
.post-template .gh-toc{
float: right; max-width: 260px;
margin: 0 0 1rem 2rem; position: sticky; top: 96px;
}
}
/* ===== 3) 리딩 프로그레스 바 ===== */
#reading-progress{
position: fixed; top: 0; left: 0; height: 3px; width: 0%;
background: var(--accent); z-index: 9999; transition: width .08s linear;
}
/* ===== 4) 미디어 & 코드 보강 ===== */
.post-template .gh-content figure.kg-image-card img{ border-radius: 10px; }
.post-template .gh-content figure.kg-gallery-card{ --gap: 8px; }
.post-template .gh-content figure.kg-gallery-card .kg-gallery-image img{ border-radius: 8px; }
.post-template .gh-content figure figcaption{
color: rgba(0,0,0,.6); font-size: .9em; margin-top: .4rem; text-align: center;
}
.post-template pre{
position: relative; overflow: auto; border-radius: 10px;
padding-top: 2.4rem; /* 복사 버튼 영역 확보 */
}
.post-template .gh-copy{
position: absolute; top: .5rem; right: .5rem;
border: 1px solid rgba(0,0,0,.12); background: rgba(255,255,255,.85);
padding: .35rem .6rem; border-radius: 8px; font-size: .9rem; cursor: pointer;
}
@media (prefers-color-scheme: dark){
.post-template .gh-toc{ background: rgba(255,255,255,.04); border-color: rgba(255,255,255,.12); }
.post-template .gh-copy{ background: rgba(0,0,0,.35); color: #fff; border-color: rgba(255,255,255,.22); }
}
</style>
<script>
/* ===== 안전 가드 ===== */
(function(){
if(!document.body.classList.contains('post-template')) return;
/* === 2) 자동 TOC 생성 === */
const content = document.querySelector('.gh-content');
if(content){
const headings = content.querySelectorAll('h2, h3');
if(headings.length){
// id 없으면 만들어주기
headings.forEach(h => {
if(!h.id){
const slug = h.textContent.trim()
.toLowerCase().replace(/[^\w가-힣]+/g,'-').replace(/-+/g,'-').replace(/^-|-$/g,'');
h.id = slug || ('h-' + Math.random().toString(36).slice(2,8));
}
});
// 목차 DOM
const toc = document.createElement('nav');
toc.className = 'gh-toc';
const title = document.createElement('div');
title.className = 'gh-toc-title';
title.textContent = 'On this page';
const list = document.createElement('ol');
headings.forEach(h => {
const li = document.createElement('li');
if(h.tagName === 'H3') li.style.marginLeft = '0.6rem';
const a = document.createElement('a');
a.href = '#' + h.id;
a.textContent = h.textContent.trim();
li.appendChild(a); list.appendChild(li);
});
toc.appendChild(title); toc.appendChild(list);
// 본문 최상단에 삽입
content.insertBefore(toc, content.firstElementChild);
}
}
/* === 3) 리딩 프로그레스 바 === */
const bar = document.createElement('div');
bar.id = 'reading-progress';
document.body.appendChild(bar);
function updateProgress(){
const target = document.querySelector('.gh-content');
if(!target) return;
const rect = target.getBoundingClientRect();
const start = window.scrollY + rect.top;
const end = start + target.offsetHeight - window.innerHeight;
const ratio = Math.max(0, Math.min(1, (window.scrollY - start) / Math.max(1, end - start)));
bar.style.width = (ratio * 100).toFixed(2) + '%';
}
window.addEventListener('scroll', updateProgress, {passive: true});
window.addEventListener('resize', updateProgress);
updateProgress();
/* === 4) 코드블록 복사 버튼 === */
document.querySelectorAll('pre > code').forEach(code => {
const pre = code.parentElement;
const btn = document.createElement('button');
btn.className = 'gh-copy'; btn.type = 'button';
btn.textContent = 'Copy';
btn.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(code.innerText);
const old = btn.textContent; btn.textContent = 'Copied'; setTimeout(()=>btn.textContent=old, 1200);
} catch(e){
// fallback
const range = document.createRange(); range.selectNodeContents(code);
const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range);
document.execCommand('copy'); sel.removeAllRanges();
const old = btn.textContent; btn.textContent = 'Copied'; setTimeout(()=>btn.textContent=old, 1200);
}
});
pre.appendChild(btn);
});
})();
</script>
적용 팁(실무 감각)
- 충돌 최소화 규칙: 전부
.post-template
프리픽스로 묶었고, 테마 업데이트에도 영향권은 Post 화면으로 제한된다. - 색상 일치:
--ghost-accent-color
를 자동 참조한다(브랜드 설정과 동기화). 필요하면 상단의--accent
만 변경. - 목차 헤딩 범위: 기본
H2/H3
만 스캔한다. 더 깊게 원하면 쿼리셀렉터에h4
를 추가. - 퍼포먼스: 스크롤 핸들러는 가벼운 계산만 한다. 이미지가 매우 많다면
updateProgress()
에requestAnimationFrame
래핑 정도만 더하면 충분.
왜 이 4가지인가(디자인 관점)
- 가독 리듬은 체류시간·이탈률을 바로 건드리는 1순위. 폰트 교체보다 행간/열폭/간격이 먼저다.
- TOC는 긴 글 탐색의 “점프 포인트”를 만든다. 스티키로 두면 사이드바처럼 작동. Casper가 쓰는
.gh-content
기준이라 튼튼하다. (유령) - 프로그레스 바는 스크롤 의식화 → 완독률에 직접 영향. 매우 가볍고 시각적 간섭이 적다.
- 미디어·코드 보강은 전문성 신호. 이미지와 코드가 깔끔하면 신뢰도가 바로 올라간다.
참고 소스
- Casper 테마 구조/파일(README, post.hbs 등), 최신 릴리스 레일(5.8.1 기준). (GitHub)
- Ghost 공식 튜토리얼(TOC:
.gh-content
,.gh-toc
전제). (유령) - Code Injection으로 레이아웃/스타일 보정 사례(포스트 헤더/높이 등). (Ghost Forum)
원하면 네 상황(폰트/컬러/여백 선호)에 맞춰 위 번들을 **“토글 가능한 옵션 세트”**로 쪼개 드릴 수 있다. 지금은 한 방에 체감을 내는 구성이니, 우선 깔고 체감부터 확인해.