블로그 부분은 아직 모바일 최적화 코드 구조화정리 등도 안된 상태이지만 블로그 자체만으로 먼저 오픈할게 아니라, 결국 웹진과 매거진 툴 개발 후 함께 오픈할거에요. 웹진 개발이 거의 중단된 이유가 xslide 편집 소스를 그대로 가져다 쓰는 바람에 꼬이게 되었는데, 웹진 편집툴을 완전 원점에서 다시 시작해야해요. 웹진은 편집의 용이성에 중점을 둬야하기 때문에 과도한 애니메이션이 필요하까하는 측면에서 GSAP 라이브러리를 쓸지아니면 단순 상하좌우 슬라이드 정도의 자바스크립트 애니메이션으로만 해도 될지 판단을 해야되고, 웹진은 한 페이지 편집, 매거진은 20페이지 등 판형과 페이지를 미리 정하고 잡지처럼 편집 후 동적인 이북, 모바일 최적화, 페이지별 pdf 저장 후 소량 인쇄, 아니면 계약 후 인쇄용은 인디자인 편집 방식으로 가려고 해요.

---

xZine 편집기 설계 — 원점 재시작 가이드

웹진 vs 매거진 — 완전히 다른 두 제품

먼저 이 둘을 분리해서 생각해야 합니다.

구분 웹진 (Webzine) 매거진 (Magazine)
편집 단위 기사 1건 = 1편집 화면 전체 호(號) = N페이지 묶음
판형 자유 스크롤 (웹 뷰) A4/A5 고정 캔버스
저장 구조 HTML/JSON 1건 pages[] JSON 배열
출력 URL 공유, 모바일 뷰 이북 뷰어 + PDF
편집 복잡도 낮음 높음 (인디자인급)
개발 순서 1순위 2순위

GSAP 쓸지 말지 — 결론부터

웹진 편집기: GSAP 불필요
웹진 뷰어(발행 후 독자가 보는 화면): CSS transition으로 충분
매거진 이북 뷰어: GSAP 권장 (페이지 플리핑 효과)

편집기 안에서 GSAP = 복잡도만 올라감, 편집 UX에 기여 없음
발행된 콘텐츠 뷰어 = GSAP 쓸 이유 있음

xSlide 편집기가 꼬인 이유가 바로 "편집기 안에 GSAP 렌더링까지 넣으려 했기 때문" 입니다. 편집기와 뷰어를 분리하는 게 핵심입니다.


웹진 편집기 — 새 설계

기존 xSlide를 가져다 쓰지 말고, 용도에 맞는 단순한 구조로 새로 시작합니다.

웹진 1페이지 = 섹션(Section) 블록들의 수직 스택

┌─────────────────────────────┐
│ [섹션 1] 커버 이미지 + 제목 │ ← 블록 타입: hero
├─────────────────────────────┤
│ [섹션 2] 본문 텍스트 │ ← 블록 타입: text
├─────────────────────────────┤
│ [섹션 3] 이미지 + 캡션 │ ← 블록 타입: image
├─────────────────────────────┤
│ [섹션 4] 인용구 │ ← 블록 타입: quote
├─────────────────────────────┤
│ [섹션 5] 2단 레이아웃 │ ← 블록 타입: columns
└─────────────────────────────┘

저장 구조 (JSON):

{
"title": "6월호 커버스토리",
"cover_image": "/uploads/...",
"sections": [
{ "type": "hero", "image": "...", "title": "제목", "subtitle": "부제" },
{ "type": "text", "content": "<p>...</p>", "typography": "body" },
{ "type": "image", "src": "...", "caption": "설명", "align": "full" },
{ "type": "columns", "ratio": "60/40", "left": {...}, "right": {...} },
{ "type": "quote", "text": "...", "author": "..." }
],
"theme": { "primary": "#1a7a55", "font": "Pretendard" }
}

이 구조의 장점:

  • 블록 추가/삭제/순서 변경 = 배열 조작
  • 모바일 자동 대응 (수직 스택이라 반응형 자연스러움)
  • xSlide 캔버스와 완전 무관 — 꼬일 이유 없음
  • 블록 타입 추가가 쉬움

편집기 참고 모델: Notion, Substack 편집기처럼 블록 기반. xSlide처럼 자유 드래그&드롭 캔버스가 아닌, 순서 있는 섹션 편집기.


매거진 편집기 — 설계 방향

2단계이므로 지금 당장 구현하지 않지만, 설계는 미리 잡아야 합니다.

핵심 원칙: A4 고정 캔버스 × N페이지

xSlide의 1920×1080 캔버스 → A4(794×1123px @96dpi) 캔버스로 전환
xSlide의 slides[] 배열 → magazine.pages[] 배열 (구조 동일)

xSlide 코드를 직접 가져다 쓰지 말고, 핵심 개념(캔버스 + 오브젝트 배열)만 참고해서 A4 매거진 전용으로 새로 작성하는 게 맞습니다.

PDF 출력 전략:

웹 편집 → JSON 저장
[경로 A] 웹 뷰어 → 브라우저 인쇄(Ctrl+P) → PDF 저장
└ print CSS 완벽하게 작성하면 품질 충분
[경로 B] 서버사이드 Puppeteer → 고품질 PDF 자동 생성
└ 소량 인쇄용 (디지털 인쇄소 납품 가능 수준)
[경로 C] 인디자인 IDML 내보내기 (장기 목표)
└ 대량 인쇄 계약 시 디자이너에게 넘기는 용도

경로 C(IDML)는 복잡하니 나중 얘기고, 경로 A+B로 소량 인쇄 커버하는 게 현실적입니다.


이북 뷰어 — 기술 선택

추천: turn.js 또는 순수 CSS 3D flip

// 옵션 1: turn.js (jQuery 기반, 검증된 이북 라이브러리)
// 장점: 안정적, 터치 지원
// 단점: jQuery 의존성
// 옵션 2: GSAP Flip으로 직접 구현
// 장점: X시리즈 일관성, 세밀한 제어
// 단점: 개발 공수
// 옵션 3: CSS perspective + transform (순수 CSS)
// 장점: 의존성 없음
// 단점: 모바일 터치 스와이프 직접 구현 필요

추천: 이북 뷰어는 turn.js 쓰는 게 가장 빠릅니다. GSAP은 웹진 뷰어 입장 애니메이션 정도에만 씁니다.


개발 순서 최종 정리

Phase 0 — 지금 (2주)
├── 기존 xzine.kr 코드 정리 (editor_xzine.php의 xSlide iframe 방식 제거)
├── 웹진 섹션 블록 JSON 구조 확정
└── 블록 타입 목록 확정 (hero, text, image, columns, quote, embed, divider)
Phase 1 — 웹진 편집기 (4~6주)
├── 섹션 블록 편집기 (drag-to-reorder + 블록별 설정 패널)
├── 이미지 업로드 + 크롭
├── 리치텍스트 (Tiptap 또는 Quill — 가벼운 것)
├── 테마/폰트/컬러 설정
└── 모바일 최적화 뷰어 (CSS transition 입장 효과)
Phase 2 — 블로그+SEO+AI (Phase 1과 병행, 3~4주)
├── SEO 메타태그 완성 + sitemap.xml
├── 구글 Indexing API 연동
├── AI 글쓰기 보조 (Claude API)
└── SNS 자동 발행
Phase 3 — 매거진 편집기 (8~12주)
├── A4 캔버스 + pages[] 구조
├── 오브젝트 자유 배치 편집기
├── turn.js 이북 뷰어
└── Puppeteer PDF 내보내기

한 가지 제안

웹진 편집기를 만들기 전에 블록 타입 목록을 먼저 확정하는 게 중요합니다. 나중에 블록 타입 추가는 쉽지만, 중간에 JSON 구조를 바꾸면 기존 저장된 웹진이 깨집니다.

----------

웹진도 1페이지이지만 텍스트, 이미지, 도형, 아이콘 등이 레이어 방식으로 쌓이고 블로그의 게시물 불러오기 등 블럭 형식으로 쌓아가는 구조로 편집이 이루어져야 돼요. 여기까지는 워드정도 사용하는 일반 직원이 쉽게 편집할 수 있게하고, 매거진은 그야말로 잡지 편집이니까 자체적으로 어려우니까 우리가 월단위 계약을 해서 편집회의 등 과정을 거쳐 제대로된 잡지처럼 온라인, 인쇄까지 처리할 예정이에요. 그래서 일단 일반인이 쉽게 사용할 수 있는 웹진 제작이 관건이에요.

----

이미 상당히 잘 만들어진 편집기네요. 2,943줄의 코드를 확인했습니다. 현재 상태와 핵심 문제를 정확히 짚어드립니다.


현재 webzine_editor.php 상태 분석

이미 구현된 것 (잘 되어 있음):

  • 레이어 캔버스 + 드래그/리사이즈/회전 핸들
  • 텍스트/이미지/도형/아이콘/게시물 블록 5종
  • 우측 속성 패널 (디자인/정렬/애니메이션 탭)
  • Grid/Snap, PC/iPad/Mobile 뷰 전환
  • GSAP 등장 애니메이션 패널

꼬인 원인 (추정):
xSlide iframe 방식( editor_xzine.php  <iframe src="xslide/index.html"> )과 이 레이어 캔버스 방식이 두 벌 혼재하면서 저장/로드 JSON 구조가 충돌했을 것입니다.


GSAP 쓸지 말지 — 최종 답변

편집기 안 → GSAP 불필요 (이미 있어도 제거 권장)
뷰어 등장 효과 → CSS transition으로 충분 (fade, slide-up)
뷰어 페이지 전환→ GSAP 쓰면 좋음 (나중 매거진 이북)

현재 애니메이션 패널은 기능으로는 유지하되, 일반 직원용 편집 시 "애니메이션 탭은 고급 기능"으로 숨기거나 맨 뒤로 두면 됩니다. 핵심은 편집 UX이지 애니메이션이 아닙니다.


"일반 직원이 쉽게" 만들기 — 4가지 핵심

① 템플릿으로 시작 (가장 중요)

빈 캔버스에서 시작하면 누구나 막힙니다. 편집 전 템플릿 선택 화면이 필수입니다.

[새 웹진 만들기] 클릭
┌──────────────────────────────────────────┐
│ 템플릿 선택 │
│ │
│ [커피숍 소식지] [기업 뉴스레터] [학교 소식지]│
│ [행사 안내] [제품 소개] [빈 캔버스] │
└──────────────────────────────────────────┘
선택한 템플릿 JSON → 편집기 로드 → 텍스트/이미지만 교체하면 완성

구현:  webzine_templates  테이블에 미리 만든 JSON 저장, 선택 시  design_data 에 복사해서 시작.


② Undo/Redo (Ctrl+Z)

레이어 편집에서 실수했을 때 되돌리기가 없으면 일반인은 포기합니다. 현재 코드에 없는 것 중 가장 먼저 추가해야 할 것입니다.

// 히스토리 스택 (현재 없음 — 추가 필요)
const history = [];
let historyIndex = -1;
function pushHistory() {
const state = serializeCanvas(); // 현재 레이어 상태 직렬화
history.splice(historyIndex + 1);
history.push(state);
historyIndex++;
if (history.length > 50) { history.shift(); historyIndex--; }
}
function undo() { /* historyIndex-- → loadCanvas(history[historyIndex]) */ }
function redo() { /* historyIndex++ → loadCanvas(history[historyIndex]) */ }
// 모든 레이어 변경 후 pushHistory() 호출

③ 레이어 패널 (Layer List)

지금은 캔버스를 클릭해야 레이어를 선택할 수 있는데, 요소가 겹치면 일반인은 뒷 레이어를 클릭할 수 없어서 막힙니다.

왼쪽 툴바 아래에 레이어 목록 패널 추가:

┌──────────┐
│ 레이어 │
├──────────┤
│ 👁 이미지 │ ← 클릭하면 선택
│ 👁 텍스트 │
│ 👁 도형 │
│ 👁 배경 │
└──────────┘

드래그로 순서 변경 (Sortable.js 이미 로드되어 있음 → 바로 활용 가능).


④ 저장/로드 안정화 (꼬임 해결의 핵심)

현재  saveWebzine()  함수가 있지만 직렬화 로직이 안정적인지가 핵심입니다. 저장 JSON 구조를 명확하게 고정해야 합니다.

// 저장 시 직렬화 (serializeCanvas)
function serializeCanvas() {
const layers = [];
document.querySelectorAll('#canvasFrame .layer-element').forEach(el => {
const type = el.getAttribute('data-type');
const layer = {
id: el.id || ('layer_' + Date.now()),
type,
x: parseFloat(el.style.left), // % 단위
y: parseFloat(el.style.top), // px 단위
w: parseFloat(el.style.width), // % 단위
h: el.offsetHeight,
locked: el.getAttribute('data-locked') === 'true',
anim: el.getAttribute('data-anim') || '',
};
// 타입별 추가 속성
if (type === 'text') {
layer.content = el.querySelector('.text-content-wrapper')?.innerHTML;
layer.font = el.getAttribute('data-font');
layer.size = el.getAttribute('data-size');
// ... 기타 텍스트 속성
} else if (type === 'image') {
layer.src = el.querySelector('img')?.src;
layer.fit = el.querySelector('img')?.style.objectFit;
}
// ... shape, icon, post
layers.push(layer);
});
return {
canvas: {
h: document.getElementById('canvasFrame').style.minHeight,
bg: document.getElementById('canvasFrame').style.background,
},
layers
};
}

저장 구조가 고정되면 로드( loadCanvas )도 안정됩니다.


개발 순서 제안

이번 주
├── iframe 방식 (editor_xzine.php) 완전 제거
├── saveWebzine() / loadWebzine() JSON 직렬화 안정화
└── 저장 → 목록 → 재편집 사이클 완성
다음 주
├── Undo/Redo 추가
├── 레이어 패널 (Sortable.js 활용)
└── 템플릿 3종 이상 제작
3주차
├── 뷰어 페이지 (저장된 JSON → 모바일 최적화 렌더링)
└── 미리보기 실제 동작 연결

지금 당장 시작할 것을 고른다면:  saveWebzine()  +  loadWebzine()  JSON 구조 확정과 저장→재편집 사이클을 먼저 완성하는 것입니다. 편집기가 아무리 좋아도 저장이 불안정하면 아무 의미가 없으니까요.