first commit

This commit is contained in:
2026-02-12 14:39:03 +03:00
parent 519c33a261
commit bb7e47c430
16 changed files with 3859 additions and 0 deletions

39
.gitignore vendored Normal file
View File

@ -0,0 +1,39 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
.eslintcache
# Cypress
/cypress/videos/
/cypress/screenshots/
# Vitest
__screenshots__/
# Vite
*.timestamp-*-*.mjs

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

1
env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

25
index.html Normal file
View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;700&display=swap">
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" rel="stylesheet"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sonoma - Free Lossless Audio Streaming</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script
src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
</script>
</body>
</html>

3216
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

31
package.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "sonoma-site",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build"
},
"dependencies": {
"vue": "^3.5.27",
"vue-router": "^5.0.1"
},
"devDependencies": {
"@tsconfig/node24": "^24.0.4",
"@types/node": "^24.10.9",
"@vitejs/plugin-vue": "^6.0.3",
"@vue/tsconfig": "^0.8.1",
"npm-run-all2": "^8.0.4",
"typescript": "~5.9.3",
"vite": "^7.3.1",
"vite-plugin-vue-devtools": "^8.0.5",
"vue-tsc": "^3.2.4"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

BIN
public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

51
src/App.vue Normal file
View File

@ -0,0 +1,51 @@
<template>
<div>
<header>
<div class="logo"><a href="/"><img src="/public/icon.png"></a></div>
<nav>
<RouterLink to='/'>Главная</RouterLink>
<a href="https://open.sonoma.su">Слушать</a>
<a onclick="toastr.error('В разработке')">Для исполнителей</a>
</nav>
<button class="theme-toggle" @click="toggleTheme">
<i :class="isLightTheme ? 'fas fa-moon' : 'fas fa-sun'"></i>
</button>
</header>
<RouterView />
<footer>
<div class="footer-top">
<div class="footer-links">
<a href="https://sonoma.su">Создано группой Sonoma</a>
<a>Обратная связь - support@sonoma.su</a>
</div>
<div class="footer-icons">
<a href="https://git.sonoma.su/Sonoma/sonoma-site"><i class="fab fa-github"></i></a>
<a href="https://t.me/saddydead"><i class="fab fa-telegram"></i></a>
</div>
</div>
<div class="footer-bottom">
<p>Copyright ©2026 Sonoma</p>
</div>
</footer>
</div>
</template>
<script lang="ts">
export default {
data(): { isLightTheme: boolean } {
return {
isLightTheme: false,
};
},
methods: {
toggleTheme(): void {
this.isLightTheme = !this.isLightTheme;
if (this.isLightTheme) {
document.body.classList.add('light-theme');
} else {
document.body.classList.remove('light-theme');
}
},
},
};
</script>

312
src/assets/main.css Normal file
View File

@ -0,0 +1,312 @@
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(180deg, #2b1d3a, #000);
color: #fff;
text-align: center;
transition: background 0.3s, color 0.3s;
}
body.light-theme {
background: linear-gradient(180deg, #f0f0f0, #ffffff);
color: #000;
}
header {
position: fixed;
top: 0;
left: 0;
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
padding: 20px;
background: rgba(0, 0, 0, 0.8);
transition: background 0.3s;
z-index: 1000;
}
body.light-theme header {
background: rgba(255, 255, 255, 0.8);
}
nav a {
color: #fff;
margin: 0 15px;
text-decoration: none;
transition: color 0.3s;
}
details a {
text-decoration: none;
color: #a18cd1;
}
.r1 {
text-decoration: none;
background: linear-gradient(90deg, #a18cd1, #fbc2eb);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
summary {
padding: 15px;
}
body.light-theme nav a {
color: #000;
}
.theme-toggle {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: inherit;
}
.main-title {
margin-top: 150px;
}
h2 {
font-family: 'Manrope', sans serif;
}
h3 {
font-family: 'Manrope', sans serif;
}
.main-title h1 {
font-family: 'Manrope', sans serif;
font-size: 64px;
background: linear-gradient(90deg, #a18cd1, #fbc2eb);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.main-title button {
margin-top: 20px;
padding: 15px 30px;
font-size: 18px;
border: none;
border-radius: 30px;
background: linear-gradient(90deg, #a18cd1, #fbc2eb);
color: #fff;
cursor: pointer;
}
.pricing {
margin: 100px auto;
max-width: 1000px;
}
.pricing h2 {
font-family: 'Manrope', sans serif;
font-size: 36px;
}
.artists {
margin: 100px auto;
max-width: 1000px;
}
.artists h2 {
font-family: 'Manrope', sans serif;
font-size: 36px;
}
.options {
display: flex;
justify-content: space-around;
margin-top: 50px;
}
.card {
background: #2b1d3a;
border-radius: 20px;
padding: 30px;
width: 250px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
transition: background 0.3s, color 0.3s;
}
body.light-theme .card {
background: #fff;
color: #000;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
.card h3 {
font-size: 24px;
margin-bottom: 10px;
}
.card p {
font-size: 18px;
margin: 5px 0;
}
.card button {
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 20px;
background: linear-gradient(90deg, #a18cd1, #fbc2eb);
color: #fff;
cursor: pointer;
}
.artist {
background: #2b1d3a;
border-radius: 200px;
padding: 30px;
width: 250px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
transition: background 0.3s, color 0.3s;
}
body.light-theme .artist {
background: #fff;
color: #000;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
.artist h3 {
font-size: 24px;
margin-bottom: 10px;
}
.artist p {
font-size: 18px;
margin: 5px 0;
}
.artist button {
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 20px;
background: linear-gradient(90deg, #a18cd1, #fbc2eb);
color: #fff;
cursor: pointer;
}
.highlight {
background: #fff;
color: #2b1d3a;
}
.logo img {
width: 50px;
}
footer {
background: #0e0e0e;
color: #fff;
padding: 40px 100px;
font-family: 'Manrope', sans-serif;
}
.footer-top {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
border-bottom: 1px solid #444;
padding-bottom: 20px;
}
.footer-links {
display: flex;
flex-wrap: wrap;
gap: 30px;
}
.footer-links a {
color: #aaa;
text-decoration: none;
font-size: 14px;
}
.footer-links a:hover {
color: #fff;
}
.footer-icons a {
margin-right: 15px;
color: #aaa;
font-size: 20px;
}
.footer-icons a:hover {
color: #fff;
}
.footer-bottom {
text-align: center;
margin-top: 40px;
}
.footer-bottom p {
font-size: 12px;
color: #777;
}
@media (max-width: 768px) {
.footer-top {
flex-direction: column;
align-items: center;
text-align: center;
}
.footer-links {
justify-content: center;
margin: 20px 0;
}
.footer-icons {
justify-content: center;
}
}
.ticker {
position: absolute;
left: 0;
width: 100%;
overflow: hidden;
white-space: nowrap;
pointer-events: none;
margin-top: 10px;
}
.ticker-track {
display: inline-flex;
gap: 64px;
padding-left: 100%;
animation: scroll 20s linear infinite;
font-size: 24px;
font-weight: 600;
opacity: 0.85;
}
.ticker-track span {
text-transform: uppercase;
letter-spacing: 2px;
}
@keyframes scroll {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}

101
src/components/Main.vue Normal file
View File

@ -0,0 +1,101 @@
<template>
<section class="main-title">
<h1>Sonoma Hi-Res</h1>
<p>Полностью бесплатный стриминговый сервис для прослушивания FLAC, Lossless и Hi-Res! <br> До 24bit/192kHz</p>
<button @click="$router.push('register')">Начать</button>
</section>
<section class="pricing">
<h2>Наши преимущества</h2>
<p>Чем же мы отличаемся от конкурентов?</p>
<div class="options">
<div class="card">
<h3>Для всех устройств</h3>
<p>У нас есть приложения для каждого</p>
<p>Windows, Mac, Linux,</p>
<p>Web, Android и iOS</p>
</div>
<div class="card highlight">
<h3>Бесплатно</h3>
<p>Сервис полностью бесплатный</p>
<p>До 24bit/192kHz</p>
<p>Все форматы, включая FLAC, Lossless и Hi-Res</p>
</div>
<div class="card">
<h3>Поддержка исполнителей</h3>
<p>Наш сервис строится на музыке,</p>
<p>которую может загрузить любой желающий!</p>
</div>
</div>
<div class="options">
<div class="card highlight">
<h3>От таких же как вы</h3>
<p>Мы тоже любим музыку в хорошем качестве и бесплатно</p>
<p>Также мы знаем, как сложно найти лейбл маленькому артисту</p>
</div>
<div class="card">
<h3>Open Source</h3>
<p>Весь исходный код сервиса открыт</p>
<p>Любой желающий может на него посмотреть</p>
<p>MIT License</p>
</div>
<div class="card highlight">
<h3>Сервера в России</h3>
<p>Не будет проблем с задержкой или лагами</p>
<p>Сервис сделан русскими людьми</p>
<p>Поддержка 24/7</p>
</div>
</div>
</section>
<section class="pricing">
<h2>Варианты подписок</h2>
<p>Все возможные варианты подписок</p>
<div class="options">
<div class="card">
<h3>Sonoma Artist</h3>
<p>Специально для исполнителей</p>
<p>Мы ищем исполнителей,</p>
<p>Но вы можете сами оставить нам заявку!</p>
<button onclick="toastr.error('В разработке')">Связаться</button>
</div>
<div class="card highlight">
<h3>Sonoma Plus</h3>
<p>Free</p>
<p>Доступ к сервису</p>
<p>24bit/192kHz</p>
<p>Все форматы, включая FLAC, Lossless и Hi-Res</p>
<button onclick="location.href='https://discord.gg/sEqEEchsQW'">Регистрация</button>
</div>
<div class="card">
<h3>Sonoma Staff</h3>
<p>Хотите присоединиться к разработке?</p>
<p>Сервис разрабатывается одним человеком</p>
<p>Будем рады в поддержке!</p>
<button onclick="location.href='https://t.me/saddydead'">Связаться</button>
</div>
</div>
</section>
<section class="artists">
<h2>Нас выбирают</h2>
<p>Посмотрите артистов, которые выбрали нас</p>
<div class="ticker">
<div class="ticker-track">
<span>SaddyDEAD</span>
<span>ARTIST</span>
<span>ARTIST</span>
<span>ARTIST</span>
<span>ARTIST</span>
<span>ARTIST</span>
<span>ARTIST</span>
<span>ARTIST</span>
<span>ARTIST</span>
<span>ARTIST</span>
<span>ARTIST</span>
</div>
</div>
</section>
</template>

20
src/main.ts Normal file
View File

@ -0,0 +1,20 @@
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import { createMemoryHistory, createRouter } from 'vue-router'
import Main from './components/Main.vue'
const routes = [
{ path: '/', component: Main }
]
const router = createRouter({
history: createMemoryHistory(),
routes,
})
createApp(App).use(router).mount('#app')

12
tsconfig.app.json Normal file
View File

@ -0,0 +1,12 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./src/*"]
}
}
}

11
tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

19
tsconfig.node.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "@tsconfig/node24/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*",
"eslint.config.*"
],
"compilerOptions": {
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

18
vite.config.ts Normal file
View File

@ -0,0 +1,18 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})