{"id":5163,"date":"2025-09-09T08:13:36","date_gmt":"2025-09-09T08:13:36","guid":{"rendered":"https:\/\/meiling412.com\/?page_id=5163"},"modified":"2025-09-10T03:11:29","modified_gmt":"2025-09-10T03:11:29","slug":"blessing-card-generator","status":"publish","type":"page","link":"https:\/\/meiling412.com\/ms\/blessing-card-generator\/","title":{"rendered":"Penjana Kad Ucapan"},"content":{"rendered":"\n<!doctype html>\n<html lang=\"zh-Hant\">\n<head>\n<meta charset=\"utf-8\" \/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" \/>\n<title>\u795d\u798f\u5361\u7522\u751f\u5668\uff08\u9023\u7e8c\u6bb5\u843d\u30fb\u4e00\u81f4\u532f\u51fa\u30fb\u9810\u8a2d\u80cc\u666f\uff09<\/title>\n\n<!-- Google Fonts\uff08\u542b\u601d\u6e90\u9ed1\u9ad4\u5bb6\u65cf\u5c0d\u61c9\uff1aNoto Sans\/Serif TC \u7b49\uff0c\u542b\u7c97\u9ad4\uff09 -->\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Noto+Sans+TC:wght@400;600;700&#038;family=Noto+Serif+TC:wght@400;600;700&#038;family=Zen+Kaku+Gothic+New:wght@400;700&#038;family=Zen+Maru+Gothic:wght@400;700&#038;family=Zen+Old+Mincho:wght@400;700&#038;family=Shippori+Mincho:wght@400;700&#038;family=Source+Sans+3:wght@400;700&#038;family=Source+Serif+4:wght@400;700&#038;display=swap\" rel=\"stylesheet\">\n\n<style>\n  :root{\n    --bg:#ffffff; --panel:#f7f7fb; --text:#111827; --muted:#6b7280; --brand:#7c3aed; --soft:#e5e7eb; --card:#ffffff;\n    --maxw:1180px;\n  }\n  *{box-sizing:border-box}\n  body{margin:0;background:var(--bg);color:var(--text);font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,\"Noto Sans TC\",\"PingFang TC\",\"Microsoft JhengHei\",sans-serif;line-height:1.6}\n  main{max-width:var(--maxw);margin:24px auto;padding:0 16px 96px}\n\n  h1{font-size:22px;margin:6px 0}\n  .card{background:var(--card);border:1px solid var(--soft);border-radius:14px;padding:16px;margin:16px 0;box-shadow:0 2px 10px rgba(17,24,39,.04)}\n  .row{display:flex;gap:12px;flex-wrap:wrap}\n  .col{flex:1 1 260px;min-width:260px}\n  label{font-size:13px;color:var(--muted);display:block;margin:10px 0 6px}\n  input,textarea,select{width:100%;padding:10px 12px;border:1px solid var(--soft);border-radius:10px;font:inherit;background:#fff;line-height:1.35}\n  textarea{min-height:120px;resize:vertical}\n  .btn{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--soft);background:#fff;padding:10px 14px;border-radius:10px;cursor:pointer}\n  .btn.primary{background:var(--brand);color:#fff;border-color:var(--brand)}\n  .btn.ghost{background:transparent}\n  .btn-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}\n  .muted{color:var(--muted);font-size:13px}\n  .sep{height:1px;background:var(--soft);opacity:.6;margin:16px 0}\n  .tag{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border:1px solid var(--soft);border-radius:999px;font-size:12px;cursor:pointer;background:#fff}\n\n  \/* \u5169\u6b04\uff1a\u5de6\u8a2d\u5b9a + \u53f3\u9810\u89bd\uff08\u56fa\u5b9a 320px\uff09 *\/\n  @media (min-width:1024px){\n    .layout-2col{display:grid;grid-template-columns: 1fr 340px;gap:20px;align-items:start}\n    .previewPanel{position:sticky;top:20px;align-self:start;justify-self:end;max-width:340px;width:100%}\n  }\n\n  .previewBox{background:var(--panel);border:1px dashed var(--soft);border-radius:12px;padding:8px}\n\n  \/* \u4e0d\u5f37\u8feb 100% \u5bec\uff0c\u4f9d\u50cf\u7d20\u6bd4\u4f8b\u5448\u73fe *\/\n  #cardCanvas{max-width:100%;height:auto;display:block;border-radius:12px}\n\n  \/* \u80cc\u666f\u7e2e\u5716 *\/\n  .thumbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(52px,1fr));gap:6px;margin-top:6px}\n  .thumb{border-radius:12px;border:1px solid var(--soft);overflow:hidden;cursor:pointer;position:relative;background:#fff}\n  .thumb canvas{display:block;width:100%;height:44px;object-fit:cover}\n  .thumb.active{outline:2px solid var(--brand)}\n  .thumbs-scroll{max-height:150px;overflow:auto;padding-right:2px}\n  .chip{font-size:10px;position:absolute;right:4px;bottom:4px;background:rgba(255,255,255,.92);border-radius:6px;padding:1px 4px;border:1px solid var(--soft)}\n\n  @media (max-width:1023px){\n    .layout-2col{display:block}\n    .previewPanel{position:static;max-width:none}\n    main{padding:0 12px 100px}\n    .card{padding:12px}\n    label{font-size:12px}\n    .btn{padding:10px 12px;font-size:14px}\n    .thumbs{grid-template-columns:repeat(auto-fit,minmax(50px,1fr));gap:4px}\n    .thumb canvas{height:40px}\n    .chip{font-size:9px}\n  }\n\n  \/* ===============================\n     \u8868\u55ae\u63a7\u4ef6\u300c\u6587\u5b57\u88ab\u58d3\u4e00\u534a\u300d\u4e4b\u8de8\u700f\u89bd\u5668\u4fee\u6b63\uff08\u91cd\u9ede\uff09\n     =============================== *\/\n  :root{ --ctrl-fs:16px; --ctrl-py:10px; }\n  input[type=\"text\"],\n  input[type=\"number\"],\n  input[type=\"search\"],\n  input[type=\"email\"],\n  input[type=\"url\"],\n  input[type=\"color\"],\n  select,\n  textarea,\n  button,\n  .btn {\n    font-size: var(--ctrl-fs) !important;\n    line-height: 1.35 !important;                  \/* \u95dc\u9375\uff1a\u907f\u514d 1.0 \u5c0e\u81f4\u88c1\u5207 *\/\n    padding: var(--ctrl-py) 12px !important;       \/* \u4e0a\u4e0b\u7559\u767d *\/\n    height: auto !important;\n    min-height: calc(1em + var(--ctrl-py) * 2 + 2px) !important;\n    -webkit-appearance: none !important;           \/* iOS \/ Safari *\/\n    appearance: none !important;\n    background-clip: padding-box !important;\n    overflow: visible !important;\n  }\n  select{ height:auto !important; }\n  :where(select, input, textarea){ line-height: normal !important; } \/* \u6709\u4e9b Reset \u6703\u628a line-height \u8a2d\u592a\u4f4e\uff0c\u9019\u88e1\u77ef\u6b63 *\/\n\n  \/* Firefox\/Edge \u5728\u67d0\u4e9b\u5b57\u9ad4\u5ea6\u91cf\u6703\u504f\u77ee\uff0c\u62c9\u9ad8\u4e9b *\/\n  @-moz-document url-prefix() {\n    select, input, textarea { line-height: 1.4 !important; }\n  }\n  \/* iOS Safari \u518d\u4fdd\u5b88\u4e00\u9ede *\/\n  @supports (-webkit-touch-callout: none) {\n    select, input, textarea { line-height: 1.5 !important; }\n  }\n\n  \/* \u7d71\u4e00\u8868\u55ae\u5b57\u9ad4\uff0c\u907f\u514d\u9047\u5230\u5ea6\u91cf\u6709\u554f\u984c\u7684 fallback \u5b57\u578b *\/\n  input, select, textarea {\n    font-family:\n      \"Noto Sans TC\",\"PingFang TC\",\"Microsoft JhengHei\",\n      system-ui,-apple-system,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif\n      !important;\n  }\n<\/style>\n<\/head>\n<body>\n<main>\n  <h1>\u795d\u798f\u5361\u7522\u751f\u5668\uff08\u9023\u7e8c\u6bb5\u843d\u30fb\u4e00\u81f4\u532f\u51fa\u30fb\u9810\u8a2d\u80cc\u666f\uff09<\/h1>\n\n  <section class=\"card\">\n    <div class=\"layout-2col\">\n      <!-- \u5de6\uff1a\u8a2d\u5b9a -->\n      <div class=\"col\">\n        <label for=\"blessName\">\u670b\u53cb\u540d\u5b57\uff08\u53ef\u7a7a\u767d\uff09<\/label>\n        <input id=\"blessName\" placeholder=\"\u4f8b\u5982\uff1a\u5c0f\u5b89 \/ Grace\" \/>\n\n        <div class=\"row\">\n          <div class=\"col\">\n            <label for=\"ratio\">\u9810\u89bd\u5c3a\u5bf8<\/label>\n            <select id=\"ratio\">\n              <option value=\"1\">\u6b63\u65b9\u5f62 1:1\uff08\u9810\u89bd\uff09<\/option>\n              <option value=\"916\">\u76f4\u5f0f 9:16\uff08\u9810\u89bd\uff09<\/option>\n              <option value=\"169\">\u6a6b\u5f0f 16:9\uff08\u9810\u89bd\uff09<\/option>\n              <option value=\"45\">\u76f4\u5f0f 4:5\uff08\u9810\u89bd\uff09<\/option>\n              <option value=\"34\">\u76f4\u5f0f 3:4\uff08\u9810\u89bd\uff09<\/option>\n            <\/select>\n          <\/div>\n          <div class=\"col\">\n            <label for=\"align\">\u5c0d\u9f4a<\/label>\n            <select id=\"align\"><option value=\"left\">\u9760\u5de6<\/option><option value=\"center\" selected>\u7f6e\u4e2d<\/option><\/select>\n          <\/div>\n        <\/div>\n\n        <div class=\"row\">\n          <div class=\"col\">\n            <label for=\"fontFamily\">\u5b57\u9ad4\uff08\u514d\u7248\u6b0a\uff0c\u542b\u601d\u6e90\u9ed1\u9ad4\u7c97\uff09<\/label>\n            <select id=\"fontFamily\">\n              <option value='\"Noto Sans TC\", \"Source Sans 3\", system-ui'>Noto Sans TC\uff08\u9ed1\u9ad4\uff0c\u652f\u63f4\u7c97\u9ad4\uff09<\/option>\n              <option value='\"Noto Serif TC\", \"Source Serif 4\", serif' selected>Noto Serif TC\uff08\u660e\u9ad4\uff09<\/option>\n              <option value='\"Zen Kaku Gothic New\", \"Noto Sans TC\", system-ui'>Zen Kaku Gothic New<\/option>\n              <option value='\"Zen Maru Gothic\", \"Noto Sans TC\", system-ui'>Zen Maru Gothic<\/option>\n              <option value='\"Zen Old Mincho\", \"Noto Serif TC\", serif'>Zen Old Mincho<\/option>\n              <option value='\"Shippori Mincho\", \"Noto Serif TC\", serif'>Shippori Mincho<\/option>\n              <option value='\"Source Sans 3\", \"Noto Sans TC\", system-ui'>Source Sans 3<\/option>\n              <option value='\"Source Serif 4\", \"Noto Serif TC\", serif'>Source Serif 4<\/option>\n            <\/select>\n          <\/div>\n          <div class=\"col\">\n            <label for=\"fontWeight\">\u5b57\u91cd<\/label>\n            <select id=\"fontWeight\">\n              <option value=\"400\">Regular 400<\/option>\n              <option value=\"600\">SemiBold 600<\/option>\n              <option value=\"700\" selected>Bold 700\uff08\u601d\u6e90\u9ed1\u9ad4\u7c97\uff09<\/option>\n            <\/select>\n          <\/div>\n        <\/div>\n\n        <div class=\"row\">\n          <div class=\"col\">\n            <label for=\"fontSize\">\u5b57\u7d1a\uff08px\uff09<\/label>\n            <input id=\"fontSize\" type=\"number\" min=\"18\" max=\"72\" value=\"28\" \/>\n          <\/div>\n          <div class=\"col\">\n            <label for=\"layoutMode\">\u6392\u7248\u65b9\u5f0f<\/label>\n            <select id=\"layoutMode\">\n              <option value=\"para\" selected>\u9023\u7e8c\u6bb5\u843d\uff08\u81ea\u7136\u63db\u884c\uff09<\/option>\n              <option value=\"lines\">\u9010\u884c\u63a7\u5236\uff08\u53ef\u504f\u79fb\uff09<\/option>\n            <\/select>\n          <\/div>\n        <\/div>\n\n        <!-- \u6587\u5b57\u984f\u8272 \/ \u5916\u6846 \/ \u4f4d\u7f6e -->\n        <div class=\"row\">\n          <div class=\"col\">\n            <label for=\"txtColor\">\u6587\u5b57\u984f\u8272<\/label>\n            <input id=\"txtColor\" type=\"color\" value=\"#111827\" \/>\n          <\/div>\n          <div class=\"col\">\n            <label for=\"strokeColor\">\u5916\u6846\u984f\u8272\uff08\u9078 #00000000 = \u95dc\u9589\uff09<\/label>\n            <input id=\"strokeColor\" type=\"color\" value=\"#00000000\" \/>\n          <\/div>\n          <div class=\"col\">\n            <label for=\"strokeWidth\">\u5916\u6846\u7c97\u7d30\uff08px\uff09<\/label>\n            <input id=\"strokeWidth\" type=\"number\" min=\"0\" max=\"12\" value=\"0\" \/>\n          <\/div>\n        <\/div>\n        <div class=\"row\">\n          <div class=\"col\">\n            <label for=\"boxWidth\">\u6587\u5b57\u6846\u5bec\u5ea6\uff08%\uff09<\/label>\n            <input id=\"boxWidth\" type=\"range\" min=\"50\" max=\"96\" value=\"86\" \/>\n          <\/div>\n          <div class=\"col\">\n            <label for=\"posX\">\u6c34\u5e73\u4f4d\u7f6e\uff08%\uff09<\/label>\n            <input id=\"posX\" type=\"range\" min=\"2\" max=\"98\" value=\"50\" \/>\n          <\/div>\n          <div class=\"col\">\n            <label for=\"posY\">\u5782\u76f4\u4f4d\u7f6e\uff08%\uff09<\/label>\n            <input id=\"posY\" type=\"range\" min=\"10\" max=\"90\" value=\"50\" \/>\n          <\/div>\n        <\/div>\n\n        <div class=\"sep\"><\/div>\n\n        <!-- \u9810\u8a2d\u795d\u798f\u8a9e -->\n        <label for=\"blessPreset\">\u9810\u8a2d\u795d\u798f\u8a9e\uff08\u5167\u5efa 60 \u689d \/ \u652f\u63f4\u532f\u5165\uff09<\/label>\n        <div class=\"row\" style=\"gap:8px;align-items:flex-end\">\n          <div class=\"col\" style=\"min-width:260px\"><select id=\"blessPreset\"><\/select><\/div>\n          <button class=\"btn\" id=\"btnUsePreset\">\u5957\u7528<\/button>\n          <button class=\"btn\" id=\"btnShufflePreset\">\u63db\u4e00\u689d<\/button>\n          <label class=\"btn ghost\" title=\"\u532f\u5165 JSON \u6216 CSV\uff08\u6b04\u4f4d\uff1acategory,text\uff09\">\n            <input type=\"file\" id=\"blessImport\" accept=\".json,.csv\" style=\"display:none\"> \u532f\u5165\u8a5e\u5eab\n          <\/label>\n        <\/div>\n\n        <label>\u76ee\u524d\u795d\u798f\u8a9e<\/label>\n        <div id=\"blessOut\" style=\"white-space:pre-wrap;border:1px dashed var(--soft);border-radius:10px;padding:10px 12px;\"><\/div>\n\n        <label for=\"customBless\">\u81ea\u8a02\u795d\u798f\u8a9e\uff08\u53ef\u9078\uff0c\u82e5\u586b\u5beb\u5c07\u512a\u5148\uff09<\/label>\n        <textarea id=\"customBless\" placeholder=\"\u5728\u9019\u88e1\u8f38\u5165\u4f60\u81ea\u5df1\u7684\u795d\u798f\u8a9e\uff1b\u82e5\u6709\u5167\u5bb9\u5c07\u512a\u5148\u4f7f\u7528\u3002\"><\/textarea>\n\n        <div class=\"sep\"><\/div>\n\n        <!-- \u532f\u51fa\u8a2d\u5b9a -->\n        <label>\u532f\u51fa\u683c\u5f0f\u8207\u5c3a\u5bf8<\/label>\n        <div class=\"row\" style=\"gap:8px\">\n          <div class=\"col\" style=\"min-width:220px\">\n            <div class=\"tag\"><input type=\"checkbox\" id=\"expJpg\" checked style=\"accent-color:#7c3aed\"> JPEG<\/div>\n            <div class=\"tag\"><input type=\"checkbox\" id=\"expPng\" style=\"accent-color:#7c3aed\"> PNG<\/div>\n            <div class=\"tag\"><input type=\"checkbox\" id=\"pngTransparent\" style=\"accent-color:#7c3aed\"> PNG \u900f\u660e\u80cc\u666f<\/div>\n          <\/div>\n          <div class=\"col\" style=\"min-width:280px\">\n            <div class=\"tag\"><input type=\"checkbox\" class=\"sizeChk\" value=\"1\" checked style=\"accent-color:#7c3aed\"> 1:1 1080<\/div>\n            <div class=\"tag\"><input type=\"checkbox\" class=\"sizeChk\" value=\"916\" style=\"accent-color:#7c3aed\"> 9:16 1080\u00d71920<\/div>\n            <div class=\"tag\"><input type=\"checkbox\" class=\"sizeChk\" value=\"169\" style=\"accent-color:#7c3aed\"> 16:9 1920\u00d71080<\/div>\n            <div class=\"tag\"><input type=\"checkbox\" class=\"sizeChk\" value=\"45\" style=\"accent-color:#7c3aed\"> 4:5 1080\u00d71350<\/div>\n            <div class=\"tag\"><input type=\"checkbox\" class=\"sizeChk\" value=\"34\" style=\"accent-color:#7c3aed\"> 3:4 1080\u00d71440<\/div>\n          <\/div>\n        <\/div>\n\n        <div class=\"btn-row\">\n          <button class=\"btn primary\" id=\"makeCard\">\u751f\u6210\u4e26\u4e0b\u8f09\uff08\u55ae\u4e00\u5c3a\u5bf8 \/ \u9075\u5faa\u9810\u89bd\uff09<\/button>\n          <button class=\"btn\" id=\"batchExport\">\u6279\u6b21\u4e0b\u8f09\uff08\u4f9d\u52fe\u9078\u5c3a\u5bf8\u8207\u683c\u5f0f\uff09<\/button>\n        <\/div>\n        <p class=\"muted\">\u63d0\u793a\uff1a\u82e5\u300c\u81ea\u8a02\u795d\u798f\u8a9e\u300d\u6709\u586b\uff0c\u6703\u512a\u5148\u4f7f\u7528\uff1b\u5426\u5247\u4f7f\u7528\u4e0a\u65b9\u9810\u8a2d\u795d\u798f\u8a9e\u3002<\/p>\n\n        <div class=\"sep\"><\/div>\n\n        <!-- LOGO \u758a\u52a0\uff08\u53ef\u9078\uff09 -->\n        <label>LOGO \u758a\u52a0\uff08\u53ef\u9078\uff09<\/label>\n        <div class=\"row\">\n          <div class=\"col\"><label class=\"btn\"><input type=\"file\" id=\"logoUpload\" accept=\"image\/*\" style=\"display:none\"> \u4e0a\u50b3 LOGO<\/label><\/div>\n          <div class=\"col\">\n            <label for=\"logoPos\">\u4f4d\u7f6e<\/label>\n            <select id=\"logoPos\">\n              <option value=\"br\">\u53f3\u4e0b<\/option><option value=\"tr\">\u53f3\u4e0a<\/option>\n              <option value=\"bl\">\u5de6\u4e0b<\/option><option value=\"tl\">\u5de6\u4e0a<\/option>\n            <\/select>\n          <\/div>\n        <\/div>\n        <div class=\"row\">\n          <div class=\"col\">\n            <label for=\"logoScale\">\u5927\u5c0f\uff08%\uff09<\/label>\n            <input type=\"range\" id=\"logoScale\" min=\"6\" max=\"30\" value=\"14\" \/>\n          <\/div>\n          <div class=\"col\">\n            <label for=\"logoAlpha\">\u900f\u660e\u5ea6<\/label>\n            <input type=\"range\" id=\"logoAlpha\" min=\"10\" max=\"100\" value=\"85\" \/>\n          <\/div>\n        <\/div>\n        <div class=\"btn-row\"><div class=\"tag\"><input type=\"checkbox\" id=\"logoToggle\" style=\"accent-color:#7c3aed\"> \u986f\u793a LOGO<\/div><\/div>\n      <\/div>\n\n      <!-- \u53f3\uff1a\u9810\u89bd + \u80cc\u666f -->\n      <div class=\"col previewPanel\">\n        <div class=\"previewBox\" style=\"margin-bottom:10px;\">\n          <canvas id=\"cardCanvas\" width=\"320\" height=\"320\"><\/canvas>\n        <\/div>\n        <p id=\"sizeHint\" class=\"muted\" style=\"margin:6px 0 12px\"><\/p>\n\n        <div class=\"btn-row\">\n          <label class=\"btn\"><input type=\"file\" id=\"bgUpload\" accept=\"image\/*\" style=\"display:none\"> \u4e0a\u50b3\u81ea\u5099\u5716\u7247<\/label>\n          <button class=\"btn\" id=\"bgUrlBtn\">\u8cbc\u4e0a\u80cc\u666f\u5716\u7247\u7db2\u5740\uff08\u5a92\u9ad4\u5eab\u9023\u7d50\u4e5f\u53ef\uff09<\/button>\n          <button class=\"btn\" id=\"bgGradient\">\u4f7f\u7528\u6f38\u5c64<\/button>\n          <button class=\"btn\" id=\"clearBg\">\u6e05\u9664\u80cc\u666f<\/button>\n          <button class=\"btn ghost\" id=\"favBg\">\u52a0\u5165\u5e38\u7528<\/button>\n        <\/div>\n\n        <label style=\"margin-top:10px\">\u9078\u64c7\u80cc\u666f\uff08\u514d\u7248\u6b0a &#038; \u7a0b\u5f0f\u751f\u6210\uff09<\/label>\n        <div class=\"thumbs thumbs-scroll\" id=\"bgThumbs\"><\/div>\n\n        <div class=\"btn-row\" id=\"favWrap\" style=\"display:none\"><span class=\"muted\">\u5e38\u7528\u6a23\u5f0f\uff1a<\/span><\/div>\n      <\/div>\n    <\/div>\n  <\/section>\n<\/main>\n\n<script>\n\/* ========== DOM \u5feb\u6377 ========== *\/\nconst $=(s,r=document)=>r.querySelector(s);\nconst $$=(s,r=document)=>[...r.querySelectorAll(s)];\nconst canvas = $('#cardCanvas'), ctx = canvas.getContext('2d');\n\nlet currentBG={kind:'img',url:'https:\/\/meiling412.com\/wp-content\/uploads\/2025\/09\/ChatGPT-Image-2025\u5e748\u670827\u65e5-11_22_41.webp'}; \/\/ \u9810\u8a2d\u5c0f\u5973\u5b69\u80cc\u666f\nlet uploadedURL=null, logoURL=null, logoImg=null;\n\n\/* ========== \u63a7\u5236 ========== *\/\nconst ctrl={\n  get name(){ return ($('#blessName').value.trim()||'\u89aa\u611b\u7684\u670b\u53cb'); },\n  get baseTxt(){ const c=($('#customBless').value||'').trim(); if(c) return c; const b=($('#blessOut').innerText||'').trim(); return b.replace(\/\\{name\\}\/g,ctrl.name); },\n  get textColor(){ return $('#txtColor').value||'#111827'; },\n  get strokeColor(){ return $('#strokeColor').value||'#00000000'; },\n  get strokeWidth(){ return Math.max(0,+$('#strokeWidth').value||0); },\n  get boxWidth(){ return (+$('#boxWidth').value||86)\/100; },\n  get posX(){ return (+$('#posX').value||50)\/100; },\n  get posY(){ return (+$('#posY').value||50)\/100; },\n  get align(){ return $('#align').value||'center'; },\n  get fontSize(){ return Math.max(18,Math.min(72,parseInt($('#fontSize').value||28))); },\n  get fontFamily(){ return $('#fontFamily').value||'\"Noto Serif TC\"'; },\n  get fontWeight(){ return $('#fontWeight').value||'700'; },\n  get layoutMode(){ return $('#layoutMode').value||'para'; },\n};\n\n\/* ========== \u795d\u798f\u8a9e\u5eab\uff0860 \u689d\uff09 ========== *\/\nlet PRESET_BLESS={\n  '\u6eab\u67d4':[\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4e3b\u7684\u624b\u4ee5\u6eab\u67d4\u8207\u771f\u5be6\u74b0\u7e5e\u4f60\uff1b\u7576\u5fc3\u4e2d\u8d77\u98a8\u6642\uff0c\u7942\u505a\u4f60\u7a69\u56fa\u7684\u6e2f\u7063\uff0c\u4f7f\u4f60\u5728\u55a7\u56b7\u88e1\u4e5f\u80fd\u807d\u898b\u7942\u7d30\u8a9e\u7684\u5b89\u6170\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u4e3b\u77e5\u9053\u4f60\u7684\u9700\u8981\uff0c\u4e5f\u660e\u767d\u4f60\u7684\u8edf\u5f31\uff1b\u7942\u4e0d\u50ac\u4fc3\u4f60\uff0c\u53ea\u6eab\u67d4\u5730\u727d\u4f60\u7684\u624b\uff0c\u966a\u4f60\u8d70\u5b8c\u6bcf\u4e00\u6b65\u3002\",\n    \"{name}\uff0c\u9858\u4e3b\u7528\u5149\u7167\u4eae\u4f60\u524d\u9762\u7684\u8def\uff0c\u7528\u5e73\u5b89\u8986\u84cb\u4f60\u88e1\u9762\u7684\u5fc3\uff1b\u4e00\u65e9\u4e00\u665a\uff0c\u90fd\u88ab\u7942\u7684\u6148\u611b\u53eb\u9192\u3001\u5b89\u7761\u3002\",\n    \"\u81f4 {name}\uff1a\u9858\u4f60\u5728\u7e41\u5fd9\u4e4b\u9593\uff0c\u4ecd\u80fd\u88ab\u6eab\u67d4\u64c1\u62b1\uff1b\u5728\u4e0d\u5b89\u4e4b\u8655\uff0c\u4ecd\u5f97\u5b89\u975c\u8207\u4fe1\u5fc3\u3002\",\n    \"{name}\uff0c\u9858\u4e3b\u8cdc\u4f60\u8010\u5fc3\uff0c\u8b93\u4f60\u4e0d\u88ab\u6bd4\u8f03\u62c9\u626f\uff1b\u8cdc\u4f60\u559c\u6a02\uff0c\u8b93\u4f60\u8a18\u5f97\u81ea\u5df1\u88ab\u611b\u3001\u88ab\u770b\u91cd\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u6069\u5178\u50cf\u7d30\u96e8\u843d\u5728\u4f60\u5fc3\u7530\uff1b\u4e0d\u6025\u4e0d\u8e81\uff0c\u537b\u4f7f\u842c\u7269\u6ecb\u9577\uff0c\u53eb\u4f60\u91cd\u65b0\u5f97\u529b\u3002\",\n    \"\u9858\u4e3b\u5b89\u7a69\u4f60\u7684\u7bc0\u594f\uff0c\u6eab\u67d4\u4f60\u7684\u773c\u795e\uff1b\u770b\u4eba\u770b\u5df1\uff0c\u90fd\u66f4\u50cf\u7942\u3002\u89aa\u611b\u7684 {name}\uff0c\u4f60\u5df2\u88ab\u611b\u5b8c\u5168\u64c1\u62b1\u3002\",\n    \"{name}\uff0c\u9858\u4f60\u88ab\u6eab\u67d4\u5c0d\u5f85\u3001\u4e5f\u5b78\u6703\u6eab\u67d4\u5f85\u4eba\uff1b\u5728\u771f\u7406\u4e2d\u5805\u5b9a\uff0c\u5728\u611b\u88e1\u67d4\u8edf\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4f60\u7684\u5fc3\u5728\u4e3b\u88e1\u88ab\u4fee\u5fa9\uff0c\u8a18\u5f97\u4f60\u4e0d\u662f\u5b64\u55ae\u7684\uff1b\u6bcf\u4e00\u500b\u5606\u606f\uff0c\u7942\u90fd\u807d\u898b\u3002\",\n    \"{name}\uff0c\u9858\u4e3b\u4f7f\u4f60\u5728\u8f49\u5f4e\u8655\u770b\u898b\u76fc\u671b\uff0c\u5728\u7b49\u5f85\u88e1\u6536\u6210\u8010\u5fc3\uff0c\u5728\u5fae\u5c0f\u88e1\u767c\u73fe\u69ae\u7f8e\u3002\"\n  ],\n  '\u9f13\u52f5':[\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4e3b\u8cdc\u4f60\u65b0\u7684\u52c7\u6c23\uff0c\u9762\u5c0d\u4eca\u5929\u7684\u6311\u6230\uff1b\u7942\u7684\u53f3\u624b\u8981\u6276\u6301\u4f60\uff0c\u53eb\u4f60\u4e0d\u81f3\u8dcc\u5012\u3002\",\n    \"{name}\uff0c\u5373\u4f7f\u8def\u7a0b\u9577\uff0c\u4ecd\u6709\u6069\u5178\u540c\u884c\uff1b\u9858\u4f60\u62ac\u982d\u770b\u898b\u6a19\u8a18\uff0c\u4f4e\u982d\u770b\u898b\u8173\u4e0b\u7684\u78ba\u64da\u3002\",\n    \"\u81f4 {name}\uff1a\u4e0d\u8981\u5c0f\u770b\u4f60\u6b63\u5728\u505a\u7684\u4e8b\uff0c\u4e3b\u7d00\u5ff5\u4f60\u6bcf\u4e00\u4efd\u5fe0\u5fc3\uff1b\u6536\u6210\u96d6\u672a\u5230\uff0c\u7a2e\u5b50\u5df2\u5728\u767c\u82bd\u3002\",\n    \"{name}\uff0c\u9858\u4e3b\u4f7f\u4f60\u5728\u632b\u6298\u4e2d\u4ecd\u80fd\u6602\u9996\uff0c\u5728\u8edf\u5f31\u6642\u4ecd\u80fd\u9760\u7942\u525b\u5f37\uff1b\u4e00\u6b65\u4e00\u6b65\uff0c\u7942\u8207\u4f60\u540c\u5728\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4f60\u7684\u79b1\u544a\u4e0d\u5f92\u7136\uff0c\u4f60\u7684\u773c\u6dda\u4e0d\u767d\u6d41\uff1b\u4e3b\u5fc5\u8981\u4ee5\u559c\u6a02\u4ee3\u66ff\u6182\u50b7\u3002\",\n    \"{name}\uff0c\u5225\u5bb3\u6015\u6162\uff1b\u53ea\u8981\u4e0d\u505c\u6b62\u3002\u4e3b\u5728\u4f60\u8eab\u4e0a\u505a\u7684\u65b0\u4e8b\uff0c\u6b63\u5728\u6084\u6084\u5c55\u958b\u3002\",\n    \"\u81f4 {name}\uff1a\u9858\u4f60\u6709\u6e05\u695a\u7684\u5fc3\u5fd7\u3001\u5805\u5b9a\u7684\u8173\u6b65\uff1b\u628a\u4e0d\u78ba\u5b9a\u4ea4\u7d66\u4e3b\uff0c\u628a\u52c7\u6562\u4ea4\u7d66\u4eca\u5929\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4e3b\u6559\u4f60\u6578\u7b97\u6069\u5178\uff0c\u770b\u898b\u7942\u5df2\u505a\u7684\u3001\u6b63\u505a\u7684\uff1b\u56e0\u800c\u5fc3\u88e1\u525b\u5f37\u800c\u4e0d\u7126\u616e\u3002\",\n    \"{name}\uff0c\u9858\u4f60\u5728\u95dc\u9580\u4e4b\u8655\u770b\u898b\u53e6\u4e00\u6247\u7a97\uff0c\u5728\u9650\u5236\u88e1\u627e\u5230\u65b0\u7684\u53ef\u80fd\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4f60\u7684\u4fe1\u5fc3\u50cf\u7a2e\u5b50\uff0c\u5373\u4f7f\u6700\u5c0f\uff0c\u4e5f\u80fd\u9577\u6210\u906e\u852d\u7684\u6a39\u3002\"\n  ],\n  '\u5b89\u6170':[\n    \"{name}\uff0c\u7576\u4f60\u89ba\u5f97\u529b\u6c23\u4e0d\u5920\uff0c\u9858\u4e3b\u6210\u70ba\u4f60\u7684\u529b\u91cf\uff1b\u7576\u4f60\u89ba\u5f97\u5b64\u55ae\uff0c\u9858\u7942\u7684\u540c\u5728\u6210\u70ba\u4f60\u7684\u6b4c\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u7942\u4e86\u89e3\u4f60\uff1b\u4f60\u6c92\u6709\u8d70\u932f\uff0c\u9019\u662f\u7942\u966a\u4f60\u7d93\u904e\u7684\u8def\u3002\u9858\u5b89\u6170\u5145\u6eff\u4f60\u5fc3\u3002\",\n    \"{name}\uff0c\u9858\u4e3b\u6309\u6642\u91ab\u6cbb\u8a18\u61b6\u4e2d\u7684\u50b7\uff1b\u4e0d\u518d\u75bc\u75db\uff0c\u53cd\u6210\u795d\u798f\uff0c\u53eb\u4f60\u8d70\u51fa\u4f86\u6642\u66f4\u50cf\u7942\u3002\",\n    \"\u81f4 {name}\uff1a\u82e5\u9ed1\u591c\u4e45\u4e00\u4e9b\uff0c\u5c31\u8b93\u661f\u5149\u66f4\u660e\u4eae\uff1b\u9858\u4f60\u5728\u591c\u88e1\u7d93\u6b77\u7942\u7684\u5b88\u671b\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4f60\u7684\u773c\u6dda\u6210\u70ba\u6f86\u704c\u7684\u6c34\uff0c\u53eb\u65b0\u751f\u7684\u76fc\u671b\u5728\u4f60\u88e1\u9762\u840c\u82bd\u3002\",\n    \"{name}\uff0c\u4f60\u88ab\u807d\u898b\u3001\u88ab\u77e5\u9053\u3001\u88ab\u73cd\u60dc\uff1b\u9858\u4e3b\u4ee5\u5e73\u5b89\u4fdd\u5b88\u4f60\u7684\u5fc3\u61f7\u610f\u5ff5\u3002\",\n    \"\u81f4 {name}\uff1a\u4e3b\u4e0d\u8981\u6c42\u4f60\u7acb\u523b\u525b\u5f37\uff0c\u53ea\u9080\u4f60\u9760\u8fd1\u7942\uff1b\u8b93\u7942\u62b1\u4f4f\u4f60\uff0c\u76f4\u5230\u4f60\u6709\u529b\u6c23\u3002\",\n    \"{name}\uff0c\u9858\u4f60\u5728\u8edf\u5f31\u88e1\u4ecd\u88ab\u5c0a\u69ae\uff0c\u5728\u7834\u788e\u4e2d\u4ecd\u88ab\u611b\uff1b\u4e3b\u7528\u6eab\u67d4\u4fee\u88dc\u4f60\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4f60\u653e\u4e0b\u904e\u5ea6\u7684\u82db\u8cac\uff0c\u9818\u53d7\u4f86\u81ea\u4e3b\u7684\u8d66\u514d\u8207\u81ea\u7531\uff1b\u4f60\u7684\u6545\u4e8b\u6703\u88ab\u91cd\u65b0\u66f8\u5beb\u3002\",\n    \"{name}\uff0c\u9858\u4e3b\u7528\u7942\u7684\u5e73\u5b89\uff0c\u53d6\u4ee3\u4f60\u5fc3\u4e2d\u7684\u60f6\u6050\uff1b\u53eb\u4f60\u4eca\u665a\u597d\u7720\uff0c\u660e\u65e9\u5f97\u529b\u3002\"\n  ],\n  '\u76fc\u671b':[\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4e3b\u628a\u65b0\u6b4c\u653e\u5728\u4f60\u53e3\u4e2d\uff0c\u628a\u65b0\u8def\u64fa\u5728\u4f60\u8173\u524d\uff1b\u904e\u53bb\u5df2\u904e\u53bb\uff0c\u65b0\u7684\u8981\u958b\u59cb\u4e86\u3002\",\n    \"\u81f4 {name}\uff1a\u9858\u4f60\u5728\u7b49\u5019\u4e2d\u4e0d\u5931\u53bb\u71b1\u60c5\uff0c\u5728\u8b19\u5351\u4e2d\u4e0d\u5931\u53bb\u5149\uff1b\u4e3b\u8207\u4f60\u540c\u5728\u3002\",\n    \"{name}\uff0c\u9858\u4f60\u7684\u5fc3\u5b9a\u5728\u4e3b\u88e1\uff0c\u4e0d\u56e0\u98a8\u6d6a\u6416\u52d5\uff1b\u7942\u7684\u6642\u9593\uff0c\u7e3d\u662f\u525b\u525b\u597d\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4f60\u770b\u898b\u4eca\u5929\u7684\u5c0f\u9032\u6b65\uff0c\u90fd\u662f\u660e\u5929\u7684\u57fa\u790e\uff1b\u8def\u96d6\u9060\uff0c\u6069\u5178\u66f4\u9060\u3002\",\n    \"\u81f4 {name}\uff1a\u9858\u4f60\u5728\u6bcf\u500b\u6e05\u6668\u88ab\u76fc\u671b\u559a\u9192\uff0c\u5728\u6bcf\u500b\u591c\u665a\u88ab\u5e73\u5b89\u64c1\u62b1\u3002\",\n    \"{name}\uff0c\u9858\u4e3b\u4f7f\u4f60\u5728\u8352\u5e74\u4ecd\u6709\u7b11\u5bb9\uff0c\u5728\u4e7e\u5730\u4ecd\u6709\u6cc9\u6e90\uff1b\u7942\u5fc5\u4f9b\u61c9\u4f60\u4e00\u5207\u6240\u9700\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4f60\u7684\u773c\u76ee\u88ab\u63d0\u5347\uff0c\u770b\u898b\u66f4\u9ad8\u7684\u5c71\u3001\u66f4\u5927\u7684\u795e\uff1b\u5fc3\u56e0\u6b64\u5f97\u5bec\u5ee3\u3002\",\n    \"\u81f4 {name}\uff1a\u9858\u4f60\u624b\u4e2d\u7684\u5c0f\u4e8b\uff0c\u56e0\u4e3b\u6210\u70ba\u5927\u4e8b\uff1b\u9858\u4f60\u5fc3\u88e1\u7684\u5fae\u5149\uff0c\u56e0\u4e3b\u6210\u70ba\u4eae\u5149\u3002\",\n    \"{name}\uff0c\u9858\u4f60\u7684\u660e\u5929\u6bd4\u4eca\u5929\u66f4\u81ea\u7531\uff0c\u6bd4\u6628\u5929\u66f4\u559c\u6a02\uff1b\u56e0\u70ba\u4e3b\u727d\u8457\u4f60\u8d70\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4f60\u52c7\u6562\u76fc\u671b\uff0c\u4e5f\u6eab\u67d4\u751f\u6d3b\uff1b\u9019\u5169\u6a23\u90fd\u5728\u4e3b\u88e1\u4e26\u5b58\u3001\u4e26\u7f8e\u597d\u3002\"\n  ],\n  '\u795d\u798f':[\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4e3b\u89aa\u81ea\u795d\u798f\u4f60\u6240\u505a\u7684\u5de5\u3001\u6240\u611b\u7684\u4eba\u3001\u6240\u76fc\u7684\u5922\uff1b\u5728\u7942\u88e1\u9762\uff0c\u51e1\u4e8b\u90fd\u6709\u8def\u3002\",\n    \"{name}\uff0c\u9858\u4f60\u51fa\u4e5f\u8499\u798f\u3001\u5165\u4e5f\u8499\u798f\uff1b\u5728\u7dd2\u7dd2\u7e41\u96dc\u88e1\u6709\u6b21\u5e8f\uff0c\u5728\u7d1b\u4e82\u8072\u97f3\u88e1\u61c2\u5206\u8fa8\u3002\",\n    \"\u81f4 {name}\uff1a\u9858\u4f60\u5e38\u5e38\u559c\u6a02\u3001\u4e0d\u4f4f\u79b1\u544a\u3001\u51e1\u4e8b\u8b1d\u6069\uff1b\u628a\u8ca0\u64d4\u4ea4\u7d66\u4e3b\uff0c\u628a\u5fae\u7b11\u7559\u7d66\u4e16\u754c\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4f60\u5728\u611b\u4e2d\u88ab\u5efa\u9020\uff0c\u5728\u771f\u7406\u4e2d\u88ab\u7a69\u56fa\uff0c\u5728\u76fc\u671b\u4e2d\u88ab\u6258\u4f4f\u3002\",\n    \"{name}\uff0c\u9858\u4e3b\u4f7f\u4f60\u624b\u6240\u505a\u7684\u3001\u53e3\u6240\u8aaa\u7684\u3001\u5fc3\u6240\u60f3\u7684\uff0c\u90fd\u8207\u7942\u7684\u5fc3\u610f\u76f8\u5408\uff0c\u4e26\u4e14\u4ea8\u901a\u3002\",\n    \"\u81f4 {name}\uff1a\u9858\u4f60\u8d8a\u4f86\u8d8a\u50cf\u8036\u7a4c\u2014\u2014\u6eab\u67d4\u3001\u5805\u5b9a\u3001\u771f\u5be6\uff1b\u5728\u4e16\u4e0a\u4f5c\u9e7d\u4f5c\u5149\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u4f60\u5b78\u6703\u5b89\u6b47\uff0c\u4e5f\u5b78\u6703\u8d77\u8eab\uff1b\u61c2\u5f97\u66ab\u505c\uff0c\u624d\u80fd\u8d70\u5f97\u66f4\u9060\u3002\",\n    \"{name}\uff0c\u9858\u4e3b\u628a\u5e73\u5b89\u653e\u5728\u4f60\u5bb6\u9580\u53e3\uff0c\u628a\u559c\u6a02\u653e\u9032\u4f60\u7684\u9910\u684c\uff0c\u628a\u6148\u611b\u653e\u5728\u4f60\u5011\u5f7c\u6b64\u773c\u4e2d\u3002\",\n    \"\u81f4 {name}\uff1a\u9858\u4f60\u64c1\u6709\u7d66\u4eba\u7684\u5fc3\uff0c\u4e5f\u6709\u88ab\u611b\u7684\u52c7\u6c23\uff1b\u5b78\u6703\u63a5\u7d0d\uff0c\u4e5f\u61c2\u5f97\u6210\u5168\u3002\",\n    \"\u89aa\u611b\u7684 {name}\uff0c\u9858\u6069\u5178\u8ffd\u8457\u4f60\u8dd1\uff0c\u76f4\u5230\u4f60\u7b11\u8457\u56de\u982d\u8aaa\uff1a\u4e3b\u771f\u597d\u3002\"\n  ]\n};\n\n\/* \u7522\u751f\u4e0b\u62c9\u9078\u55ae *\/\n(function buildBlessPreset(){\n  const sel=$('#blessPreset'); sel.innerHTML='';\n  Object.keys(PRESET_BLESS).forEach(cat=>{\n    const g=document.createElement('optgroup'); g.label=`\u3010${cat}\u3011`;\n    PRESET_BLESS[cat].forEach((t,i)=>{\n      const o=document.createElement('option');\n      o.value=JSON.stringify({cat,idx:i}); o.textContent=`${cat} - \u7b2c ${i+1} \u689d`;\n      g.appendChild(o);\n    });\n    sel.appendChild(g);\n  });\n  \/\/ \u9810\u8a2d\u5148\u653e\u7b2c\u4e00\u689d\n  $('#blessOut').innerText = PRESET_BLESS['\u6eab\u67d4'][0].replace(\/\\{name\\}\/g, ctrl.name);\n})();\n\n\/* \u795d\u798f\u9078\u55ae\u4e8b\u4ef6 *\/\n$('#btnUsePreset').addEventListener('click',()=>{\n  const sel=$('#blessPreset'); if(!sel.value) return;\n  const {cat,idx}=JSON.parse(sel.value);\n  $('#blessOut').innerText = PRESET_BLESS[cat][idx].replace(\/\\{name\\}\/g, ctrl.name);\n  drawCardPreview();\n});\n$('#btnShufflePreset').addEventListener('click',()=>{\n  const cats=Object.keys(PRESET_BLESS);\n  const cat=cats[Math.floor(Math.random()*cats.length)];\n  const idx=Math.floor(Math.random()*PRESET_BLESS[cat].length);\n  $('#blessOut').innerText=PRESET_BLESS[cat][idx].replace(\/\\{name\\}\/g, ctrl.name);\n  drawCardPreview();\n});\n$('#blessImport').addEventListener('change', async e=>{\n  const f=e.target.files?.[0]; if(!f) return; const txt=await f.text(); let rows=[];\n  if(f.name.endsWith('.json')){\n    rows=JSON.parse(txt).map(o=>({category:o.category||'\u532f\u5165',text:o.text||''}));\n  }else{\n    rows=txt.split(\/\\r?\\n\/).map(l=>l.trim()).filter(Boolean)\n      .map(l=>{const m=l.split(\/,(.+)\/);return {category:(m[0]||'\u532f\u5165').trim(),text:(m[1]||'').trim()};});\n  }\n  rows.forEach(r=>{ PRESET_BLESS[r.category] ||= []; PRESET_BLESS[r.category].push(r.text); });\n  (function rebuild(){\n    const sel=$('#blessPreset'); sel.innerHTML='';\n    Object.keys(PRESET_BLESS).forEach(cat=>{\n      const g=document.createElement('optgroup'); g.label=`\u3010${cat}\u3011`;\n      PRESET_BLESS[cat].forEach((t,i)=>{ const o=document.createElement('option'); o.value=JSON.stringify({cat,idx:i}); o.textContent=`${cat} - \u7b2c ${i+1} \u689d`; g.appendChild(o);});\n      sel.appendChild(g);\n    });\n  })();\n  alert('\u5df2\u532f\u5165\u795d\u798f\u8a9e ' + rows.length + ' \u7b46');\n});\n\n\/* ========== \u7a0b\u5f0f\u80cc\u666f ========== *\/\nconst PRESETS=[\n  {id:'grad',label:'\u67d4\u5149\u6f38\u5c64'},{id:'bokeh',label:'\u5922\u5e7b\u5149\u6591'},{id:'paper',label:'\u7d19\u5f35\u7d0b\u7406'},{id:'sunset',label:'\u65e5\u843d\u96f2\u5f69'},\n  {id:'sea',label:'\u6d77\u5929\u9060\u666f'},{id:'mount',label:'\u5c71\u5f71\u5c64\u758a'},{id:'fog',label:'\u6668\u9727\u8584\u9727'},{id:'gridsoft',label:'\u67d4\u548c\u7db2\u683c'},\n  {id:'beams',label:'\u5149\u675f\u659c\u5c04'},{id:'noise',label:'\u5674\u9727\u566a\u9ede'},{id:'fabric',label:'\u5e03\u6599\u8cea\u611f'},{id:'clouds',label:'\u68c9\u82b1\u96f2\u6735'},\n  {id:'ripple',label:'\u5713\u5f27\u6ce2\u7d0b'},{id:'glass',label:'\u73bb\u7483\u64ec\u614b'},{id:'stars',label:'\u661f\u5149\u5fae\u7c92'},{id:'rainbow',label:'\u5f69\u8679\u6688\u67d3'},\n  {id:'watercolor',label:'\u6c34\u5f69\u6688\u67d3'},{id:'chalk',label:'\u7c89\u7b46\u9ed1\u677f'},{id:'triangles',label:'\u5e7e\u4f55\u4e09\u89d2'},{id:'waves',label:'\u6ce2\u6d6a\u689d\u7d0b'}\n];\nfunction drawBackground(g,w,h,style){\n  if(style==='bokeh'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#f9fbff'); grd.addColorStop(1,'#e9e5ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    for(let i=0;i<18;i++){ g.fillStyle=`rgba(124,58,237,${0.05+Math.random()*0.08})`; const r=20+Math.random()*80, x=Math.random()*w, y=Math.random()*h; g.beginPath(); g.arc(x,y,r,0,Math.PI*2); g.fill();}\n  }else if(style==='paper'){\n    g.fillStyle='#faf9f5'; g.fillRect(0,0,w,h);\n    for(let i=0;i<400;i++){ g.fillStyle=`rgba(0,0,0,${Math.random()*0.03})`; g.fillRect(Math.random()*w,Math.random()*h,1,1); }\n    g.fillStyle='rgba(0,0,0,0.04)'; for(let y=0;y<h;y+=6){ g.fillRect(0,y,w,1); }\n  }else if(style==='sunset'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#ffd7a8'); grd.addColorStop(0.6,'#ffb3b3'); grd.addColorStop(1,'#b3c7ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n  }else if(style==='sea'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#dff0ff'); grd.addColorStop(0.55,'#a8d8ff'); grd.addColorStop(1,'#6fb3ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    g.fillStyle='rgba(255,255,255,0.4)'; for(let i=0;i<8;i++){ const y=h*0.5+i*20; g.fillRect(0,y,w,2); }\n  }else if(style==='mount'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#eaf2ff'); grd.addColorStop(1,'#cfe1ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    function ridge(y,color){ g.fillStyle=color; g.beginPath(); g.moveTo(0,y); for(let x=0;x<=w;x+=50){ g.lineTo(x, y-20-Math.random()*40); } g.lineTo(w,h); g.lineTo(0,h); g.closePath(); g.fill(); }\n    ridge(h*0.55,'#c3d4ef'); ridge(h*0.7,'#b2c6e6'); ridge(h*0.85,'#a1b6da');\n  }else if(style==='fog'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#eef6ff'); grd.addColorStop(1,'#dde7f6'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    for(let i=0;i<6;i++){ const y=h*0.2+i*90; const fog=g.createLinearGradient(0,y-40,0,y+40); fog.addColorStop(0,'rgba(255,255,255,0)'); fog.addColorStop(0.5,'rgba(255,255,255,0.7)'); fog.addColorStop(1,'rgba(255,255,255,0)'); g.fillStyle=fog; g.fillRect(0,y-40,w,80); }\n  }else if(style==='gridsoft'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#f7faff'); grd.addColorStop(1,'#ece8ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    g.strokeStyle='rgba(0,0,0,0.06)'; g.lineWidth=1; const s=28;\n    for(let y=s;y<h;y+=s){ g.beginPath(); g.moveTo(0,y); g.lineTo(w,y); g.stroke(); }\n    for(let x=s;x<w;x+=s){ g.beginPath(); g.moveTo(x,0); g.lineTo(x,h); g.stroke(); }\n  }else if(style==='beams'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#fff7f0'); grd.addColorStop(1,'#e7e4ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    g.globalAlpha=0.18;\n    for(let i=0;i<7;i++){ const x=Math.random()*w, y=-h*0.2, bw=60+Math.random()*140, ang=(Math.random()*0.5-0.25);\n      g.save(); g.translate(x,y); g.rotate(ang);\n      const lg=g.createLinearGradient(0,0,bw,h*1.4); lg.addColorStop(0,'rgba(255,255,255,0)'); lg.addColorStop(0.5,'rgba(255,255,255,1)'); lg.addColorStop(1,'rgba(255,255,255,0)');\n      g.fillStyle=lg; g.fillRect(-bw\/2,0,bw,h*1.4); g.restore();\n    } g.globalAlpha=1;\n  }else if(style==='noise'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#f9fafb'); grd.addColorStop(1,'#ebe8ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    const imgData=g.createImageData(w,h);\n    for(let i=0;i<imgData.data.length;i+=4){ const n=240+Math.random()*15; imgData.data[i]=n; imgData.data[i+1]=n; imgData.data[i+2]=n+5; imgData.data[i+3]=18; }\n    g.putImageData(imgData,0,0);\n  }else if(style==='fabric'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#f8fbff'); grd.addColorStop(1,'#eae6ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    g.strokeStyle='rgba(0,0,0,0.05)'; g.lineWidth=1;\n    for(let y=0;y<h;y+=3){ g.beginPath(); g.moveTo(0,y); g.lineTo(w,y); g.stroke(); }\n    for(let x=0;x<w;x+=3){ g.beginPath(); g.moveTo(x,0); g.lineTo(x,h); g.stroke(); }\n  }else if(style==='clouds'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#eaf6ff'); grd.addColorStop(1,'#f7efff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    g.fillStyle='rgba(255,255,255,0.85)'; for(let i=0;i<14;i++){ const r=30+Math.random()*70, x=Math.random()*w, y=Math.random()*h*0.6; g.beginPath(); g.arc(x,y,r,0,Math.PI*2); g.fill(); }\n  }else if(style==='ripple'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#f7fbff'); grd.addColorStop(1,'#e9e6ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    g.strokeStyle='rgba(124,58,237,0.10)'; g.lineWidth=2; const cx=w*0.3, cy=h*0.35;\n    for(let r=60;r<Math.max(w,h);r+=40){ g.beginPath(); g.arc(cx,cy,r,0,Math.PI*2); g.stroke(); }\n  }else if(style==='glass'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#eef3ff'); grd.addColorStop(1,'#e9e5ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    g.fillStyle='rgba(255,255,255,0.28)'; g.filter='blur(8px)'; g.fillRect(w*0.15,h*0.12,w*0.7,h*0.5); g.filter='none';\n    g.strokeStyle='rgba(255,255,255,0.6)'; g.lineWidth=1.5; g.strokeRect(w*0.15,h*0.12,w*0.7,h*0.5);\n  }else if(style==='stars'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#edf3ff'); grd.addColorStop(1,'#e7e5ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    for(let i=0;i<120;i++){ const x=Math.random()*w, y=Math.random()*h, s=Math.random()*1.6+0.2; g.fillStyle=`rgba(255,255,255,${0.4+Math.random()*0.6})`; g.fillRect(x,y,s,s); }\n  }else if(style==='rainbow'){\n    const grd=g.createLinearGradient(0,0,w,0);\n    grd.addColorStop(0.00,'#ffd1dc'); grd.addColorStop(0.25,'#ffe7a1'); grd.addColorStop(0.5,'#c9f7a1'); grd.addColorStop(0.75,'#a8e3ff'); grd.addColorStop(1,'#d5c7ff');\n    g.fillStyle=grd; g.fillRect(0,0,w,h); g.fillStyle='rgba(255,255,255,0.25)'; g.fillRect(0,0,w,h);\n  }else if(style==='watercolor'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#fdfbff'); grd.addColorStop(1,'#ece8ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    function blob(cx,cy,rx,ry,a){ g.save(); g.translate(cx,cy); g.rotate(a);\n      const grad=g.createRadialGradient(0,0,0,0,0,Math.max(rx,ry)); grad.addColorStop(0,'rgba(124,58,237,0.18)'); grad.addColorStop(1,'rgba(124,58,237,0)');\n      g.fillStyle=grad; g.beginPath(); g.ellipse(0,0,rx,ry,0,0,Math.PI*2); g.fill(); g.restore(); }\n    for(let i=0;i<6;i++){ blob(Math.random()*w,Math.random()*h,120+Math.random()*140,80+Math.random()*110,Math.random()*Math.PI); }\n  }else if(style==='chalk'){\n    g.fillStyle='#1c1c1c'; g.fillRect(0,0,w,h);\n    g.strokeStyle='rgba(255,255,255,0.08)'; for(let y=0;y<h;y+=4){ g.beginPath(); g.moveTo(0,y); g.lineTo(w,y); g.stroke(); }\n    for(let i=0;i<800;i++){ g.fillStyle=`rgba(255,255,255,${Math.random()*0.15})`; g.fillRect(Math.random()*w,Math.random()*h,1,1); }\n  }else if(style==='triangles'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#f6fbff'); grd.addColorStop(1,'#ece6ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    g.fillStyle='rgba(124,58,237,0.08)'; const s=80;\n    for(let y=0;y<h+s;y+=s){ for(let x=0;x<w+s;x+=s){ g.beginPath(); g.moveTo(x,y); g.lineTo(x+s,y); g.lineTo(x+s\/2,y+s*0.86); g.closePath(); g.fill(); } }\n  }else if(style==='waves'){\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#f6faff'); grd.addColorStop(1,'#ebe6ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n    g.strokeStyle='rgba(124,58,237,0.12)'; g.lineWidth=2;\n    for(let y=h*0.3;y<h;y+=26){ g.beginPath(); for(let x=0;x<=w;x+=12){ g.lineTo(x, y+Math.sin((x+y)\/40)*8); } g.stroke(); }\n  }else{ \/\/ default grad\n    const grd=g.createLinearGradient(0,0,0,h); grd.addColorStop(0,'#f8fafc'); grd.addColorStop(1,'#e9e5ff'); g.fillStyle=grd; g.fillRect(0,0,w,h);\n  }\n}\nconst thumbsBox=$('#bgThumbs');\nfunction addThumb(el,label,onclick){\n  el.className='thumb';\n  const chip=document.createElement('span'); chip.className='chip'; chip.textContent=label; el.appendChild(chip);\n  el.addEventListener('click',()=>{ $$('.thumb',thumbsBox).forEach(x=>x.classList.remove('active')); el.classList.add('active'); onclick(); drawCardPreview();});\n  thumbsBox.appendChild(el);\n}\nPRESETS.forEach(p=>{\n  const t=document.createElement('div'), c=document.createElement('canvas'); c.width=120; c.height=70;\n  const g=c.getContext('2d'); drawBackground(g,c.width,c.height,p.id); t.appendChild(c);\n  addThumb(t,p.label,()=>{ currentBG={kind:'proc',name:p.id}; });\n});\n\n\/* \u6536\u85cf\u5e38\u7528\u80cc\u666f *\/\nconst favKey='bgFavs';\nfunction renderFavs(){\n  const favs=JSON.parse(localStorage.getItem(favKey)||'[]');\n  const wrap=$('#favWrap'); wrap.innerHTML=''; if(!favs.length){ wrap.style.display='none'; return; }\n  wrap.style.display='flex'; const title=document.createElement('span'); title.className='muted'; title.textContent='\u5e38\u7528\u6a23\u5f0f\uff1a'; wrap.appendChild(title);\n  favs.forEach(id=>{ const b=document.createElement('button'); b.className='tag'; b.textContent=(PRESETS.find(p=>p.id===id)?.label||'\u81ea\u8a02'); b.onclick=()=>{ currentBG=(id.startsWith('url:'))?{kind:'img',url:id.slice(4)}:{kind:'proc',name:id}; drawCardPreview(); }; wrap.appendChild(b); });\n}\n$('#favBg').addEventListener('click',()=>{ \n  if(currentBG?.kind==='proc' && currentBG.name){ \n    let favs=JSON.parse(localStorage.getItem(favKey)||'[]'); if(!favs.includes(currentBG.name)) favs.push(currentBG.name); localStorage.setItem(favKey,JSON.stringify(favs)); \n  } else if(currentBG?.kind==='img' && currentBG.url){\n    let favs=JSON.parse(localStorage.getItem(favKey)||'[]'); const tag='url:'+currentBG.url; if(!favs.includes(tag)) favs.push(tag); localStorage.setItem(favKey,JSON.stringify(favs));\n  }\n  renderFavs(); \n});\nrenderFavs();\n\n\/* \u80cc\u666f\u6309\u9215 *\/\n$('#bgUpload').addEventListener('change',e=>{\n  const f=e.target.files?.[0]; if(!f) return;\n  if(uploadedURL) URL.revokeObjectURL(uploadedURL); uploadedURL=URL.createObjectURL(f);\n  currentBG={kind:'img',url:uploadedURL}; $$('.thumb',thumbsBox).forEach(x=>x.classList.remove('active')); drawCardPreview();\n});\n$('#bgGradient').addEventListener('click',()=>{ currentBG={kind:'proc',name:'grad'}; $$('.thumb',thumbsBox).forEach(x=>x.classList.remove('active')); drawCardPreview(); });\n$('#clearBg').addEventListener('click',()=>{ currentBG=null; $$('.thumb',thumbsBox).forEach(x=>x.classList.remove('active')); drawCardPreview(); });\n$('#bgUrlBtn').addEventListener('click',()=>{\n  const url=prompt('\u8acb\u8cbc\u4e0a\u80cc\u666f\u5716\u7247\u7db2\u5740\uff08\u540c\u7db2\u57df\u6700\u4f73\uff0c\u53ef\u7528\u5a92\u9ad4\u5eab\u7684\u6a94\u6848\u7db2\u5740\uff09\uff1a', currentBG?.kind==='img'? currentBG.url : '');\n  if(url){ currentBG={kind:'img',url:url}; drawCardPreview(); }\n});\n\n\/* LOGO *\/\n$('#logoUpload').addEventListener('change',e=>{\n  const f=e.target.files?.[0]; if(!f) return; if(logoURL) URL.revokeObjectURL(logoURL);\n  logoURL=URL.createObjectURL(f); logoImg=new Image(); logoImg.onload=()=>drawCardPreview(); logoImg.src=logoURL;\n});\nfunction drawLogo(g,w,h){\n  if(!$('#logoToggle').checked || !logoImg) return;\n  const scale=(+$('#logoScale').value)\/100, alpha=(+$('#logoAlpha').value)\/100, pos=$('#logoPos').value;\n  const short=Math.min(w,h), targetW=short*scale, r=targetW\/logoImg.width, dw=logoImg.width*r, dh=logoImg.height*r, pad=Math.round(short*0.04);\n  let x=pad,y=h-dh-pad; if(pos==='tr'){x=w-dw-pad;y=pad;} if(pos==='br'){x=w-dw-pad;y=h-dh-pad;} if(pos==='tl'){x=pad;y=pad;}\n  g.save(); g.globalAlpha=alpha; g.drawImage(logoImg,x,y,dw,dh); g.restore();\n}\n\n\/* \u5de5\u5177\uff1a\u7b49\u5f85\u5b57\u578b\u5c31\u7dd2\uff08\u907f\u514d\u532f\u51fa\u6642\u6587\u5b57\u4e0d\u898b\uff09 *\/\nasync function waitFonts(fontCss){\n  try{\n    if(document.fonts && document.fonts.load){\n      await document.fonts.load(fontCss, \"\u6f22\"); \/\/ \u9810\u8f09\u4e00\u6b21\n      await document.fonts.ready;\n    }else{\n      \/\/ \u700f\u89bd\u5668\u4e0d\u652f\u63f4 document.fonts\uff1a\u7c21\u55ae\u5ef6\u9072\n      await new Promise(r=>setTimeout(r,300));\n    }\n  }catch(e){}\n}\n\n\/* \u5171\u540c\uff1a\u7e6a\u88fd\u5361\u7247\uff08\u4f9b\u9810\u89bd\u8207\u532f\u51fa\uff09 *\/\nfunction roundRect(ctx,x,y,w,h,r,fill,stroke){ ctx.beginPath(); ctx.moveTo(x+r,y); ctx.arcTo(x+w,y,x+w,y+h,r); ctx.arcTo(x+w,y+h,x,y+h,r); ctx.arcTo(x,y+h,x,y,r); ctx.arcTo(x,y,x+w,y,r); ctx.closePath(); if(fill) ctx.fill(); if(stroke) ctx.stroke(); }\n\nfunction wrapByWidth(g, text, maxW){\n  \/\/ \u9023\u7e8c\u6bb5\u843d\uff1a\u81ea\u7136\u63db\u884c\n  const words = [...text]; \/\/ \u9010\u5b57\uff08\u542b\u4e2d\u6587\u6a19\u9ede\uff09\n  let line = '', lines = [];\n  for (let ch of words){\n    const test = line + ch;\n    if (g.measureText(test).width > maxW && line!==''){\n      lines.push(line);\n      line = ch;\n    }else{\n      line = test;\n    }\n  }\n  if(line) lines.push(line);\n  return lines;\n}\n\nfunction drawTextBlock(g, W, H, text){\n  \/\/ \u6587\u5b57\u6846\u4f4d\u7f6e\n  const bw=Math.round(W*ctrl.boxWidth), cx=Math.round(W*ctrl.posX), cy=Math.round(H*ctrl.posY);\n  const boxW=bw, boxH=Math.round(H*0.60), boxX=Math.max(12,cx-Math.round(boxW\/2)), boxY=Math.max(12,cy-Math.round(boxH\/2));\n\n  \/\/ \u534a\u900f\u660e\u767d\u5e95\n  g.fillStyle='rgba(255,255,255,0.82)';\n  roundRect(g,boxX,boxY,boxW,boxH,24,true,false);\n\n  \/\/ \u5b57\u578b\n  const fs=ctrl.fontSize * (W\/320); \/\/ \u96a8\u8f38\u51fa\u653e\u5927\n  g.textBaseline='top';\n  g.font=`${ctrl.fontWeight} ${fs}px ${ctrl.fontFamily}`;\n  g.fillStyle=ctrl.textColor;\n\n  const pad=Math.round(fs*0.6);\n  const maxW=boxW-pad*2;\n\n  let lines = wrapByWidth(g, text, maxW);\n\n  \/\/ \u9010\u884c\u7e6a\u88fd\n  const needStroke=ctrl.strokeWidth>0 && ctrl.strokeColor!=='#00000000';\n  const lh=fs*1.35;\n  lines.forEach((ln,i)=>{\n    let x = boxX+pad;\n    if(ctrl.align==='center'){\n      const w=g.measureText(ln).width;\n      x = boxX + (boxW - w)\/2;\n    }\n    const y = boxY + pad + i*lh;\n    if (y > boxY + boxH - lh) return; \/\/ \u8d85\u51fa\u6846\u5c31\u4e0d\u518d\u756b\n    if(needStroke){ g.lineWidth=ctrl.strokeWidth*(W\/320); g.strokeStyle=ctrl.strokeColor; g.strokeText(ln,x,y); }\n    g.fillText(ln,x,y);\n  });\n}\n\n\/* \u9810\u89bd & \u532f\u51fa\u4e00\u81f4 *\/\nasync function drawCardToContext(g, W, H){\n  \/\/ \u7b49\u5b57\u578b\uff0c\u907f\u514d\u532f\u51fa\u6642\u6587\u5b57\u4e0d\u898b\n  const fs=ctrl.fontSize * (W\/320);\n  await waitFonts(`${ctrl.fontWeight} ${fs}px ${ctrl.fontFamily}`);\n\n  \/\/ \u80cc\u666f\n  if(!currentBG){\n    drawBackground(g,W,H,'grad');\n  }else if(currentBG.kind==='proc'){\n    drawBackground(g,W,H,currentBG.name||'grad');\n  }else if(currentBG.kind==='img' && currentBG.url){\n    await new Promise(res=>{\n      const im=new Image(); im.crossOrigin='anonymous';\n      im.onload=()=>{ const r=Math.max(W\/im.width,H\/im.height); const w=im.width*r,h=im.height*r; g.drawImage(im,(W-w)\/2,(H-h)\/2,w,h); res(); };\n      im.onerror=()=>{ drawBackground(g,W,H,'grad'); res(); };\n      im.src=currentBG.url;\n    });\n  }else{\n    drawBackground(g,W,H,'grad');\n  }\n\n  \/\/ \u6587\u5b57\n  const txt = ctrl.baseTxt || '\u89aa\u611b\u7684\u670b\u53cb\uff0c\u9858\u4e3b\u7684\u624b\u4ee5\u6eab\u67d4\u8207\u771f\u5be6\u74b0\u7e5e\u4f60\u3002';\n  drawTextBlock(g, W, H, txt);\n\n  \/\/ LOGO\n  drawLogo(g, W, H);\n}\n\n\/* \u9810\u89bd *\/\nasync function drawCardPreview(){\n  ctx.clearRect(0,0,canvas.width,canvas.height);\n  await drawCardToContext(ctx, canvas.width, canvas.height);\n}\n\n\/* \u6bd4\u4f8b\uff08\u9810\u89bd\u77ed\u908a 320\uff09\uff0b\u63d0\u793a *\/\nfunction outSizeOf(ratio){\n  const outW=(ratio==='169')?1920:1080;\n  const outH=(ratio==='916')?1920:(ratio==='169')?1080:(ratio==='45')?1350:(ratio==='34')?1440:1080;\n  return {outW,outH};\n}\nfunction setCanvasRatio(r){\n  let w=320,h=320;\n  if(r==='916'){ w=320; h=Math.round(320*16\/9); }\n  if(r==='169'){ w=Math.round(320*16\/9); h=320; }\n  if(r==='45'){  w=320; h=Math.round(320*5\/4); }\n  if(r==='34'){  w=320; h=Math.round(320*4\/3); }\n  canvas.width=w; canvas.height=h;\n  const map={'1':'1:1','916':'9:16','169':'16:9','45':'4:5','34':'3:4'};\n  const {outW,outH}=outSizeOf(r);\n  $('#sizeHint').textContent=`\u9810\u89bd\uff1a${w}\u00d7${h}\uff08${map[r]||'1:1'}\uff09\u3000\u4e0b\u8f09\uff1a${outW}\u00d7${outH}`;\n  drawCardPreview();\n}\n$('#ratio').addEventListener('change',e=>setCanvasRatio(e.target.value));\n\n\/* \u532f\u51fa *\/\nfunction buildFilename(ratio,ext){ const d=new Date(), pad=n=>String(n).padStart(2,'0'); const ts=`${d.getFullYear()}${pad(d.getMonth()+1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}`; const map={'1':'1x1','916':'9x16','169':'16x9','45':'4x5','34':'3x4'}; return `BlessCard_${map[ratio]||'1x1'}_${ts}.${ext}`; }\nfunction downloadCanvas(c,ext,filename){ c.toBlob(b=>{ const url=URL.createObjectURL(b); const a=document.createElement('a'); a.href=url; a.download=filename; a.click(); URL.revokeObjectURL(url); }, ext==='png'?'image\/png':'image\/jpeg', ext==='png'?undefined:0.92); }\nasync function renderCard(ratio,{transparent=false}={}){\n  const {outW,outH}=outSizeOf(ratio); const off=document.createElement('canvas'); off.width=outW; off.height=outH; const g=off.getContext('2d');\n  if(transparent){ g.clearRect(0,0,outW,outH); } \/\/ \u80cc\u666f\u900f\u660e\n  await drawCardToContext(g, outW, outH);\n  return off;\n}\n$('#makeCard').addEventListener('click', async ()=>{\n  const ratio=$('#ratio').value||'1'; const wantJpg=$('#expJpg').checked, wantPng=$('#expPng').checked;\n  if(!wantJpg && !wantPng){ alert('\u8acb\u81f3\u5c11\u52fe\u9078\u4e00\u7a2e\u8f38\u51fa\u683c\u5f0f'); return; }\n  if(wantJpg){ const c=await renderCard(ratio,{transparent:false}); downloadCanvas(c,'jpg',buildFilename(ratio,'jpg')); }\n  if(wantPng){ const c=await renderCard(ratio,{transparent:$('#pngTransparent').checked}); downloadCanvas(c,'png',buildFilename(ratio,'png')); }\n});\n$('#batchExport').addEventListener('click', async ()=>{\n  const ratios=[...$$('.sizeChk')].filter(x=>x.checked).map(x=>x.value);\n  const wantJpg=$('#expJpg').checked, wantPng=$('#expPng').checked;\n  if(!ratios.length){ alert('\u8acb\u81f3\u5c11\u52fe\u9078\u4e00\u7a2e\u5c3a\u5bf8'); return; }\n  if(!wantJpg && !wantPng){ alert('\u8acb\u81f3\u5c11\u52fe\u9078\u4e00\u7a2e\u8f38\u51fa\u683c\u5f0f'); return; }\n  for(const r of ratios){\n    if(wantJpg){ const c=await renderCard(r,{transparent:false}); downloadCanvas(c,'jpg',buildFilename(r,'jpg')); }\n    if(wantPng){ const c=await renderCard(r,{transparent:$('#pngTransparent').checked}); downloadCanvas(c,'png',buildFilename(r,'png')); }\n  }\n});\n\n\/* \u8868\u55ae\u5373\u6642\u91cd\u7e6a *\/\n['blessName','align','fontSize','fontFamily','fontWeight','txtColor','strokeColor','strokeWidth','boxWidth','posX','posY','customBless','layoutMode'].forEach(id=>{\n  const el=$('#'+id); el && el.addEventListener(el.type==='range'?'input':'change', drawCardPreview);\n});\n\n\/* \u521d\u59cb\u5316\uff1a\u9810\u8a2d 1:1 + \u5c0f\u5973\u5b69\u80cc\u666f + \u5927\u767d\u8272\u534a\u900f\u660e\u6846\uff08\u7f6e\u4e2d\uff09 *\/\nsetCanvasRatio('1');\n$('#align').value='center';\n$('#boxWidth').value=86; $('#posX').value=50; $('#posY').value=50;\n$('#fontFamily').value='\"Noto Serif TC\", \"Source Serif 4\", serif';\n$('#fontWeight').value='400';\n$('#fontSize').value=28;\n$('#layoutMode').value='para';\n$('#blessOut').innerText = PRESET_BLESS['\u6eab\u67d4'][0].replace(\/\\{name\\}\/g, ctrl.name);\n\ndrawCardPreview();\n<\/script>\n<\/body>\n<\/html>\n\n\n\n\n<p><canvas width=\"320\" height=\"320\"><\/canvas><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u795d\u798f\u5361\u7522\u751f\u5668\uff08\u9023\u7e8c\u6bb5\u843d\u30fb\u4e00\u81f4\u532f\u51fa\u30fb\u9810\u8a2d\u80cc\u666f\uff09 \u795d\u798f\u5361\u7522\u751f\u5668\uff08\u9023\u7e8c\u6bb5\u843d\u30fb\u4e00\u81f4\u532f\u51fa\u30fb\u9810\u8a2d\u80cc\u666f\uff09 \u670b\u53cb\u540d\u5b57\uff08\u53ef\u7a7a\u767d\uff09 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"ocean_post_layout":"","ocean_both_sidebars_style":"","ocean_both_sidebars_content_width":0,"ocean_both_sidebars_sidebars_width":0,"ocean_sidebar":"","ocean_second_sidebar":"","ocean_disable_margins":"enable","ocean_add_body_class":"","ocean_shortcode_before_top_bar":"","ocean_shortcode_after_top_bar":"","ocean_shortcode_before_header":"","ocean_shortcode_after_header":"","ocean_has_shortcode":"","ocean_shortcode_after_title":"","ocean_shortcode_before_footer_widgets":"","ocean_shortcode_after_footer_widgets":"","ocean_shortcode_before_footer_bottom":"","ocean_shortcode_after_footer_bottom":"","ocean_display_top_bar":"default","ocean_display_header":"default","ocean_header_style":"","ocean_center_header_left_menu":"","ocean_custom_header_template":"","ocean_custom_logo":0,"ocean_custom_retina_logo":0,"ocean_custom_logo_max_width":0,"ocean_custom_logo_tablet_max_width":0,"ocean_custom_logo_mobile_max_width":0,"ocean_custom_logo_max_height":0,"ocean_custom_logo_tablet_max_height":0,"ocean_custom_logo_mobile_max_height":0,"ocean_header_custom_menu":"","ocean_menu_typo_font_family":"","ocean_menu_typo_font_subset":"","ocean_menu_typo_font_size":0,"ocean_menu_typo_font_size_tablet":0,"ocean_menu_typo_font_size_mobile":0,"ocean_menu_typo_font_size_unit":"px","ocean_menu_typo_font_weight":"","ocean_menu_typo_font_weight_tablet":"","ocean_menu_typo_font_weight_mobile":"","ocean_menu_typo_transform":"","ocean_menu_typo_transform_tablet":"","ocean_menu_typo_transform_mobile":"","ocean_menu_typo_line_height":0,"ocean_menu_typo_line_height_tablet":0,"ocean_menu_typo_line_height_mobile":0,"ocean_menu_typo_line_height_unit":"","ocean_menu_typo_spacing":0,"ocean_menu_typo_spacing_tablet":0,"ocean_menu_typo_spacing_mobile":0,"ocean_menu_typo_spacing_unit":"","ocean_menu_link_color":"","ocean_menu_link_color_hover":"","ocean_menu_link_color_active":"","ocean_menu_link_background":"","ocean_menu_link_hover_background":"","ocean_menu_link_active_background":"","ocean_menu_social_links_bg":"","ocean_menu_social_hover_links_bg":"","ocean_menu_social_links_color":"","ocean_menu_social_hover_links_color":"","ocean_disable_title":"default","ocean_disable_heading":"default","ocean_post_title":"","ocean_post_subheading":"","ocean_post_title_style":"","ocean_post_title_background_color":"","ocean_post_title_background":0,"ocean_post_title_bg_image_position":"","ocean_post_title_bg_image_attachment":"","ocean_post_title_bg_image_repeat":"","ocean_post_title_bg_image_size":"","ocean_post_title_height":0,"ocean_post_title_bg_overlay":0.5,"ocean_post_title_bg_overlay_color":"","ocean_disable_breadcrumbs":"default","ocean_breadcrumbs_color":"","ocean_breadcrumbs_separator_color":"","ocean_breadcrumbs_links_color":"","ocean_breadcrumbs_links_hover_color":"","ocean_display_footer_widgets":"default","ocean_display_footer_bottom":"default","ocean_custom_footer_template":"","footnotes":""},"class_list":["post-5163","page","type-page","status-publish","hentry","entry","owp-thumbs-layout-horizontal","owp-btn-normal","owp-tabs-layout-horizontal","has-no-thumbnails","has-product-nav"],"_hostinger_reach_plugin_has_subscription_block":false,"_hostinger_reach_plugin_is_elementor":false,"jetpack_sharing_enabled":true,"rttpg_featured_image_url":null,"rttpg_author":{"display_name":"Meiling","author_link":"https:\/\/meiling412.com\/ms\/author\/lin576688103gmail-com\/"},"rttpg_comment":0,"rttpg_category":null,"rttpg_excerpt":"\u795d\u798f\u5361\u7522\u751f\u5668\uff08\u9023\u7e8c\u6bb5\u843d\u30fb\u4e00\u81f4\u532f\u51fa\u30fb\u9810\u8a2d\u80cc\u666f\uff09 \u795d\u798f\u5361\u7522\u751f\u5668\uff08\u9023\u7e8c\u6bb5\u843d\u30fb\u4e00\u81f4\u532f\u51fa\u30fb\u9810\u8a2d\u80cc\u666f\uff09 \u670b\u53cb\u540d\u5b57\uff08\u53ef\u7a7a\u767d\uff09&hellip;","_links":{"self":[{"href":"https:\/\/meiling412.com\/ms\/wp-json\/wp\/v2\/pages\/5163","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/meiling412.com\/ms\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/meiling412.com\/ms\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/meiling412.com\/ms\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/meiling412.com\/ms\/wp-json\/wp\/v2\/comments?post=5163"}],"version-history":[{"count":40,"href":"https:\/\/meiling412.com\/ms\/wp-json\/wp\/v2\/pages\/5163\/revisions"}],"predecessor-version":[{"id":5216,"href":"https:\/\/meiling412.com\/ms\/wp-json\/wp\/v2\/pages\/5163\/revisions\/5216"}],"wp:attachment":[{"href":"https:\/\/meiling412.com\/ms\/wp-json\/wp\/v2\/media?parent=5163"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}