diff --git a/.gitignore b/.gitignore
index 30bc162..63b25ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,7 @@
-/node_modules
\ No newline at end of file
+/node_modules
+.env
+/uploads/media
+/uploads/songs.json
+/_bmad
+/.claude
+/_bmad-output
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 6417e93..c5cfb17 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,4 @@
{
- "workbench.editor.decorations.colors": true
+ "workbench.editor.decorations.colors": true,
+ "liveServer.settings.port": 5501
}
\ No newline at end of file
diff --git a/README.md b/README.md
index abdafca..de54b01 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
Плеер, чтобы слушать свою музыку
-Готовность: ~10%
+Готовность: ~16%
+
+На данный момент актуальная и рабочая версия.
diff --git a/app old.js b/app old.js
deleted file mode 100644
index f89ac16..0000000
--- a/app old.js
+++ /dev/null
@@ -1,167 +0,0 @@
-let songs = []
-
-const getSongs = async () => {
- try {
- const response = await fetch("http://localhost:7999/songs.json")
- songs = await response.json()
- renderSongs(songs)
- renderPlayer(songs)
- } catch (error) {
- console.error(error)
- }
-}
-
-const renderSongs = (songs) => {
- console.log(songs);
-
- const songsWrapper = document.querySelector(".songsWrapper");
- songsWrapper.innerHTML = songs.map(song => {
- return `
-
-
-
-
-
- Сортировка:
-
- Название
- Автор
- По ID песни
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Название трека
- Автор трека
-
-
-
-
-
-
-
-
-
-
-
Длительность трека
-
-
Длительность трека
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/client/css/style.css b/client/css/style.css
new file mode 100644
index 0000000..08d4612
--- /dev/null
+++ b/client/css/style.css
@@ -0,0 +1,577 @@
+/* ────────────────────────────────────────────────
+ VARIABLES
+──────────────────────────────────────────────── */
+:root {
+ --bg: #07070F;
+ --accent: #C0392B;
+ --accent-hover: #E74C3C;
+ --accent-2: #A855F7;
+ --glass-bg: rgba(255, 255, 255, 0.06);
+ --glass-border: rgba(255, 255, 255, 0.10);
+ --glass-hover: rgba(255, 255, 255, 0.10);
+ --text-1: #F1F5F9;
+ --text-2: #94A3B8;
+ --header-h: 64px;
+ --player-h: 80px;
+ --radius: 14px;
+}
+
+/* ────────────────────────────────────────────────
+ RESET & BASE
+──────────────────────────────────────────────── */
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+html { height: 100%; }
+
+body {
+ min-height: 100%;
+ background: var(--bg);
+ background-image:
+ radial-gradient(ellipse 60% 40% at 80% 10%, rgba(192,57,43,0.12) 0%, transparent 60%),
+ radial-gradient(ellipse 50% 50% at 10% 90%, rgba(168,85,247,0.08) 0%, transparent 60%);
+ color: var(--text-1);
+ font-family: 'Segoe UI', system-ui, sans-serif;
+ font-size: 14px;
+ line-height: 1.5;
+ display: flex;
+ flex-direction: column;
+}
+
+/* custom scrollbar */
+::-webkit-scrollbar { width: 6px; }
+::-webkit-scrollbar-track { background: transparent; }
+::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 3px; }
+::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.22); }
+
+/* ────────────────────────────────────────────────
+ GLASS UTILITY
+──────────────────────────────────────────────── */
+.glass {
+ background: var(--glass-bg);
+ backdrop-filter: blur(18px);
+ -webkit-backdrop-filter: blur(18px);
+ border: 1px solid var(--glass-border);
+}
+
+/* ────────────────────────────────────────────────
+ HEADER
+──────────────────────────────────────────────── */
+.header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 100;
+ height: var(--header-h);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 28px;
+ border-radius: 0;
+ border-top: none;
+ border-left: none;
+ border-right: none;
+}
+
+.header__logo {
+ font-size: 1.1rem;
+ font-weight: 700;
+ letter-spacing: 0.04em;
+ color: var(--text-1);
+ text-transform: uppercase;
+}
+
+/* ────────────────────────────────────────────────
+ NAV
+──────────────────────────────────────────────── */
+.nav {
+ display: flex;
+ gap: 4px;
+}
+
+.nav-link {
+ color: var(--text-2);
+ text-decoration: none;
+ padding: 6px 16px;
+ border-radius: 20px;
+ font-size: 0.875rem;
+ font-weight: 500;
+ transition: background 0.2s, color 0.2s;
+}
+
+.nav-link:hover {
+ background: var(--glass-hover);
+ color: var(--text-1);
+}
+
+.nav-link.active {
+ background: rgba(192, 57, 43, 0.25);
+ color: var(--accent-hover);
+}
+
+/* ────────────────────────────────────────────────
+ MAIN CONTENT
+──────────────────────────────────────────────── */
+#content {
+ margin-top: var(--header-h);
+ margin-bottom: var(--player-h);
+ flex: 1;
+ overflow-y: auto;
+ padding: 24px 0;
+ width: 100%;
+ max-width: 860px;
+ align-self: center;
+}
+
+/* Градиентное затухание контента перед плеером */
+body::after {
+ content: '';
+ position: fixed;
+ bottom: var(--player-h);
+ left: 0;
+ right: 0;
+ height: 72px;
+ background: linear-gradient(to top, var(--bg) 0%, transparent 100%);
+ pointer-events: none;
+ z-index: 50;
+}
+
+/* ────────────────────────────────────────────────
+ SEARCH BAR
+──────────────────────────────────────────────── */
+.search-bar {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 16px;
+ border-radius: var(--radius);
+ margin-bottom: 16px;
+ color: var(--text-2);
+}
+
+.search-bar input {
+ flex: 1;
+ background: none;
+ border: none;
+ outline: none;
+ color: var(--text-1);
+ font-size: 0.9rem;
+}
+
+.search-bar input::placeholder { color: var(--text-2); }
+
+/* ────────────────────────────────────────────────
+ TRACKS LIST
+──────────────────────────────────────────────── */
+.tracks-list {
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.track-item {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ padding: 10px 14px;
+ border-radius: var(--radius);
+ cursor: pointer;
+ border: 1px solid transparent;
+ transition: background 0.18s, border-color 0.18s, box-shadow 0.18s;
+ user-select: none;
+}
+
+.track-item:hover {
+ background: rgba(192, 57, 43, 0.10);
+ border-color: rgba(192, 57, 43, 0.25);
+}
+
+.track-item.active {
+ background: rgba(192, 57, 43, 0.18);
+ border-color: rgba(192, 57, 43, 0.45);
+ box-shadow: 0 0 16px rgba(192, 57, 43, 0.12);
+}
+
+.track-item.active .track-title { color: var(--accent-hover); }
+
+.track-num {
+ width: 24px;
+ text-align: right;
+ color: var(--text-2);
+ font-size: 0.8rem;
+ flex-shrink: 0;
+}
+
+.track-item.active .track-num { color: var(--accent); }
+
+.track-cover {
+ width: 48px;
+ height: 48px;
+ border-radius: 8px;
+ object-fit: cover;
+ flex-shrink: 0;
+ background: rgba(255,255,255,0.05);
+}
+
+.track-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.track-title {
+ display: block;
+ font-weight: 500;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ color: var(--text-1);
+}
+
+.track-artist {
+ display: block;
+ font-size: 0.8rem;
+ color: var(--text-2);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.track-album {
+ color: var(--text-2);
+ opacity: 0.65;
+}
+
+.track-duration {
+ font-size: 0.8rem;
+ color: var(--text-2);
+ flex-shrink: 0;
+ margin-left: auto;
+ padding-left: 12px;
+ font-variant-numeric: tabular-nums;
+}
+
+/* ────────────────────────────────────────────────
+ AUDIO PLAYER BAR
+──────────────────────────────────────────────── */
+.player {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: var(--player-h);
+ display: grid;
+ grid-template-columns: 1fr 2fr 1fr;
+ align-items: center;
+ padding: 0 24px;
+ border-radius: 0;
+ border-bottom: none;
+ border-left: none;
+ border-right: none;
+ z-index: 100;
+}
+
+/* left — cover + info */
+.player__left {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ min-width: 0;
+}
+
+.player__cover {
+ width: 52px;
+ height: 52px;
+ border-radius: 8px;
+ object-fit: cover;
+ background: rgba(255,255,255,0.05);
+ flex-shrink: 0;
+}
+
+.player__info { min-width: 0; }
+
+.player__title {
+ font-size: 0.875rem;
+ font-weight: 600;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ color: var(--text-1);
+}
+
+.player__artist {
+ font-size: 0.78rem;
+ color: var(--text-2);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* center — controls + progress */
+.player__center {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+}
+
+.player__controls {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.btn-icon {
+ background: none;
+ border: none;
+ color: var(--text-2);
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ font-size: 0.9rem;
+ transition: color 0.15s, background 0.15s;
+}
+
+.btn-icon:hover { color: var(--text-1); background: var(--glass-hover); }
+
+.btn-icon.active { color: var(--accent-hover); }
+
+.btn-play {
+ width: 40px;
+ height: 40px;
+ font-size: 1rem;
+ background: var(--accent);
+ color: #fff;
+ border-radius: 50%;
+}
+
+.btn-play:hover { background: var(--accent-hover); color: #fff; }
+
+/* progress bar */
+.player__progress {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ width: 100%;
+ max-width: 480px;
+}
+
+.player__progress span {
+ font-size: 0.72rem;
+ color: var(--text-2);
+ width: 32px;
+ text-align: center;
+ flex-shrink: 0;
+}
+
+/* right — volume */
+.player__right {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 10px;
+ color: var(--text-2);
+}
+
+/* ────────────────────────────────────────────────
+ RANGE SLIDERS
+──────────────────────────────────────────────── */
+input[type=range] {
+ -webkit-appearance: none;
+ appearance: none;
+ height: 4px;
+ border-radius: 2px;
+ outline: none;
+ cursor: pointer;
+ background: linear-gradient(
+ to right,
+ var(--accent) 0%,
+ var(--accent) var(--value, 0%),
+ rgba(255,255,255,0.18) var(--value, 0%),
+ rgba(255,255,255,0.18) 100%
+ );
+}
+
+#progressSlider { flex: 1; }
+#volumeSlider { width: 80px; }
+
+input[type=range]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ width: 0;
+ height: 12px;
+ background: #fff;
+ border-radius: 50%;
+ transition: width 0.15s;
+}
+
+input[type=range]:hover::-webkit-slider-thumb,
+input[type=range]:active::-webkit-slider-thumb { width: 12px; }
+
+/* ────────────────────────────────────────────────
+ UPLOAD FORM
+──────────────────────────────────────────────── */
+.upload-card {
+ border-radius: var(--radius);
+ padding: 32px;
+ max-width: 480px;
+ margin: 0 auto;
+}
+
+.upload-card h2 {
+ font-size: 1.2rem;
+ font-weight: 600;
+ margin-bottom: 24px;
+ color: var(--text-1);
+}
+
+.form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.form-input {
+ background: rgba(255,255,255,0.06);
+ border: 1px solid var(--glass-border);
+ border-radius: 8px;
+ padding: 10px 14px;
+ color: var(--text-1);
+ font-size: 0.875rem;
+ outline: none;
+ transition: border-color 0.2s;
+ width: 100%;
+}
+
+.form-input::placeholder { color: var(--text-2); }
+.form-input:focus { border-color: rgba(192,57,43,0.5); }
+
+.file-label {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ background: rgba(255,255,255,0.06);
+ border: 1px dashed var(--glass-border);
+ border-radius: 8px;
+ padding: 12px 14px;
+ cursor: pointer;
+ color: var(--text-2);
+ transition: border-color 0.2s, background 0.2s;
+ font-size: 0.875rem;
+}
+
+.file-label:hover {
+ border-color: rgba(192,57,43,0.4);
+ background: rgba(192,57,43,0.06);
+ color: var(--text-1);
+}
+
+.file-label input[type=file] { display: none; }
+
+.btn-submit {
+ margin-top: 8px;
+ background: var(--accent);
+ border: none;
+ border-radius: 8px;
+ color: #fff;
+ padding: 10px 20px;
+ font-size: 0.875rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background 0.2s;
+ width: 100%;
+}
+
+.btn-submit:hover { background: var(--accent-hover); }
+.btn-submit:disabled { opacity: 0.5; cursor: not-allowed; }
+
+.upload-message {
+ margin-top: 12px;
+ font-size: 0.85rem;
+ text-align: center;
+}
+
+.upload-message.success { color: #4ade80; }
+.upload-message.error { color: var(--accent-hover); }
+
+/* ────────────────────────────────────────────────
+ PEERS PAGE
+──────────────────────────────────────────────── */
+.peers-card {
+ border-radius: var(--radius);
+ padding: 28px;
+}
+
+.peers-card h2 {
+ font-size: 1.1rem;
+ font-weight: 600;
+ margin-bottom: 20px;
+}
+
+.peers-list {
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-bottom: 24px;
+}
+
+.peer-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 16px;
+ border-radius: 10px;
+ border: 1px solid var(--glass-border);
+ background: rgba(255,255,255,0.04);
+}
+
+.peer-info strong { display: block; font-size: 0.9rem; }
+.peer-info span { font-size: 0.78rem; color: var(--text-2); }
+
+.btn-remove {
+ background: none;
+ border: 1px solid rgba(192,57,43,0.3);
+ color: var(--accent);
+ border-radius: 6px;
+ padding: 4px 10px;
+ cursor: pointer;
+ font-size: 0.78rem;
+ transition: background 0.2s;
+}
+
+.btn-remove:hover { background: rgba(192,57,43,0.15); }
+
+.peers-empty { color: var(--text-2); font-size: 0.875rem; margin-bottom: 20px; }
+
+.peers-add h3 { font-size: 0.95rem; margin-bottom: 12px; color: var(--text-2); }
+.peers-add .form-group { flex-direction: row; gap: 8px; }
+.peers-add .form-input { flex: 1; }
+
+/* ────────────────────────────────────────────────
+ EMPTY / LOADING STATES
+──────────────────────────────────────────────── */
+.state-empty, .state-loading {
+ text-align: center;
+ color: var(--text-2);
+ padding: 48px 0;
+ font-size: 0.9rem;
+}
+
+.state-loading::after {
+ content: '';
+ display: inline-block;
+ width: 18px;
+ height: 18px;
+ border: 2px solid var(--glass-border);
+ border-top-color: var(--accent);
+ border-radius: 50%;
+ animation: spin 0.7s linear infinite;
+ vertical-align: middle;
+ margin-left: 8px;
+}
+
+@keyframes spin { to { transform: rotate(360deg); } }
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 0000000..8fa3721
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,65 @@
+
+
+
+