diff --git a/generate_cards.py b/generate_cards.py
new file mode 100644
index 0000000..2751ada
--- /dev/null
+++ b/generate_cards.py
@@ -0,0 +1,497 @@
+"""
+Mingle Studio Twitter Card Generator
+Clean white theme, text-focused, NanumGothic
+5 square 1080x1080 promotional cards
+"""
+
+from PIL import Image, ImageDraw, ImageFont
+import os
+
+W, H = 1080, 1080
+FONT_DIR = ".claude/skills/canvas-design/canvas-fonts"
+USER_FONTS = "C:/Users/qscft/AppData/Local/Microsoft/Windows/Fonts"
+KR_XBOLD = os.path.join(USER_FONTS, "NanumGothicExtraBold.ttf")
+KR_BOLD = os.path.join(USER_FONTS, "NanumGothicBold.ttf")
+KR_REG = os.path.join(USER_FONTS, "NanumGothic.ttf")
+KR_LIGHT = os.path.join(USER_FONTS, "NanumGothicLight.ttf")
+EN_BOLD = os.path.join(FONT_DIR, "Outfit-Bold.ttf")
+EN_REG = os.path.join(FONT_DIR, "Outfit-Regular.ttf")
+MONO = os.path.join(FONT_DIR, "GeistMono-Regular.ttf")
+KR_ROUND = "C:/Windows/Fonts/malgunbd.ttf"
+LOGO_PATH = "images/logo/mingle-logo.webp"
+OUTPUT_DIR = "twitter_cards"
+
+# Colors
+BG = (250, 250, 250)
+WHITE = (255, 255, 255)
+BLACK = (30, 30, 30)
+DARK = (50, 50, 50)
+GRAY = (120, 120, 120)
+LIGHT_GRAY = (200, 200, 200)
+VERY_LIGHT = (235, 235, 235)
+ORANGE = (255, 136, 0)
+ORANGE_LIGHT = (255, 243, 228)
+PURPLE = (108, 92, 231)
+PURPLE_LIGHT = (237, 233, 255)
+
+BIZ_EMAIL = "minglestudio@minglestudio.co.kr"
+
+# Layout constants
+MARGIN = 70
+NOTE_Y = 940 # price note y — right above footer
+FOOTER_Y = H - 80 # footer separator line
+
+os.makedirs(OUTPUT_DIR, exist_ok=True)
+
+
+def load_fonts():
+ fonts = {}
+ sizes = {
+ 'title': 52, 'subtitle': 30, 'price': 64, 'price_sm': 42,
+ 'body': 23, 'body_sm': 19, 'label': 16, 'label_sm': 14,
+ 'pct': 46, 'big_pct': 68, 'num_accent': 130,
+ }
+ for name, size in sizes.items():
+ fonts[f'kr_xb_{name}'] = ImageFont.truetype(KR_XBOLD, size)
+ fonts[f'kr_{name}'] = ImageFont.truetype(KR_BOLD, size)
+ fonts[f'kr_r_{name}'] = ImageFont.truetype(KR_REG, size)
+ fonts[f'kr_l_{name}'] = ImageFont.truetype(KR_LIGHT, size)
+ fonts[f'en_{name}'] = ImageFont.truetype(EN_BOLD, size)
+ fonts[f'en_r_{name}'] = ImageFont.truetype(EN_REG, size)
+ fonts[f'mono_{name}'] = ImageFont.truetype(MONO, size)
+ fonts[f'round_{name}'] = ImageFont.truetype(KR_ROUND, size)
+ return fonts
+
+
+def load_logo(size=40):
+ logo = Image.open(LOGO_PATH).convert("RGBA")
+ logo = logo.resize((size, size), Image.LANCZOS)
+ return logo
+
+
+# ── Common drawing helpers ──
+
+def draw_bottom_bar(img, draw, fonts):
+ draw.line([(MARGIN, FOOTER_Y), (W - MARGIN, FOOTER_Y)], fill=LIGHT_GRAY, width=1)
+ logo = load_logo(32)
+ img.paste(logo, (MARGIN, FOOTER_Y + 14), logo)
+ draw.text((MARGIN + 40, FOOTER_Y + 16), "Mingle Studio", fill=GRAY, font=fonts['en_r_label'])
+ draw.text((MARGIN + 40, FOOTER_Y + 34), BIZ_EMAIL, fill=GRAY, font=fonts['mono_label_sm'])
+
+
+def draw_orange_bar(draw):
+ draw.rectangle([0, 0, W, 8], fill=ORANGE)
+
+
+def text_vcenter(draw, x, cy, text, font, fill):
+ """Draw text vertically centered at cy."""
+ bbox = draw.textbbox((0, 0), text, font=font)
+ th = bbox[3] - bbox[1]
+ top_off = bbox[1]
+ draw.text((x, cy - th // 2 - top_off), text, fill=fill, font=font)
+ return bbox[2] - bbox[0], th
+
+
+def draw_tag(draw, x, y, text, font, bg=ORANGE, fg=WHITE):
+ """Tag badge at (x, y) top-left."""
+ bbox = draw.textbbox((0, 0), text, font=font)
+ tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
+ t_off = bbox[1]
+ px, py = 12, 6
+ bw, bh = tw + px * 2, th + py * 2
+ draw.rounded_rectangle([x, y, x + bw, y + bh], radius=6, fill=bg)
+ draw.text((x + px, y + py - t_off), text, fill=fg, font=font)
+ return bw, bh
+
+
+def draw_tag_vcenter(draw, x, cy, text, font, bg=ORANGE, fg=WHITE):
+ """Tag badge vertically centered at cy."""
+ bbox = draw.textbbox((0, 0), text, font=font)
+ th = bbox[3] - bbox[1]
+ bh = th + 12
+ return draw_tag(draw, x, cy - bh // 2, text, font, bg, fg)
+
+
+def draw_bullet(draw, x, cy, color=ORANGE, r=5):
+ """Bullet dot centered at cy."""
+ draw.ellipse([x, cy - r, x + r * 2, cy + r], fill=color)
+
+
+def draw_bullet_at_text(draw, bx, tx, ty, text, font, color=ORANGE, r=5):
+ """Draw bullet aligned to vertical center of text at (tx, ty)."""
+ bbox = draw.textbbox((0, 0), text, font=font)
+ th = bbox[3] - bbox[1]
+ top_off = bbox[1]
+ text_cy = ty + top_off + th // 2
+ draw.ellipse([bx, text_cy - r, bx + r * 2, text_cy + r], fill=color)
+
+
+def draw_price_note(draw, fonts):
+ draw.text((MARGIN, NOTE_Y), "* 상기 비용은 변동될 수 있습니다", fill=GRAY, font=fonts['kr_r_label'])
+
+
+def draw_arrow(draw, x, cy, color=ORANGE, size=6):
+ """Small right-pointing triangle arrow centered at cy."""
+ draw.polygon([(x, cy - size), (x + size, cy), (x, cy + size)], fill=color)
+
+
+def draw_header(draw, fonts, cat_label, title, underline_w, accent_num,
+ cat_color=ORANGE, line_color=ORANGE):
+ """Common header block. Returns y after underline."""
+ draw.text((MARGIN, 45), cat_label, fill=cat_color, font=fonts['mono_label'])
+ draw.text((MARGIN, 80), title, fill=BLACK, font=fonts['kr_xb_title'])
+ draw.line([(MARGIN, 143), (MARGIN + underline_w, 143)], fill=line_color, width=3)
+ # Number accent
+ draw.text((W - 220, 65), accent_num, fill=(242, 242, 242),
+ font=fonts['en_num_accent'])
+ return 143
+
+
+def draw_section_header(draw, x1, x2, y, h, text, font, bg_color,
+ tag_text=None, tag_font=None):
+ """Section card with colored header bar. Returns (card_top, header_bottom)."""
+ BAR_H = 52
+ draw.rounded_rectangle([x1, y, x2, y + h], radius=14, fill=WHITE, outline=bg_color, width=2)
+ draw.rounded_rectangle([x1, y, x2, y + BAR_H], radius=14, fill=bg_color)
+ draw.rectangle([x1, y + 28, x2, y + BAR_H], fill=bg_color)
+ # Vertically center text in header bar (nudge +1 to compensate rounded top)
+ bar_cy = y + BAR_H // 2 + 1
+ text_vcenter(draw, x1 + 20, bar_cy, text, font, WHITE)
+ if tag_text and tag_font:
+ draw_tag_vcenter(draw, x1 + 220, bar_cy, tag_text, tag_font, WHITE, bg_color)
+ return y, y + BAR_H
+
+
+# ═══════════════════════════════════════
+# Card 1: 모션캡처 녹화
+# ═══════════════════════════════════════
+def create_card1(fonts):
+ img = Image.new('RGB', (W, H), BG)
+ draw = ImageDraw.Draw(img)
+ draw_orange_bar(draw)
+ draw_header(draw, fonts, "SERVICE 01", "모션캡처 녹화", 310, "01")
+
+ # Price — "원~ / 시간" right after the number on same line
+ draw.text((MARGIN, 180), "200,000", fill=ORANGE, font=fonts['en_price'])
+ bbox_price = draw.textbbox((MARGIN, 180), "200,000", font=fonts['en_price'])
+ draw.text((bbox_price[2] + 8, bbox_price[3] - 34), "원~ / 시간 (2인 기준)", fill=BLACK, font=fonts['kr_r_subtitle'])
+ draw.text((MARGIN, 255), "VAT 별도 | 추가 인원 +100,000원~/명/시간", fill=GRAY, font=fonts['kr_r_body_sm'])
+
+ # Features
+ features = [
+ ("OptiTrack 30대 카메라", "정밀한 모션 트래킹"),
+ ("전신 / 페이셜 캡처", "풀바디 + 표정 동시 캡처"),
+ ("8 x 7m 전용 캡처 공간", "넓은 촬영 볼륨"),
+ ]
+ CARD_H = 92
+ CARD_GAP = 18
+ start_y = 380
+ for i, (title, desc) in enumerate(features):
+ y = start_y + i * (CARD_H + CARD_GAP)
+ draw.rounded_rectangle([MARGIN, y, W - MARGIN, y + CARD_H], radius=12,
+ fill=WHITE, outline=VERY_LIGHT, width=1)
+ title_y = y + 16
+ draw_bullet_at_text(draw, MARGIN + 25, MARGIN + 50, title_y, title, fonts['kr_body'])
+ draw.text((MARGIN + 50, title_y), title, fill=BLACK, font=fonts['kr_body'])
+ draw.text((MARGIN + 50, y + 50), desc, fill=GRAY, font=fonts['kr_r_body_sm'])
+
+ draw_price_note(draw, fonts)
+ draw_bottom_bar(img, draw, fonts)
+ img.save(os.path.join(OUTPUT_DIR, "card1_mocap.png"), quality=95)
+ print(" Card 1 done")
+
+
+# ═══════════════════════════════════════
+# Card 2: 라이브 방송
+# ═══════════════════════════════════════
+def create_card2(fonts):
+ img = Image.new('RGB', (W, H), BG)
+ draw = ImageDraw.Draw(img)
+ draw_orange_bar(draw)
+ draw_header(draw, fonts, "SERVICE 02", "모션캡처 라이브 방송", 480, "02")
+ draw.text((MARGIN, 162), "실시간 모션캡처 방송 풀패키지", fill=GRAY, font=fonts['kr_r_body_sm'])
+
+ # 4h package (BEST)
+ by = 210
+ PKG_H = 105
+ draw.rounded_rectangle([MARGIN, by, W - MARGIN, by + PKG_H], radius=14,
+ fill=ORANGE_LIGHT, outline=ORANGE, width=2)
+ draw.text((95, by + 12), "4시간 패키지", fill=DARK, font=fonts['kr_r_body_sm'])
+ draw_tag(draw, 260, by + 10, "BEST", fonts['mono_label_sm'])
+ draw.text((95, by + 40), "1,400,000", fill=ORANGE, font=fonts['en_price_sm'])
+ # "원 (VAT 별도)" right after the number
+ bbox_price = draw.textbbox((95, by + 40), "1,400,000", font=fonts['en_price_sm'])
+ draw.text((bbox_price[2] + 8, by + 56), "원 (VAT 별도)", fill=GRAY, font=fonts['kr_r_body_sm'])
+
+ # 6h package
+ by2 = by + PKG_H + 20
+ draw.rounded_rectangle([MARGIN, by2, W - MARGIN, by2 + PKG_H], radius=14,
+ fill=WHITE, outline=VERY_LIGHT, width=1)
+ draw.text((95, by2 + 12), "6시간 패키지", fill=GRAY, font=fonts['kr_r_body_sm'])
+ draw.text((95, by2 + 40), "2,000,000", fill=ORANGE, font=fonts['en_price_sm'])
+ bbox_price2 = draw.textbbox((95, by2 + 40), "2,000,000", font=fonts['en_price_sm'])
+ draw.text((bbox_price2[2] + 8, by2 + 56), "원 (VAT 별도)", fill=GRAY, font=fonts['kr_r_body_sm'])
+
+ # Features section
+ fy = by2 + PKG_H + 30
+ feats = [
+ "아바타 · 배경 세팅 포함",
+ "최대 5인 동시 방송",
+ "보유 프랍 무제한 무료 제공",
+ "신규 프랍 최대 6개 제공",
+ ]
+ feat_item_h = 40
+ feat_box_h = 56 + len(feats) * feat_item_h + 20
+ draw.rounded_rectangle([MARGIN, fy, W - MARGIN, fy + feat_box_h], radius=14,
+ fill=WHITE, outline=VERY_LIGHT, width=1)
+ draw.text((95, fy + 18), "포함 사항", fill=BLACK, font=fonts['kr_body'])
+ draw.line([(95, fy + 50), (W - 95, fy + 50)], fill=VERY_LIGHT, width=1)
+ for i, f in enumerate(feats):
+ y = fy + 66 + i * feat_item_h
+ draw_bullet_at_text(draw, 110, 130, y, f, fonts['kr_r_body_sm'])
+ draw.text((130, y), f, fill=DARK, font=fonts['kr_r_body_sm'])
+
+ draw_price_note(draw, fonts)
+ draw_bottom_bar(img, draw, fonts)
+ img.save(os.path.join(OUTPUT_DIR, "card2_streamingle.png"), quality=95)
+ print(" Card 2 done")
+
+
+# ═══════════════════════════════════════
+# Card 3: 뮤직비디오 제작
+# ═══════════════════════════════════════
+def create_card3(fonts):
+ img = Image.new('RGB', (W, H), BG)
+ draw = ImageDraw.Draw(img)
+ draw_orange_bar(draw)
+ draw_header(draw, fonts, "SERVICE 03", "뮤직비디오 제작", 350, "03")
+
+ # Price range
+ draw.text((MARGIN, 180), "2,000,000", fill=ORANGE, font=fonts['en_price'])
+ draw.text((MARGIN, 255), "~ 4,000,000원 (VAT 별도)", fill=BLACK, font=fonts['kr_r_subtitle'])
+
+ # Features
+ features = [
+ ("3D 모션캡처 뮤직비디오", "고퀄리티 3D 아바타 MV 제작"),
+ ("숏폼 댄스 챌린지", "틱톡 · 유튜브 숏츠 · 릴스"),
+ ("기획부터 완성까지", "풀 프로덕션 지원"),
+ ("사전 협의 필수", "기획서 및 준비물 사전 조율"),
+ ]
+ CARD_H = 88
+ CARD_GAP = 16
+ start_y = 340
+ for i, (title, desc) in enumerate(features):
+ y = start_y + i * (CARD_H + CARD_GAP)
+ draw.rounded_rectangle([MARGIN, y, W - MARGIN, y + CARD_H], radius=12,
+ fill=WHITE, outline=VERY_LIGHT, width=1)
+ title_y = y + 14
+ draw_bullet_at_text(draw, MARGIN + 25, MARGIN + 50, title_y, title, fonts['kr_body'])
+ draw.text((MARGIN + 50, title_y), title, fill=BLACK, font=fonts['kr_body'])
+ draw.text((MARGIN + 50, y + 48), desc, fill=GRAY, font=fonts['kr_r_body_sm'])
+
+ draw_price_note(draw, fonts)
+ draw_bottom_bar(img, draw, fonts)
+ img.save(os.path.join(OUTPUT_DIR, "card3_mv.png"), quality=95)
+ print(" Card 3 done")
+
+
+# ═══════════════════════════════════════
+# Card 4: 할인 혜택
+# ═══════════════════════════════════════
+def create_card4(fonts):
+ img = Image.new('RGB', (W, H), BG)
+ draw = ImageDraw.Draw(img)
+ draw_orange_bar(draw)
+
+ draw.text((MARGIN, 45), "BENEFITS", fill=ORANGE, font=fonts['mono_label'])
+ draw.text((MARGIN, 80), "할인 혜택", fill=BLACK, font=fonts['kr_xb_title'])
+ draw.line([(MARGIN, 143), (MARGIN + 210, 143)], fill=ORANGE, width=3)
+ draw.text((MARGIN, 160), "라이브 방송 4시간 패키지 기준 | VAT 별도", fill=GRAY, font=fonts['kr_r_body_sm'])
+
+ # ── Referral section ──
+ ry = 210
+ BAR_H = 52
+ REF_H = 200
+ draw_section_header(draw, MARGIN, W - MARGIN, ry, REF_H,
+ "추천인 할인", fonts['kr_subtitle'], ORANGE,
+ "신규 고객 대상", fonts['kr_r_label_sm'])
+
+ # Content area center
+ content_cy = ry + BAR_H + (REF_H - BAR_H) // 2 # exact vertical center of content area
+
+ # 20% — vertically centered
+ text_vcenter(draw, 95, content_cy, "20", fonts['en_big_pct'], ORANGE)
+ text_vcenter(draw, 193, content_cy + 6, "%", fonts['en_r_subtitle'], ORANGE)
+
+ # Description — 3 lines as a block, centered at content_cy
+ desc_lines = [
+ ("추천인 & 신규고객 모두 할인", fonts['kr_r_body'], BLACK),
+ ("추천 횟수 제한 없이 누적 가능", fonts['kr_r_body_sm'], GRAY),
+ ("첫 예약 시 추천인을 알려주세요", fonts['kr_r_body_sm'], GRAY),
+ ]
+ line_gap = 30
+ total_desc_h = line_gap * (len(desc_lines) - 1)
+ desc_start_cy = content_cy - total_desc_h // 2
+ for i, (txt, fnt, clr) in enumerate(desc_lines):
+ text_vcenter(draw, 270, desc_start_cy + i * line_gap, txt, fnt, clr)
+
+ # ── Multi-pass section ──
+ my = ry + REF_H + 20
+ MULTI_H = 330
+ draw_section_header(draw, MARGIN, W - MARGIN, my, MULTI_H,
+ "다회권 할인", fonts['kr_subtitle'], PURPLE,
+ "최대 30%", fonts['kr_r_label_sm'])
+
+ # Table header — centered in available space
+ col_header_cy = my + BAR_H + 18
+ text_vcenter(draw, 130, col_header_cy, "이용권", fonts['kr_r_body_sm'], GRAY)
+ text_vcenter(draw, 460, col_header_cy, "할인율", fonts['kr_r_body_sm'], GRAY)
+ draw.line([(90, col_header_cy + 14), (W - 90, col_header_cy + 14)], fill=VERY_LIGHT, width=1)
+
+ # Table rows — evenly distribute in space between header line and note
+ table_top = col_header_cy + 22
+ table_bottom = my + MULTI_H - 36
+ row_area = table_bottom - table_top
+ ROW_H = row_area // 3
+ rows = [("3회권", "20%", False), ("5회권", "25%", False), ("7회권", "30%", True)]
+ for i, (name, rate, best) in enumerate(rows):
+ rowy = table_top + i * ROW_H
+ cy = rowy + ROW_H // 2
+ if best:
+ draw.rounded_rectangle([85, rowy + 4, W - 85, rowy + ROW_H - 4],
+ radius=8, fill=PURPLE_LIGHT)
+ text_vcenter(draw, 130, cy, name, fonts['kr_r_body'], BLACK)
+ text_vcenter(draw, 440, cy, rate, fonts['en_pct'], PURPLE)
+ if best:
+ draw_tag_vcenter(draw, 550, cy, "BEST", fonts['mono_label_sm'], PURPLE)
+ if i < 2:
+ draw.line([(90, rowy + ROW_H), (W - 90, rowy + ROW_H)],
+ fill=(242, 242, 242), width=1)
+
+ draw.text((90, my + MULTI_H - 30), "선결제 시 할인 적용 | 3개월 이내 소진",
+ fill=GRAY, font=fonts['kr_r_body_sm'])
+
+ draw_price_note(draw, fonts)
+ draw_bottom_bar(img, draw, fonts)
+ img.save(os.path.join(OUTPUT_DIR, "card4_discount.png"), quality=95)
+ print(" Card 4 done")
+
+
+# ═══════════════════════════════════════
+# Card 5: 기업 전용 정기권
+# ═══════════════════════════════════════
+def create_card5(fonts):
+ img = Image.new('RGB', (W, H), BG)
+ draw = ImageDraw.Draw(img)
+ draw_orange_bar(draw)
+
+ draw.text((MARGIN, 45), "BUSINESS", fill=PURPLE, font=fonts['mono_label'])
+ draw.text((MARGIN, 80), "기업 전용 정기권(3개월)", fill=BLACK, font=fonts['kr_xb_title'])
+ draw.line([(MARGIN, 143), (MARGIN + 530, 143)], fill=PURPLE, width=3)
+ draw_tag(draw, MARGIN, 160, "MCN · 기업 고객", fonts['kr_r_label'], PURPLE)
+
+ # ── Pricing table ──
+ BAR_H = 52
+ ty = 215
+ TABLE_H = 220
+ draw_section_header(draw, MARGIN, W - MARGIN, ty, TABLE_H,
+ "기업 전용 다회권", fonts['kr_subtitle'], PURPLE)
+
+ # Column headers — centered in header zone
+ col_cy = ty + BAR_H + 18
+ text_vcenter(draw, 120, col_cy, "이용권", fonts['kr_r_body_sm'], GRAY)
+ text_vcenter(draw, 350, col_cy, "할인율", fonts['kr_r_body_sm'], GRAY)
+ text_vcenter(draw, 590, col_cy, "회당 가격", fonts['kr_r_body_sm'], GRAY)
+ draw.line([(90, col_cy + 14), (W - 90, col_cy + 14)], fill=VERY_LIGHT, width=1)
+
+ # Rows — evenly distribute in remaining space
+ table_top = col_cy + 22
+ table_bottom = ty + TABLE_H - 10
+ row_area = table_bottom - table_top
+ ROW_H = row_area // 2
+ corp_rows = [
+ ("4회권", "25%", "1,050,000원", False),
+ ("6회권", "30%", "980,000원", True),
+ ]
+ for i, (name, rate, price, best) in enumerate(corp_rows):
+ rowy = table_top + i * ROW_H
+ cy = rowy + ROW_H // 2
+ if best:
+ draw.rounded_rectangle([85, rowy + 4, W - 85, rowy + ROW_H - 4],
+ radius=8, fill=PURPLE_LIGHT)
+ text_vcenter(draw, 120, cy, name, fonts['kr_r_body'], BLACK)
+ text_vcenter(draw, 330, cy, rate, fonts['en_pct'], PURPLE)
+ text_vcenter(draw, 570, cy, price, fonts['kr_r_body_sm'], DARK)
+ if best:
+ draw_tag_vcenter(draw, 740, cy, "BEST", fonts['mono_label_sm'], PURPLE)
+ if i == 0:
+ draw.line([(90, rowy + ROW_H), (W - 90, rowy + ROW_H)],
+ fill=(242, 242, 242), width=1)
+
+ # ── Renewal benefit ──
+ rny = ty + TABLE_H + 25
+ RENEW_H = 300
+ draw_section_header(draw, MARGIN, W - MARGIN, rny, RENEW_H,
+ "갱신 혜택", fonts['kr_subtitle'], ORANGE)
+
+ # Flow diagram — bigger pills with body font
+ pill_font = fonts['kr_r_body'] # bigger font for readability
+ fy = rny + 62
+ PILL_H = 42
+ steps = ["다회권 구매", "3개월 내 소진", "동일 할인율 적용"]
+ pill_info = []
+ for text in steps:
+ bbox = draw.textbbox((0, 0), text, font=pill_font)
+ tw = bbox[2] - bbox[0]
+ pill_info.append((text, tw))
+
+ total_pill_w = sum(tw + 32 for _, tw in pill_info)
+ arrow_space = 32
+ total_w = total_pill_w + 2 * arrow_space
+ sx = (W - total_w) // 2
+
+ for j, (text, tw) in enumerate(pill_info):
+ pw = tw + 32
+ draw.rounded_rectangle([sx, fy, sx + pw, fy + PILL_H], radius=10,
+ fill=ORANGE_LIGHT, outline=ORANGE, width=2)
+ text_vcenter(draw, sx + 16, fy + PILL_H // 2, text, pill_font, DARK)
+ if j < 2:
+ draw_arrow(draw, sx + pw + 10, fy + PILL_H // 2, ORANGE, 7)
+ sx += pw + arrow_space
+
+ # Description text
+ ty2 = fy + PILL_H + 22
+ draw.text((95, ty2), "소진 후 추가 예약 시", fill=DARK, font=fonts['kr_r_body'])
+ draw.text((95, ty2 + 30), "직전 구매 할인율을 동일하게 적용합니다", fill=BLACK, font=fonts['kr_body'])
+
+ # Examples
+ ty3 = ty2 + 76
+ draw.text((95, ty3), "ex) 4회권(25%) 소진", fill=GRAY, font=fonts['kr_r_body_sm'])
+ draw_arrow(draw, 355, ty3 + 10, ORANGE, 5)
+ draw.text((375, ty3), "추가 예약도 25% 할인", fill=GRAY, font=fonts['kr_r_body_sm'])
+
+ draw.text((95, ty3 + 28), "ex) 6회권(30%) 소진", fill=GRAY, font=fonts['kr_r_body_sm'])
+ draw_arrow(draw, 355, ty3 + 38, ORANGE, 5)
+ draw.text((375, ty3 + 28), "추가 예약도 30% 할인", fill=GRAY, font=fonts['kr_r_body_sm'])
+
+ # Notes
+ draw.text((MARGIN, NOTE_Y - 20),
+ "선결제 · 3개월 이내 소진 의무 | 라이브 방송 4시간 패키지 기준 | VAT 별도",
+ fill=GRAY, font=fonts['kr_r_label'])
+ draw_price_note(draw, fonts)
+
+ draw_bottom_bar(img, draw, fonts)
+ img.save(os.path.join(OUTPUT_DIR, "card5_corporate.png"), quality=95)
+ print(" Card 5 done")
+
+
+if __name__ == "__main__":
+ print("Loading fonts...")
+ fonts = load_fonts()
+ print("Generating cards...")
+ create_card1(fonts)
+ create_card2(fonts)
+ create_card3(fonts)
+ create_card4(fonts)
+ create_card5(fonts)
+ print(f"Done! -> {OUTPUT_DIR}/")
diff --git a/i18n/en.json b/i18n/en.json
index 2972d73..3192752 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -751,7 +751,7 @@
"q13": "Is live streaming possible?",
"q14": "Is parking available?",
"q15": "Are facility tours or studio visits available?",
- "a1": "
You can make a reservation through the following methods:
\n
\n
\n Email Inquiry \n
\n
\n
Please contact us at least 2 weeks in advance for smooth preparation.
",
+ "a1": "
You can make a reservation through the following methods:
\n
\n
\n Email Inquiry \n
\n
\n
Please contact us at least 2 weeks in advance for smooth preparation.
",
"a2": "
Here is our refund policy:
\n
\n
\n7 days before reservation \n100% refund \n
\n
\n3 days before reservation \n70% refund \n
\n
\n1 day before reservation \n50% refund \n
\n
\nSame-day cancellation \nNo refund \n
\n
",
"a3": "
The minimum rental is 2 hours .
\n
Extensions are available in 1-hour increments.
",
"a4": "
Please contact us at least 2 weeks in advance for smooth preparation.
",
diff --git a/i18n/ja.json b/i18n/ja.json
index 41c7268..37b426a 100644
--- a/i18n/ja.json
+++ b/i18n/ja.json
@@ -751,7 +751,7 @@
"q13": "リアルタイムストリーミングは可能ですか?",
"q14": "駐車場はありますか?",
"q15": "見学や施設ツアーは可能ですか?",
- "a1": "
以下の方法でご予約いただけます:
\n
\n
\n メールでお問い合わせ \n
\n
\n
最低2週間前にご連絡いただければスムーズに準備が可能です。
",
+ "a1": "
以下の方法でご予約いただけます:
\n
\n
\n メールでお問い合わせ \n
\n
\n
最低2週間前にご連絡いただければスムーズに準備が可能です。
",
"a2": "
以下が返金規定です:
\n
\n
\n予約日の7日前 \n100% 返金 \n
\n
\n予約日の3日前 \n70% 返金 \n
\n
\n予約日の1日前 \n50% 返金 \n
\n
\n当日キャンセル \n返金不可 \n
\n
",
"a3": "
最小レンタルは2時間 からとなります。
\n
延長は1時間単位で可能です。
",
"a4": "
最低2週間前にご連絡いただければスムーズに準備が可能です。
",
diff --git a/i18n/ko.json b/i18n/ko.json
index b61c52e..d403b07 100644
--- a/i18n/ko.json
+++ b/i18n/ko.json
@@ -751,7 +751,7 @@
"q13": "실시간 스트리밍이 가능한가요?",
"q14": "주차는 가능한가요?",
"q15": "견학이나 시설 투어는 가능한가요?",
- "a1": "
아래 방법으로 예약하실 수 있습니다:
\n
\n
\n 이메일 문의하기 \n
\n
\n
최소 2주 전에 연락주시면 원활하게 준비가 가능합니다.
",
+ "a1": "
아래 방법으로 예약하실 수 있습니다:
\n
\n
\n 이메일 문의하기 \n
\n
\n
최소 2주 전에 연락주시면 원활하게 준비가 가능합니다.
",
"a2": "
아래는 환불 규정입니다:
\n
\n
\n예약일로부터 7일 전 \n100% 환불 \n
\n
\n예약일로부터 3일 전 \n70% 환불 \n
\n
\n예약일로부터 1일 전 \n50% 환불 \n
\n
\n당일 취소 \n환불 불가 \n
\n
",
"a3": "
최소 대관은 2시간 부터 가능합니다.
\n
연장은 1시간 단위로 가능합니다.
",
"a4": "
최소 2주 전에 연락주시면 원활하게 준비가 가능합니다.
",
diff --git a/i18n/zh.json b/i18n/zh.json
index 739afc1..4f1697b 100644
--- a/i18n/zh.json
+++ b/i18n/zh.json
@@ -751,7 +751,7 @@
"q13": "可以进行实时直播吗?",
"q14": "可以停车吗?",
"q15": "可以参观或设施导览吗?",
- "a1": "
您可以通过以下方式进行预约:
\n
\n
\n 邮件咨询 \n
\n
\n
请至少提前2周联系我们,以便顺利准备。
",
+ "a1": "
您可以通过以下方式进行预约:
\n
\n
\n 邮件咨询 \n
\n
\n
请至少提前2周联系我们,以便顺利准备。
",
"a2": "
以下是退款规定:
\n
\n
\n预约日7天前 \n100%退款 \n
\n
\n预约日3天前 \n70%退款 \n
\n
\n预约日1天前 \n50%退款 \n
\n
\n当天取消 \n不可退款 \n
\n
",
"a3": "
最少租赁时间为2小时 起。
\n
可按1小时为单位延长。
",
"a4": "
请至少提前2周联系我们,以便顺利准备。
",
diff --git a/index.html b/index.html
index 450f927..0c430dd 100644
--- a/index.html
+++ b/index.html
@@ -982,7 +982,6 @@
diff --git a/ja/contact.html b/ja/contact.html
index 3d1d4df..d157ec5 100644
--- a/ja/contact.html
+++ b/ja/contact.html
@@ -438,7 +438,6 @@
ご予約・お問い合わせ
簡単なオンライン予約またはよくある質問をご確認ください
diff --git a/ja/index.html b/ja/index.html
index fb43cad..236df69 100644
--- a/ja/index.html
+++ b/ja/index.html
@@ -974,7 +974,6 @@
diff --git a/ja/qna.html b/ja/qna.html
index d3e96ef..eb2e3a7 100644
--- a/ja/qna.html
+++ b/ja/qna.html
@@ -205,7 +205,6 @@
メールでお問い合わせ
diff --git a/ja/schedule.html b/ja/schedule.html
index 3cc2440..17b3bf8 100644
--- a/ja/schedule.html
+++ b/ja/schedule.html
@@ -133,7 +133,6 @@
ご予約はメールまたはお問い合わせページから承ります。 2週間前までのご予約をお勧めいたします。
diff --git a/ja/services.html b/ja/services.html
index 7b49b51..6034440 100644
--- a/ja/services.html
+++ b/ja/services.html
@@ -811,14 +811,10 @@ Mingle Studioまたは依頼者帰属を選択可能です
メールでお問い合わせ
-
- Naver予約へ
-
予約状況を確認
-
※ Naverプレイスでリアルタイムのスケジュール確認および予約が可能です