Files
Figma-to-HTML/templates_staging/cards-3col-persona.html.j2
kyeongmin beb5fd0c61 Figma-to-HTML 에이전트 초기 커밋
- 10단계 변환 프로세스 (PROCESS.md)
- 수학 공식 레퍼런스 (MATH.md, gradient_math.py)
- CSS 보정 규칙 R1~R16 (RULES.md)
- 작업 규율 7개 규칙 (PROCESS-CONTROL.md)
- 8개 Figma 프레임 1:1 HTML 변환물 (block-tests/)
- 8개 Jinja2 템플릿 staging (templates_staging/)
- 변환 완료 도서관 + 디자인 인사이트 (blocks_index.md)
- 사용법 가이드 (README.md)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:16:33 +09:00

270 lines
8.9 KiB
Django/Jinja

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1280">
<title>{{ title|default("cards-3col-persona") }}</title>
<!--
============================================================
Pattern: cards-3col-persona
Source: figma_to_html_agent/block-tests/bim-3roles-cards.html (1:1 reference)
Origin frame: 45:16 / Frame 1171281191
STRUCTURE: N개의 평행한 stakeholder 카드 (N=2~4)
각 카드 = bg image + color overlay + 상단 원형 badge + bullet list (custom marker) + optional bottom photo
PRINCIPLES (RULES.md, blocks_index.md 디자인 인사이트):
- I-1: 마커+텍스트는 시맨틱 list (R13)
- I-2: 평행 컬럼은 동일 top/bottom + 내부 spacing 자동 분배
- I-3: 모든 슬롯은 기본 optional (bottom_photo 등)
============================================================
-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@500;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
/* 디자인 토큰 — 외부에서 override 가능 */
--block-w: {{ block_w|default(1280) }}px;
--block-h: {{ block_h|default(948) }}px;
--font-family: 'Noto Sans KR', sans-serif;
--font-body-size: {{ font_body_size|default("1.05rem") }};
--font-body-lh: {{ font_body_lh|default(2.125) }}; /* 85/40 */
--font-body-lh-compact: {{ font_body_lh_compact|default(1.25) }}; /* 50/40 */
--col-gap: {{ col_gap|default("4px") }};
--col-padding-x: {{ col_padding_x|default("21px") }}; /* block 좌우 padding */
--badge-width-ratio: {{ badge_width_ratio|default("48%") }}; /* col 너비 대비 */
--bullet-list-padding-x: {{ bullet_list_padding_x|default("10%") }};
--bullet-list-padding-y: {{ bullet_list_padding_y|default("13%") }};
--photo-height-ratio: {{ photo_height_ratio|default("38%") }};
--photo-margin-x: {{ photo_margin_x|default("8%") }};
--photo-margin-bottom: {{ photo_margin_bottom|default("4%") }};
--photo-radius: {{ photo_radius|default("6%") }};
--marker-w: {{ marker_w|default("1.4rem") }};
--marker-h: {{ marker_h|default("1.4rem") }};
--marker-gap: {{ marker_gap|default("0.55rem") }};
}
body {
font-family: var(--font-family);
background: #e8ecf0;
display: flex; justify-content: center; align-items: center;
min-height: 100vh;
padding: 20px;
}
.block {
width: var(--block-w);
height: var(--block-h);
background: #ffffff;
position: relative;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, .15);
/* Layout: flex row of N columns */
display: flex;
flex-direction: row;
gap: var(--col-gap);
padding: 0 var(--col-padding-x);
}
/* ─────────────────────────────────────────────────────────
role-card: 한 stakeholder 컬럼. flex column.
───────────────────────────────────────────────────────── */
.role-card {
flex: 1 1 0; /* 모든 컬럼 동일 width */
position: relative;
display: flex;
flex-direction: column;
}
/* ─────────────────────────────────────────────────────────
badge: 카드 상단 원형 뱃지 (역할 라벨)
───────────────────────────────────────────────────────── */
.badge {
position: relative;
width: var(--badge-width-ratio);
aspect-ratio: 1; /* 정사각형 영역 */
margin: 0 auto;
z-index: 3;
flex: none;
}
.badge img.outer {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: contain;
}
.badge img.inner {
position: absolute;
width: 76%;
height: 76%;
inset: 12%;
object-fit: contain;
}
.badge .label {
position: absolute;
/* Figma: label center at ~64% of badge height (figure가 상단부 차지)
→ label area를 badge 하단 ~52%에 배치 */
left: 0; right: 0;
top: 38%;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-weight: 700;
text-align: center;
letter-spacing: -0.02em;
z-index: 10;
pointer-events: none;
}
.badge .label .ln1 { font-size: 1.7rem; line-height: 1; }
.badge .label .ln2 { font-size: 1.3rem; line-height: 1.2; margin-top: 0.1em; }
/* ─────────────────────────────────────────────────────────
card-body: bg + overlay + bullet list + optional photo
───────────────────────────────────────────────────────── */
.card-body {
position: relative;
flex: 1;
margin-top: -10%; /* badge와 약간 겹침 */
z-index: 1;
display: flex;
flex-direction: column;
}
.card-body .bg,
.card-body .overlay {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
pointer-events: none;
}
.card-body .overlay {
opacity: 0.80;
}
/* ─────────────────────────────────────────────────────────
bullet-list: R13 Custom-Marker Bullet List 패턴
각 컬럼이 동일 height + justify-content space-between
───────────────────────────────────────────────────────── */
.bullet-list {
position: relative;
z-index: 2;
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: var(--bullet-list-padding-y) var(--bullet-list-padding-x);
list-style: none;
}
.bullet-row {
display: flex;
align-items: flex-start;
--lh: var(--font-body-lh);
}
.bullet-row.compact {
--lh: var(--font-body-lh-compact);
}
.bullet-icon {
flex: none;
width: var(--marker-w);
height: var(--marker-h);
margin-right: var(--marker-gap);
/* 핵심: icon center align with text first line center */
margin-top: calc((1em * var(--lh) - var(--marker-h)) / 2);
}
.bullet-icon img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
.bullet-text {
flex: 1;
font-size: var(--font-body-size);
line-height: var(--lh);
color: #000;
white-space: normal;
word-break: keep-all;
}
/* ─────────────────────────────────────────────────────────
bottom-photo: optional. 카드 하단 사진 카드
───────────────────────────────────────────────────────── */
.bottom-photo {
position: relative;
z-index: 2;
flex: none;
height: var(--photo-height-ratio);
margin: 0 var(--photo-margin-x) var(--photo-margin-bottom) var(--photo-margin-x);
border-radius: var(--photo-radius);
overflow: hidden;
opacity: 0.70;
}
.bottom-photo img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* photo가 없는 컬럼은 bullet-list가 더 많은 공간 차지 */
.role-card.no-photo .bullet-list {
padding-bottom: 6%;
}
</style>
</head>
<body>
<div class="block">
{% for col in columns %}
<article class="role-card{% if not col.bottom_photo %} no-photo{% endif %}">
<!-- Badge: 상단 원형 -->
<div class="badge">
<img class="outer" src="{{ col.badge.outer_image }}" alt="">
<img class="inner" src="{{ col.badge.inner_image }}" alt="">
<span class="label" style="color: {{ col.role_color }};">
<span class="ln1">{{ col.role_label[0] }}</span>
<span class="ln2">{{ col.role_label[1] }}</span>
</span>
</div>
<!-- Card body: bg + overlay + bullets + optional photo -->
<div class="card-body">
<img class="bg" src="{{ col.bg_image }}" alt="">
{% if col.overlay_image %}
<img class="overlay" src="{{ col.overlay_image }}" alt="">
{% endif %}
<!-- bullet-list: R13 sub-pattern -->
<ul class="bullet-list">
{% for item in col.bullet_items %}
<li class="bullet-row{% if item.compact %} compact{% endif %}">
<span class="bullet-icon"><img src="{{ marker_icon }}" alt=""></span>
<span class="bullet-text">{{ item.text|safe }}</span>
</li>
{% endfor %}
</ul>
<!-- Optional bottom photo -->
{% if col.bottom_photo %}
<div class="bottom-photo">
<img src="{{ col.bottom_photo }}" alt="">
</div>
{% endif %}
</div>
</article>
{% endfor %}
</div>
</body>
</html>