This wedding budget calculator needs JavaScript enabled. / Esta calculadora necesita JavaScript.
/* ---------- Scoped design tokens ---------- */
.bwc{
--bwc-ivory:#fcfaf5; --bwc-champ:#f3ebdc; --bwc-blush:#f7e3df; --bwc-blush2:#d8a39e; --bwc-blush3:#f0c9c4;
--bwc-gold:#b9955a; --bwc-gold2:#e8dbbf; --bwc-gold3:#f7f0e2;
--bwc-ink:#3b332f; --bwc-muted:#7d7169; --bwc-card:#fffdf9; --bwc-line:#e8dfd0; --bwc-line2:#f1eadf;
--bwc-green:#5f9b71; --bwc-green2:#e4f1e7; --bwc-amber:#cf9e3f; --bwc-amber2:#f8ecd3; --bwc-red:#c65d57; --bwc-red2:#f8e1df;
--bwc-shadow:0 10px 30px rgba(96,74,40,.09); --bwc-shadow-sm:0 2px 8px rgba(96,74,40,.07); --bwc-r:18px; --bwc-r-sm:12px;
--bwc-serif:"Cormorant Garamond","Playfair Display","Libre Baskerville",Georgia,"Times New Roman",serif;
--bwc-sans:"Segoe UI",-apple-system,BlinkMacSystemFont,"Helvetica Neue",Helvetica,Arial,sans-serif;
font-family:var(--bwc-sans); font-size:15px; line-height:1.45; color:var(--bwc-ink);
background:linear-gradient(180deg,var(--bwc-ivory) 0%,var(--bwc-champ) 100%);
border-radius:26px; padding:22px; max-width:1240px; margin:0 auto; box-sizing:border-box; position:relative;
-webkit-font-smoothing:antialiased;
}
.bwc *,.bwc *::before,.bwc *::after{box-sizing:border-box}
.bwc h1,.bwc h2,.bwc h3,.bwc h4{font-family:var(--bwc-serif);font-weight:600;margin:0;color:var(--bwc-ink);line-height:1.2;letter-spacing:.01em}
.bwc h1{font-size:34px}.bwc h2{font-size:26px}.bwc h3{font-size:20px}.bwc h4{font-size:17px}
.bwc p{margin:0 0 10px}
.bwc input,.bwc select,.bwc textarea,.bwc button{font-family:var(--bwc-sans);font-size:14px;color:var(--bwc-ink);margin:0}
.bwc input[type=text],.bwc input[type=number],.bwc input[type=date],.bwc input[type=email],.bwc input[type=tel],.bwc input[type=url],.bwc select,.bwc textarea{
width:100%;padding:9px 11px;border:1px solid var(--bwc-line);border-radius:10px;background:#fff;min-height:40px;line-height:1.3;box-shadow:none;appearance:auto;-webkit-appearance:auto;transition:border-color .15s,box-shadow .15s
}
.bwc textarea{min-height:64px;resize:vertical}
.bwc input:focus,.bwc select:focus,.bwc textarea:focus,.bwc button:focus-visible{outline:none;border-color:var(--bwc-gold);box-shadow:0 0 0 3px rgba(185,149,90,.22)}
.bwc button{cursor:pointer}
.bwc input[type=checkbox]{width:18px;height:18px;accent-color:var(--bwc-gold);margin:0;cursor:pointer;flex:none}
.bwc input[type=number]{-moz-appearance:textfield}
.bwc input:disabled,.bwc select:disabled{background:var(--bwc-gold3);color:var(--bwc-muted)}
.bwc-sr{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.bwc-muted{color:var(--bwc-muted);font-size:13px}
.bwc-small{font-size:12.5px}
.bwc-right{text-align:right}
.bwc-nowrap{white-space:nowrap}
.bwc-mono{font-variant-numeric:tabular-nums}
.bwc [hidden]{display:none!important}
/* ---------- Header ---------- */
.bwc-header{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:14px;padding:6px 6px 18px}
.bwc-brand{display:flex;flex-direction:column;gap:4px}
.bwc-brand-kicker{font-size:11.5px;letter-spacing:.22em;text-transform:uppercase;color:var(--bwc-gold);font-weight:600}
.bwc-brand h1{font-size:36px;font-weight:500}
.bwc-brand-sub{color:var(--bwc-muted);font-size:14px}
.bwc-lang{display:inline-flex;background:#fff;border:1px solid var(--bwc-line);border-radius:999px;padding:4px;box-shadow:var(--bwc-shadow-sm)}
.bwc-lang button{border:0;background:transparent;padding:7px 16px;border-radius:999px;font-weight:600;color:var(--bwc-muted);letter-spacing:.02em}
.bwc-lang button[aria-pressed=true]{background:var(--bwc-ink);color:#fff}
.bwc-lang button:hover{color:var(--bwc-ink)}
.bwc-lang button[aria-pressed=true]:hover{color:#fff}
/* ---------- Tabs ---------- */
.bwc-tabs{display:flex;gap:6px;overflow-x:auto;padding:4px 2px 10px;scrollbar-width:thin;-webkit-overflow-scrolling:touch}
.bwc-tabs button{flex:none;border:1px solid transparent;background:rgba(255,255,255,.55);padding:10px 16px;border-radius:999px;font-weight:600;color:var(--bwc-muted);font-size:14px;transition:all .15s;white-space:nowrap}
.bwc-tabs button:hover{background:#fff;color:var(--bwc-ink)}
.bwc-tabs button[aria-selected=true]{background:#fff;color:var(--bwc-ink);border-color:var(--bwc-gold2);box-shadow:var(--bwc-shadow-sm)}
.bwc-tabs button span.bwc-tab-num{display:inline-block;width:20px;height:20px;line-height:20px;border-radius:50%;background:var(--bwc-gold3);color:var(--bwc-gold);font-size:11px;text-align:center;margin-right:7px;font-weight:700}
.bwc-tabs button[aria-selected=true] span.bwc-tab-num{background:var(--bwc-gold);color:#fff}
/* ---------- Sticky summary ---------- */
.bwc-sticky{position:sticky;top:0;z-index:5;background:rgba(255,253,249,.96);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid var(--bwc-line);border-radius:16px;padding:12px 16px;margin-bottom:16px;box-shadow:var(--bwc-shadow-sm)}
.bwc-sticky-row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;align-items:end}
.bwc-stat .bwc-stat-l{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--bwc-muted);font-weight:600}
.bwc-stat .bwc-stat-v{font-family:var(--bwc-serif);font-size:24px;font-weight:600;line-height:1.1;margin-top:2px;font-variant-numeric:tabular-nums}
.bwc-stat .bwc-stat-v.bwc-pos{color:var(--bwc-green)}.bwc-stat .bwc-stat-v.bwc-neg{color:var(--bwc-red)}
.bwc-progress{height:10px;border-radius:999px;background:var(--bwc-line2);overflow:hidden;margin-top:10px;position:relative}
.bwc-progress > span{display:block;height:100%;border-radius:999px;transition:width .35s ease,background .3s;background:var(--bwc-green)}
.bwc-progress.bwc-near > span{background:var(--bwc-amber)}
.bwc-progress.bwc-over > span{background:var(--bwc-red)}
.bwc-progress-label{display:flex;justify-content:space-between;font-size:12px;color:var(--bwc-muted);margin-top:5px}
.bwc-pill{display:inline-block;padding:3px 10px;border-radius:999px;font-size:11.5px;font-weight:600;letter-spacing:.03em}
.bwc-pill-green{background:var(--bwc-green2);color:#3f7650}.bwc-pill-amber{background:var(--bwc-amber2);color:#8a6418}.bwc-pill-red{background:var(--bwc-red2);color:#9c3f3a}
.bwc-pill-gold{background:var(--bwc-gold3);color:var(--bwc-gold)}.bwc-pill-blush{background:var(--bwc-blush);color:#a3625c}
/* ---------- Cards & layout ---------- */
.bwc-card{background:var(--bwc-card);border:1px solid var(--bwc-line);border-radius:var(--bwc-r);padding:20px;box-shadow:var(--bwc-shadow);margin-bottom:16px}
.bwc-card-head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px;margin-bottom:14px}
.bwc-card-head h2,.bwc-card-head h3{display:flex;align-items:center;gap:8px}
.bwc-grid{display:grid;gap:12px}
.bwc-grid-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.bwc-grid-3{grid-template-columns:repeat(3,minmax(0,1fr))}
.bwc-grid-4{grid-template-columns:repeat(4,minmax(0,1fr))}
.bwc-field{display:flex;flex-direction:column;gap:5px;min-width:0}
.bwc-field > span,.bwc-field-label{font-size:12px;font-weight:600;color:var(--bwc-muted);letter-spacing:.04em;text-transform:uppercase}
.bwc-field.bwc-inline{flex-direction:row;align-items:center;gap:9px}
.bwc-field.bwc-inline > span{text-transform:none;letter-spacing:0;font-size:13.5px;font-weight:500;color:var(--bwc-ink)}
.bwc-note{background:var(--bwc-gold3);border:1px dashed var(--bwc-gold2);border-radius:var(--bwc-r-sm);padding:10px 14px;font-size:13px;color:#6f5b36;margin:0 0 14px}
.bwc-note.bwc-note-blush{background:var(--bwc-blush);border-color:var(--bwc-blush3);color:#8b4f49}
.bwc-divider{border:0;border-top:1px solid var(--bwc-line);margin:14px 0}
/* ---------- Buttons ---------- */
.bwc-btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;padding:9px 16px;border-radius:999px;border:1px solid var(--bwc-line);background:#fff;font-weight:600;font-size:13.5px;transition:all .15s;color:var(--bwc-ink);line-height:1.2;min-height:38px;text-decoration:none}
.bwc-btn:hover{border-color:var(--bwc-gold);box-shadow:var(--bwc-shadow-sm)}
.bwc-btn-primary{background:var(--bwc-ink);color:#fff;border-color:var(--bwc-ink)}
.bwc-btn-primary:hover{background:#221d1a}
.bwc-btn-gold{background:var(--bwc-gold);color:#fff;border-color:var(--bwc-gold)}
.bwc-btn-gold:hover{background:#a8864d}
.bwc-btn-ghost{background:transparent}
.bwc-btn-danger{color:var(--bwc-red)}
.bwc-btn-danger:hover{border-color:var(--bwc-red);background:var(--bwc-red2)}
.bwc-btn-sm{padding:6px 12px;font-size:12.5px;min-height:32px}
.bwc-btn-row{display:flex;flex-wrap:wrap;gap:8px}
.bwc-stepper{display:flex;align-items:center;border:1px solid var(--bwc-line);border-radius:12px;background:#fff;overflow:hidden}
.bwc-stepper button{border:0;background:var(--bwc-gold3);width:40px;min-height:40px;font-size:20px;color:var(--bwc-gold);font-weight:700;flex:none}
.bwc-stepper button:hover{background:var(--bwc-gold2)}
.bwc-stepper input{border:0!important;border-radius:0!important;text-align:center;font-weight:700;font-size:18px;min-width:0;font-family:var(--bwc-serif)}
.bwc-guest-hero{display:flex;flex-wrap:wrap;align-items:center;gap:18px;background:linear-gradient(135deg,#fff 0%,var(--bwc-blush) 100%);border:1px solid var(--bwc-blush3);border-radius:var(--bwc-r);padding:16px 20px;margin-bottom:14px}
.bwc-guest-hero .bwc-guest-big{font-family:var(--bwc-serif);font-size:44px;font-weight:600;line-height:1;color:var(--bwc-ink)}
.bwc-guest-hero .bwc-stepper{width:170px}
/* ---------- Tooltip ---------- */
.bwc-tip{display:inline-flex;align-items:center;justify-content:center;width:17px;height:17px;border-radius:50%;background:var(--bwc-gold2);color:#7a5f2d;font-size:11px;font-weight:700;cursor:help;position:relative;vertical-align:middle;margin-left:5px;flex:none}
.bwc-tip::after{content:attr(data-tip);position:absolute;left:50%;bottom:calc(100% + 8px);transform:translateX(-50%);background:var(--bwc-ink);color:#fff;font-size:12px;font-weight:400;line-height:1.4;padding:8px 10px;border-radius:8px;width:230px;text-align:left;opacity:0;pointer-events:none;transition:opacity .15s;z-index:20;white-space:normal}
.bwc-tip:hover::after,.bwc-tip:focus::after,.bwc-tip:focus-visible::after{opacity:1}
/* ---------- Accordion & line items ---------- */
.bwc-cat{border:1px solid var(--bwc-line);border-radius:var(--bwc-r);background:var(--bwc-card);margin-bottom:12px;box-shadow:var(--bwc-shadow-sm);overflow:hidden}
.bwc-cat-head{display:flex;align-items:center;gap:12px;width:100%;border:0;background:#fff;padding:14px 18px;text-align:left;transition:background .15s}
.bwc-cat-head:hover{background:var(--bwc-gold3)}
.bwc-cat-letter{width:34px;height:34px;border-radius:50%;background:var(--bwc-blush);color:#9a5a54;font-family:var(--bwc-serif);font-weight:700;font-size:16px;display:flex;align-items:center;justify-content:center;flex:none}
.bwc-cat-title{flex:1;min-width:0;display:flex;flex-direction:column}
.bwc-cat-title strong{font-family:var(--bwc-serif);font-size:19px;font-weight:600}
.bwc-cat-title small{color:var(--bwc-muted);font-size:12.5px}
.bwc-cat-total{font-family:var(--bwc-serif);font-size:20px;font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}
.bwc-chev{width:22px;height:22px;flex:none;transition:transform .2s;color:var(--bwc-gold)}
.bwc-cat-head[aria-expanded=true] .bwc-chev{transform:rotate(180deg)}
.bwc-cat-body{padding:6px 14px 14px;border-top:1px solid var(--bwc-line2);background:#fdfbf7}
.bwc-item{border:1px solid var(--bwc-line2);border-radius:14px;background:#fff;margin:8px 0;transition:opacity .15s,border-color .15s}
.bwc-item.bwc-off{opacity:.62;background:#fcfaf6}
.bwc-item.bwc-excluded{border-style:dashed}
.bwc-item-main{display:grid;grid-template-columns:26px minmax(150px,1.6fr) 168px 118px 118px 118px 84px;gap:10px;align-items:center;padding:10px 12px}
.bwc-item-main .bwc-field > span{font-size:10.5px}
.bwc-item-name{display:flex;flex-direction:column;gap:2px;min-width:0}
.bwc-item-name > span:first-child{font-weight:600;font-size:14px;display:flex;align-items:center;flex-wrap:wrap;gap:2px 4px}
.bwc-item-name small{color:var(--bwc-muted);font-size:12px}
.bwc-item-name input[type=text]{min-height:34px;padding:5px 8px;font-weight:600}
.bwc-qtywrap{display:flex;align-items:center;gap:6px}
.bwc-qtywrap .bwc-mult{font-size:12px;color:var(--bwc-muted);white-space:nowrap}
.bwc-qtywrap input{min-width:0}
.bwc-sub{text-align:right}
.bwc-sub strong{font-family:var(--bwc-serif);font-size:19px;font-variant-numeric:tabular-nums;display:block}
.bwc-more{border:1px solid var(--bwc-line);background:#fff;border-radius:999px;padding:6px 10px;font-size:12px;font-weight:600;color:var(--bwc-muted)}
.bwc-more[aria-expanded=true]{background:var(--bwc-ink);color:#fff;border-color:var(--bwc-ink)}
.bwc-item-details{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;padding:4px 12px 12px;border-top:1px dashed var(--bwc-line2);margin-top:2px;padding-top:12px}
.bwc-item-details .bwc-wide{grid-column:span 2}
.bwc-badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:10.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;line-height:1.4}
.bwc-badge-excl{background:var(--bwc-gold3);color:var(--bwc-gold)}
.bwc-badge-hotel{background:var(--bwc-blush);color:#9a5a54}
.bwc-badge-custom{background:var(--bwc-green2);color:#3f7650}
.bwc-cat-tools{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end;padding:8px 0 2px}
/* ---------- Tables ---------- */
.bwc-table{width:100%;border-collapse:collapse;font-size:13.5px;margin:0}
.bwc-table th,.bwc-table td{padding:9px 10px;border-bottom:1px solid var(--bwc-line2);text-align:left;vertical-align:middle;background:transparent}
.bwc-table th{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--bwc-muted);font-weight:700}
.bwc-table td.bwc-num,.bwc-table th.bwc-num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.bwc-table tr.bwc-total td{font-weight:700;font-family:var(--bwc-serif);font-size:16px;border-top:2px solid var(--bwc-gold2)}
.bwc-table-wrap{overflow-x:auto}
.bwc-swatch{display:inline-block;width:11px;height:11px;border-radius:3px;margin-right:7px;vertical-align:-1px}
/* ---------- Dashboard ---------- */
.bwc-kpis{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px}
.bwc-kpi{background:#fff;border:1px solid var(--bwc-line);border-radius:14px;padding:12px 14px}
.bwc-kpi .bwc-stat-l{font-size:10.5px}
.bwc-kpi .bwc-stat-v{font-size:21px}
.bwc-chart-wrap{display:grid;grid-template-columns:260px minmax(0,1fr);gap:20px;align-items:center}
.bwc-donut{width:100%;max-width:260px;margin:0 auto;display:block}
.bwc-legend{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px 14px;font-size:13px}
.bwc-legend li{display:flex;justify-content:space-between;gap:8px;border-bottom:1px dotted var(--bwc-line);padding:4px 0}
.bwc-legend li span:last-child{font-variant-numeric:tabular-nums;white-space:nowrap}
.bwc-sens{width:100%;height:auto;display:block}
/* ---------- Scenarios ---------- */
.bwc-scenario{border:1px solid var(--bwc-line);border-radius:var(--bwc-r);background:#fff;padding:16px 18px;margin-bottom:12px}
.bwc-scenario-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px;margin-bottom:10px}
.bwc-scenario-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;align-items:stretch}
.bwc-opt{border:1px solid var(--bwc-line2);border-radius:14px;padding:12px 14px;background:var(--bwc-ivory);display:flex;flex-direction:column;gap:6px}
.bwc-opt.bwc-opt-current{border-color:var(--bwc-gold2);background:var(--bwc-gold3)}
.bwc-opt .bwc-opt-l{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--bwc-muted);font-weight:700}
.bwc-opt .bwc-opt-v{font-family:var(--bwc-serif);font-size:24px;font-weight:600;font-variant-numeric:tabular-nums}
.bwc-opt .bwc-opt-d{font-size:12.5px;color:var(--bwc-muted)}
.bwc-diff{display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;gap:6px}
.bwc-diff .bwc-opt-v.bwc-pos{color:var(--bwc-green)}.bwc-diff .bwc-opt-v.bwc-neg{color:var(--bwc-red)}
/* ---------- Checklist & vendors ---------- */
.bwc-task{display:grid;grid-template-columns:26px minmax(160px,2fr) 150px 140px minmax(140px,2fr) 40px;gap:10px;align-items:center;padding:10px 12px;border:1px solid var(--bwc-line2);border-radius:14px;background:#fff;margin:8px 0}
.bwc-task.bwc-done .bwc-task-name{text-decoration:line-through;color:var(--bwc-muted)}
.bwc-task-name{font-weight:600}
.bwc-task-name input{font-weight:600;min-height:36px}
.bwc-icon-btn{border:1px solid var(--bwc-line);background:#fff;border-radius:50%;width:34px;height:34px;display:inline-flex;align-items:center;justify-content:center;color:var(--bwc-muted);font-size:16px;flex:none}
.bwc-icon-btn:hover{border-color:var(--bwc-red);color:var(--bwc-red);background:var(--bwc-red2)}
.bwc-vendor{border:1px solid var(--bwc-line);border-radius:var(--bwc-r);background:#fff;padding:16px;margin-bottom:12px}
.bwc-vendor-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px;margin-bottom:10px}
.bwc-vendor-head h3{font-size:20px}
.bwc-empty{border:1px dashed var(--bwc-line);border-radius:var(--bwc-r);padding:28px 20px;text-align:center;color:var(--bwc-muted);background:#fff}
/* ---------- Toast ---------- */
.bwc-toast{position:fixed;left:50%;bottom:26px;transform:translate(-50%,20px);background:var(--bwc-ink);color:#fff;padding:11px 18px;border-radius:999px;font-size:13.5px;box-shadow:0 10px 30px rgba(0,0,0,.2);opacity:0;pointer-events:none;transition:all .25s;z-index:9999;max-width:90vw;text-align:center}
.bwc-toast.bwc-show{opacity:1;transform:translate(-50%,0)}
.bwc-toast.bwc-toast-err{background:var(--bwc-red)}
/* ---------- Print area (hidden on screen) ---------- */
.bwc-print{display:none}
/* ---------- Responsive ---------- */
@media (max-width:1024px){
.bwc-item-main{grid-template-columns:26px minmax(140px,1.4fr) 140px 110px 110px 110px 80px}
.bwc-kpis{grid-template-columns:repeat(3,minmax(0,1fr))}
.bwc-task{grid-template-columns:26px minmax(140px,2fr) 140px 130px minmax(120px,1.5fr) 40px}
}
@media (max-width:860px){
.bwc{padding:14px;border-radius:18px}
.bwc-brand h1{font-size:28px}
.bwc-grid-3,.bwc-grid-4{grid-template-columns:repeat(2,minmax(0,1fr))}
.bwc-sticky-row{grid-template-columns:repeat(2,minmax(0,1fr))}
.bwc-item-main{grid-template-columns:26px minmax(0,1fr) 84px;grid-template-areas:"chk name more" "mod mod mod" "qty qty price" "sub sub sub"}
.bwc-item-main > .bwc-check{grid-area:chk}.bwc-item-main > .bwc-item-name{grid-area:name}.bwc-item-main > .bwc-more{grid-area:more}
.bwc-item-main > .bwc-f-model{grid-area:mod}.bwc-item-main > .bwc-f-qty{grid-area:qty}.bwc-item-main > .bwc-f-price{grid-area:price}.bwc-item-main > .bwc-sub{grid-area:sub;text-align:left;display:flex;justify-content:space-between;align-items:center;border-top:1px dashed var(--bwc-line2);padding-top:8px}
.bwc-item-details{grid-template-columns:repeat(2,minmax(0,1fr))}
.bwc-chart-wrap{grid-template-columns:1fr}
.bwc-legend{grid-template-columns:1fr}
.bwc-scenario-grid{grid-template-columns:1fr}
.bwc-task{grid-template-columns:26px minmax(0,1fr) 40px;grid-template-areas:"chk name del" "due who who" "notes notes notes"}
.bwc-task > .bwc-check{grid-area:chk}.bwc-task > .bwc-task-name{grid-area:name}.bwc-task > .bwc-t-due{grid-area:due}.bwc-task > .bwc-t-who{grid-area:who}.bwc-task > .bwc-t-notes{grid-area:notes}.bwc-task > .bwc-icon-btn{grid-area:del}
.bwc-kpis{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:520px){
.bwc-grid-2,.bwc-grid-3,.bwc-grid-4{grid-template-columns:1fr}
.bwc-item-details{grid-template-columns:1fr}
.bwc-item-details .bwc-wide{grid-column:span 1}
.bwc-stat .bwc-stat-v{font-size:19px}
.bwc-guest-hero .bwc-guest-big{font-size:36px}
.bwc-tip::after{width:190px}
}
/* ---------- Print ---------- */
@media print{
body.bwc-printing *{visibility:hidden!important}
body.bwc-printing .bwc-print,body.bwc-printing .bwc-print *{visibility:visible!important}
body.bwc-printing .bwc-print{display:block!important;position:absolute;left:0;top:0;width:100%;padding:0 10mm;background:#fff;color:#000;font-family:Georgia,"Times New Roman",serif;font-size:11.5pt;line-height:1.35}
.bwc-print h1{font-size:22pt;margin:0 0 4pt;font-weight:600}
.bwc-print h2{font-size:14pt;margin:14pt 0 5pt;border-bottom:1px solid #999;padding-bottom:3pt}
.bwc-print p{margin:0 0 4pt}
.bwc-print table{width:100%;border-collapse:collapse;margin:4pt 0 8pt;page-break-inside:auto}
.bwc-print th,.bwc-print td{border-bottom:1px solid #ccc;padding:3pt 5pt;text-align:left;font-size:10pt;vertical-align:top}
.bwc-print th{background:#f2f2f2;font-size:9pt;text-transform:uppercase;letter-spacing:.05em}
.bwc-print td.n,.bwc-print th.n{text-align:right;white-space:nowrap}
.bwc-print tr.t td{font-weight:700;border-top:2px solid #333}
.bwc-print tr{page-break-inside:avoid}
.bwc-print .pmeta{display:flex;flex-wrap:wrap;gap:4pt 18pt;font-size:10.5pt;margin-bottom:8pt}
.bwc-print .pbox{border:1px solid #999;padding:6pt 9pt;margin:6pt 0}
.bwc-print .pchk{display:inline-block;width:10pt;height:10pt;border:1px solid #333;margin-right:5pt;vertical-align:-1pt}
.bwc-print .pchk.on{background:#333}
}
(function(){
'use strict';
/* ============================================================
BOUTIQUE WEDDING BUDGET CALCULATOR — core script
Everything lives inside this IIFE: no globals are created.
============================================================ */
var BWC_VERSION = 1;
/* ------------------------------------------------------------
1. TRANSLATION DICTIONARY (UI strings). Edit text here.
Item / category / checklist names are bilingual inside their
own definitions further below (section 2).
------------------------------------------------------------ */
var T = {
en:{
kicker:"Private planning studio", title:"Wedding Budget Calculator", subtitle:"A boutique budgeting workspace for an intimate waterfront celebration.",
lang_en:"English", lang_es:"Español",
tab_overview:"Overview", tab_budget:"Budget Calculator", tab_compare:"Compare Options", tab_checklist:"Wedding Checklist", tab_vendors:"Vendors & Payments", tab_settings:"Settings",
est_cost:"Estimated cost", target:"Target budget", remaining:"Remaining budget", over:"Amount over budget", per_guest:"Cost per guest", budget_used:"of budget used", guests_word:"guests", pct_used:"Budget used",
status_under:"Under budget", status_near:"Approaching budget", status_over:"Over budget",
dash_title:"Budget dashboard", deposits_req:"Deposits required", total_paid:"Total paid", outstanding:"Outstanding balance", contingency:"Contingency reserve",
distribution:"Expense distribution by category", category_totals:"Category totals", cost_breakdown:"Cost breakdown", subtotal:"Subtotal", service_charges:"Service charges",
svc_venue_line:"Venue service charge", svc_cat_line:"Catering service charge", svc_bev_line:"Beverage service charge", gratuities:"Vendor gratuities", admin_fees:"Administrative fees", taxes:"Sales tax", total:"Estimated total",
items_enabled:"line items included", payments:"Deposits & payments", no_costs:"No expenses are included yet — open the Budget Calculator tab to start.",
share:"Share", of_total:"of total", example_badge:"Preliminary planning estimates — not actual venue quotations. Every price is editable.",
profile_title:"Wedding profile", couple:"Couple's names", date:"Wedding date", date_tbd:"Exact day to be determined", location:"Wedding location", venue:"Wedding venue", style:"Wedding style",
style_elopement:"Elopement", style_intimate:"Intimate", style_boutique:"Boutique", style_traditional:"Traditional", style_luxury:"Luxury", style_destination:"Destination",
guests:"Total guests", adults:"Adults", children:"Children", vendor_meals:"Vendors needing meals", party:"Wedding party members", target_budget:"Budget target", currency:"Currency",
ceremony_setting:"Ceremony setting", indoor:"Indoor", outdoor:"Outdoor", reception_type:"Reception type", rec_seated:"Seated dinner", rec_buffet:"Buffet", rec_cocktail:"Cocktail-style", rec_brunch:"Brunch / lunch",
events:"Wedding events", nights:"Hotel nights", hotel_payer:"Guest hotel rooms paid by", couple_pays:"The couple", guests_pay:"Each guest", seats_per_table:"Seats per table",
guest_note:"Total guests always equals adults + children. Edit any of the three and the others stay consistent.", guest_hero:"Guest count", tables_word:"tables",
budget_title:"Budget line items", budget_intro:"Tick a box to include an expense. Choose how each one is priced, then edit quantity and unit price. Open Details for vendor, notes, tax, service charge and payments.",
include:"Include", pricing:"Pricing model", qty:"Quantity", unit_price:"Unit price", pct_label:"Percentage", subtotal_l:"Subtotal", vendor:"Vendor", notes:"Notes", taxable:"Taxable", svc:"Service charge applies",
svc_none:"None", svc_venue:"Venue", svc_catering:"Catering", svc_beverage:"Beverage", deposit:"Deposit required", paid:"Amount paid", balance:"Remaining balance", details:"Details", delete:"Delete",
add_custom:"Add custom expense", custom_name:"New custom expense", expense_name:"Expense name", excl_badge:"Choose one", excl_tip:"Only one option in this group can be included at a time — selecting this one unselects the others, so nothing is double-counted.",
hotel_badge:"Guests pay", hotel_tip:"Excluded from the budget because guests pay their own rooms (change this in the Wedding profile).", pct_of:"Percent of", base_all:"All other included expenses",
expand_all:"Expand all", collapse_all:"Collapse all", empty_cat:"No expenses included in this category yet.", included_count:"included",
m_guests:"guests", m_adults:"adults", m_children:"children", m_vendors:"vendor meals", m_tables:"tables", m_nights:"nights", q_hours:"Hours", q_days:"Days", q_rooms:"Rooms", q_items:"Items", q_people:"People", q_qty:"Quantity", q_units:"Units",
model_flat:"Flat fee", model_perGuest:"Per guest", model_perAdult:"Per adult", model_perChild:"Per child", model_perVendorMeal:"Per vendor meal", model_perHour:"Per hour", model_perDay:"Per day", model_perNight:"Per night",
model_perTable:"Per table", model_perItem:"Per item", model_perPerson:"Per person", model_percent:"Percentage of…", model_custom:"Custom qty × price",
compare_title:"What if?", compare_intro:"Each scenario is calculated with the same engine as your main budget, including taxes, service charges and contingency. Nothing changes until you choose Apply.",
option_a:"Option A", option_b:"Option B", difference:"Difference", apply_a:"Apply A", apply_b:"Apply B", current:"current", saves:"Option B saves", costs_more:"Option B costs more", same:"No difference",
sc_guests:"Guest count", sc_guests_d:"Adults change; children, vendors and every per-guest price stay as set.", sc_dinner:"Buffet vs plated dinner", sc_bar:"Beer & wine vs open bar", sc_music:"DJ vs live band",
sc_flowers:"Basic vs premium flowers", sc_flowers_d:"Premium multiplies every Flowers & Decorations price by the factor below (planning assumption).", sc_photo:"Photographer only vs photo + video",
sc_planner:"Full wedding planner vs day-of coordinator", premium_factor:"Premium flower multiplier", sensitivity:"Guest-count sensitivity", sens_note:"Estimated total at each guest count (children held constant; adults adjusted). The dashed line is your target budget.",
apply_confirm:"Apply this option to your saved budget?", applied:"Scenario applied to the budget", guests_a:"Guests (A)", guests_b:"Guests (B)", buffet_o:"Buffet dinner", plated_o:"Plated dinner", beerwine_o:"Beer & wine package", openbar_o:"Open bar (per guest)",
dj_o:"DJ", band_o:"Live band", basic_o:"Basic flowers (as set)", premium_o:"Premium flowers", photo_o:"Photographer only", photovideo_o:"Photography + videography", planner_o:"Full-service planner", dayof_o:"Day-of coordinator",
cl_title:"Wedding checklist", cl_intro:"Track every milestone. Progress saves automatically with the rest of your budget.", cl_progress:"complete", cl_task:"Task", cl_due:"Due date", cl_who:"Responsible", who_bride:"Bride", who_groom:"Groom", who_both:"Both", who_planner:"Planner",
cl_add:"Add task", cl_new:"New task", cl_print:"Print checklist", cl_done:"Done", cl_empty:"No tasks yet. Add your first task above.", cl_tasks:"tasks",
v_title:"Vendors & payments", v_intro:"Keep contracts, contacts and payment schedules in one place.", v_add:"Add vendor", v_cat:"Category", v_company:"Company name", v_contact:"Contact person", v_email:"Email", v_phone:"Phone", v_web:"Website",
v_amount:"Contract amount", v_dep_due:"Deposit due", v_dep_paid:"Deposit paid", v_final:"Final payment due date", v_paid:"Total paid", v_balance:"Remaining balance", v_signed:"Contract signed", v_link:"Linked budget line", v_nolink:"— Not linked —",
v_sync:"Copy payments to budget line", v_synced:"Deposit and amount paid copied to the budget line", v_empty:"No vendors yet. Add your first vendor to track contracts, deposits and balances.",
v_note:"Vendor contracts are tracked for reference and are never added to the budget total. The budget line items stay the single source of truth; use “Copy payments” to push a vendor's deposit and paid amounts into its linked line.",
v_contracts:"Total contracted", v_new:"New vendor", v_line_cost:"Budget line estimate",
s_title:"Settings", s_rates:"Taxes, service charges & fees", s_tax:"Sales tax %", s_venue_svc:"Venue service charge %", s_cat_svc:"Catering service charge %", s_bev_svc:"Beverage service charge %", s_grat:"Vendor gratuity %",
s_grat_incl:"Service charges already include gratuity (do not add gratuity again)", s_admin:"Additional administrative fee % (on subtotal)", s_cont:"Contingency % (on everything above it)", s_tax_on_svc:"Apply sales tax to service charges (Florida usually taxes mandatory service charges)",
s_rate_note:"Default rates are planning assumptions, not verified venue rates. Confirm every percentage against your signed venue, catering and bar agreements. Taxes apply only to lines marked Taxable; service charges only to lines assigned a Venue, Catering or Beverage service charge; gratuity applies to those same service-charge lines unless marked as already included.",
s_formula:"Order of calculation: subtotal → service charges → gratuity → administrative fee → sales tax → contingency → estimated total. Contingency is applied once and never to itself.",
s_data:"Save, export & print", btn_save:"Save budget", btn_reset:"Reset budget", btn_csv:"Export CSV", btn_json:"Export JSON", btn_import:"Import JSON", btn_print:"Print budget",
storage_note:"Your data is saved automatically in this browser only (localStorage). It is not a cloud backup: clearing browser data, private windows or switching devices will lose it. Export a JSON file to keep a copy or move it to another device.",
saved:"Budget saved in this browser", reset_confirm:"Reset everything to the example wedding? All edits, tasks and vendors in this browser will be lost.", reset_done:"Example wedding restored",
import_ok:"Budget imported", import_err:"That file is not a valid wedding-budget JSON export.", storage_err:"Could not save: browser storage is unavailable or full. Export JSON to keep a copy.",
s_language:"Language", s_general:"General", neg_fixed:"Negative values are not allowed — set to 0.", delete_confirm:"Delete this?", yes:"Yes", no:"No",
p_report:"Wedding Budget Report", p_checklist:"Wedding Checklist", p_generated:"Generated", p_category:"Category", p_expense:"Expense", p_detail:"Pricing detail", p_amount:"Amount", p_tax_svc:"Taxes & service charges",
p_deposits:"Deposits & balances", p_couple:"Couple", p_task:"Task", p_summary:"Summary", p_disclaimer:"Estimates are preliminary planning figures, not vendor quotations.",
csv_cat:"Category", csv_item:"Expense", csv_model:"Pricing model", csv_mult:"Multiplier", csv_qty:"Quantity", csv_price:"Unit price", csv_sub:"Subtotal", csv_tax:"Taxable", csv_svc:"Service charge", csv_vendor:"Vendor", csv_dep:"Deposit", csv_paid:"Paid", csv_bal:"Balance", csv_notes:"Notes"
},
es:{
kicker:"Estudio privado de planeación", title:"Calculadora de Presupuesto de Boda", subtitle:"Un espacio boutique para presupuestar una celebración íntima frente al mar.",
lang_en:"English", lang_es:"Español",
tab_overview:"Resumen", tab_budget:"Calculadora", tab_compare:"Comparar Opciones", tab_checklist:"Lista de Preparativos", tab_vendors:"Proveedores y Pagos", tab_settings:"Configuración",
est_cost:"Costo estimado", target:"Presupuesto objetivo", remaining:"Presupuesto disponible", over:"Monto excedido", per_guest:"Costo por invitado", budget_used:"del presupuesto usado", guests_word:"invitados", pct_used:"Presupuesto usado",
status_under:"Dentro del presupuesto", status_near:"Cerca del límite", status_over:"Presupuesto excedido",
dash_title:"Panel de presupuesto", deposits_req:"Depósitos requeridos", total_paid:"Total pagado", outstanding:"Saldo pendiente", contingency:"Reserva de contingencia",
distribution:"Distribución de gastos por categoría", category_totals:"Totales por categoría", cost_breakdown:"Desglose de costos", subtotal:"Subtotal", service_charges:"Cargos por servicio",
svc_venue_line:"Cargo por servicio del lugar", svc_cat_line:"Cargo por servicio de catering", svc_bev_line:"Cargo por servicio de bebidas", gratuities:"Propinas a proveedores", admin_fees:"Cargos administrativos", taxes:"Impuesto a las ventas", total:"Total estimado",
items_enabled:"gastos incluidos", payments:"Depósitos y pagos", no_costs:"Aún no hay gastos incluidos — abra la pestaña Calculadora para comenzar.",
share:"Participación", of_total:"del total", example_badge:"Estimados preliminares de planeación — no son cotizaciones reales del lugar. Todos los precios son editables.",
profile_title:"Perfil de la boda", couple:"Nombres de la pareja", date:"Fecha de la boda", date_tbd:"Día exacto por definir", location:"Ubicación de la boda", venue:"Lugar del evento", style:"Estilo de boda",
style_elopement:"Fuga romántica (elopement)", style_intimate:"Íntima", style_boutique:"Boutique", style_traditional:"Tradicional", style_luxury:"De lujo", style_destination:"De destino",
guests:"Total de invitados", adults:"Adultos", children:"Niños", vendor_meals:"Proveedores con comida", party:"Integrantes del cortejo", target_budget:"Presupuesto objetivo", currency:"Moneda",
ceremony_setting:"Tipo de ceremonia", indoor:"Bajo techo", outdoor:"Al aire libre", reception_type:"Tipo de recepción", rec_seated:"Cena sentada", rec_buffet:"Bufé", rec_cocktail:"Estilo cóctel", rec_brunch:"Brunch / almuerzo",
events:"Eventos de la boda", nights:"Noches de hotel", hotel_payer:"Habitaciones de invitados pagadas por", couple_pays:"La pareja", guests_pay:"Cada invitado", seats_per_table:"Puestos por mesa",
guest_note:"El total de invitados siempre es adultos + niños. Edite cualquiera de los tres y los demás se mantienen consistentes.", guest_hero:"Número de invitados", tables_word:"mesas",
budget_title:"Gastos de la boda", budget_intro:"Marque la casilla para incluir un gasto. Elija el modelo de precio y edite cantidad y precio unitario. Abra Detalles para proveedor, notas, impuesto, cargo por servicio y pagos.",
include:"Incluir", pricing:"Modelo de precio", qty:"Cantidad", unit_price:"Precio unitario", pct_label:"Porcentaje", subtotal_l:"Subtotal", vendor:"Proveedor", notes:"Notas", taxable:"Gravable (IVA/impuesto)", svc:"Aplica cargo por servicio",
svc_none:"Ninguno", svc_venue:"Lugar", svc_catering:"Catering", svc_beverage:"Bebidas", deposit:"Depósito requerido", paid:"Monto pagado", balance:"Saldo pendiente", details:"Detalles", delete:"Eliminar",
add_custom:"Agregar gasto personalizado", custom_name:"Nuevo gasto personalizado", expense_name:"Nombre del gasto", excl_badge:"Elija uno", excl_tip:"Solo una opción de este grupo puede incluirse a la vez: al seleccionar esta se desmarcan las demás, así nada se cuenta dos veces.",
hotel_badge:"Pagan los invitados", hotel_tip:"Excluido del presupuesto porque los invitados pagan sus propias habitaciones (cámbielo en el Perfil de la boda).", pct_of:"Porcentaje de", base_all:"Todos los demás gastos incluidos",
expand_all:"Expandir todo", collapse_all:"Contraer todo", empty_cat:"Aún no hay gastos incluidos en esta categoría.", included_count:"incluidos",
m_guests:"invitados", m_adults:"adultos", m_children:"niños", m_vendors:"comidas de proveedores", m_tables:"mesas", m_nights:"noches", q_hours:"Horas", q_days:"Días", q_rooms:"Habitaciones", q_items:"Artículos", q_people:"Personas", q_qty:"Cantidad", q_units:"Unidades",
model_flat:"Tarifa fija", model_perGuest:"Por invitado", model_perAdult:"Por adulto", model_perChild:"Por niño", model_perVendorMeal:"Por comida de proveedor", model_perHour:"Por hora", model_perDay:"Por día", model_perNight:"Por noche",
model_perTable:"Por mesa", model_perItem:"Por artículo", model_perPerson:"Por persona", model_percent:"Porcentaje de…", model_custom:"Cantidad × precio",
compare_title:"¿Qué pasaría si…?", compare_intro:"Cada escenario se calcula con el mismo motor del presupuesto principal, incluyendo impuestos, cargos por servicio y contingencia. Nada cambia hasta que elija Aplicar.",
option_a:"Opción A", option_b:"Opción B", difference:"Diferencia", apply_a:"Aplicar A", apply_b:"Aplicar B", current:"actual", saves:"La opción B ahorra", costs_more:"La opción B cuesta más", same:"Sin diferencia",
sc_guests:"Número de invitados", sc_guests_d:"Cambian los adultos; niños, proveedores y todos los precios por invitado se mantienen.", sc_dinner:"Bufé vs cena servida a la mesa", sc_bar:"Cerveza y vino vs barra abierta", sc_music:"DJ vs banda en vivo",
sc_flowers:"Flores básicas vs premium", sc_flowers_d:"Premium multiplica todos los precios de Flores y decoración por el factor indicado (supuesto de planeación).", sc_photo:"Solo fotógrafo vs foto + video",
sc_planner:"Wedding planner completo vs coordinador del día", premium_factor:"Multiplicador de flores premium", sensitivity:"Sensibilidad al número de invitados", sens_note:"Total estimado para cada número de invitados (niños constantes; se ajustan los adultos). La línea punteada es su presupuesto objetivo.",
apply_confirm:"¿Aplicar esta opción a su presupuesto guardado?", applied:"Escenario aplicado al presupuesto", guests_a:"Invitados (A)", guests_b:"Invitados (B)", buffet_o:"Cena tipo bufé", plated_o:"Cena servida a la mesa", beerwine_o:"Paquete de cerveza y vino", openbar_o:"Barra abierta (por invitado)",
dj_o:"DJ", band_o:"Banda en vivo", basic_o:"Flores básicas (como están)", premium_o:"Flores premium", photo_o:"Solo fotógrafo", photovideo_o:"Fotografía + video", planner_o:"Wedding planner de servicio completo", dayof_o:"Coordinador del día",
cl_title:"Lista de preparativos", cl_intro:"Siga cada hito. El progreso se guarda automáticamente junto con el presupuesto.", cl_progress:"completado", cl_task:"Tarea", cl_due:"Fecha límite", cl_who:"Responsable", who_bride:"Novia", who_groom:"Novio", who_both:"Ambos", who_planner:"Planner",
cl_add:"Agregar tarea", cl_new:"Nueva tarea", cl_print:"Imprimir lista", cl_done:"Hecho", cl_empty:"Aún no hay tareas. Agregue la primera arriba.", cl_tasks:"tareas",
v_title:"Proveedores y pagos", v_intro:"Contratos, contactos y calendario de pagos en un solo lugar.", v_add:"Agregar proveedor", v_cat:"Categoría", v_company:"Empresa", v_contact:"Persona de contacto", v_email:"Correo electrónico", v_phone:"Teléfono", v_web:"Sitio web",
v_amount:"Valor del contrato", v_dep_due:"Depósito a pagar", v_dep_paid:"Depósito pagado", v_final:"Fecha del pago final", v_paid:"Total pagado", v_balance:"Saldo pendiente", v_signed:"Contrato firmado", v_link:"Gasto vinculado del presupuesto", v_nolink:"— Sin vincular —",
v_sync:"Copiar pagos al gasto", v_synced:"Depósito y monto pagado copiados al gasto del presupuesto", v_empty:"Aún no hay proveedores. Agregue el primero para seguir contratos, depósitos y saldos.",
v_note:"Los contratos de proveedores se registran solo como referencia y nunca se suman al total del presupuesto. Los gastos del presupuesto son la única fuente de verdad; use “Copiar pagos” para llevar el depósito y lo pagado al gasto vinculado.",
v_contracts:"Total contratado", v_new:"Nuevo proveedor", v_line_cost:"Estimado del gasto",
s_title:"Configuración", s_rates:"Impuestos, cargos por servicio y tarifas", s_tax:"Impuesto a las ventas %", s_venue_svc:"Cargo por servicio del lugar %", s_cat_svc:"Cargo por servicio de catering %", s_bev_svc:"Cargo por servicio de bebidas %", s_grat:"Propina a proveedores %",
s_grat_incl:"Los cargos por servicio ya incluyen la propina (no sumarla de nuevo)", s_admin:"Cargo administrativo adicional % (sobre el subtotal)", s_cont:"Contingencia % (sobre todo lo anterior)", s_tax_on_svc:"Aplicar impuesto a los cargos por servicio (en Florida los cargos obligatorios suelen ser gravables)",
s_rate_note:"Las tasas por defecto son supuestos de planeación, no tarifas verificadas del lugar. Confirme cada porcentaje con sus contratos firmados de lugar, catering y bar. El impuesto solo aplica a los gastos marcados como gravables; los cargos por servicio solo a los gastos asignados a Lugar, Catering o Bebidas; la propina aplica a esos mismos gastos salvo que esté marcada como ya incluida.",
s_formula:"Orden de cálculo: subtotal → cargos por servicio → propina → cargo administrativo → impuesto → contingencia → total estimado. La contingencia se aplica una sola vez y nunca sobre sí misma.",
s_data:"Guardar, exportar e imprimir", btn_save:"Guardar presupuesto", btn_reset:"Restablecer presupuesto", btn_csv:"Exportar CSV", btn_json:"Exportar JSON", btn_import:"Importar JSON", btn_print:"Imprimir presupuesto",
storage_note:"Sus datos se guardan automáticamente solo en este navegador (localStorage). No es una copia de seguridad en la nube: si borra los datos del navegador, usa una ventana privada o cambia de dispositivo, se perderán. Exporte un archivo JSON para conservar una copia o llevarla a otro dispositivo.",
saved:"Presupuesto guardado en este navegador", reset_confirm:"¿Restablecer todo a la boda de ejemplo? Se perderán todas las ediciones, tareas y proveedores de este navegador.", reset_done:"Boda de ejemplo restaurada",
import_ok:"Presupuesto importado", import_err:"Ese archivo no es una exportación JSON válida de presupuesto de boda.", storage_err:"No se pudo guardar: el almacenamiento del navegador no está disponible o está lleno. Exporte un JSON para conservar una copia.",
s_language:"Idioma", s_general:"General", neg_fixed:"No se permiten valores negativos; se ajustó a 0.", delete_confirm:"¿Eliminar esto?", yes:"Sí", no:"No",
p_report:"Informe de Presupuesto de Boda", p_checklist:"Lista de Preparativos", p_generated:"Generado", p_category:"Categoría", p_expense:"Gasto", p_detail:"Detalle del precio", p_amount:"Monto", p_tax_svc:"Impuestos y cargos por servicio",
p_deposits:"Depósitos y saldos", p_couple:"Pareja", p_task:"Tarea", p_summary:"Resumen", p_disclaimer:"Los estimados son cifras preliminares de planeación, no cotizaciones de proveedores.",
csv_cat:"Categoría", csv_item:"Gasto", csv_model:"Modelo de precio", csv_mult:"Multiplicador", csv_qty:"Cantidad", csv_price:"Precio unitario", csv_sub:"Subtotal", csv_tax:"Gravable", csv_svc:"Cargo por servicio", csv_vendor:"Proveedor", csv_dep:"Depósito", csv_paid:"Pagado", csv_bal:"Saldo", csv_notes:"Notas"
}};
/* ------------------------------------------------------------
2. EXPENSE DEFINITIONS — the preloaded example wedding.
Tuple: [id, English name, Spanish name, model, unit price, included?, options]
options: qty, tax (taxable), svc ('venue'|'catering'|'beverage'), group (mutually exclusive group),
hotel (guest-room line), pct (percentage base: 'all' or a category id), tip {en,es}
Prices are preliminary planning estimates for a ~30-guest resort wedding; they are NOT venue quotations.
------------------------------------------------------------ */
var MODELS = ['flat','perGuest','perAdult','perChild','perVendorMeal','perHour','perDay','perNight','perTable','perItem','perPerson','percent','custom'];
var SVC_TYPES = ['none','venue','catering','beverage'];
var CAT_COLORS = ['#c9a56a','#d8a39e','#8fb39a','#b6a1c7','#e3b96f','#9fb9d0','#d49a7a','#a7c3b0','#c8b08a','#c29fa9','#9aa8bf','#d7c08a','#b9b9b9'];
var CATS = [
{id:'venue', letter:'A', en:'Wedding Venue & Ceremony', es:'Lugar y ceremonia', items:[
['venue_rental','Venue rental','Alquiler del lugar','flat',2000,true,{tax:true,svc:'venue',tip:{en:'Site / ceremony fee charged by the resort for the waterfront ceremony space.',es:'Tarifa del lugar que cobra el resort por el espacio de la ceremonia frente al mar.'}}],
['beach_permit','Beach ceremony permit','Permiso para ceremonia en la playa','flat',150,true,{}],
['ceremony_setup','Ceremony setup','Montaje de la ceremonia','flat',400,false,{tax:true,svc:'venue'}],
['reception_rental','Reception venue rental','Alquiler del salón de recepción','flat',1500,false,{tax:true,svc:'venue',tip:{en:'Only if the reception room is billed separately from food & beverage minimums.',es:'Solo si el salón se cobra aparte de los mínimos de comida y bebida.'}}],
['private_dining','Private dining room','Salón privado','flat',1000,false,{tax:true,svc:'venue'}],
['ceremony_arch','Ceremony arch','Arco ceremonial','flat',300,true,{tax:true}],
['ceremony_chairs','Ceremony chairs','Sillas para la ceremonia','perGuest',7,true,{tax:true,svc:'venue'}],
['chair_setup','Chair setup and breakdown','Montaje y desmontaje de sillas','flat',150,false,{}],
['aisle_decor','Aisle decorations','Decoración del pasillo','flat',300,false,{tax:true}],
['ceremony_sound','Ceremony sound system','Sistema de sonido para la ceremonia','flat',350,false,{}],
['microphones','Microphones','Micrófonos','perItem',50,false,{qty:2}],
['officiant','Officiant','Oficiante','flat',350,false,{group:'officiant'}],
['officiant_bilingual','Bilingual officiant','Oficiante bilingüe','flat',500,true,{group:'officiant',tip:{en:'An officiant who performs the ceremony in both English and Spanish.',es:'Oficiante que celebra la ceremonia en inglés y español.'}}],
['ceremony_musician','Ceremony musician','Músico para la ceremonia','flat',400,false,{group:'ceremony_music',tip:{en:'Also listed under Music & Entertainment — it can only be included in one place.',es:'También aparece en Música y entretenimiento; solo puede incluirse en un lugar.'}}],
['rehearsal','Wedding rehearsal','Ensayo de la boda','flat',200,false,{}],
['venue_coord','Venue coordination','Coordinación del lugar','flat',500,false,{tip:{en:'Resort-side coordinator fee if not already built into the venue package.',es:'Tarifa del coordinador del resort si no viene incluida en el paquete.'}}],
['event_insurance','Event insurance','Seguro del evento','flat',250,false,{group:'insurance',tip:{en:'Liability policy some venues require. Also listed under Legal as Wedding insurance — include one only.',es:'Póliza de responsabilidad que algunos lugares exigen. También aparece en Legal como Seguro de boda; incluya solo uno.'}}],
['weather_backup','Weather backup location','Lugar alterno por clima','flat',500,false,{tax:true,svc:'venue'}],
['tent','Tent rental','Alquiler de carpa','flat',1500,false,{tax:true}],
['tent_lighting','Tent lighting','Iluminación de la carpa','flat',400,false,{tax:true}],
['fans_heaters','Outdoor fans or heaters','Ventiladores o calefactores','perItem',75,false,{qty:2,tax:true}],
['setup_cleanup','Setup and cleanup fees','Tarifas de montaje y limpieza','flat',300,false,{svc:'venue'}]
]},
{id:'food', letter:'B', en:'Food & Catering', es:'Comida y catering', items:[
['cocktail_food','Cocktail hour food','Comida de la hora del cóctel','perGuest',24,true,{tax:true,svc:'catering'}],
['passed_apps','Passed appetizers per guest','Pasabocas servidos por invitado','perGuest',12,false,{tax:true,svc:'catering'}],
['grazing','Grazing table','Mesa de picoteo','flat',600,false,{tax:true,svc:'catering'}],
['buffet','Buffet dinner','Cena tipo bufé','perGuest',78,false,{tax:true,svc:'catering',group:'dinner'}],
['plated','Plated dinner','Cena servida a la mesa','perGuest',85,true,{tax:true,svc:'catering',group:'dinner'}],
['family_style','Family-style dinner','Cena estilo familiar','perGuest',82,false,{tax:true,svc:'catering',group:'dinner'}],
['kids_meals','Children’s meals','Menú infantil','perChild',35,true,{tax:true,svc:'catering'}],
['vendor_meals','Vendor meals','Comidas para proveedores','perVendorMeal',45,true,{tax:true,svc:'catering',tip:{en:'Photographer, DJ, planner… count them in the Wedding profile.',es:'Fotógrafo, DJ, planner… cuéntelos en el Perfil de la boda.'}}],
['late_night','Late-night snacks','Pasabocas de medianoche','perGuest',12,false,{tax:true,svc:'catering'}],
['dessert','Dessert station','Estación de postres','perGuest',10,false,{tax:true,svc:'catering'}],
['coffee_tea','Coffee and tea','Café y té','perGuest',5,false,{tax:true,svc:'catering'}],
['cake','Wedding cake','Torta de bodas','flat',450,true,{tax:true,tip:{en:'Outside bakery — no resort service charge assumed.',es:'Pastelería externa; no se asume cargo por servicio del resort.'}}],
['cake_cutting','Cake cutting fee','Cargo por corte de torta','perGuest',3,true,{tax:true,svc:'catering'}],
['catering_staff','Catering staffing','Personal de catering','perHour',35,false,{qty:5}],
['chef','Chef fees','Honorarios del chef','flat',300,false,{}],
['bartender_fees','Bartender fees','Honorarios de bartender','perHour',45,false,{qty:4,group:'bartender',tip:{en:'Also listed under Alcohol & Beverages as Bartender labor — include one only.',es:'También aparece en Licor y bebidas como Mano de obra de bartender; incluya solo uno.'}}],
['outside_catering','Outside catering fees','Cargo por catering externo','flat',500,false,{}],
['catering_delivery','Catering delivery and setup','Entrega y montaje del catering','flat',250,false,{}]
]},
{id:'bar', letter:'C', en:'Alcohol & Beverages', es:'Licor y bebidas', items:[
['open_bar_guest','Open bar per guest','Barra abierta por invitado','perGuest',62,false,{tax:true,svc:'beverage',group:'bar'}],
['open_bar_hour','Open bar per hour','Barra abierta por hora','perHour',600,false,{qty:4,tax:true,svc:'beverage',group:'bar'}],
['beer_wine','Beer and wine package','Paquete de cerveza y vino','perGuest',38,true,{tax:true,svc:'beverage',group:'bar'}],
['premium_upgrade','Premium liquor upgrade','Mejora a licor premium','perGuest',15,false,{tax:true,svc:'beverage'}],
['champagne','Champagne toast','Brindis con champaña','perGuest',7,true,{tax:true,svc:'beverage'}],
['signature','Signature cocktails','Cócteles de autor','perGuest',12,false,{tax:true,svc:'beverage'}],
['nonalc','Nonalcoholic beverages','Bebidas sin alcohol','perGuest',6,false,{tax:true,svc:'beverage'}],
['mocktail','Mocktail station','Estación de mocktails','flat',350,false,{tax:true,svc:'beverage'}],
['bev_service_fee','Beverage service fee','Cargo por servicio de bebidas','flat',150,false,{tip:{en:'Use only for a flat bar fee. Percentage service charges belong in Settings.',es:'Úselo solo para una tarifa fija del bar. Los cargos porcentuales van en Configuración.'}}],
['corkage','Corkage fees','Descorche','perItem',25,false,{qty:6}],
['bartender_labor','Bartender labor','Mano de obra de bartender','perHour',45,false,{qty:4,group:'bartender'}],
['bar_setup','Bar setup','Montaje del bar','flat',200,false,{}]
]},
{id:'flowers', letter:'D', en:'Flowers & Decorations', es:'Flores y decoración', items:[
['bouquet','Bridal bouquet','Ramo de novia','flat',250,true,{tax:true}],
['bm_bouquets','Bridesmaids’ bouquets','Ramos de las damas de honor','perPerson',85,false,{qty:2,tax:true}],
['groom_bout','Groom boutonniere','Boutonnier del novio','flat',35,true,{tax:true}],
['gm_bouts','Groomsmen boutonnieres','Boutonniers de los caballeros de honor','perPerson',25,false,{qty:2,tax:true}],
['arch_flowers','Ceremony arch flowers','Flores del arco ceremonial','flat',400,true,{tax:true}],
['aisle_flowers','Aisle flowers','Flores del pasillo','flat',300,false,{tax:true}],
['centerpieces','Reception centerpieces','Centros de mesa','perTable',95,true,{tax:true,tip:{en:'Tables = guests ÷ seats per table (Settings), rounded up.',es:'Mesas = invitados ÷ puestos por mesa (Configuración), redondeado hacia arriba.'}}],
['sweetheart','Sweetheart table flowers','Flores de la mesa de los novios','flat',150,false,{tax:true}],
['welcome_table','Welcome table decorations','Decoración de la mesa de bienvenida','flat',120,false,{tax:true}],
['candles','Candles','Velas','flat',100,false,{tax:true}],
['lanterns','Lanterns','Faroles','flat',150,false,{tax:true}],
['linens','Table linens','Mantelería','perTable',25,false,{tax:true}],
['chair_covers','Chair covers','Forros para sillas','perGuest',4,false,{tax:true}],
['deco_lighting','Decorative lighting','Iluminación decorativa','flat',400,false,{tax:true}],
['string_lights','String lights','Luces de guirnalda','flat',300,false,{tax:true}],
['floral_labor','Floral installation labor','Mano de obra de instalación floral','flat',300,false,{}],
['flower_delivery','Delivery and setup','Entrega y montaje','flat',150,true,{}],
['deco_removal','Decoration removal','Retiro de la decoración','flat',100,false,{}]
]},
{id:'photo', letter:'E', en:'Photography & Videography', es:'Fotografía y video', items:[
['photographer','Wedding photographer','Fotógrafo de bodas','flat',2200,true,{tip:{en:'Typical 6-hour boutique package. Add Extra coverage hours below if needed.',es:'Paquete boutique típico de 6 horas. Agregue horas extra abajo si las necesita.'}}],
['second_photog','Additional photographer','Segundo fotógrafo','perHour',150,false,{qty:6}],
['engagement','Engagement photos','Fotos de compromiso','flat',400,false,{}],
['ceremony_photo','Ceremony photography','Fotografía de la ceremonia','flat',800,false,{tip:{en:'Only if booked separately from a full-day photographer package.',es:'Solo si se contrata aparte del paquete completo del fotógrafo.'}}],
['reception_photo','Reception photography','Fotografía de la recepción','flat',1200,false,{}],
['videographer','Wedding videographer','Videógrafo de bodas','flat',2200,false,{}],
['drone','Drone coverage','Cobertura con dron','flat',400,false,{}],
['highlight_film','Highlight film','Video resumen (highlight)','flat',600,false,{}],
['full_ceremony_video','Full ceremony video','Video completo de la ceremonia','flat',300,false,{}],
['album','Wedding album','Álbum de bodas','flat',500,false,{tax:true}],
['prints','Printed photos','Fotos impresas','flat',200,false,{tax:true}],
['digital_gallery','Digital photo gallery','Galería digital de fotos','flat',150,false,{}],
['extra_hours','Extra coverage hours','Horas extra de cobertura','perHour',300,false,{qty:1}],
['photo_travel','Travel fees','Gastos de desplazamiento','flat',200,false,{}]
]},
{id:'music', letter:'F', en:'Music & Entertainment', es:'Música y entretenimiento', items:[
['ceremony_musician_m','Ceremony musician','Músico para la ceremonia','flat',400,false,{group:'ceremony_music'}],
['cocktail_musician','Cocktail hour musician','Músico para la hora del cóctel','flat',450,false,{}],
['dj','DJ','DJ','flat',1000,true,{group:'music_main',tip:{en:'DJ and Live band are mutually exclusive here. Enable both only via a custom expense if you truly want both.',es:'DJ y Banda en vivo son excluyentes aquí. Si de verdad quiere ambos, agregue un gasto personalizado.'}}],
['band','Live band','Banda en vivo','flat',3500,false,{group:'music_main'}],
['colombian_music','Colombian music entertainment','Música colombiana (vallenato, cumbia, salsa)','flat',800,false,{}],
['italian_music','Italian music entertainment','Música italiana','flat',800,false,{}],
['sax','Saxophonist','Saxofonista','flat',500,false,{}],
['violin','Violinist','Violinista','flat',450,false,{}],
['dance_floor','Dance floor rental','Alquiler de pista de baile','flat',600,false,{tax:true}],
['uplighting','Uplighting','Iluminación ambiental (uplighting)','flat',350,false,{tax:true}],
['audio_equipment','Audio equipment','Equipo de sonido','flat',400,false,{tax:true}],
['wireless_mics','Wireless microphones','Micrófonos inalámbricos','perItem',50,false,{qty:2}],
['photo_booth','Photo booth','Cabina de fotos','flat',800,false,{}],
['ent_overtime','Entertainment overtime','Horas extra de entretenimiento','perHour',200,false,{qty:1}]
]},
{id:'attire', letter:'G', en:'Wedding Attire & Beauty', es:'Vestuario y belleza', items:[
['dress','Wedding dress','Vestido de novia','flat',1200,true,{tax:true}],
['alterations','Dress alterations','Arreglos del vestido','flat',250,true,{}],
['bridal_shoes','Bridal shoes','Zapatos de la novia','flat',150,false,{tax:true}],
['bridal_accessories','Bridal accessories','Accesorios de la novia','flat',150,false,{tax:true}],
['groom_suit','Groom suit or tuxedo','Traje o esmoquin del novio','flat',500,true,{tax:true}],
['groom_shoes','Groom shoes','Zapatos del novio','flat',120,false,{tax:true}],
['groom_accessories','Groom accessories','Accesorios del novio','flat',100,false,{tax:true}],
['hair','Hair styling','Peinado','flat',150,true,{}],
['makeup','Makeup','Maquillaje','flat',150,true,{}],
['trials','Hair and makeup trials','Pruebas de peinado y maquillaje','flat',200,false,{}],
['bm_hair','Bridesmaids’ hair','Peinado de las damas de honor','perPerson',90,false,{qty:2}],
['bm_makeup','Bridesmaids’ makeup','Maquillaje de las damas de honor','perPerson',90,false,{qty:2}],
['groom_grooming','Groom grooming','Arreglo del novio (barbería)','flat',60,false,{}],
['rings','Wedding rings','Argollas de matrimonio','flat',1200,false,{tax:true}],
['engraving','Ring engraving','Grabado de las argollas','flat',60,false,{}],
['jewelry','Jewelry','Joyería','flat',300,false,{tax:true}],
['preservation','Dry cleaning or preservation','Lavado en seco o preservación','flat',250,false,{}]
]},
{id:'stationery', letter:'H', en:'Invitations & Stationery', es:'Invitaciones y papelería', items:[
['save_dates','Save-the-date cards','Tarjetas “reserva la fecha”','perItem',3,false,{qty:20,tax:true}],
['invitations','Invitations','Invitaciones','perItem',6,false,{qty:20,tax:true,tip:{en:'Quantity = households, not guests.',es:'Cantidad = hogares, no invitados.'}}],
['digital_invites','Digital invitations','Invitaciones digitales','flat',50,false,{}],
['rsvp_site','RSVP website','Sitio web para confirmar asistencia (RSVP)','flat',60,false,{}],
['rsvp_cards','Printed RSVP cards','Tarjetas RSVP impresas','perItem',1.5,false,{qty:20,tax:true}],
['postage','Postage','Franqueo / estampillas','perItem',0.78,false,{qty:40}],
['programs','Wedding programs','Programas de la ceremonia','perGuest',2,false,{tax:true,group:'programs'}],
['bilingual_programs','Bilingual ceremony programs','Programas bilingües de la ceremonia','perGuest',3,true,{tax:true,group:'programs'}],
['seating_chart','Seating chart','Plano de mesas','flat',120,false,{tax:true}],
['place_cards','Place cards','Tarjetas de puesto','perGuest',2,false,{tax:true}],
['table_numbers','Table numbers','Números de mesa','perTable',8,false,{tax:true}],
['welcome_sign','Welcome sign','Letrero de bienvenida','flat',120,false,{tax:true}],
['menus','Menus','Menús impresos','perGuest',2.5,false,{tax:true}],
['thank_you','Thank-you cards','Tarjetas de agradecimiento','perItem',2,false,{qty:20,tax:true}],
['favors','Wedding favors','Recordatorios para invitados','perGuest',6,false,{tax:true}]
]},
{id:'planning', letter:'I', en:'Wedding Planning & Coordination', es:'Planeación y coordinación', items:[
['full_planner','Full-service wedding planner','Wedding planner de servicio completo','flat',4500,false,{group:'planner'}],
['partial_planner','Partial wedding planner','Wedding planner parcial','flat',2500,false,{group:'planner'}],
['day_of','Day-of coordinator','Coordinador(a) del día','flat',1000,true,{group:'planner'}],
['rehearsal_coord','Rehearsal coordinator','Coordinador(a) del ensayo','flat',300,false,{}],
['vendor_mgmt','Vendor management','Gestión de proveedores','flat',500,false,{}],
['setup_supervision','Event setup supervision','Supervisión del montaje','flat',300,false,{}],
['timeline','Wedding timeline preparation','Preparación del cronograma','flat',150,false,{}],
['bilingual_coord','Bilingual coordination','Coordinación bilingüe','flat',300,false,{}],
['assistant_coord','Assistant coordinator','Coordinador(a) asistente','flat',250,false,{}],
['planning_software','Planning software','Software de planeación','flat',50,false,{}],
['vendor_gratuities','Vendor gratuities','Propinas a proveedores','flat',400,false,{tip:{en:'Flat cash tips. If you use the Vendor gratuity % in Settings, leave this off to avoid double-counting.',es:'Propinas fijas en efectivo. Si usa la Propina % en Configuración, deje esto apagado para no contar doble.'}}]
]},
{id:'travel', letter:'J', en:'Transportation & Accommodation', es:'Transporte y alojamiento', items:[
['bridal_suite','Bridal suite','Suite nupcial','perNight',450,false,{qty:1,tax:true}],
['groom_suite','Groom suite','Suite del novio','perNight',300,false,{qty:1,tax:true}],
['couple_hotel','Couple’s hotel accommodations','Alojamiento de la pareja','perNight',350,false,{qty:1,tax:true}],
['guest_rooms','Guest hotel rooms','Habitaciones para invitados','perNight',220,false,{qty:10,tax:true,hotel:true,tip:{en:'Quantity = rooms. Counted only when the couple pays for guest rooms (Wedding profile).',es:'Cantidad = habitaciones. Solo cuenta cuando la pareja paga las habitaciones (Perfil de la boda).'}}],
['room_block','Hotel room block commitment','Compromiso de bloque de habitaciones','flat',0,false,{hotel:true,tip:{en:'Attrition / guaranteed-room exposure if guests do not fill the block.',es:'Riesgo por habitaciones garantizadas si los invitados no llenan el bloque.'}}],
['airport_transfers','Airport transfers','Traslados al aeropuerto','perItem',80,false,{qty:2}],
['shuttle','Guest shuttle','Transporte para invitados','perHour',150,false,{qty:4}],
['limo','Limousine','Limusina','flat',600,false,{}],
['luxury_vehicle','Luxury vehicle','Vehículo de lujo','flat',400,false,{}],
['parking','Parking','Parqueadero','perItem',20,false,{qty:10}],
['valet','Valet parking','Valet parking','perGuest',12,false,{}],
['vendor_transport','Vendor transportation','Transporte de proveedores','flat',150,false,{}],
['travel_exp','Travel expenses','Gastos de viaje','flat',500,false,{}]
]},
{id:'legal', letter:'K', en:'Legal & Administrative', es:'Legal y administrativo', items:[
['marriage_license','Marriage license','Licencia de matrimonio','flat',86,true,{tip:{en:'Florida fee is about $86 ($61 with a premarital course certificate). Verify with the Clerk of Court.',es:'En Florida cuesta cerca de $86 ($61 con certificado de curso prematrimonial). Verifique con el Clerk of Court.'}}],
['cert_copies','Certified marriage certificate copies','Copias certificadas del acta de matrimonio','perItem',10,true,{qty:3}],
['officiant_filing','Officiant filing fee','Tarifa de registro del oficiante','flat',25,false,{}],
['name_change','Name change expenses','Gastos de cambio de nombre','flat',150,false,{}],
['doc_translation','Document translation','Traducción de documentos','perItem',45,false,{qty:2,tip:{en:'Certified translations of Colombian documents (cédula, birth certificate) if required.',es:'Traducciones certificadas de documentos colombianos (cédula, registro civil) si se requieren.'}}],
['notary','Notary expenses','Gastos de notaría','perItem',10,false,{qty:2}],
['intl_docs','International document expenses','Gastos de documentos internacionales (apostilla)','flat',200,false,{}],
['wedding_insurance','Wedding insurance','Seguro de boda','flat',275,false,{group:'insurance'}]
]},
{id:'cultural', letter:'L', en:'Special Experiences & Cultural Traditions', es:'Experiencias especiales y tradiciones culturales', items:[
['bilingual_ceremony','Bilingual English/Spanish ceremony','Ceremonia bilingüe inglés/español','flat',200,false,{tip:{en:'Extra for scripts, translation and rehearsal beyond the officiant fee.',es:'Costo extra por guiones, traducción y ensayo, además del oficiante.'}}],
['italian_traditions','Italian cultural traditions','Tradiciones italianas','flat',300,false,{}],
['colombian_traditions','Colombian cultural traditions','Tradiciones colombianas','flat',300,false,{}],
['hora_loca','Colombian hora loca entertainment','Hora loca','flat',900,false,{tip:{en:'Costumed performers, props, confetti and high-energy music for the late reception.',es:'Animadores disfrazados, accesorios, confeti y música de alta energía para el cierre de la fiesta.'}}],
['special_dances','Special dances','Bailes especiales','flat',200,false,{}],
['cultural_musicians','Cultural musicians','Músicos tradicionales','flat',700,false,{}],
['welcome_party','Welcome party','Fiesta de bienvenida','perGuest',35,false,{tax:true}],
['rehearsal_dinner','Rehearsal dinner','Cena de ensayo','perPerson',55,false,{qty:12,tax:true}],
['farewell_brunch','Farewell brunch','Brunch de despedida','perGuest',30,false,{tax:true}],
['welcome_bags','Welcome bags','Bolsas de bienvenida','perItem',25,false,{qty:15,tax:true}],
['personalized_gifts','Personalized gifts','Regalos personalizados','perItem',40,false,{qty:4,tax:true}],
['family_gifts','Family gifts','Regalos para la familia','perItem',75,false,{qty:4,tax:true}],
['special_experiences','Special guest experiences','Experiencias especiales para invitados','flat',500,false,{}]
]},
{id:'misc', letter:'M', en:'Miscellaneous & Contingency', es:'Varios y contingencia', items:[
['emergency_kit','Emergency supplies','Kit de emergencia','flat',100,false,{tax:true}],
['weather_contingency','Weather contingency','Contingencia por clima','flat',300,false,{}],
['last_minute','Last-minute vendor charges','Cargos de último momento','flat',300,false,{}],
['vendor_overtime','Vendor overtime','Horas extra de proveedores','perHour',250,false,{qty:1}],
['additional_rentals','Additional rentals','Alquileres adicionales','flat',300,false,{tax:true}],
['unplanned','Unplanned expenses','Gastos imprevistos','flat',500,false,{}],
['contingency_line','General contingency reserve','Reserva general de contingencia','flat',750,false,{tip:{en:'A flat reserve. The Contingency % in Settings already adds a reserve; use one or the other.',es:'Reserva fija. La Contingencia % de Configuración ya agrega una reserva; use una u otra.'}}]
]}
];
/* Default checklist (bilingual). who: bride | groom | both | planner */
var CHECKLIST_DEFAULTS = [
['confirm_date','Confirm wedding date','Confirmar la fecha de la boda','both'],
['confirm_venue','Confirm venue','Confirmar el lugar','both'],
['sign_venue','Sign venue contract','Firmar el contrato del lugar','both'],
['venue_deposit','Pay venue deposit','Pagar el depósito del lugar','groom'],
['guest_list','Finalize guest list','Cerrar la lista de invitados','both'],
['hire_planner','Hire wedding planner','Contratar wedding planner','both'],
['book_officiant','Book officiant','Reservar el oficiante','both'],
['license','Apply for marriage license','Solicitar la licencia de matrimonio','both'],
['book_photo','Book photographer','Reservar fotógrafo','bride'],
['book_video','Book videographer','Reservar videógrafo','bride'],
['catering','Select catering','Elegir el catering','both'],
['bar_pkg','Select bar package','Elegir el paquete de bar','groom'],
['cake_choose','Choose wedding cake','Elegir la torta de bodas','bride'],
['florist','Book florist','Reservar florista','bride'],
['music','Book DJ or musicians','Reservar DJ o músicos','groom'],
['attire','Purchase wedding attire','Comprar el vestuario de la boda','both'],
['beauty','Arrange hair and makeup','Reservar peinado y maquillaje','bride'],
['order_invites','Order invitations','Encargar las invitaciones','bride'],
['send_invites','Send invitations','Enviar las invitaciones','both'],
['rsvps','Track RSVPs','Hacer seguimiento a las confirmaciones (RSVP)','planner'],
['accommodations','Arrange accommodations','Organizar el alojamiento','groom'],
['vows','Prepare ceremony vows','Preparar los votos','both'],
['bilingual','Prepare bilingual ceremony','Preparar la ceremonia bilingüe','both'],
['seating','Create seating chart','Crear el plano de mesas','both'],
['menu','Finalize menu','Definir el menú final','both'],
['payments','Confirm vendor payments','Confirmar los pagos a proveedores','groom'],
['timeline','Confirm wedding timeline','Confirmar el cronograma de la boda','planner'],
['walkthrough','Complete final venue walkthrough','Hacer el recorrido final del lugar','both']
];
/* Lookup maps built from the definitions */
var ITEM_DEF = {}, CAT_DEF = {};
CATS.forEach(function(c){ CAT_DEF[c.id]=c; c.items.forEach(function(d){ ITEM_DEF[d[0]]={cat:c.id,en:d[1],es:d[2],model:d[3],price:d[4],enabled:d[5],opts:d[6]||{}}; }); });
var CHECK_DEF = {}; CHECKLIST_DEFAULTS.forEach(function(d){ CHECK_DEF[d[0]]={en:d[1],es:d[2],who:d[3]}; });
/* Default state factory */
function defaultState(){
return {
v:BWC_VERSION,
profile:{ couple:'', date:'2026-12-01', dateTbd:true, location:'Clearwater Beach, Florida', venue:'Clearwater Beach Marriott Resort on Sand Key', style:'boutique',
adults:30, children:0, vendorMeals:3, party:4, target:1500000, currency:'USD', ceremonyType:'outdoor', receptionType:'seated', events:1, hotelNights:2, hotelPayer:'guests', seatsPerTable:8 },
rates:{ tax:7, venueSvc:0, cateringSvc:24, beverageSvc:24, gratuity:0, gratIncluded:true, admin:0, contingency:5, taxOnSvc:true },
categories: CATS.map(function(c){ return { id:c.id, items:c.items.map(function(d){ var o=d[6]||{}; return { id:d[0], enabled:!!d[5], model:d[3], qty:o.qty!=null?o.qty:1, price:Math.round(d[4]*100), vendor:'', notes:'', taxable:!!o.tax, svc:o.svc||'none', pctOf:o.pct||'all', deposit:0, paid:0 }; }) }; }),
checklist: CHECKLIST_DEFAULTS.map(function(d){ return { id:d[0], done:false, due:'', who:d[3], notes:'' }; }),
vendors: [],
scenario:{ guestsA:20, guestsB:30, premiumFactor:1.6 }
};
}
/* ------------------------------------------------------------
3. SHARED HELPERS
------------------------------------------------------------ */
function esc(s){ return String(s==null?'':s).replace(/[&"']/g,function(c){ return {'&':'&','':'>','"':'"',"'":'''}[c]; }); }
function num(v,min,max){ var n=parseFloat(v); if(!isFinite(n)) n=0; if(min!=null&&nmax) n=max; return n; }
function intv(v,min,max){ return Math.round(num(v,min,max)); }
function toCents(v){ return Math.round(num(v,0)*100); }
function fromCents(c){ return (num(c)/100).toFixed(2); }
function clone(o){ return JSON.parse(JSON.stringify(o)); }
function tget(lang,k){ var d=T[lang]||T.en; var v=d[k]; if(v==null) v=T.en[k]; return v==null?k:v; }
function uid(prefix){ return prefix+'_'+Date.now().toString(36)+'_'+Math.random().toString(36).slice(2,7); }
function isOneOf(v,list){ return list.indexOf(v)>=0; }
function str(v,max){ return typeof v==='string'? v.slice(0,max||300) : ''; }
function todayISO(){ var d=new Date(); return d.getFullYear()+'-'+('0'+(d.getMonth()+1)).slice(-2)+'-'+('0'+d.getDate()).slice(-2); }
/* Validate & merge any untrusted object (storage or import) into a clean state */
function hydrate(raw){
var d=defaultState();
if(!raw||typeof raw!=='object') return d;
var p=raw.profile||{}, dp=d.profile;
['couple','location','venue'].forEach(function(k){ if(typeof p[k]==='string') dp[k]=str(p[k],200); });
if(typeof p.date==='string'&&/^\d{4}-\d{2}-\d{2}$/.test(p.date)) dp.date=p.date; else if(p.date==='') dp.date='';
if(typeof p.dateTbd==='boolean') dp.dateTbd=p.dateTbd;
if(isOneOf(p.style,['elopement','intimate','boutique','traditional','luxury','destination'])) dp.style=p.style;
['adults','children','vendorMeals','party','events','hotelNights','seatsPerTable'].forEach(function(k){ if(p[k]!=null&&isFinite(p[k])) dp[k]=intv(p[k],0,100000); });
if(dp.seatsPerTable0?Math.ceil(guests/p.seatsPerTable):0;
switch(model){
case 'perGuest': return {mult:guests,multKey:'m_guests',qtyEditable:false};
case 'perAdult': return {mult:p.adults,multKey:'m_adults',qtyEditable:false};
case 'perChild': return {mult:p.children,multKey:'m_children',qtyEditable:false};
case 'perVendorMeal': return {mult:p.vendorMeals,multKey:'m_vendors',qtyEditable:false};
case 'perTable': return {mult:tables,multKey:'m_tables',qtyEditable:false};
case 'perNight': return {mult:p.hotelNights,multKey:'m_nights',qtyEditable:true,qtyKey:'q_rooms'};
case 'perHour': return {mult:1,qtyEditable:true,qtyKey:'q_hours'};
case 'perDay': return {mult:1,qtyEditable:true,qtyKey:'q_days'};
case 'perItem': return {mult:1,qtyEditable:true,qtyKey:'q_items'};
case 'perPerson': return {mult:1,qtyEditable:true,qtyKey:'q_people'};
case 'custom': return {mult:1,qtyEditable:true,qtyKey:'q_qty'};
case 'percent': return {mult:1,qtyEditable:false,isPercent:true};
default: return {mult:1,qtyEditable:false};
}
}
function calc(st){
var p=st.profile, r=st.rates, guests=p.adults+p.children;
var res={items:{},cats:{},subtotal:0,svc:{venue:0,catering:0,beverage:0},svcTotal:0,gratuity:0,admin:0,tax:0,contingency:0,total:0,deposits:0,paid:0,balance:0,guests:guests,count:0,target:p.target,perGuest:0,remaining:0,pct:0};
var base1={}, allBase=0;
st.categories.forEach(function(cat){ var cs=0; cat.items.forEach(function(it){ var inc=isIncluded(it,st), mi=modelInfo(it.model,st), sub=0; if(inc&&!mi.isPercent){ sub=Math.round(mi.mult*num(it.qty,0)*num(it.price,0)); cs+=sub; } res.items[it.id]={sub:sub,included:inc,svc:0,tax:0,grat:0,deposit:0,paid:0,balance:0}; }); base1[cat.id]=cs; allBase+=cs; });
st.categories.forEach(function(cat){ var cs=base1[cat.id], n=0; cat.items.forEach(function(it){ var ri=res.items[it.id]; if(ri.included&&it.model==='percent'){ var b=(it.pctOf&&it.pctOf!=='all'&&base1[it.pctOf]!=null)?base1[it.pctOf]:allBase; ri.sub=Math.round(b*num(it.price,0)/10000); cs+=ri.sub; } if(ri.included) n++; }); res.cats[cat.id]={sub:cs,count:n}; });
st.categories.forEach(function(cat){ cat.items.forEach(function(it){ var ri=res.items[it.id]; if(!ri.included) return; res.count++; res.subtotal+=ri.sub;
var rate=it.svc==='venue'?num(r.venueSvc,0):it.svc==='catering'?num(r.cateringSvc,0):it.svc==='beverage'?num(r.beverageSvc,0):0;
if(it.svc!=='none'&&SVC_TYPES.indexOf(it.svc)>0){ ri.svc=Math.round(ri.sub*rate/100); res.svc[it.svc]+=ri.svc; if(!r.gratIncluded&&num(r.gratuity,0)>0){ ri.grat=Math.round(ri.sub*num(r.gratuity,0)/100); res.gratuity+=ri.grat; } }
if(it.taxable){ ri.tax=Math.round((ri.sub+(r.taxOnSvc?ri.svc:0))*num(r.tax,0)/100); res.tax+=ri.tax; }
ri.deposit=num(it.deposit,0); ri.paid=num(it.paid,0); ri.balance=ri.sub-ri.paid; res.deposits+=ri.deposit; res.paid+=ri.paid; }); });
res.svcTotal=res.svc.venue+res.svc.catering+res.svc.beverage;
res.admin=Math.round(res.subtotal*num(r.admin,0)/100);
var pre=res.subtotal+res.svcTotal+res.gratuity+res.admin+res.tax;
res.contingency=Math.round(pre*num(r.contingency,0)/100);
res.total=pre+res.contingency;
res.balance=res.total-res.paid;
res.perGuest=guests>0?Math.round(res.total/guests):0;
res.remaining=p.target-res.total;
res.pct=p.target>0?res.total/p.target*100:(res.total>0?999:0);
return res;
}
/* ------------------------------------------------------------
5. ONE CALCULATOR INSTANCE
------------------------------------------------------------ */
function createCalculator(root){
var storageKey=root.getAttribute('data-bwc-key')||'bwc-default';
var LANG_KEY=storageKey+':lang';
var lang='en', state, ui={tab:'overview',openCats:{venue:true,food:true},openItems:{},saveTimer:null,storageWarned:false,toastTimer:null};
var TABS=[['overview','tab_overview'],['budget','tab_budget'],['compare','tab_compare'],['checklist','tab_checklist'],['vendors','tab_vendors'],['settings','tab_settings']];
var fmtCache={};
function tr(k){ return tget(lang,k); }
function fmt(cents){ var cur=state.profile.currency||'USD', loc=lang==='es'?'es-CO':'en-US', key=loc+cur, f=fmtCache[key];
if(!f){ try{ f=new Intl.NumberFormat(loc,{style:'currency',currency:cur,minimumFractionDigits:2,maximumFractionDigits:2}); f.format(1); }catch(e){ f={format:function(n){ return cur+' '+n.toFixed(2); }}; } fmtCache[key]=f; }
return f.format(num(cents)/100); }
function fmtK(cents){ var cur=state.profile.currency||'USD', loc=lang==='es'?'es-CO':'en-US'; try{ return new Intl.NumberFormat(loc,{style:'currency',currency:cur,maximumFractionDigits:0}).format(num(cents)/100); }catch(e){ return cur+' '+Math.round(num(cents)/100); } }
function nameOf(it){ if(it.custom){ var n=it.name||{}; return n[lang]||n.en||n.es||tr('custom_name'); } var d=ITEM_DEF[it.id]; return d?(d[lang]||d.en):it.id; }
function catName(c){ var d=CAT_DEF[c.id]||c; return lang==='es'?d.es:d.en; }
function taskName(c){ if(c.custom){ var n=c.name||{}; return n[lang]||n.en||n.es||tr('cl_new'); } var d=CHECK_DEF[c.id]; return d?(lang==='es'?d.es:d.en):c.id; }
function storage(){ try{ var s=window.localStorage; s.setItem('__bwc_t','1'); s.removeItem('__bwc_t'); return s; }catch(e){ return null; } }
function loadLang(){ var s=storage(); if(s){ try{ var v=s.getItem(LANG_KEY); if(v==='es'||v==='en') return v; }catch(e){} } return 'en'; }
function loadState(){ var s=storage(); if(s){ try{ var raw=s.getItem(storageKey); if(raw){ var obj=JSON.parse(raw); if(obj&&obj.app==='bwc') return hydrate(obj.state); } }catch(e){} } return defaultState(); }
function save(){ var s=storage(); if(!s){ warnStorage(); return false; } try{ s.setItem(storageKey,JSON.stringify({app:'bwc',v:BWC_VERSION,savedAt:new Date().toISOString(),state:state})); s.setItem(LANG_KEY,lang); return true; }catch(e){ warnStorage(); return false; } }
function warnStorage(){ if(!ui.storageWarned){ ui.storageWarned=true; toast(tr('storage_err'),true); } }
function scheduleSave(){ if(ui.saveTimer) clearTimeout(ui.saveTimer); ui.saveTimer=setTimeout(save,500); }
function toast(msg,isErr){ var el=root.querySelector('.bwc-toast'); if(!el) return; el.textContent=msg; el.className='bwc-toast bwc-show'+(isErr?' bwc-toast-err':''); if(ui.toastTimer) clearTimeout(ui.toastTimer); ui.toastTimer=setTimeout(function(){ el.className='bwc-toast'; },2600); }
function tipHtml(text){ return text?'?':''; }
function opt(v,label,sel){ return ''+esc(label)+''; }
/* ---------- status helpers ---------- */
function statusOf(res){ if(res.target0) return 'over'; if(res.pct>100) return 'over'; if(res.pct>=90) return 'near'; return 'under'; }
/* ---------- RENDER: shell ---------- */
function shell(){
var h='
';
return h;
}
function breakdownTable(res){
var rows=[[tr('subtotal'),res.subtotal]];
if(res.svc.venue) rows.push([tr('svc_venue_line')+' ('+state.rates.venueSvc+'%)',res.svc.venue]);
if(res.svc.catering) rows.push([tr('svc_cat_line')+' ('+state.rates.cateringSvc+'%)',res.svc.catering]);
if(res.svc.beverage) rows.push([tr('svc_bev_line')+' ('+state.rates.beverageSvc+'%)',res.svc.beverage]);
if(!res.svc.venue&&!res.svc.catering&&!res.svc.beverage) rows.push([tr('service_charges'),0]);
rows.push([tr('gratuities')+(state.rates.gratIncluded?'':' ('+state.rates.gratuity+'%)'),res.gratuity]);
rows.push([tr('admin_fees')+' ('+state.rates.admin+'%)',res.admin]);
rows.push([tr('taxes')+' ('+state.rates.tax+'%)',res.tax]);
rows.push([tr('contingency')+' ('+state.rates.contingency+'%)',res.contingency]);
var h='
'+rows.map(function(r){ return '
'+esc(r[0])+'
'+esc(fmt(r[1]))+'
'; }).join('');
h+='
'+esc(tr('total'))+'
'+esc(fmt(res.total))+'
';
h+='
'+esc(tr('payments'))+'
'+esc(tr('deposits_req'))+'
'+esc(fmt(res.deposits))+'
'+esc(tr('total_paid'))+'
'+esc(fmt(res.paid))+'
'+esc(tr('outstanding'))+'
'+esc(fmt(res.balance))+'
';
return h;
}
function donutHtml(res){
var parts=[]; state.categories.forEach(function(c,i){ var v=res.cats[c.id].sub; if(v>0) parts.push({name:catName(c),v:v,color:CAT_COLORS[i%CAT_COLORS.length]}); });
var total=parts.reduce(function(a,b){return a+b.v;},0);
if(!total) return '
'+esc(tr('no_costs'))+'
';
var R=70, C=2*Math.PI*R, off=0, svg='';
parts.forEach(function(pt){ var len=pt.v/total*C; svg+=''+esc(pt.name)+': '+esc(fmt(pt.v))+''; off+=len; });
svg+=''+esc(fmtK(res.subtotal))+''+esc(tr('subtotal').toUpperCase())+'';
var legend='
';
}
/* ---------- RENDER: budget ---------- */
function fld(label,inner,cls){ return ''; }
function profileHtml(){
var p=state.profile, g=p.adults+p.children;
var h='
';
return h;
}
function scenarioResultsHtml(){
var cur=calc(state).total, h='';
scenarios().forEach(function(s){
var A=runScenario(s.a.mod), B=runScenario(s.b.mod), d=B-A;
h+='