Youngger9765 commited on
Commit
5a12f56
·
1 Parent(s): 0a1821d

更新 index.html 模板,新增進度條與樣式調整

Browse files

- 將背景顏色改為淺灰色,提升可讀性
- 新增水平與內聯進度條,顯示生成過程
- 調整標題與圖示顏色,增強視覺效果
- 優化響應式設計,確保在不同裝置上顯示良好
- 更新表單變數,移除不必要的變數設定

此次更新旨在改善使用者體驗,提供更清晰的進度顯示與視覺效果。

Files changed (1) hide show
  1. templates/index.html +788 -177
templates/index.html CHANGED
@@ -11,7 +11,7 @@
11
 
12
  body {
13
  font-family: 'Noto Sans TC', sans-serif;
14
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
15
  min-height: 100vh;
16
  }
17
 
@@ -91,19 +91,404 @@
91
  opacity: 1;
92
  }
93
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  </style>
95
  </head>
96
  <body>
97
  <!-- Loading Spinner -->
98
  <div class="loading-spinner">
99
  <div class="bg-white rounded-lg p-8 shadow-xl">
100
- <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-purple-600 mx-auto"></div>
101
  <p class="mt-4 text-gray-600">處理中...</p>
102
  </div>
103
  </div>
104
 
105
  <!-- Toast Notification -->
106
  <div class="toast" id="toast"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
  <div class="container mx-auto p-6">
109
  <!-- Header -->
@@ -111,15 +496,41 @@
111
  <div class="flex items-center justify-between">
112
  <div>
113
  <h1 class="text-3xl font-bold text-gray-800 flex items-center">
114
- <i class="fas fa-file-code mr-3 text-purple-600"></i>
115
  樂寫網徵文比賽 HTML 建立器
116
  </h1>
117
  <p class="text-gray-600 mt-2">輕鬆建立精美的徵文比賽頁面</p>
118
  </div>
119
- <div class="text-gray-600">
120
  <i class="fas fa-info-circle mr-2"></i>
121
  填寫變數後點擊「套用變數並生成 HTML」
122
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  </div>
124
  </div>
125
 
@@ -353,11 +764,84 @@
353
  }
354
 
355
  .flow-arrow {
356
- text-align: center;
357
- font-size: 2rem;
358
- color: #438951;
359
- margin: 15px 0;
360
- font-weight: bold;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
  }
362
 
363
  .reward-box {
@@ -620,7 +1104,7 @@
620
  <div class="header">
621
  <h1>{{ mainTitle }}</h1>
622
  <h2>{{ theme }}</h2>
623
- <p>{{ headerDescription }}</p>
624
 
625
  <div class="buttons">
626
  <a href="{{ personalRegUrl }}" class="btn btn-primary">個人報名</a>
@@ -639,7 +1123,7 @@
639
 
640
  <div class="method-section">
641
  <h3>🚀 進行方式</h3>
642
- <p>{{ methodIntro }}</p>
643
  </div>
644
 
645
  <div class="flow-container">
@@ -654,21 +1138,21 @@
654
  <div class="flow-details">
655
  <div class="detail-item">
656
  <span class="detail-label">活動期間</span>
657
- <span class="detail-value">{{ stage1Period }}</span>
658
  </div>
659
  <div class="detail-item">
660
  <span class="detail-label">任務要求</span>
661
- <span class="detail-value">{{ stage1Requirements }}</span>
662
  </div>
663
  </div>
664
 
665
- <div class="flow-arrow">↓</div>
666
 
667
  <div class="reward-box">
668
  <div class="reward-icon">🏆</div>
669
  <div class="reward-content">
670
  <h4>{{ stage1AwardTitle }}</h4>
671
- <p>{{ stage1AwardDesc }}</p>
672
  </div>
673
  </div>
674
  </div>
@@ -690,7 +1174,7 @@
690
  <div class="flow-details">
691
  <div class="detail-item">
692
  <span class="detail-label">活動期間</span>
693
- <span class="detail-value">{{ stage2Period }}</span>
694
  </div>
695
  <div class="detail-item">
696
  <span class="detail-label">邀請通知</span>
@@ -698,7 +1182,7 @@
698
  </div>
699
  </div>
700
 
701
- <div class="flow-arrow">↓</div>
702
 
703
  <div class="reward-box">
704
  <div class="reward-icon">🎖️</div>
@@ -839,7 +1323,7 @@
839
  <p>活動網頁:<a href="{{ websiteUrl }}" style="color: #fff;">{{ websiteUrl }}</a></p>
840
  {% endif %}
841
  {% if contactInfo %}
842
- <p>{{ contactInfo }}</p>
843
  {% endif %}
844
  </div>
845
  </div>
@@ -851,15 +1335,6 @@
851
  <!-- Variables Section -->
852
  <div id="variables-content">
853
  <div class="mb-4">
854
- <label class="block text-sm font-medium text-gray-700 mb-2">
855
- 變數設定
856
- </label>
857
- <div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
858
- <p class="text-sm text-blue-800">
859
- <i class="fas fa-info-circle mr-2"></i>
860
- 填寫下方表單來設定模板變數,或切換到 JSON 模式進行進階編輯
861
- </p>
862
- </div>
863
 
864
  <!-- Toggle between Form and JSON mode -->
865
  <div class="flex justify-between mb-4">
@@ -884,23 +1359,23 @@
884
  <div class="space-y-3">
885
  <div>
886
  <label class="block text-sm font-medium text-gray-700 mb-1">主標題</label>
887
- <input type="text" id="var-mainTitle" value="2025聯發科技公益活動" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
888
  </div>
889
  <div>
890
  <label class="block text-sm font-medium text-gray-700 mb-1">活動主題</label>
891
- <input type="text" id="var-theme" value="世界觀察員計畫" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
892
  </div>
893
  <div>
894
  <label class="block text-sm font-medium text-gray-700 mb-1">標題描述</label>
895
- <textarea id="var-headerDescription" rows="3" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">讓孩子透過文字探索自然,成為真正的世界觀察員!樂寫公益學習網誠摯邀請全國國小學生,參加&lt;strong&gt;自然書寫徵文比賽&lt;/strong&gt;,展現你獨特的觀察力與創意思維。</textarea>
896
  </div>
897
  <div>
898
  <label class="block text-sm font-medium text-gray-700 mb-1">個人報名連結</label>
899
- <input type="url" id="var-personalRegUrl" value="https://www.colearn30.com/" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
900
  </div>
901
  <div>
902
  <label class="block text-sm font-medium text-gray-700 mb-1">學校報名連結</label>
903
- <input type="url" id="var-schoolRegUrl" value="https://www.facebook.com/CoWrite30" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
904
  </div>
905
  </div>
906
  </div>
@@ -911,12 +1386,9 @@
911
  <div class="space-y-3">
912
  <div>
913
  <label class="block text-sm font-medium text-gray-700 mb-1">參加對象</label>
914
- <input type="text" id="var-participants" value="全國公私立國小學生(本屆小六畢業生亦可參加)" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
915
- </div>
916
- <div>
917
- <label class="block text-sm font-medium text-gray-700 mb-1">進行方式說明</label>
918
- <textarea id="var-methodIntro" rows="2" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">本活動分為&lt;span class="highlight"&gt;兩關兩獎&lt;/span&gt;進行,讓學生循序漸進培養寫作能力</textarea>
919
  </div>
 
920
  </div>
921
  </div>
922
 
@@ -926,23 +1398,27 @@
926
  <div class="space-y-3">
927
  <div>
928
  <label class="block text-sm font-medium text-gray-700 mb-1">第一關標題</label>
929
- <input type="text" id="var-stage1Title" value="平台陪練寫作積分" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
930
  </div>
931
  <div>
932
- <label class="block text-sm font-medium text-gray-700 mb-1">活動期間</label>
933
- <input type="text" id="var-stage1Period" value="報名至 &lt;span class='highlight'&gt;2025年8月30日&lt;/span&gt; 截止" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
934
  </div>
935
  <div>
936
- <label class="block text-sm font-medium text-gray-700 mb-1">任務要求</label>
937
- <textarea id="var-stage1Requirements" rows="2" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">於樂寫公益平台發表至少 &lt;span class="highlight"&gt;三篇文章&lt;/span&gt;,累積寫作經驗</textarea>
938
  </div>
939
  <div>
940
  <label class="block text-sm font-medium text-gray-700 mb-1">積分獎標題</label>
941
- <input type="text" id="var-stage1AwardTitle" value="積分獎:企業參訪體驗" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
 
 
 
 
942
  </div>
943
  <div>
944
- <label class="block text-sm font-medium text-gray-700 mb-1">積分獎說明</label>
945
- <textarea id="var-stage1AwardDesc" rows="3" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">從報名活動到 &lt;span class="highlight"&gt;2025年8月31日&lt;/span&gt; 前,於樂寫公益學習平台 &lt;span class="highlight"&gt;積分最高的前三十名&lt;/span&gt; 得主,可獲得聯發科技企業參訪活動體驗資格</textarea>
946
  </div>
947
  </div>
948
  </div>
@@ -953,23 +1429,23 @@
953
  <div class="space-y-3">
954
  <div>
955
  <label class="block text-sm font-medium text-gray-700 mb-1">第二關標題</label>
956
- <input type="text" id="var-stage2Title" value="徵文比賽" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
957
  </div>
958
  <div>
959
  <label class="block text-sm font-medium text-gray-700 mb-1">活動期間</label>
960
- <input type="text" id="var-stage2Period" value="&lt;span class='highlight'&gt;2025年9月1日至9月30日&lt;/span&gt;" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
961
  </div>
962
  <div>
963
  <label class="block text-sm font-medium text-gray-700 mb-1">邀請通知</label>
964
- <textarea id="var-stage2Notice" rows="2" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">通過第一關的學員將於8月31日收到徵文比賽邀請通知及徵文題目</textarea>
965
  </div>
966
  <div>
967
  <label class="block text-sm font-medium text-gray-700 mb-1">徵文獎標題</label>
968
- <input type="text" id="var-stage2AwardTitle" value="徵文獎:企業受獎殊榮" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
969
  </div>
970
  <div>
971
  <label class="block text-sm font-medium text-gray-700 mb-1">徵文獎說明</label>
972
- <textarea id="var-stage2AwardDesc" rows="3" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">初審入圍前一百名者可獲得電子獎狀一幀,複審前三十名者,榮獲蒞臨聯發科技頒獎,現場公布得獎獎項</textarea>
973
  </div>
974
  </div>
975
  </div>
@@ -980,11 +1456,11 @@
980
  <div class="space-y-3">
981
  <div>
982
  <label class="block text-sm font-medium text-gray-700 mb-1">個人報名說明</label>
983
- <textarea id="var-personalRegDesc" rows="3" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">請先填寫線上表單進行報名,平台會寄送確認信件開通您的帳號。完成報名後,即可開始在樂寫公益平台發表文章,累積寫作積分。</textarea>
984
  </div>
985
  <div>
986
  <label class="block text-sm font-medium text-gray-700 mb-1">團體報名說明</label>
987
- <textarea id="var-schoolRegDesc" rows="3" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">以學校或班級為單位填寫表單報名,樂寫專員將主動與校方聯繫,協助學生集體開通帳號,讓老師能夠更便利帶領全班參與這次有意義的寫作活動。</textarea>
988
  </div>
989
  </div>
990
  </div>
@@ -995,7 +1471,7 @@
995
  <div class="space-y-3">
996
  <div>
997
  <label class="block text-sm font-medium text-gray-700 mb-1">評選條件 (JSON 格式)</label>
998
- <textarea id="var-requirements" rows="4" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent font-mono text-xs">[
999
  {"title": "字數與格式要求", "content": "徵文字數至少五百字。電腦打字需使用全形符號;於樂寫公益學習網平台個人帳號上傳。"},
1000
  {"title": "原創性要求", "content": "作品必須未曾在任何形式的平面和網路媒體(樂寫公益平台除外)發表,嚴禁抄襲及代筆(包括AI),違者取消資格。"},
1001
  {"title": "投稿注意事項", "content": "稿件送出前,請確認內容正確性(包含錯字、斷行、特殊字、空格等)。為公平起見,投稿後不可再修改文章。"}
@@ -1003,7 +1479,7 @@
1003
  </div>
1004
  <div>
1005
  <label class="block text-sm font-medium text-gray-700 mb-1">評分標準 (JSON 格式)</label>
1006
- <textarea id="var-scoringCriteria" rows="6" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent font-mono text-xs">[
1007
  {"item": "內容與結構", "percentage": "25%", "description": "切合題旨,思想積極健康,論點合理,文字生動;結構嚴謹,行文流暢,兼具廣度與深度。"},
1008
  {"item": "邏輯與修辭", "percentage": "25%", "description": "邏輯分明,條理清晰,敘事明白;用字遣詞合宜,修辭靈活優美。"},
1009
  {"item": "創意與觀察", "percentage": "20%", "description": "富含想像力,觀察微小細節,洞悉人性幽微。"},
@@ -1013,14 +1489,14 @@
1013
  </div>
1014
  <div>
1015
  <label class="block text-sm font-medium text-gray-700 mb-1">注意事項 (JSON 格式)</label>
1016
- <textarea id="var-notes" rows="3" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent font-mono text-xs">[
1017
  {"title": "創作注意事項", "content": "作文以白話文寫作,不得使用文言文、詩歌、韻文寫作;使用標準字體,並詳加全形標點符號;內文不得書寫姓名、校名等個人資訊"},
1018
  {"title": "評審辦法", "content": "本比賽採初審、複審、決審三輪制,聘請樂寫平台資深志工教練群、知名作家、專家學者、與聯發科技長官共同參與評比,確保評選過程公平公正。"}
1019
  ]</textarea>
1020
  </div>
1021
  <div>
1022
  <label class="block text-sm font-medium text-gray-700 mb-1">獎勵辦法 (JSON 格式)</label>
1023
- <textarea id="var-awards" rows="8" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent font-mono text-xs">[
1024
  {
1025
  "title": "勤筆獎勵—企業參訪體驗",
1026
  "description": "從報名活動到8/31前,於樂寫公益學習平台積分最高的前三十名得主,可獲得聯發科技企業參訪活動體驗資格。"
@@ -1041,7 +1517,7 @@
1041
  </div>
1042
  <div>
1043
  <label class="block text-sm font-medium text-gray-700 mb-1">獎勵備註</label>
1044
- <input type="text" id="var-awardNote" value="得獎者將獲頒獎盃一座,並成為聯發科技「世界觀察員」推廣大使!" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
1045
  </div>
1046
  </div>
1047
  </div>
@@ -1052,20 +1528,24 @@
1052
  <div class="space-y-3">
1053
  <div>
1054
  <label class="block text-sm font-medium text-gray-700 mb-1">頒獎典禮</label>
1055
- <textarea id="var-ceremonyDate" rows="2" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">2025年11月17日(星期一)於聯發科技總部舉行盛大頒獎典禮,並於當日公告最終得獎名單。得獎者將獲邀參加,一同分享寫作的喜悅與成果!</textarea>
1056
  </div>
1057
  <div>
1058
  <label class="block text-sm font-medium text-gray-700 mb-1">活動網頁</label>
1059
- <input type="url" id="var-websiteUrl" value="https://www.colearn30.com/" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
1060
  </div>
1061
  <div>
1062
  <label class="block text-sm font-medium text-gray-700 mb-1">聯絡說明</label>
1063
- <textarea id="var-contactInfo" rows="2" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">如有任何問題,歡迎透過樂寫公益學習臉書聯繫我們:&lt;a href="https://www.facebook.com/CoWrite30" style="color: #fff;"&gt;https://www.facebook.com/CoWrite30&lt;/a&gt;</textarea>
 
 
 
 
1064
  </div>
1065
  </div>
1066
  </div>
1067
  <div>
1068
- <button onclick="addCustomVariable()" class="text-purple-600 hover:text-purple-800 text-sm font-medium">
1069
  <i class="fas fa-plus-circle mr-1"></i>新增自訂變數
1070
  </button>
1071
  </div>
@@ -1076,7 +1556,7 @@
1076
 
1077
  <!-- JSON Mode -->
1078
  <div id="variables-json" class="hidden">
1079
- <textarea id="variables" class="w-full h-96 p-4 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent font-mono text-sm" placeholder='{"mainTitle": "主標題", "theme": "主題"}'>
1080
  {
1081
  "mainTitle": "2025聯發科技公益活動",
1082
  "theme": "世界觀察員計畫",
@@ -1084,14 +1564,14 @@
1084
  "personalRegUrl": "https://www.colearn30.com/",
1085
  "schoolRegUrl": "https://www.facebook.com/CoWrite30",
1086
  "participants": "全國公私立國小學生(本屆小六畢業生亦可參加)",
1087
- "methodIntro": "本活動分為<span class=\"highlight\">兩關兩獎</span>進行,讓學生循序漸進培養寫作能力",
1088
  "stage1Title": "平台陪練寫作積分",
1089
- "stage1Period": "報名至 <span class='highlight'>2025年8月30日</span> 截止",
1090
- "stage1Requirements": "於樂寫公益平台發表至少 <span class=\"highlight\">三篇文章</span>,累積寫作經驗",
1091
  "stage1AwardTitle": "積分獎:企業參訪體驗",
1092
- "stage1AwardDesc": "從報名活動到 <span class=\"highlight\">2025年8月31日</span> 前,於樂寫公益學習平台 <span class=\"highlight\">積分最高的前三十名</span> 得主,可獲得聯發科技企業參訪活動體驗資格",
 
1093
  "stage2Title": "徵文比賽",
1094
- "stage2Period": "<span class='highlight'>2025年9月1日至9月30日</span>",
1095
  "stage2Notice": "通過第一關的學員將於8月31日收到徵文比賽邀請通知及徵文題目",
1096
  "stage2AwardTitle": "徵文獎:企業受獎殊榮",
1097
  "stage2AwardDesc": "初審入圍前一百名者可獲得電子獎狀一幀,複審前三十名者,榮獲蒞臨聯發科技頒獎,現場公布得獎獎項",
@@ -1134,7 +1614,8 @@
1134
  "awardNote": "得獎者將獲頒獎盃一座,並成為聯發科技「世界觀察員」推廣大使!",
1135
  "ceremonyDate": "2025年11月17日(星期一)於聯發科技總部舉行盛大頒獎典禮,並於當日公告最終得獎名單。得獎者將獲邀參加,一同分享寫作的喜悅與成果!",
1136
  "websiteUrl": "https://www.colearn30.com/",
1137
- "contactInfo": "如有任何問題,歡迎透過樂寫公益學習臉書聯繫我們:<a href=\"https://www.facebook.com/CoWrite30\" style=\"color: #fff;\">https://www.facebook.com/CoWrite30</a>"
 
1138
  }</textarea>
1139
  </div>
1140
  </div>
@@ -1142,25 +1623,37 @@
1142
  </div>
1143
  </div>
1144
 
1145
- <!-- Right Panel - HTML Source Code -->
1146
  <div class="space-y-4">
1147
  <div class="flex items-center justify-between">
1148
- <h3 class="text-lg font-medium text-gray-700">
1149
- <i class="fas fa-code mr-2"></i>HTML 原始碼
1150
- </h3>
 
 
 
 
 
1151
  <div class="flex gap-2">
1152
- <button onclick="previewHTML()" class="text-sm text-purple-600 hover:text-purple-800">
1153
- <i class="fas fa-eye mr-1"></i>預覽
1154
  </button>
1155
- <button onclick="copyToClipboard()" class="text-sm text-purple-600 hover:text-purple-800">
1156
  <i class="fas fa-copy mr-1"></i>複製
1157
  </button>
1158
- <button onclick="downloadHTML()" class="text-sm text-purple-600 hover:text-purple-800">
1159
  <i class="fas fa-download mr-1"></i>下載
1160
  </button>
1161
  </div>
1162
  </div>
1163
- <div class="bg-gray-900 rounded-lg p-4 split-panel" style="overflow-y: auto; max-height: calc(100vh - 250px);">
 
 
 
 
 
 
 
1164
  <pre class="text-green-400 text-sm font-mono" id="source-code"><code><!-- 點擊「套用變數並生成 HTML」按鈕後,這裡會顯示渲染後的 HTML 原始碼 --></code></pre>
1165
  </div>
1166
  </div>
@@ -1170,7 +1663,7 @@
1170
 
1171
  <script>
1172
  let currentTab = 'template';
1173
- const awardsData = {{ awards_data | tojson | safe }};
1174
 
1175
  // No more tab switching needed
1176
 
@@ -1295,20 +1788,6 @@
1295
  }
1296
  }
1297
 
1298
- function previewHTML() {
1299
- const sourceCode = document.getElementById('source-code').textContent;
1300
-
1301
- // Check if HTML has been generated
1302
- if (sourceCode.includes('<!-- 點擊')) {
1303
- showToast('請先生成 HTML!', 'error');
1304
- return;
1305
- }
1306
-
1307
- // Open in new window
1308
- const previewWindow = window.open('', '_blank', 'width=1200,height=800');
1309
- previewWindow.document.write(sourceCode);
1310
- previewWindow.document.close();
1311
- }
1312
 
1313
  function copyToClipboard() {
1314
  const sourceCode = document.getElementById('source-code').textContent;
@@ -1445,8 +1924,8 @@
1445
  const div = document.createElement('div');
1446
  div.className = 'flex gap-2';
1447
  div.innerHTML = `
1448
- <input type="text" id="custom-key-${customVarCount}" placeholder="變數名稱" class="flex-1 px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
1449
- <input type="text" id="custom-var-${customVarCount}" placeholder="變數值" class="flex-1 px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
1450
  <button onclick="removeCustomVariable(this)" class="px-3 py-2 text-red-600 hover:text-red-800">
1451
  <i class="fas fa-trash"></i>
1452
  </button>
@@ -1480,115 +1959,215 @@
1480
  // Add event listeners for form inputs
1481
  const formFields = [
1482
  'mainTitle', 'theme', 'headerDescription', 'personalRegUrl', 'schoolRegUrl',
1483
- 'participants', 'methodIntro', 'stage1Title', 'stage1Period', 'stage1Requirements',
1484
- 'stage1AwardTitle', 'stage1AwardDesc', 'stage2Title', 'stage2Period', 'stage2Notice',
1485
- 'stage2AwardTitle', 'stage2AwardDesc', 'personalRegDesc', 'schoolRegDesc',
1486
- 'awardNote', 'ceremonyDate', 'websiteUrl', 'contactInfo'
 
1487
  ];
1488
 
1489
  // Remove auto-update listeners - only sync form to JSON when switching modes
1490
 
1491
- // Add apply variables function
1492
- async function applyVariables() {
1493
- console.log('========== 開始生成 HTML ==========');
1494
- showToast('正在生成 HTML...', 'info');
1495
 
1496
- // Step 1: 檢查當前模式並更新 JSON
1497
- console.log('Step 1: 當前模式:', variableMode);
1498
- if (variableMode === 'form') {
1499
- console.log('Step 1.1: 執行 updateJsonFromForm()');
1500
- updateJsonFromForm();
 
 
 
 
1501
  }
1502
 
1503
- // Step 2: 取得模板和變數內容
1504
- const templateContent = document.getElementById('template').value;
1505
- const variablesContent = document.getElementById('variables').value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1506
 
1507
- console.log('Step 2: 準備發送的資料');
1508
- console.log('Template 長度:', templateContent.length, '字元');
1509
- console.log('Template 預覽:', templateContent.substring(0, 200) + '...');
1510
- console.log('Variables 內容:', variablesContent);
1511
 
1512
- // Step 3: 解析並驗證 JSON
1513
- try {
1514
- const parsedVariables = JSON.parse(variablesContent);
1515
- console.log('Step 3: JSON 解析成功');
1516
- console.log('變數物件:', parsedVariables);
1517
- console.log('變數 keys:', Object.keys(parsedVariables));
1518
- } catch (e) {
1519
- console.error('Step 3: JSON 解析失敗:', e);
 
 
1520
  }
1521
 
1522
- // Step 4: 準備 FormData
1523
- const formData = new FormData();
1524
- formData.append('template_content', templateContent);
1525
- formData.append('variables', variablesContent);
1526
-
1527
- console.log('Step 4: FormData 準備完成');
1528
- console.log('FormData entries:');
1529
- for (let [key, value] of formData.entries()) {
1530
- console.log(` ${key}:`, value.substring(0, 100) + '...');
 
 
 
 
 
 
 
1531
  }
 
 
 
 
 
 
 
 
 
 
 
1532
 
1533
  try {
1534
- // Step 5: 發送請求
1535
- console.log('Step 5: 發送 POST 請求到 /api/preview');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1536
  const response = await fetch('/api/preview', {
1537
  method: 'POST',
1538
  body: formData
1539
  });
1540
 
1541
- console.log('Step 6: 收到回應');
1542
- console.log('Response status:', response.status);
1543
- console.log('Response headers:', response.headers);
1544
 
1545
- // Step 7: 解析回應
1546
  const result = await response.json();
1547
- console.log('Step 7: 解析回應 JSON');
1548
- console.log('完整回應:', result);
1549
 
1550
  if (result.debug) {
1551
  console.log('Debug 資訊:', result.debug);
1552
  }
1553
 
1554
  if (result.error) {
1555
- console.error('Step 8: 後端回傳錯誤');
1556
- console.error('錯誤訊息:', result.error);
1557
- if (result.debug && result.debug.traceback) {
1558
- console.error('錯誤堆疊:', result.debug.traceback);
1559
- }
1560
-
1561
  showToast(`錯誤:${result.error}`, 'error');
1562
  document.getElementById('source-code').innerHTML = `<code style="color: #ef4444;">錯誤:${result.error}</code>`;
1563
- } else {
1564
- console.log('Step 8: 成功生成 HTML');
1565
- console.log('HTML 長度:', result.html.length, '字元');
1566
- console.log('HTML 預覽:', result.html.substring(0, 300) + '...');
1567
-
1568
- // Store the generated HTML
1569
- generatedHTML = result.html;
1570
-
1571
- // Display HTML source code with syntax highlighting
1572
- const htmlCode = result.html;
1573
- const escapedHtml = htmlCode
1574
- .replace(/&/g, '&amp;')
1575
- .replace(/</g, '&lt;')
1576
- .replace(/>/g, '&gt;')
1577
- .replace(/"/g, '&quot;')
1578
- .replace(/'/g, '&#039;');
1579
-
1580
- document.getElementById('source-code').innerHTML = `<code>${escapedHtml}</code>`;
1581
- showToast('HTML 已成功生成!', 'success');
1582
-
1583
- console.log('Step 9: HTML 已顯示在畫面上');
1584
  }
1585
- } catch (error) {
1586
- console.error('Step X: 發生錯誤');
1587
- console.error('錯誤類型:', error.name);
1588
- console.error('錯誤訊息:', error.message);
1589
- console.error('錯誤堆疊:', error.stack);
1590
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1591
  showToast(`錯誤:${error.message}`, 'error');
 
1592
  }
1593
 
1594
  console.log('========== 結束生成 HTML ==========');
@@ -1596,25 +2175,57 @@
1596
 
1597
  // Store generated HTML globally
1598
  let generatedHTML = '';
 
1599
 
1600
- // Preview HTML in popup window
1601
- function previewHTML() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1602
  if (!generatedHTML) {
1603
- showToast('請先生成 HTML', 'error');
1604
  return;
1605
  }
1606
 
1607
- // Calculate center position
1608
  const width = 1200;
1609
  const height = 800;
1610
  const left = (window.screen.width - width) / 2;
1611
  const top = (window.screen.height - height) / 2;
1612
 
1613
- // Open popup window
1614
  const previewWindow = window.open(
1615
  '',
1616
  'preview_popup',
1617
- `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes,toolbar=no,menubar=no,location=no,directories=no,status=yes`
1618
  );
1619
 
1620
  if (previewWindow) {
 
11
 
12
  body {
13
  font-family: 'Noto Sans TC', sans-serif;
14
+ background: #f5f5f5;
15
  min-height: 100vh;
16
  }
17
 
 
91
  opacity: 1;
92
  }
93
  }
94
+
95
+ /* Horizontal Progress Bar in Header */
96
+ .progress-header {
97
+ display: none;
98
+ background: #f8f9fa;
99
+ border-bottom: 1px solid #e5e7eb;
100
+ padding: 15px 0;
101
+ margin-bottom: 10px;
102
+ }
103
+
104
+ .progress-header.show {
105
+ display: block;
106
+ }
107
+
108
+ .progress-container {
109
+ max-width: 1200px;
110
+ margin: 0 auto;
111
+ padding: 0 20px;
112
+ }
113
+
114
+ .progress-title {
115
+ text-align: center;
116
+ font-size: 1rem;
117
+ font-weight: 600;
118
+ color: #2C6E49;
119
+ margin-bottom: 15px;
120
+ }
121
+
122
+ .progress-steps {
123
+ display: flex;
124
+ justify-content: space-between;
125
+ align-items: center;
126
+ list-style: none;
127
+ padding: 0;
128
+ margin: 0;
129
+ position: relative;
130
+ }
131
+
132
+ .progress-step {
133
+ display: flex;
134
+ flex-direction: column;
135
+ align-items: center;
136
+ text-align: center;
137
+ position: relative;
138
+ z-index: 2;
139
+ }
140
+
141
+ .step-icon {
142
+ width: 40px;
143
+ height: 40px;
144
+ border-radius: 50%;
145
+ display: flex;
146
+ align-items: center;
147
+ justify-content: center;
148
+ font-weight: bold;
149
+ font-size: 14px;
150
+ margin-bottom: 8px;
151
+ transition: all 0.3s ease;
152
+ border: 3px solid #e9ecef;
153
+ background: white;
154
+ color: #6c757d;
155
+ }
156
+
157
+ .progress-step.pending .step-icon {
158
+ background: white;
159
+ color: #6c757d;
160
+ border-color: #e9ecef;
161
+ }
162
+
163
+ .progress-step.active .step-icon {
164
+ background: #71DDB1;
165
+ color: white;
166
+ border-color: #71DDB1;
167
+ animation: progressPulse 2s infinite;
168
+ box-shadow: 0 0 0 4px rgba(113, 221, 177, 0.2);
169
+ }
170
+
171
+ .progress-step.completed .step-icon {
172
+ background: #28a745;
173
+ color: white;
174
+ border-color: #28a745;
175
+ }
176
+
177
+ .progress-step.error .step-icon {
178
+ background: #dc3545;
179
+ color: white;
180
+ border-color: #dc3545;
181
+ }
182
+
183
+ .step-title {
184
+ font-size: 12px;
185
+ font-weight: 600;
186
+ color: #495057;
187
+ margin-bottom: 3px;
188
+ line-height: 1.2;
189
+ }
190
+
191
+ .step-description {
192
+ font-size: 10px;
193
+ color: #6c757d;
194
+ line-height: 1.2;
195
+ max-width: 100px;
196
+ }
197
+
198
+ .progress-step.active .step-title {
199
+ color: #2C6E49;
200
+ }
201
+
202
+ .progress-step.completed .step-title {
203
+ color: #155724;
204
+ }
205
+
206
+ .progress-step.error .step-title {
207
+ color: #721c24;
208
+ }
209
+
210
+ /* Progress line connecting steps */
211
+ .progress-steps::before {
212
+ content: '';
213
+ position: absolute;
214
+ top: 20px;
215
+ left: 5%;
216
+ right: 5%;
217
+ height: 3px;
218
+ background: #e9ecef;
219
+ z-index: 1;
220
+ }
221
+
222
+ .progress-line {
223
+ position: absolute;
224
+ top: 20px;
225
+ left: 5%;
226
+ height: 3px;
227
+ background: linear-gradient(90deg, #71DDB1 0%, #5EC99D 100%);
228
+ z-index: 1;
229
+ width: 0;
230
+ transition: width 0.5s ease;
231
+ }
232
+
233
+ @keyframes progressPulse {
234
+ 0% {
235
+ box-shadow: 0 0 0 4px rgba(113, 221, 177, 0.2);
236
+ }
237
+ 50% {
238
+ box-shadow: 0 0 0 8px rgba(113, 221, 177, 0.1);
239
+ }
240
+ 100% {
241
+ box-shadow: 0 0 0 4px rgba(113, 221, 177, 0.2);
242
+ }
243
+ }
244
+
245
+ /* Responsive design */
246
+ @media (max-width: 768px) {
247
+ .step-title {
248
+ font-size: 10px;
249
+ }
250
+
251
+ .step-description {
252
+ font-size: 8px;
253
+ max-width: 80px;
254
+ }
255
+
256
+ .step-icon {
257
+ width: 32px;
258
+ height: 32px;
259
+ font-size: 12px;
260
+ }
261
+
262
+ .progress-steps::before {
263
+ top: 16px;
264
+ }
265
+
266
+ .progress-line {
267
+ top: 16px;
268
+ }
269
+ }
270
+
271
+ /* Inline Progress Bar in Header */
272
+ .progress-inline {
273
+ display: none;
274
+ align-items: center;
275
+ gap: 15px;
276
+ }
277
+
278
+ .progress-inline.show {
279
+ display: flex;
280
+ }
281
+
282
+ .progress-title {
283
+ font-size: 14px;
284
+ font-weight: 600;
285
+ color: #2C6E49;
286
+ white-space: nowrap;
287
+ }
288
+
289
+ .progress-steps {
290
+ display: flex;
291
+ align-items: center;
292
+ list-style: none;
293
+ padding: 0;
294
+ margin: 0;
295
+ gap: 8px;
296
+ }
297
+
298
+ .progress-connector {
299
+ width: 30px;
300
+ height: 3px;
301
+ background: #e9ecef;
302
+ transition: background-color 0.3s ease;
303
+ }
304
+
305
+ .progress-connector.completed {
306
+ background: linear-gradient(90deg, #71DDB1 0%, #5EC99D 100%);
307
+ }
308
+
309
+ .progress-inline .progress-step {
310
+ display: flex;
311
+ flex-direction: column;
312
+ align-items: center;
313
+ text-align: center;
314
+ position: relative;
315
+ z-index: 2;
316
+ }
317
+
318
+ .progress-inline .step-icon {
319
+ width: 24px;
320
+ height: 24px;
321
+ border-radius: 50%;
322
+ display: flex;
323
+ align-items: center;
324
+ justify-content: center;
325
+ font-weight: bold;
326
+ font-size: 10px;
327
+ transition: all 0.3s ease;
328
+ border: 2px solid #e9ecef;
329
+ background: white;
330
+ color: #6c757d;
331
+ }
332
+
333
+ .progress-inline .progress-step.pending .step-icon {
334
+ background: white;
335
+ color: #6c757d;
336
+ border-color: #e9ecef;
337
+ }
338
+
339
+ .progress-inline .progress-step.active .step-icon {
340
+ background: #71DDB1;
341
+ color: white;
342
+ border-color: #71DDB1;
343
+ animation: progressPulse 2s infinite;
344
+ box-shadow: 0 0 0 2px rgba(113, 221, 177, 0.2);
345
+ }
346
+
347
+ .progress-inline .progress-step.completed .step-icon {
348
+ background: #28a745;
349
+ color: white;
350
+ border-color: #28a745;
351
+ }
352
+
353
+ .progress-inline .progress-step.error .step-icon {
354
+ background: #dc3545;
355
+ color: white;
356
+ border-color: #dc3545;
357
+ }
358
+
359
+ .step-icon {
360
+ width: 24px;
361
+ height: 24px;
362
+ border-radius: 50%;
363
+ display: flex;
364
+ align-items: center;
365
+ justify-content: center;
366
+ font-weight: bold;
367
+ font-size: 10px;
368
+ transition: all 0.3s ease;
369
+ border: 2px solid #e9ecef;
370
+ background: white;
371
+ color: #6c757d;
372
+ }
373
+
374
+ .progress-step.pending .step-icon {
375
+ background: white;
376
+ color: #6c757d;
377
+ border-color: #e9ecef;
378
+ }
379
+
380
+ .progress-step.active .step-icon {
381
+ background: #71DDB1;
382
+ color: white;
383
+ border-color: #71DDB1;
384
+ animation: inlineProgressPulse 2s infinite;
385
+ box-shadow: 0 0 0 2px rgba(113, 221, 177, 0.3);
386
+ }
387
+
388
+ .progress-step.completed .step-icon {
389
+ background: #28a745;
390
+ color: white;
391
+ border-color: #28a745;
392
+ }
393
+
394
+ .progress-step.error .step-icon {
395
+ background: #dc3545;
396
+ color: white;
397
+ border-color: #dc3545;
398
+ }
399
+
400
+ /* Progress connectors */
401
+ .progress-connector {
402
+ width: 20px;
403
+ height: 2px;
404
+ background: #e9ecef;
405
+ transition: background 0.3s ease;
406
+ }
407
+
408
+ .progress-connector.completed {
409
+ background: #71DDB1;
410
+ }
411
+
412
+ @keyframes inlineProgressPulse {
413
+ 0% {
414
+ box-shadow: 0 0 0 2px rgba(113, 221, 177, 0.3);
415
+ }
416
+ 50% {
417
+ box-shadow: 0 0 0 4px rgba(113, 221, 177, 0.1);
418
+ }
419
+ 100% {
420
+ box-shadow: 0 0 0 2px rgba(113, 221, 177, 0.3);
421
+ }
422
+ }
423
+
424
+ /* Responsive design for inline progress */
425
+ @media (max-width: 768px) {
426
+ .progress-title {
427
+ font-size: 12px;
428
+ }
429
+
430
+ .progress-inline .step-icon {
431
+ width: 20px;
432
+ height: 20px;
433
+ font-size: 9px;
434
+ }
435
+
436
+ .progress-connector {
437
+ width: 15px;
438
+ }
439
+
440
+ .progress-inline {
441
+ gap: 6px;
442
+ }
443
+ }
444
  </style>
445
  </head>
446
  <body>
447
  <!-- Loading Spinner -->
448
  <div class="loading-spinner">
449
  <div class="bg-white rounded-lg p-8 shadow-xl">
450
+ <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600 mx-auto"></div>
451
  <p class="mt-4 text-gray-600">處理中...</p>
452
  </div>
453
  </div>
454
 
455
  <!-- Toast Notification -->
456
  <div class="toast" id="toast"></div>
457
+
458
+ <!-- Progress Header -->
459
+ <div class="progress-header" id="progress-header">
460
+ <div class="progress-container">
461
+ <h3 class="progress-title">正在生成 HTML</h3>
462
+ <ul class="progress-steps" id="progress-steps">
463
+ <div class="progress-line" id="progress-line"></div>
464
+ <li class="progress-step pending" id="step-1">
465
+ <div class="step-icon">1</div>
466
+ <div class="step-title">驗證表單</div>
467
+ <div class="step-description">檢查資料</div>
468
+ </li>
469
+ <li class="progress-step pending" id="step-2">
470
+ <div class="step-icon">2</div>
471
+ <div class="step-title">準備模板</div>
472
+ <div class="step-description">載入內容</div>
473
+ </li>
474
+ <li class="progress-step pending" id="step-3">
475
+ <div class="step-icon">3</div>
476
+ <div class="step-title">發送資料</div>
477
+ <div class="step-description">傳送後端</div>
478
+ </li>
479
+ <li class="progress-step pending" id="step-4">
480
+ <div class="step-icon">4</div>
481
+ <div class="step-title">渲染 HTML</div>
482
+ <div class="step-description">處理模板</div>
483
+ </li>
484
+ <li class="progress-step pending" id="step-5">
485
+ <div class="step-icon">5</div>
486
+ <div class="step-title">完成生成</div>
487
+ <div class="step-description">顯示結果</div>
488
+ </li>
489
+ </ul>
490
+ </div>
491
+ </div>
492
 
493
  <div class="container mx-auto p-6">
494
  <!-- Header -->
 
496
  <div class="flex items-center justify-between">
497
  <div>
498
  <h1 class="text-3xl font-bold text-gray-800 flex items-center">
499
+ <i class="fas fa-file-code mr-3 text-green-600"></i>
500
  樂寫網徵文比賽 HTML 建立器
501
  </h1>
502
  <p class="text-gray-600 mt-2">輕鬆建立精美的徵文比賽頁面</p>
503
  </div>
504
+ <div class="text-gray-600" id="info-text">
505
  <i class="fas fa-info-circle mr-2"></i>
506
  填寫變數後點擊「套用變數並生成 HTML」
507
  </div>
508
+
509
+ <!-- Inline Progress Bar -->
510
+ <div class="progress-inline" id="progress-inline" style="display: none;">
511
+ <span class="progress-title">正在生成 HTML</span>
512
+ <div class="progress-steps" id="progress-steps">
513
+ <div class="progress-step pending" id="step-1">
514
+ <div class="step-icon">1</div>
515
+ </div>
516
+ <div class="progress-connector" id="connector-1"></div>
517
+ <div class="progress-step pending" id="step-2">
518
+ <div class="step-icon">2</div>
519
+ </div>
520
+ <div class="progress-connector" id="connector-2"></div>
521
+ <div class="progress-step pending" id="step-3">
522
+ <div class="step-icon">3</div>
523
+ </div>
524
+ <div class="progress-connector" id="connector-3"></div>
525
+ <div class="progress-step pending" id="step-4">
526
+ <div class="step-icon">4</div>
527
+ </div>
528
+ <div class="progress-connector" id="connector-4"></div>
529
+ <div class="progress-step pending" id="step-5">
530
+ <div class="step-icon">5</div>
531
+ </div>
532
+ </div>
533
+ </div>
534
  </div>
535
  </div>
536
 
 
764
  }
765
 
766
  .flow-arrow {
767
+ display: flex;
768
+ flex-direction: column;
769
+ align-items: center;
770
+ justify-content: center;
771
+ margin: 25px 0;
772
+ position: relative;
773
+ height: 80px;
774
+ width: 100%;
775
+ }
776
+
777
+ /* Modern geometric arrow design */
778
+ .flow-arrow::before {
779
+ content: '';
780
+ width: 4px;
781
+ height: 40px;
782
+ background: linear-gradient(180deg, #71DDB1 0%, #5EC99D 100%);
783
+ border-radius: 2px;
784
+ margin-bottom: 5px;
785
+ box-shadow: 0 4px 15px rgba(113, 221, 177, 0.3);
786
+ animation: flowLine 2s ease-in-out infinite;
787
+ }
788
+
789
+ .flow-arrow::after {
790
+ content: '';
791
+ width: 0;
792
+ height: 0;
793
+ border-left: 12px solid transparent;
794
+ border-right: 12px solid transparent;
795
+ border-top: 18px solid #5EC99D;
796
+ filter: drop-shadow(0 4px 8px rgba(113, 221, 177, 0.4));
797
+ animation: arrowBounce 2s ease-in-out infinite;
798
+ }
799
+
800
+ /* Add decorative dots */
801
+ .flow-arrow {
802
+ position: relative;
803
+ }
804
+
805
+ .flow-arrow:hover::before {
806
+ background: linear-gradient(180deg, #5EC99D 0%, #4CAF86 100%);
807
+ transform: scaleY(1.1);
808
+ }
809
+
810
+ .flow-arrow:hover::after {
811
+ border-top-color: #4CAF86;
812
+ transform: translateY(-2px);
813
+ }
814
+
815
+ @keyframes flowLine {
816
+ 0%, 100% {
817
+ opacity: 0.8;
818
+ transform: scaleY(1);
819
+ }
820
+ 50% {
821
+ opacity: 1;
822
+ transform: scaleY(1.05);
823
+ }
824
+ }
825
+
826
+ @keyframes arrowBounce {
827
+ 0%, 100% {
828
+ transform: translateY(0);
829
+ }
830
+ 50% {
831
+ transform: translateY(-3px);
832
+ }
833
+ }
834
+
835
+ /* Alternative design with connecting line */
836
+ .flow-arrow {
837
+ background: linear-gradient(to bottom,
838
+ transparent 0%,
839
+ transparent 45%,
840
+ rgba(113, 221, 177, 0.1) 45%,
841
+ rgba(113, 221, 177, 0.1) 55%,
842
+ transparent 55%,
843
+ transparent 100%
844
+ );
845
  }
846
 
847
  .reward-box {
 
1104
  <div class="header">
1105
  <h1>{{ mainTitle }}</h1>
1106
  <h2>{{ theme }}</h2>
1107
+ <p>{{ headerDescription | safe }}</p>
1108
 
1109
  <div class="buttons">
1110
  <a href="{{ personalRegUrl }}" class="btn btn-primary">個人報名</a>
 
1123
 
1124
  <div class="method-section">
1125
  <h3>🚀 進行方式</h3>
1126
+ <p>本活動分為<span class="highlight">兩關兩獎</span>進行,讓學生循序漸進培養寫作能力</p>
1127
  </div>
1128
 
1129
  <div class="flow-container">
 
1138
  <div class="flow-details">
1139
  <div class="detail-item">
1140
  <span class="detail-label">活動期間</span>
1141
+ <span class="detail-value">報名至 <span class="highlight">{{ stage1Period }}</span> 截止</span>
1142
  </div>
1143
  <div class="detail-item">
1144
  <span class="detail-label">任務要求</span>
1145
+ <span class="detail-value">於樂寫公益平台發表至少 <span class="highlight">{{ stage1Requirements }}</span>,累積寫作經驗</span>
1146
  </div>
1147
  </div>
1148
 
1149
+ <div class="flow-arrow"></div>
1150
 
1151
  <div class="reward-box">
1152
  <div class="reward-icon">🏆</div>
1153
  <div class="reward-content">
1154
  <h4>{{ stage1AwardTitle }}</h4>
1155
+ <p>從報名活動到 <span class="highlight">{{ stage1AwardEndDate }}</span> 前,於樂寫公益學習平台 <span class="highlight">積分最高的前{{ stage1AwardQuota }}名</span> 得主,可獲得聯發科技企業參訪活動體驗資格</p>
1156
  </div>
1157
  </div>
1158
  </div>
 
1174
  <div class="flow-details">
1175
  <div class="detail-item">
1176
  <span class="detail-label">活動期間</span>
1177
+ <span class="detail-value"><span class="highlight">{{ stage2Period }}</span></span>
1178
  </div>
1179
  <div class="detail-item">
1180
  <span class="detail-label">邀請通知</span>
 
1182
  </div>
1183
  </div>
1184
 
1185
+ <div class="flow-arrow"></div>
1186
 
1187
  <div class="reward-box">
1188
  <div class="reward-icon">🎖️</div>
 
1323
  <p>活動網頁:<a href="{{ websiteUrl }}" style="color: #fff;">{{ websiteUrl }}</a></p>
1324
  {% endif %}
1325
  {% if contactInfo %}
1326
+ <p>{{ contactInfo }}{% if facebookUrl %}:<a href="{{ facebookUrl }}" style="color: #fff;">{{ facebookUrl }}</a>{% endif %}</p>
1327
  {% endif %}
1328
  </div>
1329
  </div>
 
1335
  <!-- Variables Section -->
1336
  <div id="variables-content">
1337
  <div class="mb-4">
 
 
 
 
 
 
 
 
 
1338
 
1339
  <!-- Toggle between Form and JSON mode -->
1340
  <div class="flex justify-between mb-4">
 
1359
  <div class="space-y-3">
1360
  <div>
1361
  <label class="block text-sm font-medium text-gray-700 mb-1">主標題</label>
1362
+ <input type="text" id="var-mainTitle" value="2025聯發科技公益活動" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1363
  </div>
1364
  <div>
1365
  <label class="block text-sm font-medium text-gray-700 mb-1">活動主題</label>
1366
+ <input type="text" id="var-theme" value="世界觀察員計畫" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1367
  </div>
1368
  <div>
1369
  <label class="block text-sm font-medium text-gray-700 mb-1">標題描述</label>
1370
+ <textarea id="var-headerDescription" rows="3" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">讓孩子透過文字探索自然,成為真正的世界觀察員!樂寫公益學習網誠摯邀請全國國小學生,參加<strong>自然書寫徵文比賽</strong>,展現你獨特的觀察力與創意思維。</textarea>
1371
  </div>
1372
  <div>
1373
  <label class="block text-sm font-medium text-gray-700 mb-1">個人報名連結</label>
1374
+ <input type="url" id="var-personalRegUrl" value="https://www.colearn30.com/" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1375
  </div>
1376
  <div>
1377
  <label class="block text-sm font-medium text-gray-700 mb-1">學校報名連結</label>
1378
+ <input type="url" id="var-schoolRegUrl" value="https://www.facebook.com/CoWrite30" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1379
  </div>
1380
  </div>
1381
  </div>
 
1386
  <div class="space-y-3">
1387
  <div>
1388
  <label class="block text-sm font-medium text-gray-700 mb-1">參加對象</label>
1389
+ <input type="text" id="var-participants" value="全國公私立國小學生(本屆小六畢業生亦可參加)" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
 
 
 
 
1390
  </div>
1391
+ <!-- Removed methodIntro as it's now hardcoded in template -->
1392
  </div>
1393
  </div>
1394
 
 
1398
  <div class="space-y-3">
1399
  <div>
1400
  <label class="block text-sm font-medium text-gray-700 mb-1">第一關標題</label>
1401
+ <input type="text" id="var-stage1Title" value="平台陪練寫作積分" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1402
  </div>
1403
  <div>
1404
+ <label class="block text-sm font-medium text-gray-700 mb-1">活動截止日期</label>
1405
+ <input type="text" id="var-stage1Period" value="2025年8月30" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1406
  </div>
1407
  <div>
1408
+ <label class="block text-sm font-medium text-gray-700 mb-1">文章數量要求</label>
1409
+ <input type="text" id="var-stage1Requirements" value="三篇文章" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1410
  </div>
1411
  <div>
1412
  <label class="block text-sm font-medium text-gray-700 mb-1">積分獎標題</label>
1413
+ <input type="text" id="var-stage1AwardTitle" value="積分獎:企業參訪體驗" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1414
+ </div>
1415
+ <div>
1416
+ <label class="block text-sm font-medium text-gray-700 mb-1">積分獎截止日期</label>
1417
+ <input type="text" id="var-stage1AwardEndDate" value="2025年8月31日" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1418
  </div>
1419
  <div>
1420
+ <label class="block text-sm font-medium text-gray-700 mb-1">積分獎名額</label>
1421
+ <input type="text" id="var-stage1AwardQuota" value="三十" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1422
  </div>
1423
  </div>
1424
  </div>
 
1429
  <div class="space-y-3">
1430
  <div>
1431
  <label class="block text-sm font-medium text-gray-700 mb-1">第二關標題</label>
1432
+ <input type="text" id="var-stage2Title" value="徵文比賽" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1433
  </div>
1434
  <div>
1435
  <label class="block text-sm font-medium text-gray-700 mb-1">活動期間</label>
1436
+ <input type="text" id="var-stage2Period" value="2025年9月1日至9月30" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1437
  </div>
1438
  <div>
1439
  <label class="block text-sm font-medium text-gray-700 mb-1">邀請通知</label>
1440
+ <textarea id="var-stage2Notice" rows="2" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">通過第一關的學員將於8月31日收到徵文比賽邀請通知及徵文題目</textarea>
1441
  </div>
1442
  <div>
1443
  <label class="block text-sm font-medium text-gray-700 mb-1">徵文獎標題</label>
1444
+ <input type="text" id="var-stage2AwardTitle" value="徵文獎:企業受獎殊榮" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1445
  </div>
1446
  <div>
1447
  <label class="block text-sm font-medium text-gray-700 mb-1">徵文獎說明</label>
1448
+ <textarea id="var-stage2AwardDesc" rows="3" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">初審入圍前一百名者可獲得電子獎狀一幀,複審前三十名者,榮獲蒞臨聯發科技頒獎,現場公布得獎獎項</textarea>
1449
  </div>
1450
  </div>
1451
  </div>
 
1456
  <div class="space-y-3">
1457
  <div>
1458
  <label class="block text-sm font-medium text-gray-700 mb-1">個人報名說明</label>
1459
+ <textarea id="var-personalRegDesc" rows="3" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">請先填寫線上表單進行報名,平台會寄送確認信件開通您的帳號。完成報名後,即可開始在樂寫公益平台發表文章,累積寫作積分。</textarea>
1460
  </div>
1461
  <div>
1462
  <label class="block text-sm font-medium text-gray-700 mb-1">團體報名說明</label>
1463
+ <textarea id="var-schoolRegDesc" rows="3" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">以學校或班級為單位填寫表單報名,樂寫專員將主動與校方聯繫,協助學生集體開通帳號,讓老師能夠更便利帶領全班參與這次有意義的寫作活動。</textarea>
1464
  </div>
1465
  </div>
1466
  </div>
 
1471
  <div class="space-y-3">
1472
  <div>
1473
  <label class="block text-sm font-medium text-gray-700 mb-1">評選條件 (JSON 格式)</label>
1474
+ <textarea id="var-requirements" rows="4" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent font-mono text-xs">[
1475
  {"title": "字數與格式要求", "content": "徵文字數至少五百字。電腦打字需使用全形符號;於樂寫公益學習網平台個人帳號上傳。"},
1476
  {"title": "原創性要求", "content": "作品必須未曾在任何形式的平面和網路媒體(樂寫公益平台除外)發表,嚴禁抄襲及代筆(包括AI),違者取消資格。"},
1477
  {"title": "投稿注意事項", "content": "稿件送出前,請確認內容正確性(包含錯字、斷行、特殊字、空格等)。為公平起見,投稿後不可再修改文章。"}
 
1479
  </div>
1480
  <div>
1481
  <label class="block text-sm font-medium text-gray-700 mb-1">評分標準 (JSON 格式)</label>
1482
+ <textarea id="var-scoringCriteria" rows="6" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent font-mono text-xs">[
1483
  {"item": "內容與結構", "percentage": "25%", "description": "切合題旨,思想積極健康,論點合理,文字生動;結構嚴謹,行文流暢,兼具廣度與深度。"},
1484
  {"item": "邏輯與修辭", "percentage": "25%", "description": "邏輯分明,條理清晰,敘事明白;用字遣詞合宜,修辭靈活優美。"},
1485
  {"item": "創意與觀察", "percentage": "20%", "description": "富含想像力,觀察微小細節,洞悉人性幽微。"},
 
1489
  </div>
1490
  <div>
1491
  <label class="block text-sm font-medium text-gray-700 mb-1">注意事項 (JSON 格式)</label>
1492
+ <textarea id="var-notes" rows="3" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent font-mono text-xs">[
1493
  {"title": "創作注意事項", "content": "作文以白話文寫作,不得使用文言文、詩歌、韻文寫作;使用標準字體,並詳加全形標點符號;內文不得書寫姓名、校名等個人資訊"},
1494
  {"title": "評審辦法", "content": "本比賽採初審、複審、決審三輪制,聘請樂寫平台資深志工教練群、知名作家、專家學者、與聯發科技長官共同參與評比,確保評選過程公平公正。"}
1495
  ]</textarea>
1496
  </div>
1497
  <div>
1498
  <label class="block text-sm font-medium text-gray-700 mb-1">獎勵辦法 (JSON 格式)</label>
1499
+ <textarea id="var-awards" rows="8" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent font-mono text-xs">[
1500
  {
1501
  "title": "勤筆獎勵—企業參訪體驗",
1502
  "description": "從報名活動到8/31前,於樂寫公益學習平台積分最高的前三十名得主,可獲得聯發科技企業參訪活動體驗資格。"
 
1517
  </div>
1518
  <div>
1519
  <label class="block text-sm font-medium text-gray-700 mb-1">獎勵備註</label>
1520
+ <input type="text" id="var-awardNote" value="得獎者將獲頒獎盃一座,並成為聯發科技「世界觀察員」推廣大使!" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1521
  </div>
1522
  </div>
1523
  </div>
 
1528
  <div class="space-y-3">
1529
  <div>
1530
  <label class="block text-sm font-medium text-gray-700 mb-1">頒獎典禮</label>
1531
+ <textarea id="var-ceremonyDate" rows="2" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">2025年11月17日(星期一)於聯發科技總部舉行盛大頒獎典禮,並於當日公告最終得獎名單。得獎者將獲邀參加,一同分享寫作的喜悅與成果!</textarea>
1532
  </div>
1533
  <div>
1534
  <label class="block text-sm font-medium text-gray-700 mb-1">活動網頁</label>
1535
+ <input type="url" id="var-websiteUrl" value="https://www.colearn30.com/" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1536
  </div>
1537
  <div>
1538
  <label class="block text-sm font-medium text-gray-700 mb-1">聯絡說明</label>
1539
+ <textarea id="var-contactInfo" rows="2" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">如有任何問題,歡迎透過樂寫公益學習臉書聯繫我們</textarea>
1540
+ </div>
1541
+ <div>
1542
+ <label class="block text-sm font-medium text-gray-700 mb-1">Facebook 頁面</label>
1543
+ <input type="url" id="var-facebookUrl" value="https://www.facebook.com/CoWrite30" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1544
  </div>
1545
  </div>
1546
  </div>
1547
  <div>
1548
+ <button onclick="addCustomVariable()" class="text-green-600 hover:text-green-800 text-sm font-medium">
1549
  <i class="fas fa-plus-circle mr-1"></i>新增自訂變數
1550
  </button>
1551
  </div>
 
1556
 
1557
  <!-- JSON Mode -->
1558
  <div id="variables-json" class="hidden">
1559
+ <textarea id="variables" class="w-full h-96 p-4 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent font-mono text-sm" placeholder='{"mainTitle": "主標題", "theme": "主題"}'>
1560
  {
1561
  "mainTitle": "2025聯發科技公益活動",
1562
  "theme": "世界觀察員計畫",
 
1564
  "personalRegUrl": "https://www.colearn30.com/",
1565
  "schoolRegUrl": "https://www.facebook.com/CoWrite30",
1566
  "participants": "全國公私立國小學生(本屆小六畢業生亦可參加)",
 
1567
  "stage1Title": "平台陪練寫作積分",
1568
+ "stage1Period": "2025年8月30",
1569
+ "stage1Requirements": "三篇文章",
1570
  "stage1AwardTitle": "積分獎:企業參訪體驗",
1571
+ "stage1AwardEndDate": "2025年8月31",
1572
+ "stage1AwardQuota": "三十",
1573
  "stage2Title": "徵文比賽",
1574
+ "stage2Period": "2025年9月1日至9月30",
1575
  "stage2Notice": "通過第一關的學員將於8月31日收到徵文比賽邀請通知及徵文題目",
1576
  "stage2AwardTitle": "徵文獎:企業受獎殊榮",
1577
  "stage2AwardDesc": "初審入圍前一百名者可獲得電子獎狀一幀,複審前三十名者,榮獲蒞臨聯發科技頒獎,現場公布得獎獎項",
 
1614
  "awardNote": "得獎者將獲頒獎盃一座,並成為聯發科技「世界觀察員」推廣大使!",
1615
  "ceremonyDate": "2025年11月17日(星期一)於聯發科技總部舉行盛大頒獎典禮,並於當日公告最終得獎名單。得獎者將獲邀參加,一同分享寫作的喜悅與成果!",
1616
  "websiteUrl": "https://www.colearn30.com/",
1617
+ "contactInfo": "如有任何問題,歡迎透過樂寫公益學習臉書聯繫我們",
1618
+ "facebookUrl": "https://www.facebook.com/CoWrite30"
1619
  }</textarea>
1620
  </div>
1621
  </div>
 
1623
  </div>
1624
  </div>
1625
 
1626
+ <!-- Right Panel - Preview (Default) -->
1627
  <div class="space-y-4">
1628
  <div class="flex items-center justify-between">
1629
+ <div class="flex items-center gap-4">
1630
+ <button onclick="toggleView('preview')" id="preview-view-btn" class="text-lg font-medium text-gray-700 px-3 py-1 rounded-lg transition-all duration-200 border-b-2 border-green-600">
1631
+ <i class="fas fa-eye mr-2"></i>預覽
1632
+ </button>
1633
+ <button onclick="toggleView('source')" id="source-view-btn" class="text-lg font-medium text-gray-500 px-3 py-1 rounded-lg transition-all duration-200 border-b-2 border-transparent hover:text-gray-700">
1634
+ <i class="fas fa-code mr-2"></i>HTML 原始碼
1635
+ </button>
1636
+ </div>
1637
  <div class="flex gap-2">
1638
+ <button onclick="openPreviewPopup()" class="text-sm text-green-600 hover:text-green-800">
1639
+ <i class="fas fa-external-link-alt mr-1"></i>彈出視窗
1640
  </button>
1641
+ <button onclick="copyToClipboard()" class="text-sm text-green-600 hover:text-green-800">
1642
  <i class="fas fa-copy mr-1"></i>複製
1643
  </button>
1644
+ <button onclick="downloadHTML()" class="text-sm text-green-600 hover:text-green-800">
1645
  <i class="fas fa-download mr-1"></i>下載
1646
  </button>
1647
  </div>
1648
  </div>
1649
+
1650
+ <!-- Preview View (Default) -->
1651
+ <div id="preview-view" class="bg-white rounded-lg p-4 split-panel" style="overflow-y: auto; max-height: calc(100vh - 250px);">
1652
+ <iframe id="preview-frame" class="w-full h-full min-h-[600px] border-0" srcdoc="<div style='display: flex; align-items: center; justify-content: center; height: 100vh; color: #999; font-size: 18px;'>點擊「套用變數並生成 HTML」按鈕後,這裡會顯示預覽</div>"></iframe>
1653
+ </div>
1654
+
1655
+ <!-- Source Code View -->
1656
+ <div id="source-view" class="bg-gray-900 rounded-lg p-4 split-panel hidden" style="overflow-y: auto; max-height: calc(100vh - 250px);">
1657
  <pre class="text-green-400 text-sm font-mono" id="source-code"><code><!-- 點擊「套用變數並生成 HTML」按鈕後,這裡會顯示渲染後的 HTML 原始碼 --></code></pre>
1658
  </div>
1659
  </div>
 
1663
 
1664
  <script>
1665
  let currentTab = 'template';
1666
+ const awardsData = [];
1667
 
1668
  // No more tab switching needed
1669
 
 
1788
  }
1789
  }
1790
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1791
 
1792
  function copyToClipboard() {
1793
  const sourceCode = document.getElementById('source-code').textContent;
 
1924
  const div = document.createElement('div');
1925
  div.className = 'flex gap-2';
1926
  div.innerHTML = `
1927
+ <input type="text" id="custom-key-${customVarCount}" placeholder="變數名稱" class="flex-1 px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1928
+ <input type="text" id="custom-var-${customVarCount}" placeholder="變數值" class="flex-1 px-4 py-2 border rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent">
1929
  <button onclick="removeCustomVariable(this)" class="px-3 py-2 text-red-600 hover:text-red-800">
1930
  <i class="fas fa-trash"></i>
1931
  </button>
 
1959
  // Add event listeners for form inputs
1960
  const formFields = [
1961
  'mainTitle', 'theme', 'headerDescription', 'personalRegUrl', 'schoolRegUrl',
1962
+ 'participants', 'stage1Title', 'stage1Period', 'stage1Requirements',
1963
+ 'stage1AwardTitle', 'stage1AwardEndDate', 'stage1AwardQuota', 'stage2Title',
1964
+ 'stage2Period', 'stage2Notice', 'stage2AwardTitle', 'stage2AwardDesc',
1965
+ 'personalRegDesc', 'schoolRegDesc', 'awardNote', 'ceremonyDate',
1966
+ 'websiteUrl', 'contactInfo', 'facebookUrl'
1967
  ];
1968
 
1969
  // Remove auto-update listeners - only sync form to JSON when switching modes
1970
 
1971
+ // Progress control functions
1972
+ function showProgress() {
1973
+ const infoText = document.getElementById('info-text');
1974
+ const progressInline = document.getElementById('progress-inline');
1975
 
1976
+ infoText.style.display = 'none';
1977
+ progressInline.style.display = 'flex';
1978
+
1979
+ // Reset all steps to pending
1980
+ for (let i = 1; i <= 5; i++) {
1981
+ const step = document.getElementById(`step-${i}`);
1982
+ step.className = 'progress-step pending';
1983
+ const icon = step.querySelector('.step-icon');
1984
+ icon.innerHTML = i;
1985
  }
1986
 
1987
+ // Reset connectors
1988
+ for (let i = 1; i <= 4; i++) {
1989
+ const connector = document.getElementById(`connector-${i}`);
1990
+ connector.classList.remove('completed');
1991
+ }
1992
+ }
1993
+
1994
+ function hideProgress() {
1995
+ setTimeout(() => {
1996
+ const infoText = document.getElementById('info-text');
1997
+ const progressInline = document.getElementById('progress-inline');
1998
+
1999
+ progressInline.style.display = 'none';
2000
+ infoText.style.display = 'flex';
2001
+ }, 3000);
2002
+ }
2003
+
2004
+ function updateProgressConnector(stepNumber) {
2005
+ if (stepNumber > 1) {
2006
+ const connector = document.getElementById(`connector-${stepNumber - 1}`);
2007
+ if (connector) {
2008
+ connector.classList.add('completed');
2009
+ }
2010
+ }
2011
+ }
2012
+
2013
+ function updateStep(stepNumber, status, message = '') {
2014
+ const step = document.getElementById(`step-${stepNumber}`);
2015
+ const icon = step.querySelector('.step-icon');
2016
 
2017
+ // Remove all status classes
2018
+ step.classList.remove('pending', 'active', 'completed', 'error');
2019
+ step.classList.add(status);
 
2020
 
2021
+ // Update icon
2022
+ if (status === 'active') {
2023
+ icon.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
2024
+ } else if (status === 'completed') {
2025
+ icon.innerHTML = '<i class="fas fa-check"></i>';
2026
+ updateProgressConnector(stepNumber);
2027
+ } else if (status === 'error') {
2028
+ icon.innerHTML = '<i class="fas fa-times"></i>';
2029
+ } else {
2030
+ icon.innerHTML = stepNumber;
2031
  }
2032
 
2033
+ // Update description if provided
2034
+ if (message) {
2035
+ const description = step.querySelector('.step-description');
2036
+ if (description) {
2037
+ description.textContent = message;
2038
+ }
2039
+ }
2040
+ }
2041
+
2042
+ function completeProgress(success = true) {
2043
+ if (success) {
2044
+ // All connectors should be completed when progress is done
2045
+ for (let i = 1; i <= 4; i++) {
2046
+ updateProgressConnector(i + 1);
2047
+ }
2048
+ hideProgress();
2049
  }
2050
+ }
2051
+
2052
+ // Helper function for delays
2053
+ function sleep(ms) {
2054
+ return new Promise(resolve => setTimeout(resolve, ms));
2055
+ }
2056
+
2057
+ // Add apply variables function with progress
2058
+ async function applyVariables() {
2059
+ console.log('========== 開始生成 HTML ==========');
2060
+ showProgress();
2061
 
2062
  try {
2063
+ // Step 1: 驗證表單資料
2064
+ updateStep(1, 'active', '正在檢查表單資料...');
2065
+ await sleep(500);
2066
+
2067
+ console.log('Step 1: 當前模式:', variableMode);
2068
+ if (variableMode === 'form') {
2069
+ console.log('Step 1.1: 執行 updateJsonFromForm()');
2070
+ updateJsonFromForm();
2071
+ }
2072
+
2073
+ updateStep(1, 'completed', '表單資料驗證完成');
2074
+
2075
+ // Step 2: 準備模板內容
2076
+ updateStep(2, 'active', '正在載入模板內容...');
2077
+ await sleep(300);
2078
+
2079
+ const templateContent = document.getElementById('template').value;
2080
+ const variablesContent = document.getElementById('variables').value;
2081
+
2082
+ console.log('Step 2: 準備發送的資料');
2083
+ console.log('Template 長度:', templateContent.length, '字元');
2084
+ console.log('Variables 內容:', variablesContent);
2085
+
2086
+ // 驗證 JSON
2087
+ try {
2088
+ const parsedVariables = JSON.parse(variablesContent);
2089
+ console.log('JSON 解析成功, 變數 keys:', Object.keys(parsedVariables));
2090
+ } catch (e) {
2091
+ console.error('JSON 解析失敗:', e);
2092
+ updateStep(2, 'error', 'JSON 格式錯誤');
2093
+ throw new Error('變數 JSON 格式不正確');
2094
+ }
2095
+
2096
+ updateStep(2, 'completed', '模板內容準備完成');
2097
+
2098
+ // Step 3: 發送到伺服器
2099
+ updateStep(3, 'active', '正在發送資料到伺服器...');
2100
+ await sleep(200);
2101
+
2102
+ const formData = new FormData();
2103
+ formData.append('template_content', templateContent);
2104
+ formData.append('variables', variablesContent);
2105
+
2106
+ console.log('FormData 準備完成');
2107
+ updateStep(3, 'completed', '資料發送成功');
2108
+
2109
+ // Step 4: 渲染 HTML
2110
+ updateStep(4, 'active', '伺服器正在處理模板...');
2111
+
2112
  const response = await fetch('/api/preview', {
2113
  method: 'POST',
2114
  body: formData
2115
  });
2116
 
2117
+ console.log('收到伺服器回應, status:', response.status);
 
 
2118
 
 
2119
  const result = await response.json();
2120
+ console.log('解析回應 JSON 完成');
 
2121
 
2122
  if (result.debug) {
2123
  console.log('Debug 資訊:', result.debug);
2124
  }
2125
 
2126
  if (result.error) {
2127
+ console.error('後端回傳錯誤:', result.error);
2128
+ updateStep(4, 'error', `渲染失敗: ${result.error}`);
 
 
 
 
2129
  showToast(`錯誤:${result.error}`, 'error');
2130
  document.getElementById('source-code').innerHTML = `<code style="color: #ef4444;">錯誤:${result.error}</code>`;
2131
+ completeProgress(false);
2132
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2133
  }
 
 
 
 
 
2134
 
2135
+ updateStep(4, 'completed', 'HTML 渲染完成');
2136
+
2137
+ // Step 5: 完成生成
2138
+ updateStep(5, 'active', '正在顯示結果...');
2139
+ await sleep(300);
2140
+
2141
+ console.log('HTML 生成成功, 長度:', result.html.length, '字元');
2142
+
2143
+ // Store the generated HTML
2144
+ generatedHTML = result.html;
2145
+
2146
+ // Display HTML source code with syntax highlighting
2147
+ const htmlCode = result.html;
2148
+ const escapedHtml = htmlCode
2149
+ .replace(/&/g, '&amp;')
2150
+ .replace(/</g, '&lt;')
2151
+ .replace(/>/g, '&gt;')
2152
+ .replace(/"/g, '&quot;')
2153
+ .replace(/'/g, '&#039;');
2154
+
2155
+ document.getElementById('source-code').innerHTML = `<code>${escapedHtml}</code>`;
2156
+
2157
+ // Always update the preview since it's the default view
2158
+ const iframe = document.getElementById('preview-frame');
2159
+ iframe.srcdoc = generatedHTML;
2160
+
2161
+ updateStep(5, 'completed', 'HTML 已成功生成並顯示');
2162
+ showToast('HTML 已成功生成!', 'success');
2163
+
2164
+ console.log('HTML 已顯示在畫面上');
2165
+ completeProgress(true);
2166
+
2167
+ } catch (error) {
2168
+ console.error('發生錯誤:', error);
2169
  showToast(`錯誤:${error.message}`, 'error');
2170
+ completeProgress(false);
2171
  }
2172
 
2173
  console.log('========== 結束生成 HTML ==========');
 
2175
 
2176
  // Store generated HTML globally
2177
  let generatedHTML = '';
2178
+ let currentView = 'preview';
2179
 
2180
+ // Toggle between source code and preview view
2181
+ function toggleView(view) {
2182
+ currentView = view;
2183
+
2184
+ if (view === 'source') {
2185
+ // Check if HTML has been generated
2186
+ if (!generatedHTML) {
2187
+ showToast('請先生成 HTML!', 'error');
2188
+ return;
2189
+ }
2190
+
2191
+ document.getElementById('source-view').classList.remove('hidden');
2192
+ document.getElementById('preview-view').classList.add('hidden');
2193
+ document.getElementById('source-view-btn').classList.add('text-gray-700', 'border-green-600');
2194
+ document.getElementById('source-view-btn').classList.remove('text-gray-500', 'border-transparent');
2195
+ document.getElementById('preview-view-btn').classList.add('text-gray-500', 'border-transparent');
2196
+ document.getElementById('preview-view-btn').classList.remove('text-gray-700', 'border-green-600');
2197
+ } else {
2198
+ document.getElementById('source-view').classList.add('hidden');
2199
+ document.getElementById('preview-view').classList.remove('hidden');
2200
+ document.getElementById('preview-view-btn').classList.add('text-gray-700', 'border-green-600');
2201
+ document.getElementById('preview-view-btn').classList.remove('text-gray-500', 'border-transparent');
2202
+ document.getElementById('source-view-btn').classList.add('text-gray-500', 'border-transparent');
2203
+ document.getElementById('source-view-btn').classList.remove('text-gray-700', 'border-green-600');
2204
+
2205
+ // Update preview iframe if HTML is generated
2206
+ if (generatedHTML) {
2207
+ const iframe = document.getElementById('preview-frame');
2208
+ iframe.srcdoc = generatedHTML;
2209
+ }
2210
+ }
2211
+ }
2212
+
2213
+ // Open preview in popup window
2214
+ function openPreviewPopup() {
2215
  if (!generatedHTML) {
2216
+ showToast('請先生成 HTML', 'error');
2217
  return;
2218
  }
2219
 
 
2220
  const width = 1200;
2221
  const height = 800;
2222
  const left = (window.screen.width - width) / 2;
2223
  const top = (window.screen.height - height) / 2;
2224
 
 
2225
  const previewWindow = window.open(
2226
  '',
2227
  'preview_popup',
2228
+ `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`
2229
  );
2230
 
2231
  if (previewWindow) {