From 2ee8d27bf064a78d19db6a82501b4c14a98948b7 Mon Sep 17 00:00:00 2001 From: geoffrey45 Date: Mon, 17 Jan 2022 12:32:27 +0300 Subject: [PATCH] server: add get album bio from last fm function - co-written by Github Copilot --- Pipfile | 11 + server/app/api.py | 29 ++- server/app/functions.py | 188 ++++++++++++++++-- server/app/helpers.py | 177 ++--------------- server/app/models.py | 2 +- src/assets/css/_variables.scss | 3 +- src/assets/css/global.scss | 12 -- src/assets/images/featured-artists.webp | Bin 0 -> 25932 bytes src/components/AlbumView/AlbumBio.vue | 59 ++++-- src/components/FolderView/SongList.vue | 117 ++++------- src/components/LeftSidebar/Navigation.vue | 21 +- .../PlaylistView/FeaturedArtists.vue | 70 +++---- src/components/PlaylistView/SongList.vue | 94 --------- src/components/RightSideBar/NowPlaying.vue | 4 +- src/components/SongItem.vue | 9 +- src/composables/perks.js | 1 + src/composables/state.js | 14 +- src/main.js | 2 +- src/views/AlbumView.vue | 1 - src/views/PlaylistView.vue | 2 +- 20 files changed, 370 insertions(+), 446 deletions(-) create mode 100644 Pipfile create mode 100644 src/assets/images/featured-artists.webp delete mode 100644 src/components/PlaylistView/SongList.vue diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..5d44a48 --- /dev/null +++ b/Pipfile @@ -0,0 +1,11 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] + +[requires] +python_version = "3.8" diff --git a/server/app/api.py b/server/app/api.py index 5dc76ff..007c463 100644 --- a/server/app/api.py +++ b/server/app/api.py @@ -5,18 +5,19 @@ from app import functions, instances, helpers, cache bp = Blueprint('api', __name__, url_prefix='') -all_the_f_music = [] home_dir = helpers.home_dir - all_the_f_music = helpers.getAllSongs() + def initialize() -> None: helpers.create_config_dir() helpers.check_for_new_songs() + initialize() + @bp.route('/') def adutsfsd(): return "^ _ ^" @@ -79,7 +80,7 @@ def x(): def get_album_artists(album, artist): album = album.replace('|', '/') artist = artist.replace('|', '/') - + tracks = [] for track in all_the_f_music: @@ -161,7 +162,7 @@ def getArtistData(artist: str): @bp.route("/f/") @cache.cached() -def getFolderTree(folder: str = None): +def getFolderTree(folder: str): req_dir = folder.replace('|', '/') if folder == "home": @@ -180,21 +181,11 @@ def getFolderTree(folder: str = None): dir = { "name": entry.name, "count": len(files_in_dir), - "path": entry.path.replace(home_dir, "") + "path": entry.path.replace(home_dir, ""), } folders.append(dir) - # if entry.is_file(): - # if isValidFile(entry.name) == True: - # file = instances.songs_instance.find_song_by_path(entry.path) - - # if not file: - # getTags(entry.path) - - # songs_array = instances.songs_instance.find_songs_by_folder( - # req_dir) - songs = [] for x in all_the_f_music: @@ -203,6 +194,7 @@ def getFolderTree(folder: str = None): return {"files": helpers.remove_duplicates(songs), "folders": sorted(folders, key=lambda i: i['name'])} + @bp.route('/qwerty') def populateArtists(): all_songs = instances.songs_instance.get_all_songs() @@ -222,6 +214,7 @@ def populateArtists(): return {'songs': artists} + @bp.route('/albums') def getAlbums(): s = instances.songs_instance.get_all_songs() @@ -239,6 +232,7 @@ def getAlbums(): return {'albums': albums} + @bp.route('/albums/') @cache.cached() def getAlbumSongs(query: str): @@ -260,3 +254,8 @@ def getAlbumSongs(query: str): } return {'songs': helpers.remove_duplicates(songs), 'info': album_obj} + + +@bp.route('/album//<artist>/bio') +def drop_db(title, artist): + return functions.getAlbumBio(title, artist) diff --git a/server/app/functions.py b/server/app/functions.py index 8c1cc97..c1efc9d 100644 --- a/server/app/functions.py +++ b/server/app/functions.py @@ -3,14 +3,23 @@ This module contains larger functions for the server """ import time -from progress.bar import Bar -import requests import os +import requests +import random + from mutagen.flac import MutagenError +from mutagen.mp3 import MP3 +from mutagen.id3 import ID3 +from mutagen.flac import FLAC +from progress.bar import Bar +from PIL import Image +from io import BytesIO + from app import helpers from app import instances from app import api + def populate(): ''' Populate the database with all songs in the music directory @@ -23,21 +32,8 @@ def populate(): files = helpers.run_fast_scandir(helpers.home_dir, [".flac", ".mp3"])[1] for file in files: - file_in_db_obj = instances.songs_instance.find_song_by_path(file) + getTags(file) - try: - image = file_in_db_obj['image'] - - if not os.path.exists(os.path.join(helpers.app_dir, 'images', 'thumbnails', image)): - helpers.extract_thumb(file) - except: - image = None - - if image is None: - try: - helpers.getTags(file) - except MutagenError: - pass api.all_the_f_music = helpers.getAllSongs() print('\ncheck done') @@ -82,3 +78,163 @@ def populate_images(): bar.next() bar.finish() + + +def extract_thumb(path: str) -> str: + """ + Extracts the thumbnail from an audio file. Returns the path to the thumbnail. + """ + + def use_defaults() -> str: + """ + Returns a path to a random image in the defaults directory. + """ + path = "http://127.0.0.1:8900/images/defaults/" + \ + str(random.randint(0, 10)) + '.webp' + return path + + webp_path = path.split('/')[-1] + '.webp' + img_path = os.path.join(helpers.app_dir, "images", "thumbnails", webp_path) + + if os.path.exists(img_path): + return "http://127.0.0.1:8900/images/thumbnails/" + webp_path + + if path.endswith('.flac'): + try: + audio = FLAC(path) + album_art = audio.pictures[0].data + except: + album_art = None + elif path.endswith('.mp3'): + try: + audio = ID3(path) + album_art = audio.getall('APIC')[0].data + except: + album_art = None + + if album_art is None: + return use_defaults() + else: + img = Image.open(BytesIO(album_art)) + + try: + small_img = img.resize((150, 150), Image.ANTIALIAS) + small_img.save(img_path, format="webp") + except OSError: + try: + png = img.convert('RGB') + small_img = png.resize((150, 150), Image.ANTIALIAS) + small_img.save(img_path, format="webp") + except: + return use_defaults() + + final_path = "http://127.0.0.1:8900/images/thumbnails/" + webp_path + + return final_path + + +def getTags(full_path: str) -> dict: + """ + Returns a dictionary of tags for a given file. + """ + + if full_path.endswith('.flac'): + try: + audio = FLAC(full_path) + except: + return + elif full_path.endswith('.mp3'): + try: + audio = MP3(full_path) + except: + return + + try: + artists = audio['artist'][0] + except KeyError: + try: + artists = audio['TPE1'][0] + except: + artists = 'Unknown' + except IndexError: + artists = 'Unknown' + + try: + album_artist = audio['albumartist'][0] + except KeyError: + try: + album_artist = audio['TPE2'][0] + except: + album_artist = 'Unknown' + except IndexError: + album_artist = 'Unknown' + + try: + title = audio['title'][0] + except KeyError: + try: + title = audio['TIT2'][0] + except: + title = full_path.split('/')[-1] + except: + title = full_path.split('/')[-1] + + try: + album = audio['album'][0] + except KeyError: + try: + album = audio['TALB'][0] + except: + album = "Unknown" + except IndexError: + album = "Unknown" + + try: + genre = audio['genre'][0] + except KeyError: + try: + genre = audio['TCON'][0] + except: + genre = "Unknown" + except IndexError: + genre = "Unknown" + + img_path = extract_thumb(full_path) + + tags = { + "filepath": full_path.replace(helpers.home_dir, ''), + "folder": os.path.dirname(full_path).replace(helpers.home_dir, ""), + "title": title, + "artists": artists, + "album_artist": album_artist, + "album": album, + "genre": genre, + "length": round(audio.info.length), + "bitrate": audio.info.bitrate, + "image": img_path, + "type": { + "name": None, + "id": None + } + } + + instances.songs_instance.insert_song(tags) + return tags + + +def getAlbumBio(title: str, album_artist: str) -> dict: + last_fm_url = 'http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key={}&artist={}&album={}&format=json'.format( + helpers.last_fm_api_key, album_artist, title) + + response = requests.get(last_fm_url) + data = response.json() + + try: + bio = data['album']['wiki']['content'] + except KeyError: + bio = None + + if bio is None: + return "None" + + return {'data': data} diff --git a/server/app/helpers.py b/server/app/helpers.py index 9913adc..3881b72 100644 --- a/server/app/helpers.py +++ b/server/app/helpers.py @@ -7,28 +7,25 @@ import threading import time import requests -from mutagen.mp3 import MP3 -from mutagen.id3 import ID3 -from mutagen.flac import FLAC - from io import BytesIO from PIL import Image from app import instances from app import functions -home_dir = os.path.expanduser('~') + "/" -app_dir = home_dir + '/.musicx' - +home_dir = os.path.expanduser('~') + '/' +app_dir = os.path.join(home_dir, '.musicx') +last_fm_api_key = "762db7a44a9e6fb5585661f5f2bdf23a" def background(f): ''' a threading decorator use @background above the function you want to run in the background ''' - def backgrnd_func(*a, **kw): + def background_func(*a, **kw): threading.Thread(target=f, args=a, kwargs=kw).start() - return backgrnd_func + return background_func + @background def check_for_new_songs(): @@ -62,142 +59,6 @@ def run_fast_scandir(dir: str, ext: str) -> list: return subfolders, files -def extract_thumb(path: str) -> str: - """ - Extracts the thumbnail from an audio file. Returns the path to the thumbnail. - """ - - webp_path = path.split('/')[-1] + '.webp' - img_path = app_dir + "/images/thumbnails/" + webp_path - - if os.path.exists(img_path): - return webp_path - - if path.endswith('.flac'): - try: - audio = FLAC(path) - album_art = audio.pictures[0].data - except: - album_art = None - elif path.endswith('.mp3'): - try: - audio = ID3(path) - album_art = audio.getall('APIC')[0].data - except: - album_art = None - - if album_art is None: - return "null.webp" - else: - img = Image.open(BytesIO(album_art)) - - try: - small_img = img.resize((150, 150), Image.ANTIALIAS) - small_img.save(img_path, format="webp") - except OSError: - try: - png = img.convert('RGB') - small_img = png.resize((150, 150), Image.ANTIALIAS) - small_img.save(img_path, format="webp") - print('{} :: was png'.format( - img_path - )) - - except: - img_path = None - - return webp_path - - -def getTags(full_path: str) -> dict: - """ - Returns a dictionary of tags for a given file. - """ - - if full_path.endswith('.flac'): - try: - audio = FLAC(full_path) - except: - return - elif full_path.endswith('.mp3'): - try: - audio = MP3(full_path) - except: - return - - try: - artists = audio['artist'][0] - except KeyError: - try: - artists = audio['TPE1'][0] - except: - artists = 'Unknown' - except IndexError: - artists = 'Unknown' - - try: - album_artist = audio['albumartist'][0] - except KeyError: - try: - album_artist = audio['TPE2'][0] - except: - album_artist = 'Unknown' - except IndexError: - album_artist = 'Unknown' - - try: - title = audio['title'][0] - except KeyError: - try: - title = audio['TIT2'][0] - except: - title = full_path.split('/')[-1] - except: - title = full_path.split('/')[-1] - - try: - album = audio['album'][0] - except KeyError: - try: - album = audio['TALB'][0] - except: - album = "Unknown" - except IndexError: - album = "Unknown" - - try: - genre = audio['genre'][0] - except KeyError: - try: - genre = audio['TCON'][0] - except: - genre = "Unknown" - except IndexError: - genre = "Unknown" - - img_path = extract_thumb(full_path) - - tags = { - "filepath": full_path.replace(home_dir, ''), - "folder": os.path.dirname(full_path).replace(home_dir, ""), - "title": title, - "artists": artists, - "album_artist": album_artist, - "album": album, - "genre": genre, - "length": round(audio.info.length), - "bitrate": audio.info.bitrate, - "image": img_path, - "type": { - "name": None, - "id": None - } - } - - instances.songs_instance.insert_song(tags) - return tags - - def remove_duplicates(array: list) -> list: """ Removes duplicates from a list. Returns a list without duplicates. @@ -245,33 +106,33 @@ def create_config_dir() -> None: """ home_dir = os.path.expanduser('~') - config_folder = home_dir + app_dir + config_folder = os.path.join(home_dir, app_dir) - dirs = ["", "/images", "/images/artists", "/images/thumbnails"] + dirs = ["", "images", "images/defaults", + "images/artists", "images/thumbnails"] for dir in dirs: - if not os.path.exists(config_folder + dir): - os.makedirs(config_folder + dir) + path = os.path.join(config_folder, dir) + os.chmod(path, 0o755) + try: + os.makedirs(path) + except FileExistsError: + pass def getAllSongs() -> None: """ Gets all songs under the ~/ directory. """ - - tracks = [] - tracks.extend(instances.songs_instance.get_all_songs()) + + tracks = instances.songs_instance.get_all_songs() for track in tracks: try: os.chmod(os.path.join(home_dir, track['filepath']), 0o755) except FileNotFoundError: - instances.songs_instance.remove_song_by_filepath( - os.path.join(home_dir, track['filepath'])) - - if track['image'] is not None: - track['image'] = "http://127.0.0.1:8900/images/thumbnails/" + \ - track['image'] + instances.songs_instance.remove_song_by_filepath(track['filepath']) + if track['artists'] is not None: track['artists'] = track['artists'].split(', ') diff --git a/server/app/models.py b/server/app/models.py index 6b9c2c1..543e935 100644 --- a/server/app/models.py +++ b/server/app/models.py @@ -115,7 +115,7 @@ class AllSongs(Mongo): def remove_song_by_filepath(self, filepath): try: - self.collection.remove({'filepath': filepath}) + self.collection.delete_one({'filepath': filepath}) return True except: return False diff --git a/src/assets/css/_variables.scss b/src/assets/css/_variables.scss index 60f7e4b..f0ba04b 100644 --- a/src/assets/css/_variables.scss +++ b/src/assets/css/_variables.scss @@ -7,5 +7,4 @@ $green: rgb(67, 148, 67); $separator: #ffffff46; // sizes $small: .5em; -$smaller: .25em; - +$smaller: .25em; \ No newline at end of file diff --git a/src/assets/css/global.scss b/src/assets/css/global.scss index 4765b09..bc56122 100644 --- a/src/assets/css/global.scss +++ b/src/assets/css/global.scss @@ -74,18 +74,6 @@ button { background-size: cover; background-position: center; background: rgba(0, 0, 0, 0.575); - backdrop-filter: blur(40px); - -webkit-backdrop-filter: blur(40px); - -moz-backdrop-filter: blur(40px); -} - -#bg-blur { - position: absolute; - width: 100vw; - height: 100vh; - background-color: rgba(27, 27, 27, 0.548); - background-image: url(../images/dark-bg.jpg); - z-index: -1; } .l-sidebar { diff --git a/src/assets/images/featured-artists.webp b/src/assets/images/featured-artists.webp new file mode 100644 index 0000000000000000000000000000000000000000..2f0fc5b17ddb44a5b62d541a3b80f778d88f01f1 GIT binary patch literal 25932 zcmb@tV~{3K^r-o^ZDZQDZQHhO+qOMz+cu_c+n%<)^V=IY{`bRf?3c}o%zWz9NuA87 z%F4=8k`xmYF$DnBMTHd96gV`X0RRBOKkp9=xC8=73JWV>0{=S&0Av0af-(RAwstN~ zO5#EU8k$-J|H=NhGc<N~_}~2h5CZ`K&ntj`Apo#Q`~PtL|4RgAV(M)CPwDobcX9ex zH~;`-@{du?{}+4w$438)WBy|&6(!++I{E*Y!u<bWqyK~5T^wBg>5ToSZ|vaoA8-9* z9$OpN|H}H${a0gHQ#)1Vf3e0tj|*@HC;`L)LjU;xSNA{mv@ZkzIGz9ikc9t{8D#<h zZJ_`F=EncXNb&&ygb)Crb=JYq$?*TK4CucQ*vt$7xGw_$;4}dMlsNzZTKj)j^ncR+ zPhb4Mw3Xmr8r*;FwEuUQ18e}M00Mv{zz$#xp!-Km00saffbHLiAprkpsoPAT-#}CW zz<+`G7%`(nhzJXiGCz+9KtP+@Y-~6AEXt^;jNTCBe($T{Ez6NZp0L`N)}{YQfA`&2 zefXF9$@Z9jyj<nI%76R+<<o4++gSbB3HhP@czNHw)$iEd|MC33@wflMcwqjltLkI( zAO4|zqrb;^!+)T^<=^VF;~(@p@bw<7YS#b#qrJqv;a~Oh`}^rT_`B{6A=H2Mr{)4; zbJu|X+<)7D|0n9WG9Uj!{&Q!V|M2JNNA>&k1z{I|8UKZU-cQ9h%%APM<9qYt^zG*> z?}-2B=k$L3+>X5$k#6|JdLfw!lzKTjR))$+H6<*y`*gOW82gMQC-yuu77!%zy0Nq| zOR#{VkE=T8M&jBb`d+goR?f|(k`I&E>@3mF$sY9XH7~p|Yf6QrmEor1#I?dz$5S9s zt`(R1G%4d{?9h_mJ1DR;ocws2V7AqaQ}ImkO^Jk33MW5Dv1XgaO^@3-^`qtM(CLj9 z?59rlNiO5je9%Og*@wGldHmxiwFZ_QELxmcJtre#)z{4hMqmm;u*|uSr>}%+(i;L^ zs>6fT+Q59ob(O)r`FInHfxR#!xwR}j)`AQ-5Fq?EgLi-iv3WgCs+^E)GtNis7L9yL z&P2qUOjOiVE|U@NQ8i$X7=3rvjRo(W@XJkQMN$w5OPN>zSnElJkbd+SE&)HK>&v6} z^!J+A2r7q^sEHWSdFO_hd5bmX<BXEJqF$GDIz)P<R9R|j<MbazRgNM}|FLdFbGC~( zvv>=mfXyU<2D=D|zk}1yl&Hghh@)eW#LvdnDZx3(*4Hh9u#_Zmk_{N{j$oyRJCrg( z8@y9aeM022eQN@(KA}CjS9uLqd6h4g{1H(F5?fE)9zg$!L4{KjZTTvEIS}5@<r&UJ z{=M2O*%IDWs%G1RL)^jSw7Py|c8|m;Ru^V8f8rz<+J%g5FzHTBIv1$(b2(^%|FJ-1 zSc<cBp|Ky-o`pcG5YCmB$q~#4rVLzGKH6K;L5s6${rnS_3>@dk!C_F@l7$gc9Ii~- z=A)-qD-?ll35KXU>f9(ssU+}yly<H=sTO7Pr>wCT?&z&2C<bGsTogWiQC7J}&nf35 zfwlOe&q+NMBr9+O8W)>4gVB3U{Li*0`cHWO@RU@MQsM!yHw>ZS{6cnCld>kO_ah#r z4a!kt*wx<tBGUH;61sB8L0l_!#GA<FFjBw>oiMJccALfdwfS$t{PlDPIv3p7Ov!*J z4&@Cx#%~PuQ$}r?<K-7VTb$!4-sVr3Q<>dGRq?83rpaTE-0gt@Q2;k_rKYh(`M?)+ zmep;ne7(f>6_^47YR+-PQ_Th|duo$an2SlMx3SSK0p2WhmcgEeO5v+gZLi0e?MQ?S z*AcGLwPcZOt|i-{B8yGLdtOSR%~I&*_i@3oQna0p#0ViAkzUAbwc~YoDvLn3dU7R) z%J@|T>PAf_epJJ~<VW|uw;clei8o@Rk?kbat@C8>l?pU#sjE;y9qWpXKDKANvYWCd zRR{xrP3K>c^T<8Brrp~KeY#@<Xc$OZp>l>~{G+946Ko`+Uk9a{xK4P^LO^<=<}02? zST2;WJ{WzSdcFxGCGQ(JVIl-%w?4i$wqt8+YYTG)O+)ly7ZH*hf}sgjJtwU6wumya zJHxd)WB^IqKfpJPvP^WB-g5i)U^bIi7ltTjz;LB!2BpWFPi!{iz(06`-pxD#7%Daf z$kR#t{hsTZje`L11u%O-n<9BaX7GjT&%G1NLrIk5FISRIK96dvjK-_l>vkt40g8b7 z1A@~AVQ4*C1fRGNZ4`zZ)AMllxN^T4E!@w+JP8m)CbONr2ia8oQXdEX8><|k<HTne z<-reqvgWpfySW@=HZJ^feJBo;AG#0r0Fm$LZ0*(es_kHidBI*@87ng*7H?u+IqtcH zNygB#hvDR(4xcX?F)$M;=emy^gM}4NYGEBh5e{;q1@&o*YyTU)3SFaDEGr->QPF4r zqO9DHzShT^3-MCwcEm9?wS>QQHobk2^JH!T8`e@}?2>^C@mHP#dE%k3tY#@cJc66` zSP&*IanAu7mNOFr)NI|1Ag^x4SSf?KeUMmCZxSCA*l9ZK$Wmh`14UdhsJT`S54^*s zH@*k{<xCLwmtz&&89rcwe%*PLG@|X~hmA!^No=k`YbQqJcBfgHe`QrOdi9H;^Brtx zZ=V32>Q=E^7%`)!<v(uRxMCR=e9ERf3zaZU8GBqMVl28NDw>;JO<_WQ+*F*DG%P9N z3|6~(V@o}@${D)oG>H1HNkACSm3vQ`pM{phNS2YJVjo-Mw>;uP&6h|E7`5E7<2~v3 z!j#o5)#XRU4IqTv4yT{-MDV99i#t#20%Kf7SseoFx$1}}xD6x=CVp~z;uV$!43xH* zb}IK%jh=H_jcE$M!fk_n{4~}rqZpe1S1#n(eZdpt@DnkoYB)*5Jj%aKFPVa~cdcih zg<{k*>WlRJMSd3FJBMoJ@Ef-#Rmx&i#U+9IhZ4BB`a{r-X#~dPM@@y@Hx>!F`n^fl z5kf0=e}9po=U0C*Ai#QM?B82yx)$0Q4xHoL!J(%SnEW#$G-c`Hfc>kJQhEvvndic5 zqZ4y<!vcqt+aj)Y;ze>ix}MiO+JB~Q2i4DhkbjMT0hR1?F|+9rwyQq|F$=*hQgp%N z$6rk`8R@J1&k_W{p4P#smJ=W$4;HmR%o^mWq+W)Jx?8Of7v+HoG1?6R3AuuZ2NcVE zF|ffYX?Tnr6aL(7mo5G<{97m~v&kTYV8mhMY`i6s7qz#*_bv23#TIE+u`yV+Tni3( zpgF~uR)+25!zt$PV*+!0yMB+(o7;=<6LFGHS`dA|*p9>HUn+0mExlI{;Z=2tTqsFn zVi_&B&;(g$ur=(bU6$p5qHca>cwaLivA_clQ-jNjL8SNzQCgci@_7@9K3v7XjHOB5 zWI^cy|5mq(HTM~ST$y@)PVg{Gf$W(1Cy8~T_1Sv=Rk@QdMa0KtbVB_IldV1%pqg#d z5@W!*k~cT0()Uwa*gGZ-|3npt7au0y{rMY|(W%z&#(I$h;P<k5auzT#;YZ4jN$Rp_ zJ$4}>VUIWyACtEc0Jk1xo@O?801ukeBQ<JDp{!7%4eFw3X9M(2=t9XqXv#&HGL^94 z##YLma5FDmcC4L<<2!CXh8$PSs7=J-7lpIgH3N*%#cd|dC2hD#NBLF!<c;~Sg+hP! zfI13iqmy0A%15F7zgXNgg1%r5^{1VM<rgnwp#xPL*Mq#pIUqclMc|{53^aen3wp|= z81q&Uz~5!|HqrGh!U7N-$*VALdzvan(B^(5ozaNT;-78U;j}56PfFQ@p@Ri6JR7qW zK`Y`Hrujl8LVqrYRk{w4y-%j~gTJNC*TG<FbM)nuZ$1jrmvtIWu^7{HCi0c+a&uO* zx6&U1#iCB$?;|+8L@ytD02pZ^>Q#w|<HEr|MlTB`yyFaq@uWe@b80AL6j1M=2%@(@ zW1?~~=MBBFF&(T3bK`f|oY={GuQC!#8{niCSE3IbQp{F9n{^8*fy-2l8xQj*j+~Xb zrVd>q5a`fL+)kVBzKLqc*9M*UpF4zRTdT0Yplpg!p(%pKGaM%x-bFtoC`B}=gM%Pq z!5*z*4OYj7Zu_<AX=-h@`|23Sder%DzPWQJxH8jd%8_<}a7Bj=>o@4_Hb?0EKh*JD zRCUPS8b#c~GqDec1vzGEGxJ#PAF0+*J7xG2z60XB9YX8@;_*$|BLXyo8x`oG8Y8<u zWp0W38|wTIEo*9L&@RHWsMT4x6>cP_GZsjlPMDWe^L6%DSrE!TNR%XoaL5c=bs+G( zZ@u?q(R^3E7Yp)#zGGy}7gw9LnqXc#fv_rE0!+j2VdzLnvCJ8?7yQU;u6V)DXps3( zuAvmPXaxpUJ@}V8E7!Y*>fy5$l8`>zic6|xrf3ves%OaXG?ALSNgY0#?c^Ml`?w%) zGZaDHlH0xrZ$V1D+$tt>C28lZfW2io`W!2`+s0zFOB}B{4wq|28jB(vtJIBcv&Oq9 zd$fF|(bjpSLbkBf<{G}<O}H-WK{cs2!OwR<*jP=ARXYhMdKPmy9Ro}n9yll6(Sb3z z`9&S?EpVFNeQ&<l1zDki;hL-2x&|&!8?8T27R<eWx=HTZ+5-F`28Z$H$g5ngB&fO? z1SUR<KmX02mH2ks8jSO>lg(2c?v4)bI#IxwpN9$yX^Or$e2@5jRaU~~D;D0cPei|8 z9#G61q9Uu;+wFngZ=b$8cl`0;+C>ORYd<X{@LX{}j@Xq!l{_@VR&410@cfszMxYG< z@cogSczli^X$%f%^#ed(%o&EbSsk7*Alv9S3uPeGCs(NPsQ#)=^Hh?Lrfn@6aO?|R z);W2!x<W~ZWxvKjLPgxC3eY(^wW^s`_1lv7?pVUrxI8t?P~IgY;Fb9{@DCSZ*>f_7 zC)=f`SaGk?9>dQMC%@M5!e|NlzK-%O<wAs0x%=AytOe#hYMZsg2u(Fqt**->rZT>> z{a5L=KN@5$4N6J6M4ub}leI`zMWrQVZ7@Rj2T}*$U@!ucs|^ul<QX{`xw^R>xyV$Q z5mqw)!A#V+oP{gBQJD<ZjJQYk4o{yjII|;#dvhK~<GP{H#ptH%Ss>VdfbI)*zg_Z` zW+ErZai|6cC~~vVJoOo%#_uwiZ?26XVswIVYmWEa7i>0boXBqkYU=eTt&|I+kWEag z`fZC}tT$bA@iW`2JUDC5q`*y)%R@ZOV{j2<0xRSW32pf+yEDK2i{}t?W}0V0Wypll z*c>oznKkFVQQT5=0cW0_9r*y2*L)oBXtvYb^J#YcrU1}+OVe0QYiN&+UJTCw>9#$S z89F;x+`P!h8dD4{TYsw8y4RvIl0zi+#!9LfoZQCz-fMKZxIe>SlR4#zk{hWk-+Xy+ zb83$Un5Dq<HRZoX9sJ!ajkQ67J!`ObIH{G9hqP$JKX}&*-?9xa8TQ(L&1|0WB*8tW z%ZClkFnZ8jNGF@8_7p&Xt;F&=T!b<Uzt>@NB`52lEg183Y-;3w#o!B~P+|WW7fFDx zjyjEzB%t`f?EbU1U+yj^bjW;T3Ml|>E5>{M0WCMy_v`j<!vh;w#l7=w+|{^FG+M`# z&>e>;c+!vxxpJ6g`X_NlpEe0RJo6NVHZ>guR~iD}0Yz?qSmj#QsYi?0)0jN`Gv)=x zV@MNZU<9E(a;dDD?)r0JXBII*vyb=<l(W*wuE6|nCKV6Ou!`Y%l=tU0C-~2d{Hgul z!sWUd<RYtvxkY-(2BVk^C;^nY(~)(FPNZ-4SLN*R7P~)N^<`#@MIUXCm1OQm$nu2Y ztI$SwrVLzsDjKV$RY3;$ufKAfTLo<?GWUkB>JI{Wt*OHKGO!(3qe89KT)1c8{gN`_ zJ^`B=6U=>=lK!%)rD0J;t5l%Y+-3dn4b+Bob`h`k*|k1qyI(IC<;$sIQ}q%bU)Ua% z-L$b;o1va58U!UrR^HT@m8*I|7pf9c+2O*?mdWkQ*e5I}d+I22UA{Rw{q3+@k+TaL zE(j0;=WV9F(1HxNMZ#$PVi?bj6;~`;p;Uv0ip?h~s#QFWCJx7*n5#waJIegSQ&-Du zBcN}gIr;5&;=^7CbeBvcHkT2jkD7r!?drtV*M}r?Y%I4<f@-oFLqU+UmdrR~%Nv(e zlf##FlL2J555f%VESbWwMLm?Uay$6;Re4B1xYx$Sx(SH<Hct3voq<Z+t<saoW8SQ+ zRF!c7#F1c)s;qfNrAww_d7MW9(3T?$o=YcfFWBsXAmZPArGv<l#0@gV!aW!_$%Lh0 zShWo_s<|73n%q?r__4bNO?gu8I3w5)zLi}Rk^5YgzLLEP7vHz^g~y>2tOKT}&vxVp zN&cXcNee7=9XmodFPTRzm?=FS{-AaQo>0xNKRJ@%X90qrMD`2|aH_G80=Vqw4VPKV zZ&&^&QNTX1Yp?q99K3FMw3nfaWtlnGXX4<M`py!{86>$9cWMsYn<8eNY6p5}4)Bf= zYGw1udLgUoAc7^3G8LdV_=>y6#Hxj8C=<VH)A0o}-UH4j+ENY{wPhHnZ1LU>oA4ET z|Gix+%+TK}<#CpSE-24YGlq#SK`49O&gZf7LWc53M$*XxvBw9qbfYtzKL@IWFyI{f z7oKhDMm)Sigfw@{Of8Hz{9gW<Qy!U0njU3XpZ;O6R71<S9eJJ$@`)2JamDXlnZ4Qu z3&^;dxQ%&8R&V*N!4YC^_GDS{0G-9Kz)H(}_>B%jlE3U%$lbi1XW)wsM*|-Uk@wf+ z*q;DNGANdY?<Kq@HM_V~L95~bwYE5)0d0@$7gm5!YysXPOtO)&d-;~?iO}DOVv~wg z{rHgBRCsxIDe7Jz6dwN$d=Z->Rfl!NHghR8>Pjuz&?L^`gtWu{e$oSa+r;W9|J}N` z<{yx~oDv<C$Ga!yL-=JQ;OJ@WHi5uoX`Nc42x!jvJsXlySQL4A%NY_yVCsBy&gxnY zklOOD11;R@Wo~D`wes!UY{6g0n7+osZg&9^=%40rtC~L`xZ9DYwiT^ha|qEHbt1b@ z+`1NOXl9B*N_*@3{p_UORuLM99I->KkvQlnS|_Du#&%PI+6{GDFl^~jB%^OLiu8Tn z#k*%Bs+Rn5^X}+W5jHPfBq2^aWx-doVb3x^+fe>!e8g&Yi_lEor|eAb-hX=c{#ve> z^D1R;^I-r}xIr1-({&!#Y{f;l`UeVYn2spigo_Ux5r$w<dC)yrTfc5TAeWDs8qqGC z$wE>>xUo#PNs(}ms)g4bIZ+Sa$t#Hk!u??+j%9J5>1)+&*9bz!9BN1+Ir7Lv`bI={ zrM?n)Cf5#QkS*xcv_>P9PH>yl|59ZFX$vP3nHfI`c3L*gw!jkDuqWIXfhxFeF!$Wh zOAT~iSg*_37O{BlBbC02hoCI6@T}alPx)xsk1>=xZrl1BEOM4(whflC82NBvYH{X1 z@n=QzexmjlUqjbr+yJmi^gU6x2#V0$nP;zfpRkmosopa<_4%EmC~FTt$-Ci#L*Qxz zUHGke06jEPlknpP{eJO$#`xgWw`ZHfU6sq&TumJ<Jf1pK`BN!bu%@2;me>5HV5~SI zaW(Le;SOe(wpAroU^%x(Vt*GX&FtH5B1w2~eS1K~I9}XU7!h;2-i_!dE0Aa!-PMq{ z&Gmtm58u`?{yPiMQxHga32T{fpud9ERc+0yMEqgDddOIYynfrwcV*PCOW_SKX{fRz z@&*Yv8+_q&aK{S7uI@TmIeKb-&|@v0usbU_nf@T+jg~vzOEi)2n?#yH9rrkqk{FS9 z&S=H@LM$BTE!8=uJQT%z$JZJ|5vb`5Fws`z&d;bDaoasL5BCh|Nf>3<W2csP?M<&D zFs6^M+2F;YQN)t5rgSs%Sc5~mpbgCnEF`b*4X$G8feUY(AF1I&slU*_yxc;9E(IJF zOtHPnCNkr{D_l3rVxw9)1aDV25JRtzuMh3{ay18HE<;;eyk2<Q6(pp_=HpwCjQyma z#UzLD{M)}2J$=u@cP*LyAAM2=AbNmW`wC?B4}7FV5peVb8`J4rzVUn23{(pSIbAoA zllICr?O+E?PRzU?)&e%SN0;P%r;A2ol#RT_9zTTcwT7&hP5(&NMqCO|n*N|;h&^Qx zg_KDevh{cTxmY#?2hv@iOyZ8U7k%0J9<_Jb<n1`CF_JFmR2|QNEW~BYhxcT1R{wym zT?#twlR#nNL-vp(63O1~mA$KvA`(I1R^wG4)9svP-QJOMRnRq%qu1#{!7*bP>+l<s z%vo}rX{IWw;j}4-4ru8zZQo+#JGu$<t{Xj|fI^WvW$VFLtqarq&U-(M@5iIz>n}#! z7h-ISWSAt=PZ6UXIf7S`KX0z1U1$kL@p=pv_hGnT(z(YM1`d;BWB$#w=LNj(_;O?| z1P=W8WFGI1tajECmDF+^UnseyyZyC$UfMbMaj-v}Ms&ZkzFI<!IOh|SHZeV!V&kOS z`$rf%4-3)3R48lsxR1Nw&?RYQFCSU<>qUGP9XF_na5og%oD>Oz=9F`Sqk~a_`J%M6 zn&V$`OhNTuPX_H5&){DJ>uPjc7`YW~0VXm1jwwC1Mv!TA##an=F0I-NXtJ5j7}>EU zft?MO;0$&G7z<RX+7l+>`5dK?^D9NYG`-L^uQ?1F{6*{;)01IUTY;PC+43s(X;lvT z6-ZQ<p2?tTI|<ZGllI0dX@T7@ylSQ{ybr;1zgDPtynde_MYUV(qZsXNngw57r%Jg} zq_>#H&gVxqwOL$C6~&*5J=iR_Hm0Pk#K&9r_%4leMk%_B6sBb7Vqbobb(X<(IYlYc z6n`BxB<1FNUE>G!H`Z+(q{-v8>jmb0OJyz{Z>nJMNL+v@39NFg)O{=Gb7$MT7^|6p z+EK9ri_UOf3Tw&8nqH0lmJ4clUe@Nhs@E$1w7n9KSIcpQ90$AF@|!bnwu^sawMsPT zZxGksdHwqGF&a<d-IoWAG`Pf6M`4X^sCeO#7-eRrx%-lxhN-JOlod`g(kxDH`J*F5 z0W+2*BDM80oYWN{J`|#QtzWQ+7>@s~<ar#i-kNQyO~W7IlI|G9)zBJW)sr{Lf|?j3 zB7fT8PBfaTQhXCuw93?+UF?8T1mLr(F*p72DDmXreR6FU;q@aM6-lfMMw50C%YfEp z;p_YeaDsM#tTotOQLaYWUU-vRqO)r1`?mn{tI)|lhSvKA70PDIY%|%;wN6pTa)Q6B z(wC4>WU}UbwZj`8{|EO>IM&g%RVh)CXC(m}?x{u8g(`fo7Q@-7sIi>rRK4a_wsW9t z_VlIsgd}?>U|ZZN%7Q-Kvz5u*?M<dJ#mM*Ok9U6r&BTQle^Yp}i2m_Od0Obt1I?dY z54P~lhHptfZKl(9-~!oaa8t6Xv^gV`tlGV;^)<&X1!q&t6`ydl#OEQxv257H-El*j zFNr_(g=8T*&RSfm=wnko%%u(dN*GfFhQGzg_<tuEVy}+6<c_q-;`Y>Z5XZsXN(AiR z_x*8w_|&QCW9H{)sK&5YWtsGEJ!p7WyeT0MVWFa#F5ezm7{`upNgP(CRZdpZn>_$& zwa-p@qInMHI17K<WGFYKTcoPJPow_302Hf_v)cy>D5o#%YuFvUmyNLGs)2<{XSV-^ z8*MLLy1>V{+BU)YqxdSFj#MnYPr!#ZBP4gRFK)^qv-QPTB&p35j{?-0@{G5$)DmYu zp<bo!FJoH_ln-Go2Q)!r<|FdUj|dW!*yr!5eiGpra*WoMn;-3Ibt-prpz0I)Y95r6 zjAy%vs@*uYcpPgJeJegBunZG6EiY*AXdipe@S@N*qMUhcONOvaF<_=I0_U;Fh)7P2 z(BN}?2nE9|nVbG*{8ok?)j?N1Y0=>NTX~ob15s37ESof`El5$a&LmXy=r+)d$jtEs zlf%8%>ImwixhyCP2(bpc$A;UE3EM=;w!IW9NvO~l6EVnp=Y=RRLM(33YJ<DHFJp-Y z+tEkS2P8`?);L=Ln#%y2uQ!1S=_qSd&mwNKtzJuu3nL<*j?$(YYGrfsIRT_>NG#bV z@kFpY&Tmxm{6M1%0|HXQ#_N^VMz=hv4SfhnlvMK?K1dvoZ`*#UhivRhzkk-5T;=34 zNg}--LZkL1D=o8pO4K|gcM1rzkaDXDI#CGz0-U>YFmn|>T=NIR@iXLr`!kNbKNuy~ zASR1vpS##<H@y9NJYS3Ets7knX6O%?Pz}e%w8bNzS#N<!i;tqp4^NcIT0Pg7ndgl{ z<o*CR7)oGfs0kYuB2MYbWGub-xFY(1)v04ae%voqCwIOp(B#SXq^O~*>8^c5?p&cI z{L`zdd<CWAPVY!lJn(eO3*{&r3Z@#ik_cgiY+8igm*n~94cTe)$AhDzo`w6NDcNb3 zDGU}A%)ovRxqwj+gcny(hDm8D5Lzf6!GRj<!uAd8_EOiKno<6AXhp0UC4wA1I7R0= zWVD@H_`);)NtePued->twzPIWzop+NyxECdzX#TbF0J)9ZA2=u6d?&YD-kzCCAYxW zP}u}pjJ!kC1Z4-cljD*aPiOQTc@u2bCI`-#Fy4>vS(@-TLeSFMP@(p@50IKnIVj0~ zF}46_zRt|@cGdEWjl}20Ub0qHu(#X>TLP2(b(xz8yG`Xrca;J4?Bw%x?;-SS)X=of zW7U&OJK@MjPNihB*oj!d4sb(3#Q(y0=Qxe-WJ1)QQRI(=x^GIm4L%6{>q%P29{m9| zJx2}+0ixROQK+XO^z`W>G7tYNv$s#%-TH%ogoqhlF}*A$DXzKoq-@+4{%K9@H5e3^ zxAe%%w(EO>Ee)1ZR1-LM14aDllsg0895N^BkKj{M=StSTEN0$&!gfjGn03xU<ghXe zrF2p&2Rv|X5=4j(zcEkr1KWT!YcmZGixruAFe6^)6(3nVNgR|Kv@NO^sj|w>1;1!K z9&7)zL`fLJL^0?IVSROvAe9I*6MWybf8X!h2Yl(Ul8q3-=O8=C92<EQv{D%DeI5*t z8;q{bQTPV^Hs@=;-&)AabVBtHmj2>4^RH%89zo~LW2<6LoWhRL-hgcUz0?r;yAtv( z1$>>D0txf=@$uRu#p1EMFrNsn!;nmCy255-6dI+x3!SCi7<bWTHzHvL4_7YQn8MuE z*J1vfCM3C{5&EAfc;Ge{iAa%F_2|<F0<RG|&2#pbv`777xG(~F-+(lhEdVAL@o@?m zw^>VYN&r}Bgn=_7oEyfheC)^D^`&@BA{VhQ4;ujlU550AgB9U4(pi6UJOWc~PDi+a zwSod=CQ@kNYRuXn-;<Vf2Vm<YI|pVEHe1Z<aU1;dSz347e7iWC&YM3lkab9cV_{2! zEh1f{<x_&s(NEQEXoHGk$2K>b{KmVJyOiSE+acQvsF#PtaL9%Y>i46ZCmG=FwzuBZ z5<t~RSoCk+7D}aXc_~n)nMKlc`#RfeYh%^2adzBL!`vzHAERu=dC&PV*+bk`KE><g zkOlo1iz`I(Uwny90y}i)47Scb^mFpe{HI}CBd$ue^svCvOdLcqDjSHy&SPK$F;Whw z#>w86p3}fgxB+t2Kkmf9>T?fp`|rAm-0A2IlI?sf__CjHzVO{FIm`~^CK$u(SbCps z(&W`188_8QbK?|%-`4A()oyLaw?e7q<d2?>i1Xw*a8xq7S{k0#&izxfX%BLkvK^_X zt5MLulrxE)|32L7kR}rvYo^0RWB=(w0;A+TX_=Jwu#0!&6ULjuWCz%**72hovv^LV z(CkM_@0vDoyaycHlUVGN(``y1?)J;x!PXcW57$?)IOx5L?EM5-vVp+H@H$Q_Oo8X+ zI2B;PnQ3d2bh;)twX$F;x~UJIM0oZgB@a^2v82j=m@g0!zcxt_uxz6G=U70PF5n#3 zdyIG%@rA`RMYSaTLCk$lZ3u|{QVD3|*`?{#$(&ph_j`Snx0U6@7*;I0BXyNb?hbBB zmO=m}Dbq-wh>x|)@X*L1v|p#cZUh0c+~p6t)_t(;J;(<T^jQIT;NRK-G{mwyGe~4^ zkzOkS<sRgh)KsMm1DGnX%7NXq5~o?WH*>-|U)mw$)XdhLLk?-$kFORXJ&pJ%f)3zX zNy&Oc1Y?Sa>UPGrMyO^~KPx|ATN>Xl?;HMpWLV=|N@todsB06>?9BEFtD+ZR+=>)| zm_swth((lMc8u1buy#3{{;DvZ0IJA^H~mp@1KdwO@Ur9MqYOu126-XFJ>J(%&U49= zI1UmX*K8&WsS7`y<Hg1GAdh7VK^ntpMP)nQ{in;E9bXel#Tpv&un`$D!p5=qy-M<j zFU-4^*_&oCi*hT{#Jt=%9?b~yZ%a{Vl=~zO5RXu<;=tw&V_*0X7^k|V;MkYMw)vFa z;=1`IfBYi}g}a>nnodIvEE^3T#+5HB9|;igt?!!$CbDdOaLK>NthIQOW7C`ZGJ#5B zi1K&>AKIE`eN@}xKq)Dmh-=OYY_HLdf2ur3<#~s)p|Tg7u?1yV8-6}RTV-D@(x{Wk zYMP^!X4QvH?Q~vC`=g<#Sc2Y|3P(JeXpA5QIu%bz=`?c)_dO+DUXDFlJUj~4GPOXW z`H$jC04!08@BK>USG+;kH2oDp>YNGWo)55Wnqur4DeQR`s;e#+K)7h%QEfa@8B2W; z{eOY&8pSXHTCvw88m;$gv!}rr;dU0Ub8py<zJ;-)hQ9YyB4zCWL9R(s{xR*ec0rW2 z_I`c-<qp$yjexSjb8G$1-|->SCQ4~{d@O^*M~Mn^ktQ(!>ep5()#i!sf1jENuoA^| zVZ^JkW?c1Fa1mLtwqGF8yE%aKBRhVtJ*6P6(5shRIKyRc?TUIicoRw6B1#1dIw8Kf znIRnhsyfLAS;37fdyJu+qH^hz#=fq+r2q|JF+4S?4*xn7>A5hN$FS6sFuBL`lZ;$C zyzNTxHM8G!?ztYbtCq!9^Z>PV+XV%RG2X49T7S<KRfE?Qw~+vc1{nq_Gqt587aR6X zlg=vM5K5)~)>)iTi7pwd#l@i=9PN=4$hNz|pRLiz08~blR!R{Tu&%r`=c+J?>!6yL zK13t{?dMw3Q<~nh!y4MKD=>TiyxilSojQb|Z+ZYF?$26XF^wj%9ASz&n)H64)y4l* zm7C@J*)ATlx!B=Xx8ECQm8$lqub^^4P0=ttQP<y|0u_x?pa&tQHN5DUh^wF-f2p)^ z^TBXZ^TTEj1@C8>r(i@Qy1(dTf<sZdz{8%RneV;XED%`WNWlM~qbi<|SF8AWl{h9- zokoD(vOUXdC(mV#tsBx@T(;?$DqV;mihV(ub>gA6#B(+BbAPQvv}R!FX{BBK`1%d# zevOvK;M-lRW}!|wV6_`Sz`+M4ccldV4zzvG*7dA1W3>p`e%<6sg0b8g0)fXx*;E>g zg@_j{OD)F*ldTCtCWwFc>YyNznX4xy6yp*h<zPVkn{8im7nkOuq5!VQ)-34iR$o`~ z{LXQs+`ya57n=VdoNy1C>SC(8BcteEa)f!|?R3o>vj@iq|A#ySKmPb)rEKQ_t_Zk; z+z{6uxuW*bdCY(*Iv1YejhzUG5oIMsa|VTL0*}s$am7<_$;V9mX~+%6EYv?(FR)0^ zWHHTY`Y(NpUS!zv6>d!o%R~sC^K7Udtm5pX(+<%0#65qFY#fsyDv*7ENxSm8KD5L< zi5ve99xhau4ot&vcga&FQMCq!oC2tMH3Yu*Nw~J&veC>zRJ#GfopCeMt~N`HkoUU_ zGxXzBt_mj-L@7|6=w^yrA@u1{7GrI3PXMrGSn%+B4rs5V;q*Gl2xw<Y2tKLfya>*j zt_lYZie4rlreHV`Qz&0N(in!6za5W^;(#6kuY?_xGiWtwYsKyZ4JCq`1cCyC_E4%k zUnC;*a0JYC^TC0-HlCi{ni$23bN>{eLy?+Lg=?nN;2G=M*!w$?<&0FL6+8vmt>3IM zz_RG$9^>j-&zjnwFo%4)Xu42R*`S82{?rJoNASV050)Sc25Hp37VfOXyCY&JN6H`7 zu~j^Av7C;R9FtdWiy{;oLY5x%vI4pE65W<?tpf5`{ck?=OQwJ#U$8*P-H4@T8+wjM zfT$VsgNU^*@lh;B=?LQ2Xut&+g@{6?xS;&ovVz0QynN191K>TC3#j+#1XytQ>x6uR zZ|Wg<diySwa<t3Wq<a&&B1xzkE?ob|k#2cNx^0Kyrx?f-*@UeBeDA3pA^lG%?9R?m zRj1p)@(Uu#I%~*2X4>c{ascDYq*8QQrWz#q^4l*%rRWWXRh0L6ahHAY?Kxwb(Rc7t z(|95*l~28^CG+Ez`TQvosCGV9C)QK0K_UGPXu7}Wjs4@7^o0w!8bWxX&KkM+{%bZB zf=oCm`2@Z+)-ivAJ%hjg)}o022_UcLTBOax5AKgd+y31%p<!W+lJSVU_4#s#e=V>Q z{1KHSz79l=4iS*=lEzRx!UG=GCGoB85kIX9{d-{g^fEGOSiP*ly>*Owu0p#?ejWBN zKQkjI6^PTu0==XJ=C*nD$<<cfc3Yn_lDr{B-qPKr)X-SWs<_>XT`IKynzXb>=0@hR zlL~cz%8zMJ5x;AnB^3mO;J*bjue4v|U?I-F(l>v*e*?<ub;T8kH#pma<pwaO8L|uf zOO4+hD8KAvk0tSP*(Z@bR7CS=SGsrO+Q_(MYDCEEz<F4+?i*!k3!wc=r*pcfP{WGe zf+?P5VdNyRh}@D`{Gzny8J_&beJXl-;mtd6<;NqKcfkh8qcl%RV6uwqxf7hhArN6f zrd&_Dv1{a?3dgkVdLr;4&?ONy3=d^_!SJ0joFvDhPMQ`zS7e1|+IlpI-VZE+_zRb2 z;1oIq90pI{tHT!Hz#F~X6%{R(o?X5(^-rhlwTTq;9wqwgDYYzX6!OW^hBmDMKvDsq zNt$8|(j*+EJZ={Wfob8cMOO7_DSAUs6Z5=kUa0SerL!}83xMBvfa>x`bAv=;d(o}R z_m}X9h0AegvvtQ}C9~TD=72=XlJP&^4Q<`O2CLWDvXbQZJ3f0)6&`w)r>#j{GBnKP z4uO17WN<D%^_&6&1TlrX%G39Kw7Egw|LA!T1&j7O?cZRlmLb}Md<&-MA{cFj@2J6q zpS2c%NjwG&^nU>m56O!xk_RQV{O+<-f=`9`<IwX2YmY!QMxP)!j|r+TTm!AfwB`R& zicV)ODnk$u<tW6RU%*@LwSJehkRDWertWKnYrRbogV*>J%NO<QKOzi{+YdV*+`L8% zdz%U_yr?W1fkI{q0D#yxNF|xg3=Pc0XxD%J5D%b+NO~=15sxV#%W{8L|Ef`-fO%3n z(xkjz?|q`A)C<>5HMoc$x+4J+((tHAVg^$x+el;WRXN3d%K9DYTdvdcZ|R%tZK_2Y zskc#iU920Nb{(#Qf3L0``=pi|OqUa4+lC}%`}Cc(+`$N@8eyQ&{&&|04UHL7Bq9n0 zbe_qO%K2}a+V9!AYTe4cu+7hzb|Unnx4G|oc#&OHL;QiCyHcOKgp5Zc<WG==B#2z| zUjCG5UDDmjyaH3Z)+X5XBh-u<<XFG24h4zT;dWC<VkP!4{ajX<rr0h2e;d+hR#ohT z14Dt>9bgPHI@FmvLd=s~d%`Jr4n>42*X)EtVkT<mU#nEEoW{+X&2%n6i<Gfe1!`I0 zBJLhzHII&*NM^)+UeR#(iRaQp{ldepnye72F_g<TuxTj&-G!OvO6ZvFRVxeM<%?Fr zao71~pOJ;1D<pvY0*q}^sjDxkOL1ne{Nh!wqr;&C7e5IR|7}eV{|Z|RwNw1zc80<N zy*1Q&VVOo)_!uQ=N{7f=5ZWY4GBZ_&v}>AA!0)*Va7?K$PwHOVn&E^~>DnW>f<$Rf zmEbm!gw&lovxyzur1T82bbMP+8gkZjDeV><gfr9Jg%?Tynj03Jl86-3?U+yY_e+~u zqp1^%AaN8FAO&V~V*<<F**|R!gaur8aLYuKke);ee1H=#0O=%QYDg8H{(^e?n0*@X zNaw*@%*`gS@ZCiA_uiU6{LwJ2oqdtJ-kAsb^c@;P(|jDwUvQghA1#M|%J5-a<z4kj z*nd#b!hV~zE7GX^vtU8l$*bOrFP2O0(tdGNP7Z<a$|%s_nQEd39T@&AwW@(&!YmKc zcvLj{cF8(|_}j4(*0yLT7#1lIWWIl15Zhl?d&5q?iYXjOhb-|Ift4fJZ6NlY8u^Et zZw}8{*w1g+@*6&w_OYve<^u;}a#S(IUTo!Ae@gUGFU^&wK@eJa4vaY-4KmzLtG9W~ z_-4GN7+i%vL>Z$EnU2R)I(SJRETQ`i4!KjRCmofVa);|`yu&y7lU;{lzC@JtjI(OK z7}m~dU?_S9<*ty{BtBsr|EAma`#){#c{}Hj&1KRlj>y0S0RcEnRp{!Ao(iLZeN&>v z8i(;=yks4Rvpt=hKf#I*`UM;gyY3x?j+axL-N1_-#_aptJwrgE%1xe6<!P_qD?Vjp z1jQ^ct|iz$vVpD?fwW!w{yjn^GHx0?Af?0&@{AQMzOs8bTr#YKrBQ!uPUzctBP_7+ z`7=Sn^dCr%f6*`T6U)OS&}!jxCG_j_qQ8G}(C7sKguAh@5o$y?I8~AuHkO{lfqFe& zg{GR&7je!*H{=(dws&vhYN5+0YQQa7amJq`xWwH!Lx@`LS%a%ZlOd(8u(P;Cxublq z$NIX)D~T6=O=OO%5^Q@&!>j@vCbWY2!2A*F_)>doJSu*Zi>tDaFWQMFz=!(2ygiNp zTO}5yw8dHvhIvu<8`a)j$_QdJ#%2AbZsSB_;u`&NLR$D~Lcany=zRyy%a9teCi`kH z(a9_}k^+B<-TDQ>$)4>iNF=?MN#00HGlDN3obSn9W*|jTDuWSzBddTHtP%o+)o44U z(1fU0iI?)Zi+%V5Yb3N@8KLxQm1dd5zz{mp1l!HaQ#2<M*&U|Rf2k=p+@h{>x5?Hc zu)MTC)cDsCPBS8zf}x`8ewPMg^8BP)&=~cRYrU#On4^I>Wp886D`6G~&il+|AI^IK zDB!+}nw&a#MxdWoSy7kvtt!8jnlyiwfG!`m6{gb1UVArMGVjci44@OM#FF0FEgTAp zMS3545XMSR0yOV$DP`rT%?$U%NHb)Gs)3wRi)x^B+7F;y)`H-n#-IC#z(JSGV0DFb zYF~T;QnWdf`SJ|m3fH>I#49d?s<##i62FAm0xGU1|2>sTpOmeTWRVP~4B?%zw1Io} zO*C*Gy>nN$unMtU!>v$=DAsZ(L3}qVA~&Rv*mjg#{=8iC@g%)Fa|akpS(OdA#gR}M zvSV`Gxnvzg`gmM9_u%gy1mn0aC8f=QCOphRc2y`}S5J}XGI^MZ8@sv8N<C8<+a~F0 zv{&B=m?Muu_HlIv$iEwEMpD88r@5v=g9$;*W!ry9vL5)>1l0dpoVW(gxI_+&uBSB| zn7~fn#r<jflq^8?Off9T<@3O7V~*fSRDV|7VU~h`>#bxPrIKz^X3_GN&0i3X45z7Z zDK5)z&bd{@bGkCdb7%dK`Qb!P9!ye31d!IY_bax`81{@c^2-2{3`A;KN{YzVF=;6@ zNUPZ2RH58$Q%~^^I3p}h91l*RI9AuhNsyf+Pq-^!cs4#7c-Yrtkgu*oG`jyV13|7S zy=%|A4Ea&k4gWqTzx+kY!}|~=AL}K`7nZ*8a}BL1t_g$IE6*}BXh0a4!@&%%w?;(d z7DfIT?(W_{*!iW_K}lP#4Xe87D~Iu8xb%wlX&V!@p$J;|R~#oZAW-uDL64r~NgE-4 zkG)ka*nP^jXDBrKGN%42cgm@fQY!aMoF!QVN-7A@_{poAnxZ!QWCL@d`^fk4Q=qhE z){sRRgvEe@$Q>t=wCdfCpD!eV^@b7L7Yr%u=6v~q+nK&ly(w?2D$T1?L>AEN$VQu2 z*^V>RtE^k@3EB@#{OTnKFJY`TP5@yBZquxl77ud_z9_2Km*Md^&q6yZz)AS)tOD_? zyKN4?LmxOh1?~ouX-)vDD&QfJ_)tHp`*UK&bdx{*D?mSHaEcd~YlBQixu~*k45DKQ zq0*yA)k0v%g>yDl^CYSAJfxC+ChTunmv`(o^V<6>En%-~3s3gaP|>P-Foa-qr~OKI zM0cI9RQlDCvf~6~*l$&jb0({$r8<o}sxHgPk=&d4b%S4hP>Q&ddTQ~(6-^&xlf7rc znf#Qv=aoww4-|;BVbrdBS~Bpi*ct+6j>nWCG(rvYKAOGcCkN%VW9X{*=Wx0a(&nBq ze~*GqNV`L>>e;c3a;_Izh0tUWPOylf0wDJXb*ArxhH+sSy3yaFY|GEb!Z)X^;<H;= zga0tfa9l7tuS;pe+W=7z+hFo#Lcfs#$AqRdd@h?mkQ5cg0SPe=%A?R0r(ty2DAT0? zfFeZM!2#Ov-;S3$0YYfphv;^Tao<SIzq{fE0!l%k6m~uWVKD6jC;I$VuZHsI$W|eI z9A#Un(;8lfK8Dl!q4jD}-maGDKfI0nTGu8B+X~?$t*il_d|bpup9AcSmL5k@3HtgU zzO_-nG9z)#bZG8Lh1#B}vrgurUa8=hieA5s7PsJk&=j~Rr4ThQAQ7LJnkxp;59IyQ zjkVnRo2m2y=)ez#^I{x8T_9k!vqjbfdW=$lsdo7!h*2#_g_{<WuMIsM3jtDGZePjQ zpS6i<G<uxAF|y{&>@@}px+)mRN)9q;wulj^WI>0~In?6)I6(`La`j-a@?%hK8PrDQ z1^BuT7o}|iML!@TLeu)O8sb3!&Gyu}MhrQ>WF;j}(_5gNO6zH2F%nvijO0<ZEJkKO zpB%Tz{aJ}sR;f{eB3o(ua0uUA?Rvlq54nrzFw%I3=(8FjUpJS|(<A+N_&xW#ouM<V z1%lnFM%^P1+PuTNF5`9-mC+?9v%S|=POInN=^_c;Ui%WuqSg*k*bLYr@ifBY_SY-c z;b9no2SsNr$?Xuv@Zwc_yp#G(ipai<xjuXc-G2l2LhMLjZrq@OsB$aGoffDI72#{i z40I%rhyXuAu0j3)4&Tz1l`t`aH_JjS2JfABvmz#g6(<<!Dws~th8}^@>U`vAT-z*H z<m$J5FB#4k?*j8AB^uZuJ0EHhb$Tf9Q**yj^)l+h9L*c$u3QUVEo>Rl?9XV}gdFcf z!P0UKDHB?re%YVYEjvIl@ouzUoItfuZmfh)!VKc^_!YsS+cUQpAyC6t0#~U`eK|qg z<E~Z)6ASPMUGr%Oic{AGU<2oxmb1E_oqI@1Uddn~GNHw8WR6`XsodwQR=8stU+)YW zXxbW9Gb*3SrE_Oz%8QI%al_Vm+W6u1EnV-pwPTd8`zC~_#BJQFEpU59wx4jA?pI$| z5=f$AXd=%ptaUMdLxdX0EP?D$-Dn^;o~5BanKLOrImzDW`r8UVA9#I7L)uldH7Zzg zKfgy+CAv2;1#kQ-ZC!tSQsnM>rV%*|HFP*=f#iBll6rw<crKIVRB033{3^s|^n&|D zV1-9PNSW=xYPE&kXP)F!ftpVI{0<HOUoo0;r`weD<sIHLV(hA_5FyskU=uw7`{zcb z%Nfmp-I5ypb119f<bVPwXflk_>}Fe+rlm=?K^@4-lKQMfB!(H9ghLlT|FYFb6%ZM( z`D-VxP4!`}T@h8^F*Mu-baBn$bMK=K7mM921@pT5yT#2%xdA5UV>#i-H*-u|7AS05 zAy`M^**xf*_~au<J%wM7p{{ExN;QCW1X%?;M3QV?!|ih5E058u$N}ylg?+x;5#Q6t z<Fq=y7R+?_;um{xhvx7)9Q{>hV^)D{5*vP<cd!ih!NjTBXZFdd{Ep)9z%?>Ro-T!G z97K_F^!Ad>h&kw#XuM8^``MkZ4_%m{*MSzg0&D@GC@yQU-C28VTc)%g1yz#tHP7j0 z=KaiM4?Pv2<KE}&t)}qX;bc2g3%v<%x#NAu?QK@3aTLgBf2wExCalr9>d7Jxldkgb z6S}#g&|T`O!JFG31IbLD<%R{qs|M2vV^qtcO-?joq){cqqQ^OS5YG;9%(OBA<A+AI zkwY@1r6zJoV@b1ZZUlFJ5;uJKS6?U8?CdatHU27j@f=HI_+?>Xqgt8yYg-9eN(4mT ze#t^VVs3NS8B`8Kch*X$hGRyXLefGKS>z`;=COFkU&p;ynPImMaS8h2sgN~kYDq}# zbI38E&~B(jLWG}tb;7qmMP%9|$^2>qi+i`|IGvzzQ5Cd~E3+?}`fg#42kzP>LWZ>C z21P|MK8%RO1h_0ff9om{VRF>@h=n`hn-9(IJ&~23Z(A7vps;+LhQgS@evKSR3Vx)E z{g*8As^KTDMlFm*ZhGJPkA}m~)vs-e))kA^9%sa+?dH!Q>0>pp?QTV@3^Lb;K3U+^ zJWwS36E*9zOm}7L#1F{<mYlg8eSdO&&ICUH{TYF|6|mmgpDuE8Vx&cK7GCUs1K$*V z?<aHfd}vYsSHk=_Df~O|l<Vx+`S(xet<MKk`<WMQYD=n7>7w_oXyXxardGCF(NX3h zphWKi4SQOD*%NaL5_a-J<_QoWs8gW013MZB3BS4DMf!_`J%9dy0G@VlpON<$8%{#P zFue=XI<t5>Ff`>%zID+UJm^O@Cd&Pp1*GN>1Plr=9QqY<-Vp?q>*q|D?saFxUA9aD zIQTIBxX0Ex8*a^$DovasO4hwQV@p?gDb=^bK$RSK@6-k{k<Sa!M!9`tEfVF<F~gVv zAnOiTt?4;+#Hp`2G!xfl*u<Z@vuSYwMRsz$|Fby0%k1RYA00<gxrXWrFa=bksi4rp zcK5A4K=*unZi2Ycz5oF0a~8@W)n|g1zJWi`ZS{|Ivu1qp$zCE|`S?L@Zh|jwA{oZQ zbzrK9euM+zNrtj6_j$D@qf2i8D_1!aJgH2(WGjA0i8q=iT5X|wId$htpOo7AtKaum zSmI~<AH6*VS*5m(EP{ljHXW&mpqm%OPYG^p{r)lh-AgF7vLwm(^$Q>WnXn?$QzeG5 z5}fJuA3@!}>5IYmgvSZwS&NFN?CL>cVdAKRtzvLsneyY%2%&WjU)9u-qK{`o*(mL} zw}}Tv9<=4o0#4DNY+u337?})o7AtQSAn>(|VkJ=9V>SG~_AMGhCHI|e_F!*`1tiTa zxTs4YM$X8e#`0IRoIxLB5}^uR1?XYy=O++%71Z<vcVN*qhLp*5`bnJ`%9d)vixlqU ze~YvF{s`|Me-$*>ak`OvBOYh4Jibv4;yRGy$}BIsF)omUldXGzTKT6<6SGo9L^-W# zK*AQc{CrkctyGnh`eO9R43ZLRRd;}*LS*|Uk?jj^&{p4o4Ci6R@HWa9n;ZEtO)NlJ zGADxJL&9lJsKKq-v&u0)-~2X8ee1L-Rao=FM;V6&FL}ky)s>7K(bU~3_>Mw;%v=|N zYB+dAU8gzo1!4ulx4jzwI^bi6OkcXGfd{8lFr+Uw<3ZD8G#p!icf)Hd=IaqId@yw0 z{rQ-Lf%d3?G0aPxIRD+;3dfa-<X^BT#xRG$?XbTc8`U2wcu0LE{8m}V2U%H~pp}F& zREc%J&TfF3p7YQwzxBK$qricq<z7usNxA1qe{JQmn$CuyvhuTB7b+>zr7Q(@BEK#^ zGVFogA67ivR2gmesa?5?P;bFCFPolA5|p}AHoDjn{2)trgi87$#^}F`p6ab=nU`2_ z9Tdnm>FRkjv66l49fM0_OqAdMd;c1ydRq9$vIBz?+kX;@V>YsJt?VGX?GK$CsGw6u zF2kDRn)vSj71JXu+<%W|B4m?_{7x!3rSGGfIlEe4aXeb#4o<%>&f{(#dY9nnB5vp3 z54UF+N4&Rc)X3CYN;75IUmu37o|U{di&8IsdRd<>E`xr`wRNpQOh&|*7`$H0U=~`T z@mxzQwmD3+(U9myjzR)qc1EiDr9<R4wYX4`iF>cds3ZSq2_8Jm`EGww6m3E<HH^-7 zdD|gif)9-J#h#g6Po<^e(I4Fos{n)Lv*TNE{>~#<Wtqsy-hRQczDm^I_4~kiWhExZ zE(I9qtB~KT?u+6bDAR=NiA0Uew(8f0<QRU~iAnUU5`-*LMVb|5vqLP)bo6ATF^>Y7 z-=BLo%}dtW(>ZvGpERQH1%wPC+J36NaOLA7hzrxr!rd$`^9!Vp$v`}lOvZnC*qFQl zS`ZKJplT*Y^p@}R*rT}dn@JsKnx6MrsTa@G6@jeZpi?U9$=As=<I8**SKirp3mB;U zsWRzyPTJM_0+rgbO}ZHV3c!7+7<lvY#CS8CO&~K60C{r#XtL}oIgM+U`8_>Ct)>AD z3K+5!!l4PWKsE-!f-gXRw9=QCu8QvH2-36LTXE`L+mn88Mfyn0b$%)_;>nU#hqklj zHkwN@p`_$ZFfNN&&)2A=gSX#3B&A+QKpXSW4Uo<OPaXiU%a$YJUqP=Z>2o$wn&?1^ zqbVQ(b_)>l%h&@iEadm-LX!j#aPI|CoTyMWCFp+^n<5#cG`zXMHdA)=es}z3hPzr7 z*8m0_-BCQ6JA1~SjDnvn@a(C*V(hXz$Vtc1tr`h*8<B_uCNIs24`l}7Nlcw!4l60j z!q96d!B0qTrb3-1r(}n&T%N`TMSU*e(o@wI*nKpH(~%?XA%J|u5ehcDy16hcP(2et z<VE|hC=%-`vf~c9lVj9|iKRES<Tl6K|3f@LC>+E<b`<bQKdZuE&cMQjs43bo&IaYe zYJIq^CuH0FVnh!hui^Rk!&j-epv9x0#IhYXu^AIbGd34p*0-391QW%Y<XS?O8KG=W zgxSL##81G7_Y1qS?K*rpD!U3T!x0MD2q%L1vbIOW9)YZ?pN|<U0M+=yXfw=n-^bcx zuTGcomiivcP!!lldO0dxy3!ZCMAYyrg%Gb`5Vg8sGvWmxe8j0NJ>{4D`C*RFNQQZ| zA;~uFd|0VwpnH(wIa>yOm-S=ww*0~Qa+o(U)2`IKu~zfE5@|<XjT1;Bwxb$RhPr?$ zO{=p9#rQ?N2=bzf(|>VlgD3oUV#sgLj9Hu6(f>j`49O`H#y1`fd$d$TTc;8zWguYE zK`kr2B1(_j3#}6BH0(<bbfLW4w50BO2yp_sP(fJ9n*H4S3*vYd9mOAw`+PT}%%6jM z;OiP0Wr+=zE*P6e=Koiad2OVSp*XKDeG8wB)Ul36Iv)E;*^b_S#4DsAqjh!J<Yn=v zl%P97eRTA|twBD5B|7`a^vpz;y?-Vsgk^Sf%4bfLs-}5SFSrTi>+;dY%T=Lj8#G)4 zfpS_%xDCvC5aBh#$naBPn|3d6YvFV8RSzgPP$u_3$k=u$r&+9V>Lv%y0i_ELJy*4) z3s&V3U*tNH+KOa$DQAE={kKJKsL|WM6GPGalHknrru*HPQR4>2CXlXgz>jq0kQ>3j ztmnSiY&f-{7&nyRY$SL8(`ou?!^6D|z*@-l4@9_vobsOr8x)jYz^1SGg%;&f?=;{I zDgf%snDj3sYrC}%xGXu&7gRpd-1*;K>}nKwMswnAju)<R&oSHU@~Bx#pud{B%Rf>p zWodNGa;|2S`9nC(9wKQ|df2eXlfnPXO4zR^Ld&lKCu4LRo;_nZjJ!vc+eYofL_&{J zDt%4zAS7x-I$}s$lHm}$5Zg-ERgFPAj5bR}@2(Z|fjR1@JI>aCcj*kRSa3`V;g};_ zp;X=Dh2yCM2-VK`-0UP$<dy@HMmz}D_wpjtbxC?fe++UvnR!=oRnu*pZ+}W<ZI2u@ zXSh-)^Akv@mS^@YZf!{@CrnS!zz&uQ8b)9pRv5yXQRfX^2l)}C<G}xg*DV|P70-C{ z$zzhL@%B|71IvLaY}#%m{1b&_jL0*xwpC0R>7GB}(J5@MF;g{v`5r>6^EPHez=7_Y z`WrZdR_wdn*Ey3pm5(>q%e|{k?AnoJGXG{5zOIhMV=Q#k-Yy4auC3T12ebUGo!k3W z`|I6*S57a<b#*HRee5x~*LO(m6?y>Hbo+df(L7;n&9~mfo|0()zrX!;ZWnn5jj%g+ zK9gAG%IZchGv1h|-*ByaV9iyT8KGJFfB7I^o`4*NnGpU|3g-7*NvU6o$(eWmVw(pv zIm2bL3%~02R_(PR6PpV4c9$jdQOzxYF3RQq%sCYkOK?HX#J_tKK4cVbVCNj^+s&*m z)ubMPxj{9X!m^^^lLC8HQcGQn);<jkx9Hd>*h>8z0NF|9d}R$tlYS^16ROQ+%mnfN z@4w&cNr$;DJ?Se0amPs`ZFaTAwbopwuQ}r?oT9v7IfbwKKnejog2fRUAYV$<(oI%k zN3`?>k)zo|pra;4<TKapiIXZMCLWkZzVj&psK9m{w=`Boo82w%&=a`W<^@=LBjNV! zW00ZYlbH*$7ET@eYr9v^*fNlPDoOgMCEv}HjPgCfv;gmz(|z>*^znre?NfANJZBy? z%^+1|5blcPT0)vSjzbSWLFLuOlpz{5ses!8LR2n41rbzDGbKY4{)lZD>L(MVCbp|4 z)Q~2xelu*B(MjZ-c%{%UMQ5tFP`aV3LYlst|9i_=@31+_k|0fbM$PVB-O}Ac_mRxo z=4nV7P>S6q22BA7+*%}(4qGT7t5o^qBj-_Fc3Pg)E9HqfrMw<#GYSy`Hredr`HP16 zR;lh-MC6JCnmThos9i$|)iH}D8w3faT3t+~uU0T#`ie;TKW_$W_NR_1Dw!nEA4jIw zyXuE);rhpnt*$22;_SQ10`!Y0tFWiqYY)U?hlqD&q8^N+EWzNCaHoTl9S31}zIdLz z8U_nihw$IlwjcZXKINs}e3NMb^*gtKT~v6@$2X!TM~JZ}(xEFL+Wf-j6E$YTPfLMS zfIf;NJukI31_;w}NF`$yFIt^tZArVVa4jJ!2X6$VL}?!Lu`CKr{@n2mb;K3Qs&AeW zGJ9|KqU(wxQID7E4Axh%Dya<A+R)B#4!%r$Zn~N9T_;w92F{V@*q+NkQHLNn5c?sp zxam>zL`f@tpdWm;x5w+b1$|$3punptNPv~v)++h$d!9r|-aoqNfIes;mqKyl$-%5? z@a<-}OO<9;t96Hb*t`)GIhO_@U6~FVYB6J9dDmYL=if%MxLDM()0}g)m(<UlTH14M zXY~*Iv(+?l06`2lqCE8^qrHEebqnmf&2v0+NU<ryj2KnM{Q#Z)1Z^74IQl}FuFXr= z@QlrFiIGqO+KjDwdzbyl9wt@~@woL_wrk1;onSoT%B-BJh6bHb{~x@bW&Fh1)zi1A zyPqRPJ9*mZ0^D|GY3dko3<O53L0}&`b$ooJf*-fX%%?AS%Y9A#Cr&GcOqeRtEJo7( zyRKD89uzvh@YnfBs3GsqcyyC~C5?Ol+|K!X48(!uNfi*=wXOZt*e5@ntojp-HGwAE zxffk8TninVylenl$FQ?54SQ5aEi{XFTy?78V1;Eo6^@uWKURf`<RUa%noN8C?QS-r zBpG&CWvGka2G}#DXNa_Ry&9C@%3G>{;X&FPPn?r~Jt#rH$+o~I3F1J}%!{MJDPLFY z=tc<#2nt*f3syo(PK^*YEHJCrvRYn!S2CO)XChsjkvLe$CHcF+Wu+_hiv^XA6J5=u z>!y?xk7FujgH*Gs1`O;Auh#+@|Mom-N$B*lu&YX8@+m*^uOsaE&i@X!oxq+;&cm+P z^_{Z@bvB+V`UZLjn=ClsDgj3FetnN3>aCo6aSq=_Q=>!PD8t#tdkQH(pvMfyU~Cyx zocTH-l715?Q`+EcV>NMG`;ezj=S6;(B2Vsv+vR(dFl%gD)|=i5RLCE~{#lJh-WRp2 zVX^W8dKL$G8m%a!XS}GNFGrc>7DH>))u~tp{gW6}Zd4ivEZUcaz!nm&mO+6!ZmB*5 z|2sxM9<*rSuyUKax^ORyv3Y`PNWuHv_tX*f%2peEb3bEm_SQ}J?N6z6Z#(?5T=06D z*LEwf1=fvfL+YCYOFIE<#8Hr8wsM7SZpYR*gllm(-^+5dI7hC}lMA}2f|Jvcy{-&N z+iOs8;rk7~gi)QURur4>XFQP>%m{CQ?T9*(DRT`BSfy>>qA=Qa1V{KHG#Gc*G;$7~ z-5H(fF>Py`xuu<@WvA0@rBv~Zv(T2wb7QIiGifH})j-;Gl+*p~{AL7drYFvx!hXN= zX*Ai}227=mpk7~S@D?}K{tQaKN<+bgPOxt7*6O{uuX!GpX|>*Vhrmkr$0Ky@qZ28X zzM=tW^sC1MR)$%b1)HpM)J|9l)5^&dAPs(DLaW!e$9I3f4;{D0k)Q(eLs+A#wX`X( zwWf~;vobm<y8is7xh?MB8<V^BHVvY|sVpzNL7S2Gz$g_sTW+a>Ik+l`4Y<jS&$|sy zRWGJ*66OB%v%qU7f6S*7xk@FGwp5w)eX_&v8)5Z7%SKxUYvH70(m(dI8ILl?=Q+5B z10j*Y6&e-@V$Y@QIrnO(DyjqtP}9nxPBzpW>_NNc>QDaHwyEFc8H^BQc6HfZeIU&C zf#>6I%kjInT_in_`*Hn!OK}(mT}HEtw=|Fsl?+MX>lG#gGc#6ik=+{l$c^w?4IYV- zkJK!JVG28<3QrI<rRrw8a;l}#fy;z(m9Q>P=m`64C$G;h#$@e{nZ#=a66K>+BZh_M zTNqg0q5_QkHTr$OksFAXZNy@RBHj_p)}VwYMjLy=<{T#Ij^|%)DzuCDGvuNVMXHlN z?L)%K+r1QPI?acnhpM193;XnP_q{&IEtyW{bsDGlV$>4Ih+2NOSQ4|7cC|gZORQ_* z&B%>a>)0WI)!SxUw&-H~;8TQy=1OfG*zZpQ4dL$Q)w1^|9`TCSJXO)DmtF)%EU*uC zh2QF)66#&y>Z3GY2oIGdXCRJ_hzimV#4`-}0by{|>Xlq-D_YG`>6|orpPjPx?6uXh zWpVtpO9~||?;VpY_fN`z7+2vTW*#rwp=E;g&*O-%{*5aH(u{trY<6VgmPNyGgxN@{ z2BeB6c!Q3&SHU{+hq#7L3~&-|bj;NyVvXN2<WlvRxltU6li@B)G3Mb^z>pj-(uOID z_TUk=u)UK>Yc{L8TThF0C$L-)_Q9tAvDIi-E7D45A@hI1AmsGHQYSB@gegm_r4<O0 z8qe44Yt?qv0aAYJRR_HkX8<OArODAkCstJ~X^F43@2!Z<FI2;LLG&L*v<Xb=51H}0 zu^6RH_hndwq9j9hjM_B&L8=jmUX_zzgu%UuiqsGr8q?%sGzbJrNiBja4hBr~Y_VVZ zLR)D#_LY>ojg+2R*1Gq#4TU5NxYcXiU@Cqq`PZf+EHsmQ7-RlT#G28>IjpZ0l*}Ql zIZ(XL!ymIqheryB8OMiq{_6k+9d!iys1t9No}|)>Cn<}<P)ZE`(%tbm?}SE;Tm@{- zPA;)#AsxR;XjAXLI_sTp$;Z&6WcljlgC?Z%PvvxRlt8xTs*|9s2w~J6XZV!+i{Ga( zo{|o@#~O7<vHyCt!9d?b2ORTbOW)Gx+ppL6P$<RAii(4u`|c(+^@KIGX6FO(g_RqO z0LdqvtYTs%q*`aA2|G++QLLWKOsIcitR~R*B^Z3h26b7VSbtwUKEzJQ4M0*2lHGZN zt?oBD62k$v8=vgMtRlsv3ykl_lAMmLNN5B4BNW;uN-eCHVWMhMmfLY-Z6HhvNit3~ zN2cr~z3<Y1H>nHD-@1+ieJTdA+C4k=M&wQ&h!e*p3Az7m&}3DmLpk<54!eHKfTnZX zNKQM4Zr4|RfbEqZJYlp@o%KBl9Fmp}{^a0Lm}7{O+xI}5^qv|4a9XSv+TQcjn;Z}W zluU$5nr^jZ81L!egEoXAT-<IDR&s@tE!jl1upcFsGtj++l?#cA`B|2vwC$%<h-Xu= zJZULde)_xPs%3@p#<fX2MS6FSLS2<C28ZoH8q;5ob<5c}ShimWxyt=X(oxdOX@eo9 zwE}rQ?e}`g5K@Usk=PZZMdiUid2%?Qyuui32)&2F*;XrNbCYWk_~#MRGbxn%#Bl~3 zDi3L3GlI3p`nT+;D*ugowG(%_e@eXrlXK@m(9LO}A8&JqW91t>shDM1v2jv2w!!xI zs_cL192d?N3&V+=<<D435A7KA_L~nJd5?#4$5?b#z)w|KU-qe6*?bo9aiy}~(NnbU z=rH6VzdN#@SQpAa{IJ`i6-RK*x__Cr3j_a$Z30KF&r9|tw3CD#n~$n;0}d4-t^YdQ zC`Wf5+hPPO8QIC)ampZe4)C37Y0|nP|7_X-ui0=YSK#q4>Z=J8SC7^k8A^$q-Q#qj zUob{QdXG_o{?;4Ti8bg8f32@VY^X@s;#QNO*M<w}VC(3^%4>&}4xM(J4NT2Y1_WC> zm4aDZY6l>(Bz^={wIEW0^Y=6|;1^Qo9XFD=1LiFJEQxRW%Poj*&DSWTdSFjvwcsu@ zBHP81Z##3oKi*-j4epomH+u8>?#m+}$qo7Jd!n(SduBie-Lk%s;KNB5<u$F=;K(rm z!bXpstny{@-hhWgZ41*e5C6dLwUYKIht%=|_}Feq2SoKb2~?3wvsjEX??FHgpU95e zWpG5T>Z$=5$iNauq*}f8_3g9yGEx`(hbEN~eIgN!Hg*2YUvd_qYt<riw6+-x$9Vi3 zhZvq<U85XyhkgjwD`&8fnSp4hwr@Ww@TAm}nr_|&qN15%^sFOS?FM}fgh~gjz;EMq zu`LSE!e5#DYAOq~t@F(qEuZP*T12*Z!~QsFRay5jkW4xGcLZ5i#wQU`s!L*yoBzPD zQI0(9*Utsw8Nv7xmKKo<QRJ$Ld<LW_@p1b9vC*1Q?T!bJxz(&G=?yHdz%{Hql*1vD z3lmoC6yihusYrRAi*Y99?KsFhw9|4$*ul9bZftnwooJ7}<5#-Z(II*oM+d6*2$gb> zz#E_0QN(4QM-1PD@e+Z(X&T{b3>s)DMFkk8zq?=)PvG!7DbsnLH`7Ps^V<H`jxj*I zv18Y3oHd}?bYB5U$X>ID7%K;!J;Z&hk?p8RwKDm|?y<U&`|urSm^we2>T=1#{7*J0 zl0KlH>zNqo5Z4_CqZ{LH4KZikM{4)X>O&W}+x}a*-F^XHDoMLIx0W}a5REyh-YtQj z)qU5srrgsBX4*xkgofUuEl#7dS62Sq5kb&fkvHE0LT+NDB_lI4Im$v0D>|6(aUi!I zJ`^?t*=4_hlh*d|m|Wm!aW*|&ym%|_MxNX8fCV_bS}~p0*Jt-JHDpXsVQwq{O*Lkw zqHcNOs4_N}_k3oZJT7KoI#LbG3x-dlJged`FPNSd(k_9`a;zs?NS)>aCQ^Lpd|~s; z*Q!3HiOTX}G`;34y<GqXH*2cpeaw$}ajEB#Bgag{!<*|dM8}WL1_xe2YmF<1{?Zxr zhbX?%gc(6EVy&9!D5D8`ZD(712h=EfR^Hydwwynyr;6HL8T!~~CV1Zp7FBx$BaRY- zulzanD~bhO_K^LLuv8Fn-0cuj{|>4>*QPy6R}eIIj*Gqh<VLMxHXsIJM5h@~xd2Ug zc|QxPScodw+|#_x0zsRQPA1#LWhPsOs8!)VMFx7!JEnGg1lIZF%IS0ECv_Gis+~vn zZrP7_X~Klni`vZ^zI<MrLE9)$sG*p86M9+o*$v{*XrcB01ZqNkiA)Z2$nper|2$we zCW{g<Zjo-ACo4oPEWX?9%FXB>$JBNqRA-7AO%o?Wm<yWHO?D^2yTK9F`7XwfztpIM zt~1&;_MZ1V*Y#se;2ZAl(c;4!x!gQAl*`f&HSw_qS+tqzsnxwPep>7@HD{mbNv&rX zgUl8<mem)$%}k9$dT3uhU)}%is!xjQ<K;inl30WlO3D`gIjBg!%k0#TSbX3lg@kbE z(8!C~E5$Q^{+C9u2H{WMCNk4&0ctwmVNc{CG@;VBEX6p0OM`4yIZQkl+TxLw-p4Af z&zC%H@@7_6CYIA<q+!-Hv&%28>$o-$5auOAFA8f1_yZm<|9wg{0DZtl*UaT9Pg>Md z@n{vY&KJpZ{_D~%Ibq$0FMI&oJ+}O_?3W5fpIH@2Z4DFlFeCGD^7lVJ`YKDp%@-!S z|HQ-;Gk(dJ13BvMn$*eqI`;HBk8?IsX*v8}nid_3N#X|8NIRy4z(TU!NihAVwa@ya zN+WW~!8gVwSUmnn!6iDu37rD;fbCR7+ttk2Gi`9u!K9Sm(>W}o#?Xa-0<3x<WgLEl zCPePWc#G*r)7Ny-Ot6>KK9oD>gzNY57M<ly;n)BvJzgbRNoq$ZM%ielF={B1dl$B~ zWlgc*2j5WR&P_U}NE~;^v>ttd%@MR?Clv0{)?bmgg;kYMzaV0(rbb2X)LZX;9Q(ap zabkcM@5N1wUh7{NK32GAhVDP`J~<V<R;PX)o5;1vgDxFfoYe<>@zdcK(OH63z&;gT z<pl-%?nx^9VZh;Q00JaSvyNW}XXZbNM{%(1NlqqTwkSL-xrm~<eIAVF%pMeK#DUWb z1?c;DY*Pz1ld_okV)o=<k;|17+Sf(zP4R%?O)oY+#)hg#+rqCxuGE5~9Hh_n)L<U% zGa3JhrUr_HiP!;mG~z0etedmDesjVK!u6xG_a(}b7y@W?1sJ7=_R`SFazXs5OxI!J zxX4g^ST+Nmf6J;12MSB5DzKO&{Fs=d)*AYwA8xY+)>Ka3hy0xjyY^@efYI{f(ALiU z0si`N7zs`BxdITSjUcG+rvmYXTq57$Wx-A5&7PL|4NeI0MZJ~!Rzstrh*Mw=y8FyG z?$ccwHQ37(QH@QiZk@s#-{5|uP2QAUZE&}P+~SY&%XU`D7CoV!#7hwXIF!i!4bDw( zT-_-2Xud2)rsfkQRzx~>+XJEOyvr~j_tw%rjWRw4Wd^WWqZs=syH>w8jQJeCour`m zqZt<g3##;%<&$INn7g}r|3B-1Vz^ttnMk_FVcRF_GWMfO+p+Bu9aJ<fiJOk=iw%|v zm7ZT4-~XOGsxLx_ZKXc)!E{1)#AiO?<4kO@ld+E?IRY6&=|9f}IKks<(^@uHiK)rj z>(iE>Vei%R$PyA8%S|B{rBeX=LBjP4`hPqlwUkgi4WXP_0WZd>j9F`JwC@qQw9|1E zcE|%dB-Cv*^`+a(Qp3G>jXa_d%R1RVnI(FigaVLUq1Pbi|JieuZz=auZ@*;3ow`4( zrwHYw&7cAczqPJtNM(r+fYR~C#doJM!8eM-8iQsQB1LpY{><-3tu50dM4N;20M2Hb z2oR#iw%jNJKZq(($Trkh6es{~ZeThRDG|wnufeth=%4o99tb6M&w^OFTRgZ`La)Kc zPOU&eLE3<4&A?qlb!M3@Jb@l7LZv+VOP^gx*8~Z9#x2(YHx6bD;|Mc9nEE~0gg?Os zVJczW0P<mLUW9)XQD!U{<~b&Vq;0Ww(z*<og^TbC6t|3)TA?d-;GWfz6)GSBA>w9< zsfsf;5z5X1TB;Z-CYbfXub222`cx!V^9h#_+JMMRFb%-OFD;3Xi!m{4?M<HuGcVs- zKQfL?|F?01fu=feH-?AT32}!gGEB7R9FI#CK^hYZ1x;+ohDEjopbN4!qHc;F1IA;w z(0PTd;;bi`qk4#QMM=#`0L#pDtLVO0&Byxm>%|Kf^!O#w7R46Y)sg|!UXNXsF%In& zz<kDf4JvK0VTD%pB?J^l*iI;^C}pTA=#%4^?b-dM)BWj*E&a+rOJ$ujS!VK+%f?Lo z#bLb%$dt9GZezU?<3J_BZO7AI-cL{l^)oYJiJ5b-`qI`~aFliOt-%ET)2T)CV{FT) zaKS-f9r7oD#aPhtmE4_iw_I;g(VqGdweOOm#x*)0%I0)#54I;@jx~b8TaRCR%76C~ zh;nDSPg3EkeF{6?{YFNf{Kvub3~MV5=O8p^S{>}dd;2Pt_z~n@slCXUk;sZYY1z8J zXhF%WkekD-BoifEmsZUR^wtSgX~NJ_89<$i%D4g-Els{O_*2q#kx>+r$!(a;F=3O( zT*s<|w>Uon;~39*4ZHmGVs;~+$@`O8RhVfXMiE9QJ}p96ak&uz&6KE|?ewQaW>J!3 z0P?4!MERfgMrMvl;9nV2v#&TjTV~d?aC5o-D6+uLj|LibYmvNtn$R!|+YAKu^W%fU z%IKwGcsiOrK2E`)h|GGM`-b&h(Z7lBPQ33!iwAz9MU5mvz|<KEJYt0U=f0{kiUoH} z3HPR(qS9YBF(*|l>@w2nh6t&;L8TfSYZUb_V&w?|{oBDQu=E~5^JY7Pz8!e;2t8d! zw6ajK5{gRYeir-LlHKkEO9>x5^u4tf(D@{E2Q|nO9>$p7BSiQ{x9;bbz48~j)G(6b zL^9erX=rRB&K9cJcHJDse@W4z2W|>E``LdKE)13R$eO8oho3}1Re+~<&OVtFeXjXz zw1Ra&_>|eXNb-P&Y@;DB9A>L)b{bvuKJ6zs5UpuoITG)Mu*kx=Ig}*qZ(oTj4@f1< zz`D@E)2>4aYKX^;)!2G=O;MhT8ijS1RVj^x1=!qXRy2nXiYIi**RlWYq>gZ!P)eU& z|3-^6DR*H!ia^Wm1uGk~L>B-PjGaC#{$~AchNuBhy?ju$@s|dYLI>b!(+eTB9HWi; zmN1st#wTVBiqW?!#z#2PCsH85_{A>t_1s?+GZVk9d%$!C0aTmw^#kK3bD2+NhE%z@ z>&BWx;rCpJ-wJh~6#uhe{pRt*wQ%?eP1Vx-z>0?2QwJm1;)_90W}vHzr}G7dcKR^_ z!tE`X-MGzBZ#h;_wB8*pXaEXt#}M9PRov(@X<LdD03U&93^qeX(XB`u=E<o{IRi_0 zo}FI^hWec9ic%o2r~@;R0%&IQL6Pr+FyH-5&O5DKp>z-8=LkSIU0(?6&+AYdpe!%Z z&B>^9w`vdGCB~UPmj!=;<j^oybrgNSj@i0j2Q?+;6e18`+%B!1q1gms-mk&|*h{!@ z_t-+IFW<6XrV<mtK4B>Z$z*GD#(pfgsULzbc?G?Xguy*QIz_+&0agRaGwZ3WP3^rd zynn9xz&+=G=5BDX=;#*l+XFvk&|81P>~Y}8NUwn}QFzrKCsb+D7h5nVybOqW|0L{9 z!zZ}K!C^))p?PNNhxBq+{;eeJ)x?7o#DOP#x=cQKiGLY4Dr|&{%$q|*1;AMuM{HbJ znPnm*<(ElR<W0qnRT>j!-g50xJ5loFxm{s`TS;Zq1#3ZXGiMb+g5nhZc7^F4WGe%O ze?js04vJZwWNl6w7N*&+aqKSHKq(@k>Z;yj3C;R`$#I~+7$Gv4@S8mpb!2fvXdjB2 z>7ed>{q8(UcPo68n|*HNE_$1FmFT#J)YM1$j<9@V#7uZ*HBZOebBwvp=Q2dZi}vhx zRE1Cbcc%g;bXu;oS}utI(EN07Yu;Ca{%v~;yOGL4H^j)N6w=tJ4A<r(vrt@Y<>f`^ zQ`x*7;s_;Z%SmQSX$3wnub-1FMrsh~JaM^JH4ef2tp?!(uyBFl_;dbf#vJ&8Iys?C zezkY_aj=MZ<_jN_3S9&dOsD;%Xxm3zxXaTOwEN%46t}o4cT%}j66^|@lmW7rmX#oo zLw(3mqyN$LVS+2-kK|uBSUI!HeAC%diuv`C!A~&Qeul>@8rgikf^%i{pK&Tn-)Mym zT6Lx7Iw;h{kcIx(j>{$78_ymNHyfcR7RoUWZGaKe73r|g@$u63)rJmZc|84mI;sxr zWO8tyOqM7*?sz2u!Qp}LrMtQ91-W5?<wBt|z0X<p6y6nBwBz?<9=Xk8`NIkOm>M$$ z;V>R^eOv7X22`;Heph+F5F0+WX#yepKcGG*eISDE@i{Q@s?G-dN9m@boIo~*zgfNn j)y*JBVs0lauh3(rHN+&VSUmxu{PpgFhwN*D00000mu|?u literal 0 HcmV?d00001 diff --git a/src/components/AlbumView/AlbumBio.vue b/src/components/AlbumView/AlbumBio.vue index 1e515a3..93c5bc0 100644 --- a/src/components/AlbumView/AlbumBio.vue +++ b/src/components/AlbumView/AlbumBio.vue @@ -1,10 +1,17 @@ <template> <div class="al-bio rounded"> - <div class="heading">ALBUM BIOGRAPHY</div> + <div class="heading"> + The Very Best Of UB40: ALBUM BIOGRAPHY + <div class="tags"> + <div class="item" v-for="tag in tags" :key="tag"> + {{ tag }} + </div> + </div> + </div> <div class="separator"></div> <div class="content"> Two years after he prematurely left us, the Juice WRLD story continues - with Fighting Demons. The rapper’s second posthumous album dropped at + with Fighting Demons. The rapper's second posthumous album dropped at midnight, and is the followup to Legends Never Die, which arrived in July 2020 and hit No. 1 on the Billboard 200 chart. <br /><br /> @@ -15,22 +22,29 @@ Higgins) was just 21 when he died of an accidental overdose of oxycodone and codeine. Following his death on Dec. 9, 2019, his mother, Carmela Wallace, created the Live Free 999 Fund, to help youth struggling with - mental health and substance use issues. - <br><br> - “Jarad was always searingly honest - about his struggles and through his musical genius he articulated what was - on his heart and mind vividly through his art. He never gave up and his - friends and family never gave up on offering their support to him,” she - continued. “We encourage all of you who struggle with addiction and mental - health to never give up the fight.” Juice’s fast rise in the hip-hop space - and untimely passing is the focus of Into the Abyss, a Tommy - Oliver-directed documentary set to premiere Dec. 16 at 8PM on HBO Max. + mental health and substance use issues. + <br /><br /> + “Jarad was always searingly honest about his struggles and through his + musical genius he articulated what was on his heart and mind vividly + through his art. He never gave up and his friends and family never gave up + on offering their support to him,” she continued. “We encourage all of you + who struggle with addiction and mental health to never give up the fight." + Juice's fast rise in the hip-hop space and untimely passing is the focus + of Into the Abyss, a Tommy Oliver-directed documentary set to premiere + Dec. 16 at 8PM on HBO Max. </div> </div> </template> <script> -export default {}; +export default { + setup() { + const tags = ["reggea", "ub40", "ali campbell", "astro"]; + return { + tags, + }; + }, +}; </script> <style lang="scss"> @@ -40,6 +54,25 @@ export default {}; .heading { margin: 0 0 0 $small; + height: 2rem; + position: relative; + + .tags { + position: absolute; + right: 0; + display: flex; + font-weight: normal; + + .item { + padding: $small; + background-color: rgb(15, 74, 114); + margin-left: $small; + border-radius: $small; + } + .item::before { + content: "#" + } + } } .content { diff --git a/src/components/FolderView/SongList.vue b/src/components/FolderView/SongList.vue index f484c8f..be5b2b0 100644 --- a/src/components/FolderView/SongList.vue +++ b/src/components/FolderView/SongList.vue @@ -1,30 +1,27 @@ <template> <div class="folder"> - <div class="table rounded" ref="songtitle" v-if="songs.length"> + <div class="table rounded" v-if="songs.length"> <table> <thead> <tr> <th>Track</th> <th>Artist</th> <th>Album</th> - <th v-if="songTitleWidth > minWidth">Duration</th> + <th>Duration</th> </tr> </thead> <tbody> <SongItem - :songTitleWidth="songTitleWidth" - :minWidth="minWidth" v-for="song in songs" :key="song" :song="song" - :current="current" @updateQueue="updateQueue" @loadAlbum="loadAlbum" /> </tbody> </table> </div> - <div ref="songtitle" v-else-if="songs.length === 0 && search_query"> + <div v-else-if="songs.length === 0 && search_query"> <div class="no-results"> <div class="icon"></div> <div class="text">❗ Track not found!</div> @@ -36,7 +33,7 @@ <script> import { ref } from "@vue/reactivity"; -import { onMounted, onUnmounted } from "@vue/runtime-core"; +import { onMounted } from "@vue/runtime-core"; import SongItem from "../SongItem.vue"; import album from "@/composables/album.js"; @@ -50,40 +47,14 @@ export default { SongItem, }, setup() { - const songtitle = ref(null); - const songTitleWidth = ref(null); - - const minWidth = ref(300); let routex; const current = ref(perks.current); const search_query = ref(state.search_query); const route = useRouter(); - const resizeSongTitleWidth = () => { - try { - let a = songtitle.value.clientWidth; - - songTitleWidth.value = a > minWidth.value * 4 ? a / 4 : a / 3; - } catch (error) { - return; - } - }; - onMounted(() => { routex = useRoute().name; - - resizeSongTitleWidth(); - - window.addEventListener("resize", () => { - resizeSongTitleWidth(); - }); - }); - - onUnmounted(() => { - window.removeEventListener("resize", () => { - resizeSongTitleWidth(); - }); }); function updateQueue(song) { @@ -91,10 +62,10 @@ export default { switch (routex) { case "FolderView": - type = "folder"; + type = "folder"; break; case "AlbumView": - type = "album"; + type = "album"; break; } @@ -122,9 +93,6 @@ export default { return { updateQueue, loadAlbum, - songtitle, - songTitleWidth, - minWidth, current, search_query, }; @@ -161,49 +129,48 @@ export default { } } -.folder .table table td .album-art { - width: 3rem; - height: 3rem; - margin-right: 1rem; - background-image: url(../../assets/images/null.webp); - display: grid; - place-items: center; -} - -.folder .table .flex { - position: relative; - align-items: center; -} - -.folder .table .flex > div > span { - position: absolute; - bottom: 1.5rem; - width: calc(100% - 6rem); -} -td, -th { - padding: $small 0 $small $small; - text-align: left; -} - -th { - text-transform: uppercase; - font-weight: normal; - // display: none; -} - -td .artist { - margin-right: 0.2rem; -} - .folder .table table { border-collapse: collapse; text-transform: capitalize; - position: relative; + width: 100%; + table-layout: fixed; + + thead { + height: 2rem; + text-transform: uppercase; + + th { + text-align: left; + padding-left: $small; + } + } + tbody tr { cursor: pointer; - transition: all 0.1s ease; + + .flex { + position: relative; + padding-left: 4rem; + align-items: center; + + .album-art { + position: absolute; + left: $small; + width: 3rem; + height: 3rem; + margin-right: 1rem; + background-image: url(../../assets/images/null.webp); + display: grid; + place-items: center; + } + } + + td { + height: 4rem; + padding: $small; + + } &:hover { & { diff --git a/src/components/LeftSidebar/Navigation.vue b/src/components/LeftSidebar/Navigation.vue index 0f04db6..4c1de97 100644 --- a/src/components/LeftSidebar/Navigation.vue +++ b/src/components/LeftSidebar/Navigation.vue @@ -73,15 +73,20 @@ export default { </script> <style lang="scss"> -.collapsed .nav-button { - font-size: smaller; - margin-top: 5px; - align-items: center; - justify-content: center; +.collapsed { + .nav-button { + font-size: smaller; + margin-top: 5px; + align-items: center; + justify-content: center; - .in { - width: 100%; - flex-direction: column; + span { + display: none; + } + .in { + width: 100%; + flex-direction: column; + } } } diff --git a/src/components/PlaylistView/FeaturedArtists.vue b/src/components/PlaylistView/FeaturedArtists.vue index 4517958..4db3044 100644 --- a/src/components/PlaylistView/FeaturedArtists.vue +++ b/src/components/PlaylistView/FeaturedArtists.vue @@ -5,7 +5,7 @@ <div class="next" @click="scrollRight"></div> </div> <div class="artists" ref="artists_dom" v-on:mouseover="scrollArtists"> - <div class="artist c1"> + <div class="artist c1 image"> <div class="blur"></div> <div class="s2"></div> <p>Featured Artists</p> @@ -74,7 +74,7 @@ export default { <style lang="scss"> .f-artists { position: relative; - height: 13em; + height: 15em; width: calc(100%); background-color: $card-dark; padding: $small; @@ -143,69 +143,69 @@ export default { margin-left: $smaller; margin-right: $smaller; width: 9em; - height: 9em; + height: 11em; border-radius: $small; background-color: #0f0e0e; display: flex; align-items: center; justify-content: center; - transition: all 0.5s ease-in-out; cursor: pointer; + border: solid 1px rgba(165, 151, 151, 0.055); .artist-image { width: 7em; height: 7em; - margin-left: 0.5em; border-radius: 50%; margin-bottom: $small; background: url(../../assets/images/girl1.jpg); - background-size: cover; + background-size: 7rem 7rem; background-repeat: no-repeat; background-position: center; + transition: all 0.75s ease-in-out; + border: solid 1px rgba(165, 151, 151, 0.055); + box-shadow: 0px 0px 80px rgb(0, 0, 0); + } + + &:hover { + .artist-image { + background-position: 50% 20%; + border-radius: 20%; + background-size: 10rem 10rem; + } } .artist-name { margin: 0; text-align: center; - font-size: small; - width: 10em; - } - &:hover { - transform: translateY(-0.5em); - transition: all 0.5s ease-in-out; + font-size: 0.9rem; + font-weight: 510; + max-width: 7rem; } } .f-artists .c1 { position: relative; - background: rgb(16, 25, 51); - width: 15em; + background-size: 400px 11rem; + background-position: 100%; - &:hover > .s2 { - background: rgba(53, 53, 146, 0.8); - transition: all 0.5s ease; - width: 12em; - height: 12em; + background-image: linear-gradient( + 320deg, + hsl(0deg 3% 6%) 13%, + hsl(211deg 81% 23%) 50%, + hsl(209deg 94% 30%) 87% + ); + + transition: all 0.75s ease-in-out; + + &:hover { + background-position: 10%; } p { - position: absolute; - bottom: -2rem; - margin-left: 0.5rem; - font-size: 2rem; + margin-left: 1rem; + font-size: 1.5rem; font-weight: 700; - color: #ffffff; - } - - .s2 { - position: absolute; - left: -2em; - bottom: -4em; - width: 10em; - height: 10em; - background: rgba(53, 53, 146, 0.445); - border-radius: 50%; - transition: all 0.5s ease; + text-shadow: 0px 0px 80px rgb(0, 0, 0); } } </style> \ No newline at end of file diff --git a/src/components/PlaylistView/SongList.vue b/src/components/PlaylistView/SongList.vue deleted file mode 100644 index ebf97ea..0000000 --- a/src/components/PlaylistView/SongList.vue +++ /dev/null @@ -1,94 +0,0 @@ -<template> - <div class="folder" id="p-table"> - <div class="table rounded" ref="songtitle"> - <table> - <tr> - <th>Track</th> - <th>Artist</th> - <th>Album</th> - <th v-if="songTitleWidth > minWidth">Duration</th> - </tr> - <tr v-for="song in songs" :key="song"> - <td :style="{ width: songTitleWidth + 'px' }" class="flex"> - <div class="album-art rounded image"></div> - <div> - <span class="ellipsis">{{ song.title }}</span> - </div> - </td> - <td :style="{ width: songTitleWidth + 'px' }"> - <span class="artist" v-for="artist in song.artists" :key="artist">{{ - artist - }}</span> - </td> - <td :style="{ width: songTitleWidth + 'px' }">{{ song.album }}</td> - <td - :style="{ width: songTitleWidth + 'px' }" - v-if="songTitleWidth > minWidth" - > - {{ song.duration }} - </td> - </tr> - </table> - </div> - </div> -</template> - -<script> -import { ref } from "@vue/reactivity"; -import { onMounted, onUnmounted } from "@vue/runtime-core"; -import Songs from "../../data/songs.js"; - -export default { - setup() { - const songtitle = ref(null); - const songTitleWidth = ref(null); - - const minWidth = ref(300); - - const songs = Songs.songs; - - const resizeSongTitleWidth = () => { - let a = songtitle.value.clientWidth; - - songTitleWidth.value = a > minWidth.value * 4 ? a / 4 : a / 3; - }; - - onMounted(() => { - resizeSongTitleWidth(); - - window.addEventListener("resize", () => { - resizeSongTitleWidth(); - }); - }); - - onUnmounted(() => { - window.removeEventListener("resize", () => { - resizeSongTitleWidth(); - }); - }); - - return { songtitle, songTitleWidth, songs, minWidth }; - }, -}; -</script> - -<style lang="scss"> -#p-table { - height: calc(100% - 0rem) !important; - overflow: hidden; - padding-bottom: 0rem; - - table { - &::-webkit-scrollbar { - display: none; - } - - th { - position: sticky; - background-color: rgb(58, 57, 57); - top: 0; - z-index: 5; - } - } -} -</style> \ No newline at end of file diff --git a/src/components/RightSideBar/NowPlaying.vue b/src/components/RightSideBar/NowPlaying.vue index 048bc10..95fd61e 100644 --- a/src/components/RightSideBar/NowPlaying.vue +++ b/src/components/RightSideBar/NowPlaying.vue @@ -276,8 +276,8 @@ export default { border-radius: 0.5rem; margin-right: 0.5rem; margin-left: $small; - background-color: #ad1717a8; - background-image: url(../../assets/images/null.webp); + // background-color: #ad1717a8; + background-image: url("../../assets/images/null.webp"); } } } diff --git a/src/components/SongItem.vue b/src/components/SongItem.vue index 671c9f9..39a1c57 100644 --- a/src/components/SongItem.vue +++ b/src/components/SongItem.vue @@ -1,7 +1,6 @@ <template> <tr :class="{ current: current._id.$oid == song._id.$oid }"> <td - :style="{ width: songTitleWidth + 'px' }" class="flex" @click="emitUpdate(song)" > @@ -21,7 +20,7 @@ <span class="ellip">{{ song.title }}</span> </div> </td> - <td :style="{ width: songTitleWidth + 'px' }"> + <td> <div class="ellip" v-if="song.artists[0] != ''"> <span class="artist" @@ -34,7 +33,7 @@ <span class="artist">{{ song.album_artist }}</span> </div> </td> - <td :style="{ width: songTitleWidth + 'px' }"> + <td> <div class="ellip" @click="emitLoadAlbum(song.album, song.album_artist)" @@ -42,8 +41,6 @@ > </td> <td - :style="{ width: songTitleWidth + 'px' }" - v-if="songTitleWidth > minWidth" > {{ `${Math.trunc(song.length / 60)} min` }} </td> @@ -55,7 +52,7 @@ import perks from "@/composables/perks.js"; import state from "@/composables/state.js"; export default { - props: ["song", "songTitleWidth", "minWidth"], + props: ["song"], setup(props, { emit }) { function emitUpdate(song) { emit("updateQueue", song); diff --git a/src/composables/perks.js b/src/composables/perks.js index cd8f11c..46f94fa 100644 --- a/src/composables/perks.js +++ b/src/composables/perks.js @@ -10,6 +10,7 @@ const current = ref(state.current); const next = ref({ title: "The next song", artists: ["... blah blah blah"], + image: "http://127.0.0.1:8900/images/defaults/4.webp", _id: { $oid: "", }, diff --git a/src/composables/state.js b/src/composables/state.js index 43a6084..cd3ead9 100644 --- a/src/composables/state.js +++ b/src/composables/state.js @@ -6,18 +6,20 @@ const queue = ref([ { title: "Nothing played yet", artists: ["... blah blah blah"], + image: "http://127.0.0.1:8900/images/defaults/5.webp", _id: { $oid: "", }, }, ]); -const folder_song_list = ref([]) -const folder_list = ref([]) +const folder_song_list = ref([]); +const folder_list = ref([]); const current = ref({ title: "Nothing played yet", artists: ["... blah blah blah"], + image: "http://127.0.0.1:8900/images/defaults/1.webp", _id: { $oid: "", }, @@ -31,9 +33,9 @@ const prev = ref({ }, }); -const album_song_list = ref([]) -const album_info = ref([]) -const album_artists = ref([]) +const album_song_list = ref([]); +const album_info = ref([]); +const album_artists = ref([]); const filters = ref([]); const magic_flag = ref(false); @@ -61,5 +63,5 @@ export default { search_artists, album_song_list, album_info, - album_artists + album_artists, }; diff --git a/src/main.js b/src/main.js index c270c1e..d844492 100644 --- a/src/main.js +++ b/src/main.js @@ -13,4 +13,4 @@ const emitter = mitt(); const app = createApp(App); app.use(router); app.provide('emitter', emitter); -app.mount('#app'); \ No newline at end of file +app.mount('#app'); diff --git a/src/views/AlbumView.vue b/src/views/AlbumView.vue index fa6cc54..72bb086 100644 --- a/src/views/AlbumView.vue +++ b/src/views/AlbumView.vue @@ -57,7 +57,6 @@ export default { if (state.album_artists.value.length == 0) { album.getAlbumArtists(title, album_artists).then((data) => { state.album_artists.value = data; - console.log(state.album_artists.value) }); } }); diff --git a/src/views/PlaylistView.vue b/src/views/PlaylistView.vue index 769dc94..d67217a 100644 --- a/src/views/PlaylistView.vue +++ b/src/views/PlaylistView.vue @@ -14,7 +14,7 @@ <script> import Header from "@/components/PlaylistView/Header.vue"; -import SongList from "@/components/PlaylistView/SongList.vue"; +import SongList from "@/components/FolderView/SongList.vue"; import FeaturedArtists from "@/components/PlaylistView/FeaturedArtists.vue"; export default {