Add remaining samples, tooling, and local project assets
No files matched your search
@@ -56,7 +56,76 @@
|
||||
"Bash(head -20 /d/ad-hoc/kei/design_agent/samples/mdx/01*.mdx)",
|
||||
"Bash(head -20 /d/ad-hoc/kei/design_agent/samples/mdx/02*.mdx)",
|
||||
"Bash(head -20 /d/ad-hoc/kei/design_agent/samples/mdx/03*.mdx)",
|
||||
"Bash(ls -la /d/ad-hoc/kei/design_agent/data/runs/20260413_*/)"
|
||||
"Bash(ls -la /d/ad-hoc/kei/design_agent/data/runs/20260413_*/)",
|
||||
"Bash(python -c \"from src.config import settings; print\\(f'API configured: {bool\\(settings.anthropic_api_key\\)}'\\)\")",
|
||||
"Bash(python run_test.py)",
|
||||
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8000/health)",
|
||||
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8000/)",
|
||||
"Read(//d/ad-hoc/kei/**)",
|
||||
"Bash(curl -s http://localhost:8000/docs)",
|
||||
"Bash(taskkill //F //IM python.exe)",
|
||||
"Bash(python assemble_mdx02_test.py)",
|
||||
"Bash(wc -c data/runs/20260407_*/final.html)",
|
||||
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8000/docs)",
|
||||
"Bash(curl -s http://localhost:8080/docs -o /dev/null -w \"%{http_code}\")",
|
||||
"Bash(curl -s http://localhost:8001/docs -o /dev/null -w \"%{http_code}\")",
|
||||
"Bash(curl -s http://localhost:3000/ -o /dev/null -w \"%{http_code}\")",
|
||||
"Bash(python assemble_mdx02_v3.py)",
|
||||
"Bash(python assemble_mdx02_v4.py)",
|
||||
"Bash(python assemble_mdx02_v5.py)",
|
||||
"Bash(python assemble_mdx02_v6.py)",
|
||||
"Bash(python assemble_mdx02_v7.py)",
|
||||
"Bash(python assemble_mdx02_v8_3plans.py)",
|
||||
"Bash(python assemble_mdx02_v9.py)",
|
||||
"Bash(python build_plan1.py)",
|
||||
"Bash(python build_plan2.py)",
|
||||
"Bash(python build_plan3.py)",
|
||||
"Bash(python build_plan1_v2.py)",
|
||||
"Bash(python build_4plans_final.py)",
|
||||
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://127.0.0.1:8000/docs)",
|
||||
"Bash(uvicorn backend.main:app --port 8000)",
|
||||
"Bash(python final_plan1.py)",
|
||||
"Bash(python final_plan2.py)",
|
||||
"Bash(python build_all_4plans.py)",
|
||||
"Bash(python build_plan3_kei.py)",
|
||||
"Bash(python build_plan4_kei.py)",
|
||||
"Bash(python make_4plans.py)",
|
||||
"Bash(ls -la \"d:/ad-hoc/kei/design_agent/figma_to_html_agent/block-tests/html_render_final\"*)",
|
||||
"Bash(taskkill //PID 48540 //F)",
|
||||
"Bash(python make_mdx03.py)",
|
||||
"Bash(python run_mdx03_pipeline.py)",
|
||||
"Bash(powershell -Command \"Get-Process python -ErrorAction SilentlyContinue | Select-Object Id,StartTime\")",
|
||||
"Bash(grep \"class FontHierarchy\" src/*.py)",
|
||||
"Bash(ls -ltr /d/ad-hoc/kei/design_agent/data/runs/*/step_*_context.json)",
|
||||
"Bash(awk '{print $2}')",
|
||||
"Bash(stat /d/ad-hoc/kei/design_agent/data/runs/20260414_120225/stage_*_context.json)",
|
||||
"Bash(ls -la /d/ad-hoc/kei/design_agent/data/runs/20260414_120225/stage_*_context.json)",
|
||||
"Bash(awk '{print $6, $7, $8, $9}')",
|
||||
"Bash(ls -lt data/runs/20260414_120225/*_context.json)",
|
||||
"Bash(awk '{print $6,$7,$8,$9}')",
|
||||
"Bash(python -c \" import yaml with open\\('catalog.yaml'\\) as f: data = yaml.safe_load\\(f\\) blocks = data.get\\('blocks', []\\) for b in blocks: print\\(f\\\\\"{b['id']} | {b.get\\('category',''\\)} | items:{b.get\\('min_items','?'\\)}-{b.get\\('max_items','?'\\)}\\\\\"\\) \")",
|
||||
"Bash(python add_tags.py)",
|
||||
"Bash(python -c \"import src.block_reference; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.block_assembler; print\\('OK'\\)\")",
|
||||
"Bash(ls -t d:/ad-hoc/kei/design_agent/docs/history/PHASE-*.md d:/ad-hoc/kei/design_agent/docs/history/IMPROVEMENT-PHASE-*.md)",
|
||||
"Bash(python -c \"import src.pipeline_context; import src.kei_client; import src.pipeline; print\\('모든 import OK'\\)\")",
|
||||
"Bash(python -c \"import src.step_visualizer; import src.pipeline; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; import src.block_assembler; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.block_assembler; import src.pipeline; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.kei_client; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; import src.block_assembler; import src.pipeline_context; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; import src.validators; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.validators; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.validators; import src.pipeline; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; import src.section_parser; import src.block_assembler; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; import src.block_assembler; import src.section_parser; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.pipeline; import src.block_assembler; import src.space_allocator; import src.pipeline_context; print\\('OK'\\)\")",
|
||||
"Bash(grep -l \"pp2-grid-wrap\\\\|pp2\" templates/blocks/**/*.html)",
|
||||
"Bash(echo file:///D:/ad-hoc/kei/design_agent/data/runs/20260415_110323/final.html)",
|
||||
"Bash(python -c \"import src.block_reference; import src.section_parser; import src.pipeline; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.block_assembler; import src.section_parser; import src.pipeline; print\\('OK'\\)\")",
|
||||
"Bash(python -c \"import src.block_reference; import src.pipeline; import src.block_assembler; print\\('OK'\\)\")"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"d:\\ad-hoc\\kei\\design_agent\\templates\\blocks\\new"
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Legacy / 임시 파일
|
||||
block_index.faiss
|
||||
block_metadata.json
|
||||
figma_beps_full.json
|
||||
figma_center_elements.json
|
||||
figma_center_full.json
|
||||
figma_frames.json
|
||||
figma-analysis/
|
||||
figma-assets/
|
||||
figma-screenshots/
|
||||
figma_ref/
|
||||
previews/
|
||||
FIGMA-COMPONENT-EXTRACTION-PLAN.md
|
||||
FIGMA-CONVERSION-REVIEW.md
|
||||
FIGMA-DESIGN-LANGUAGE.md
|
||||
FIGMA-EXTRACTION.md
|
||||
INSIGHT-GRADIENT.md
|
||||
PHASE-FIGMA-BLOCKS.md
|
||||
PLAN.md
|
||||
RESEARCH.md
|
||||
|
||||
# Selenium 렌더 결과 / 비교 스크린샷
|
||||
block-tests/_renders/
|
||||
block-tests/*.png
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
scripts/__pycache__/
|
||||
@@ -19,7 +19,7 @@ Figma 프레임을 **수학적으로 정확하게** HTML/CSS로 변환하고,
|
||||
6. **AI 역할 분담** — AI는 분류(고르기)만, 구성(만들기)은 코드. LLM은 px을 못 본다
|
||||
7. **컨텍스트 관리는 compact로** — 한 세션에서 여러 프레임을 연속 작업할 수 있다. 컨텍스트가 무거워지면 `/compact` 로 핵심만 요약하고 계속 진행. 이유: 핵심 결정/구조/규칙은 모두 파일(CLAUDE.md, PROCESS.md, RULES.md, blocks_index.md, 산출물)에 박혀있어 compact 후에도 보존됨. 손실되는 건 시행착오/디버깅 과정 뿐이며, 이건 잃어도 OK. 매 프레임마다 새 세션을 강제하면 누적 학습이 silo되어 R13 같은 sub-pattern 발견의 즉시 적용이 불가능해짐.
|
||||
8. **순수 CSS 우선, SVG는 곡선/필터에만** — 동적 재구성 위해 가능한 한 HTML div + linear-gradient 사용
|
||||
9. **프로모션 게이트는 사용자 전용** — 에이전트는 절대 `design_agent/templates/` 에 직접 쓰지 않는다. 모든 작업은 `figma_to_html_agent/` 안에서 끝나며, 본체 라이브러리 이전은 사용자 수동 검수 후 사용자 본인이 수행한다.
|
||||
9. **프로모션은 사용자 승인 후 진행** — 1:1 HTML 검증 완료 후, 사용자 승인을 받아 `templates/blocks/new/`에 블록을 생성하고 `templates/blocks/svg/`에 이미지를 정리한다. catalog.yaml 등록과 blocks_index.md 업데이트까지 에이전트가 수행한다. 단, 사용자 승인 없이 프로모션하지 않는다.
|
||||
10. **시맨틱 우선, Figma 평면 레이어 그대로 옮기지 말 것** — Figma의 평면 레이어 구조는 디자인 도구의 한계일 뿐, 의미 구조가 아니다. 마커+텍스트는 list item, 카드 묶음은 column unit, 등 시맨틱하게 재그룹핑하여 작성한다. RULES.md R13 (Custom-marker bullet list) 참조. 새로 발견되는 sub-pattern은 [blocks_index.md](blocks_index.md) "디자인 인사이트" 섹션에 누적한다.
|
||||
11. **모든 슬롯은 기본 optional** — 1:1 단계에서 모든 슬롯이 채워져 있다고 해서 "이 블록은 필수" 로 해석하지 않는다. 같은 블록이 사진 없는/짧은/긴 mdx에 모두 매칭되어야 한다는 가정으로 설계한다.
|
||||
|
||||
@@ -60,12 +60,18 @@ Figma 프레임을 **수학적으로 정확하게** HTML/CSS로 변환하고,
|
||||
## 입출력
|
||||
|
||||
**입력:** Figma 파일 + 노드 ID (또는 현재 선택 노드)
|
||||
**출력:**
|
||||
- `block-tests/{slug}.html` — 변환 결과
|
||||
**출력 (변환):**
|
||||
- `block-tests/{slug}.html` — 1:1 변환 결과
|
||||
- `block-tests/{slug}_flat.md` — 플래튼/이상 탐지/변형 축 메모
|
||||
- `assets/shared/...` — 공유 자산 캐시
|
||||
- `blocks_index.md` 한 줄 추가
|
||||
|
||||
**출력 (프로모션, 사용자 승인 후):**
|
||||
- `templates/blocks/new/{pattern}.html` — AI가 재디자인 가능한 블록 (CSS + Jinja2)
|
||||
- `templates/blocks/svg/{name}.png/svg` — 블록 공용 이미지
|
||||
- `templates/catalog.yaml` — 블록 등록 (content_structure, when, not_for)
|
||||
- `blocks_index.md` 상태 → promoted
|
||||
|
||||
## 폴더 구조
|
||||
|
||||
```
|
||||
@@ -73,9 +79,9 @@ figma_to_html_agent/ ← 에이전트 작업 영역 (staging)
|
||||
├── CLAUDE.md ← 이 파일 (에이전트 명세)
|
||||
├── PROCESS.md ← 10단계 운영 절차 (변환 핸드북)
|
||||
├── MATH.md ← 수학 공식 레퍼런스
|
||||
├── RULES.md ← CSS 보정 규칙 (R1~R12)
|
||||
├── RULES.md ← CSS 보정 규칙 (R1~R16)
|
||||
├── PROCESS-CONTROL.md ← "찍어맞추기 금지" 규칙
|
||||
├── PLAN.md ← 현재 진행 현황
|
||||
├── README.md ← 사용법 가이드
|
||||
├── blocks_index.md ← 변환 완료 도서관
|
||||
│
|
||||
├── scripts/
|
||||
@@ -90,22 +96,22 @@ figma_to_html_agent/ ← 에이전트 작업 영역 (staging)
|
||||
│ ├── shared/ ← 해시 기반 자산 캐시 (재사용)
|
||||
│ └── frame_{id}/ ← 프레임 전용 자산 (legacy)
|
||||
│
|
||||
└── templates_staging/ ← Stage 2: Jinja2 추상화
|
||||
├── {pattern_id}.html.j2
|
||||
└── {pattern_id}.meta.yaml ← when/slots/min_size_px 초안
|
||||
└── templates_staging/ ← Stage 2: Jinja2 추상화 (legacy, 현재 미사용)
|
||||
|
||||
────────────────────────────────────────────────────────
|
||||
🚧 프로모션 게이트 (사용자 수동 작업) 🚧
|
||||
프로모션 (사용자 승인 후 에이전트가 실행)
|
||||
────────────────────────────────────────────────────────
|
||||
|
||||
design_agent/ ← 본체 라이브러리 (에이전트 접근 금지)
|
||||
└── templates/
|
||||
├── blocks/{category}/
|
||||
│ └── {pattern_id}.html.j2 ← 사용자가 staging에서 이전
|
||||
└── catalog.yaml ← 사용자가 when/slots 등록
|
||||
design_agent/templates/
|
||||
├── blocks/
|
||||
│ ├── new/ ← Figma 추출 블록 (에이전트가 작성)
|
||||
│ ├── svg/ ← 블록 공용 이미지 (에이전트가 정리)
|
||||
│ ├── slide-base.html ← 고정 슬라이드 배경
|
||||
│ └── cards/, emphasis/, ... ← 기존 블록
|
||||
└── catalog.yaml ← 블록 등록 (에이전트가 업데이트)
|
||||
```
|
||||
|
||||
**중요:** 에이전트는 위 구분선 아래(`design_agent/templates/`)를 **절대 수정하지 않는다.** 그 영역은 사용자가 staging 결과물을 검수한 뒤 본인이 직접 프로모션한다.
|
||||
**중요:** 프로모션(블록 생성, 이미지 정리, catalog 등록)은 사용자가 1:1 HTML을 검증하고 승인한 후에만 진행한다. 사용자 승인 없이 templates/ 를 수정하지 않는다.
|
||||
|
||||
## 금지 사항
|
||||
|
||||
@@ -116,6 +122,8 @@ design_agent/ ← 본체 라이브러리 (에이전트 접
|
||||
- 블록 배경을 검정으로 표시 (미리보기는 항상 흰색 배경)
|
||||
- **이미지 해석으로 gradient 방향 판단** (멀티모달 금지, 데이터로만 판단 — PROCESS-CONTROL.md 참조)
|
||||
- **한 번에 여러 값 동시 수정** (gradient 각도와 border-radius 동시 변경 금지)
|
||||
- **장식 요소를 이미지 슬롯(img src)으로 넣기** — gradient bar, ribbon, pill, badge, 오버레이 등은 CSS로 구현. AI가 색상/크기를 조정할 수 없는 이미지 의존 블록은 쓸 수 없다
|
||||
- **사용자 승인 없이 templates/ 수정** — 프로모션은 사용자 승인 후에만
|
||||
- **여러 프레임을 한 세션에 변환** (1세션 1프레임 원칙)
|
||||
- **plus-darker 블렌드 사용** (Safari 전용 → multiply로 교체, RULES.md R10)
|
||||
- **Figma 인벤토리/지문/군집 같은 사전 분류** (work-creating-work, 패턴은 bottom-up으로 발견)
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
# Figma → HTML Agent
|
||||
|
||||
Figma 디자인 프레임을 **수학적으로 정확하게** HTML/CSS로 변환하고, design_agent의 **블록 라이브러리**로 축적하는 독립 에이전트.
|
||||
|
||||
---
|
||||
|
||||
## 이 에이전트는 뭘 하는가?
|
||||
|
||||
1. Figma 파일에서 프레임을 선택하면
|
||||
2. MCP(Figma Dev Mode)로 구조/스타일/스크린샷을 가져와서
|
||||
3. 수학적 계산(scale, gradient 변환, shadow 등)으로 1:1 HTML/CSS를 만들고
|
||||
4. AI가 재디자인할 수 있는 블록(순수 CSS + Jinja2 슬롯)으로 변환하여
|
||||
5. design_agent의 블록 라이브러리(`templates/blocks/`)에 편입한다
|
||||
|
||||
**핵심:** AI redesigner가 색상/크기/구조를 조정할 수 있도록, 장식 요소는 **CSS로 구현**하고 이미지는 crop/곡선/일러스트/사진만 남긴다.
|
||||
|
||||
---
|
||||
|
||||
## 폴더 구조
|
||||
|
||||
```
|
||||
figma_to_html_agent/
|
||||
├── README.md ← 이 파일 (사용법)
|
||||
├── CLAUDE.md ← 에이전트 명세 (11개 원칙)
|
||||
├── PROCESS.md ← 10단계 변환 절차 핸드북
|
||||
├── MATH.md ← 수학 공식 (§1~9)
|
||||
├── RULES.md ← CSS 보정 규칙 (R1~R16)
|
||||
├── PROCESS-CONTROL.md ← 작업 규율 (찍어맞추기 금지 등)
|
||||
├── blocks_index.md ← 변환 완료 도서관 + 디자인 인사이트
|
||||
│
|
||||
├── scripts/
|
||||
│ └── gradient_math.py ← SVG → CSS gradient 수학 변환 도구
|
||||
│
|
||||
├── block-tests/ ← 1:1 HTML 변환물 (원본 검증용)
|
||||
│ ├── {slug}.html ← Figma 원본과 100% 동일한 정적 HTML
|
||||
│ ├── {slug}_flat.md ← 플래튼/이상탐지/변형축 메모
|
||||
│ └── assets/ ← 이미지 에셋
|
||||
│ ├── shared/ ← 해시 기반 공유 캐시
|
||||
│ └── frame_{id}/ ← 프레임 전용 (legacy)
|
||||
│
|
||||
└── templates_staging/ ← Jinja2 템플릿 초안 (legacy, 현재 미사용)
|
||||
```
|
||||
|
||||
### 산출물 최종 위치 (design_agent 본체)
|
||||
|
||||
```
|
||||
design_agent/
|
||||
└── templates/
|
||||
└── blocks/
|
||||
├── new/ ← Figma 추출 블록 (8개)
|
||||
├── svg/ ← 블록 공용 이미지 (33개)
|
||||
├── slide-base.html ← 고정 슬라이드 배경 (16:9)
|
||||
└── cards/, emphasis/, headers/, media/, visuals/, BEPs/, redesign/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 사용법
|
||||
|
||||
### 사전 준비
|
||||
|
||||
1. **Figma Desktop** 앱에서 대상 파일을 열고 **Dev Mode** 활성화
|
||||
2. `.mcp.json`에 Figma Desktop SSE 서버 등록 확인:
|
||||
```json
|
||||
{ "url": "http://127.0.0.1:3845/sse" }
|
||||
```
|
||||
3. Claude Code 세션 시작
|
||||
|
||||
### 변환 실행 (10단계)
|
||||
|
||||
```
|
||||
STEP 0 에이전트가 blocks_index.md 읽기 → 기존 패턴 확인
|
||||
STEP 1 get_metadata — 구조 + bbox (XML)
|
||||
STEP 2 get_design_context — gradient/filter/font (React+Tailwind)
|
||||
STEP 3 get_screenshot — Figma 원본 PNG (검증용)
|
||||
STEP 4 자산 다운로드 — block-tests/assets/shared/ 캐시
|
||||
STEP 5 flat.md 작성 — bottom-up 플래튼 + 이상탐지 + 변형축
|
||||
STEP 6 gradient 수학 변환 — scripts/gradient_math.py
|
||||
STEP 7 HTML 작성 — 순수 CSS 우선, transform: scale() 균일 축소
|
||||
STEP 8 검증 — Figma 스크린샷과 비교
|
||||
STEP 9 저장 — block-tests/{slug}.html + flat.md
|
||||
STEP 10 인덱스 업데이트 — blocks_index.md 1줄 추가
|
||||
```
|
||||
|
||||
**상세:** [PROCESS.md](PROCESS.md) 참조
|
||||
|
||||
### 블록 프로모션 (block-tests → templates/blocks/)
|
||||
|
||||
1:1 HTML이 검증되면:
|
||||
|
||||
1. **CSS 전환** — SVG/PNG 장식 요소를 CSS gradient/border-radius/shadow로 변환
|
||||
- CSS 전환 대상: gradient bar, ribbon, pill, badge, 오버레이 등
|
||||
- 이미지 유지 대상: crop/프레임 배치(R16), 곡선 아크, 아이콘, 일러스트, 실사 사진
|
||||
2. **블록 작성** — `templates/blocks/new/{pattern}.html`
|
||||
- flex layout, width: 100%, Jinja2 슬롯
|
||||
- 수학적 계산 주석 (Figma px → scale → CSS 값)
|
||||
- 이미지는 `svg/` 폴더에서 참조 (`{{ slot | default('svg/파일명') }}`)
|
||||
3. **이미지 정리** — `templates/blocks/svg/`에 의미 있는 이름으로 저장
|
||||
4. **catalog.yaml 등록** — content_structure, when, not_for 작성
|
||||
5. **blocks_index.md 상태** — `staged` → `promoted` 업데이트
|
||||
|
||||
---
|
||||
|
||||
## 핵심 원칙 요약
|
||||
|
||||
| # | 원칙 | 설명 |
|
||||
|---|------|------|
|
||||
| 1 | 수학적 계산만 | Figma 좌표 → scale → CSS. 시행착오 px 조정 금지 |
|
||||
| 2 | Bottom-up | leaf 노드부터 → 2개씩 묶기 → 계층 쌓기 |
|
||||
| 3 | 순수 CSS 우선 | AI가 색상/크기 조정할 수 있게. SVG는 곡선/필터만 |
|
||||
| 4 | 이미지 해석 금지 | gradient 방향은 데이터로. "보니까 ~인 것 같다" 금지 |
|
||||
| 5 | 하드코딩 금지 | 결과물을 고치지 말고 프로세스를 고친다 |
|
||||
| 6 | 전면 재작성 금지 | 80점에서 2개 고칠 때 구조를 갈아엎지 말 것 |
|
||||
| 7 | 모든 슬롯 optional | 블록이 다양한 MDX에 매칭될 수 있게 |
|
||||
|
||||
**상세:** [CLAUDE.md](CLAUDE.md), [PROCESS-CONTROL.md](PROCESS-CONTROL.md), [RULES.md](RULES.md)
|
||||
|
||||
---
|
||||
|
||||
## 도구
|
||||
|
||||
| 도구 | 용도 |
|
||||
|------|------|
|
||||
| Figma MCP `get_metadata` | 프레임 구조 + 절대 좌표 (XML) |
|
||||
| Figma MCP `get_design_context` | gradient/filter/font (React+Tailwind 코드) |
|
||||
| Figma MCP `get_screenshot` | Figma 원본 PNG |
|
||||
| `scripts/gradient_math.py` | SVG linearGradient → CSS linear-gradient 수학 변환 |
|
||||
| Pillow | 이미지 픽셀 분석 (gradient 색상 추출, 이미지 해석 아님) |
|
||||
|
||||
---
|
||||
|
||||
## 수학 공식 요약
|
||||
|
||||
| 공식 | 용도 | 상세 |
|
||||
|------|------|------|
|
||||
| `S = 1280 / W_figma` | 균일 축소 scale | MATH.md §1 |
|
||||
| `CSS_angle = 90 - Figma_angle` | gradient 각도 변환 | MATH.md §2 |
|
||||
| SVG `<linearGradient>` → CSS | gradient stop % 변환 | gradient_math.py |
|
||||
| `plus-darker → multiply` | blend mode 호환 | RULES.md R10 |
|
||||
| bbox 비율 ≠ 텍스트 비율 → 회전 | rotation 감지 | RULES.md R11 |
|
||||
|
||||
---
|
||||
|
||||
## 현재 상태 (2026-04-13)
|
||||
|
||||
### 프로모션 완료 블록 (8개)
|
||||
|
||||
| 패턴 | 원본 프레임 | 블록 위치 |
|
||||
|------|-----------|---------|
|
||||
| statement-pill-highlight | 1171281207 | blocks/new/ |
|
||||
| stacked-arrow-list | 1171281180 | blocks/new/ |
|
||||
| split-panel-numbered | 1171281202 | blocks/new/ |
|
||||
| issues-paired-rows | 1171281194 | blocks/new/ |
|
||||
| compare-vs-rows | 1171281195 | blocks/new/ |
|
||||
| quadrant-2x2-issues | 1171281193 | blocks/new/ |
|
||||
| cards-3col-persona | 1171281191 | blocks/new/ |
|
||||
| cycle-3way-intersect | 1171281211 | blocks/new/ |
|
||||
|
||||
### 공용 이미지 (templates/blocks/svg/, 33개)
|
||||
|
||||
화살표, 아크, 뱃지, 아이콘, 배경 텍스처, 사진 등. 블록에서 `svg/파일명`으로 참조.
|
||||
|
||||
### 고정 슬라이드 배경
|
||||
|
||||
`templates/blocks/slide-base.html` — 16:9 (1280×720), 상단 제목 + 구분선 + 본문 영역 + 하단 결론 pill.
|
||||
@@ -111,11 +111,10 @@ Sub-patterns: **bullet-list-with-marker (R13)** — 각 컬럼의 bullet_items
|
||||
|
||||
---
|
||||
|
||||
## templates_staging 대기열
|
||||
## 프로모션 완료
|
||||
|
||||
> 2번째 등장한 패턴이 여기에 들어옴. 사용자 검수 후 design_agent 본체로 프로모션.
|
||||
|
||||
(아직 없음)
|
||||
> 8개 블록 모두 `templates/blocks/new/`로 프로모션 완료 (2026-04-13).
|
||||
> 이미지 33개 `templates/blocks/svg/`로 통합. catalog.yaml 등록 완료.
|
||||
|
||||
---
|
||||
|
||||
@@ -252,4 +251,4 @@ Sub-patterns: **bullet-list-with-marker (R13)** — 각 컬럼의 bullet_items
|
||||
|
||||
| slug | frame | pattern | 위반 사항 | 처리 |
|
||||
|------|-------|---------|--------|------|
|
||||
| [prerequisites-3col](block-tests/prerequisites-3col.html) | 45:15 / Frame 1171281190 | 3-column-comparison (legacy) | R8 위반 (수동 ×0.60290 곱셈, transform: scale 미사용), R9 미적용 (gradient text만, div 패턴 없음) | 재변환 권장. 동일 패턴 등장 시 신규 변환물 작성 후 이 행은 archive 처리. |
|
||||
| [prerequisites-3col](block-tests/prerequisites-3col.html) | 45:15 / Frame 1171281190 | prerequisites-3col | **promoted** → [block](../../templates/blocks/new/prerequisites-3col.html) + [catalog](../../templates/catalog.yaml) | 2026-04-13. legacy HTML 기반 블록화. 순수 CSS (이미지 없음). |
|
||||
@@ -0,0 +1,274 @@
|
||||
"""MDX 02 최종 4안 — 처음부터 독립, CSS font-size override 금지.
|
||||
|
||||
글씨 크기: 블록 CSS 원래 값 그대로. 절대 변경 금지.
|
||||
zone 크기: 글씨 크기에 맞게 조정 (글씨가 기준, 레이아웃이 맞춤).
|
||||
|
||||
안 1: 내 판단 + new/ only
|
||||
상단: cards-3col-persona 하단좌: stacked-arrow-list 하단우: cards-3col-persona(3주체)
|
||||
안 2: 내 판단 + new/ + 기존
|
||||
상단: card-compare-3col 하단좌: card-numbered 하단우: table-simple-striped
|
||||
안 3: Kei 판단 + new/ only (Kei 응답: 상단1 하단좌4 하단우2 결론6)
|
||||
상단: cards-3col-persona 하단좌: split-panel-numbered 하단우: compare-vs-rows
|
||||
안 4: Kei 판단 + new/ + 기존 (Kei 응답: 상단A 하단좌7 하단우H 결론6)
|
||||
상단: card-compare-3col 하단좌: issues-paired-rows 하단우: compare-3col-badge
|
||||
"""
|
||||
import base64, re
|
||||
from pathlib import Path
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
BLOCKS = Path("templates/blocks")
|
||||
SVG = BLOCKS / "svg"
|
||||
OUT = Path("data/runs/mdx02_4plans_final")
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
env = Environment(loader=FileSystemLoader(str(BLOCKS)), autoescape=False)
|
||||
|
||||
def b64(f):
|
||||
p = SVG / f
|
||||
if not p.exists(): return ""
|
||||
ext = "svg+xml" if f.endswith(".svg") else "png"
|
||||
return f"data:image/{ext};base64," + base64.b64encode(p.read_bytes()).decode()
|
||||
|
||||
def clean(h): return re.sub(r'<!--.*?-->', '', h, flags=re.DOTALL).strip()
|
||||
|
||||
img_uri = open("data/runs/mdx02_v4_test/slide_image.txt").read().strip()
|
||||
title = "DX의 시행 목표 및 기대효과"
|
||||
ft = '고품질의 성과품, 비용 절감, 시간 단축, 의사소통에 도움이 <em>안 되면 DX가 아니다</em>'
|
||||
|
||||
# slide-body: 1200×590px. 블록 글씨 크기에 맞춰 zone 크기 계산.
|
||||
# 컨테이너 계산 → 글자수 예산 → overflow 방지
|
||||
IW = 500 # 이미지
|
||||
GAP = 10
|
||||
# zone 높이: 블록 필요 높이 기반 계산
|
||||
# new/ 블록(15~22px): 상단 244px, 하단 336px
|
||||
# 기존 블록(13~15px): 상단 204px, 하단 310px
|
||||
TH_NEW = 244
|
||||
BH_NEW = 336
|
||||
TH_OLD = 204
|
||||
BH_OLD = 310
|
||||
|
||||
# 원문 텍스트
|
||||
TOP = [
|
||||
("안전과 품질", [
|
||||
"시설물의 요구 성능을 설계-시공-운영 전 과정에서 <strong>디지털로 검증</strong>하여 <strong>안전성 확보</strong>",
|
||||
"Copy & Paste로 하향 평준화된 성과물의 <strong>하자 최소화</strong>로 <strong>고품질 성과물 제공</strong>"]),
|
||||
("생산성 향상", [
|
||||
"Analogue 기반 업무를 Digital 기반 프로세스로 전환하여 <strong>업무 속도·정확성·일관성 향상</strong>",
|
||||
"건설 비용 및 유지관리비 절감, 건설 기간 단축, 인력투입 최소화를 통해 <strong>부가가치 제고</strong>"]),
|
||||
("소통과 신뢰", [
|
||||
"성과품과 Solution을 통한 협업 강화로 <strong>의사소통 효율 및 운영·유지관리</strong>의 <strong>편리성 증진</strong>",
|
||||
"3D 모델 및 데이터 기반 검증을 통한 <strong>오류 최소화 및 Claim 예방</strong>으로 <strong>신뢰성 확보</strong>"]),
|
||||
]
|
||||
BL = [
|
||||
("생산 방식", "수작업 의존의 반복 업무에서 벗어나, <strong>SW를 활용한 체계화된 방식</strong>으로 전환"),
|
||||
("인지·검토", "2D 도면 해석 중심에서 <strong>3D 모델 기반의 직관적 인지·검토 체계</strong>로 전환"),
|
||||
("협업 구조", "개별 문서 중심 협업에서 <strong>데이터 통합 기반의 정보 공유·관리 협업 환경</strong>으로 전환"),
|
||||
("검증·대응", "사후 대응 중심의 문제 처리에서 <strong>사전 검증 중심의 예방적 업무 방식</strong>으로 전환"),
|
||||
]
|
||||
BR = {
|
||||
"headers": ["구분", "발주자", "시공자", "설계자"],
|
||||
"rows": [
|
||||
["필요 역량", "실행 의지와 합리적 판단", "기술 투자와 운영 역량", "SW개발 투자 역량"],
|
||||
["SW 기반 체계화", "행정 자동화로 생산성 향상", "체계적 공정관리로 신뢰성 확보", "설계 프로세스 체계화"],
|
||||
["3D 기반 전환", "직관적 시각화로 품질 향상", "안전성 제고 및 관리 편의", "3D 검증으로 설계 오류 방지"],
|
||||
["데이터 통합 협업", "원활한 의사소통으로 오류 감소", "협업 효율 및 서류 감소", "설계 신뢰도로 상호신뢰 증진"],
|
||||
["사전 검증 관리", "민원·소송 등 사전 예방", "설계·시공 오류 예방", "설계 책임 리스크 감소"],
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def wrap_slide(body_html, fname):
|
||||
sb = clean((BLOCKS / "slide-base.html").read_text(encoding="utf-8"))
|
||||
r = sb.replace('{{ title|default("슬라이드") }}', title)
|
||||
r = r.replace('{{ title|default("슬라이드 제목") }}', title)
|
||||
r = r.replace('{% block body %}{% endblock %}', body_html)
|
||||
pill = b64("pill_scroll.png")
|
||||
r = r.replace('{% if footer_text %}', '').replace('{% if footer_pill_bg %}', '')
|
||||
r = r.replace('{{ footer_pill_bg }}', pill).replace('{% else %}', '')
|
||||
r = r.replace('<div class="slide-footer-bg slide-footer--css"></div>', '')
|
||||
li = r.rfind('{% endif %}')
|
||||
if li > 0: r = r[:li] + r[li + len('{% endif %}'):]
|
||||
r = r.replace('{% endif %}', '').replace('{{ footer_text|safe }}', ft)
|
||||
r = r.replace('src="svg/bg_slide_texture.png"', f'src="{b64("bg_slide_texture.png")}"')
|
||||
r = r.replace('src="svg/line_divider.svg"', f'src="{b64("line_divider.svg")}"')
|
||||
out = OUT / fname
|
||||
out.write_text(r, encoding="utf-8")
|
||||
return out
|
||||
|
||||
|
||||
def make_body(top_html, bl_html, br_html, th, bh):
|
||||
"""slide-body 안에 배치. zone 높이는 블록 글씨 크기 기반 계산값."""
|
||||
return f"""
|
||||
<div style="height:{th}px;margin-bottom:{GAP}px;overflow:hidden;">
|
||||
<div style="font-weight:700;font-size:13px;color:#1a365d;margin-bottom:4px;">DX의 궁극적 목표</div>
|
||||
<div style="display:flex;gap:8px;height:{th-20}px;">
|
||||
<div style="flex:1;overflow:hidden;">{top_html}</div>
|
||||
<div style="width:{IW}px;flex-shrink:0;display:flex;flex-direction:column;justify-content:center;">
|
||||
<img src="{img_uri}" style="width:100%;border-radius:8px;object-fit:contain;">
|
||||
<div style="font-size:10px;color:#94a3b8;text-align:center;margin-top:2px;">DX의 궁극적 목표</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="height:{bh}px;overflow:hidden;">
|
||||
<div style="font-weight:700;font-size:13px;color:#1a365d;padding-bottom:4px;border-bottom:1px solid #e2e8f0;margin-bottom:4px;">DX 기반 Process 혁신에 따른 주체별 기대효과</div>
|
||||
<div style="display:flex;gap:12px;height:{bh-28}px;">
|
||||
<div style="flex:1;overflow:hidden;">
|
||||
<div style="font-weight:700;font-size:12px;color:#1a365d;margin-bottom:4px;">업무 수행 과정(Process)의 변화</div>
|
||||
{bl_html}
|
||||
</div>
|
||||
<div style="width:1px;background:#cbd5e1;flex-shrink:0;"></div>
|
||||
<div style="flex:1;overflow:hidden;">
|
||||
<div style="font-weight:700;font-size:12px;color:#1a365d;margin-bottom:4px;">DX 시행 주체별 기대효과</div>
|
||||
{br_html}
|
||||
</div>
|
||||
</div>
|
||||
</div>"""
|
||||
|
||||
|
||||
# Jinja2 .items() 충돌 방지용 클래스
|
||||
class _Cat:
|
||||
def __init__(self, name, color, items): self.name=name; self.color=color; self.items=items
|
||||
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# 안 1: 내 판단 + new/ only — 글씨 크기 그대로
|
||||
# ══════════════════════════════════════════
|
||||
print("=== 안 1 ===")
|
||||
t1 = clean(env.get_template("new/cards-3col-persona.html").render(personas=[
|
||||
{"overlay_color":c,"label_line1":t,"label_color":"#1a365d","bullets":[{"text":b} for b in bs]}
|
||||
for (t,bs),c in zip(TOP,["#dce8d4","#d4dce8","#e8dcd4"])
|
||||
]))
|
||||
# 뱃지/사진만 숨김 — 글씨 크기는 원래 값(15px body, 20px label)
|
||||
t1 += "<style>.c3p-badge{display:none}.c3p-photo{display:none}.c3p-badge-label{position:relative;z-index:2;margin-bottom:4px}</style>"
|
||||
|
||||
b1l = clean(env.get_template("new/stacked-arrow-list.html").render(items=[
|
||||
{"text":f"<strong>{k}</strong>: {v}","border_color":c}
|
||||
for (k,v),c in zip(BL,["#fb5915","#e79000","#919f00","#0d6361"])
|
||||
]))
|
||||
# 헤더/장식만 숨김 — 글씨 크기(22px)는 원래 값
|
||||
b1l += "<style>.sal-header{display:none}.sal-deco{display:none}</style>"
|
||||
|
||||
b1r = clean(env.get_template("new/cards-3col-persona.html").render(personas=[
|
||||
{"overlay_color":c,"label_line1":h,"label_color":"#1a365d",
|
||||
"bullets":[{"text":BR["rows"][ri][ci+1]} for ri in range(5)]}
|
||||
for ci,(h,c) in enumerate([("발주자","#c8d8e8"),("시공자","#d8e8c8"),("설계자","#e8d8c8")])
|
||||
]))
|
||||
b1r = b1r.replace('block-c3p','block-c3p2').replace('c3p-','c3p2-')
|
||||
b1r += "<style>.c3p2-badge{display:none}.c3p2-photo{display:none}.c3p2-badge-label{position:relative;z-index:2;margin-bottom:4px}</style>"
|
||||
|
||||
p1 = wrap_slide(make_body(t1, b1l, b1r, TH_NEW, BH_NEW), "plan1_new_only.html")
|
||||
print(f" {p1.stat().st_size:,} bytes")
|
||||
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# 안 2: 내 판단 + mixed — 글씨 크기 그대로
|
||||
# ══════════════════════════════════════════
|
||||
print("=== 안 2 ===")
|
||||
t2 = clean(env.get_template("cards/card-compare-3col.html").render(cards=[
|
||||
{"title":t,"color":c,"bullets":bs}
|
||||
for (t,bs),c in zip(TOP,["#1a365d","#15803d","#b45309"])
|
||||
]))
|
||||
|
||||
b2l = clean(env.get_template("cards/card-numbered.html").render(items=[
|
||||
{"title":k,"description":v,"color":c}
|
||||
for (k,v),c in zip(BL,["#2563eb","#16a34a","#d97706","#7c3aed"])
|
||||
]))
|
||||
|
||||
b2r = clean(env.get_template("tables/table-simple-striped.html").render(**BR))
|
||||
|
||||
p2 = wrap_slide(make_body(t2, b2l, b2r, TH_OLD, BH_OLD), "plan2_mixed.html")
|
||||
print(f" {p2.stat().st_size:,} bytes")
|
||||
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# 안 3: Kei 판단 + new/ only (1,4,2,6)
|
||||
# ══════════════════════════════════════════
|
||||
print("=== 안 3 (Kei: 1,4,2,6) ===")
|
||||
# 상단: 1. cards-3col-persona (안 1과 동일 블록, 동일 데이터)
|
||||
t3 = clean(env.get_template("new/cards-3col-persona.html").render(personas=[
|
||||
{"overlay_color":c,"label_line1":t,"label_color":"#1a365d","bullets":[{"text":b} for b in bs]}
|
||||
for (t,bs),c in zip(TOP,["#dce8d4","#d4dce8","#e8dcd4"])
|
||||
]))
|
||||
t3 += "<style>.c3p-badge{display:none}.c3p-photo{display:none}.c3p-badge-label{position:relative;z-index:2;margin-bottom:4px}</style>"
|
||||
|
||||
# 하단좌: 4. split-panel-numbered (Kei 선택)
|
||||
b3l = clean(env.get_template("new/split-panel-numbered.html").render(
|
||||
categories=[_Cat(k,c,[v]) for (k,v),c in zip(BL,["#417d38","#008e52","#008970","#2563eb"])],
|
||||
right_items=[],
|
||||
))
|
||||
b3l += "<style>.spn-header{display:none}.spn-right{display:none}.spn-mid{display:none}.spn-left{flex:1}</style>"
|
||||
|
||||
# 하단우: 2. compare-vs-rows (Kei 선택) — 3주체를 좌/우로
|
||||
b3r = clean(env.get_template("new/compare-vs-rows.html").render(
|
||||
main_labels={"left":"발주자","center":"구분","right":"시공자·설계자"},
|
||||
rows=[{"category":r[0],"left_text":r[1],"right_text":f"{r[2]} / {r[3]}"} for r in BR["rows"]],
|
||||
))
|
||||
b3r += "<style>.cvr-header{display:none}.cvr-conclusion{display:none}</style>"
|
||||
|
||||
p3 = wrap_slide(make_body(t3, b3l, b3r, TH_NEW, BH_NEW), "plan3_kei_new.html")
|
||||
print(f" {p3.stat().st_size:,} bytes")
|
||||
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# 안 4: Kei 판단 + mixed (A,7,H,6)
|
||||
# ══════════════════════════════════════════
|
||||
print("=== 안 4 (Kei: A,7,H,6) ===")
|
||||
# 상단: A. card-compare-3col (안 2와 동일 블록)
|
||||
t4 = clean(env.get_template("cards/card-compare-3col.html").render(cards=[
|
||||
{"title":t,"color":c,"bullets":bs}
|
||||
for (t,bs),c in zip(TOP,["#1a365d","#15803d","#b45309"])
|
||||
]))
|
||||
|
||||
# 하단좌: 7. issues-paired-rows (Kei 선택)
|
||||
b4l = clean(env.get_template("new/issues-paired-rows.html").render(
|
||||
pill_bg=b64("pill_scroll.png"),
|
||||
rows=[
|
||||
{"left":{"label":BL[0][0],"text":BL[0][1]},"right":{"label":BL[1][0],"text":BL[1][1]}},
|
||||
{"left":{"label":BL[2][0],"text":BL[2][1]},"right":{"label":BL[3][0],"text":BL[3][1]},"pills_bottom":True},
|
||||
],
|
||||
))
|
||||
b4l += "<style>.ipr-header{display:none}</style>"
|
||||
|
||||
# 하단우: H. compare-3col-badge (Kei 선택)
|
||||
b4r = clean(env.get_template("tables/compare-3col-badge.html").render(**BR))
|
||||
|
||||
p4 = wrap_slide(make_body(t4, b4l, b4r, TH_OLD, BH_OLD), "plan4_kei_mixed.html")
|
||||
print(f" {p4.stat().st_size:,} bytes")
|
||||
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# Selenium 검증
|
||||
# ══════════════════════════════════════════
|
||||
print("\n=== Selenium 검증 ===")
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
import time
|
||||
|
||||
options = Options()
|
||||
options.add_argument('--headless')
|
||||
options.add_argument('--window-size=1400,900')
|
||||
options.add_argument('--force-device-scale-factor=2')
|
||||
driver = webdriver.Chrome(options=options)
|
||||
|
||||
for fname in ["plan1_new_only.html","plan2_mixed.html","plan3_kei_new.html","plan4_kei_mixed.html"]:
|
||||
path = OUT / fname
|
||||
driver.get(f"file:///{path.resolve()}")
|
||||
time.sleep(2)
|
||||
driver.save_screenshot(str(path).replace(".html",".png"))
|
||||
|
||||
html = path.read_text(encoding="utf-8")
|
||||
c = re.sub(r'data:image/[^;]+;base64,[A-Za-z0-9+/=]+','I',html)
|
||||
|
||||
# font-size override 확인 — !important가 있으면 위반
|
||||
overrides = re.findall(r'font-size.*?!important', c)
|
||||
|
||||
j = re.findall(r'\{[%{].*?[%}]\}',c)
|
||||
ts = ['안전','품질','생산','향상','소통','신뢰','생산 방식','인지','협업','검증',
|
||||
'발주자','시공자','설계자','안 되면 DX가 아니다','DX의 궁극적 목표','Process 혁신','업무 수행']
|
||||
m = [t for t in ts if t not in c]
|
||||
print(f" [{fname}]")
|
||||
print(f" Jinja:{len(j)} 텍스트:{len(ts)-len(m)}/{len(ts)} font-size override:{len(overrides)}")
|
||||
if m: print(f" MISSING: {m}")
|
||||
if overrides: print(f" OVERRIDE 위반: {overrides[:3]}")
|
||||
|
||||
driver.quit()
|
||||
print("\n완료!")
|
||||
@@ -0,0 +1,306 @@
|
||||
"""MDX 03: slide-base + prerequisites-3col(상단) + process-product-2col(하단) + pill(결론).
|
||||
|
||||
블록 CSS 글씨 크기 그대로. font-size override 금지.
|
||||
텍스트 원문 그대로. 요약/추론 금지.
|
||||
"""
|
||||
import base64, re
|
||||
from pathlib import Path
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
BLOCKS = Path("templates/blocks")
|
||||
SVG = BLOCKS / "svg"
|
||||
OUT = Path("data/runs/mdx03_final")
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
env = Environment(loader=FileSystemLoader(str(BLOCKS)), autoescape=False)
|
||||
|
||||
def b64(f):
|
||||
p = SVG / f
|
||||
if not p.exists(): return ""
|
||||
ext = "svg+xml" if f.endswith(".svg") else "png"
|
||||
return f"data:image/{ext};base64," + base64.b64encode(p.read_bytes()).decode()
|
||||
|
||||
def clean(h): return re.sub(r'<!--.*?-->', '', h, flags=re.DOTALL).strip()
|
||||
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# 상단: prerequisites-3col — MDX 원문 그대로
|
||||
# ══════════════════════════════════════════
|
||||
|
||||
class _Item:
|
||||
def __init__(self, heading, desc): self.heading = heading; self.desc = desc
|
||||
|
||||
class _Col:
|
||||
def __init__(self, **kw):
|
||||
for k, v in kw.items(): setattr(self, k, v)
|
||||
|
||||
# 블록 CSS만 가져오고, HTML은 콘텐츠 items 수에 맞게 동적 생성
|
||||
# prerequisites-3col의 CSS를 추출
|
||||
_p3c_raw = (BLOCKS / "new" / "prerequisites-3col.html").read_text(encoding="utf-8")
|
||||
_p3c_css = re.search(r'<style>(.*?)</style>', _p3c_raw, re.DOTALL).group(0)
|
||||
|
||||
# MDX 원문 그대로 — 각 열의 항목 수가 다름
|
||||
cols_data = [
|
||||
{
|
||||
"name": "기술", "sub": "디지털",
|
||||
"bar": "linear-gradient(180deg, #0D78D0 0%, #023056 100%)",
|
||||
"hgrad": "linear-gradient(180deg, #0D78D0 0%, #134D7F 100%)",
|
||||
"items": [
|
||||
("Digital 기술(S/W, H/W)과 업무 Process의 통합", [
|
||||
"기존 업무 프로세스에 다양한 디지털 기술을 접목하여 업무 수행",
|
||||
"프로젝트 전반에 걸친 업무 프로세스의 연결 및 조율",
|
||||
]),
|
||||
("분야별 전문 지식(설계, 시공, 유지관리 등) 보유", [
|
||||
"건설 전 단계에 대한 근본적인 이해와 지식 및 경험",
|
||||
"최신 토목 기술 트랜드 및 표준 기준 등에 대한 높은 지식",
|
||||
]),
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "사람", "sub": "역량",
|
||||
"bar": "linear-gradient(180deg, #FF9A23 0%, #CC5200 100%)",
|
||||
"hgrad": "linear-gradient(180deg, #CC5200 0%, #883700 100%)",
|
||||
"items": [
|
||||
("혁신적 사고방식과 창의적 문제 해결 능력", [
|
||||
"기존 수행 방식과 관습적 사고 등에 의한 접근 방식 탈피",
|
||||
"디지털 기술을 활용한 창의적, 혁신적인 솔루션 제시",
|
||||
]),
|
||||
("사용자 중심 사고와 DX 수행 경험", [
|
||||
"사용자의 요구와 기대를 충족시키는 설계 및 구현",
|
||||
"시행착오를 포함한 수행 경험과 사용자 경험(UX)을 반영한 해결 방안 제시",
|
||||
]),
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "자연", "sub": "여건",
|
||||
"bar": "linear-gradient(180deg, #39BE49 0%, #23742C 100%)",
|
||||
"hgrad": "linear-gradient(180deg, #39BE49 0%, #1E6328 100%)",
|
||||
"items": [
|
||||
("지속적인 투자 및 실행 의지", [
|
||||
"기술 도입 초기 단계에 필요한 인력·기간·비용 등의 대규모 투자",
|
||||
"기술 고도화를 위한 지속적인 개선 및 투자 체계 구축",
|
||||
"변화와 혁신을 통해 부가가치를 창출하려는 실행 의지와 추진력",
|
||||
]),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
# 동적 HTML 생성 — 블록 CSS 클래스 사용, items 수 동적
|
||||
cols_html = ""
|
||||
for col in cols_data:
|
||||
items_html = ""
|
||||
n = len(col["items"])
|
||||
for i, (heading, bullets) in enumerate(col["items"]):
|
||||
pct_h = int(95 / n)
|
||||
pct_top = int(3 + i * (95 / n))
|
||||
bul = "".join(f'<div class="bul">• {b}</div>' for b in bullets)
|
||||
items_html += f"""
|
||||
<div class="p3c-section" style="position:absolute;left:60px;right:6px;top:{pct_top}%;height:{pct_h}%;">
|
||||
<div class="p3c-heading" style="background-image:{col['hgrad']}">{heading}</div>
|
||||
<div class="p3c-desc">{bul}</div>
|
||||
</div>"""
|
||||
if i < n - 1:
|
||||
line_top = pct_top + pct_h
|
||||
items_html += f'<div class="p3c-mid-line" style="position:absolute;left:56px;right:0;top:{line_top}%;border-top:1.2px dashed #000;"></div>'
|
||||
|
||||
cols_html += f"""
|
||||
<div class="p3c-col" style="flex:1;position:relative;height:100%;border-top:1.2px solid #000;border-bottom:1.2px solid #000;">
|
||||
<div class="p3c-bar" style="background:{col['bar']};position:absolute;left:0;top:0;width:56px;height:100%;"></div>
|
||||
<div class="p3c-vlabel-area" style="position:absolute;left:0;top:0;width:56px;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;z-index:3;">
|
||||
<div class="p3c-vlabel">{col['name']}</div>
|
||||
<div class="p3c-vlabel-sub">{col['sub']}</div>
|
||||
</div>
|
||||
{items_html}
|
||||
</div>"""
|
||||
|
||||
top = f'<div class="block-p3c" style="display:flex;gap:12px;width:100%;height:100%;">{cols_html}</div>\n{_p3c_css}'
|
||||
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# 하단: process-product-2col — MDX 원문 그대로
|
||||
# ══════════════════════════════════════════
|
||||
|
||||
# process-product-2col: 블록 CSS 사용 + HTML은 좌우 소제목이 같은 행에 오도록 Grid 재구성
|
||||
_pp2_raw = (BLOCKS / "BEPs" / "process-product-2col.html").read_text(encoding="utf-8")
|
||||
_pp2_css = re.search(r'<style>(.*?)</style>', _pp2_raw, re.DOTALL).group(0)
|
||||
|
||||
arrow_uri = b64("arrow_asis_tobe.png")
|
||||
|
||||
# 좌우 소제목을 행 단위로 대응시킴
|
||||
# 행1: Analogue 기반 업무의 Digital화 (As-is→To-be) | Copy & Paste로 인해... 품질 향상
|
||||
# 행2: GIS + BIM의 연계 | Analogue 기반 도서 외 Digital 기반 정보물 추가
|
||||
# 행3: 사용자 중심의 Solution 제공 | Solution을 활용한 업무 효율화
|
||||
|
||||
# 2열 grid + ::before로 열 배경 gradient → 행 높이 동기화 + gradient 유지
|
||||
bottom = f"""
|
||||
<div class="pp2-grid-wrap" style="position:relative;width:100%;height:100%;">
|
||||
<!-- 좌측 배경 gradient (::before 대신 absolute div) -->
|
||||
<div style="position:absolute;left:0;top:0;width:50%;height:100%;background:linear-gradient(180deg,#ffffff 46%,#39311e 100%);z-index:0;"></div>
|
||||
<!-- 우측 배경 gradient -->
|
||||
<div style="position:absolute;left:50%;top:0;width:50%;height:100%;background:linear-gradient(0deg,#296b55 0%,#ffffff 56%);z-index:0;"></div>
|
||||
|
||||
<!-- 2열 grid — 행 높이 동기화 -->
|
||||
<div style="position:relative;z-index:1;display:grid;grid-template-columns:1fr 1fr;width:100%;height:100%;">
|
||||
<!-- 헤더 행 -->
|
||||
<div class="pp2-header-bar pp2-header-bar--left" style="background:linear-gradient(270deg,#a4a096 0%,#39311e 100%);border-radius:0 24px 24px 0;display:flex;align-items:center;justify-content:center;height:30px;margin-top:4px;">
|
||||
<span class="pp2-header-text pp2-header-text--left" style="font-size:13px;font-weight:900;color:#3e3523;">과정(Process)의 혁신</span>
|
||||
</div>
|
||||
<div class="pp2-header-bar pp2-header-bar--right" style="background:linear-gradient(90deg,#296b55 0%,#022017 100%);border-radius:24px 0 0 24px;display:flex;align-items:center;padding-left:20px;height:30px;margin-top:4px;">
|
||||
<span class="pp2-header-text pp2-header-text--right" style="font-size:13px;font-weight:900;color:#ffffff;">결과(Product)의 변화</span>
|
||||
</div>
|
||||
|
||||
<!-- 행1: Analogue Digital화 | 품질 향상 -->
|
||||
<div style="padding:3px 16px;">
|
||||
<div class="pp2-mid-title pp2-mid-title--left">Analogue 기반 업무의 Digital화</div>
|
||||
<div style="display:flex;align-items:center;gap:4px;">
|
||||
<div style="flex:1;">
|
||||
<div class="pp2-body-text">• <strong>개념·문서·행정 절차 중심</strong></div>
|
||||
<div class="pp2-body-text">• <strong>2D 도면, 전문가, 규정</strong></div>
|
||||
<div class="pp2-body-text">• <strong>업무 구분(단절), 책임</strong></div>
|
||||
</div>
|
||||
<div style="flex-shrink:0;width:30px;text-align:center;"><img src="{arrow_uri}" style="width:30px;height:16px;object-fit:contain;" alt="→"></div>
|
||||
<div style="flex:1;">
|
||||
<div class="pp2-body-text">• <strong>시각화된 목적물, 소통, 투명성 중심</strong></div>
|
||||
<div class="pp2-body-text">• <strong>3D 모델, 참여자, 실체</strong></div>
|
||||
<div class="pp2-body-text">• <strong>협업(융·복합), 창의성</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:3px 16px;">
|
||||
<div class="pp2-mid-title pp2-mid-title--right">Copy & Paste로 인해 하향 평준화된 기존 성과물의 품질 향상</div>
|
||||
<div class="pp2-body-text">• 과거 수작업으로 시행하면서 발생하던 오류 등의 최소화</div>
|
||||
<div class="pp2-body-text">• 정확한 Data에 기반한 계획으로 고품질 성과물 도출</div>
|
||||
</div>
|
||||
|
||||
<!-- 행2: GIS+BIM | Digital 정보물 -->
|
||||
<div style="padding:2px 16px;">
|
||||
<div class="pp2-mid-title pp2-mid-title--left">GIS + BIM의 연계</div>
|
||||
<div class="pp2-body-text">• 지리·지형·지반 등 위치정보(GIS)와 3D모델(형상, 속성정보) 기반의 건설 정보를 포함하는 BIM의 연계를 통한 업무 프로세스의 혁신</div>
|
||||
</div>
|
||||
<div style="padding:2px 16px;">
|
||||
<div class="pp2-mid-title pp2-mid-title--right">Analogue 기반 도서 외 Digital 기반 정보물 추가</div>
|
||||
<div class="pp2-body-text">• 기존 성과물(도면, 수량, 계산서, 시방서 등)에 3D 모델, Simulation 등의 Digital 기반 정보물 추가</div>
|
||||
</div>
|
||||
|
||||
<!-- 행3: Solution -->
|
||||
<div style="padding:2px 16px;">
|
||||
<div class="pp2-mid-title pp2-mid-title--left">사용자 중심의 Solution 제공</div>
|
||||
<div class="pp2-body-text">• 서로 다른 S/W로 작성되어 분절화된 Analogue 방식의 성과물과 정보물을 연계할 수 있는 설계·시공 Solution 제공</div>
|
||||
</div>
|
||||
<div style="padding:2px 16px;">
|
||||
<div class="pp2-mid-title pp2-mid-title--right">Solution을 활용한 업무 효율화</div>
|
||||
<div class="pp2-body-text">• Engn. Solution을 통해 성과물에 관한 이슈를 함께 검토·논의하는 협업 환경 조성</div>
|
||||
<div class="pp2-body-text">• 건설 단계별 정보를 디지털 데이터로 축적하여, 건설 전 과정을 통합관리</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{_pp2_css}
|
||||
"""
|
||||
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# slide-base 조립
|
||||
# ══════════════════════════════════════════
|
||||
|
||||
title = "DX 실행 체계 구축 방안"
|
||||
ft = 'DX는 필요한 요건과 체계를 갖춘 후 시행해야만 그 효과를 <em>기대할 수 있다</em>'
|
||||
|
||||
# font_hierarchy: key_msg=14, core=12, bg=12, sidebar=11
|
||||
# zone제목=13px, 블록 heading=12px, 블록 desc=11px
|
||||
font_override = """<style>
|
||||
/* prerequisites-3col: font_hierarchy 적용 + 높이 채움 */
|
||||
.p3c-heading { font-size: 12px !important; line-height: 1.5 !important; }
|
||||
.p3c-desc { font-size: 11px !important; line-height: 1.6 !important; }
|
||||
.p3c-desc .bul { padding-left: 12px; text-indent: -12px; }
|
||||
.p3c-vlabel { font-size: 14px !important; }
|
||||
.p3c-vlabel-sub { font-size: 12px !important; }
|
||||
.p3c-kanji { font-size: 16px !important; }
|
||||
.p3c-col { min-height: 0 !important; height: 100% !important; }
|
||||
.block-p3c { height: 100% !important; }
|
||||
/* 한자 숨김 — MDX 원문에 없음 */
|
||||
.p3c-kanji { display: none !important; }
|
||||
/* 색상바 내 라벨 영역을 바 전체로 */
|
||||
.p3c-vlabel-area { width: 56px !important; }
|
||||
/* section을 바 바로 옆으로 — 들여쓰기 2단 효과 */
|
||||
.p3c-section { left: 60px !important; right: 6px !important; }
|
||||
.p3c-mid-line { left: 56px !important; }
|
||||
/* 자연(3번째 열): heading 1개이므로 하단 숨김 */
|
||||
.p3c-col:nth-child(3) .p3c-section--top { height: 90% !important; }
|
||||
.p3c-col:nth-child(3) .p3c-section--bottom { display: none !important; }
|
||||
.p3c-col:nth-child(3) .p3c-mid-line { display: none !important; }
|
||||
|
||||
/* process-product-2col: font_hierarchy 적용 + 높이 채움 */
|
||||
.pp2-header-text { font-size: 13px !important; letter-spacing: 0.8px !important; }
|
||||
.pp2-mid-title { font-size: 12px !important; line-height: 1.5 !important; margin-top: 6px !important; }
|
||||
.pp2-mid-title:first-child { margin-top: 0 !important; }
|
||||
.pp2-body-text { font-size: 11px !important; line-height: 1.6 !important; padding-left: 12px !important; text-indent: -12px !important; }
|
||||
.pp2-col { min-height: 0 !important; }
|
||||
.pp2-header-bar { height: 30px !important; margin-top: 4px !important; }
|
||||
.pp2-body { padding: 6px 16px !important; }
|
||||
.block-pp2 { height: 100% !important; }
|
||||
/* 우측 헤더 텍스트: 배경 gradient와 겹쳐서 안 보임 → 흰색 */
|
||||
.pp2-header-text--right { color: #ffffff !important; }
|
||||
/* 좌우 본문 영역 높이 맞춤 */
|
||||
.pp2-body { display: flex !important; flex-direction: column !important; }
|
||||
</style>"""
|
||||
|
||||
body = f"""
|
||||
{font_override}
|
||||
<div style="height:38%;margin-bottom:1%;padding-top:8px;">
|
||||
<div style="font-weight:700;font-size:13px;color:#1a365d;margin-bottom:8px;">DX 시행을 위한 필수 요건</div>
|
||||
<div style="height:calc(100% - 28px);padding:0 12px 0 24px;">{top}</div>
|
||||
</div>
|
||||
<div style="height:60%;margin-top:12px;">
|
||||
<div style="font-weight:700;font-size:13px;color:#1a365d;margin-bottom:8px;">Process의 혁신과 Product의 변화</div>
|
||||
<div style="height:calc(100% - 28px);padding:0 12px 0 24px;">{bottom}</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
sb = clean((BLOCKS / "slide-base.html").read_text(encoding="utf-8"))
|
||||
r = sb.replace('{{ title|default("슬라이드") }}', title)
|
||||
r = r.replace('{{ title|default("슬라이드 제목") }}', title)
|
||||
r = r.replace('{% block body %}{% endblock %}', body)
|
||||
pill = b64("pill_scroll.png")
|
||||
r = r.replace('{% if footer_text %}', '').replace('{% if footer_pill_bg %}', '')
|
||||
r = r.replace('{{ footer_pill_bg }}', pill).replace('{% else %}', '')
|
||||
r = r.replace('<div class="slide-footer-bg slide-footer--css"></div>', '')
|
||||
li = r.rfind('{% endif %}')
|
||||
if li > 0: r = r[:li] + r[li + len('{% endif %}'):]
|
||||
r = r.replace('{% endif %}', '').replace('{{ footer_text|safe }}', ft)
|
||||
r = r.replace('src="svg/bg_slide_texture.png"', f'src="{b64("bg_slide_texture.png")}"')
|
||||
r = r.replace('src="svg/line_divider.svg"', f'src="{b64("line_divider.svg")}"')
|
||||
|
||||
out = OUT / "final.html"
|
||||
out.write_text(r, encoding="utf-8")
|
||||
print(f"저장: {out} ({out.stat().st_size:,} bytes)")
|
||||
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# Selenium 검증
|
||||
# ══════════════════════════════════════════
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
import time
|
||||
|
||||
options = Options()
|
||||
options.add_argument('--headless')
|
||||
options.add_argument('--window-size=1400,900')
|
||||
options.add_argument('--force-device-scale-factor=2')
|
||||
driver = webdriver.Chrome(options=options)
|
||||
driver.get(f"file:///{out.resolve()}")
|
||||
time.sleep(2)
|
||||
driver.save_screenshot(str(out).replace(".html", ".png"))
|
||||
driver.quit()
|
||||
|
||||
# 텍스트 검증
|
||||
c = re.sub(r'data:image/[^;]+;base64,[A-Za-z0-9+/=]+', 'I', r)
|
||||
overrides = re.findall(r'font-size.*?!important', c)
|
||||
j = re.findall(r'\{[%{].*?[%}]\}', c)
|
||||
ts = ['기술','사람','자연','디지털','역량','여건',
|
||||
'Digital 기술','혁신적 사고방식','지속적인 투자',
|
||||
'Process의 혁신','Product의 변화',
|
||||
'Analogue','Digital','GIS','BIM','Solution',
|
||||
'기대할 수 있다']
|
||||
m = [t for t in ts if t not in c]
|
||||
print(f"Jinja:{len(j)} font-override:{len(overrides)} 텍스트:{len(ts)-len(m)}/{len(ts)}")
|
||||
if m: print(f" MISSING: {m}")
|
||||
else: print(" ALL OK")
|
||||
@@ -0,0 +1,48 @@
|
||||
"""MDX 03을 기존 파이프라인으로 Stage 1.7까지 돌린 뒤, 산출물을 저장.
|
||||
|
||||
파이프라인 코드 그대로 사용:
|
||||
- Stage 0: mdx_normalizer
|
||||
- Stage 1A: kei_client.classify_content (Kei API)
|
||||
- Stage 1B: kei_client.refine_concepts + generate_structured_text
|
||||
- Stage 1.5a: space_allocator (컨테이너 계산 + font_hierarchy)
|
||||
- Stage 1.7: block_reference (블록 선택)
|
||||
|
||||
Stage 2(조립)는 여기서 하지 않음 — 산출물만 저장.
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from src.pipeline import generate_slide
|
||||
|
||||
|
||||
async def main():
|
||||
mdx_path = Path("samples/mdx/03. DX 시행을 위한 필수 요건 및 혁신 방안.mdx")
|
||||
content = mdx_path.read_text(encoding="utf-8")
|
||||
|
||||
print(f"MDX 03: {mdx_path.name}")
|
||||
print(f"내용 길이: {len(content)}자")
|
||||
print()
|
||||
|
||||
start = time.time()
|
||||
async for event in generate_slide(content, base_path=str(mdx_path.parent)):
|
||||
ev_type = event.get("event", "")
|
||||
data = event.get("data", "")
|
||||
if ev_type == "progress":
|
||||
print(f" {data}")
|
||||
elif ev_type == "result":
|
||||
elapsed = time.time() - start
|
||||
print(f"\n완료! ({elapsed:.1f}초)")
|
||||
if isinstance(data, dict):
|
||||
run_id = data.get("run_id", "")
|
||||
print(f"run_id: {run_id}")
|
||||
print(f"결과: data/runs/{run_id}/")
|
||||
elif ev_type == "error":
|
||||
print(f" 에러: {data}")
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="119" height="55" viewBox="0 0 119 55" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M36.7205 21.7305H29.7212C29.5934 20.8249 29.3324 20.0206 28.9382 19.3175C28.544 18.6037 28.038 17.9964 27.4201 17.4957C26.8022 16.995 26.0884 16.6115 25.2788 16.3452C24.4798 16.0788 23.6115 15.9457 22.674 15.9457C20.9801 15.9457 19.5046 16.3665 18.2475 17.2081C16.9904 18.0391 16.0156 19.2536 15.3232 20.8516C14.6307 22.4389 14.2844 24.3672 14.2844 26.6364C14.2844 28.9695 14.6307 30.9297 15.3232 32.517C16.0263 34.1044 17.0064 35.3029 18.2635 36.1126C19.5206 36.9222 20.9748 37.3271 22.6261 37.3271C23.5529 37.3271 24.4105 37.2045 25.1989 36.9595C25.9979 36.7145 26.7063 36.3576 27.3242 35.8888C27.9421 35.4094 28.4535 34.8288 28.8583 34.147C29.2738 33.4652 29.5614 32.6875 29.7212 31.8139L36.7205 31.8459C36.5394 33.348 36.0866 34.7969 35.3622 36.1925C34.6484 37.5774 33.6843 38.8185 32.4698 39.9158C31.266 41.0025 29.8278 41.8654 28.1552 42.5046C26.4933 43.1332 24.6129 43.4474 22.5142 43.4474C19.5952 43.4474 16.9851 42.7869 14.6839 41.4659C12.3935 40.1449 10.5824 38.2326 9.25071 35.729C7.92969 33.2255 7.26918 30.1946 7.26918 26.6364C7.26918 23.0675 7.94034 20.0312 9.28267 17.5277C10.625 15.0241 12.4467 13.1172 14.7479 11.8068C17.049 10.4858 19.6378 9.82528 22.5142 9.82528C24.4105 9.82528 26.1683 10.0916 27.7876 10.6243C29.4176 11.157 30.8612 11.9347 32.1183 12.9574C33.3754 13.9695 34.3981 15.2106 35.1864 16.6808C35.9854 18.1509 36.4968 19.8342 36.7205 21.7305ZM41.6824 43V10.2727H63.7349V15.9776H48.6017V23.7759H62.6003V29.4808H48.6017V37.2951H63.7988V43H41.6824ZM69.2361 43V10.2727H76.1555V37.2951H90.186V43H69.2361Z" fill="black"/>
|
||||
<path d="M6.47045 49.2188H5.22614C5.20341 49.0578 5.15701 48.9148 5.08693 48.7898C5.01686 48.6629 4.92689 48.5549 4.81704 48.4659C4.7072 48.3769 4.5803 48.3087 4.43636 48.2614C4.29432 48.214 4.13996 48.1903 3.97329 48.1903C3.67216 48.1903 3.40985 48.2652 3.18636 48.4148C2.96288 48.5625 2.78958 48.7784 2.66648 49.0625C2.54337 49.3447 2.48182 49.6875 2.48182 50.0909C2.48182 50.5057 2.54337 50.8542 2.66648 51.1364C2.79148 51.4186 2.96572 51.6316 3.1892 51.7756C3.41269 51.9195 3.67121 51.9915 3.96477 51.9915C4.12954 51.9915 4.28201 51.9697 4.42216 51.9261C4.5642 51.8826 4.69015 51.8191 4.8 51.7358C4.90985 51.6506 5.00076 51.5473 5.07273 51.4261C5.14659 51.3049 5.19773 51.1667 5.22614 51.0114L6.47045 51.017C6.43826 51.2841 6.35776 51.5417 6.22898 51.7898C6.10208 52.036 5.93068 52.2566 5.71477 52.4517C5.50076 52.6449 5.24508 52.7983 4.94773 52.9119C4.65227 53.0237 4.31799 53.0795 3.94489 53.0795C3.42595 53.0795 2.96193 52.9621 2.55284 52.7273C2.14564 52.4924 1.82367 52.1525 1.58693 51.7074C1.35208 51.2623 1.23466 50.7235 1.23466 50.0909C1.23466 49.4564 1.35398 48.9167 1.59261 48.4716C1.83125 48.0265 2.15511 47.6875 2.5642 47.4545C2.97329 47.2197 3.43352 47.1023 3.94489 47.1023C4.28201 47.1023 4.59451 47.1496 4.88239 47.2443C5.17216 47.339 5.42879 47.4773 5.65227 47.6591C5.87576 47.839 6.05758 48.0597 6.19773 48.321C6.33977 48.5824 6.43068 48.8816 6.47045 49.2188ZM9.70267 47.1818V53H8.47256V47.1818H9.70267ZM12.8859 47.1818L14.2921 51.6023H14.3461L15.7552 47.1818H17.1188L15.1132 53H13.5279L11.5194 47.1818H12.8859ZM20.1692 47.1818V53H18.9391V47.1818H20.1692ZM22.3013 53V47.1818H23.5314V51.9858H26.0257V53H22.3013ZM30.9319 53V47.1818H34.8524V48.196H32.162V49.5824H34.6507V50.5966H32.162V51.9858H34.8637V53H30.9319ZM41.8168 47.1818V53H40.7543L38.2231 49.3381H38.1805V53H36.9504V47.1818H38.0299L40.5413 50.8409H40.5924V47.1818H41.8168ZM47.8111 49.0625C47.7714 48.9242 47.7155 48.8021 47.6435 48.696C47.5716 48.5881 47.4835 48.4972 47.3793 48.4233C47.277 48.3475 47.1596 48.2898 47.027 48.25C46.8964 48.2102 46.7515 48.1903 46.5924 48.1903C46.295 48.1903 46.0337 48.2642 45.8083 48.4119C45.5848 48.5597 45.4106 48.7746 45.2856 49.0568C45.1606 49.3371 45.0981 49.6799 45.0981 50.0852C45.0981 50.4905 45.1596 50.8352 45.2827 51.1193C45.4058 51.4034 45.5801 51.6203 45.8055 51.7699C46.0308 51.9176 46.2969 51.9915 46.6037 51.9915C46.8822 51.9915 47.1198 51.9422 47.3168 51.8438C47.5157 51.7434 47.6672 51.6023 47.7714 51.4205C47.8774 51.2386 47.9305 51.0237 47.9305 50.7756L48.1805 50.8125H46.6805V49.8864H49.1151V50.6193C49.1151 51.1307 49.0072 51.5701 48.7912 51.9375C48.5753 52.303 48.278 52.5852 47.8992 52.7841C47.5204 52.9811 47.0867 53.0795 46.5981 53.0795C46.0526 53.0795 45.5734 52.9593 45.1606 52.7188C44.7477 52.4763 44.4257 52.1326 44.1947 51.6875C43.9655 51.2405 43.8509 50.7102 43.8509 50.0966C43.8509 49.625 43.9191 49.2045 44.0555 48.8352C44.1937 48.464 44.3869 48.1496 44.635 47.892C44.8831 47.6345 45.1719 47.4384 45.5015 47.304C45.831 47.1695 46.188 47.1023 46.5725 47.1023C46.902 47.1023 47.2089 47.1506 47.493 47.2472C47.777 47.3419 48.0289 47.4763 48.2486 47.6506C48.4702 47.8248 48.6511 48.0322 48.7912 48.2727C48.9314 48.5114 49.0214 48.7746 49.0611 49.0625H47.8111ZM52.3892 47.1818V53H51.1591V47.1818H52.3892ZM59.3878 47.1818V53H58.3253L55.794 49.3381H55.7514V53H54.5213V47.1818H55.6009L58.1122 50.8409H58.1634V47.1818H59.3878ZM61.5241 53V47.1818H65.4446V48.196H62.7542V49.5824H65.2429V50.5966H62.7542V51.9858H65.4559V53H61.5241ZM67.5426 53V47.1818H71.463V48.196H68.7727V49.5824H71.2613V50.5966H68.7727V51.9858H71.4744V53H67.5426ZM73.561 53V47.1818H75.8564C76.2958 47.1818 76.6708 47.2604 76.9814 47.4176C77.2939 47.5729 77.5316 47.7936 77.6945 48.0795C77.8593 48.3636 77.9417 48.6979 77.9417 49.0824C77.9417 49.4687 77.8583 49.8011 77.6917 50.0795C77.525 50.3561 77.2835 50.5682 76.9672 50.7159C76.6529 50.8636 76.2722 50.9375 75.8252 50.9375H74.2883V49.9489H75.6263C75.8612 49.9489 76.0563 49.9167 76.2116 49.8523C76.3669 49.7879 76.4824 49.6913 76.5582 49.5625C76.6358 49.4337 76.6746 49.2737 76.6746 49.0824C76.6746 48.8892 76.6358 48.7263 76.5582 48.5938C76.4824 48.4612 76.3659 48.3608 76.2087 48.2926C76.0534 48.2225 75.8574 48.1875 75.6207 48.1875H74.7911V53H73.561ZM76.703 50.3523L78.1491 53H76.7911L75.3763 50.3523H76.703ZM81.1611 47.1818V53H79.931V47.1818H81.1611ZM88.1597 47.1818V53H87.0972L84.5659 49.3381H84.5233V53H83.2932V47.1818H84.3727L86.8841 50.8409H86.9352V47.1818H88.1597ZM94.154 49.0625C94.1142 48.9242 94.0583 48.8021 93.9863 48.696C93.9144 48.5881 93.8263 48.4972 93.7221 48.4233C93.6199 48.3475 93.5024 48.2898 93.3699 48.25C93.2392 48.2102 93.0943 48.1903 92.9352 48.1903C92.6379 48.1903 92.3765 48.2642 92.1511 48.4119C91.9276 48.5597 91.7534 48.7746 91.6284 49.0568C91.5034 49.3371 91.4409 49.6799 91.4409 50.0852C91.4409 50.4905 91.5024 50.8352 91.6255 51.1193C91.7486 51.4034 91.9229 51.6203 92.1483 51.7699C92.3736 51.9176 92.6397 51.9915 92.9466 51.9915C93.225 51.9915 93.4627 51.9422 93.6596 51.8438C93.8585 51.7434 94.01 51.6023 Line truncated
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 75 KiB |