# -*- coding: utf-8 -*-
def K(t): return "".join(c if ord(c) < 128 else "&#x%X;" % ord(c) for c in t)
def J(t): return "".join(c if ord(c) < 128 else "\\u%04x" % ord(c) for c in t)

G, B = "이상빈", "김하린"
GE, BE = "LEE SANGBIN", "KIM HARIN"
DATE_T = "2027. 2. 28. Sun. 12:30 PM"
HALL = "루클라비더화이트"
ADDR = "서울 강남구 논현로 742 파티오나인 빌딩 2층"

CAST = [
    ("GROOM", "신랑", GE, G), ("BRIDE", "신부", BE, B),
    ("GROOM'S FATHER", "신랑 아버지", "LEE SOONWOO", "이순우"),
    ("GROOM'S MOTHER", "신랑 어머니", "BAEK MYUNGSOOK", "백명숙"),
    ("BRIDE'S FATHER", "신부 아버지", "KIM JONGMU", "김종무"),
    ("BRIDE'S MOTHER", "신부 어머니", "KIM KYUNGYEOM", "김경염"),
]

# ---------- 직접 그린 SVG ----------
PROJ = """<svg viewBox="0 0 120 70" class="proj"><g fill="none" stroke="#D8D4CB" stroke-width="2.2" stroke-linejoin="round">
<rect x="14" y="34" width="56" height="20" rx="3"/><circle cx="30" cy="20" r="11"/><circle cx="56" cy="22" r="9"/>
<path d="M30 31v3M56 31v3M20 54v6M64 54v6"/></g>
<circle cx="30" cy="20" r="3" fill="#D8D4CB"/><circle cx="56" cy="22" r="2.5" fill="#D8D4CB"/>
<path d="M70 38 L108 26 L108 62 L70 50 Z" fill="url(#bm)"/>
<defs><linearGradient id="bm" x1="0" x2="1"><stop offset="0" stop-color="#EFEAD8" stop-opacity=".85"/><stop offset="1" stop-color="#EFEAD8" stop-opacity="0"/></linearGradient></defs></svg>"""

LAUREL = """<svg viewBox="0 0 64 64" class="laurel"><circle cx="32" cy="32" r="30" fill="none" stroke="currentColor" stroke-width="1.2"/>
<g stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round">
<path d="M32 46 C 24 40, 20 30, 22 20"/><path d="M32 46 C 40 40, 44 30, 42 20"/>
<path d="M24 36c-4-1-6-4-6-7M26 29c-4-1-6-4-6-7M28 23c-3-2-4-5-4-8"/>
<path d="M40 36c4-1 6-4 6-7M38 29c4-1 6-4 6-7M36 23c3-2 4-5 4-8"/></g></svg>"""

RINGS = """<svg viewBox="0 0 300 180" class="rings"><g fill="none" stroke="#17201D" stroke-width="5" opacity=".82">
<circle cx="112" cy="96" r="52"/><circle cx="190" cy="96" r="52"/></g>
<path d="M14 12 L60 2 L62 10 L16 20 Z" fill="none"/>
<path d="M150 18 l9-10 9 10 -6 7 h-6 Z" fill="#17201D" opacity=".82"/>
<path d="M30 88 C 90 58, 210 58, 272 44 L 276 68 C 214 82, 92 82, 34 112 Z" fill="#DCE8E3" stroke="#17201D" stroke-width="2.4" opacity=".95"/>
<text x="56" y="100" font-family="Montserrat,sans-serif" font-size="15" font-weight="600" letter-spacing="3.4" fill="#17201D" transform="rotate(-9 56 100)">WHEN</text>
<text x="132" y="86" font-family="Montserrat,sans-serif" font-size="15" font-weight="600" letter-spacing="3.4" fill="#17201D" transform="rotate(-7 132 86)">LOVE UNFOLDS</text></svg>"""

# ---------- CSS ----------
CSS = """
*{box-sizing:border-box}
:root{--ink:#191919;--body:#333;--mute:#6B6560;--line:#CFCAC4;
 --paper:#E9E6E2;--dark:#070C0D;--butter:#E6E09B;--mint:#BCD1CB;--mint2:#A8BFB8;
 --film:#0B0B0B;--uf:calc(min(100vw,var(--cardmax,100vw))/375)}
html{scroll-behavior:smooth;scrollbar-width:none;-ms-overflow-style:none}
html::-webkit-scrollbar,body::-webkit-scrollbar{width:0;height:0;display:none}
html,body{overflow-x:hidden;max-width:100%;touch-action:manipulation}
body{margin:0;background:#15181A;color:var(--body);
 font-family:Pretendard,"Apple SD Gothic Neo","Noto Sans KR",system-ui,sans-serif;-webkit-font-smoothing:antialiased}
@media (min-width:481px){:root{--cardmax:430px}}
.stage{width:100%;max-width:var(--cardmax,100%);margin:0 auto;position:relative;background:var(--paper);
 overflow:hidden;container-type:inline-size;--u:calc(100cqw/375);box-shadow:0 0 0 1px var(--paper)}
.en{font-family:Montserrat,system-ui,sans-serif}
.sec{padding:calc(var(--u)*72) calc(var(--u)*24)}
.tit-g{font-family:Montserrat,sans-serif;font-size:calc(var(--u)*20);font-weight:600;color:var(--ink);
 text-align:center;margin:0 0 calc(var(--u)*34);letter-spacing:.01em}
.emb{display:block;width:calc(var(--u)*34);height:calc(var(--u)*34);margin:0 auto calc(var(--u)*12);color:var(--ink)}
.rv{opacity:0;transform:translateY(calc(var(--u)*26))}
.js .rv{transition:opacity 1.1s cubic-bezier(.2,.7,.3,1),transform 1.25s cubic-bezier(.2,.7,.3,1)}
.js .rv.in{opacity:1;transform:none}
.no-js .rv{opacity:1;transform:none}

/* ---- music ---- */
.music{position:fixed;top:calc(var(--uf)*18);right:calc(var(--uf)*16);z-index:90;display:flex;align-items:center;
 gap:calc(var(--uf)*7);height:calc(var(--uf)*34);padding:0 calc(var(--uf)*14);border:1px solid rgba(255,255,255,.55);
 border-radius:999px;background:rgba(20,22,22,.28);backdrop-filter:blur(6px);color:#fff;cursor:pointer;
 font-family:Montserrat,sans-serif;font-size:calc(var(--uf)*13);font-weight:600}
.music svg{width:calc(var(--uf)*16);height:calc(var(--uf)*16);fill:none;stroke:#fff;stroke-width:1.7}
.music .x{display:block}.music.on .x{display:none}

/* ---- cover ---- */
.cover{position:relative;min-height:100svh;display:grid;grid-template-rows:auto 1fr auto auto;
 background:linear-gradient(170deg,#4A403A,#262120 42%,#161312);overflow:hidden}
.cover__img{position:absolute;inset:0;background:
 radial-gradient(60% 40% at 30% 12%,rgba(255,246,228,.30),transparent 70%),
 linear-gradient(200deg,#6A5A4C 0%,#3A322C 38%,#221D1A 72%,#14110F 100%)}
.cover__img::after{content:"";position:absolute;inset:0;background:
 repeating-linear-gradient(97deg,rgba(255,255,255,.028) 0 2px,transparent 2px 7px);mix-blend-mode:screen}
.cover__hd{position:relative;text-align:center;padding:calc(var(--u)*78) calc(var(--u)*22) 0;color:#fff}
.cover__hd b,.cover__hd i{display:block;font-weight:400;line-height:.98}
.cover__hd b{font-family:"Playfair Display",Georgia,serif;font-size:calc(var(--u)*44);letter-spacing:.01em}
.cover__hd i{font-family:"Playfair Display",Georgia,serif;font-style:italic;font-size:calc(var(--u)*62);
 margin:calc(var(--u)*-4) 0 calc(var(--u)*-2)}
.cover__nm{position:relative;display:flex;justify-content:space-between;padding:0 calc(var(--u)*34) calc(var(--u)*26);color:#fff}
.cover__nm div{text-align:center}
.cover__nm em{display:block;font-family:"Playfair Display",Georgia,serif;font-style:italic;
 font-size:calc(var(--u)*13);opacity:.9;margin-bottom:calc(var(--u)*3)}
.cover__nm span{font-family:Montserrat,sans-serif;font-size:calc(var(--u)*15);font-weight:300;letter-spacing:.08em}
.cover__ft{position:relative;background:linear-gradient(180deg,transparent,rgba(10,9,8,.82) 38%);
 padding:calc(var(--u)*26) calc(var(--u)*20) calc(var(--u)*40);text-align:center;color:#fff}
.cover__ft p{margin:0 0 calc(var(--u)*10);font-size:calc(var(--u)*14);font-weight:300;
 display:flex;align-items:center;justify-content:center;gap:calc(var(--u)*8)}
.cover__ft p:last-child{margin:0}
.cover__ft svg{width:calc(var(--u)*14);height:calc(var(--u)*14);fill:none;stroke:#fff;stroke-width:1.6;flex:none}
.cover__ft .d{font-family:Montserrat,sans-serif;letter-spacing:.01em}

/* ---- invite (dark) ---- */
.inv{background:var(--dark);color:#fff;text-align:center;padding:calc(var(--u)*86) calc(var(--u)*24) 0;position:relative}
.proj{display:block;width:calc(var(--u)*120);height:calc(var(--u)*70);margin:0 auto calc(var(--u)*44)}
.inv__q{font-size:calc(var(--u)*16);font-weight:300;line-height:2.1;margin:0 0 calc(var(--u)*26);color:#F2F0EC}
.inv__from{font-size:calc(var(--u)*15);font-weight:300;color:#CFCBC4;margin:0 0 calc(var(--u)*64)}
.inv__g{font-size:calc(var(--u)*16);font-weight:300;line-height:2.1;color:var(--butter);margin:0 0 calc(var(--u)*72)}
/* 35mm film strip */
.strip{position:relative;width:118%;margin-left:-9%;transform:rotate(-4.2deg);background:var(--film);
 padding:calc(var(--u)*17) 0;box-shadow:0 calc(var(--u)*10) calc(var(--u)*30) rgba(0,0,0,.5)}
.strip::before,.strip::after{content:"";position:absolute;left:0;right:0;height:calc(var(--u)*17);
 background:repeating-linear-gradient(90deg,transparent 0 calc(var(--u)*9),#E7E4DE calc(var(--u)*9) calc(var(--u)*18),transparent calc(var(--u)*18) calc(var(--u)*27));
 -webkit-mask:repeating-linear-gradient(90deg,#000 0 calc(var(--u)*9),#000 calc(var(--u)*9) calc(var(--u)*18),#000 calc(var(--u)*18) calc(var(--u)*27));opacity:.92}
.strip::before{top:0}.strip::after{bottom:0}
.strip__row{display:flex;gap:calc(var(--u)*10);padding:0 calc(var(--u)*10)}
.strip__f{flex:0 0 calc(var(--u)*150);height:calc(var(--u)*112);filter:grayscale(1) contrast(1.08)}
.strip__lab{position:absolute;top:calc(var(--u)*3);left:calc(var(--u)*12);font-family:Montserrat,sans-serif;
 font-size:calc(var(--u)*8);color:#9C9890;letter-spacing:.08em}
.inv__tear{height:calc(var(--u)*46);margin-top:calc(var(--u)*56);background:var(--paper);
 -webkit-mask:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 375 46' preserveAspectRatio='none'%3E%3Cpath d='M0 14 Q 10 6 22 12 T 48 10 T 74 15 T 99 8 T 126 14 T 152 9 T 178 15 T 204 10 T 231 16 T 257 9 T 283 14 T 309 10 T 336 15 T 362 9 T 375 13 L375 46 L0 46 Z' fill='%23000'/%3E%3C/svg%3E") bottom/100% 100% no-repeat;mask:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 375 46' preserveAspectRatio='none'%3E%3Cpath d='M0 14 Q 10 6 22 12 T 48 10 T 74 15 T 99 8 T 126 14 T 152 9 T 178 15 T 204 10 T 231 16 T 257 9 T 283 14 T 309 10 T 336 15 T 362 9 T 375 13 L375 46 L0 46 Z' fill='%23000'/%3E%3C/svg%3E") bottom/100% 100% no-repeat}

/* ---- showbox ---- */
.show{padding:calc(var(--u)*10) calc(var(--u)*24) calc(var(--u)*70)}
.show__box{background:#1C1E1E;border-radius:calc(var(--u)*14);padding:calc(var(--u)*16);
 box-shadow:0 calc(var(--u)*16) calc(var(--u)*38) rgba(0,0,0,.26)}
.show__bar{display:flex;align-items:center;justify-content:space-between;padding:calc(var(--u)*6) calc(var(--u)*10) calc(var(--u)*12)}
.show__bar span{font-family:Montserrat,sans-serif;font-size:calc(var(--u)*11);font-weight:400;color:#fff;letter-spacing:.14em}
.show__mark{width:calc(var(--u)*30);height:calc(var(--u)*18);border:1px solid rgba(255,255,255,.6);border-radius:999px;
 display:grid;place-items:center;font-family:"Playfair Display",serif;font-style:italic;font-size:calc(var(--u)*9);color:#fff}
.show__ph{position:relative;aspect-ratio:3/4;filter:grayscale(1) contrast(1.06);border-radius:calc(var(--u)*3)}
.show__ov{position:absolute;inset:0;display:grid;place-content:center;text-align:center;line-height:.82}
.show__ov b{display:block;font-family:Montserrat,sans-serif;font-weight:800;font-size:calc(var(--u)*44);
 color:var(--butter);letter-spacing:-.01em}
.show__ov i{display:block;font-family:"Playfair Display",serif;font-style:italic;font-weight:400;
 font-size:calc(var(--u)*34);color:#fff;margin:calc(var(--u)*-10) 0}

/* ---- countdown ---- */
.cd{display:flex;align-items:center;justify-content:center;gap:calc(var(--u)*7);margin:0 0 calc(var(--u)*22)}
.cd__b{width:calc(var(--u)*72);height:calc(var(--u)*76);background:#17191A;border-radius:calc(var(--u)*10);
 display:grid;place-content:center;text-align:center}
.cd__b b{display:block;font-family:Montserrat,sans-serif;font-size:calc(var(--u)*23);font-weight:500;
 color:#fff;letter-spacing:.5px;font-variant-numeric:tabular-nums}
.cd__b span{display:block;font-family:Montserrat,sans-serif;font-size:calc(var(--u)*12);color:var(--butter);margin-top:calc(var(--u)*4)}
.cd__s{font-family:Montserrat,sans-serif;font-size:calc(var(--u)*15);color:#8E8A84}
.cd__t{text-align:center;font-size:calc(var(--u)*15);font-weight:300;color:var(--body);margin:0}
.cd__t b{font-weight:600}.cd__t i{font-family:"Playfair Display",serif;font-style:italic;color:var(--mute);padding:0 calc(var(--u)*5)}

/* ---- cast ---- */
.cast__r{text-align:center;margin-bottom:calc(var(--u)*34)}
.cast__r:last-child{margin:0}
.cast__p{font-size:calc(var(--u)*14);font-weight:300;color:var(--mute);margin:0 0 calc(var(--u)*6)}
.cast__n{font-size:calc(var(--u)*26);font-weight:400;color:var(--ink);margin:0 0 calc(var(--u)*2)}
.cast__e{font-family:Montserrat,sans-serif;font-size:calc(var(--u)*12);font-weight:300;color:var(--mute);letter-spacing:.14em;margin:0}

/* ---- ticket ---- */
.tk__hd{text-align:center;color:var(--mute);line-height:.86;margin-bottom:calc(var(--u)*40)}
.tk__hd b{display:block;font-family:"Playfair Display",Georgia,serif;font-weight:400;font-size:calc(var(--u)*30);letter-spacing:.02em}
.tk__hd i{display:block;font-family:"Playfair Display",Georgia,serif;font-style:italic;font-weight:500;font-size:calc(var(--u)*56)}
.tk__hd .r{display:flex;align-items:baseline;justify-content:center;gap:calc(var(--u)*10)}
.tk__hd .r b,.tk__hd .r i{display:inline-block}
.tk{perspective:1200px;cursor:pointer}
.tk__in{position:relative;transform-style:preserve-3d;transition:transform .85s cubic-bezier(.3,.85,.35,1)}
.tk.flip .tk__in{transform:rotateY(180deg)}
.tk__f,.tk__bk{backface-visibility:hidden;background:var(--mint);padding:calc(var(--u)*22) calc(var(--u)*22) calc(var(--u)*34);
 background-image:repeating-linear-gradient(0deg,rgba(255,255,255,.3) 0 1px,transparent 1px 3px);
 -webkit-mask:radial-gradient(circle calc(var(--u)*9) at 50% 100%,transparent 98%,#000 100%) 0 0/calc(var(--u)*33) 100% repeat-x,linear-gradient(#000,#000);
 mask:radial-gradient(circle calc(var(--u)*9) at 50% 100%,transparent 98%,#000 100%) 0 0/calc(var(--u)*33) 100% repeat-x,linear-gradient(#000,#000);
 -webkit-mask-composite:source-in;mask-composite:intersect}
.tk__bk{position:absolute;inset:0;transform:rotateY(180deg)}
.tk__perf{border-top:1px dashed rgba(23,32,29,.5);margin:0 0 calc(var(--u)*18)}
.rings{display:block;width:100%;height:auto;margin:calc(var(--u)*6) 0 calc(var(--u)*22)}
.tk__hr{border-top:1px dashed rgba(23,32,29,.45);margin:calc(var(--u)*8) 0 calc(var(--u)*16)}
.tk__hr2{border-top:1px solid rgba(23,32,29,.55);margin:calc(var(--u)*16) 0 calc(var(--u)*14)}
.tk__row{display:flex;gap:calc(var(--u)*14);font-size:calc(var(--u)*15);color:#17201D;margin-bottom:calc(var(--u)*6)}
.tk__row dt{font-family:Montserrat,sans-serif;font-size:calc(var(--u)*12);font-weight:500;flex:0 0 calc(var(--u)*42);padding-top:calc(var(--u)*3)}
.tk__row dd{margin:0;line-height:1.55}
.tk__logo{text-align:center;font-family:Montserrat,sans-serif;font-weight:700;font-size:calc(var(--u)*17);
 letter-spacing:.24em;color:#17201D}
.tk__hint{text-align:center;font-size:calc(var(--u)*15);color:#A49C7E;margin:calc(var(--u)*26) 0 0}

/* ---- gallery ---- */
.gal__strip{position:relative;background:var(--film);padding:calc(var(--u)*19) 0}
.gal__edge{position:absolute;left:0;right:0;height:calc(var(--u)*19);display:flex;align-items:center;
 justify-content:space-around;font-family:Montserrat,sans-serif;font-size:calc(var(--u)*9);color:#8E8A82;letter-spacing:.06em}
.gal__edge.t{top:0}.gal__edge.b{bottom:0;transform:scaleY(-1)}
.gal__tr{display:flex;gap:calc(var(--u)*10);overflow-x:auto;scroll-snap-type:x mandatory;
 padding:0 calc(var(--u)*22);scrollbar-width:none}
.gal__tr::-webkit-scrollbar{display:none}
.gal__it{flex:0 0 calc(var(--u)*210);aspect-ratio:3/4;scroll-snap-align:center;border:0;padding:0;cursor:zoom-in}
.gal__hint{text-align:center;font-size:calc(var(--u)*15);color:#A49C7E;margin:calc(var(--u)*24) 0 0}

/* ---- location / accordion ---- */
.map{height:calc(var(--u)*240);background:#D5D1CB;margin-bottom:calc(var(--u)*18)}
.loc__n{text-align:center;font-size:calc(var(--u)*18);font-weight:500;color:var(--ink);margin:0 0 calc(var(--u)*4)}
.loc__a{text-align:center;font-size:calc(var(--u)*14);font-weight:300;color:var(--mute);margin:0 0 calc(var(--u)*26)}
.acc{border-top:1px solid var(--line)}
.acc__h{width:100%;display:flex;align-items:center;justify-content:space-between;background:none;border:0;
 padding:calc(var(--u)*18) 0;font-family:inherit;font-size:calc(var(--u)*16);font-weight:500;color:#111;cursor:pointer}
.acc__h svg{width:calc(var(--u)*16);height:calc(var(--u)*16);fill:none;stroke:#555;stroke-width:1.8;transition:transform .3s}
.acc.open .acc__h svg{transform:rotate(180deg)}
.acc__b{display:none;padding:0 0 calc(var(--u)*22)}
.acc.open .acc__b{display:block}
.acc__st{font-size:calc(var(--u)*14);font-weight:500;color:#333;margin:0 0 calc(var(--u)*8)}
.acc__p{font-size:calc(var(--u)*14);font-weight:300;line-height:1.75;color:#5A5651;margin:0 0 calc(var(--u)*16)}
.acc__div{border-top:1px dashed var(--line);margin:0 0 calc(var(--u)*16)}

/* ---- tabs (info / account) ---- */
.tabs{display:flex;border-bottom:1px solid var(--line);margin-bottom:calc(var(--u)*24)}
.tabs button{flex:1;background:none;border:0;padding:calc(var(--u)*14) 0;font-family:inherit;
 font-size:calc(var(--u)*15);font-weight:500;color:#A9A49D;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px}
.tabs button[aria-selected=true]{color:#2B2723;border-bottom-color:#8D8577}
.pane{display:none}.pane.on{display:block}
.info__ph{aspect-ratio:1/1;margin-bottom:calc(var(--u)*22)}
.info__t{text-align:center;font-size:calc(var(--u)*15);font-weight:300;line-height:1.9;color:#5A5651;margin:0}

/* ---- account ---- */
.acct__r{margin-bottom:calc(var(--u)*24)}
.acct__h{display:flex;align-items:center;gap:calc(var(--u)*8);margin-bottom:calc(var(--u)*10)}
.acct__h em{font-style:normal;font-size:calc(var(--u)*15);font-weight:300;color:#6E6960}
.acct__h b{font-size:calc(var(--u)*17);font-weight:600;color:#1C1917}
.acct__box{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--u)*10);
 background:#D9D2CA;border:0;width:100%;padding:calc(var(--u)*16) calc(var(--u)*18);
 font-family:inherit;font-size:calc(var(--u)*15);color:#241F1B;text-align:left;cursor:pointer;border-radius:calc(var(--u)*3)}
.acct__box svg{width:calc(var(--u)*17);height:calc(var(--u)*17);fill:none;stroke:#3A342E;stroke-width:1.6;flex:none}

/* ---- guestbook ---- */
.gb__l{list-style:none;margin:0;padding:0}
.gb__i{background:#DFDAD4;padding:calc(var(--u)*16) calc(var(--u)*18);margin-bottom:calc(var(--u)*10);border-radius:calc(var(--u)*3)}
.gb__i p{margin:0 0 calc(var(--u)*8);font-size:calc(var(--u)*15);font-weight:300;line-height:1.7;color:#3A352F}
.gb__m{display:flex;justify-content:space-between;font-size:calc(var(--u)*12);color:#89837B}

/* ---- credits ---- */
.cr{background:var(--dark);color:#fff;padding:calc(var(--u)*78) calc(var(--u)*24) calc(var(--u)*60)}
.cr__ph{aspect-ratio:3/4;margin:0 auto calc(var(--u)*58);max-width:calc(var(--u)*230);filter:grayscale(1)}
.cr__t{font-family:Montserrat,sans-serif;font-size:calc(var(--u)*14);font-weight:600;color:#fff;
 text-align:center;letter-spacing:.14em;margin:0 0 calc(var(--u)*26)}
.cr__g{margin:0 0 calc(var(--u)*52)}
.cr__r{display:grid;grid-template-columns:1fr 1fr;gap:calc(var(--u)*14);align-items:baseline;
 padding:calc(var(--u)*9) 0;font-size:calc(var(--u)*12);font-weight:300}
.cr__r .l{text-align:right;color:#9A9A9A;line-height:1.6}
.cr__r .l i{display:block;font-style:normal;font-family:Montserrat,sans-serif;letter-spacing:.06em}
.cr__r .r{text-align:left;line-height:1.6}
.cr__r .r i{display:block;font-style:normal;font-family:Montserrat,sans-serif;color:#fff;letter-spacing:.06em}
.cr__r .r span{color:#D4D4D4}
.cr__end{text-align:center;font-family:"Playfair Display",serif;font-style:italic;
 font-size:calc(var(--u)*22);color:#C9C4BA;margin:calc(var(--u)*40) 0 0}
.toast{position:fixed;left:50%;bottom:calc(var(--uf)*42);transform:translateX(-50%) translateY(calc(var(--uf)*10));
 background:rgba(18,20,20,.92);color:#F2EFE9;font-size:calc(var(--uf)*13);padding:calc(var(--uf)*11) calc(var(--uf)*20);
 border-radius:999px;opacity:0;pointer-events:none;transition:opacity .25s,transform .25s;z-index:120;max-width:calc(100vw - 48px)}
.toast.on{opacity:1;transform:translateX(-50%)}
.top{position:fixed;right:calc(var(--uf)*16);bottom:calc(var(--uf)*20);width:calc(var(--uf)*48);height:calc(var(--uf)*48);
 border:0;border-radius:50%;background:#F2F0EC;box-shadow:0 calc(var(--uf)*5) calc(var(--uf)*14) rgba(0,0,0,.2);
 display:grid;place-items:center;cursor:pointer;z-index:85;opacity:0;pointer-events:none;transition:opacity .3s}
.top.on{opacity:1;pointer-events:auto}
.top svg{width:calc(var(--uf)*20);height:calc(var(--uf)*20);fill:none;stroke:#1C1917;stroke-width:2}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
"""

# 사진 플레이스홀더(그라디언트) — 에디터에서 교체 가능
PH = ["linear-gradient(145deg,#8E8176,#4B433C)","linear-gradient(145deg,#A1958A,#5C534A)",
      "linear-gradient(145deg,#7C7066,#3E3832)","linear-gradient(145deg,#B0A396,#6B6258)",
      "linear-gradient(145deg,#6F655C,#332E29)","linear-gradient(145deg,#9B8F82,#564E46)"]
def ph(i, cls=""):
    return '<div class="%s" style="background:%s"></div>' % (cls, PH[i % len(PH)])

ICO_CAL = '<svg viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M8 3v4M16 3v4M3 10h18"/></svg>'
ICO_PIN = '<svg viewBox="0 0 24 24"><path d="M12 21s7-6.2 7-11a7 7 0 1 0-14 0c0 4.8 7 11 7 11z"/><circle cx="12" cy="10" r="2.6"/></svg>'
ICO_CP  = '<svg viewBox="0 0 24 24"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>'
ICO_CH  = '<svg viewBox="0 0 24 24"><path d="M6 15l6-6 6 6"/></svg>'
ICO_MU  = '<svg viewBox="0 0 24 24"><path d="M4 9v6h4l5 4V5L8 9H4z"/><path class="x" d="M17 9l4 6M21 9l-4 6"/></svg>'

cast_rows = "".join(
    '<div class="cr__r"><div class="l"><i>%s</i>%s</div><div class="r"><i>%s</i><span>%s</span></div></div>'
    % (en, K(ko), nen, K(nko)) for en, ko, nen, nko in CAST)

HTML = """<!doctype html><html lang="ko" class="no-js"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>%(TT)s</title>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;800&family=Playfair+Display:ital,wght@0,400;0,500;1,400;1,500&display=swap">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.css">
<style>%(CSS)s</style></head><body>
<div class="stage">

<button class="music" id="mu" type="button" aria-pressed="false">Music %(ICO_MU)s</button>

<section class="cover">
  <div class="cover__img"></div>
  <div class="cover__hd"><b>THE</b><i>Grandest</i><b>SHOW</b><b>OF OUR</b><i>{ love }</i></div>
  <div></div>
  <div class="cover__nm">
    <div><em>Groom</em><span>%(GE)s</span></div>
    <div><em>Bride</em><span>%(BE)s</span></div>
  </div>
  <div class="cover__ft">
    <p>%(ICO_CAL)s<span class="d">%(DATE)s</span></p>
    <p>%(ICO_PIN)s<span>%(HALL)s</span></p>
  </div>
</section>

<section class="inv">
  %(PROJ)s
  <p class="inv__q rv">%(Q1)s</p>
  <p class="inv__from rv">%(Q2)s</p>
  <p class="inv__g rv">%(GREET)s</p>
  <div class="strip"><span class="strip__lab">BR Film</span>
    <div class="strip__row">%(STRIP)s</div></div>
  <div class="inv__tear"></div>
</section>

<section class="show">
  <div class="show__box rv">
    <div class="show__bar"><span>%(GEF)s</span><span class="show__mark">BR</span><span>%(BEF)s</span></div>
    <div class="show__ph" style="background:%(PH0)s">
      <div class="show__ov"><b>WEDDING</b><i>Our</i><b>DAY</b></div></div>
  </div>
</section>

<section class="sec">
  <h2 class="tit-g rv">OUR DAY</h2>
  <div class="cd rv" id="cd">
    <div class="cd__b"><b id="dD">--</b><span>Days</span></div><span class="cd__s">:</span>
    <div class="cd__b"><b id="dH">--</b><span>Hours</span></div><span class="cd__s">:</span>
    <div class="cd__b"><b id="dM">--</b><span>Min</span></div><span class="cd__s">:</span>
    <div class="cd__b"><b id="dS">--</b><span>Sec</span></div>
  </div>
  <p class="cd__t rv">%(G)s<i>&amp;</i>%(B)s%(SUF)s <b id="dLeft">--</b>%(LEFT)s</p>
</section>

<section class="sec">
  <h2 class="tit-g rv">CAST</h2>
  <div class="cast__r rv"><p class="cast__p">%(GP)s</p><p class="cast__n">%(G)s</p><p class="cast__e">%(GE)s</p></div>
  <div class="cast__r rv"><p class="cast__p">%(BP)s</p><p class="cast__n">%(B)s</p><p class="cast__e">%(BE)s</p></div>
</section>

<section class="sec">
  <div class="tk__hd rv"><b>AN</b><i>Invitation</i><span class="r"><b>FOR</b><i>you</i></span></div>
  <div class="tk rv" id="tk" role="button" tabindex="0" aria-label="%(TKA)s"><div class="tk__in">
    <div class="tk__f">
      <div class="tk__perf"></div>
      %(RINGS)s
      <div class="tk__hr"></div>
      <dl class="tk__row"><dt>Date</dt><dd>%(DATE)s</dd></dl>
      <dl class="tk__row"><dt>Place</dt><dd>%(HALL)s<br>%(ADDR)s</dd></dl>
      <div class="tk__hr2"></div>
      <div class="tk__logo">BIN&amp;RIN</div>
    </div>
    <div class="tk__bk">
      <div class="tk__perf"></div>
      <div class="tk__logo" style="margin:calc(var(--u)*34) 0 calc(var(--u)*26)">ADMIT TWO</div>
      <dl class="tk__row"><dt>Cast</dt><dd>%(G)s &amp; %(B)s</dd></dl>
      <dl class="tk__row"><dt>Seat</dt><dd>%(SEAT)s</dd></dl>
      <dl class="tk__row"><dt>Note</dt><dd>%(NOTE)s</dd></dl>
      <div class="tk__hr2"></div>
      <div class="tk__logo">BIN&amp;RIN</div>
    </div>
  </div></div>
  <p class="tk__hint">%(TKH)s</p>
</section>

<section class="sec">
  <div class="rv">%(LAUREL_W)s<h2 class="tit-g">GALLERY</h2></div>
  <div class="gal__strip rv">
    <div class="gal__edge t"><span>BR Film</span><span>13</span><span>BR Film</span><span>13A</span></div>
    <div class="gal__tr" id="galTr">%(GAL)s</div>
    <div class="gal__edge b"><span>13A</span><span>00A-A00</span><span>13</span><span>00A-A00</span></div>
  </div>
  <p class="gal__hint">%(GALH)s</p>
</section>

<section class="sec">
  <h2 class="tit-g rv">LOCATION</h2>
  <div class="map rv" id="map"></div>
  <p class="loc__n rv">%(HALL)s</p>
  <p class="loc__a rv">%(ADDR)s</p>
  <div class="acc open rv"><button class="acc__h" type="button">%(A1)s%(ICO_CH)s</button>
    <div class="acc__b">
      <p class="acc__st">%(A1A)s</p><p class="acc__p">%(A1B)s</p>
      <div class="acc__div"></div>
      <p class="acc__st">%(A1C)s</p><p class="acc__p">%(A1D)s</p>
    </div></div>
  <div class="acc open rv"><button class="acc__h" type="button">%(A2)s%(ICO_CH)s</button>
    <div class="acc__b"><p class="acc__st">%(A2A)s</p><p class="acc__p">%(A2B)s</p></div></div>
</section>

<section class="sec">
  <h2 class="tit-g rv">INFORMATION</h2>
  <div class="rv">
  <div class="tabs" id="infoTabs" role="tablist">
    <button role="tab" aria-selected="true">%(I1)s</button>
    <button role="tab" aria-selected="false">%(I2)s</button>
    <button role="tab" aria-selected="false">%(I3)s</button>
  </div>
  <div class="pane on"><div class="info__ph" style="background:%(PH1)s"></div><p class="info__t">%(I1T)s</p></div>
  <div class="pane"><div class="info__ph" style="background:%(PH2)s"></div><p class="info__t">%(I2T)s</p></div>
  <div class="pane"><div class="info__ph" style="background:%(PH3)s"></div><p class="info__t">%(I3T)s</p></div>
  </div>
</section>

<section class="sec">
  <h2 class="tit-g rv">ACCOUNT</h2>
  <p class="info__t rv" style="margin-bottom:calc(var(--u)*26)">%(ACCT)s</p>
  <div class="rv">
  <div class="tabs" id="acctTabs" role="tablist">
    <button role="tab" aria-selected="true">%(AG)s</button>
    <button role="tab" aria-selected="false">%(AB)s</button>
  </div>
  <div class="pane on">%(ACCG)s</div>
  <div class="pane">%(ACCB)s</div>
  </div>
</section>

<section class="sec">
  <h2 class="tit-g rv">GUEST BOOK</h2>
  <ul class="gb__l rv">%(GB)s</ul>
</section>

<footer class="cr">
  <div class="cr__ph rv" style="background:%(PH4)s"></div>
  <h2 class="cr__t rv">CAST</h2>
  <div class="cr__g rv">%(CASTROWS)s</div>
  <p class="cr__end rv">The End</p>
</footer>

</div>
<div class="toast" id="toast" role="status"></div>
<button class="top" id="top" type="button" aria-label="%(TOPA)s">%(ICO_CH)s</button>
<script>%(JS)s</script>
</body></html>
"""

STRIP = "".join('<div class="strip__f" style="background:%s"></div>' % PH[i] for i in range(5))
GAL = "".join('<button class="gal__it" type="button" style="background:%s"></button>' % PH[i % 6] for i in range(6))

def acct_rows(items):
    out = ""
    for role, name, bank, num in items:
        out += ('<div class="acct__r"><div class="acct__h"><em>%s</em><b>%s</b></div>'
                '<button class="acct__box" type="button" data-copy="%s %s">'
                '<span>%s %s</span>%s</button></div>'
                % (K(role), K(name), K(bank), K(num), K(bank), K(num), ICO_CP))
    return out

ACC_G = [("신랑", G, "은행 미입력", "계좌번호 입력 전"), ("아버지", "이순우", "은행 미입력", "계좌번호 입력 전"),
         ("어머니", "백명숙", "은행 미입력", "계좌번호 입력 전")]
ACC_B = [("신부", B, "은행 미입력", "계좌번호 입력 전"), ("아버지", "김종무", "은행 미입력", "계좌번호 입력 전"),
         ("어머니", "김경염", "은행 미입력", "계좌번호 입력 전")]

GB_ITEMS = [("두 사람 앞날에 늘 좋은 일만 가득하길 바랄게요.", "민서", "2026. 10. 20."),
            ("드디어 결혼하는구나! 진심으로 축하해.", "현우", "2026. 10. 18."),
            ("선남선녀 신랑신부, 행복한 가정 이루세요.", "지안", "2026. 10. 15.")]
GB = "".join('<li class="gb__i"><p>%s</p><div class="gb__m"><span>%s</span><span>%s</span></div></li>'
             % (K(t), K(n), d) for t, n, d in GB_ITEMS)

JS = """
document.documentElement.className='js';
var T=document.getElementById('toast'),tt;
function toast(m){T.textContent=m;T.classList.add('on');clearTimeout(tt);tt=setTimeout(function(){T.classList.remove('on');},1900);}
/* reveal */
if('IntersectionObserver' in window){
  var io=new IntersectionObserver(function(en){en.forEach(function(x){if(x.isIntersecting){x.target.classList.add('in');io.unobserve(x.target);}});},{rootMargin:'0px 0px -12% 0px',threshold:.12});
  document.querySelectorAll('.rv').forEach(function(e){io.observe(e);});
  setTimeout(function(){document.querySelectorAll('.rv').forEach(function(e){e.classList.add('in');});},9000);
}else{document.querySelectorAll('.rv').forEach(function(e){e.classList.add('in');});}
/* countdown */
var WD=new Date(__WY__,__WM__,__WD2__,__WH__,__WI__,0);
function pad(n){return n<10?'0'+n:''+n;}
function cdTick(){
  var g=WD-new Date();if(g<0)g=0;
  var d=Math.floor(g/864e5),h=Math.floor(g/36e5)%24,m=Math.floor(g/6e4)%60,s=Math.floor(g/1e3)%60;
  document.getElementById('dD').textContent=d;
  document.getElementById('dH').textContent=pad(h);
  document.getElementById('dM').textContent=pad(m);
  document.getElementById('dS').textContent=pad(s);
  document.getElementById('dLeft').textContent=(d+1);
}
cdTick();setInterval(cdTick,1000);
/* ticket flip */
var tk=document.getElementById('tk');
function flip(){tk.classList.toggle('flip');}
tk.addEventListener('click',flip);
tk.addEventListener('keydown',function(e){if(e.key==='Enter'||e.key===' '){e.preventDefault();flip();}});
/* accordions */
document.querySelectorAll('.acc__h').forEach(function(b){
  b.addEventListener('click',function(){b.parentNode.classList.toggle('open');});});
/* tabs */
function wireTabs(id){
  var box=document.getElementById(id);if(!box)return;
  var btns=[].slice.call(box.querySelectorAll('button')),
      panes=[].slice.call(box.parentNode.querySelectorAll('.pane'));
  btns.forEach(function(b,i){b.addEventListener('click',function(){
    btns.forEach(function(x,j){x.setAttribute('aria-selected',j===i?'true':'false');});
    panes.forEach(function(p,j){p.classList.toggle('on',j===i);});});});
}
wireTabs('infoTabs');wireTabs('acctTabs');
/* copy */
document.querySelectorAll('[data-copy]').forEach(function(b){
  b.addEventListener('click',function(){
    var v=b.getAttribute('data-copy');
    if(v.indexOf(__NOACC__)>-1){toast(__MSGNO__);return;}
    if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(v).then(function(){toast(__MSGOK__);},function(){toast(__MSGNG__);});}
    else{var t=document.createElement('textarea');t.value=v;document.body.appendChild(t);t.select();try{document.execCommand('copy');toast(__MSGOK__);}catch(e){toast(__MSGNG__);}document.body.removeChild(t);}
  });});
/* top button */
var topBtn=document.getElementById('top'),sTick=false;
addEventListener('scroll',function(){if(!sTick){sTick=true;requestAnimationFrame(function(){
  topBtn.classList.toggle('on',(window.scrollY||document.documentElement.scrollTop)>700);sTick=false;});}},{passive:true});
topBtn.addEventListener('click',function(){window.scrollTo({top:0,behavior:'smooth'});});
/* music */
var mu=document.getElementById('mu'),au=new Audio('bgm.mp3');au.loop=true;au.volume=0;
var fT=null;function fade(to,ms,done){clearInterval(fT);var f=au.volume,t0=Date.now();
  fT=setInterval(function(){var k=Math.min(1,(Date.now()-t0)/ms);try{au.volume=f+(to-f)*k;}catch(e){}
  if(k>=1){clearInterval(fT);if(done)done();}},40);}
function on(){mu.classList.add('on');mu.setAttribute('aria-pressed','true');
  try{au.volume=0;}catch(e){}var p=au.play();if(p&&p.then)p.then(function(){fade(.55,1600);}).catch(function(){});}
function off(){mu.classList.remove('on');mu.setAttribute('aria-pressed','false');fade(0,600,function(){au.pause();});}
mu.addEventListener('click',function(){mu.classList.contains('on')?off():on();});
(function(){var s=false;function kick(){if(s||mu.classList.contains('on'))return;s=true;on();
  ['pointerdown','touchstart','keydown','scroll'].forEach(function(e){removeEventListener(e,kick,true);});}
  var p=au.play();if(p&&p.then)p.then(function(){mu.classList.add('on');mu.setAttribute('aria-pressed','true');fade(.55,1600);})
    .catch(function(){['pointerdown','touchstart','keydown','scroll'].forEach(function(e){addEventListener(e,kick,true);});});})();
document.addEventListener('visibilitychange',function(){
  if(document.hidden&&mu.classList.contains('on'))au.pause();
  else if(!document.hidden&&mu.classList.contains('on'))au.play().catch(function(){});});
"""
JS = (JS.replace("__WY__", "2027").replace("__WM__", "1").replace("__WD2__", "28")
        .replace("__WH__", "12").replace("__WI__", "30")
        .replace("__NOACC__", "'" + J("계좌번호 입력 전") + "'")
        .replace("__MSGNO__", "'" + J("계좌번호가 아직 등록되지 않았습니다") + "'")
        .replace("__MSGOK__", "'" + J("계좌번호가 복사되었습니다") + "'")
        .replace("__MSGNG__", "'" + J("복사에 실패했습니다") + "'"))

out = HTML % dict(
    TT=K("이상빈 💌 김하린 청첩장"), CSS=CSS, JS=JS,
    ICO_MU=ICO_MU, ICO_CAL=ICO_CAL, ICO_PIN=ICO_PIN, ICO_CH=ICO_CH,
    PROJ=PROJ, RINGS=RINGS, LAUREL_W='<span class="emb">%s</span>' % LAUREL,
    G=K(G), B=K(B), GE=GE, BE=BE, GEF=GE.split()[-1], BEF=BE.split()[-1],
    DATE=DATE_T, HALL=K(HALL), ADDR=K(ADDR),
    Q1=K("사랑은 함께 같은 곳을 바라보는 것이다."), Q2=K("- 생텍쥐페리 -"),
    GREET=K("새로운 여행의 출발선에 서있는<br>저희를 축복해 주시면 감사하겠습니다."),
    STRIP=STRIP, GAL=GAL, GB=GB, CASTROWS=cast_rows,
    PH0=PH[1], PH1=PH[3], PH2=PH[0], PH3=PH[4], PH4=PH[2],
    SUF=K(" 의 결혼식이"), LEFT=K("일 남았습니다."),
    GP=K("이순우 · 백명숙 의 아들"), BP=K("김종무 · 김경염 의 딸"),
    SEAT=K("마음이 머무는 자리 어디든"), NOTE=K("소중한 걸음으로 자리를 빛내 주세요"),
    TKA=K("티켓 뒤집기"), TKH=K("티켓을 클릭해 보세요!"), GALH=K("클릭 시 이미지를 크게 보실 수 있습니다."),
    A1=K("대중교통 이용 시"), A1A=K("지하철 이용 시"),
    A1B=K("7호선 학동역 8번 출구 (도보 5분)<br>3호선 신사역 1번 출구 (도보 10분)"),
    A1C=K("셔틀버스 이용 시"), A1D=K("학동역 8번 출구 · 신사역 1번 출구에서<br>상시 운행합니다."),
    A2=K("자가용 이용 시"), A2A=K("네비게이션"), A2B=K("'파티오나인 주차장' 으로 검색하세요"),
    I1=K("답례품"), I2=K("포토부스"), I3=K("식사"),
    I1T=K("계절에 어울리는 꽃으로 작은 꽃다발을 준비했습니다.<br>예식 후, 축의대 옆에서 나누어 드릴 예정이니<br>자연의 향기를 담아 가시길 바랍니다."),
    I2T=K("예식장 2층에 포토부스를 준비했습니다.<br>두 사람과 함께한 순간을 남겨 주세요."),
    I3T=K("예식 후 같은 층 연회장에서 식사가 준비됩니다.<br>편안한 시간 보내시길 바랍니다."),
    ACCT=K("전해주시는 축하의 마음은 소중하게 간직하여<br>좋은 부부의 모습으로 보답하겠습니다."),
    AG=K("신랑 측"), AB=K("신부 측"), ACCG=acct_rows(ACC_G), ACCB=acct_rows(ACC_B),
    TOPA=K("맨 위로"),
)

open("deploy/movie.html", "w", encoding="utf-8").write(out)
print("movie.html", round(len(out.encode()) / 1024), "KB")
nonascii = [c for c in out if ord(c) > 127]
print("non-ascii:", len(nonascii))
print("brace balance:", out.count("{") - out.count("}"))
