download.html 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Download - {{ filename }} - HokoriTemp</title>
  7. <!-- SVG Favicon -->
  8. <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
  9. <link rel="stylesheet" href="/static/css/styles.css">
  10. <!-- Fancybox CSS and JS -->
  11. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css">
  12. <script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.umd.js"></script>
  13. <script src="https://cdn.tailwindcss.com"></script>
  14. <script>
  15. (function() {
  16. const savedTheme = localStorage.getItem('theme') || 'light';
  17. if (savedTheme === 'dark') {
  18. document.documentElement.classList.add('dark');
  19. }
  20. })();
  21. tailwind.config = {
  22. darkMode: 'class',
  23. theme: {
  24. extend: {
  25. animation: {
  26. 'fade-in': 'fadeIn 0.3s ease-in-out',
  27. 'slide-up': 'slideUp 0.3s ease-out',
  28. 'pulse-slow': 'pulse 2s infinite'
  29. }
  30. }
  31. }
  32. }
  33. </script>
  34. </head>
  35. <body class="bg-gray-50 dark:bg-gray-900 min-h-screen transition-colors duration-300">
  36. <div class="container mx-auto px-4 py-8 max-w-4xl">
  37. <!-- Header -->
  38. <div class="flex justify-between items-center mb-8">
  39. <div class="text-center flex-1">
  40. <h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">HokoriTemp</h1>
  41. <p class="text-gray-600 dark:text-gray-400">File Download</p>
  42. </div>
  43. <!-- Theme Toggle Button -->
  44. <button id="themeToggle" class="p-2 rounded-lg bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors ml-4">
  45. <!-- Sun Icon (Light Mode) -->
  46. <svg id="sunIcon" class="w-5 h-5 text-gray-800 dark:text-gray-200 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  47. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path>
  48. </svg>
  49. <!-- Moon Icon (Dark Mode) -->
  50. <svg id="moonIcon" class="w-5 h-5 text-gray-800 dark:text-gray-200 block dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  51. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
  52. </svg>
  53. </button>
  54. </div>
  55. <!-- File Info Section -->
  56. <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-6 transition-colors duration-300">
  57. <div class="flex items-center space-x-4 mb-4">
  58. <div class="flex-shrink-0">
  59. <svg class="w-12 h-12 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  60. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
  61. </svg>
  62. </div>
  63. <div class="flex-1">
  64. <h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-1">{{ filename }}</h2>
  65. <div class="flex flex-wrap gap-4 text-sm text-gray-600 dark:text-gray-400">
  66. <span>Size: {{ "%.2f"|format(file_size / 1024 / 1024) }} MB</span>
  67. {% if mime_type %}
  68. <span>Type: {{ mime_type }}</span>
  69. {% endif %}
  70. <span>Expires: {{ expiration_date[:19].replace('T', ' ') }}</span>
  71. </div>
  72. </div>
  73. </div>
  74. <!-- Download Button -->
  75. <div class="flex gap-3">
  76. <a href="/download/{{ file_id }}/direct"
  77. class="inline-flex items-center px-6 py-3 bg-blue-600 hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-600 text-white font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
  78. <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  79. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
  80. </svg>
  81. Download File
  82. </a>
  83. <a href="/"
  84. class="inline-flex items-center px-6 py-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800">
  85. <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  86. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"></path>
  87. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5a2 2 0 012-2h2a2 2 0 012 2v0H8v0z"></path>
  88. </svg>
  89. Upload More Files
  90. </a>
  91. </div>
  92. </div>
  93. <!-- Text File Preview -->
  94. {% if is_text_file and file_content %}
  95. <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-300">
  96. <div class="flex items-center justify-between mb-4">
  97. <h3 class="text-lg font-semibold text-gray-900 dark:text-white">File Preview</h3>
  98. <div class="flex items-center space-x-2">
  99. <button onclick="copyTextToClipboard()" class="inline-flex items-center px-3 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors duration-200">
  100. <svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  101. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
  102. </svg>
  103. Copy Text
  104. </button>
  105. <span class="text-sm text-gray-500 dark:text-gray-400">Text file preview</span>
  106. </div>
  107. </div>
  108. <div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 overflow-auto max-h-96">
  109. <pre id="file-content" class="text-sm text-gray-800 dark:text-gray-200 whitespace-pre-wrap font-mono">{{ file_content }}</pre>
  110. </div>
  111. <div class="mt-4 text-xs text-gray-500 dark:text-gray-400">
  112. <p>Preview shows the first part of the file content. Download the file to view the complete content.</p>
  113. </div>
  114. </div>
  115. {% elif is_text_file %}
  116. <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-300">
  117. <div class="text-center py-8">
  118. <svg class="w-12 h-12 mx-auto mb-4 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  119. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
  120. </svg>
  121. <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Preview Not Available</h3>
  122. <p class="text-gray-600 dark:text-gray-400">This text file couldn't be previewed. Please download it to view the content.</p>
  123. </div>
  124. </div>
  125. <!-- Media File Preview -->
  126. {% elif is_media_file %}
  127. <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-300">
  128. <div class="flex items-center justify-between mb-4">
  129. <h3 class="text-lg font-semibold text-gray-900 dark:text-white">Media Preview</h3>
  130. <span class="text-sm text-gray-500 dark:text-gray-400">{{ media_type|title }} file preview</span>
  131. </div>
  132. <div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 flex justify-center">
  133. {% if media_type == 'image' %}
  134. <div class="relative group">
  135. <a href="/preview/{{ file_id }}" data-fancybox="gallery" data-caption="{{ filename }}">
  136. <img src="/preview/{{ file_id }}" alt="{{ filename }}" class="max-w-full max-h-96 rounded-lg shadow-sm cursor-pointer transition-transform duration-200 hover:scale-105" loading="lazy">
  137. </a>
  138. <div class="mt-3 flex flex-col items-center space-y-2">
  139. <p class="text-xs text-gray-500 dark:text-gray-400">Click to view with zoom, fullscreen & advanced controls</p>
  140. <div class="flex space-x-2">
  141. <button onclick="downloadImage('{{ file_id }}', '{{ filename }}')" class="inline-flex items-center px-2 py-1 text-xs font-medium text-white bg-green-600 hover:bg-green-700 rounded transition-colors duration-200">
  142. <svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  143. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3M3 17V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2z"></path>
  144. </svg>
  145. Save Image
  146. </button>
  147. <button onclick="showImageInfo('{{ filename }}', '{{ file_size }}', '{{ mime_type }}')" class="inline-flex items-center px-2 py-1 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded transition-colors duration-200">
  148. <svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  149. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
  150. </svg>
  151. Info
  152. </button>
  153. </div>
  154. </div>
  155. </div>
  156. {% elif media_type == 'video' %}
  157. <video controls class="max-w-full max-h-96 rounded-lg shadow-sm">
  158. <source src="/preview/{{ file_id }}" type="{{ mime_type }}">
  159. Your browser does not support the video tag.
  160. </video>
  161. {% elif media_type == 'audio' %}
  162. <div class="w-full max-w-md">
  163. <div class="flex items-center space-x-4 mb-4">
  164. <svg class="w-12 h-12 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
  165. <path fill-rule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM15.657 6.343a1 1 0 011.414 0A9.972 9.972 0 0119 12a9.972 9.972 0 01-1.929 5.657 1 1 0 11-1.414-1.414A7.971 7.971 0 0017 12c0-2.21-.895-4.21-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 12a5.983 5.983 0 01-.757 2.829 1 1 0 01-1.415-1.415A3.987 3.987 0 0013 12a3.987 3.987 0 00-.172-1.414 1 1 0 010-1.415z" clip-rule="evenodd"></path>
  166. </svg>
  167. <div>
  168. <h4 class="font-medium text-gray-900 dark:text-white">{{ filename }}</h4>
  169. <p class="text-sm text-gray-500 dark:text-gray-400">Audio file</p>
  170. </div>
  171. </div>
  172. <audio controls class="w-full">
  173. <source src="/preview/{{ file_id }}" type="{{ mime_type }}">
  174. Your browser does not support the audio tag.
  175. </audio>
  176. </div>
  177. {% endif %}
  178. </div>
  179. <div class="mt-4 text-xs text-gray-500 dark:text-gray-400">
  180. <p>Media preview is displayed above. Download the file for full quality or offline viewing.</p>
  181. </div>
  182. </div>
  183. <!-- Archive File Preview -->
  184. {% elif is_archive_file and archive_contents %}
  185. <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-300">
  186. <div class="flex items-center justify-between mb-4">
  187. <h3 class="text-lg font-semibold text-gray-900 dark:text-white">Archive Contents</h3>
  188. <span class="text-sm text-gray-500 dark:text-gray-400">{{ archive_contents.type|upper }} archive preview</span>
  189. </div>
  190. <div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 max-h-96 overflow-auto">
  191. <div class="space-y-2">
  192. {% for file in archive_contents.files %}
  193. <div class="flex items-center justify-between py-2 px-3 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700">
  194. <div class="flex items-center space-x-3">
  195. {% if file.type == 'directory' %}
  196. <svg class="w-4 h-4 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
  197. <path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"></path>
  198. </svg>
  199. {% else %}
  200. <svg class="w-4 h-4 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
  201. <path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"></path>
  202. </svg>
  203. {% endif %}
  204. <span class="text-sm font-medium text-gray-900 dark:text-white truncate">{{ file.name }}</span>
  205. </div>
  206. <div class="flex items-center space-x-4 text-xs text-gray-500 dark:text-gray-400">
  207. {% if file.type != 'directory' %}
  208. <span>{{ "%.1f"|format(file.size / 1024) }} KB</span>
  209. {% if file.compressed_size %}
  210. <span class="text-green-600 dark:text-green-400">{{ "%.1f%%"|format((1 - file.compressed_size / file.size) * 100) }} compressed</span>
  211. {% endif %}
  212. {% endif %}
  213. {% if file.date_time %}
  214. <span>{{ "%04d-%02d-%02d"|format(file.date_time[0], file.date_time[1], file.date_time[2]) }}</span>
  215. {% endif %}
  216. </div>
  217. </div>
  218. {% endfor %}
  219. </div>
  220. </div>
  221. <div class="mt-4 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
  222. <p>Archive contains {{ archive_contents.files|length }} items. Download to extract files.</p>
  223. <div class="flex space-x-4">
  224. {% set total_files = archive_contents.files|selectattr('type', 'equalto', 'file')|list|length %}
  225. {% set total_dirs = archive_contents.files|selectattr('type', 'equalto', 'directory')|list|length %}
  226. <span>{{ total_files }} files</span>
  227. {% if total_dirs > 0 %}
  228. <span>{{ total_dirs }} folders</span>
  229. {% endif %}
  230. </div>
  231. </div>
  232. </div>
  233. {% elif is_archive_file %}
  234. <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-300">
  235. <div class="text-center py-8">
  236. <svg class="w-12 h-12 mx-auto mb-4 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  237. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
  238. </svg>
  239. <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Archive Preview Not Available</h3>
  240. <p class="text-gray-600 dark:text-gray-400">This archive file couldn't be previewed. Please download it to view the contents.</p>
  241. </div>
  242. </div>
  243. {% else %}
  244. <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-colors duration-300">
  245. <div class="text-center py-8">
  246. <svg class="w-12 h-12 mx-auto mb-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  247. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
  248. </svg>
  249. <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Binary File</h3>
  250. <p class="text-gray-600 dark:text-gray-400">This file cannot be previewed. Click the download button above to get the file.</p>
  251. </div>
  252. </div>
  253. {% endif %}
  254. </div>
  255. <!-- Toast Notification -->
  256. <div id="toast" class="fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg transform translate-x-full transition-transform duration-300 z-50">
  257. <p id="toastMessage"></p>
  258. </div>
  259. <script>
  260. // Theme management
  261. function initTheme() {
  262. const savedTheme = localStorage.getItem('theme') || 'light';
  263. if (savedTheme === 'dark') {
  264. document.documentElement.classList.add('dark');
  265. } else {
  266. document.documentElement.classList.remove('dark');
  267. }
  268. }
  269. function toggleTheme() {
  270. const isDark = document.documentElement.classList.contains('dark');
  271. if (isDark) {
  272. document.documentElement.classList.remove('dark');
  273. localStorage.setItem('theme', 'light');
  274. } else {
  275. document.documentElement.classList.add('dark');
  276. localStorage.setItem('theme', 'dark');
  277. }
  278. }
  279. // Toast notification
  280. function showToast(message, type = 'success') {
  281. const toast = document.getElementById('toast');
  282. const toastMessage = document.getElementById('toastMessage');
  283. toastMessage.textContent = message;
  284. toast.classList.remove('bg-green-500', 'bg-red-500', 'bg-blue-500');
  285. if (type === 'success') {
  286. toast.classList.add('bg-green-500');
  287. } else if (type === 'error') {
  288. toast.classList.add('bg-red-500');
  289. } else if (type === 'info') {
  290. toast.classList.add('bg-blue-500');
  291. }
  292. toast.classList.remove('translate-x-full');
  293. setTimeout(() => {
  294. toast.classList.add('translate-x-full');
  295. }, 3000);
  296. }
  297. // Copy text to clipboard
  298. function copyTextToClipboard() {
  299. const fileContent = document.getElementById('file-content');
  300. if (!fileContent) {
  301. showToast('No content to copy', 'error');
  302. return;
  303. }
  304. const textToCopy = fileContent.textContent || fileContent.innerText;
  305. if (navigator.clipboard && window.isSecureContext) {
  306. // Use modern clipboard API
  307. navigator.clipboard.writeText(textToCopy).then(() => {
  308. showToast('Text copied to clipboard!', 'success');
  309. }).catch(err => {
  310. console.error('Failed to copy text: ', err);
  311. fallbackCopyTextToClipboard(textToCopy);
  312. });
  313. } else {
  314. // Fallback for older browsers
  315. fallbackCopyTextToClipboard(textToCopy);
  316. }
  317. }
  318. function fallbackCopyTextToClipboard(text) {
  319. const textArea = document.createElement('textarea');
  320. textArea.value = text;
  321. textArea.style.position = 'fixed';
  322. textArea.style.left = '-999999px';
  323. textArea.style.top = '-999999px';
  324. document.body.appendChild(textArea);
  325. textArea.focus();
  326. textArea.select();
  327. try {
  328. const successful = document.execCommand('copy');
  329. if (successful) {
  330. showToast('Text copied to clipboard!', 'success');
  331. } else {
  332. showToast('Failed to copy text', 'error');
  333. }
  334. } catch (err) {
  335. console.error('Fallback: Could not copy text: ', err);
  336. showToast('Failed to copy text', 'error');
  337. }
  338. document.body.removeChild(textArea);
  339. }
  340. // Image functions
  341. function downloadImage(fileId, filename) {
  342. const link = document.createElement('a');
  343. link.href = `/download/${fileId}/direct`;
  344. link.download = filename;
  345. document.body.appendChild(link);
  346. link.click();
  347. document.body.removeChild(link);
  348. showToast('Image download started!', 'success');
  349. }
  350. function showImageInfo(filename, fileSize, mimeType) {
  351. const sizeInKB = Math.round(fileSize / 1024);
  352. const sizeInMB = (fileSize / (1024 * 1024)).toFixed(2);
  353. const sizeText = fileSize > 1024 * 1024 ? `${sizeInMB} MB` : `${sizeInKB} KB`;
  354. const infoMessage = `File: ${filename}\nSize: ${sizeText}\nType: ${mimeType}`;
  355. // Create a custom modal for image info
  356. const modal = document.createElement('div');
  357. modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
  358. modal.innerHTML = `
  359. <div class="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-md mx-4 shadow-xl">
  360. <div class="flex items-center justify-between mb-4">
  361. <h3 class="text-lg font-semibold text-gray-900 dark:text-white">Image Information</h3>
  362. <button onclick="this.closest('.fixed').remove()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
  363. <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  364. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
  365. </svg>
  366. </button>
  367. </div>
  368. <div class="space-y-3">
  369. <div>
  370. <span class="text-sm font-medium text-gray-500 dark:text-gray-400">Filename:</span>
  371. <p class="text-gray-900 dark:text-white break-all">${filename}</p>
  372. </div>
  373. <div>
  374. <span class="text-sm font-medium text-gray-500 dark:text-gray-400">File Size:</span>
  375. <p class="text-gray-900 dark:text-white">${sizeText}</p>
  376. </div>
  377. <div>
  378. <span class="text-sm font-medium text-gray-500 dark:text-gray-400">MIME Type:</span>
  379. <p class="text-gray-900 dark:text-white">${mimeType}</p>
  380. </div>
  381. </div>
  382. <div class="mt-6 flex justify-end">
  383. <button onclick="this.closest('.fixed').remove()" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors duration-200">
  384. Close
  385. </button>
  386. </div>
  387. </div>
  388. `;
  389. document.body.appendChild(modal);
  390. // Close modal when clicking outside
  391. modal.addEventListener('click', function(e) {
  392. if (e.target === modal) {
  393. modal.remove();
  394. }
  395. });
  396. }
  397. // Initialize
  398. document.addEventListener('DOMContentLoaded', function() {
  399. initTheme();
  400. document.getElementById('themeToggle').addEventListener('click', toggleTheme);
  401. });
  402. </script>
  403. <!-- Fancybox Configuration -->
  404. <script>
  405. // Initialize Fancybox
  406. document.addEventListener('DOMContentLoaded', function() {
  407. if (typeof Fancybox !== 'undefined') {
  408. Fancybox.bind('[data-fancybox]', {
  409. // UI options
  410. animated: true,
  411. showClass: 'f-fadeIn',
  412. hideClass: 'f-fadeOut',
  413. // Toolbar options
  414. Toolbar: {
  415. display: {
  416. left: ['infobar'],
  417. middle: ['zoomIn', 'zoomOut', 'toggle1to1', 'rotateCCW', 'rotateCW', 'flipX', 'flipY'],
  418. right: ['slideshow', 'fullscreen', 'thumbs', 'close']
  419. }
  420. },
  421. // Image options
  422. Images: {
  423. zoom: true,
  424. wheel: 'zoom'
  425. },
  426. // Thumbs options
  427. Thumbs: {
  428. showOnStart: false
  429. },
  430. // Slideshow options
  431. Slideshow: {
  432. speed: 3000
  433. }
  434. });
  435. console.log('Fancybox initialized successfully');
  436. }
  437. });
  438. </script>
  439. </body>
  440. </html>