diff --git a/README.md b/README.md index 84edd62..65f5fc8 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,3 @@ -# ChillBox +# team-1 -> *A project by Pausetta.org, Simone Tesini, Francesco De Carlo, Leonardo Segala, Matteo Peretto* - -**ChillBox** is a web app that lets you create a shared radio station with a democratic voting system, so everyone gets to enjoy their favorite music together. -Perfect for venues like swimming pools, cafΓ©s, or even lively parties. - ---- - -## 🎡 Voting System - -Joining a ChillBox room is easy: users can either scan the QR code displayed on the host screen or use GPS to find nearby rooms. -Hosts can set a location range, ensuring only people physically present can add or vote for songs. - ---- - -## πŸ“Š Ranking Algorithm - -ChillBox uses a smart ranking algorithm to decide what plays next. The score of each song is based on: - -* Votes from users -* How recently similar songs (same genre or artist) have been played (less = better) -* A bit of randomness to keep things interesting -* A strong penalty for songs played too recently - ---- - -## πŸ‘ Hands-Off Experience - -ChillBox is designed to be almost entirely hands-free. -Once the host sets up a room and optionally connects a screen or projector -(to show the current track, QR code, etc.), ChillBox takes care of the rest. - -ChillBox comes with built-in automatic moderation to keep the music fair and on-theme. - -* Users can’t vote for the same song multiple times. -* A cooldown prevents users from spamming song requests. -* Hosts can define preferred genres and overall mood, so no one can hijack your chill beach vibes with unexpected death metal. - -That said, hosts still have access to essential controls, like pause and skip, if needed. +Test diff --git a/SPEECH.md b/SPEECH.md deleted file mode 100644 index 2ab507f..0000000 --- a/SPEECH.md +++ /dev/null @@ -1,15 +0,0 @@ -# speech - -## Home screen -We start here in the home page. -We can see this little radar animation, which means that the app is looking for nearby ChillBox rooms to join. -It uses GPS for this feature. - -## Join room -When we join a room, the server checks our location and checks if it's within a specified range. -That way, you must physically be in the location to actually be able to add new songs - -## Talk about the host -As you can see here (and hear) on the left, the host is already playing some music. -Now i will add a song on the client side and it will pop up in the list. - diff --git a/backend/Dockerfile b/backend/Dockerfile index 3390030..c6a1412 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,6 +12,5 @@ RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 5000 -# CMD ["flask", "--app", "src.app", "run", "--debug", "--host=0.0.0.0"] -CMD ["python3", "src/app.py"] +CMD ["flask", "--app", "src.app", "run", "--debug", "--host=0.0.0.0"] # flask --app src.app run --host=0.0.0.0 --port=5001 --debug diff --git a/backend/requirements.txt b/backend/requirements.txt index cb18a70..5eae02e 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,5 +4,4 @@ flask-socketio dotenv requests qrcode -Pillow -eventlet>=0.33 +Pillow \ No newline at end of file diff --git a/backend/src/app.py b/backend/src/app.py index 28c66e9..c11cf37 100644 --- a/backend/src/app.py +++ b/backend/src/app.py @@ -1,17 +1,17 @@ import uuid from dataclasses import asdict - import dotenv -from connect import get_connection from flask import Flask, Response, jsonify, request from flask_cors import CORS from flask_socketio import SocketIO, join_room, leave_room -from gps import Coordinates, distance_between_coords -from qrcode_gen import generate_qr -from room import Room -from song import Song, add_song_in_db, get_song_by_title_artist, get_song_by_uuid, init_db -from song_fetch import query_search, yt_get_audio_url, yt_search_song -from state import State + +from .state import State +from .connect import get_connection +from .room import Room +from .song import Song, init_db, get_song_by_title_artist, add_song_in_db, get_song_by_uuid +from .song_fetch import query_search, yt_get_audio_url, yt_search_song +from .qrcode_gen import generate_qr + dotenv.load_dotenv() @@ -28,11 +28,11 @@ init_db(state.db) state.rooms[1000] = Room( id=1000, - coord=Coordinates(46.6769043, 11.1851585), - name="Lido Scena", - pin=1234, - tags=set(["chill", "raggaetton", "spanish", "latino", "mexican", "rock"]), - range_size=150, + coord=(1.0, 5.5), + name="Test Room", + pin=None, + tags=set(), + range_size=100, songs={}, history=[], playing=[], @@ -73,7 +73,7 @@ def on_leave(data): @app.get("/api/join") def join(): room_id = request.args.get("room") - code = request.args.get("pin") + code = request.args.get("code") if room_id is None: return error("Missing room id") @@ -81,22 +81,8 @@ def join(): if (room := state.rooms.get(int(room_id))) is None: return error("Invalid room") - if room.pin is not None: - if code is None: - return error("Missing code") - if int(room.pin) != int(code): - return error("Invalid code") - - distance = distance_between_coords( - lhs=room.coord, - rhs=Coordinates( - latitude=float(request.args["lat"]), - longitude=float(request.args["lon"]), - ), - ) - - if distance > room.range_size: - return error("You are not within the room range") + if room.pin is not None and room.pin != code: + return error("Invalid code") return {"success": True, "ws": f"/ws/{room_id}"} @@ -154,8 +140,8 @@ def room_new(): lat, lon = room_cords.split(",") room = Room( - id=max(state.rooms or [0]) + 1, - coord=Coordinates(float(lat), float(lon)), + id=max(state.rooms or [0]) + 1, # + coord=(float(lat), float(lon)), range_size=int(room_range), name=room_name, pin=room_pin, @@ -173,14 +159,6 @@ def room_new(): @app.get("/api/room") def room(): - lat = request.args.get("lat") - lon = request.args.get("lon") - - if lat and lon: - user_coords = Coordinates(latitude=float(lat), longitude=float(lon)) - else: - return error("Missing user coordinates") - return [ { "id": room.id, @@ -188,10 +166,8 @@ def room(): "private": room.pin is not None, "coords": room.coord, "range": room.range_size, - "distance": d, } for room in state.rooms.values() - if (d := distance_between_coords(user_coords, room.coord)) <= room.range_size ] @@ -213,9 +189,6 @@ def add_song(): ## song not found, downolad from YT yt_video_id = yt_search_song(info.title, info.artist) - if yt_video_id is None: - return error("No video found on youtube") - ## add in DB song = Song( uuid=str(uuid.uuid4()), @@ -228,15 +201,6 @@ def add_song(): add_song_in_db(song) - if len(room.tags) > 0: - tag_ok = False - for tag in song.tags: - if tag in room.tags: - tag_ok = True - - if not tag_ok: - return error("Song genre does not belong to this room") - ## add the song in the room if does not exists if song.uuid not in room.songs: room.songs[song.uuid] = (song, 1) # start with one vote @@ -309,4 +273,4 @@ def get_audio_url(): if __name__ == "__main__": - socketio.run(app, host="0.0.0.0", port=5000, debug=True) + socketio.run(app, debug=True) diff --git a/backend/src/gps.py b/backend/src/gps.py index 98bda0c..546531b 100644 --- a/backend/src/gps.py +++ b/backend/src/gps.py @@ -1,11 +1,9 @@ import math -from dataclasses import dataclass -@dataclass class Coordinates: - latitude: float - longitude: float + latitude: int + longitude: int def distance_between_coords(lhs: Coordinates, rhs: Coordinates) -> float: diff --git a/backend/src/room.py b/backend/src/room.py index cb775b6..5109884 100644 --- a/backend/src/room.py +++ b/backend/src/room.py @@ -1,8 +1,7 @@ import random from dataclasses import dataclass -from gps import Coordinates -from song import Song +from .song import Song USER_SCORE_WEIGHT = 0.7 ARTIST_WEIGHT = 0.1 @@ -31,7 +30,7 @@ class Rank: @dataclass class Room: id: int - coord: Coordinates + coord: tuple[float, float] name: str pin: int | None tags: set[str] diff --git a/backend/src/song.py b/backend/src/song.py index f95609b..7409d06 100644 --- a/backend/src/song.py +++ b/backend/src/song.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from sqlite3 import Cursor -from connect import get_connection +from .connect import get_connection def init_db(db: Cursor): diff --git a/backend/src/song_fetch.py b/backend/src/song_fetch.py index 905d126..665e2c9 100644 --- a/backend/src/song_fetch.py +++ b/backend/src/song_fetch.py @@ -1,10 +1,9 @@ -import os -import os.path -import sys -import urllib.parse -from dataclasses import dataclass - import requests +import urllib.parse +import os.path +import os +import sys +from dataclasses import dataclass sys.path.append("/yt-dlp") import yt_dlp @@ -18,7 +17,7 @@ class SongInfo: tags: list[str] -def _lastfm_search(query: str) -> tuple[str, str] | None: +def _lastfm_search(query: str) -> tuple[str, str]: response = requests.get( url="https://ws.audioscrobbler.com/2.0/?method=track.search&format=json", params={"limit": 5, "track": query, "api_key": os.environ["LASTFM_API_KEY"]}, @@ -26,10 +25,7 @@ def _lastfm_search(query: str) -> tuple[str, str] | None: assert response.status_code == 200 - tracks = response.json()["results"]["trackmatches"]["track"] - if len(tracks) == 0: - return None - track_info = tracks[0] + track_info = response.json()["results"]["trackmatches"]["track"][0] return track_info["name"], track_info["artist"] @@ -46,16 +42,11 @@ def _lastfm_getinfo(name: str, artist: str) -> tuple[str, list[str]]: # ( image track_info = response.json()["track"] - image_id = "" - if "album" in track_info: - image_url = urllib.parse.urlparse(track_info["album"]["image"][0]["#text"]) - image_id = os.path.splitext(os.path.basename(image_url.path))[0] - else: - print("this song haas no image", flush=True) + image_url = urllib.parse.urlparse(track_info["album"]["image"][0]["#text"]) return ( # track_info["mbid"], - image_id, + os.path.splitext(os.path.basename(image_url.path))[0], [t["name"] for t in track_info["toptags"]["tag"]], ) @@ -77,17 +68,14 @@ def _yt_search(query: str) -> tuple[str, str]: def query_search(query: str) -> SongInfo | None: - res = _lastfm_search(query) - if res is None: - return None + name, artist = _lastfm_search(query) - name, artist = res img_id, tags = _lastfm_getinfo(name, artist) return SongInfo(artist=artist, title=name, img_id=img_id, tags=tags) -def yt_search_song(name: str, artist: str) -> str | None: # video id +def yt_search_song(name: str, artist: str) -> str: # video id ydl_opts = { "format": "bestaudio", "default_search": "ytsearch1", @@ -98,9 +86,6 @@ def yt_search_song(name: str, artist: str) -> str | None: # video id with yt_dlp.YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(f"{name!r} - {artist!r}", download=False) - if len(info["entries"]) == 0: - return None - return info["entries"][0]["id"] diff --git a/backend/src/state.py b/backend/src/state.py index 73d05d2..e1f3e7c 100644 --- a/backend/src/state.py +++ b/backend/src/state.py @@ -4,7 +4,7 @@ from sqlite3 import Cursor from flask import Flask from flask_socketio import SocketIO -from room import Room +from .room import Room @dataclass diff --git a/frontend/src/app.html b/frontend/src/app.html index 0e6ef01..7af2f6d 100644 --- a/frontend/src/app.html +++ b/frontend/src/app.html @@ -1,13 +1,15 @@ - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- + + + + + + %sveltekit.head% + + + +
%sveltekit.body%
+ + diff --git a/frontend/src/lib/components/QueueSlider.svelte b/frontend/src/lib/components/QueueSlider.svelte index 5df1e8d..ec5a647 100644 --- a/frontend/src/lib/components/QueueSlider.svelte +++ b/frontend/src/lib/components/QueueSlider.svelte @@ -8,10 +8,6 @@ queueSongs[playingIndex], playingIndex == queueSongs.length - 1 ? createEmptySong() : queueSongs[playingIndex + 1], ]) - - $effect(() => { - console.log(displaySongs) - })
@@ -23,22 +19,20 @@ class={`flex h-[60vw] max-h-[250px] w-[60vw] max-w-[250px] items-center justify-center ${i === 1 ? "spin-slower rounded-full border-2 border-black" : "rounded"} object-cover`} > {#if i === 1} -
+
{/if} - Song cover + Song cover
{#if i === 1} -

{song.title} - {song.artist}

+

{song.title} - {song.artist}

{/if} {:else} -
+
+ {#if i === 1} +

No song in queue

+ {/if} +
{/if} {/each} diff --git a/frontend/src/lib/components/RoomComponent.svelte b/frontend/src/lib/components/RoomComponent.svelte index 0192683..e98e000 100644 --- a/frontend/src/lib/components/RoomComponent.svelte +++ b/frontend/src/lib/components/RoomComponent.svelte @@ -1,45 +1,18 @@ -
- - {#if showPinModal} - - - {/if} -
+
+ {room.name} + {room.private ? "πŸ”’" : ""} +
+
+
+
64m
+
Join
+
+ diff --git a/frontend/src/lib/components/SuggestionInput.svelte b/frontend/src/lib/components/SuggestionInput.svelte index d19dd17..09982fd 100644 --- a/frontend/src/lib/components/SuggestionInput.svelte +++ b/frontend/src/lib/components/SuggestionInput.svelte @@ -1,69 +1,19 @@ -
- { - errorMsg = null - if (e.key == "Enter") { - sendSong() - } - }} - disabled={loading} - /> - {#if loading} - - - - {/if} - +
+
- -

- {errorMsg} -

diff --git a/frontend/src/lib/components/SuggestionList.svelte b/frontend/src/lib/components/SuggestionList.svelte index 6c667a3..285d242 100644 --- a/frontend/src/lib/components/SuggestionList.svelte +++ b/frontend/src/lib/components/SuggestionList.svelte @@ -1,62 +1,41 @@
{#if suggestions.length == 0} -

No suggestions yet! Try to add a new one using the Add button

+

No suggestions yet! Try to add a new one using the Add button

{/if} - {#each suggestions as sug} -
-
- Song cover -
- {sug.title} + {#each suggestions as sug, idx} +
+
+ Song cover +
+

{sug.title}

{sug.artist}

{ + vote(idx, 1, sug.uuid) + }}>πŸ‘ -

{sug.upvote}

+

{sug.upvote}

{ + vote(idx, -1, sug.uuid) + }}>
πŸ‘
diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index 87182fc..0c2e8f5 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -10,19 +10,19 @@ const SongSchema = z.object({ }) export type Song = z.infer -export const parseSong = async function (song: any): Promise { +export const parseSong = async function(song: any): Promise { let resp = await SongSchema.parseAsync(song) return resp } -export const createEmptySong = function (): Song { +export const createEmptySong = function(): Song { return { uuid: "-1", title: "", artist: "", tags: [""], image_id: "", - youtube_id: "", + youtube_id: 0, } } @@ -31,22 +31,21 @@ const SuggestionSchema = SongSchema.extend({ }) export type Suggestion = z.infer -export const parseSuggestion = async function (sugg: any): Promise { +export const parseSuggestion = async function(sugg: any): Promise { let resp = await SuggestionSchema.parseAsync(sugg) return resp } const RoomSchema = z.object({ - id: z.number(), + id: z.string(), name: z.string(), private: z.boolean(), - coords: z.object({ latitude: z.number(), longitude: z.number() }), - range: z.number().int(), - distance: z.number() + coords: z.tuple([z.number(), z.number()]), + range: z.number().int() }) export type Room = z.infer -export const parseRoom = async function (room: any): Promise { +export const parseRoom = async function(room: any): Promise { let resp = await RoomSchema.parseAsync(room) return resp } diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 470da85..1b806c5 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -1,10 +1,9 @@ -import { type Coordinates } from "./gps" import { parseSong, parseSuggestion, type FetchError, type Song, type Suggestion } from "./types" -export const joinRoom = async function (roomId: string, coords: Coordinates, pin: string): Promise<[FetchError | null, string]> { - let res = await fetch(`/api/join?room=${roomId}&lat=${coords.latitude}&lon=${coords.longitude}&pin=${pin}`) +export const joinRoom = async function (roomId: string): Promise<[FetchError | null, string]> { + let resp = await fetch("/api/join?room=" + roomId) - if (res.status != 200) { + if (resp.status != 200) { return [{ code: 400, message: "Cannot join the room" }, ""] } diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 0ab9b2b..e4c699e 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -4,6 +4,6 @@ let { children } = $props() -
+
{@render children()}
diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index b0b35bf..319ce8d 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,20 +1,9 @@
@@ -23,22 +12,46 @@ ChillBox
- radar + radar Scanning for rooms near you... -
- {#each rooms as room} - - {/each} + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/routes/admin/[id]/+page.svelte b/frontend/src/routes/admin/[id]/+page.svelte index 02f5f08..e017cc5 100644 --- a/frontend/src/routes/admin/[id]/+page.svelte +++ b/frontend/src/routes/admin/[id]/+page.svelte @@ -15,37 +15,14 @@ let currentPlaying = $derived(queueSongs[playingIndex]) let audioController = $state() - let playerInfo = $state({ - playing: false, - currentTime: 0, - duration: 0, - }) - async function playOnEnd() { let url = await getStreamingUrl(currentPlaying.uuid) if (audioController) { audioController.src = url - await audioController.play() + audioController.play() } } - $effect(() => { - if (audioController) { - audioController.ontimeupdate = () => { - playerInfo.currentTime = audioController?.currentTime || 0 - } - audioController.onloadedmetadata = () => { - playerInfo.duration = audioController?.duration || 0 - } - audioController.onplay = () => { - playerInfo.playing = true - } - audioController.onpause = () => { - playerInfo.playing = false - } - } - }) - onMount(async () => { let songs, index ;[returnError, songs, index] = await getQueueSongs(data.roomId) @@ -53,21 +30,12 @@ queueSongs = songs playingIndex = index - if (queueSongs.length == 0) { - playNext() + if (audioController) { + audioController.src = await getStreamingUrl(currentPlaying.uuid) + audioController.play() } }) - $effect(() => { - playOnEnd() - }) - - const formatTime = (t: number) => { - const min = Math.floor(t / 60) - const sec = Math.floor(t % 60) - return `${min}:${sec.toString().padStart(2, "0")}` - } - async function playNext() { let songs, index ;[returnError, songs, index] = await triggerPlayNext(data.roomId) @@ -76,20 +44,6 @@ queueSongs = songs playingIndex = index - - if (audioController) { - audioController.pause() - audioController.currentTime = 0 - } - } - - function seek(e: Event) { - const target = e.target as HTMLInputElement - const seekTime = parseFloat(target.value) - playerInfo.currentTime = seekTime - if (audioController) { - audioController.currentTime = seekTime - } } @@ -98,21 +52,7 @@ {:else}
- - - -
-

{formatTime(playerInfo.currentTime)} - {formatTime(playerInfo.duration)}

- -
- - -
-
- - + +
{/if} diff --git a/frontend/src/routes/room/[id]/+page.svelte b/frontend/src/routes/room/[id]/+page.svelte index 84d8d67..e53cb58 100644 --- a/frontend/src/routes/room/[id]/+page.svelte +++ b/frontend/src/routes/room/[id]/+page.svelte @@ -8,7 +8,6 @@ import { getQueueSongs, getSuggestions, joinRoom } from "$lib/utils.js" import type { FetchError } from "$lib/types.js" import { io, Socket } from "socket.io-client" - import { get_coords } from "$lib/gps.js" let { data } = $props() @@ -24,14 +23,8 @@ onMount(async () => { // Join the room - let { coords, error } = await get_coords() - if (error || coords == null) { - // Default to Lido - coords = { latitude: 46.6769043, longitude: 11.1851585 } - } - let sugg, queue, index - ;[returnError] = await joinRoom(data.roomId, coords, data.pin) + ;[returnError] = await joinRoom(data.roomId) if (returnError) { return } @@ -76,7 +69,7 @@ {#if returnError} {:else} -
+
diff --git a/frontend/src/routes/room/[id]/+page.ts b/frontend/src/routes/room/[id]/+page.ts index f3bea98..969ffc5 100644 --- a/frontend/src/routes/room/[id]/+page.ts +++ b/frontend/src/routes/room/[id]/+page.ts @@ -1,8 +1,13 @@ +import { error, type Load, type ServerLoad } from "@sveltejs/kit" import type { PageLoad } from "./$types" +import type { FetchError } from "$lib/util" +import { parseSuggestion, type Suggestion } from "$lib/types" -export const load: PageLoad = ({ params, url }) => { - return { - roomId: params.id || "", - pin: url.searchParams.get("pin") || "", +export const load: PageLoad = async ({ params }) => { + if (params.id) { + return { + roomId: params.id, + } } + error(400, "Please provide a room id") } diff --git a/frontend/src/routes/room/create/+page.svelte b/frontend/src/routes/room/create/+page.svelte deleted file mode 100644 index d0009f3..0000000 --- a/frontend/src/routes/room/create/+page.svelte +++ /dev/null @@ -1,83 +0,0 @@ - - -
-

Create Room

- -
- - - - -

- Room Coordinates: - {coord.latitude},{coord.longitude} -

- - - - -
-
diff --git a/frontend/static/android-chrome-192x192.png b/frontend/static/android-chrome-192x192.png deleted file mode 100644 index ddc7955..0000000 Binary files a/frontend/static/android-chrome-192x192.png and /dev/null differ diff --git a/frontend/static/android-chrome-512x512.png b/frontend/static/android-chrome-512x512.png deleted file mode 100644 index 71ec4aa..0000000 Binary files a/frontend/static/android-chrome-512x512.png and /dev/null differ diff --git a/frontend/static/apple-touch-icon.png b/frontend/static/apple-touch-icon.png deleted file mode 100644 index a60445a..0000000 Binary files a/frontend/static/apple-touch-icon.png and /dev/null differ diff --git a/frontend/static/favicon-16x16.png b/frontend/static/favicon-16x16.png deleted file mode 100644 index 6bb39f3..0000000 Binary files a/frontend/static/favicon-16x16.png and /dev/null differ diff --git a/frontend/static/favicon-32x32.png b/frontend/static/favicon-32x32.png deleted file mode 100644 index 7d938c2..0000000 Binary files a/frontend/static/favicon-32x32.png and /dev/null differ diff --git a/frontend/static/favicon.ico b/frontend/static/favicon.ico deleted file mode 100644 index f5208bb..0000000 Binary files a/frontend/static/favicon.ico and /dev/null differ diff --git a/frontend/static/favicon.svg b/frontend/static/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/frontend/static/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/frontend/static/manifest.json b/frontend/static/manifest.json index 8a414d3..f042a95 100644 --- a/frontend/static/manifest.json +++ b/frontend/static/manifest.json @@ -1,21 +1,21 @@ { - "name": "Chillbox Music Player", - "short_name": "Chillbox", - "start_url": "/", - "display": "standalone", - "background_color": "#334155", - "theme_color": "#334155", - "orientation": "portrait-primary", - "icons": [ - { - "src": "/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - }, - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - } - ] + "name": "Chillbox Music Player", + "short_name": "Chillbox", + "start_url": "/", + "display": "standalone", + "background_color": "#334155", + "theme_color": "#334155", + "orientation": "portrait-primary", + "icons": [ + { + "src": "/icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "/icon-192.png", + "sizes": "192x192", + "type": "image/png" + } + ] } diff --git a/frontend/static/radar_bonus.gif b/frontend/static/smerdo_radar_bonus.gif similarity index 100% rename from frontend/static/radar_bonus.gif rename to frontend/static/smerdo_radar_bonus.gif diff --git a/frontend/static/radar.gif b/frontend/static/smerdoradar.gif similarity index 100% rename from frontend/static/radar.gif rename to frontend/static/smerdoradar.gif