├── .gitignore ├── Assets └── index.jpg ├── LICENSE ├── README.md └── Web ├── .gitignore ├── app.py ├── requirements.txt ├── src └── tailwind.css ├── static ├── QRCode │ └── AlipayQRCode.jpg ├── about.js ├── favicon.png ├── search.js ├── style.css ├── tailwind.css └── tailwind.min.css ├── tailwind.config.js ├── templates ├── about.html └── index.html ├── vercel.json └── zip.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | Web/package*json 3 | .DS_Store 4 | */.DS_Store 5 | web.zip 6 | deploy.sh 7 | generate.sh 8 | .vercel 9 | -------------------------------------------------------------------------------- /Assets/index.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mazzzystar/PodFind/02f6781012daab38c83daf71a0bda7432fa631cc/Assets/index.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GPT-4.0 License 2 | 3 | Copyright (c) 2023 OpenAI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PodFind 2 | [podfind.xyz](https://www.podfind.xyz/) 3 | 4 | A website that help you to know what podcasters think of hot & new things, **every line** of code in the project is written by gpt-4. 5 | 6 | ![](Assets/index.jpg) -------------------------------------------------------------------------------- /Web/.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | venv/ 6 | .env 7 | 8 | # Node 9 | node_modules/ 10 | 11 | # But don't ignore static files 12 | !static/*.css 13 | !static/*.js 14 | !static/*.png 15 | -------------------------------------------------------------------------------- /Web/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, jsonify, url_for 2 | import os 3 | 4 | app = Flask(__name__, 5 | static_url_path='/static', 6 | static_folder='static', 7 | template_folder='templates' 8 | ) 9 | 10 | 11 | @app.route('/about') 12 | def about(): 13 | return render_template('about.html') 14 | 15 | 16 | @app.route('/') 17 | def index(): 18 | return render_template('index.html') 19 | 20 | 21 | @app.route('/log', methods=['POST']) 22 | def log_to_server(): 23 | message = request.json.get('message', '') 24 | print(message) # Log the message to the console 25 | return jsonify(success=True) # Return success status 26 | 27 | 28 | if __name__ == '__main__': 29 | app.run() -------------------------------------------------------------------------------- /Web/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==2.0.1 2 | Flask-Migrate==3.1.0 3 | flask_wtf==1.0.0 4 | Werkzeug==2.0.1 5 | python-dotenv==0.19.0 -------------------------------------------------------------------------------- /Web/src/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /Web/static/QRCode/AlipayQRCode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mazzzystar/PodFind/02f6781012daab38c83daf71a0bda7432fa631cc/Web/static/QRCode/AlipayQRCode.jpg -------------------------------------------------------------------------------- /Web/static/about.js: -------------------------------------------------------------------------------- 1 | function calculateServerCost() { 2 | const startDate = new Date("2023-03-15"); 3 | const dailyCost = 0.27; 4 | const now = new Date(); 5 | const differenceInTime = now.getTime() - startDate.getTime(); 6 | const differenceInDays = Math.ceil(differenceInTime / (1000 * 3600 * 24)); 7 | 8 | const totalCost = (differenceInDays * dailyCost).toFixed(2); 9 | document.getElementById("server-cost").innerHTML = "$"+totalCost; 10 | } 11 | 12 | function showDonationOptions() { 13 | const donationMethods = document.getElementById("donation-methods"); 14 | donationMethods.style.display = "block"; 15 | } 16 | 17 | function showQRCode(id) { 18 | const qrCodeElements = document.querySelectorAll(".qr-code"); 19 | qrCodeElements.forEach((element) => { 20 | element.style.display = "none"; 21 | }); 22 | 23 | const qrCodeToShow = document.getElementById(id); 24 | qrCodeToShow.style.display = "block"; 25 | } 26 | 27 | function showQRCode(id) { 28 | const qrCodeElements = document.querySelectorAll(".qr-code"); 29 | qrCodeElements.forEach((element) => { 30 | element.style.display = "none"; 31 | }); 32 | 33 | const qrCodeToShow = document.getElementById(id); 34 | qrCodeToShow.style.display = "block"; 35 | 36 | // Add a slight delay before scrolling 37 | setTimeout(() => { 38 | // Calculate the position to scroll to 39 | const scrollToPosition = qrCodeToShow.getBoundingClientRect().top + window.pageYOffset - (window.innerHeight / 2); 40 | 41 | // Scroll to the displayed QR code 42 | window.scrollTo({ top: scrollToPosition, behavior: 'smooth' }); 43 | }, 100); // 100ms delay 44 | } 45 | 46 | function showDonationOptions() { 47 | const donationMethods = document.getElementById("donation-methods"); 48 | donationMethods.style.display = "block"; 49 | 50 | // Add a slight delay before scrolling 51 | setTimeout(() => { 52 | // Calculate the position to scroll to 53 | const scrollToPosition = donationMethods.getBoundingClientRect().top + window.pageYOffset - (window.innerHeight / 2); 54 | 55 | // Scroll to the donation methods 56 | window.scrollTo({ top: scrollToPosition, behavior: 'smooth' }); 57 | }, 100); // 100ms delay 58 | } 59 | 60 | function translateAboutPage() { 61 | const userLang = navigator.language || navigator.userLanguage; 62 | 63 | if (userLang.startsWith("zh")) { 64 | // Change the text to Chinese 65 | document.title = "关于PodFind"; 66 | const titleElement = document.querySelector(".title a"); 67 | titleElement.innerHTML = 'PodFind.xyz'; 68 | 69 | const sections = [ 70 | { 71 | selector: "h2:nth-of-type(1)", 72 | text: "1. 初衷" 73 | }, 74 | { 75 | selector: "h2:nth-of-type(2)", 76 | text: "2. 为什么选择播客" 77 | }, 78 | { 79 | selector: "h2:nth-of-type(3)", 80 | text: "3. 免费使用?" 81 | }, 82 | { 83 | selector: "h2:nth-of-type(4)", 84 | text: "4. 未来计划" 85 | } 86 | ]; 87 | 88 | sections.forEach(({ selector, text }) => { 89 | const element = document.querySelector(selector); 90 | element.textContent = text; 91 | }); 92 | 93 | const paragraphs = [ 94 | { 95 | selector: "blockquote", 96 | text: "PodFind.xyz并非ChatGPT,也不是播客搜索引擎。" 97 | }, 98 | { 99 | selector: '.designed-for', 100 | text: "它是一个试图让你借助播客来获取新知的工具,你可以:" 101 | }, 102 | { 103 | selector: "ul li:nth-of-type(1)", 104 | text: "搜索关键词来了解Podcaster们对新事物或事件的看法,如ChatGPT、孔乙己、影视作品。" 105 | }, 106 | { 107 | selector: "ul li:nth-of-type(2)", 108 | text: "点击时间戳,可以只听你感兴趣的部分。部分作品没有时间戳,你可以通过筛选按钮切换。" 109 | }, 110 | { 111 | selector: ".why-podcast", 112 | text: "从形式上,播客要求有录音设备、多人对谈,这既是某种过滤,也给深度观点交锋带来了可能性,所以我倾向于认为播客的信噪比(SNR)更高,你可以了解最有想法的群体怎么看待新事物。" 113 | }, 114 | { 115 | selector: ".pure-free", 116 | text: "没错!纯粹出于好玩,服务器会持续产生费用,所以你的捐赠会让它活得更久一点。(2023/10更新:服务部署在Vercel上,已经不再有新费用了。)" 117 | }, 118 | { 119 | selector: '.support-my-product', 120 | text: "或者,你也可以支持我的其他产品(iOS):" 121 | }, 122 | { 123 | selector: ".support-my-product + ul li:nth-child(1) a", 124 | text: "寻隐 - 用文字找照片" 125 | }, 126 | { 127 | selector: ".support-my-product + ul li:nth-child(2) a", 128 | text: "Whisper Notes - 离线、准确的语音识别" 129 | }, 130 | { 131 | selector: ".support-my-product + ul li:nth-child(3) a", 132 | text: "Dolores: 虚拟陪伴" 133 | }, 134 | { 135 | selector: ".future-plan", 136 | text: "这个版本实在太简陋了,很多音频没有时间戳。将来(如果有钱),我会添加Whisper为每一集构建时间戳,这样你搜任何关键词,都能直接只听与之有关的片段。" 137 | }, 138 | { 139 | selector: ".have-fun", 140 | text: "总之,玩得开心!如果你想联系我:myfancoo@gmail.com" 141 | }, 142 | { 143 | selector: "p.mb-4.text-center", 144 | update: (element) => { 145 | element.innerHTML = "至今的服务器费用:"; 146 | } 147 | }, 148 | { 149 | selector: "#donation-message", 150 | text: "捐赠" 151 | }, 152 | { 153 | selector: "#alipay-qr", 154 | alt: "支付宝二维码" 155 | }, 156 | { 157 | selector: "#donation-methods a:nth-of-type(1)", 158 | text: "支付宝" 159 | }, 160 | { 161 | selector: "#donation-methods a:nth-of-type(2)", 162 | text: "Paypal" 163 | }, 164 | { 165 | selector: "#alipay-qr", 166 | alt: "支付宝二维码" 167 | }, 168 | { 169 | selector: "p.mb-4.text-center", 170 | update: (element) => { 171 | element.innerHTML = "服务器费用至今:"; 172 | } 173 | }, 174 | ]; 175 | 176 | paragraphs.forEach(({ selector, text, alt }) => { 177 | const element = document.querySelector(selector); 178 | if (element) { 179 | if (text) { 180 | element.innerHTML = text; 181 | } 182 | if (alt) { 183 | element.setAttribute("alt", alt); 184 | } 185 | } else { 186 | console.error(`Element not found: ${selector}`); 187 | } 188 | }); 189 | 190 | calculateServerCost(); 191 | } else { 192 | calculateServerCost(); 193 | } 194 | 195 | updateDonationMessage(); 196 | } 197 | 198 | function updateDonationMessage() { 199 | const lang = navigator.language || navigator.userLanguage; 200 | const message = document.getElementById('donation-message'); 201 | 202 | if (lang.startsWith('zh')) { 203 | message.innerHTML = '如果你捐款,请务必邮件联系我,你的头像和名字会在这里显示,以感谢你让它活得更久了一点。'; 204 | } else { 205 | message.innerHTML = 'Your donation keeps this website alive. Email me to display your name and avatar as thanks.'; 206 | } 207 | } 208 | 209 | // Call the functions when the page loads 210 | window.addEventListener("load", () => { 211 | translateAboutPage(); 212 | }); 213 | -------------------------------------------------------------------------------- /Web/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mazzzystar/PodFind/02f6781012daab38c83daf71a0bda7432fa631cc/Web/static/favicon.png -------------------------------------------------------------------------------- /Web/static/search.js: -------------------------------------------------------------------------------- 1 | let currentAudio = null; 2 | 3 | function applyFilter(data) { 4 | const filterTimestamps = document.getElementById("filterTimestamps"); 5 | const timestampRegex = /(\d{1,2}:\d{2}(:\d{2})?)/; 6 | 7 | if (filterTimestamps.checked) { 8 | data = data.filter((episode) => timestampRegex.test(episode.description)); 9 | } 10 | 11 | return data; 12 | } 13 | 14 | async function executeSearch() { 15 | const searchButton = document.getElementById("searchButton"); 16 | const searchButtonText = document.getElementById("searchButtonText"); 17 | const searchButtonProgress = document.getElementById("searchButtonProgress"); 18 | 19 | const searchInput = document.getElementById("search"); 20 | const filterTimestamps = document.getElementById("filterTimestamps"); 21 | const searchResultsDiv = document.getElementById("searchResults"); 22 | const filterContainer = document.getElementById("filterContainer"); 23 | const query = searchInput.value.trim().toLowerCase(); 24 | 25 | // Log the search query to the server 26 | const now = new Date(); 27 | const formattedDateTime = now.toLocaleString(); 28 | const logMessage = `[${formattedDateTime}] User query: ${query}`; 29 | logToServer(logMessage); 30 | 31 | // Disable the button and show the progress indicator 32 | searchButton.disabled = true; 33 | searchButtonText.classList.add("hidden"); 34 | searchButtonProgress.classList.remove("hidden"); 35 | 36 | let data = await fetchData(query); 37 | 38 | // Sort episodes by releaseDate in descending order 39 | data.sort((a, b) => new Date(b.releaseDate) - new Date(a.releaseDate)); 40 | 41 | // Apply the filter 42 | data = applyFilter(data); 43 | 44 | searchResultsDiv.innerHTML = ""; 45 | 46 | // Check if there are no results when the Timestamp filter is on 47 | if (data.length === 0 && filterTimestamps.checked) { 48 | // Turn off the Timestamp filter and apply the filter again 49 | filterTimestamps.checked = false; 50 | data = applyFilter(await fetchData(query)); 51 | } 52 | 53 | if (data.length > 0) { 54 | filterContainer.classList.remove("hidden"); 55 | data.forEach((episode) => { 56 | const episodeDiv = document.createElement("div"); 57 | episodeDiv.classList.add("mb-6", "bg-white", "shadow-md", "rounded", "p-4", "flex", "items-start"); 58 | 59 | const coverImage = document.createElement("img"); 60 | coverImage.src = episode.artworkUrl600 || episode.artworkUrl100 || episode.artworkUrl60 || episode.artworkUrl30; 61 | coverImage.alt = `${episode.trackName} cover image`; 62 | coverImage.classList.add("w-20", "h-20", "mr-4"); 63 | 64 | const contentDiv = document.createElement("div"); 65 | 66 | const titleLink = document.createElement("a"); 67 | titleLink.href = episode.collectionViewUrl; 68 | titleLink.target = "_blank"; 69 | titleLink.classList.add("text-blue-600", "hover:text-blue-800"); 70 | 71 | const title = document.createElement("h2"); 72 | title.innerHTML = highlightMatches(episode.trackName, query); 73 | title.classList.add("text-lg", "font-bold", "mb-2", "overflow-wrap-anywhere"); 74 | 75 | titleLink.appendChild(title); 76 | contentDiv.appendChild(titleLink); 77 | 78 | const artist = document.createElement("p"); 79 | artist.textContent = `Producer: ${episode.collectionName}`; 80 | artist.classList.add("text-sm", "mb-2"); 81 | 82 | const releaseDate = new Date(episode.releaseDate); 83 | const formattedDate = releaseDate.toLocaleDateString("en-US", { 84 | year: "numeric", 85 | month: "long", 86 | day: "numeric", 87 | }); 88 | 89 | const date = document.createElement("p"); 90 | date.textContent = `Date: ${formattedDate}`; 91 | date.classList.add("text-sm", "mb-2"); 92 | 93 | const description = document.createElement("p"); 94 | const truncatedDescriptionText = truncateBeforeTimestamp(episode.description, 4); 95 | description.innerHTML = highlightMatches(linkTimestamps(truncatedDescriptionText, episode.previewUrl), query); 96 | description.classList.add("text-sm", "overflow-wrap-anywhere"); 97 | 98 | contentDiv.appendChild(artist); 99 | contentDiv.appendChild(date); 100 | contentDiv.appendChild(description); 101 | 102 | const transcribeButton = document.createElement("button"); 103 | transcribeButton.textContent = "Transcribe"; 104 | transcribeButton.classList.add("mt-2", "bg-black", "text-white", "px-3", "py-2", "rounded"); 105 | transcribeButton.addEventListener("click", (e) => { 106 | e.preventDefault(); 107 | const existingTranscribeMessage = contentDiv.querySelector(".transcribe-message"); 108 | if (!existingTranscribeMessage) { 109 | const transcribeMessage = document.createElement("p"); 110 | 111 | // Set the transcribe message text content based on the user's default language 112 | const userLang = navigator.language || navigator.userLanguage; 113 | if (userLang.startsWith("zh")) { 114 | transcribeMessage.textContent = "(如果将来有钱租GPU)会用Whisper转录全文+时间轴🤔"; 115 | } else { 116 | transcribeMessage.textContent = "Will add Whisper to transcribe full text(if can rent GPU🤔)"; 117 | } 118 | 119 | transcribeMessage.classList.add("text-sm", "mt-2", "transcribe-message"); 120 | contentDiv.appendChild(transcribeMessage); 121 | } 122 | }); 123 | contentDiv.appendChild(transcribeButton); 124 | 125 | episodeDiv.appendChild(coverImage); 126 | episodeDiv.appendChild(contentDiv); 127 | 128 | searchResultsDiv.appendChild(episodeDiv); 129 | }); 130 | } else { 131 | searchResultsDiv.innerHTML = "No results found."; 132 | } 133 | 134 | // Re-enable the button and hide the progress indicator 135 | searchButton.disabled = false; 136 | searchButtonText.classList.remove("hidden"); 137 | searchButtonProgress.classList.add("hidden"); 138 | 139 | // Add the event listener for the filterTimestamps checkbox 140 | filterTimestamps.removeEventListener("change", executeSearch); 141 | filterTimestamps.addEventListener("change", executeSearch); 142 | } 143 | 144 | function setFancySentence(event) { 145 | const sentence = event.target.innerText; 146 | const searchInput = document.getElementById("search"); 147 | searchInput.value = sentence; 148 | } 149 | 150 | 151 | async function fetchData(query) { 152 | const apiUrl = `https://itunes.apple.com/search?term=${query}&media=podcast&entity=podcastEpisode&limit=100`; 153 | const response = await fetch(apiUrl); 154 | const data = await response.json(); 155 | return data.results; 156 | } 157 | 158 | function logToServer(message) { 159 | fetch('/log', { 160 | method: 'POST', 161 | headers: { 162 | 'Content-Type': 'application/json' 163 | }, 164 | body: JSON.stringify({ 165 | message: message 166 | }) 167 | }) 168 | .then(response => response.json()) 169 | .then(data => { 170 | if (!data.success) { 171 | console.error('Error logging message to server'); 172 | } 173 | }) 174 | .catch(error => { 175 | console.error('Error logging message to server:', error); 176 | }); 177 | } 178 | 179 | 180 | function highlightMatches(text, query) { 181 | const regex = new RegExp(`(${query})`, 'gi'); 182 | const highlightedText = text.replace(regex, '$1'); 183 | return highlightedText; 184 | } 185 | 186 | function linkTimestamps(text, audioUrl) { 187 | const regex = /(\d{1,2}:\d{2}(:\d{2})?)/g; 188 | const linkedText = text.replace(regex, (match, timestamp) => { 189 | // Normalize timestamp format 190 | const normalizedTimestamp = normalizeTimestamp(timestamp); 191 | 192 | return `
${normalizedTimestamp}`; 193 | }); 194 | return linkedText; 195 | } 196 | 197 | function normalizeTimestamp(timestamp) { 198 | const timeParts = timestamp.split(":"); 199 | 200 | if (timeParts.length === 3) { 201 | const hours = String(timeParts[0]).padStart(2, "0"); 202 | const minutes = String(timeParts[1]).padStart(2, "0"); 203 | const seconds = String(timeParts[2]).padStart(2, "0"); 204 | return `${hours}:${minutes}:${seconds}`; 205 | } else if (timeParts.length === 2) { 206 | const minutes = String(timeParts[0]).padStart(2, "0"); 207 | const seconds = String(timeParts[1]).padStart(2, "0"); 208 | return `${minutes}:${seconds}`; 209 | } 210 | 211 | return timestamp; 212 | } 213 | 214 | function truncateBeforeTimestamp(description, maxLines) { 215 | const regex = /(\d{1,2}:\d{2}(:\d{2})?)/; 216 | const lines = description.split('\n'); 217 | let truncatedDescription = ''; 218 | let lineCount = 0; 219 | let timestampFound = false; 220 | 221 | for (const line of lines) { 222 | if (regex.test(line)) { 223 | timestampFound = true; 224 | } 225 | 226 | if (!timestampFound) { 227 | if (lineCount < maxLines) { 228 | truncatedDescription += line + '\n'; 229 | lineCount++; 230 | } else { 231 | truncatedDescription += '\n'; 232 | } 233 | } else { 234 | truncatedDescription += line + '\n'; 235 | } 236 | } 237 | 238 | return truncatedDescription.trim(); 239 | } 240 | 241 | function playEpisodeAtTimestamp(audioUrl, timestamp) { 242 | if (currentAudio) { 243 | currentAudio.pause(); 244 | } 245 | 246 | currentAudio = new Audio(audioUrl); 247 | const timeParts = timestamp.split(":"); 248 | let seconds = 0; 249 | 250 | if (timeParts.length === 3) { 251 | seconds += parseInt(timeParts[0]) * 3600; 252 | seconds += parseInt(timeParts[1]) * 60; 253 | seconds += parseInt(timeParts[2]); 254 | } else if (timeParts.length === 2) { 255 | seconds += parseInt(timeParts[0]) * 60; 256 | seconds += parseInt(timeParts[1]); 257 | } 258 | 259 | currentAudio.currentTime = seconds; 260 | currentAudio.play(); 261 | } 262 | 263 | // Add this code to the bottom of your JavaScript code 264 | function scrollToTop() { 265 | window.scrollTo({ 266 | top: 0, 267 | behavior: "smooth", 268 | }); 269 | } 270 | 271 | function setTextLanguage() { 272 | const userLang = navigator.language || navigator.userLanguage; 273 | const scrollTextDiv = document.querySelector(".scrollText"); 274 | const h1Title = document.querySelector("h1"); 275 | const aboutLink = document.querySelector("a[href$='about']"); 276 | const pageTitle = document.querySelector("title"); 277 | const footerText = document.querySelector("footer p.text-sm.text-gray-500"); 278 | const timestampsOnlyText = document.getElementById("timestampsOnlyText"); 279 | 280 | if (userLang.startsWith("zh")) { 281 | scrollTextDiv.innerHTML = ` 282 |

平替

283 |

数字游民

284 |

A股

285 |

人工智能

286 |

大厂

287 | `; 288 | 289 | // Change the h1 title, About link text, and page title to Chinese 290 | h1Title.textContent = "Podcaster们怎么看..."; 291 | aboutLink.textContent = "关于"; 292 | pageTitle.textContent = "PodFind - 汇聚观点"; 293 | footerText.textContent = "此项目中的每一行代码均由 GPT-4 生成。"; 294 | timestampsOnlyText.textContent = "有时间戳"; 295 | } 296 | } 297 | 298 | document.addEventListener("DOMContentLoaded", setTextLanguage); 299 | 300 | // Wrap this code inside a window.onload event 301 | window.onload = function () { 302 | document.getElementById("filterTimestamps").addEventListener("change", executeSearch); 303 | }; 304 | 305 | 306 | 307 | -------------------------------------------------------------------------------- /Web/static/style.css: -------------------------------------------------------------------------------- 1 | .scrollText { 2 | display: flex; 3 | flex-wrap: wrap; 4 | justify-content: center; 5 | gap: 0.5rem; 6 | } 7 | 8 | .overflow-wrap-anywhere { 9 | overflow-wrap: anywhere; 10 | } -------------------------------------------------------------------------------- /Web/static/tailwind.css: -------------------------------------------------------------------------------- 1 | /* 2 | ! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com 3 | */ 4 | 5 | /* 6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) 7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) 8 | */ 9 | 10 | *, 11 | ::before, 12 | ::after { 13 | box-sizing: border-box; 14 | /* 1 */ 15 | border-width: 0; 16 | /* 2 */ 17 | border-style: solid; 18 | /* 2 */ 19 | border-color: #e5e7eb; 20 | /* 2 */ 21 | } 22 | 23 | ::before, 24 | ::after { 25 | --tw-content: ''; 26 | } 27 | 28 | /* 29 | 1. Use a consistent sensible line-height in all browsers. 30 | 2. Prevent adjustments of font size after orientation changes in iOS. 31 | 3. Use a more readable tab size. 32 | 4. Use the user's configured `sans` font-family by default. 33 | 5. Use the user's configured `sans` font-feature-settings by default. 34 | 6. Use the user's configured `sans` font-variation-settings by default. 35 | */ 36 | 37 | html { 38 | line-height: 1.5; 39 | /* 1 */ 40 | -webkit-text-size-adjust: 100%; 41 | /* 2 */ 42 | -moz-tab-size: 4; 43 | /* 3 */ 44 | -o-tab-size: 4; 45 | tab-size: 4; 46 | /* 3 */ 47 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 48 | /* 4 */ 49 | font-feature-settings: normal; 50 | /* 5 */ 51 | font-variation-settings: normal; 52 | /* 6 */ 53 | } 54 | 55 | /* 56 | 1. Remove the margin in all browsers. 57 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. 58 | */ 59 | 60 | body { 61 | margin: 0; 62 | /* 1 */ 63 | line-height: inherit; 64 | /* 2 */ 65 | } 66 | 67 | /* 68 | 1. Add the correct height in Firefox. 69 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) 70 | 3. Ensure horizontal rules are visible by default. 71 | */ 72 | 73 | hr { 74 | height: 0; 75 | /* 1 */ 76 | color: inherit; 77 | /* 2 */ 78 | border-top-width: 1px; 79 | /* 3 */ 80 | } 81 | 82 | /* 83 | Add the correct text decoration in Chrome, Edge, and Safari. 84 | */ 85 | 86 | abbr:where([title]) { 87 | -webkit-text-decoration: underline dotted; 88 | text-decoration: underline dotted; 89 | } 90 | 91 | /* 92 | Remove the default font size and weight for headings. 93 | */ 94 | 95 | h1, 96 | h2, 97 | h3, 98 | h4, 99 | h5, 100 | h6 { 101 | font-size: inherit; 102 | font-weight: inherit; 103 | } 104 | 105 | /* 106 | Reset links to optimize for opt-in styling instead of opt-out. 107 | */ 108 | 109 | a { 110 | color: inherit; 111 | text-decoration: inherit; 112 | } 113 | 114 | /* 115 | Add the correct font weight in Edge and Safari. 116 | */ 117 | 118 | b, 119 | strong { 120 | font-weight: bolder; 121 | } 122 | 123 | /* 124 | 1. Use the user's configured `mono` font family by default. 125 | 2. Correct the odd `em` font sizing in all browsers. 126 | */ 127 | 128 | code, 129 | kbd, 130 | samp, 131 | pre { 132 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 133 | /* 1 */ 134 | font-size: 1em; 135 | /* 2 */ 136 | } 137 | 138 | /* 139 | Add the correct font size in all browsers. 140 | */ 141 | 142 | small { 143 | font-size: 80%; 144 | } 145 | 146 | /* 147 | Prevent `sub` and `sup` elements from affecting the line height in all browsers. 148 | */ 149 | 150 | sub, 151 | sup { 152 | font-size: 75%; 153 | line-height: 0; 154 | position: relative; 155 | vertical-align: baseline; 156 | } 157 | 158 | sub { 159 | bottom: -0.25em; 160 | } 161 | 162 | sup { 163 | top: -0.5em; 164 | } 165 | 166 | /* 167 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) 168 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) 169 | 3. Remove gaps between table borders by default. 170 | */ 171 | 172 | table { 173 | text-indent: 0; 174 | /* 1 */ 175 | border-color: inherit; 176 | /* 2 */ 177 | border-collapse: collapse; 178 | /* 3 */ 179 | } 180 | 181 | /* 182 | 1. Change the font styles in all browsers. 183 | 2. Remove the margin in Firefox and Safari. 184 | 3. Remove default padding in all browsers. 185 | */ 186 | 187 | button, 188 | input, 189 | optgroup, 190 | select, 191 | textarea { 192 | font-family: inherit; 193 | /* 1 */ 194 | font-feature-settings: inherit; 195 | /* 1 */ 196 | font-variation-settings: inherit; 197 | /* 1 */ 198 | font-size: 100%; 199 | /* 1 */ 200 | font-weight: inherit; 201 | /* 1 */ 202 | line-height: inherit; 203 | /* 1 */ 204 | color: inherit; 205 | /* 1 */ 206 | margin: 0; 207 | /* 2 */ 208 | padding: 0; 209 | /* 3 */ 210 | } 211 | 212 | /* 213 | Remove the inheritance of text transform in Edge and Firefox. 214 | */ 215 | 216 | button, 217 | select { 218 | text-transform: none; 219 | } 220 | 221 | /* 222 | 1. Correct the inability to style clickable types in iOS and Safari. 223 | 2. Remove default button styles. 224 | */ 225 | 226 | button, 227 | [type='button'], 228 | [type='reset'], 229 | [type='submit'] { 230 | -webkit-appearance: button; 231 | /* 1 */ 232 | background-color: transparent; 233 | /* 2 */ 234 | background-image: none; 235 | /* 2 */ 236 | } 237 | 238 | /* 239 | Use the modern Firefox focus style for all focusable elements. 240 | */ 241 | 242 | :-moz-focusring { 243 | outline: auto; 244 | } 245 | 246 | /* 247 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) 248 | */ 249 | 250 | :-moz-ui-invalid { 251 | box-shadow: none; 252 | } 253 | 254 | /* 255 | Add the correct vertical alignment in Chrome and Firefox. 256 | */ 257 | 258 | progress { 259 | vertical-align: baseline; 260 | } 261 | 262 | /* 263 | Correct the cursor style of increment and decrement buttons in Safari. 264 | */ 265 | 266 | ::-webkit-inner-spin-button, 267 | ::-webkit-outer-spin-button { 268 | height: auto; 269 | } 270 | 271 | /* 272 | 1. Correct the odd appearance in Chrome and Safari. 273 | 2. Correct the outline style in Safari. 274 | */ 275 | 276 | [type='search'] { 277 | -webkit-appearance: textfield; 278 | /* 1 */ 279 | outline-offset: -2px; 280 | /* 2 */ 281 | } 282 | 283 | /* 284 | Remove the inner padding in Chrome and Safari on macOS. 285 | */ 286 | 287 | ::-webkit-search-decoration { 288 | -webkit-appearance: none; 289 | } 290 | 291 | /* 292 | 1. Correct the inability to style clickable types in iOS and Safari. 293 | 2. Change font properties to `inherit` in Safari. 294 | */ 295 | 296 | ::-webkit-file-upload-button { 297 | -webkit-appearance: button; 298 | /* 1 */ 299 | font: inherit; 300 | /* 2 */ 301 | } 302 | 303 | /* 304 | Add the correct display in Chrome and Safari. 305 | */ 306 | 307 | summary { 308 | display: list-item; 309 | } 310 | 311 | /* 312 | Removes the default spacing and border for appropriate elements. 313 | */ 314 | 315 | blockquote, 316 | dl, 317 | dd, 318 | h1, 319 | h2, 320 | h3, 321 | h4, 322 | h5, 323 | h6, 324 | hr, 325 | figure, 326 | p, 327 | pre { 328 | margin: 0; 329 | } 330 | 331 | fieldset { 332 | margin: 0; 333 | padding: 0; 334 | } 335 | 336 | legend { 337 | padding: 0; 338 | } 339 | 340 | ol, 341 | ul, 342 | menu { 343 | list-style: none; 344 | margin: 0; 345 | padding: 0; 346 | } 347 | 348 | /* 349 | Reset default styling for dialogs. 350 | */ 351 | 352 | dialog { 353 | padding: 0; 354 | } 355 | 356 | /* 357 | Prevent resizing textareas horizontally by default. 358 | */ 359 | 360 | textarea { 361 | resize: vertical; 362 | } 363 | 364 | /* 365 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) 366 | 2. Set the default placeholder color to the user's configured gray 400 color. 367 | */ 368 | 369 | input::-moz-placeholder, textarea::-moz-placeholder { 370 | opacity: 1; 371 | /* 1 */ 372 | color: #9ca3af; 373 | /* 2 */ 374 | } 375 | 376 | input::placeholder, 377 | textarea::placeholder { 378 | opacity: 1; 379 | /* 1 */ 380 | color: #9ca3af; 381 | /* 2 */ 382 | } 383 | 384 | /* 385 | Set the default cursor for buttons. 386 | */ 387 | 388 | button, 389 | [role="button"] { 390 | cursor: pointer; 391 | } 392 | 393 | /* 394 | Make sure disabled buttons don't get the pointer cursor. 395 | */ 396 | 397 | :disabled { 398 | cursor: default; 399 | } 400 | 401 | /* 402 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) 403 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) 404 | This can trigger a poorly considered lint error in some tools but is included by design. 405 | */ 406 | 407 | img, 408 | svg, 409 | video, 410 | canvas, 411 | audio, 412 | iframe, 413 | embed, 414 | object { 415 | display: block; 416 | /* 1 */ 417 | vertical-align: middle; 418 | /* 2 */ 419 | } 420 | 421 | /* 422 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) 423 | */ 424 | 425 | img, 426 | video { 427 | max-width: 100%; 428 | height: auto; 429 | } 430 | 431 | /* Make elements with the HTML hidden attribute stay hidden by default */ 432 | 433 | [hidden] { 434 | display: none; 435 | } 436 | 437 | *, ::before, ::after { 438 | --tw-border-spacing-x: 0; 439 | --tw-border-spacing-y: 0; 440 | --tw-translate-x: 0; 441 | --tw-translate-y: 0; 442 | --tw-rotate: 0; 443 | --tw-skew-x: 0; 444 | --tw-skew-y: 0; 445 | --tw-scale-x: 1; 446 | --tw-scale-y: 1; 447 | --tw-pan-x: ; 448 | --tw-pan-y: ; 449 | --tw-pinch-zoom: ; 450 | --tw-scroll-snap-strictness: proximity; 451 | --tw-gradient-from-position: ; 452 | --tw-gradient-via-position: ; 453 | --tw-gradient-to-position: ; 454 | --tw-ordinal: ; 455 | --tw-slashed-zero: ; 456 | --tw-numeric-figure: ; 457 | --tw-numeric-spacing: ; 458 | --tw-numeric-fraction: ; 459 | --tw-ring-inset: ; 460 | --tw-ring-offset-width: 0px; 461 | --tw-ring-offset-color: #fff; 462 | --tw-ring-color: rgb(59 130 246 / 0.5); 463 | --tw-ring-offset-shadow: 0 0 #0000; 464 | --tw-ring-shadow: 0 0 #0000; 465 | --tw-shadow: 0 0 #0000; 466 | --tw-shadow-colored: 0 0 #0000; 467 | --tw-blur: ; 468 | --tw-brightness: ; 469 | --tw-contrast: ; 470 | --tw-grayscale: ; 471 | --tw-hue-rotate: ; 472 | --tw-invert: ; 473 | --tw-saturate: ; 474 | --tw-sepia: ; 475 | --tw-drop-shadow: ; 476 | --tw-backdrop-blur: ; 477 | --tw-backdrop-brightness: ; 478 | --tw-backdrop-contrast: ; 479 | --tw-backdrop-grayscale: ; 480 | --tw-backdrop-hue-rotate: ; 481 | --tw-backdrop-invert: ; 482 | --tw-backdrop-opacity: ; 483 | --tw-backdrop-saturate: ; 484 | --tw-backdrop-sepia: ; 485 | } 486 | 487 | ::backdrop { 488 | --tw-border-spacing-x: 0; 489 | --tw-border-spacing-y: 0; 490 | --tw-translate-x: 0; 491 | --tw-translate-y: 0; 492 | --tw-rotate: 0; 493 | --tw-skew-x: 0; 494 | --tw-skew-y: 0; 495 | --tw-scale-x: 1; 496 | --tw-scale-y: 1; 497 | --tw-pan-x: ; 498 | --tw-pan-y: ; 499 | --tw-pinch-zoom: ; 500 | --tw-scroll-snap-strictness: proximity; 501 | --tw-gradient-from-position: ; 502 | --tw-gradient-via-position: ; 503 | --tw-gradient-to-position: ; 504 | --tw-ordinal: ; 505 | --tw-slashed-zero: ; 506 | --tw-numeric-figure: ; 507 | --tw-numeric-spacing: ; 508 | --tw-numeric-fraction: ; 509 | --tw-ring-inset: ; 510 | --tw-ring-offset-width: 0px; 511 | --tw-ring-offset-color: #fff; 512 | --tw-ring-color: rgb(59 130 246 / 0.5); 513 | --tw-ring-offset-shadow: 0 0 #0000; 514 | --tw-ring-shadow: 0 0 #0000; 515 | --tw-shadow: 0 0 #0000; 516 | --tw-shadow-colored: 0 0 #0000; 517 | --tw-blur: ; 518 | --tw-brightness: ; 519 | --tw-contrast: ; 520 | --tw-grayscale: ; 521 | --tw-hue-rotate: ; 522 | --tw-invert: ; 523 | --tw-saturate: ; 524 | --tw-sepia: ; 525 | --tw-drop-shadow: ; 526 | --tw-backdrop-blur: ; 527 | --tw-backdrop-brightness: ; 528 | --tw-backdrop-contrast: ; 529 | --tw-backdrop-grayscale: ; 530 | --tw-backdrop-hue-rotate: ; 531 | --tw-backdrop-invert: ; 532 | --tw-backdrop-opacity: ; 533 | --tw-backdrop-saturate: ; 534 | --tw-backdrop-sepia: ; 535 | } 536 | 537 | .container { 538 | width: 100%; 539 | } 540 | 541 | @media (min-width: 640px) { 542 | .container { 543 | max-width: 640px; 544 | } 545 | } 546 | 547 | @media (min-width: 768px) { 548 | .container { 549 | max-width: 768px; 550 | } 551 | } 552 | 553 | @media (min-width: 1024px) { 554 | .container { 555 | max-width: 1024px; 556 | } 557 | } 558 | 559 | @media (min-width: 1280px) { 560 | .container { 561 | max-width: 1280px; 562 | } 563 | } 564 | 565 | @media (min-width: 1536px) { 566 | .container { 567 | max-width: 1536px; 568 | } 569 | } 570 | 571 | .sr-only { 572 | position: absolute; 573 | width: 1px; 574 | height: 1px; 575 | padding: 0; 576 | margin: -1px; 577 | overflow: hidden; 578 | clip: rect(0, 0, 0, 0); 579 | white-space: nowrap; 580 | border-width: 0; 581 | } 582 | 583 | .fixed { 584 | position: fixed; 585 | } 586 | 587 | .absolute { 588 | position: absolute; 589 | } 590 | 591 | .relative { 592 | position: relative; 593 | } 594 | 595 | .inset-y-0 { 596 | top: 0px; 597 | bottom: 0px; 598 | } 599 | 600 | .bottom-4 { 601 | bottom: 1rem; 602 | } 603 | 604 | .left-0 { 605 | left: 0px; 606 | } 607 | 608 | .right-4 { 609 | right: 1rem; 610 | } 611 | 612 | .top-4 { 613 | top: 1rem; 614 | } 615 | 616 | .mx-2 { 617 | margin-left: 0.5rem; 618 | margin-right: 0.5rem; 619 | } 620 | 621 | .mx-auto { 622 | margin-left: auto; 623 | margin-right: auto; 624 | } 625 | 626 | .mb-2 { 627 | margin-bottom: 0.5rem; 628 | } 629 | 630 | .mb-3 { 631 | margin-bottom: 0.75rem; 632 | } 633 | 634 | .mb-4 { 635 | margin-bottom: 1rem; 636 | } 637 | 638 | .mb-6 { 639 | margin-bottom: 1.5rem; 640 | } 641 | 642 | .ml-2 { 643 | margin-left: 0.5rem; 644 | } 645 | 646 | .mr-2 { 647 | margin-right: 0.5rem; 648 | } 649 | 650 | .mr-3 { 651 | margin-right: 0.75rem; 652 | } 653 | 654 | .mr-4 { 655 | margin-right: 1rem; 656 | } 657 | 658 | .mt-2 { 659 | margin-top: 0.5rem; 660 | } 661 | 662 | .mt-4 { 663 | margin-top: 1rem; 664 | } 665 | 666 | .mt-8 { 667 | margin-top: 2rem; 668 | } 669 | 670 | .block { 671 | display: block; 672 | } 673 | 674 | .inline { 675 | display: inline; 676 | } 677 | 678 | .flex { 679 | display: flex; 680 | } 681 | 682 | .inline-flex { 683 | display: inline-flex; 684 | } 685 | 686 | .hidden { 687 | display: none; 688 | } 689 | 690 | .h-20 { 691 | height: 5rem; 692 | } 693 | 694 | .h-4 { 695 | height: 1rem; 696 | } 697 | 698 | .h-6 { 699 | height: 1.5rem; 700 | } 701 | 702 | .h-8 { 703 | height: 2rem; 704 | } 705 | 706 | .min-h-screen { 707 | min-height: 100vh; 708 | } 709 | 710 | .w-10 { 711 | width: 2.5rem; 712 | } 713 | 714 | .w-20 { 715 | width: 5rem; 716 | } 717 | 718 | .w-6 { 719 | width: 1.5rem; 720 | } 721 | 722 | .w-full { 723 | width: 100%; 724 | } 725 | 726 | .max-w-2xl { 727 | max-width: 42rem; 728 | } 729 | 730 | .max-w-3xl { 731 | max-width: 48rem; 732 | } 733 | 734 | .cursor-pointer { 735 | cursor: pointer; 736 | } 737 | 738 | .resize { 739 | resize: both; 740 | } 741 | 742 | .appearance-none { 743 | -webkit-appearance: none; 744 | -moz-appearance: none; 745 | appearance: none; 746 | } 747 | 748 | .flex-col { 749 | flex-direction: column; 750 | } 751 | 752 | .items-start { 753 | align-items: flex-start; 754 | } 755 | 756 | .items-center { 757 | align-items: center; 758 | } 759 | 760 | .justify-center { 761 | justify-content: center; 762 | } 763 | 764 | .overflow-hidden { 765 | overflow: hidden; 766 | } 767 | 768 | .rounded { 769 | border-radius: 0.25rem; 770 | } 771 | 772 | .rounded-full { 773 | border-radius: 9999px; 774 | } 775 | 776 | .border { 777 | border-width: 1px; 778 | } 779 | 780 | .border-b { 781 | border-bottom-width: 1px; 782 | } 783 | 784 | .border-b-2 { 785 | border-bottom-width: 2px; 786 | } 787 | 788 | .border-none { 789 | border-style: none; 790 | } 791 | 792 | .border-black { 793 | --tw-border-opacity: 1; 794 | border-color: rgb(0 0 0 / var(--tw-border-opacity)); 795 | } 796 | 797 | .border-teal-500 { 798 | --tw-border-opacity: 1; 799 | border-color: rgb(20 184 166 / var(--tw-border-opacity)); 800 | } 801 | 802 | .bg-black { 803 | --tw-bg-opacity: 1; 804 | background-color: rgb(0 0 0 / var(--tw-bg-opacity)); 805 | } 806 | 807 | .bg-blue-200 { 808 | --tw-bg-opacity: 1; 809 | background-color: rgb(191 219 254 / var(--tw-bg-opacity)); 810 | } 811 | 812 | .bg-gray-100 { 813 | --tw-bg-opacity: 1; 814 | background-color: rgb(243 244 246 / var(--tw-bg-opacity)); 815 | } 816 | 817 | .bg-gray-300 { 818 | --tw-bg-opacity: 1; 819 | background-color: rgb(209 213 219 / var(--tw-bg-opacity)); 820 | } 821 | 822 | .bg-purple-100 { 823 | --tw-bg-opacity: 1; 824 | background-color: rgb(243 232 255 / var(--tw-bg-opacity)); 825 | } 826 | 827 | .bg-red-200 { 828 | --tw-bg-opacity: 1; 829 | background-color: rgb(254 202 202 / var(--tw-bg-opacity)); 830 | } 831 | 832 | .bg-transparent { 833 | background-color: transparent; 834 | } 835 | 836 | .bg-white { 837 | --tw-bg-opacity: 1; 838 | background-color: rgb(255 255 255 / var(--tw-bg-opacity)); 839 | } 840 | 841 | .bg-yellow-200 { 842 | --tw-bg-opacity: 1; 843 | background-color: rgb(254 240 138 / var(--tw-bg-opacity)); 844 | } 845 | 846 | .bg-yellow-300 { 847 | --tw-bg-opacity: 1; 848 | background-color: rgb(253 224 71 / var(--tw-bg-opacity)); 849 | } 850 | 851 | .bg-gradient-to-r { 852 | background-image: linear-gradient(to right, var(--tw-gradient-stops)); 853 | } 854 | 855 | .from-green-200 { 856 | --tw-gradient-from: #bbf7d0 var(--tw-gradient-from-position); 857 | --tw-gradient-to: rgb(187 247 208 / 0) var(--tw-gradient-to-position); 858 | --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); 859 | } 860 | 861 | .via-green-300 { 862 | --tw-gradient-to: rgb(134 239 172 / 0) var(--tw-gradient-to-position); 863 | --tw-gradient-stops: var(--tw-gradient-from), #86efac var(--tw-gradient-via-position), var(--tw-gradient-to); 864 | } 865 | 866 | .to-green-400 { 867 | --tw-gradient-to: #4ade80 var(--tw-gradient-to-position); 868 | } 869 | 870 | .p-4 { 871 | padding: 1rem; 872 | } 873 | 874 | .px-2 { 875 | padding-left: 0.5rem; 876 | padding-right: 0.5rem; 877 | } 878 | 879 | .px-3 { 880 | padding-left: 0.75rem; 881 | padding-right: 0.75rem; 882 | } 883 | 884 | .px-4 { 885 | padding-left: 1rem; 886 | padding-right: 1rem; 887 | } 888 | 889 | .py-1 { 890 | padding-top: 0.25rem; 891 | padding-bottom: 0.25rem; 892 | } 893 | 894 | .py-2 { 895 | padding-top: 0.5rem; 896 | padding-bottom: 0.5rem; 897 | } 898 | 899 | .py-4 { 900 | padding-top: 1rem; 901 | padding-bottom: 1rem; 902 | } 903 | 904 | .py-8 { 905 | padding-top: 2rem; 906 | padding-bottom: 2rem; 907 | } 908 | 909 | .pb-8 { 910 | padding-bottom: 2rem; 911 | } 912 | 913 | .pt-6 { 914 | padding-top: 1.5rem; 915 | } 916 | 917 | .text-center { 918 | text-align: center; 919 | } 920 | 921 | .text-2xl { 922 | font-size: 1.5rem; 923 | line-height: 2rem; 924 | } 925 | 926 | .text-lg { 927 | font-size: 1.125rem; 928 | line-height: 1.75rem; 929 | } 930 | 931 | .text-sm { 932 | font-size: 0.875rem; 933 | line-height: 1.25rem; 934 | } 935 | 936 | .text-xl { 937 | font-size: 1.25rem; 938 | line-height: 1.75rem; 939 | } 940 | 941 | .font-bold { 942 | font-weight: 700; 943 | } 944 | 945 | .font-semibold { 946 | font-weight: 600; 947 | } 948 | 949 | .italic { 950 | font-style: italic; 951 | } 952 | 953 | .leading-tight { 954 | line-height: 1.25; 955 | } 956 | 957 | .text-blue-600 { 958 | --tw-text-opacity: 1; 959 | color: rgb(37 99 235 / var(--tw-text-opacity)); 960 | } 961 | 962 | .text-gray-500 { 963 | --tw-text-opacity: 1; 964 | color: rgb(107 114 128 / var(--tw-text-opacity)); 965 | } 966 | 967 | .text-gray-600 { 968 | --tw-text-opacity: 1; 969 | color: rgb(75 85 99 / var(--tw-text-opacity)); 970 | } 971 | 972 | .text-gray-700 { 973 | --tw-text-opacity: 1; 974 | color: rgb(55 65 81 / var(--tw-text-opacity)); 975 | } 976 | 977 | .text-green-900 { 978 | --tw-text-opacity: 1; 979 | color: rgb(20 83 45 / var(--tw-text-opacity)); 980 | } 981 | 982 | .text-white { 983 | --tw-text-opacity: 1; 984 | color: rgb(255 255 255 / var(--tw-text-opacity)); 985 | } 986 | 987 | .underline { 988 | text-decoration-line: underline; 989 | } 990 | 991 | .opacity-50 { 992 | opacity: 0.5; 993 | } 994 | 995 | .shadow { 996 | --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); 997 | --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); 998 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 999 | } 1000 | 1001 | .shadow-inner { 1002 | --tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05); 1003 | --tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color); 1004 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 1005 | } 1006 | 1007 | .shadow-md { 1008 | --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); 1009 | --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); 1010 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 1011 | } 1012 | 1013 | .filter { 1014 | filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); 1015 | } 1016 | 1017 | .transition { 1018 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; 1019 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; 1020 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; 1021 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 1022 | transition-duration: 150ms; 1023 | } 1024 | 1025 | .transition-all { 1026 | transition-property: all; 1027 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 1028 | transition-duration: 150ms; 1029 | } 1030 | 1031 | .duration-200 { 1032 | transition-duration: 200ms; 1033 | } 1034 | 1035 | .duration-300 { 1036 | transition-duration: 300ms; 1037 | } 1038 | 1039 | .ease-in-out { 1040 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 1041 | } 1042 | 1043 | .hover\:border-gray-800:hover { 1044 | --tw-border-opacity: 1; 1045 | border-color: rgb(31 41 55 / var(--tw-border-opacity)); 1046 | } 1047 | 1048 | .hover\:bg-gray-800:hover { 1049 | --tw-bg-opacity: 1; 1050 | background-color: rgb(31 41 55 / var(--tw-bg-opacity)); 1051 | } 1052 | 1053 | .hover\:text-blue-800:hover { 1054 | --tw-text-opacity: 1; 1055 | color: rgb(30 64 175 / var(--tw-text-opacity)); 1056 | } 1057 | 1058 | .hover\:text-gray-800:hover { 1059 | --tw-text-opacity: 1; 1060 | color: rgb(31 41 55 / var(--tw-text-opacity)); 1061 | } 1062 | 1063 | .hover\:text-green-600:hover { 1064 | --tw-text-opacity: 1; 1065 | color: rgb(22 163 74 / var(--tw-text-opacity)); 1066 | } 1067 | 1068 | .hover\:opacity-100:hover { 1069 | opacity: 1; 1070 | } 1071 | 1072 | .focus\:outline-none:focus { 1073 | outline: 2px solid transparent; 1074 | outline-offset: 2px; 1075 | } 1076 | 1077 | @media (min-width: 640px) { 1078 | .sm\:px-6 { 1079 | padding-left: 1.5rem; 1080 | padding-right: 1.5rem; 1081 | } 1082 | 1083 | .sm\:px-8 { 1084 | padding-left: 2rem; 1085 | padding-right: 2rem; 1086 | } 1087 | } 1088 | 1089 | @media (min-width: 768px) { 1090 | .md\:px-8 { 1091 | padding-left: 2rem; 1092 | padding-right: 2rem; 1093 | } 1094 | 1095 | .md\:text-4xl { 1096 | font-size: 2.25rem; 1097 | line-height: 2.5rem; 1098 | } 1099 | } -------------------------------------------------------------------------------- /Web/static/tailwind.min.css: -------------------------------------------------------------------------------- 1 | *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0;bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.right-4{right:1rem}.top-4{top:1rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-2{margin-left:.5rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mr-4{margin-right:1rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.hidden{display:none}.h-20{height:5rem}.h-4{height:1rem}.h-6{height:1.5rem}.h-8{height:2rem}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-20{width:5rem}.w-6{width:1.5rem}.w-full{width:100%}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.cursor-pointer{cursor:pointer}.resize{resize:both}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-none{border-style:none}.border-black{--tw-border-opacity:1;border-color:rgb(0 0 0/var(--tw-border-opacity))}.border-teal-500{--tw-border-opacity:1;border-color:rgb(20 184 166/var(--tw-border-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-blue-200{--tw-bg-opacity:1;background-color:rgb(191 219 254/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity))}.bg-purple-100{--tw-bg-opacity:1;background-color:rgb(243 232 255/var(--tw-bg-opacity))}.bg-red-200{--tw-bg-opacity:1;background-color:rgb(254 202 202/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-yellow-200{--tw-bg-opacity:1;background-color:rgb(254 240 138/var(--tw-bg-opacity))}.bg-yellow-300{--tw-bg-opacity:1;background-color:rgb(253 224 71/var(--tw-bg-opacity))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-green-200{--tw-gradient-from:#b5f0d0 var(--tw-gradient-from-position);--tw-gradient-to:rgba(181,240,208,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.via-green-300{--tw-gradient-to:rgba(140,227,185,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),#8ce3b9 var(--tw-gradient-via-position),var(--tw-gradient-to)}.to-green-400{--tw-gradient-to:#10b981 var(--tw-gradient-to-position)}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem}.pb-8,.py-8{padding-bottom:2rem}.pt-6{padding-top:1.5rem}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.italic{font-style:italic}.leading-tight{line-height:1.25}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-green-900{--tw-text-opacity:1;color:rgb(20 83 45/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.underline{text-decoration-line:underline}.opacity-50{opacity:.5}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-inner{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-inner{--tw-shadow:inset 0 2px 4px 0 rgba(0,0,0,.05);--tw-shadow-colored:inset 0 2px 4px 0 var(--tw-shadow-color)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.hover\:border-gray-800:hover{--tw-border-opacity:1;border-color:rgb(31 41 55/var(--tw-border-opacity))}.hover\:bg-gray-800:hover{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.hover\:text-blue-800:hover{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity))}.hover\:text-gray-800:hover{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.hover\:text-green-600:hover{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.hover\:opacity-100:hover{opacity:1}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}@media (min-width:640px){.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:px-8{padding-left:2rem;padding-right:2rem}}@media (min-width:768px){.md\:px-8{padding-left:2rem;padding-right:2rem}.md\:text-4xl{font-size:2.25rem;line-height:2.5rem}} -------------------------------------------------------------------------------- /Web/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./templates/**/*.{html,js}", "./static/**/*.{html,js}"], 4 | theme: { 5 | extend: { 6 | colors: { 7 | // Add the old green colors back 8 | green: { 9 | 200: '#b5f0d0', 10 | 300: '#8ce3b9', 11 | 400: '#10b981', 12 | }, 13 | }, 14 | }, 15 | }, 16 | plugins: [], 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /Web/templates/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | About PodFind 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 22 | 128 | 129 | 130 | 131 | 132 | 133 | 134 |
135 | PodFind.xyz 137 |
138 |
139 |
140 |

1. Initial intention

141 |

142 |

143 | PodFind.xyz is not designed to be a podcast search engine. 144 |
145 |

In fact, I would prefer it to be helping you gain new awareness. You can:

146 | 151 |

152 |

153 | 154 |

2. Why choose podcasts

155 |

In terms of format, podcasts require recording equipment and multi-person 156 | conversations, which act as a filter and also provide possibilities for in-depth viewpoint exchanges. Therefore, 157 | I tend to believe that the signal-to-noise ratio(SNR) of podcasts is higher.

158 | 159 |

3. Free to use?

160 |

161 | Yes! It's purely for fun. Your donation will keep this site alive a little longer. (2023-10 Update: This service 162 | is now on Vercel, so no new server cost now.) 163 |

164 | 165 |

166 | Alternatively, you can support my other (iOS) products! 167 |

168 | 182 | 183 |

184 | 185 |

4. Future plans

186 |

Many audio files don't have timestamps. In the future(if funds are available), I'll 187 | add Whisper to build timestamp for every 188 | episode, so you can directly listen to relevant segments.

189 |

Anyway, have fun! Contact: myfancoo@gmail.com

190 |
191 | 192 |

193 | Server cost so far: 194 |

195 | 196 |

197 | Your donation keeps this website alive. Email me to display your name and 198 | avatar as thanks. 199 |

200 | 201 | 204 | 205 | 209 | 210 | 213 | 214 |
215 | 216 | 217 | -------------------------------------------------------------------------------- /Web/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PodFind: Uniting Perspectives 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | 39 | About 40 | 41 | 42 | 43 |
44 | 45 |
46 | 47 |

See Podcasters Thoughts On..

48 |
49 |
50 |
51 |

OpenAI

53 |

Generative AI

55 |

Kamala

57 |

Isreal

59 |

Orion

61 |
62 |
63 |
65 |
66 |
67 | 69 | 76 |
77 |
78 | 79 |
80 | 94 |
95 | 96 |
97 |
98 | 99 |
100 | 101 | 102 | 105 | 106 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /Web/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { "src": "app.py", "use": "@vercel/python" }, 5 | { "src": "static/**", "use": "@vercel/static" } 6 | ], 7 | "routes": [ 8 | { "src": "/static/(.*)", "dest": "/static/$1" }, 9 | { "src": "/(.*)", "dest": "/app.py" } 10 | ] 11 | } -------------------------------------------------------------------------------- /Web/zip.sh: -------------------------------------------------------------------------------- 1 | zip -r Web.zip . -x '.??*' 2 | --------------------------------------------------------------------------------