├── dist ├── favicon.ico ├── index.html └── assets │ └── index-DRB2_ZPb.css ├── .vscode └── extensions.json ├── postcss.config.js ├── public └── favicon.ico ├── src ├── index.css ├── assets │ ├── favicon_package_v0.16.zip │ ├── favicon_package_v0.16 │ │ ├── favicon.ico │ │ ├── mstile-70x70.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── browserconfig.xml │ │ ├── site.webmanifest │ │ └── safari-pinned-tab.svg │ └── spotdl.svg ├── main.js ├── config.js ├── views │ ├── Download.vue │ ├── Front.vue │ └── Search.vue ├── App.vue ├── router │ └── index.js ├── components │ ├── About.vue │ ├── SearchInput.vue │ ├── Hero.vue │ ├── Footer.vue │ ├── Navbar.vue │ ├── SearchList.vue │ ├── DownloadList.vue │ └── Settings.vue └── model │ ├── settings.js │ ├── theme.js │ ├── search.js │ ├── api.js │ └── download.js ├── vite.config.js ├── .prettierrc.js ├── index.html ├── package.json ├── README.md ├── LICENSE ├── tailwind.config.js └── .gitignore /dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/dist/favicon.ico -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["johnsoncodehk.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require("tailwindcss")], 3 | }; 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/src/assets/favicon_package_v0.16.zip -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/src/assets/favicon_package_v0.16/favicon.ico -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/src/assets/favicon_package_v0.16/mstile-70x70.png -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/src/assets/favicon_package_v0.16/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/src/assets/favicon_package_v0.16/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/src/assets/favicon_package_v0.16/mstile-144x144.png -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/src/assets/favicon_package_v0.16/mstile-150x150.png -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/src/assets/favicon_package_v0.16/mstile-310x150.png -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/src/assets/favicon_package_v0.16/mstile-310x310.png -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/src/assets/favicon_package_v0.16/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/src/assets/favicon_package_v0.16/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotDL/web-ui/HEAD/src/assets/favicon_package_v0.16/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router/index' 4 | 5 | import './index.css' 6 | 7 | const app = createApp(App) 8 | app.use(router) 9 | app.mount('#app') 10 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | define: { 8 | 'process.env': {}, 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | tabWidth: 2, 4 | useTabs: false, 5 | printWidth: 80, 6 | endOfLine: 'auto', 7 | singleQuote: true, 8 | trailingComma: 'es5', 9 | bracketSpacing: true, 10 | arrowParens: 'always', 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #00a300 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | spotDL 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | PROTOCOL: process.env.PROTOCOL || window.location.protocol, 3 | WS_PROTOCOL: process.env.WS_PROTOCOL || window.location.protocol === 'https:' ? 'wss:' : 'ws:', 4 | BACKEND: process.env.BACKEND || window.location.hostname, 5 | PORT: process.env.PORT || window.location.port, 6 | WS_PORT: process.env.WS_PORT || window.location.port, 7 | BASEURL: process.env.BASEURL || '', 8 | } 9 | 10 | export default config 11 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | spotDL 8 | 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spotdl-web", 3 | "version": "4.2.6", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "preview": "vite preview" 8 | }, 9 | "dependencies": { 10 | "axios": "^1.5.0", 11 | "daisyui": "^4.7.2", 12 | "uuid": "^9.0.0", 13 | "vue": "^3.3.4", 14 | "vue-router": "^4.2.4" 15 | }, 16 | "devDependencies": { 17 | "@iconify/vue": "^4.1.1", 18 | "@vitejs/plugin-vue": "^5.0.4", 19 | "tailwindcss": "^3.3.3", 20 | "vite": "^5.1.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/views/Download.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web frontend for spotdl 2 | 3 | This repo currently serves as the content center for the client-side code. 4 | Each time `spotdl web` is run, the latest build gets copied from this repo. The latest build can be found under `dist/` 5 | 6 | ## Locally 7 | 8 | ``` 9 | spotdl web 10 | ``` 11 | 12 | ## Selfhost 13 | 14 | coming soon 15 | 16 | ``` 17 | 18 | ``` 19 | 20 | ## Build from source 21 | 22 | git clone the repo 23 | `npm i` 24 | `npm run dev` 25 | `npm build` 26 | 27 | `npm i -g serve` 28 | `serve ./dist` 29 | 30 | Powered by: 31 | 32 | - Vue 33 | - Vite 34 | - daisyUI 35 | - Tailwind CSS 36 | - Iconify 37 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createWebHistory, createRouter } from 'vue-router' 2 | import Home from '/src/views/Front.vue' 3 | import Search from '/src/views/Search.vue' 4 | import Download from '/src/views/Download.vue' 5 | import config from '/src/config' 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | name: 'Home', 11 | component: Home, 12 | }, 13 | { 14 | path: '/search/:query', 15 | name: 'Search', 16 | component: Search, 17 | }, 18 | { 19 | path: '/download', 20 | name: 'Download', 21 | component: Download, 22 | }, 23 | ] 24 | 25 | const router = createRouter({ 26 | history: createWebHistory(config.BASEURL), 27 | routes, 28 | }) 29 | 30 | export default router 31 | -------------------------------------------------------------------------------- /src/views/Front.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/views/Search.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 spotDL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/About.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 39 | 40 | 48 | -------------------------------------------------------------------------------- /src/model/settings.js: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue' 2 | 3 | import API from '/src/model/api' 4 | 5 | const settings = ref({ 6 | audio_providers: [''], 7 | lyrics_providers: [''], 8 | format: '', 9 | output: '', 10 | }) 11 | 12 | const settingsOptions = { 13 | audio_providers: ['youtube', 'youtube-music'], 14 | lyrics_providers: ['genius', 'musixmatch', 'azlyrics'], 15 | format: ['mp3', 'flac', 'ogg', 'opus', 'm4a'], 16 | output: '{artists} - {title}.{output-ext}', 17 | } 18 | 19 | API.getSettings().then((res) => { 20 | if (res.status === 200) { 21 | console.log('Received settings:', res.data) 22 | settings.value = res.data 23 | } else { 24 | console.log('Error loading settings') 25 | } 26 | }) 27 | 28 | export function useSettingsManager() { 29 | const isSaved = ref() 30 | function saveSettings() { 31 | console.log('Saving settings:', settings.value) 32 | API.setSettings(settings.value).then((res) => { 33 | if (res.status === 200) { 34 | console.log('Saved!') 35 | isSaved.value = true 36 | setTimeout(() => { 37 | isSaved.value = null 38 | }, 2000) 39 | } else { 40 | console.error('Error saving settings.', res) 41 | isSaved.value = false 42 | setTimeout(() => { 43 | isSaved.value = null 44 | }, 2000) 45 | } 46 | }) 47 | } 48 | return { saveSettings, settings, settingsOptions, isSaved } 49 | } 50 | -------------------------------------------------------------------------------- /src/assets/spotdl.svg: -------------------------------------------------------------------------------- 1 | Layer 1 -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'jit', 3 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 4 | daisyui: { 5 | themes: [ 6 | 'light', 7 | 'dark', 8 | 'forest', 9 | { 10 | 'spotdl-dark': { 11 | // ...require('daisyui/src/colors/themes')['[data-theme=forest]'], 12 | primary: '#22d662', // spotdl green 13 | 'primary-content': '#ffffff', // font color on primary 14 | secondary: '#ffffff', // unused? 15 | accent: '#ffffff', // unused 16 | neutral: '#4c4c4c', // navbar & footer 17 | 'base-100': '#252525', // input & content bg 18 | 'base-200': '#252525', // border & special content bg 19 | 'base-300': '#121212', // background 20 | info: '#3ABFF8', 21 | success: '#22d662', 22 | warning: '#FBBD23', 23 | error: '#F87272', 24 | '--rounded-btn': '1.9rem', 25 | }, 26 | }, 27 | { 28 | 'spotdl-light': { 29 | primary: '#22d662', 30 | 'primary-content': '#ffffff', 31 | secondary: '#5d5d5d', 32 | accent: '#16ce57', 33 | neutral: '#4c4c4c', 34 | 'base-100': '#ffffff', 35 | 'base-200': '#ffffff', 36 | 'base-300': '#ffffff', 37 | info: '#3ABFF8', 38 | success: '#22d662', 39 | warning: '#FBBD23', 40 | error: '#F87272', 41 | '--rounded-btn': '1.9rem', 42 | }, 43 | }, 44 | ], 45 | }, 46 | plugins: [require('daisyui')], 47 | } 48 | -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | Created by potrace 1.14, written by Peter Selinger 2001-2017 -------------------------------------------------------------------------------- /src/model/theme.js: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue' 2 | 3 | const currentTheme = ref('') 4 | 5 | const lightAlias = ref('light') 6 | const darkAlias = ref('dark') 7 | 8 | export function useBinaryThemeManager({ 9 | useSystem = true, 10 | initialTheme = '', 11 | newLightAlias = '', 12 | newDarkAlias = '', 13 | } = {}) { 14 | function setLightAlias(alias) { 15 | lightAlias.value = alias 16 | _updateDocument() 17 | } 18 | function setDarkAlias(alias) { 19 | darkAlias.value = alias 20 | _updateDocument() 21 | } 22 | 23 | function getSystemTheme() { 24 | const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)') 25 | if (darkThemeMq.matches) { 26 | return 'dark' 27 | } else { 28 | return 'light' 29 | } 30 | } 31 | 32 | function setTheme(newTheme) { 33 | currentTheme.value = newTheme 34 | _updateDocument() 35 | } 36 | 37 | function switchTheme() { 38 | if (currentTheme === 'dark') currentTheme.value = 'light' 39 | else if (currentTheme === 'light') currentTheme.value = 'dark' 40 | _updateDocument() 41 | } 42 | 43 | function _updateDocument() { 44 | document.documentElement.setAttribute( 45 | 'data-theme', 46 | currentTheme.value === 'dark' ? darkAlias.value : lightAlias.value 47 | ) 48 | } 49 | 50 | if (currentTheme.value !== 'light' && currentTheme.value !== 'dark') { 51 | if (useSystem) setTheme(getSystemTheme()) 52 | if (initialTheme === 'light' || initialTheme === 'dark') { 53 | currentTheme.value = initialTheme 54 | } 55 | } 56 | if (newLightAlias) setLightAlias(newLightAlias) 57 | if (newDarkAlias) setDarkAlias(newDarkAlias) 58 | 59 | _updateDocument() 60 | 61 | return { 62 | currentTheme, 63 | setLightAlias, 64 | setDarkAlias, 65 | getSystemTheme, 66 | setTheme, 67 | switchTheme, 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | # dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /src/components/SearchInput.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/model/search.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | import API from '/src/model/api' 4 | 5 | const searchTerm = ref('') 6 | const results = ref() 7 | const isSearching = ref(false) 8 | const error = ref(false) 9 | const errorValue = ref('') 10 | 11 | function useSearchManager() { 12 | function isValid(str) { 13 | return isValidSearch(str) || isValidURL(str) 14 | } 15 | function isValidSearch(str) { 16 | if ( 17 | str === '' || 18 | str.includes('://open.spotify.com/album/') || 19 | str.includes('://open.spotify.com/playlist/') || 20 | str.includes('://open.spotify.com/show/') || 21 | str.includes('://open.spotify.com/artist/') 22 | ) { 23 | return false 24 | } 25 | return true 26 | } 27 | function isValidURL(str) { 28 | if ( 29 | (str.includes('://open.spotify.com/track/') || 30 | str.includes('://open.spotify.com/album/') || 31 | str.includes('://open.spotify.com/playlist/') || 32 | str.includes('://open.spotify.com/artist/')) && 33 | localStorage.getItem('version') >= '4.2.1' 34 | ) { 35 | return true 36 | } else if (str.includes('://open.spotify.com/track/')) { 37 | return true 38 | } 39 | return false 40 | } 41 | 42 | function searchFor(query) { 43 | console.log('Searching for:', query) 44 | results.value = [] 45 | isSearching.value = true 46 | searchTerm.value = query 47 | error.value = false 48 | errorValue.value = '' 49 | API.search(query) 50 | .then((res) => { 51 | console.log('Received Search Data:', res.data) 52 | if (res.status === 200) { 53 | results.value = res.data 54 | isSearching.value = false 55 | } else { 56 | console.error('Error Searching:', res) 57 | isSearching.value = false 58 | error.value = true 59 | errorValue.value = res.toString() 60 | } 61 | }) 62 | .catch((err) => { 63 | console.error('Other Error Searching:', err.message) 64 | isSearching.value = false 65 | error.value = true 66 | errorValue.value = err.message 67 | }) 68 | } 69 | 70 | return { 71 | searchTerm, 72 | isSearching, 73 | results, 74 | error, 75 | errorValue, 76 | searchFor, 77 | isValid, 78 | isValidSearch, 79 | isValidURL, 80 | } 81 | } 82 | 83 | export { useSearchManager } 84 | -------------------------------------------------------------------------------- /src/components/Hero.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 77 | 78 | 85 | -------------------------------------------------------------------------------- /src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/model/api.js: -------------------------------------------------------------------------------- 1 | // small file used as placeholder/settings for API calls via axios to server-side 2 | import axios from 'axios' // used to connect to server backend in ./server folder 3 | import config from '/src/config.js' 4 | 5 | import { v4 as uuidv4 } from 'uuid' 6 | 7 | console.log('using env:', process.env) 8 | console.log('using config: ', config) 9 | 10 | const API = axios.create({ 11 | baseURL: `${config.PROTOCOL}//${config.BACKEND}:${config.PORT}${config.BASEURL}`, 12 | }) 13 | 14 | const sessionID = uuidv4() 15 | console.log('session ID: ', sessionID) 16 | 17 | getVersion() 18 | 19 | const wsConnection = new WebSocket( 20 | `${config.WS_PROTOCOL}//${config.BACKEND}${config.PORT !== '' ? ':' + config.PORT : '' 21 | }${config.BASEURL}/api/ws?client_id=${sessionID}` 22 | ) 23 | 24 | wsConnection.onopen = (event) => { 25 | console.log('websocket connection opened', event) 26 | } 27 | 28 | function getVersion() { 29 | 30 | API.get("/api/version") 31 | .then(res => { 32 | const prevItem = localStorage.getItem("version") 33 | console.log("Backend version: ", res.data) 34 | localStorage.setItem("version", res.data) 35 | if(prevItem != res.data) { 36 | location.reload() 37 | } 38 | }) 39 | .catch(error => { 40 | console.error(error) 41 | console.log("Error getting version, using 0") 42 | localStorage.setItem("version", "0.0.0") 43 | }) 44 | 45 | } 46 | 47 | function search(query) { 48 | return API.get('/api/songs/search', { params: { query } }) 49 | } 50 | 51 | function open(songURL) { 52 | //4.2 53 | if (localStorage.getItem("version") >= "4") { 54 | return API.get('/api/url', { params: { url: songURL } }) 55 | } else { 56 | return API.get('/api/song/url', { params: { url: songURL } }) 57 | } 58 | } 59 | 60 | function download(songURL) { 61 | return API.post('/api/download/url', null, { 62 | params: { url: songURL, client_id: sessionID }, 63 | }) 64 | } 65 | 66 | function check_for_update() { 67 | return API.get('/api/check_update') 68 | } 69 | 70 | function downloadFileURL(fileName) { 71 | return ( 72 | API.defaults.baseURL + 73 | '/api/download/file?file=' + 74 | encodeURIComponent(fileName) + 75 | '&client_id=' + 76 | encodeURIComponent(sessionID) 77 | ) 78 | } 79 | 80 | function getSettings() { 81 | return API.get('/api/settings', { params: { client_id: sessionID } }) 82 | } 83 | function setSettings(settings) { 84 | return API.post('/api/settings/update', settings, { 85 | params: { client_id: sessionID }, 86 | }) 87 | } 88 | 89 | function ws_onmessage(fn) { 90 | return (wsConnection.onmessage = fn) 91 | } 92 | function ws_onerror(fn) { 93 | return (wsConnection.onerror = fn) 94 | } 95 | 96 | export default { 97 | search, 98 | open, 99 | download, 100 | downloadFileURL, 101 | getSettings, 102 | setSettings, 103 | check_for_update, 104 | ws_onmessage, 105 | ws_onerror, 106 | getVersion 107 | } 108 | -------------------------------------------------------------------------------- /src/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 101 | 102 | 110 | -------------------------------------------------------------------------------- /src/components/SearchList.vue: -------------------------------------------------------------------------------- 1 | 92 | 93 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/components/DownloadList.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/components/Settings.vue: -------------------------------------------------------------------------------- 1 | 112 | 113 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /src/model/download.js: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue' 2 | 3 | import API from '/src/model/api' 4 | 5 | const STATUS = { 6 | QUEUED: 'In Queue', 7 | DOWNLOADING: 'Downloading...', 8 | DOWNLOADED: 'Done', 9 | ERROR: 'Error', 10 | } 11 | 12 | const downloadQueue = ref([]) 13 | 14 | class DownloadItem { 15 | constructor(song) { 16 | this.song = song 17 | this.web_status = STATUS.QUEUED 18 | this.progress = 0 19 | this.message = '' 20 | this.web_download_url = null 21 | } 22 | setDownloading() { 23 | this.web_status = STATUS.DOWNLOADING 24 | } 25 | setDownloaded() { 26 | this.web_status = STATUS.DOWNLOADED 27 | } 28 | setError() { 29 | this.web_status = STATUS.ERROR 30 | } 31 | setWebURL(URL) { 32 | this.web_download_url = URL 33 | } 34 | isQueued() { 35 | return this.song.song_id !== undefined ? true : false 36 | // return this.web_status === STATUS.QUEUED 37 | } 38 | isDownloading() { 39 | return this.web_status === STATUS.DOWNLOADING 40 | } 41 | isDownloaded() { 42 | return this.web_status === STATUS.DOWNLOADED 43 | } 44 | isErrored() { 45 | return this.web_status === STATUS.ERROR 46 | } 47 | wsUpdate(message) { 48 | this.progress = message.progress 49 | this.message = message.message 50 | } 51 | } 52 | 53 | export function useProgressTracker() { 54 | function _findIndex(song) { 55 | return downloadQueue.value.findIndex( 56 | (downloadItem) => downloadItem.song.song_id === song.song_id 57 | ) 58 | } 59 | function appendSong(song) { 60 | let downloadItem = new DownloadItem(song) 61 | downloadQueue.value.push(downloadItem) 62 | } 63 | function removeSong(song) { 64 | console.log('removing', song, song.song_id) 65 | downloadQueue.value = downloadQueue.value.filter( 66 | (downloadItem) => downloadItem.song.song_id !== song.song_id 67 | ) 68 | console.log(downloadQueue.value) 69 | } 70 | 71 | function getBySong(song) { 72 | const idx = _findIndex(song) 73 | if (idx === -1) return null 74 | return downloadQueue.value[_findIndex(song)] 75 | } 76 | 77 | return { 78 | appendSong, 79 | removeSong, 80 | getBySong, 81 | downloadQueue, 82 | } 83 | } 84 | 85 | const progressTracker = useProgressTracker() 86 | 87 | // If Websocket connection exists, set status using descriptive events, else, fallback to simple messages. 88 | API.ws_onmessage((event) => { 89 | // event: MessageEvent 90 | let data = JSON.parse(event.data) 91 | progressTracker.getBySong(data.song).wsUpdate(data) 92 | }) 93 | API.ws_onerror((event) => { 94 | // event: MessageEvent 95 | console.log('websocket error:', event) 96 | }) 97 | 98 | export function useDownloadManager() { 99 | const loading = ref(false) 100 | function fromURL(url) { 101 | loading.value = true 102 | return API.open(url) 103 | .then((res) => { 104 | console.log('Received Response:', res) 105 | if (res.status === 200) { 106 | const songs = res.data 107 | if (Array.isArray(songs)) { 108 | for (const song of songs) { 109 | console.log('Opened Song:', song) 110 | queue(song) 111 | } 112 | } else { 113 | console.log('Opened Song:', songs) 114 | queue(songs) 115 | } 116 | } else { 117 | console.log('Error:', res) 118 | } 119 | }) 120 | .catch((err) => { 121 | console.log('Other Error:', err.message) 122 | }) 123 | .finally(() => { 124 | loading.value = false 125 | }) 126 | } 127 | 128 | function download(song) { 129 | console.log('Downloading', song) 130 | progressTracker.getBySong(song).setDownloading() 131 | API.download(song.url) 132 | .then((res) => { 133 | console.log('Received Response:', res) 134 | if (res.status === 200) { 135 | let filename = res.data 136 | console.log('Download Complete:', filename) 137 | progressTracker 138 | .getBySong(song) 139 | .setWebURL(API.downloadFileURL(filename)) 140 | progressTracker.getBySong(song).setDownloaded() 141 | } else { 142 | console.log('Error:', res) 143 | progressTracker.getBySong(song).setError() 144 | } 145 | }) 146 | .catch((err) => { 147 | console.log('Other Error:', err.message) 148 | progressTracker.getBySong(song).setError() 149 | }) 150 | } 151 | 152 | function queue(song, beginDownload = true) { 153 | progressTracker.appendSong(song) 154 | if (beginDownload) download(song) 155 | } 156 | function remove(song) { 157 | console.log('removing') 158 | progressTracker.removeSong(song) 159 | } 160 | 161 | return { 162 | fromURL, 163 | download, 164 | queue, 165 | remove, 166 | loading, 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /dist/assets/index-DRB2_ZPb.css: -------------------------------------------------------------------------------- 1 | .center[data-v-316605e7],.center[data-v-b653c167]{text-align:center;margin-left:auto;margin-right:auto}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-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]){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,samp,pre{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,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}:root,[data-theme]{background-color:var(--fallback-b1,oklch(var(--b1)/1));color:var(--fallback-bc,oklch(var(--bc)/1))}@supports not (color: oklch(0% 0 0)){:root{color-scheme:light;--fallback-p: #491eff;--fallback-pc: #d4dbff;--fallback-s: #ff41c7;--fallback-sc: #fff9fc;--fallback-a: #00cfbd;--fallback-ac: #00100d;--fallback-n: #2b3440;--fallback-nc: #d7dde4;--fallback-b1: #ffffff;--fallback-b2: #e5e6e6;--fallback-b3: #e5e6e6;--fallback-bc: #1f2937;--fallback-in: #00b3f0;--fallback-inc: #000000;--fallback-su: #00ca92;--fallback-suc: #000000;--fallback-wa: #ffc22d;--fallback-wac: #000000;--fallback-er: #ff6f70;--fallback-erc: #000000}@media (prefers-color-scheme: dark){:root{color-scheme:dark;--fallback-p: #7582ff;--fallback-pc: #050617;--fallback-s: #ff71cf;--fallback-sc: #190211;--fallback-a: #00c7b5;--fallback-ac: #000e0c;--fallback-n: #2a323c;--fallback-nc: #a6adbb;--fallback-b1: #1d232a;--fallback-b2: #191e24;--fallback-b3: #15191e;--fallback-bc: #a6adbb;--fallback-in: #00b3f0;--fallback-inc: #000000;--fallback-su: #00ca92;--fallback-suc: #000000;--fallback-wa: #ffc22d;--fallback-wac: #000000;--fallback-er: #ff6f70;--fallback-erc: #000000}}}html{-webkit-tap-highlight-color:transparent}*{scrollbar-color:color-mix(in oklch,currentColor 35%,transparent) transparent}*:hover{scrollbar-color:color-mix(in oklch,currentColor 60%,transparent) transparent}:root{color-scheme:light;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--pc: 89.824% .06192 275.75;--ac: 15.352% .0368 183.61;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 49.12% .3096 275.75;--s: 69.71% .329 342.55;--sc: 98.71% .0106 342.55;--a: 76.76% .184 183.61;--n: 32.1785% .02476 255.701624;--nc: 89.4994% .011585 252.096176;--b1: 100% 0 0;--b2: 96.1151% 0 0;--b3: 92.4169% .00108 197.137559;--bc: 27.8078% .029596 256.847952}@media (prefers-color-scheme: dark){:root{color-scheme:dark;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--pc: 13.138% .0392 275.75;--sc: 14.96% .052 342.55;--ac: 14.902% .0334 183.61;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 65.69% .196 275.75;--s: 74.8% .26 342.55;--a: 74.51% .167 183.61;--n: 31.3815% .021108 254.139175;--nc: 74.6477% .0216 264.435964;--b1: 25.3267% .015896 252.417568;--b2: 23.2607% .013807 253.100675;--b3: 21.1484% .01165 254.087939;--bc: 74.6477% .0216 264.435964}}[data-theme=light]{color-scheme:light;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--pc: 89.824% .06192 275.75;--ac: 15.352% .0368 183.61;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 49.12% .3096 275.75;--s: 69.71% .329 342.55;--sc: 98.71% .0106 342.55;--a: 76.76% .184 183.61;--n: 32.1785% .02476 255.701624;--nc: 89.4994% .011585 252.096176;--b1: 100% 0 0;--b2: 96.1151% 0 0;--b3: 92.4169% .00108 197.137559;--bc: 27.8078% .029596 256.847952}[data-theme=dark]{color-scheme:dark;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--pc: 13.138% .0392 275.75;--sc: 14.96% .052 342.55;--ac: 14.902% .0334 183.61;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-btn: .5rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 65.69% .196 275.75;--s: 74.8% .26 342.55;--a: 74.51% .167 183.61;--n: 31.3815% .021108 254.139175;--nc: 74.6477% .0216 264.435964;--b1: 25.3267% .015896 252.417568;--b2: 23.2607% .013807 253.100675;--b3: 21.1484% .01165 254.087939;--bc: 74.6477% .0216 264.435964}[data-theme=forest]{color-scheme:dark;--b2: 17.522% .007709 17.911578;--b3: 16.2032% .007129 17.911578;--in: 72.06% .191 231.6;--su: 64.8% .15 160;--wa: 84.71% .199 83.87;--er: 71.76% .221 22.18;--bc: 83.7682% .001658 17.911578;--sc: 13.9553% .027077 168.327128;--ac: 14.1257% .02389 185.713193;--nc: 86.1397% .007806 171.364646;--inc: 0% 0 0;--suc: 0% 0 0;--wac: 0% 0 0;--erc: 0% 0 0;--rounded-box: 1rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--p: 68.6283% .185567 148.958922;--pc: 0% 0 0;--s: 69.7764% .135385 168.327128;--a: 70.6285% .119451 185.713193;--n: 30.6985% .039032 171.364646;--b1: 18.8409% .00829 17.911578;--rounded-btn: 1.9rem}[data-theme=spotdl-dark]{--p: 76.7826% .208912 148.923424;--bc: 85.2896% 0 0;--sc: 20% 0 0;--ac: 20% 0 0;--nc: 88.3308% 0 0;--inc: 15.1705% .027481 231.6166;--suc: 15.3565% .041782 148.923424;--wac: 16.6604% .032836 83.424292;--erc: 14.2439% .032979 22.150603;--rounded-box: 1rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--pc: 100% 0 0;--s: 100% 0 0;--a: 100% 0 0;--n: 41.654% 0 0;--b1: 26.448% 0 0;--b2: 26.448% 0 0;--b3: 18.2204% 0 0;--in: 75.8525% .137405 231.6166;--su: 76.7826% .208912 148.923424;--wa: 83.302% .164179 83.424292;--er: 71.2196% .164893 22.150603;--rounded-btn: 1.9rem}[data-theme=spotdl-light]{--p: 76.7826% .208912 148.923424;--bc: 20% 0 0;--sc: 89.5672% 0 0;--ac: 14.8847% .042057 148.118822;--nc: 88.3308% 0 0;--inc: 15.1705% .027481 231.6166;--suc: 15.3565% .041782 148.923424;--wac: 16.6604% .032836 83.424292;--erc: 14.2439% .032979 22.150603;--rounded-box: 1rem;--rounded-badge: 1.9rem;--animation-btn: .25s;--animation-input: .2s;--btn-focus-scale: .95;--border-btn: 1px;--tab-border: 1px;--tab-radius: .5rem;--pc: 100% 0 0;--s: 47.8359% 0 0;--a: 74.4235% .210286 148.118822;--n: 41.654% 0 0;--b1: 100% 0 0;--b2: 100% 0 0;--b3: 100% 0 0;--in: 75.8525% .137405 231.6166;--su: 76.7826% .208912 148.923424;--wa: 83.302% .164179 83.424292;--er: 71.2196% .164893 22.150603;--rounded-btn: 1.9rem}*,:before,:after{--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: rgb(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: rgb(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: }.alert{display:grid;width:100%;grid-auto-flow:row;align-content:flex-start;align-items:center;justify-items:center;gap:1rem;text-align:center;border-radius:var(--rounded-box, 1rem);border-width:1px;--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));padding:1rem;--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--alert-bg: var(--fallback-b2,oklch(var(--b2)/1));--alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));background-color:var(--alert-bg)}@media (min-width: 640px){.alert{grid-auto-flow:column;grid-template-columns:auto minmax(auto,1fr);justify-items:start;text-align:start}}.avatar.placeholder>div{display:flex;align-items:center;justify-content:center}.badge{display:inline-flex;align-items:center;justify-content:center;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s;height:1.25rem;font-size:.875rem;line-height:1.25rem;width:fit-content;padding-left:.563rem;padding-right:.563rem;border-radius:var(--rounded-badge, 1.9rem);border-width:1px;--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}@media (hover:hover){.label a:hover{--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}}.btn{display:inline-flex;height:3rem;min-height:3rem;flex-shrink:0;cursor:pointer;-webkit-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;border-radius:var(--rounded-btn, .5rem);border-color:transparent;border-color:oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity));padding-left:1rem;padding-right:1rem;text-align:center;font-size:.875rem;line-height:1em;gap:.5rem;font-weight:600;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);border-width:var(--border-btn, 1px);transition-property:color,background-color,border-color,opacity,box-shadow,transform;--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);outline-color:var(--fallback-bc,oklch(var(--bc)/1));background-color:oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity));--tw-bg-opacity: 1;--tw-border-opacity: 1}.btn-disabled,.btn[disabled],.btn:disabled{pointer-events:none}.btn-square{height:3rem;width:3rem;padding:0}.btn-circle{height:3rem;width:3rem;border-radius:9999px;padding:0}:where(.btn:is(input[type=checkbox])),:where(.btn:is(input[type=radio])){width:auto;-webkit-appearance:none;-moz-appearance:none;appearance:none}.btn:is(input[type=checkbox]):after,.btn:is(input[type=radio]):after{--tw-content: attr(aria-label);content:var(--tw-content)}.card{position:relative;display:flex;flex-direction:column;border-radius:var(--rounded-box, 1rem)}.card:focus{outline:2px solid transparent;outline-offset:2px}.card-body{display:flex;flex:1 1 auto;flex-direction:column;padding:var(--padding-card, 2rem);gap:.5rem}.card-body :where(p){flex-grow:1}.card-actions{display:flex;flex-wrap:wrap;align-items:flex-start;gap:.5rem}.card figure{display:flex;align-items:center;justify-content:center}.card.image-full{display:grid}.card.image-full:before{position:relative;content:"";z-index:10;border-radius:var(--rounded-box, 1rem);--tw-bg-opacity: 1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));opacity:.75}.card.image-full:before,.card.image-full>*{grid-column-start:1;grid-row-start:1}.card.image-full>figure img{height:100%;object-fit:cover}.card.image-full>.card-body{position:relative;z-index:20;--tw-text-opacity: 1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.carousel{display:inline-flex;overflow-x:scroll;scroll-snap-type:x mandatory;scroll-behavior:smooth;-ms-overflow-style:none;scrollbar-width:none}.carousel-item{box-sizing:content-box;display:flex;flex:none;scroll-snap-align:start}.carousel-start .carousel-item{scroll-snap-align:start}.carousel-center .carousel-item{scroll-snap-align:center}.carousel-end .carousel-item{scroll-snap-align:end}.checkbox{flex-shrink:0;--chkbg: var(--fallback-bc,oklch(var(--bc)/1));--chkfg: var(--fallback-b1,oklch(var(--b1)/1));height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--rounded-btn, .5rem);border-width:1px;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-border-opacity: .2}@media (hover: hover){.btn:hover{--tw-border-opacity: 1;border-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn:hover{background-color:color-mix(in oklab,oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%,black);border-color:color-mix(in oklab,oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%,black)}}@supports not (color: oklch(0% 0 0)){.btn:hover{background-color:var(--btn-color, var(--fallback-b2));border-color:var(--btn-color, var(--fallback-b2))}}.btn.glass:hover{--glass-opacity: 25%;--glass-border-opacity: 15%}.btn-ghost:hover{border-color:transparent}@supports (color: oklch(0% 0 0)){.btn-ghost:hover{background-color:var(--fallback-bc,oklch(var(--bc)/.2))}}.btn-outline:hover{--tw-border-opacity: 1;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity)))}.btn-outline.btn-primary:hover{--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-primary:hover{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black)}}.btn-outline.btn-secondary:hover{--tw-text-opacity: 1;color:var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-secondary:hover{background-color:color-mix(in oklab,var(--fallback-s,oklch(var(--s)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-s,oklch(var(--s)/1)) 90%,black)}}.btn-outline.btn-accent:hover{--tw-text-opacity: 1;color:var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-accent:hover{background-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,black)}}.btn-outline.btn-success:hover{--tw-text-opacity: 1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-success:hover{background-color:color-mix(in oklab,var(--fallback-su,oklch(var(--su)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-su,oklch(var(--su)/1)) 90%,black)}}.btn-outline.btn-info:hover{--tw-text-opacity: 1;color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-info:hover{background-color:color-mix(in oklab,var(--fallback-in,oklch(var(--in)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-in,oklch(var(--in)/1)) 90%,black)}}.btn-outline.btn-warning:hover{--tw-text-opacity: 1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-warning:hover{background-color:color-mix(in oklab,var(--fallback-wa,oklch(var(--wa)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-wa,oklch(var(--wa)/1)) 90%,black)}}.btn-outline.btn-error:hover{--tw-text-opacity: 1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)))}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-error:hover{background-color:color-mix(in oklab,var(--fallback-er,oklch(var(--er)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-er,oklch(var(--er)/1)) 90%,black)}}.btn-disabled:hover,.btn[disabled]:hover,.btn:disabled:hover{--tw-border-opacity: 0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity: .2;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity: .2}@supports (color: color-mix(in oklab,black,black)){.btn:is(input[type=checkbox]:checked):hover,.btn:is(input[type=radio]:checked):hover{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black)}}}.footer{display:grid;width:100%;grid-auto-flow:row;place-items:start;column-gap:1rem;row-gap:2.5rem;font-size:.875rem;line-height:1.25rem}.footer>*{display:grid;place-items:start;gap:.5rem}@media (min-width: 48rem){.footer{grid-auto-flow:column}.footer-center{grid-auto-flow:row dense}}.form-control{display:flex;flex-direction:column}.label{display:flex;-webkit-user-select:none;user-select:none;align-items:center;justify-content:space-between;padding:.5rem .25rem}.hero{display:grid;width:100%;place-items:center;background-size:cover;background-position:center}.hero>*{grid-column-start:1;grid-row-start:1}.hero-content{z-index:0;display:flex;align-items:center;justify-content:center;max-width:80rem;gap:1rem;padding:1rem}.indicator{position:relative;display:inline-flex;width:max-content}.indicator :where(.indicator-item){z-index:1;position:absolute;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));white-space:nowrap}.input{flex-shrink:1;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:3rem;padding-left:1rem;padding-right:1rem;font-size:1rem;line-height:2;line-height:1.5rem;border-radius:var(--rounded-btn, .5rem);border-width:1px;border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.input[type=number]::-webkit-inner-spin-button,.input-md[type=number]::-webkit-inner-spin-button{margin-top:-1rem;margin-bottom:-1rem;margin-inline-end:-1rem}.link{cursor:pointer;text-decoration-line:underline}:where(.menu li) .badge{justify-self:end}.modal{pointer-events:none;position:fixed;top:0;right:0;bottom:0;left:0;margin:0;display:grid;height:100%;max-height:none;width:100%;max-width:none;justify-items:center;padding:0;opacity:0;overscroll-behavior:contain;z-index:999;background-color:transparent;color:inherit;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);transition-property:transform,opacity,visibility;overflow-y:hidden}:where(.modal){align-items:center}.modal-box{max-height:calc(100vh - 5em);grid-column-start:1;grid-row-start:1;width:91.666667%;max-width:32rem;--tw-scale-x: .9;--tw-scale-y: .9;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-bottom-right-radius:var(--rounded-box, 1rem);border-bottom-left-radius:var(--rounded-box, 1rem);border-top-left-radius:var(--rounded-box, 1rem);border-top-right-radius:var(--rounded-box, 1rem);--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));padding:1.5rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s;box-shadow:#00000040 0 25px 50px -12px;overflow-y:auto;overscroll-behavior:contain}.modal-open,.modal:target,.modal-toggle:checked+.modal,.modal[open]{pointer-events:auto;visibility:visible;opacity:1}.modal-action{display:flex;margin-top:1.5rem;justify-content:flex-end}.modal-toggle{position:fixed;height:0px;width:0px;-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}:root:has(:is(.modal-open,.modal:target,.modal-toggle:checked+.modal,.modal[open])){overflow:hidden;scrollbar-gutter:stable}.navbar{display:flex;align-items:center;padding:var(--navbar-padding, .5rem);min-height:4rem;width:100%}:where(.navbar>*:not(script,style)){display:inline-flex;align-items:center}.navbar-start{width:50%;justify-content:flex-start}.navbar-center{flex-shrink:0}.navbar-end{width:50%;justify-content:flex-end}.progress{position:relative;width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;overflow:hidden;height:.5rem;border-radius:var(--rounded-box, 1rem);background-color:var(--fallback-bc,oklch(var(--bc)/.2))}.radial-progress{position:relative;display:inline-grid;height:var(--size);width:var(--size);place-content:center;border-radius:9999px;background-color:transparent;vertical-align:middle;box-sizing:content-box;--value: 0;--size: 5rem;--thickness: calc(var(--size) / 10)}.radial-progress::-moz-progress-bar{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent}.radial-progress::-webkit-progress-value{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent}.radial-progress::-webkit-progress-bar{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent}.radial-progress:before,.radial-progress:after{position:absolute;border-radius:9999px;content:""}.radial-progress:before{top:0;right:0;bottom:0;left:0;background:radial-gradient(farthest-side,currentColor 98%,#0000) top/var(--thickness) var(--thickness) no-repeat,conic-gradient(currentColor calc(var(--value) * 1%),#0000 0);-webkit-mask:radial-gradient(farthest-side,#0000 calc(99% - var(--thickness)),#000 calc(100% - var(--thickness)));mask:radial-gradient(farthest-side,#0000 calc(99% - var(--thickness)),#000 calc(100% - var(--thickness)))}.radial-progress:after{inset:calc(50% - var(--thickness) / 2);transform:rotate(calc(var(--value) * 3.6deg - 90deg)) translate(calc(var(--size) / 2 - 50%));background-color:currentColor}.select{display:inline-flex;cursor:pointer;-webkit-user-select:none;user-select:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:3rem;min-height:3rem;padding-inline-start:1rem;padding-inline-end:2.5rem;font-size:.875rem;line-height:1.25rem;line-height:2;border-radius:var(--rounded-btn, .5rem);border-width:1px;border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position:calc(100% - 20px) calc(1px + 50%),calc(100% - 16.1px) calc(1px + 50%);background-size:4px 4px,4px 4px;background-repeat:no-repeat}.select[multiple]{height:auto}.stat-figure{grid-column-start:2;grid-row:span 3 / span 3;grid-row-start:1;place-self:center;justify-self:end}.swap{position:relative;display:inline-grid;-webkit-user-select:none;user-select:none;place-content:center;cursor:pointer}.swap>*{grid-column-start:1;grid-row-start:1;transition-duration:.3s;transition-timing-function:cubic-bezier(0,0,.2,1);transition-property:transform,opacity}.swap input{-webkit-appearance:none;-moz-appearance:none;appearance:none}.swap .swap-on,.swap .swap-indeterminate,.swap input:indeterminate~.swap-on{opacity:0}.swap input:checked~.swap-off,.swap-active .swap-off,.swap input:indeterminate~.swap-off{opacity:0}.swap input:checked~.swap-on,.swap-active .swap-on,.swap input:indeterminate~.swap-indeterminate{opacity:1}.alert-info{border-color:var(--fallback-in,oklch(var(--in)/.2));--tw-text-opacity: 1;color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)));--alert-bg: var(--fallback-in,oklch(var(--in)/1));--alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1))}.alert-success{border-color:var(--fallback-su,oklch(var(--su)/.2));--tw-text-opacity: 1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));--alert-bg: var(--fallback-su,oklch(var(--su)/1));--alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1))}.alert-error{border-color:var(--fallback-er,oklch(var(--er)/.2));--tw-text-opacity: 1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));--alert-bg: var(--fallback-er,oklch(var(--er)/1));--alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1))}.badge-secondary{--tw-border-opacity: 1;border-color:var(--fallback-s,oklch(var(--s)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-s,oklch(var(--s)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)))}.badge-error{border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)))}.badge-outline.badge-secondary{--tw-text-opacity: 1;color:var(--fallback-s,oklch(var(--s)/var(--tw-text-opacity)))}.badge-outline.badge-error{--tw-text-opacity: 1;color:var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity)))}.btm-nav>* .label{font-size:1rem;line-height:1.5rem}@media (prefers-reduced-motion: no-preference){.btn{animation:button-pop var(--animation-btn, .25s) ease-out}}.btn:active:hover,.btn:active:focus{animation:button-pop 0s ease-out;transform:scale(var(--btn-focus-scale, .97))}@supports not (color: oklch(0% 0 0)){.btn{background-color:var(--btn-color, var(--fallback-b2));border-color:var(--btn-color, var(--fallback-b2))}.btn-primary{--btn-color: var(--fallback-p)}.btn-secondary{--btn-color: var(--fallback-s)}.btn-accent{--btn-color: var(--fallback-a)}.btn-error{--btn-color: var(--fallback-er)}}@supports (color: color-mix(in oklab,black,black)){.btn-outline.btn-primary.btn-active{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,black)}.btn-outline.btn-secondary.btn-active{background-color:color-mix(in oklab,var(--fallback-s,oklch(var(--s)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-s,oklch(var(--s)/1)) 90%,black)}.btn-outline.btn-accent.btn-active{background-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,black)}.btn-outline.btn-success.btn-active{background-color:color-mix(in oklab,var(--fallback-su,oklch(var(--su)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-su,oklch(var(--su)/1)) 90%,black)}.btn-outline.btn-info.btn-active{background-color:color-mix(in oklab,var(--fallback-in,oklch(var(--in)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-in,oklch(var(--in)/1)) 90%,black)}.btn-outline.btn-warning.btn-active{background-color:color-mix(in oklab,var(--fallback-wa,oklch(var(--wa)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-wa,oklch(var(--wa)/1)) 90%,black)}.btn-outline.btn-error.btn-active{background-color:color-mix(in oklab,var(--fallback-er,oklch(var(--er)/1)) 90%,black);border-color:color-mix(in oklab,var(--fallback-er,oklch(var(--er)/1)) 90%,black)}}.btn:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px}.btn-primary{--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));outline-color:var(--fallback-p,oklch(var(--p)/1))}@supports (color: oklch(0% 0 0)){.btn-primary{--btn-color: var(--p)}.btn-secondary{--btn-color: var(--s)}.btn-accent{--btn-color: var(--a)}.btn-error{--btn-color: var(--er)}}.btn-secondary{--tw-text-opacity: 1;color:var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)));outline-color:var(--fallback-s,oklch(var(--s)/1))}.btn-accent{--tw-text-opacity: 1;color:var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));outline-color:var(--fallback-a,oklch(var(--a)/1))}.btn-error{--tw-text-opacity: 1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));outline-color:var(--fallback-er,oklch(var(--er)/1))}.btn.glass{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);outline-color:currentColor}.btn.glass.btn-active{--glass-opacity: 25%;--glass-border-opacity: 15%}.btn-ghost{border-width:1px;border-color:transparent;background-color:transparent;color:currentColor;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);outline-color:currentColor}.btn-ghost.btn-active{border-color:transparent;background-color:var(--fallback-bc,oklch(var(--bc)/.2))}.btn-outline{border-color:currentColor;background-color:transparent;--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.btn-outline.btn-active{--tw-border-opacity: 1;border-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity)))}.btn-outline.btn-primary{--tw-text-opacity: 1;color:var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)))}.btn-outline.btn-primary.btn-active{--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.btn-outline.btn-secondary{--tw-text-opacity: 1;color:var(--fallback-s,oklch(var(--s)/var(--tw-text-opacity)))}.btn-outline.btn-secondary.btn-active{--tw-text-opacity: 1;color:var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)))}.btn-outline.btn-accent{--tw-text-opacity: 1;color:var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity)))}.btn-outline.btn-accent.btn-active{--tw-text-opacity: 1;color:var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)))}.btn-outline.btn-success{--tw-text-opacity: 1;color:var(--fallback-su,oklch(var(--su)/var(--tw-text-opacity)))}.btn-outline.btn-success.btn-active{--tw-text-opacity: 1;color:var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)))}.btn-outline.btn-info{--tw-text-opacity: 1;color:var(--fallback-in,oklch(var(--in)/var(--tw-text-opacity)))}.btn-outline.btn-info.btn-active{--tw-text-opacity: 1;color:var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)))}.btn-outline.btn-warning{--tw-text-opacity: 1;color:var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity)))}.btn-outline.btn-warning.btn-active{--tw-text-opacity: 1;color:var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)))}.btn-outline.btn-error{--tw-text-opacity: 1;color:var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity)))}.btn-outline.btn-error.btn-active{--tw-text-opacity: 1;color:var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)))}.btn.btn-disabled,.btn[disabled],.btn:disabled{--tw-border-opacity: 0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity: .2;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity: .2}.btn:is(input[type=checkbox]:checked),.btn:is(input[type=radio]:checked){--tw-border-opacity: 1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.btn:is(input[type=checkbox]:checked):focus-visible,.btn:is(input[type=radio]:checked):focus-visible{outline-color:var(--fallback-p,oklch(var(--p)/1))}@keyframes button-pop{0%{transform:scale(var(--btn-focus-scale, .98))}40%{transform:scale(1.02)}to{transform:scale(1)}}.card :where(figure:first-child){overflow:hidden;border-start-start-radius:inherit;border-start-end-radius:inherit;border-end-start-radius:unset;border-end-end-radius:unset}.card :where(figure:last-child){overflow:hidden;border-start-start-radius:unset;border-start-end-radius:unset;border-end-start-radius:inherit;border-end-end-radius:inherit}.card:focus-visible{outline:2px solid currentColor;outline-offset:2px}.card.bordered,.card-bordered{border-width:1px;--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)))}.card.compact .card-body{padding:1rem;font-size:.875rem;line-height:1.25rem}.card-title{display:flex;align-items:center;gap:.5rem;font-size:1.25rem;line-height:1.75rem;font-weight:600}.card.image-full :where(figure){overflow:hidden;border-radius:inherit}.carousel::-webkit-scrollbar{display:none}.checkbox:focus{box-shadow:none}.checkbox:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/1))}.checkbox:disabled{border-width:0px;cursor:not-allowed;border-color:transparent;--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));opacity:.2}.checkbox:checked,.checkbox[aria-checked=true]{background-repeat:no-repeat;animation:checkmark var(--animation-input, .2s) ease-out;background-color:var(--chkbg);background-image:linear-gradient(-45deg,transparent 65%,var(--chkbg) 65.99%),linear-gradient(45deg,transparent 75%,var(--chkbg) 75.99%),linear-gradient(-45deg,var(--chkbg) 40%,transparent 40.99%),linear-gradient(45deg,var(--chkbg) 30%,var(--chkfg) 30.99%,var(--chkfg) 40%,transparent 40.99%),linear-gradient(-45deg,var(--chkfg) 50%,var(--chkbg) 50.99%)}.checkbox:indeterminate{--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));background-repeat:no-repeat;animation:checkmark var(--animation-input, .2s) ease-out;background-image:linear-gradient(90deg,transparent 80%,var(--chkbg) 80%),linear-gradient(-90deg,transparent 80%,var(--chkbg) 80%),linear-gradient(0deg,var(--chkbg) 43%,var(--chkfg) 43%,var(--chkfg) 57%,var(--chkbg) 57%)}@keyframes checkmark{0%{background-position-y:5px}50%{background-position-y:-2px}to{background-position-y:0}}.footer-title{margin-bottom:.5rem;font-weight:700;text-transform:uppercase;opacity:.6}.label-text{font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.input input{--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));background-color:transparent}.input input:focus{outline:2px solid transparent;outline-offset:2px}.input[list]::-webkit-calendar-picker-indicator{line-height:1em}.input-bordered{border-color:var(--fallback-bc,oklch(var(--bc)/.2))}.input:focus,.input:focus-within{box-shadow:none;border-color:var(--fallback-bc,oklch(var(--bc)/.2));outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/.2))}.input:has(>input[disabled]),.input-disabled,.input:disabled,.input[disabled]{cursor:not-allowed;--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));color:var(--fallback-bc,oklch(var(--bc)/.4))}.input:has(>input[disabled])::placeholder,.input-disabled::placeholder,.input:disabled::placeholder,.input[disabled]::placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity: .2}.input:has(>input[disabled])>input[disabled]{cursor:not-allowed}.input::-webkit-date-and-time-value{text-align:inherit}.join>:where(*:not(:first-child)):is(.btn){margin-inline-start:calc(var(--border-btn) * -1)}.link:focus{outline:2px solid transparent;outline-offset:2px}.link:focus-visible{outline:2px solid currentColor;outline-offset:2px}.loading{pointer-events:none;display:inline-block;aspect-ratio:1 / 1;width:1.5rem;background-color:currentColor;-webkit-mask-size:100%;mask-size:100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center;-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E")}.mockup-browser .mockup-browser-toolbar .input{position:relative;margin-left:auto;margin-right:auto;display:block;height:1.75rem;width:24rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));padding-left:2rem;direction:ltr}.mockup-browser .mockup-browser-toolbar .input:before{content:"";position:absolute;left:.5rem;top:50%;aspect-ratio:1 / 1;height:.75rem;--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-radius:9999px;border-width:2px;border-color:currentColor;opacity:.6}.mockup-browser .mockup-browser-toolbar .input:after{content:"";position:absolute;left:1.25rem;top:50%;height:.5rem;--tw-translate-y: 25%;--tw-rotate: -45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-radius:9999px;border-width:1px;border-color:currentColor;opacity:.6}.modal:not(dialog:not(.modal-open)),.modal::backdrop{background-color:#0006;animation:modal-pop .2s ease-out}.modal-open .modal-box,.modal-toggle:checked+.modal .modal-box,.modal:target .modal-box,.modal[open] .modal-box{--tw-translate-y: 0px;--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.modal-action>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}@keyframes modal-pop{0%{opacity:0}}.progress::-moz-progress-bar{border-radius:var(--rounded-box, 1rem);--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)))}.progress:indeterminate{--progress-color: var(--fallback-bc,oklch(var(--bc)/1));background-image:repeating-linear-gradient(90deg,var(--progress-color) -1%,var(--progress-color) 10%,transparent 10%,transparent 90%);background-size:200%;background-position-x:15%;animation:progress-loading 5s ease-in-out infinite}.progress::-webkit-progress-bar{border-radius:var(--rounded-box, 1rem);background-color:transparent}.progress::-webkit-progress-value{border-radius:var(--rounded-box, 1rem);--tw-bg-opacity: 1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)))}.progress:indeterminate::-moz-progress-bar{background-color:transparent;background-image:repeating-linear-gradient(90deg,var(--progress-color) -1%,var(--progress-color) 10%,transparent 10%,transparent 90%);background-size:200%;background-position-x:15%;animation:progress-loading 5s ease-in-out infinite}@keyframes progress-loading{50%{background-position-x:-115%}}@keyframes radiomark{0%{box-shadow:0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset}50%{box-shadow:0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset}to{box-shadow:0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset}}@keyframes rating-pop{0%{transform:translateY(-.125em)}40%{transform:translateY(-.125em)}to{transform:translateY(0)}}.select:focus{box-shadow:none;border-color:var(--fallback-bc,oklch(var(--bc)/.2));outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/.2))}.select-disabled,.select:disabled,.select[disabled]{cursor:not-allowed;--tw-border-opacity: 1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));color:var(--fallback-bc,oklch(var(--bc)/.4))}.select-disabled::placeholder,.select:disabled::placeholder,.select[disabled]::placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity: .2}.select-multiple,.select[multiple],.select[size].select:not([size="1"]){background-image:none;padding-right:1rem}[dir=rtl] .select{background-position:calc(0% + 12px) calc(1px + 50%),calc(0% + 16px) calc(1px + 50%)}@keyframes skeleton{0%{background-position:150%}to{background-position:-50%}}.swap-rotate .swap-on,.swap-rotate .swap-indeterminate,.swap-rotate input:indeterminate~.swap-on{--tw-rotate: 45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.swap-rotate input:checked~.swap-off,.swap-active:where(.swap-rotate) .swap-off,.swap-rotate input:indeterminate~.swap-off{--tw-rotate: -45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.swap-rotate input:checked~.swap-on,.swap-active:where(.swap-rotate) .swap-on,.swap-rotate input:indeterminate~.swap-indeterminate{--tw-rotate: 0deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.swap-flip .swap-on,.swap-flip .swap-indeterminate,.swap-flip input:indeterminate~.swap-on{transform:rotateY(180deg);backface-visibility:hidden;opacity:1}.swap-flip input:checked~.swap-off,.swap-active:where(.swap-flip) .swap-off,.swap-flip input:indeterminate~.swap-off{transform:rotateY(-180deg);backface-visibility:hidden;opacity:1}.swap-flip input:checked~.swap-on,.swap-active:where(.swap-flip) .swap-on,.swap-flip input:indeterminate~.swap-indeterminate{transform:rotateY(0)}@keyframes toast-pop{0%{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}.btn-sm{height:2rem;min-height:2rem;padding-left:.75rem;padding-right:.75rem;font-size:.875rem}.btn-square:where(.btn-xs){height:1.5rem;width:1.5rem;padding:0}.btn-square:where(.btn-sm){height:2rem;width:2rem;padding:0}.btn-square:where(.btn-md){height:3rem;width:3rem;padding:0}.btn-square:where(.btn-lg){height:4rem;width:4rem;padding:0}.btn-circle:where(.btn-xs){height:1.5rem;width:1.5rem;border-radius:9999px;padding:0}.btn-circle:where(.btn-sm){height:2rem;width:2rem;border-radius:9999px;padding:0}.btn-circle:where(.btn-md){height:3rem;width:3rem;border-radius:9999px;padding:0}.btn-circle:where(.btn-lg){height:4rem;width:4rem;border-radius:9999px;padding:0}.indicator :where(.indicator-item){bottom:auto;inset-inline-end:0px;inset-inline-start:auto;top:0;--tw-translate-y: -50%;--tw-translate-x: 50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item):where([dir=rtl],[dir=rtl] *){--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-start){inset-inline-end:auto;inset-inline-start:0px;--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-start):where([dir=rtl],[dir=rtl] *){--tw-translate-x: 50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-center){inset-inline-end:50%;inset-inline-start:50%;--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-center):where([dir=rtl],[dir=rtl] *){--tw-translate-x: 50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-end){inset-inline-end:0px;inset-inline-start:auto;--tw-translate-x: 50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-end):where([dir=rtl],[dir=rtl] *){--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-bottom){bottom:0;top:auto;--tw-translate-y: 50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-middle){bottom:50%;top:50%;--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.indicator :where(.indicator-item.indicator-top){bottom:auto;top:0;--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.card-compact .card-body{padding:1rem;font-size:.875rem;line-height:1.25rem}.card-compact .card-title{margin-bottom:.25rem}.card-normal .card-body{padding:var(--padding-card, 2rem);font-size:1rem;line-height:1.5rem}.card-normal .card-title{margin-bottom:.75rem}.join.join-vertical>:where(*:not(:first-child)):is(.btn){margin-top:calc(var(--border-btn) * -1)}.join.join-horizontal>:where(*:not(:first-child)):is(.btn){margin-inline-start:calc(var(--border-btn) * -1)}.modal-top :where(.modal-box){width:100%;max-width:none;--tw-translate-y: -2.5rem;--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-bottom-right-radius:var(--rounded-box, 1rem);border-bottom-left-radius:var(--rounded-box, 1rem);border-top-left-radius:0;border-top-right-radius:0}.modal-middle :where(.modal-box){width:91.666667%;max-width:32rem;--tw-translate-y: 0px;--tw-scale-x: .9;--tw-scale-y: .9;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-top-left-radius:var(--rounded-box, 1rem);border-top-right-radius:var(--rounded-box, 1rem);border-bottom-right-radius:var(--rounded-box, 1rem);border-bottom-left-radius:var(--rounded-box, 1rem)}.modal-bottom :where(.modal-box){width:100%;max-width:none;--tw-translate-y: 2.5rem;--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));border-top-left-radius:var(--rounded-box, 1rem);border-top-right-radius:var(--rounded-box, 1rem);border-bottom-right-radius:0;border-bottom-left-radius:0}.absolute{position:absolute}.relative{position:relative}.bottom-0{bottom:0}.right-0{right:0}.right-2{right:.5rem}.top-0{top:0}.top-2{top:.5rem}.m-2{margin:.5rem}.m-4{margin:1rem}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.aspect-square{aspect-ratio:1 / 1}.h-48{height:12rem}.h-6{height:1.5rem}.h-8{height:2rem}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-32{width:8rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-96{width:24rem}.w-full{width:100%}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.grid-flow-col{grid-auto-flow:column}.grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.gap-2{gap:.5rem}.gap-4{gap:1rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.rounded-box{border-radius:var(--rounded-box, 1rem)}.border-4{border-width:4px}.border-primary{--tw-border-opacity: 1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)))}.bg-base-100{--tw-bg-opacity: 1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.bg-base-200{--tw-bg-opacity: 1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)))}.bg-base-300{--tw-bg-opacity: 1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)))}.bg-neutral{--tw-bg-opacity: 1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)))}.bg-primary{--tw-bg-opacity: 1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)))}.bg-cover{background-size:cover}.bg-center{background-position:center}.bg-no-repeat{background-repeat:no-repeat}.fill-current{fill:currentColor}.stroke-current{stroke:currentColor}.object-contain{object-fit:contain}.p-10{padding:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pr-2{padding-right:.5rem}.text-center{text-align:center}.text-5xl{font-size:3rem;line-height:1}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-base-content{--tw-text-opacity: 1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.text-neutral-content{--tw-text-opacity: 1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.text-primary{--tw-text-opacity: 1;color:var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)))}.text-primary-content{--tw-text-opacity: 1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px 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)}@media (min-width: 768px){.md\:card-side{align-items:stretch;flex-direction:row}.md\:card-side :where(figure:first-child){overflow:hidden;border-start-start-radius:inherit;border-start-end-radius:unset;border-end-start-radius:inherit;border-end-end-radius:unset}.md\:card-side :where(figure:last-child){overflow:hidden;border-start-start-radius:unset;border-start-end-radius:inherit;border-end-start-radius:unset;border-end-end-radius:inherit}.md\:card-side figure>*{max-width:unset}:where(.md\:card-side figure>*){width:100%;height:100%;object-fit:cover}}@media (min-width: 640px){.sm\:flex{display:flex}.sm\:hidden{display:none}}@media (min-width: 768px){.md\:max-h-44{max-height:11rem}.md\:max-h-fit{max-height:fit-content}} 2 | --------------------------------------------------------------------------------