add: figma_to_html_agent/blocks/ + 변환 도구 docs 갱신

전체 401 files (397 추가 + 4 수정), 14304 insertions.

추가:
- figma_to_html_agent/blocks/ — Figma 변환 결과 (32 frame, ~79MB).
  각 frame folder = {analysis.md, flat.md, texts.md, index.html, assets/,
  _renders/, _render.py, RELATIONSHIPS.md / STATUS.md / classification.md
  (일부 frame)}.
  Phase Z 의 *figma source layer* — runtime 에서 직접 사용 X, contract /
  partial / builder adapter (미래 axis A) 의 source.
- figma_to_html_agent/DISCUSSION-SUMMARY-20260411.md — 변환 설계 회의 기록.
- figma_to_html_agent/HARNESS.md — 변환 검증 harness.
- figma_to_html_agent/scripts/fetch_figma_screenshots.py — Figma 스크린샷 자동 수집.

수정:
- figma_to_html_agent/PROCESS-CONTROL.md / PROCESS.md / RULES.md —
  변환 프로세스 / 룰 갱신 (R8/R9 lock 강화 등).
- figma_to_html_agent/blocks_index.md — 32 frame 인덱스 갱신.

Phase Z 영향 0 (figma_to_html_agent/blocks/ 가 V4 catalog +
templates/phase_z2/families adapter 의 source — runtime 에서 직접 import X).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 09:41:05 +09:00
parent cc2f434000
commit 9fbe3ac90c
401 changed files with 14304 additions and 2 deletions

View File

@@ -0,0 +1,86 @@
"""HTML 렌더 + Figma MCP 데이터 기반 정밀 비교"""
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from PIL import Image
import os, time, json
here = os.path.dirname(os.path.abspath(__file__))
renders = os.path.join(here, '_renders')
os.makedirs(renders, exist_ok=True)
# 1. 큰 viewport로 띄워서 높은 해상도 렌더 (zoom 1.0으로)
opts = Options()
opts.add_argument('--headless=new')
opts.add_argument('--hide-scrollbars')
opts.add_argument('--force-device-scale-factor=1')
opts.add_argument('--window-size=1920,1200')
d = webdriver.Chrome(options=opts)
p = os.path.join(here, 'index.html').replace(os.sep, '/')
d.get('file:///' + p)
# 폰트 + 이미지 모두 로드 대기
d.execute_script("""
return Promise.all([
document.fonts.ready,
...Array.from(document.images).map(img =>
img.complete ? Promise.resolve() : new Promise(r => { img.onload = img.onerror = r; })
)
]);
""")
time.sleep(2.0)
# 모든 핵심 요소의 computed style + bounding rect 측정
data = d.execute_script("""
const out = {};
const selectors = [
'.title-text',
'.title-text .green',
'.title-text .white',
'.card-title-1',
'.card-title-2',
'.card-title-3',
'.body-card-1',
'.body-card-2-p1',
'.body-card-3',
'.dx-circle img',
'.bullet-1-1 img',
'.card-1-frame img',
'.card-1-header img'
];
for (const sel of selectors) {
const el = document.querySelector(sel);
if (!el) { out[sel] = 'NOT FOUND'; continue; }
const r = el.getBoundingClientRect();
const cs = getComputedStyle(el);
out[sel] = {
rect: { x: r.x, y: r.y, w: r.width, h: r.height },
color: cs.color,
fontSize: cs.fontSize,
fontWeight: cs.fontWeight,
fontFamily: cs.fontFamily,
backgroundImage: cs.backgroundImage.length > 100 ? cs.backgroundImage.slice(0,100)+'...' : cs.backgroundImage,
webkitTextFillColor: cs.webkitTextFillColor,
text: el.textContent ? el.textContent.slice(0, 50) : null
};
}
return out;
""")
print(json.dumps(data, indent=2, ensure_ascii=False))
# 풀 해상도 스크린샷
full_png = os.path.join(renders, '_compare_full.png')
d.save_screenshot(full_png)
# slide만 잘라서 저장
r = d.execute_script(
'const r=document.querySelector(".slide").getBoundingClientRect();'
'return [r.x,r.y,r.width,r.height];'
)
print('\nslide bounds:', r)
Image.open(full_png).crop(
(int(r[0]), int(r[1]), int(r[0]+r[2]), int(r[1]+r[3]))
).save(os.path.join(renders, 'slide_v2.png'))
d.quit()
print('\nSaved:', os.path.join(renders, 'slide_v2.png'))

View File

@@ -0,0 +1,81 @@
"""Figma 원본 vs HTML 렌더 정밀 diff
- selenium으로 HTML을 1280px 폭으로 정확히 렌더 (slide만)
- Figma get_screenshot 응답 화면을 미리 저장한 figma_ref.png로 비교
"""
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from PIL import Image, ImageDraw, ImageFont
import os, time, urllib.request
here = os.path.dirname(os.path.abspath(__file__))
renders = os.path.join(here, '_renders')
# ──────────────────────────
# 1. HTML을 1280×426 정확히 캡처 (block만)
# ──────────────────────────
opts = Options()
opts.add_argument('--headless=new')
opts.add_argument('--hide-scrollbars')
opts.add_argument('--force-device-scale-factor=1')
opts.add_argument('--window-size=1600,900')
d = webdriver.Chrome(options=opts)
p = os.path.join(here, 'index.html').replace(os.sep, '/')
d.get('file:///' + p)
d.execute_script("""
return Promise.all([
document.fonts.ready,
...Array.from(document.images).map(img =>
img.complete ? Promise.resolve() : new Promise(r => { img.onload = img.onerror = r; })
)
]);
""")
time.sleep(2.0)
full_png = os.path.join(renders, '_diff_full.png')
d.save_screenshot(full_png)
block_rect = d.execute_script(
'const r=document.querySelector(".block").getBoundingClientRect();'
'return [Math.round(r.x), Math.round(r.y), Math.round(r.width), Math.round(r.height)];'
)
print('block:', block_rect)
block_img = Image.open(full_png).crop(
(block_rect[0], block_rect[1], block_rect[0]+block_rect[2], block_rect[1]+block_rect[3])
)
block_path = os.path.join(renders, 'html_block.png')
block_img.save(block_path)
print(f'Saved html_block: {block_img.size}')
d.quit()
# ──────────────────────────
# 2. Figma get_screenshot URL이 없으므로 — 별도로 figma_ref.png 가 있어야 함
# (없으면 사용자에게 직접 캡처 요청)
# ──────────────────────────
figma_ref = os.path.join(here, 'figma_ref.png')
if not os.path.exists(figma_ref):
print(f'figma_ref.png NOT FOUND at {figma_ref}')
print('=> Figma 원본 스크린샷 직접 저장 필요')
else:
figma_img = Image.open(figma_ref)
print(f'figma_ref: {figma_img.size}')
# 둘 다 같은 폭 1280으로 리사이즈
target_w = 1280
h_block = block_img.height
f_w, f_h = figma_img.size
f_h_scaled = int(f_h * target_w / f_w)
figma_resized = figma_img.resize((target_w, f_h_scaled), Image.LANCZOS)
# 위/아래로 합치기 (위: Figma, 아래: HTML)
combined_h = f_h_scaled + h_block + 80
combined = Image.new('RGB', (target_w, combined_h), 'white')
draw = ImageDraw.Draw(combined)
draw.text((10, 5), 'Figma 원본', fill='red')
combined.paste(figma_resized, (0, 30))
draw.text((10, 30 + f_h_scaled + 5), 'HTML 렌더 (block crop)', fill='red')
combined.paste(block_img, (0, 30 + f_h_scaled + 30))
combined.save(os.path.join(renders, 'side_by_side.png'))
print(f'Saved side_by_side ({combined.size})')

View File

@@ -0,0 +1,99 @@
"""모든 title span 색상 + 위치 검증"""
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from PIL import Image
import os, time, json
here = os.path.dirname(os.path.abspath(__file__))
renders = os.path.join(here, '_renders')
opts = Options()
opts.add_argument('--headless=new')
opts.add_argument('--hide-scrollbars')
opts.add_argument('--force-device-scale-factor=2') # 2x for higher resolution
opts.add_argument('--window-size=1600,900')
d = webdriver.Chrome(options=opts)
p = os.path.join(here, 'index.html').replace(os.sep, '/')
d.get('file:///' + p)
d.execute_script("""
return Promise.all([
document.fonts.ready,
...Array.from(document.images).map(img =>
img.complete ? Promise.resolve() : new Promise(r => { img.onload = img.onerror = r; })
)
]);
""")
time.sleep(2.0)
# 모든 title span 측정
result = d.execute_script("""
const out = [];
// title 모든 span
document.querySelectorAll('.title-text span').forEach((el, i) => {
const r = el.getBoundingClientRect();
const cs = getComputedStyle(el);
out.push({
type: 'title-span-' + i,
text: el.textContent,
color: cs.color,
rect: {x: r.x, y: r.y, w: r.width, h: r.height},
});
});
// 모든 card-title
document.querySelectorAll('[class*="card-title-"]').forEach((el, i) => {
const r = el.getBoundingClientRect();
const cs = getComputedStyle(el);
out.push({
type: 'card-title-' + i,
text: el.textContent.trim(),
color: cs.color,
fillColor: cs.webkitTextFillColor,
bgImage: cs.backgroundImage.slice(0, 80),
rect: {x: r.x, y: r.y, w: r.width, h: r.height},
});
});
// body-card 위치/크기
['.body-card-1', '.body-card-2-p1', '.body-card-2-p2', '.body-card-2-p3', '.body-card-3'].forEach(sel => {
const el = document.querySelector(sel);
if (el) {
const r = el.getBoundingClientRect();
out.push({
type: 'body-' + sel,
text: el.textContent.trim().slice(0, 40),
rect: {x: r.x, y: r.y, w: r.width, h: r.height},
});
}
});
return out;
""")
print(json.dumps(result, indent=2, ensure_ascii=False))
# 2x screenshot
full_png = os.path.join(renders, '_inspect_full_2x.png')
d.save_screenshot(full_png)
# slide만 2x crop
r = d.execute_script(
'const dpr = window.devicePixelRatio;'
'const r=document.querySelector(".slide").getBoundingClientRect();'
'return [Math.round(r.x*dpr), Math.round(r.y*dpr), Math.round(r.width*dpr), Math.round(r.height*dpr)];'
)
Image.open(full_png).crop(
(r[0], r[1], r[0]+r[2], r[1]+r[3])
).save(os.path.join(renders, 'slide_hires.png'))
# title 영역만 zoom (title은 block 상단)
b = d.execute_script(
'const dpr = window.devicePixelRatio;'
'const t=document.querySelector(".title-text").getBoundingClientRect();'
'return [Math.round(t.x*dpr), Math.round(t.y*dpr), Math.round(t.width*dpr), Math.round(t.height*dpr)];'
)
Image.open(full_png).crop(
(b[0]-20, b[1]-20, b[0]+b[2]+20, b[1]+b[3]+20)
).save(os.path.join(renders, 'title_zoom.png'))
d.quit()
print('\nSaved:')
print(' - slide_hires.png')
print(' - title_zoom.png')

View File

@@ -0,0 +1,51 @@
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from PIL import Image
import os, time
here = os.path.dirname(os.path.abspath(__file__))
renders = os.path.join(here, '_renders')
os.makedirs(renders, exist_ok=True)
opts = Options()
opts.add_argument('--headless=new')
opts.add_argument('--hide-scrollbars')
opts.add_argument('--force-device-scale-factor=1')
opts.add_argument('--window-size=1600,900')
d = webdriver.Chrome(options=opts)
p = os.path.join(here, 'index.html').replace(os.sep, '/')
d.get('file:///' + p)
# 폰트 + 이미지 모두 로드될 때까지 대기
d.execute_script("""
return Promise.all([
document.fonts.ready,
...Array.from(document.images).map(img =>
img.complete ? Promise.resolve() : new Promise(r => { img.onload = img.onerror = r; })
)
]);
""")
time.sleep(1.0)
full_png = os.path.join(renders, '_full.png')
d.save_screenshot(full_png)
r = d.execute_script(
'const r=document.querySelector(".slide").getBoundingClientRect();'
'return [r.x,r.y,r.width,r.height];'
)
print('slide bounds:', r)
Image.open(full_png).crop(
(int(r[0]), int(r[1]), int(r[0]+r[2]), int(r[1]+r[3]))
).save(os.path.join(renders, 'slide.png'))
b = d.execute_script(
'const r=document.querySelector(".block").getBoundingClientRect();'
'return [r.x,r.y,r.width,r.height];'
)
print('block bounds:', b)
Image.open(full_png).crop(
(int(b[0]), int(b[1]), int(b[0]+b[2]), int(b[1]+b[3]))
).save(os.path.join(renders, 'block.png'))
d.quit()
print('OK')

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -0,0 +1,64 @@
# Frame 1171281198 — 디지털 전환(DX)은 S/W가 필수다
## 내용 설명
디지털 전환(DX) 실현을 위해 S/W가 필수임을 BIM·디지털·전면설계의
3가지 관점 카드로 설명하는 디자인. 3열 카드 헤더 라벨 + 카드별
본문 구조로 고부가가치 실현 논거를 전개. 이 디자인은 BIM과 DX를
서로 비교하는 것이 아니라, DX를 주 서사로 두고 S/W 필수성을 3관점
으로 뒷받침하는 구조. DX·S/W 필수성 서사·BIM/디지털/전면설계 3관점
병렬 전개·고부가가치 실현 논거에 적합.
BIM vs DX 다면 비교 (Frame 18)·단순 2개 개념 대조·시간 순서·
4개 이상 항목에는 부적합.
## 후보 키워드
DX, S/W, 필수, BIM, 디지털, 전면설계, 고부가가치, 실현
## 정제 Anchor Sets
- **dx_sw_necessity**: DX, S/W, 필수, 필수
- **three_design_perspectives**: BIM, 디지털, 전면설계
- **high_value_goal**: 고부가가치, 실현
## 구조 매칭 정보
- **family**: cards
- **layout**: cards-3-header
- **axis**: horizontal
- **relation_type**: parallel
- **cardinality**: ideal 3 / min 3 / max 3
- **slots** (7개, required 7개): title, perspective_1_label, perspective_1_body, perspective_2_label, perspective_2_body, perspective_3_label, perspective_3_body
- **source title**: 디지털 전환(DX)은 S/W가 필수다
- **original layout**: 3col-cards
## 적합/부적합 기준
### suits
- DX와 S/W 필수성 서사
- BIM/디지털/전면설계 3관점
- 고부가가치 실현 논거
### not_suits
- 2개 개념 다면 비교
- BIM vs DX 직접 대조
- 시간 순서
- 4개 이상 항목
- 단일 관점 심층
- 문제/약점 진단 (부정형)
## 재구성 허용
- **split**: False
- **merge**: False
- **infer_missing_slot**: False
- **rewrite_label**: True
- **rewrite_body**: True
## 메타
- schema_version: template-fit-v1 mirror
- source_of_truth: structure_ontology.yaml + keyword_base.yaml
- structure_content_original_tagged_by: claude-opus-4-7 (2026-04-21)
- keyword_base_sync_at: 2026-04-22
- anchor_sets_cleaned_at: 2026-04-22

View File

@@ -0,0 +1,17 @@
# Frame 1171281198 — 디지털 전환(DX)은 S/W가 필수다
## 구조
- **layout**: 3col-cards
- **detail**: 타이틀("디지털 전환(DX)은 S/W가 필수다") + 3열 카드 헤더(상단 라벨) + 카드별 내용
## 내용
디지털 전환(DX) 실현을 위한 S/W 필수성 — BIM·디지털·전면설계 관점에서 고부가가치 실현을 3열 카드로 정리.
## 후보 키워드
디지털전환, DX, S/W필수, BIM, 디지털, 전면설계, 고부가가치, 3열카드, DX전환
## 메타
- source: texts.md + flat.md
- schema_version: analysis-v1
- tagged_by: claude-opus-4-7
- tagged_at: 2026-04-21

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,124 @@
# Frame 1171281198 — 실측 기록 (flat)
> 원본: 2734.634 × 910.775 px (node 182:2766, file 9S6LsQyO6zlRxtiqZccOUM, page 29:373)
> 패턴: dx-banner-with-3perspective-cards (상단 다크 그린 배너 + 좌측 DX 원형 라벨 + 우측 3 카드)
> Scale: **min(1280/2734.6, 720/910.8) = 1280/2734.6 = 0.46810** (width fit, 1280×426 block)
## 구조 (bottom-up)
```
Frame 182:2766 "Frame 1171281198" (2734.634 × 910.775)
├─ ① 상단 다크 그린 배너 (0~198)
│ ├─ 182:2768 header_left.png (0, 0.99, 364×198) — 좌측 헤더 BG (대각선 패턴)
│ ├─ 182:2769 header_center.png (363, 0, 2007×198) — 중앙
│ ├─ 182:2770 header_right.png (2369, 0, 366×198) — 우측
│ ├─ 182:2771 image 3568 placeholder (539, 19.74, 1748×145) — design_context에 src 없음, 무시
│ ├─ 182:2772 header_decor.png (30.94, 37.51, 127×114) — 좌측 데코
│ └─ 182:2773 header_decor.png (2585.11, 68.11, 127×114) — 우측 데코 (rotate-180)
├─ ② 타이틀 (배너 위 텍스트)
│ └─ 182:2774 TEXT "디지털 전환(DX)은 S/W가 필수다."
│ center=(1369.38, 96.24) → top-left=(3.6, 0.98)
│ w=2730.503, h=190.518
│ 90px Bold, line-height 65, text-align center
│ "디지털 전환(DX)" #90FA33 (green) / "은" white / " S/W" green / "가 필수다." white
├─ ③ 본문 영역 (164~910)
│ │
│ ├─ 좌측 DX 원형 영역 (164~880, x=0~620)
│ │ ├─ 182:2776 body_bg.png (1.03, 196.09, 2733.6×508) — 본문 BG
│ │ ├─ 182:2777 middle_overlay.png (805.34, 263.21, 1897×648, opacity 0.8)
│ │ ├─ 182:2778 left_bg.png (127.86, 164.5, 888×720)
│ │ ├─ 182:2779 left_decor.png (4.13, 233.6, 612×639) — scaleY(-1) + rotate(90)
│ │ ├─ 182:2780 left_inner_bg.png (27.84, 256.3, 591×566)
│ │ ├─ 182:2781 hanmaek_label.png (142.3, 374.76, 56×32, opacity 0.9)
│ │ └─ 182:2782 dx_circle.png (28.87, 378.71, 592×320) — "DX를 위한 S/W" 텍스트 포함된 원형 이미지
│ │
│ ├─ 우측 3 카드 영역 (310~880, x=913~2596)
│ │ ├─ Card 1 (BIM 전면설계, x=913) green tone
│ │ │ ├─ 182:2783 card_frame.png (913, 310, 515×378) — 카드 outline frame (orange/green)
│ │ │ ├─ 182:2786 card_header_bg.png (974, 310, 378×86) — 헤더 BG (label 배경)
│ │ │ ├─ 182:2787 card_bottom_decor.png (913, 642, 515×186)
│ │ │ ├─ 182:2792 TEXT "BIM 전면설계" — 40px Bold gradient #296B55→#123328
│ │ │ │ center=(1164.18, 323.43+32.08=355.51) → top-left=(969, 323)
│ │ │ ├─ 182:2796 본문 텍스트 (989.91, 450.77, 426×334) — 37px Medium black, 5 단락
│ │ │ │ "건설산업 생산성 향상" / blank / "고부가가치 산업으로의 전환" / blank /
│ │ │ │ "건설산업의 디지털전환(DX)\n체계 마련 및 고도화 필요"
│ │ │ └─ 3 bullet icons (936.29, 461.63 / 936.29, 543.56 / 936.29, 622.53) 41×39
│ │ │
│ │ ├─ Card 2 (디지털 전환 S/W, x=1500) orange tone
│ │ │ ├─ 182:2784 card_frame.png (1500, 310, 515×377)
│ │ │ ├─ 182:2788 card_header_bg.png (1558, 310, 378×86)
│ │ │ ├─ 182:2789 card_bottom_decor.png (1500, 642, 515×186)
│ │ │ ├─ 182:2793 TEXT "디지털 전환 S/W" — 40px Bold gradient #CC5200→#8D3900
│ │ │ ├─ 182:2797 "노동집약형 업무 탈피" (1570, 450, 453×60) — 37px Medium
│ │ │ ├─ 182:2808 "S/W의 지속적인 고도화 필요" (1571, 530, 453×63) — 37px Medium
│ │ │ ├─ 182:2809 "충분한 투자(인력, 비용, 시간 등)와 엔지니어의 적극적 동참" (1568, 612, 453×120)
│ │ │ │ — 37px Medium + 30px (인력, 비용, 시간 등)
│ │ │ └─ 3 bullet icons (1520.96, 461 / 1520.96, 543 / 1520.96, 625) 41×39
│ │ │
│ │ └─ Card 3 (고부가가치 산업전환, x=2080) green tone
│ │ ├─ 182:2785 card_frame.png (2080, 310, 515×377)
│ │ ├─ 182:2790 card_header_bg.png (2141, 310, 378×86)
│ │ ├─ 182:2791 card_bottom_decor.png (2081, 642, 515×186)
│ │ ├─ 182:2794 TEXT "고부가가치 산업전환" — 40px Bold gradient #296B55→#123328
│ │ ├─ 182:2798 본문 (2154, 450, 442×334) — 37px Medium, 3 단락
│ │ │ "기본기술의 이해 & 발전 필요" / blank /
│ │ │ "업무 본질에 대한 깊은 이해를 바탕으로 창의적 아이디어 발현" / blank /
│ │ │ "업무환경 개선과 DX를 통한\nProcess 혁신과 Product 개선"
│ │ └─ 3 bullet icons (2101, 461 / 2101, 543 / 2101, 678) 41×39
```
## 에셋 (15개)
| 파일 | 의미 | 크기 | 사용 |
|------|------|------|------|
| `header_left.png` | 좌측 다크 그린 헤더 BG (대각선 패턴) | 364×198 | 1× |
| `header_center.png` | 중앙 다크 그린 헤더 BG | 2007×198 | 1× |
| `header_right.png` | 우측 다크 그린 헤더 BG | 366×198 | 1× |
| `header_decor.png` | 헤더 좌/우 작은 데코 (사각형 군) | 127×114 | 2× (우측은 rotate-180) |
| `body_bg.png` | 본문 BG | 2733×508 | 1× |
| `middle_overlay.png` | 중앙 BG 오버레이 | 1897×648 | 1× (opacity 0.8) |
| `left_bg.png` | 좌측 영역 BG | 888×720 | 1× |
| `left_decor.png` | 좌측 데코 | 612×639 | 1× (scaleY(-1) + rotate(90)) |
| `left_inner_bg.png` | 좌측 내부 BG | 591×566 | 1× |
| `hanmaek_label.png` | 한맥기술 작은 라벨 | 56×32 | 1× (opacity 0.9) |
| `dx_circle.png` | "DX를 위한 S/W" 텍스트 포함 원형 이미지 | 592×320 | 1× |
| `card_frame.png` | 카드 외곽 프레임 (orange/green border) | 515×378 | **3×** |
| `card_header_bg.png` | 카드 상단 라벨 BG | 378×86 | **3×** |
| `card_bottom_decor.png` | 카드 하단 데코 | 515×186 | **3×** |
| `bullet_arrow.png` | 녹색 ▶ 화살표 (1171281209 공유) | 41×39 | **9×** |
## 이상 탐지 결과
| 검사 | 결과 |
|------|------|
| 회전 적용 | header_decor 우측 인스턴스 rotate-180; left_decor scaleY(-1) + rotate(90) |
| Vector 좌표 metadata vs design_context | 일치 |
| 중복 노드 | header_decor (2×), card_frame/header_bg/bottom_decor (3×), bullet_arrow (9×) — 정상 재사용 |
| **누락 자산** | image 3568 (placeholder, src 없음) — 무시 |
| **DX를 위한 S/W 텍스트** | 텍스트 노드 아님, dx_circle.png 안에 baked in. 1:1 변환에는 그대로 PNG 사용. 향후 템플릿화 시 CSS+텍스트 분리 검토 |
## 변형 가능 축
- **cards[N=3] (required)** — cardinality 3 고정 (analysis.md)
- `card_label` (required, 40px Bold gradient — color depends on tone)
- `card_color_palette` (required) — green (#296B55#123328) 또는 orange (#CC5200#8D3900)
- `body_paragraphs[N=1~3]` (required) — 37px Medium, line-height 60
- `body_bullets[N=3]` (required) — 녹색 ▶
- `title` (required) — 90px Bold, 인라인 색상 변경 가능 (#90FA33 강조 + 흰색)
- `header_decor_left/right` (optional) — 작은 사각형 군 데코
- `dx_circle` (required for this layout) — 좌측 큰 원형 텍스트 이미지
## Sub-patterns
- `dark-green-banner-title` — 상단 다크 그린 배너 + 인라인 색상 강조 텍스트 (#90FA33)
- `card-with-orange-frame-and-bullet-list` — 카드 외곽 프레임 PNG + 헤더 BG PNG + 본문 + ▶ 불릿
- `bullet-paragraph-multiline-with-spacer` — 단락 사이에 빈 줄 패턴 (line-height 60 빈 paragraph)
## 임시 보정 (1:1, 템플릿화 시 재고려)
- 카드 프레임/헤더/하단 데코를 PNG로 유지 (CSS gradient/border-radius로 변환 가능 → 프로모션 시 검토)
- 좌측 dx_circle은 텍스트가 baked-in 이미지라 동적 변경 불가 — 템플릿화 시 별도 CSS 처리 필요
- 본문의 `tracking-[-X.YYpx]` 마이크로 letter-spacing 다수 — 자연 wrap에 맡길 것

View File

@@ -0,0 +1,386 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1280">
<title>디지털 전환(DX)은 S/W가 필수다 (Frame 1171281198)</title>
<!--
Frame: 182:2766, 2734.634 × 910.775 px (Frame 1171281198)
Source: file 9S6LsQyO6zlRxtiqZccOUM, page 29:373
Scale: min(1280/2734.6, 720/910.8) = 1280/2734.6 = 0.46810 (width fit)
Block in slide: 1280 × 426 px
패턴: dx-banner-with-3perspective-cards
-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700;900&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Noto Sans KR', sans-serif;
background: #e8ecf0;
display: flex; justify-content: center; align-items: center;
min-height: 100vh;
word-break: keep-all;
}
.slide {
width: 1280px; height: 720px;
background: #fff;
position: relative; overflow: hidden;
box-shadow: 0 4px 20px rgba(0,0,0,.15);
display: flex; align-items: center; justify-content: center;
}
.block {
width: 1280px;
position: relative;
overflow: hidden;
}
.inner {
width: 2734.634px; height: 910.775px;
position: relative;
zoom: 0.46810;
}
/* ────────────────────────────────────────────────
① 상단 배너 — 가로 그라디언트 (녹 → 갈)
원본 3 PNG 들은 가장자리 alpha=0 (rounded corner) 이라 어떻게 합성해도 솔기 발생.
샘플링한 색상 stop 으로 CSS linear-gradient 로 재현 (R8: 디자인 의도 명확한 배경 허용).
──────────────────────────────────────────────── */
.header-bg-full {
position: absolute;
left: 0; top: 0;
width: 2735px; height: 199px;
background: linear-gradient(90deg,
rgb(15, 50, 30) 0%,
rgb(23, 62, 47) 5%,
rgb(29, 60, 45) 20%,
rgb(36, 59, 43) 30%,
rgb(41, 57, 41) 40%,
rgb(46, 56, 39) 50%,
rgb(51, 55, 38) 60%,
rgb(54, 54, 36) 70%,
rgb(57, 53, 35) 80%,
rgb(59, 52, 35) 90%,
rgb(60, 52, 34) 100%
);
}
.header-decor-l, .header-decor-r {
position: absolute;
width: 127.864px; height: 114.508px;
}
.header-decor-l { left: 30.94px; top: 37.51px; }
.header-decor-r { left: 2585.11px; top: 68.11px; transform: rotate(180deg); transform-origin: center; }
.header-decor-l img, .header-decor-r img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* ② 타이틀 텍스트 */
.title-text {
position: absolute;
/* center=(1369.38, 96.24), w=2730.503, h=190.518 */
left: 3.6px; /* 1369.38 - 2730.503/2 */
top: 0.98px; /* 96.24 - 190.518/2 */
width: 2730.503px;
height: 190.518px;
font-family: 'Noto Sans KR', sans-serif;
font-weight: 700;
font-size: 90px;
text-align: center;
display: flex; flex-direction: column; justify-content: center;
}
.title-text > p { margin: 0; line-height: 65px; }
.title-text .green { color: #90FA33; }
.title-text .white { color: #ffffff; }
/* ③ 본문 영역 BG */
.body-bg {
position: absolute;
left: 1.03px; top: 196.09px;
width: 2733.6px; height: 508.377px;
}
.body-bg img { width: 100%; height: 100%; object-fit: cover; display: block; }
.middle-overlay {
position: absolute;
left: 805.34px; top: 263.21px;
width: 1897.331px; height: 647.564px;
pointer-events: none;
}
.middle-overlay img { width: 100%; height: 100%; object-fit: cover; opacity: 0.8; display: block; }
.left-bg {
position: absolute;
left: 127.86px; top: 164.5px;
width: 888.858px; height: 719.625px;
pointer-events: none;
}
.left-bg img { width: 100%; height: 100%; object-fit: cover; display: block; }
.left-decor {
position: absolute;
left: 4.13px; top: 233.6px;
width: 639.318px; height: 612.027px;
display: flex; align-items: center; justify-content: center;
pointer-events: none;
}
.left-decor-inner {
width: 612.027px; height: 639.318px;
transform: scaleY(-1) rotate(90deg);
}
.left-decor-inner img { width: 100%; height: 100%; object-fit: cover; display: block; }
.left-inner-bg {
position: absolute;
left: 27.84px; top: 256.3px;
width: 591.885px; height: 566.618px;
pointer-events: none;
}
.left-inner-bg img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hanmaek-label {
position: absolute;
left: 142.3px; top: 374.76px;
width: 56.714px; height: 32.576px;
pointer-events: none;
}
.hanmaek-label img { width: 100%; height: 100%; object-fit: cover; opacity: 0.9; display: block; }
.dx-circle {
position: absolute;
left: 28.87px; top: 378.71px;
width: 592.916px; height: 320.82px;
pointer-events: none;
}
.dx-circle img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* ────────────────────────────────────────────────
카드 (3개)
──────────────────────────────────────────────── */
.card-frame {
position: absolute;
width: 515.579px; height: 377.767px;
}
.card-frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card-header-bg {
position: absolute;
width: 378.435px; height: 85.881px;
}
.card-header-bg img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card-bottom-decor {
position: absolute;
width: 515.579px; height: 185.746px;
}
.card-bottom-decor img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 카드 1 (BIM 전면설계, x=913) */
.card-1-frame { left: 913.61px; top: 310.59px; }
.card-1-header { left: 974.45px; top: 310.59px; }
.card-1-decor { left: 913.61px; top: 642.27px; }
/* 카드 2 (디지털 전환 S/W, x=1500) */
.card-2-frame { left: 1500.34px; top: 310.59px; height: 376.908px; }
.card-2-header { left: 1558.08px; top: 310.59px; }
.card-2-decor { left: 1500.34px; top: 642.27px; }
/* 카드 3 (고부가가치 산업전환, x=2080) */
.card-3-frame { left: 2080.88px; top: 310.59px; height: 376.908px; }
.card-3-header { left: 2141.71px; top: 310.59px; }
.card-3-decor { left: 2081.91px; top: 642.27px; }
/* 카드 타이틀 — 40px Bold, 흰 fill + 컬러 stroke (Figma 실제 렌더 기준)
design_context 가 잘못 알려준 dark gradient 가 아니라 시각 검증으로 확정 */
.card-title {
position: absolute;
width: 389.778px; height: 64.164px;
font-family: 'Noto Sans KR', sans-serif;
font-weight: 700;
font-size: 40px;
line-height: 64.164px;
text-align: center;
white-space: nowrap;
color: #ffffff;
/* 외곽 다크 글로우 + 컬러 stroke (paint-order 로 fill 위에) */
}
.card-title-1 {
/* center=(1164.18, 355.51) → top-left=(969.29, 323.43) */
left: 969.29px; top: 323.43px;
-webkit-text-stroke: 1.5px #1d4d3e;
text-shadow: 0 0 4px #322c1e;
paint-order: stroke fill;
}
.card-title-2 {
/* center=(1743.69, 355.51) → top-left=(1548.8, 323.43) */
left: 1548.8px; top: 323.43px;
-webkit-text-stroke: 1.5px #cc5200;
text-shadow: 0 0 4px #322c1e;
paint-order: stroke fill;
}
.card-title-3 {
/* center=(2336.6, 355.51) → top-left=(2141.71, 323.43) */
left: 2141.71px; top: 323.43px;
-webkit-text-stroke: 1.5px #1d4d3e;
text-shadow: 0 0 4px #322c1e;
paint-order: stroke fill;
}
/* 본문 텍스트 — Figma leading-[0] + per-p leading-[60px] 패턴 */
.body-text {
position: absolute;
font-family: 'Noto Sans KR', sans-serif;
font-weight: 500;
font-size: 37px;
color: black;
line-height: 0; /* 부모 leading-0 (Figma) — 자식 <p> 가 자기 leading 가짐 */
}
.body-text p { margin: 0; line-height: 60px; }
.body-text p.blank { line-height: 60px; }
.body-text p.blank::before { content: "\200B"; } /* zero-width-space — Figma의 와 동일 */
/* card 1 — 3 단락 절대좌표 (bullet top - 10.86 에 align) */
.body-card-1-p1 {
left: 989.91px; top: 450.77px; /* 1st bullet 461.63 - 10.86 */
width: 426.233px;
letter-spacing: 0;
}
.body-card-1-p3 {
left: 989.91px; top: 532.70px; /* bullet 1.2 543.56 - 10.86 */
width: 426.233px;
letter-spacing: -0.48px;
}
.body-card-1-p5 {
left: 989.91px; top: 611.67px; /* bullet 1.3 622.53 - 10.86 */
width: 426.233px;
letter-spacing: -0.48px;
}
/* card 2 — 3 단락 (Figma 그대로 분리되어 있음) */
.body-card-2-p1 {
left: 1570.48px; top: 450.77px;
width: 453.964px;
letter-spacing: 0;
}
.body-card-2-p2 {
left: 1571.49px; top: 530.77px;
width: 453.964px;
letter-spacing: -0.64px;
}
.body-card-2-p3 {
left: 1568.11px; top: 612.29px;
width: 453.964px;
}
.body-card-2-p3 .seg-37-tight { font-size: 37px; letter-spacing: -2.22px; }
.body-card-2-p3 .seg-30 { font-size: 30px; letter-spacing: -1.8px; }
.body-card-2-p3 .seg-37-loose { font-size: 37px; letter-spacing: -1.48px; }
/* card 3 — 4 그룹 절대좌표 (p3+p4 는 한 컨테이너로 연속, p6 는 br 으로 2줄) */
.body-card-3-p1 {
left: 2154.09px; top: 450.77px;
width: 442.62px;
letter-spacing: -1.48px;
}
.body-card-3-p3p4 {
/* p3 "업무 본질..." 와 p4 "바탕으로..." 가 연속 (between blank 없음) */
left: 2154.09px; top: 532.70px; /* bullet 3.2 543.56 - 10.86 */
width: 442.62px;
}
.body-card-3-p3p4 .p3 { letter-spacing: -2.59px; }
.body-card-3-p3p4 .p4 { letter-spacing: -2.96px; }
.body-card-3-p6 {
left: 2154.09px; top: 667.94px; /* bullet 3.3 678.8 - 10.86 */
width: 442.62px;
}
.body-card-3-p6 .seg-37 { font-size: 37px; letter-spacing: -1.11px; }
.body-card-3-p6 .seg-33 { font-size: 33px; letter-spacing: -0.99px; }
/* 불릿 (▶ 화살표 9개) */
.bullet {
position: absolute;
width: 41.246px; height: 39.486px;
}
.bullet img { width: 100%; height: 100%; object-fit: cover; display: block; }
.bullet-1-1 { left: 936.29px; top: 461.63px; }
.bullet-1-2 { left: 936.29px; top: 543.56px; }
.bullet-1-3 { left: 936.29px; top: 622.53px; }
.bullet-2-1 { left: 1520.96px; top: 461.63px; }
.bullet-2-2 { left: 1520.96px; top: 543.56px; }
.bullet-2-3 { left: 1520.96px; top: 625.5px; }
.bullet-3-1 { left: 2101.5px; top: 461.63px; }
.bullet-3-2 { left: 2101.5px; top: 543.56px; }
.bullet-3-3 { left: 2101.5px; top: 678.8px; }
</style>
</head>
<body>
<div class="slide">
<div class="block">
<div class="inner">
<!-- ① 상단 배너 (가로 그라디언트, CSS) -->
<div class="header-bg-full"></div>
<div class="header-decor-l"><img src="assets/header_decor.png" alt=""></div>
<div class="header-decor-r"><img src="assets/header_decor.png" alt=""></div>
<!-- ② 타이틀 -->
<div class="title-text">
<p><span class="green">디지털 전환(DX)</span><span class="white"></span><span class="green"> S/W</span><span class="white">가 필수다.</span></p>
</div>
<!-- ③ 본문 BG -->
<div class="body-bg"><img src="assets/body_bg.png" alt=""></div>
<div class="middle-overlay"><img src="assets/middle_overlay.png" alt=""></div>
<div class="left-bg"><img src="assets/left_bg.png" alt=""></div>
<div class="left-decor"><div class="left-decor-inner"><img src="assets/left_decor.png" alt=""></div></div>
<div class="left-inner-bg"><img src="assets/left_inner_bg.png" alt=""></div>
<div class="hanmaek-label"><img src="assets/hanmaek_label.png" alt=""></div>
<div class="dx-circle"><img src="assets/dx_circle.png" alt=""></div>
<!-- 카드 1 — BIM 전면설계 -->
<div class="card-frame card-1-frame"><img src="assets/card_frame.png" alt=""></div>
<div class="card-header-bg card-1-header"><img src="assets/card_header_bg.png" alt=""></div>
<div class="card-bottom-decor card-1-decor"><img src="assets/card_bottom_decor.png" alt=""></div>
<div class="card-title card-title-1">BIM 전면설계</div>
<div class="body-text body-card-1-p1"><p>건설산업 생산성 향상</p></div>
<div class="body-text body-card-1-p3"><p>고부가가치 산업으로의 전환</p></div>
<div class="body-text body-card-1-p5"><p>건설산업의 디지털전환(DX)<br>체계 마련 및 고도화 필요</p></div>
<div class="bullet bullet-1-1"><img src="assets/bullet_arrow.png" alt="▶"></div>
<div class="bullet bullet-1-2"><img src="assets/bullet_arrow.png" alt="▶"></div>
<div class="bullet bullet-1-3"><img src="assets/bullet_arrow.png" alt="▶"></div>
<!-- 카드 2 — 디지털 전환 S/W -->
<div class="card-frame card-2-frame"><img src="assets/card_frame.png" alt=""></div>
<div class="card-header-bg card-2-header"><img src="assets/card_header_bg.png" alt=""></div>
<div class="card-bottom-decor card-2-decor"><img src="assets/card_bottom_decor.png" alt=""></div>
<div class="card-title card-title-2">디지털 전환 S/W</div>
<div class="body-text body-card-2-p1"><p>노동집약형 업무 탈피</p></div>
<div class="body-text body-card-2-p2"><p class="p1">S/W의 지속적인 고도화 필요</p></div>
<div class="body-text body-card-2-p3">
<p><span class="seg-37-tight">충분한 투자</span><span class="seg-30">(인력, 비용, 시간 등)</span><span class="seg-37-tight"></span><span class="seg-37-loose">&nbsp;엔지니어의 적극적 동참</span></p>
</div>
<div class="bullet bullet-2-1"><img src="assets/bullet_arrow.png" alt="▶"></div>
<div class="bullet bullet-2-2"><img src="assets/bullet_arrow.png" alt="▶"></div>
<div class="bullet bullet-2-3"><img src="assets/bullet_arrow.png" alt="▶"></div>
<!-- 카드 3 — 고부가가치 산업전환 -->
<div class="card-frame card-3-frame"><img src="assets/card_frame.png" alt=""></div>
<div class="card-header-bg card-3-header"><img src="assets/card_header_bg.png" alt=""></div>
<div class="card-bottom-decor card-3-decor"><img src="assets/card_bottom_decor.png" alt=""></div>
<div class="card-title card-title-3">고부가가치 산업전환</div>
<div class="body-text body-card-3-p1"><p>기본기술의 이해 &amp; 발전 필요</p></div>
<div class="body-text body-card-3-p3p4">
<p class="p3">업무 본질에 대한 깊은 이해를</p>
<p class="p4">바탕으로 창의적 아이디어 발현</p>
</div>
<div class="body-text body-card-3-p6">
<p><span class="seg-37">업무환경 개선과 DX를 통한<br></span><span class="seg-33">Process</span><span class="seg-37">&nbsp;혁신과&nbsp;</span><span class="seg-33">Product</span><span class="seg-37">&nbsp;개선</span></p>
</div>
<div class="bullet bullet-3-1"><img src="assets/bullet_arrow.png" alt="▶"></div>
<div class="bullet bullet-3-2"><img src="assets/bullet_arrow.png" alt="▶"></div>
<div class="bullet bullet-3-3"><img src="assets/bullet_arrow.png" alt="▶"></div>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

View File

@@ -0,0 +1,40 @@
# Frame 1171281198 — 텍스트 (TF-IDF 매칭용)
> 프레임 안의 모든 텍스트를 빠짐없이 추출.
## 타이틀
디지털 전환(DX)은 S/W가 필수다.
> "디지털 전환(DX)" 및 "S/W" 부분은 녹색(#90fa33), 나머지("은", "가 필수다.")는 흰색
## 3열 카드 헤더 (상단 라벨)
### 열1 헤더
BIM 전면설계
### 열2 헤더
디지털 전환 S/W
### 열3 헤더
고부가가치 산업전환
## 본문 (3열 불릿 리스트)
### 열1 — BIM 전면설계
- 건설산업 생산성 향상
- 고부가가치 산업으로의 전환
- 건설산업의 디지털전환(DX) 체계 마련 및 고도화 필요
### 열2 — 디지털 전환 S/W
- 노동집약형 업무 탈피
- S/W의 지속적인 고도화 필요
- 충분한 투자(인력, 비용, 시간 등)와 엔지니어의 적극적 동참
### 열3 — 고부가가치 산업전환
- 기본기술의 이해 & 발전 필요
- 업무 본질에 대한 깊은 이해를 바탕으로 창의적 아이디어 발현
- 업무환경 개선과 DX를 통한 Process 혁신과 Product 개선