- 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>
270 lines
8.9 KiB
Django/Jinja
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>
|