playlists.html 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. {% extends "base.html" %}
  2. {% block title %}Playlists - {{ app_name or 'Dune Weaver' }}{% endblock %}
  3. {% block additional_styles %}
  4. /* Minimal custom styles - rely on Tailwind utilities */
  5. .pattern-preview {
  6. border: 1px solid #e2e8f0;
  7. overflow: hidden;
  8. display: flex;
  9. align-items: center;
  10. justify-content: center;
  11. }
  12. .dark .pattern-preview img {
  13. filter: invert(1);
  14. }
  15. /* Fix checkbox visibility in light mode */
  16. html:not(.dark) input[type="checkbox"]:checked {
  17. background-color: #2563eb !important; /* Darker blue for better contrast */
  18. border-color: #2563eb !important;
  19. }
  20. html:not(.dark) input[type="checkbox"] {
  21. background-color: #ffffff !important;
  22. border-color: #d1d5db !important;
  23. }
  24. /* Mobile responsive utilities */
  25. @media (max-width: 768px) {
  26. .mobile-hidden {
  27. display: none !important;
  28. }
  29. .mobile-show {
  30. display: block !important;
  31. }
  32. .mobile-flex {
  33. display: flex !important;
  34. }
  35. .mobile-playlist-container {
  36. padding-top: 0.5rem;
  37. padding-bottom: 75px;
  38. padding-left: 0;
  39. padding-right: 0;
  40. }
  41. .mobile-full-width {
  42. width: 100% !important;
  43. max-width: 100% !important;
  44. border-radius: 0 !important;
  45. border-left: none !important;
  46. border-right: none !important;
  47. }
  48. .mobile-patterns-grid {
  49. grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)) !important;
  50. gap: 0.75rem !important;
  51. padding: 1rem !important;
  52. }
  53. }
  54. .mobile-playlists-sidebar {
  55. padding: 0 !important;
  56. border-radius: 0 !important;
  57. border-left: none !important;
  58. border-right: none !important;
  59. border-bottom: none !important;
  60. }
  61. /* Override base template's dark mode rules for playlists page in light mode */
  62. html:not(.dark) #playlistsSidebar,
  63. html:not(.dark) #playlistDetails,
  64. html:not(.dark) #playlistsNav,
  65. html:not(.dark) .bg-white {
  66. background-color: #ffffff !important;
  67. }
  68. html:not(.dark) .bg-gray-50 {
  69. background-color: #f9fafb !important;
  70. }
  71. html:not(.dark) .bg-gray-100 {
  72. background-color: #f3f4f6 !important;
  73. }
  74. html:not(.dark) .text-gray-900 {
  75. color: #111827 !important;
  76. }
  77. html:not(.dark) .text-gray-700 {
  78. color: #374151 !important;
  79. }
  80. html:not(.dark) .text-gray-500 {
  81. color: #6b7280 !important;
  82. }
  83. html:not(.dark) .border-gray-200 {
  84. border-color: #e5e7eb !important;
  85. }
  86. html:not(.dark) .border-gray-300 {
  87. border-color: #d1d5db !important;
  88. }
  89. /* Fix hover states in light mode */
  90. html:not(.dark) .hover\:bg-gray-100:hover,
  91. html:not(.dark) #playlistsNav a:hover {
  92. background-color: #f3f4f6 !important;
  93. }
  94. html:not(.dark) .hover\:bg-gray-200:hover {
  95. background-color: #e5e7eb !important;
  96. }
  97. html:not(.dark) .hover\:text-gray-900:hover {
  98. color: #111827 !important;
  99. }
  100. html:not(.dark) .hover\:text-gray-700:hover {
  101. color: #374151 !important;
  102. }
  103. /* Ensure active playlist item styling works in light mode */
  104. html:not(.dark) #playlistsNav a.active,
  105. html:not(.dark) .bg-gray-100 {
  106. background-color: #f3f4f6 !important;
  107. color: #111827 !important;
  108. }
  109. /* Fix pattern text colors in light mode */
  110. html:not(.dark) .text-gray-800 {
  111. color: #1f2937 !important;
  112. }
  113. html:not(.dark) .group:hover .group-hover\:text-gray-900,
  114. html:not(.dark) .group-hover\:text-gray-900 {
  115. color: #111827 !important;
  116. }
  117. /* Pattern cards text in light mode */
  118. html:not(.dark) #patternsGrid p,
  119. html:not(.dark) #patternsGrid .text-sm {
  120. color: #1f2937 !important;
  121. }
  122. html:not(.dark) #patternsGrid .group:hover p,
  123. html:not(.dark) #patternsGrid .group:hover .text-sm {
  124. color: #111827 !important;
  125. }
  126. /* Available patterns modal text in light mode */
  127. html:not(.dark) #availablePatternsGrid p,
  128. html:not(.dark) #availablePatternsGrid .text-xs {
  129. color: #1f2937 !important;
  130. }
  131. {% endblock %}
  132. {% block content %}
  133. <div class="flex flex-1 justify-center p-4 sm:p-6 gap-6 bg-gray-50 dark:bg-gray-900 max-w-5xl mx-auto w-full mobile-playlist-container" style="min-height: calc(100vh - 72px - 64px);">
  134. <!-- Sidebar for Playlists -->
  135. <aside id="playlistsSidebar" class="w-80 bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 flex flex-col mobile-full-width mobile-playlists-sidebar" style="height: calc(100vh - 72px - 64px - 48px);">
  136. <div class="flex items-center justify-between gap-3 p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0 bg-white dark:bg-gray-800">
  137. <h3 class="text-gray-900 dark:text-gray-100 text-xl font-semibold leading-tight">Playlists</h3>
  138. <button id="addPlaylistBtn" class="flex items-center justify-center size-8 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors duration-150">
  139. <span class="material-icons text-xl">add</span>
  140. </button>
  141. </div>
  142. <nav id="playlistsNav" class="flex-1 overflow-y-auto p-3 space-y-1 bg-white dark:bg-gray-800">
  143. <!-- Playlists will be populated here -->
  144. <div class="flex items-center justify-center py-8 text-gray-500 dark:text-gray-400">
  145. <span class="text-sm">Loading playlists...</span>
  146. </div>
  147. </nav>
  148. </aside>
  149. <!-- Main Content -->
  150. <main id="playlistDetails" class="flex-1 bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col mobile-full-width" style="height: calc(100vh - 72px - 64px - 48px);">
  151. <header class="flex items-center justify-between gap-4 p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0 bg-white dark:bg-gray-800">
  152. <!-- Mobile back button in header -->
  153. <div class="flex items-center gap-3 flex-1 min-w-0">
  154. <button id="mobileBackBtn" class="hidden mobile-flex md:hidden items-center justify-center size-8 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors duration-150 flex-shrink-0">
  155. <span class="material-icons text-lg">arrow_back</span>
  156. </button>
  157. <div id="currentPlaylistTitle" class="flex items-center gap-3 min-w-0 flex-1">
  158. <h1 class="text-gray-900 dark:text-gray-100 text-2xl font-semibold leading-tight truncate">Select a Playlist</h1>
  159. </div>
  160. </div>
  161. <button id="addPatternsBtn" class="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors duration-150 disabled:opacity-50 disabled:cursor-not-allowed flex-shrink-0" disabled>
  162. <span class="material-icons text-lg">add_photo_alternate</span>
  163. <span class="hidden sm:inline">Add Patterns</span>
  164. <span class="sm:hidden">Add</span>
  165. </button>
  166. </header>
  167. <div class="flex-1 flex flex-col overflow-hidden bg-white dark:bg-gray-800">
  168. <!-- Patterns Grid - Scrollable -->
  169. <div class="flex-1 overflow-y-auto border-b border-gray-200 dark:border-gray-700">
  170. <div id="patternsGrid" class="grid grid-cols-[repeat(auto-fill,minmax(128px,1fr))] gap-4 p-4 mobile-patterns-grid">
  171. <div class="flex items-center justify-center col-span-full py-12 text-gray-500 dark:text-gray-400">
  172. <span class="text-sm text-center">Select a playlist to view its patterns</span>
  173. </div>
  174. </div>
  175. </div>
  176. <!-- Playback Settings - Fixed at bottom -->
  177. <div id="playbackSettings" class="bg-white dark:bg-gray-800 flex-shrink-0 hidden">
  178. <div class="px-4 py-4 border-b border-gray-200 dark:border-gray-700">
  179. <h2 class="text-gray-900 dark:text-gray-100 text-base font-semibold mb-3">Playback Settings</h2>
  180. <div class="space-y-4">
  181. <!-- Run Mode & Shuffle Section -->
  182. <div class="pb-3 border-b border-gray-100 dark:border-gray-600">
  183. <div class="flex flex-wrap items-center gap-3 mb-3">
  184. <div class="flex gap-2">
  185. <label class="text-xs font-medium flex items-center justify-center rounded-md border-2 border-gray-300 dark:border-gray-600 px-3 h-8 text-gray-700 dark:text-gray-300 has-[:checked]:border-blue-500 has-[:checked]:bg-blue-50 dark:has-[:checked]:bg-blue-900/20 has-[:checked]:text-blue-700 dark:has-[:checked]:text-blue-300 has-[:checked]:font-semibold relative cursor-pointer transition-all duration-200 hover:border-gray-400 dark:hover:border-gray-500">
  186. <span class="material-icons text-sm mr-1">play_circle</span>
  187. Once
  188. <input class="invisible absolute" name="run_playlist" type="radio" value="single" checked/>
  189. </label>
  190. <label class="text-xs font-medium flex items-center justify-center rounded-md border-2 border-gray-300 dark:border-gray-600 px-3 h-8 text-gray-700 dark:text-gray-300 has-[:checked]:border-blue-500 has-[:checked]:bg-blue-50 dark:has-[:checked]:bg-blue-900/20 has-[:checked]:text-blue-700 dark:has-[:checked]:text-blue-300 has-[:checked]:font-semibold relative cursor-pointer transition-all duration-200 hover:border-gray-400 dark:hover:border-gray-500">
  191. <span class="material-icons text-sm mr-1">repeat</span>
  192. Loop
  193. <input class="invisible absolute" name="run_playlist" type="radio" value="indefinite"/>
  194. </label>
  195. </div>
  196. <div class="flex items-center gap-2">
  197. <input id="shuffleCheckbox" type="checkbox" class="h-4 w-4 text-blue-600 bg-gray-100 dark:bg-gray-700 border-gray-300 dark:border-gray-600 rounded focus:ring-blue-500 dark:focus:ring-blue-600">
  198. <label for="shuffleCheckbox" class="text-xs font-medium text-gray-700 dark:text-gray-300 select-none cursor-pointer flex items-center gap-1">
  199. <span class="material-icons text-sm">shuffle</span>
  200. Shuffle
  201. </label>
  202. </div>
  203. </div>
  204. </div>
  205. <!-- Timing & Clear Pattern Section -->
  206. <div class="pb-3 border-b border-gray-100 dark:border-gray-600">
  207. <div class="grid grid-cols-2 gap-3">
  208. <div class="flex items-center gap-2">
  209. <span class="material-icons text-sm text-gray-500 dark:text-gray-400">schedule</span>
  210. <span class="text-xs font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap">Pause:</span>
  211. <div class="relative flex-1">
  212. <input id="pauseTimeInput" class="w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-1 focus:ring-blue-500 focus:border-blue-500 h-8 px-2 pr-8 text-xs" type="number" value="5" min="0" step="1"/>
  213. <span class="absolute right-2 top-1/2 transform -translate-y-1/2 text-xs text-gray-500 dark:text-gray-400">s</span>
  214. </div>
  215. </div>
  216. <div class="flex items-center gap-2">
  217. <span class="material-icons text-sm text-gray-500 dark:text-gray-400">clear_all</span>
  218. <span class="text-xs font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap">Clear:</span>
  219. <select id="clearPatternSelect" class="flex-1 rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-1 focus:ring-blue-500 focus:border-blue-500 h-8 px-2 text-xs">
  220. <option value="adaptive">Adaptive</option>
  221. <option value="clear_from_in">Center</option>
  222. <option value="clear_from_out">Perimeter</option>
  223. <option value="clear_sideway">Sideway</option>
  224. <option value="none">None</option>
  225. </select>
  226. </div>
  227. </div>
  228. </div>
  229. <!-- Run Button Section -->
  230. <div class="pt-1">
  231. <button id="runPlaylistBtn" class="flex items-center justify-center gap-2 w-full h-9 px-4 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:hover:bg-gray-400 text-white text-sm font-medium rounded-md transition-colors duration-200 disabled:opacity-60 disabled:cursor-not-allowed" disabled>
  232. <span class="material-icons text-base">play_arrow</span>
  233. <span>Run Playlist</span>
  234. </button>
  235. </div>
  236. </div>
  237. </div>
  238. </div>
  239. </div>
  240. </main>
  241. </div>
  242. <!-- Add Playlist Modal -->
  243. <div id="addPlaylistModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
  244. <div class="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md mx-4">
  245. <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Create New Playlist</h3>
  246. <div class="space-y-4">
  247. <div>
  248. <label for="newPlaylistName" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Playlist Name</label>
  249. <input id="newPlaylistName" type="text" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 px-3 py-2" placeholder="Enter playlist name" autofocus>
  250. </div>
  251. <div class="flex gap-3 justify-end">
  252. <button id="cancelPlaylistBtn" class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-colors duration-150">Cancel</button>
  253. <button id="createPlaylistBtn" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors duration-150">Create</button>
  254. </div>
  255. </div>
  256. </div>
  257. </div>
  258. <!-- Rename Playlist Modal -->
  259. <div id="renamePlaylistModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
  260. <div class="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md mx-4">
  261. <h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Rename Playlist</h3>
  262. <div class="space-y-4">
  263. <div>
  264. <label for="renamePlaylistInput" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">New Name</label>
  265. <input id="renamePlaylistInput" type="text" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 px-3 py-2" placeholder="Enter new playlist name">
  266. </div>
  267. <div class="flex gap-3 justify-end">
  268. <button id="cancelRenameBtn" class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-colors duration-150">Cancel</button>
  269. <button id="confirmRenameBtn" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors duration-150">Rename</button>
  270. </div>
  271. </div>
  272. </div>
  273. </div>
  274. <!-- Add Patterns Modal -->
  275. <div id="addPatternsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
  276. <div class="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-4xl mx-4 max-h-[80vh] overflow-hidden flex flex-col">
  277. <div class="flex items-center justify-between mb-4 flex-shrink-0">
  278. <h3 id="modalTitle" class="text-lg font-semibold text-gray-900 dark:text-gray-100">Add Patterns to Playlist</h3>
  279. </div>
  280. <!-- Search Bar and Controls -->
  281. <div class="mb-4 flex-shrink-0 space-y-3">
  282. <div class="relative">
  283. <span class="material-icons absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-gray-500 text-lg">search</span>
  284. <input
  285. id="patternSearchInput"
  286. type="text"
  287. placeholder="Search patterns..."
  288. class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
  289. />
  290. <button id="clearSearchBtn" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 hidden">
  291. <span class="material-icons text-lg">clear</span>
  292. </button>
  293. </div>
  294. <!-- Sort and Filter Controls -->
  295. <div class="flex flex-wrap gap-3 items-center">
  296. <div class="flex items-center gap-2">
  297. <span class="text-xs font-medium text-gray-700 dark:text-gray-300">Sort by:</span>
  298. <select id="sortFieldSelect" class="text-xs rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-2 py-1">
  299. <option value="name">Name</option>
  300. <option value="date">Date Modified</option>
  301. <option value="coordinates">Coordinates</option>
  302. <option value="favorite">Favorite</option>
  303. </select>
  304. <button id="sortDirectionBtn" class="p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-500 dark:text-gray-400" title="Toggle sort direction">
  305. <span class="material-icons text-sm" id="sortDirectionIcon">arrow_upward</span>
  306. </button>
  307. </div>
  308. <div class="flex items-center gap-2">
  309. <span class="text-xs font-medium text-gray-700 dark:text-gray-300">Folder:</span>
  310. <select id="categoryFilterSelect" class="text-xs rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-2 py-1">
  311. <option value="all">All</option>
  312. </select>
  313. </div>
  314. </div>
  315. <!-- Smart Toggle Select All button -->
  316. <div class="flex items-center justify-between">
  317. <button
  318. id="toggleSelectAllBtn"
  319. class="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-colors duration-150"
  320. >
  321. <span class="material-icons text-base" id="toggleSelectAllIcon">check_box_outline_blank</span>
  322. <span id="toggleSelectAllText">Select All</span>
  323. </button>
  324. <span id="selectionCount" class="text-xs text-gray-500 dark:text-gray-400">
  325. 0 selected
  326. </span>
  327. </div>
  328. </div>
  329. <div class="flex-1 overflow-y-auto">
  330. <div id="availablePatternsGrid" class="grid grid-cols-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3 sm:gap-4 p-2">
  331. <!-- Available patterns will be populated here -->
  332. </div>
  333. <!-- Loading indicator -->
  334. <div id="patternsLoadingIndicator" class="flex items-center justify-center py-12 text-gray-500 dark:text-gray-400 hidden">
  335. <div class="flex items-center gap-3">
  336. <div class="bg-gray-200 dark:bg-gray-700 rounded-full h-6 w-6 flex items-center justify-center">
  337. <div class="bg-gray-500 dark:bg-gray-400 rounded-full h-3 w-3"></div>
  338. </div>
  339. <span class="text-sm">Loading patterns...</span>
  340. </div>
  341. </div>
  342. <!-- No results message -->
  343. <div id="noResultsMessage" class="flex items-center justify-center py-12 text-gray-500 dark:text-gray-400 hidden">
  344. <span class="text-sm">No patterns found matching your search</span>
  345. </div>
  346. </div>
  347. <div class="flex gap-3 justify-end mt-4 pt-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0">
  348. <button id="cancelAddPatternsBtn" class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-colors duration-150">Cancel</button>
  349. <button id="confirmAddPatternsBtn" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors duration-150">Save</button>
  350. </div>
  351. </div>
  352. </div>
  353. {% endblock %}
  354. {% block scripts %}
  355. <script>
  356. // Force light mode styling if not in dark mode
  357. document.addEventListener('DOMContentLoaded', function() {
  358. const isDark = document.documentElement.classList.contains('dark');
  359. if (!isDark) {
  360. // Force light backgrounds
  361. const sidebar = document.getElementById('playlistsSidebar');
  362. const mainContent = document.getElementById('playlistDetails');
  363. const nav = document.getElementById('playlistsNav');
  364. if (sidebar) {
  365. sidebar.style.backgroundColor = '#ffffff';
  366. sidebar.style.borderColor = '#e5e7eb';
  367. }
  368. if (mainContent) {
  369. mainContent.style.backgroundColor = '#ffffff';
  370. mainContent.style.borderColor = '#e5e7eb';
  371. }
  372. if (nav) {
  373. nav.style.backgroundColor = '#ffffff';
  374. }
  375. // Force light text colors
  376. document.querySelectorAll('h1, h2, h3').forEach(el => {
  377. if (!el.closest('.dark')) {
  378. el.style.color = '#111827';
  379. }
  380. });
  381. }
  382. });
  383. </script>
  384. <script src="/static/js/playlists.js"></script>
  385. {% endblock %}