mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-03-30 16:30:22 +02:00
803 lines
54 KiB
HTML
803 lines
54 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Partners & Services - SpotiFLAC Mobile</title>
|
|
<meta name="description" content="The APIs and services that power SpotiFLAC Mobile. Giving credit to the platforms that make lossless music downloads possible.">
|
|
<meta name="theme-color" content="#0a0a0a">
|
|
<link rel="icon" href="icon.png" type="image/png">
|
|
|
|
<!-- Google Sans Flex -->
|
|
<style>
|
|
@font-face { font-family: 'Google Sans Flex'; font-style: normal; font-display: swap; font-weight: 400; src: url(https://cdn.jsdelivr.net/fontsource/fonts/google-sans-flex@latest/latin-400-normal.woff2) format('woff2'); }
|
|
@font-face { font-family: 'Google Sans Flex'; font-style: normal; font-display: swap; font-weight: 500; src: url(https://cdn.jsdelivr.net/fontsource/fonts/google-sans-flex@latest/latin-500-normal.woff2) format('woff2'); }
|
|
@font-face { font-family: 'Google Sans Flex'; font-style: normal; font-display: swap; font-weight: 600; src: url(https://cdn.jsdelivr.net/fontsource/fonts/google-sans-flex@latest/latin-600-normal.woff2) format('woff2'); }
|
|
@font-face { font-family: 'Google Sans Flex'; font-style: normal; font-display: swap; font-weight: 700; src: url(https://cdn.jsdelivr.net/fontsource/fonts/google-sans-flex@latest/latin-700-normal.woff2) format('woff2'); }
|
|
@font-face { font-family: 'Google Sans Flex'; font-style: normal; font-display: swap; font-weight: 800; src: url(https://cdn.jsdelivr.net/fontsource/fonts/google-sans-flex@latest/latin-800-normal.woff2) format('woff2'); }
|
|
</style>
|
|
|
|
<style>
|
|
/* ── M3 AMOLED surface ramp ── */
|
|
:root {
|
|
--green: #1DB954;
|
|
--green-dim: #1aa34a;
|
|
--bg: #0a0a0a;
|
|
--bg-card: #1a1a1a;
|
|
--bg-card-hover: #222222;
|
|
--surface: #121212;
|
|
--text: #e8e8e8;
|
|
--text-dim: #999;
|
|
--max-w: 1100px;
|
|
}
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
html { scroll-behavior: smooth; scrollbar-width: thin; scrollbar-color: #333 transparent; }
|
|
html::-webkit-scrollbar { width: 8px; }
|
|
html::-webkit-scrollbar-track { background: transparent; }
|
|
html::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; }
|
|
html::-webkit-scrollbar-thumb:hover { background: #555; }
|
|
|
|
body {
|
|
font-family: 'Google Sans Flex', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
background: var(--bg); color: var(--text); line-height: 1.6;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
a { color: var(--green); text-decoration: none; }
|
|
a:hover { text-decoration: underline; }
|
|
|
|
/* ── NAV ── */
|
|
nav {
|
|
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
|
|
background: rgba(18,18,18,.78);
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
}
|
|
.nav-inner {
|
|
max-width: var(--max-w); margin: auto;
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 0 24px; height: 64px;
|
|
}
|
|
.nav-brand { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 1.1rem; color: var(--text); }
|
|
.nav-brand img { width: 32px; height: 32px; border-radius: 50%; }
|
|
.nav-links { display: flex; align-items: center; gap: 24px; list-style: none; }
|
|
.nav-links a { color: var(--text-dim); font-size: .9rem; transition: color .2s; }
|
|
.nav-links a:hover { color: var(--text); text-decoration: none; }
|
|
.nav-links a.active { color: var(--text); font-weight: 600; }
|
|
.nav-links .nav-icon { display: flex; align-items: center; opacity: .6; transition: opacity .2s; margin-left: -12px; }
|
|
.nav-links .nav-icon:hover { opacity: 1; }
|
|
.nav-links .nav-icon svg { width: 24px; height: 24px; fill: currentColor; }
|
|
.nav-links .nav-divider { width: 1px; height: 20px; background: rgba(255,255,255,.15); margin-left: -4px; }
|
|
.search-trigger {
|
|
display: flex; align-items: center; gap: 6px;
|
|
background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.12);
|
|
border-radius: 8px; padding: 6px 12px;
|
|
color: var(--text-dim); font-size: .85rem; cursor: pointer;
|
|
font-family: inherit; transition: color .2s, border-color .2s, background .2s;
|
|
white-space: nowrap; text-decoration: none;
|
|
}
|
|
.search-trigger:hover { color: var(--text); border-color: rgba(255,255,255,.25); background: rgba(255,255,255,.1); text-decoration: none; }
|
|
.search-trigger svg { width: 14px; height: 14px; fill: currentColor; flex-shrink: 0; }
|
|
.search-trigger kbd {
|
|
background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.1);
|
|
border-radius: 4px; padding: 0px 4px; font-size: .65rem;
|
|
font-family: inherit; color: #555; line-height: 1.4; margin-left: 2px;
|
|
}
|
|
|
|
/* ── PAGE HEADER ── */
|
|
.page-header {
|
|
padding: 100px 24px 40px; text-align: center;
|
|
}
|
|
.page-header h1 { font-size: 2rem; font-weight: 800; margin-bottom: 8px; }
|
|
.page-header p { color: var(--text-dim); font-size: 1rem; max-width: 560px; margin: 0 auto; }
|
|
|
|
/* ── SECTIONS ── */
|
|
section { padding: 40px 24px 60px; }
|
|
.section-inner { max-width: var(--max-w); margin: auto; }
|
|
.section-label {
|
|
font-size: .85rem; font-weight: 600;
|
|
color: var(--green); margin-bottom: 8px;
|
|
}
|
|
.section-title { font-size: 1.5rem; font-weight: 700; margin-bottom: 8px; }
|
|
.section-sub { color: var(--text-dim); font-size: .95rem; margin-bottom: 32px; max-width: 600px; }
|
|
|
|
/* ── INFRA CARDS ── */
|
|
.infra-grid {
|
|
display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
gap: 12px;
|
|
}
|
|
.infra-card {
|
|
background: var(--bg-card);
|
|
border-radius: 20px;
|
|
padding: 24px;
|
|
display: flex; align-items: flex-start; gap: 16px;
|
|
transition: background .2s;
|
|
}
|
|
.infra-card:hover { background: var(--bg-card-hover); }
|
|
.infra-icon {
|
|
width: 48px; height: 48px; border-radius: 12px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.infra-icon svg { width: 24px; height: 24px; fill: currentColor; }
|
|
.infra-info { flex: 1; min-width: 0; }
|
|
.infra-name { font-size: 1.05rem; font-weight: 700; margin-bottom: 4px; }
|
|
.infra-desc { color: var(--text-dim); font-size: .88rem; line-height: 1.6; margin-bottom: 10px; }
|
|
.infra-link {
|
|
font-size: .82rem; font-weight: 600; color: var(--text-dim);
|
|
display: inline-flex; align-items: center; gap: 5px;
|
|
transition: color .2s;
|
|
}
|
|
.infra-link:hover { color: var(--text); text-decoration: none; }
|
|
.infra-link svg { width: 13px; height: 13px; fill: currentColor; }
|
|
|
|
/* ── FOOTER ── */
|
|
footer {
|
|
background: var(--surface);
|
|
padding: 40px 24px; text-align: center;
|
|
}
|
|
.footer-inner { max-width: var(--max-w); margin: auto; }
|
|
.footer-links { display: flex; gap: 24px; justify-content: center; flex-wrap: wrap; margin-bottom: 16px; }
|
|
.footer-links a { color: var(--text-dim); font-size: .9rem; }
|
|
.footer-links a:hover { color: var(--text); }
|
|
.footer-copy { color: #555; font-size: .8rem; }
|
|
|
|
/* ── DISCLAIMER ── */
|
|
.disclaimer {
|
|
max-width: var(--max-w); margin: 0 auto; padding: 0 24px 60px;
|
|
text-align: center;
|
|
}
|
|
.disclaimer p {
|
|
color: #555; font-size: .8rem; line-height: 1.6;
|
|
max-width: 600px; margin: 0 auto;
|
|
padding: 20px; border-radius: 16px;
|
|
background: var(--surface);
|
|
}
|
|
|
|
/* ── MOBILE MENU ── */
|
|
.nav-burger {
|
|
display: none; width: 40px; height: 40px; border-radius: 12px;
|
|
background: none; border: none; cursor: pointer;
|
|
align-items: center; justify-content: center; flex-shrink: 0;
|
|
position: relative;
|
|
}
|
|
.nav-burger .bar {
|
|
display: block; width: 20px; height: 2px; background: var(--text);
|
|
border-radius: 2px; transition: transform .3s cubic-bezier(.4,0,.2,1), opacity .2s;
|
|
position: absolute; left: 10px;
|
|
}
|
|
.nav-burger .bar:nth-child(1) { top: 12px; }
|
|
.nav-burger .bar:nth-child(2) { top: 19px; }
|
|
.nav-burger .bar:nth-child(3) { top: 26px; }
|
|
.nav-burger.active .bar:nth-child(1) { top: 19px; transform: rotate(45deg); }
|
|
.nav-burger.active .bar:nth-child(2) { opacity: 0; }
|
|
.nav-burger.active .bar:nth-child(3) { top: 19px; transform: rotate(-45deg); }
|
|
.mobile-overlay {
|
|
position: fixed; top: 64px; left: 0; right: 0; bottom: 0;
|
|
background: rgba(0,0,0,.5); z-index: 98;
|
|
opacity: 0; pointer-events: none;
|
|
transition: opacity .3s cubic-bezier(.4,0,.2,1);
|
|
}
|
|
.mobile-overlay.open { opacity: 1; pointer-events: auto; }
|
|
.mobile-menu {
|
|
position: fixed; top: 64px; left: 0; right: 0;
|
|
background: rgba(18,18,18,.95); padding: 8px 16px 16px; z-index: 99;
|
|
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
|
|
transform: translateY(-8px); opacity: 0; pointer-events: none;
|
|
transition: transform .3s cubic-bezier(.4,0,.2,1), opacity .3s cubic-bezier(.4,0,.2,1);
|
|
}
|
|
.mobile-menu.open { transform: translateY(0); opacity: 1; pointer-events: auto; }
|
|
.mobile-menu a {
|
|
display: flex; align-items: center; gap: 12px;
|
|
padding: 14px 16px; border-radius: 12px;
|
|
color: var(--text-dim); font-size: .95rem; font-weight: 500;
|
|
transition: background .2s; opacity: 0; transform: translateY(-6px);
|
|
}
|
|
.mobile-menu.open a {
|
|
opacity: 1; transform: translateY(0);
|
|
transition: background .2s, opacity .3s cubic-bezier(.4,0,.2,1), transform .3s cubic-bezier(.4,0,.2,1);
|
|
}
|
|
.mobile-menu.open a:nth-child(1) { transition-delay: .03s; }
|
|
.mobile-menu.open a:nth-child(2) { transition-delay: .06s; }
|
|
.mobile-menu.open a:nth-child(3) { transition-delay: .09s; }
|
|
.mobile-menu.open a:nth-child(4) { transition-delay: .12s; }
|
|
.mobile-menu.open a:nth-child(5) { transition-delay: .15s; }
|
|
.mobile-menu a:hover { background: var(--bg-card); color: var(--text); text-decoration: none; }
|
|
.mobile-menu a.active { color: var(--text); font-weight: 600; background: var(--bg-card); }
|
|
.mobile-menu .mobile-divider {
|
|
height: 1px; background: rgba(255,255,255,.06); margin: 4px 0;
|
|
opacity: 0; transition: opacity .3s .15s;
|
|
}
|
|
.mobile-menu.open .mobile-divider { opacity: 1; }
|
|
.mobile-menu .mobile-icons {
|
|
display: flex; gap: 8px; padding: 8px 16px 0;
|
|
opacity: 0; transform: translateY(-6px);
|
|
transition: opacity .3s cubic-bezier(.4,0,.2,1) .18s, transform .3s cubic-bezier(.4,0,.2,1) .18s;
|
|
}
|
|
.mobile-menu.open .mobile-icons { opacity: 1; transform: translateY(0); }
|
|
.mobile-menu .mobile-icons a {
|
|
padding: 10px; border-radius: 12px; background: var(--bg-card);
|
|
display: flex; align-items: center; justify-content: center;
|
|
opacity: 1; transform: none;
|
|
}
|
|
.mobile-menu .mobile-icons a svg { width: 20px; height: 20px; fill: currentColor; }
|
|
|
|
/* ── MOBILE ── */
|
|
@media (max-width: 640px) {
|
|
.nav-links { display: none; }
|
|
.nav-burger { display: flex; }
|
|
.page-header { padding: 80px 16px 32px; }
|
|
section { padding: 32px 16px 48px; }
|
|
.infra-grid { grid-template-columns: 1fr; }
|
|
.disclaimer { padding: 0 16px 48px; }
|
|
}
|
|
</style>
|
|
<style>
|
|
/* ── SEARCH MODAL ── */
|
|
.search-overlay {
|
|
position: fixed; inset: 0; background: rgba(0,0,0,.6);
|
|
z-index: 300; opacity: 0; pointer-events: none;
|
|
transition: opacity .2s cubic-bezier(.4,0,.2,1);
|
|
display: flex; align-items: flex-start; justify-content: center;
|
|
padding-top: min(20vh, 140px);
|
|
}
|
|
.search-overlay.open { opacity: 1; pointer-events: auto; }
|
|
.search-modal {
|
|
background: var(--surface); border: 1px solid rgba(255,255,255,.1);
|
|
border-radius: 16px; width: 580px; max-width: calc(100vw - 32px);
|
|
max-height: min(70vh, 520px); display: flex; flex-direction: column;
|
|
box-shadow: 0 16px 70px rgba(0,0,0,.6);
|
|
transform: translateY(-12px) scale(.97); opacity: 0;
|
|
transition: transform .25s cubic-bezier(.4,0,.2,1), opacity .2s;
|
|
}
|
|
.search-overlay.open .search-modal { transform: translateY(0) scale(1); opacity: 1; }
|
|
.search-header {
|
|
display: flex; align-items: center; gap: 10px;
|
|
padding: 14px 16px; border-bottom: 1px solid rgba(255,255,255,.08);
|
|
}
|
|
.search-header svg { width: 18px; height: 18px; fill: var(--text-dim); flex-shrink: 0; }
|
|
.search-input {
|
|
flex: 1; background: none; border: none; outline: none;
|
|
color: var(--text); font-size: .95rem; font-family: inherit;
|
|
}
|
|
.search-input::placeholder { color: var(--text-dim); }
|
|
.search-esc {
|
|
background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.1);
|
|
border-radius: 4px; padding: 2px 7px; font-size: .72rem;
|
|
color: var(--text-dim); font-family: inherit; cursor: pointer;
|
|
}
|
|
.search-esc:hover { background: rgba(255,255,255,.14); }
|
|
.search-body { overflow-y: auto; padding: 8px; scrollbar-width: thin; scrollbar-color: #333 transparent; }
|
|
.search-body::-webkit-scrollbar { width: 6px; }
|
|
.search-body::-webkit-scrollbar-track { background: transparent; }
|
|
.search-body::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
|
|
.search-group-label {
|
|
padding: 8px 10px 4px; font-size: .72rem; font-weight: 600;
|
|
color: var(--text-dim); text-transform: uppercase; letter-spacing: .04em;
|
|
}
|
|
.search-item {
|
|
display: flex; align-items: center; gap: 10px;
|
|
padding: 10px 12px; border-radius: 10px; cursor: pointer;
|
|
color: var(--text); font-size: .88rem; transition: background .15s;
|
|
}
|
|
.search-item:hover, .search-item.active { background: rgba(255,255,255,.07); }
|
|
.search-item svg { width: 16px; height: 16px; fill: var(--text-dim); flex-shrink: 0; }
|
|
.search-item-text { flex: 1; min-width: 0; }
|
|
.search-item-title { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.search-item-section { font-size: .78rem; color: var(--text-dim); margin-top: 1px; }
|
|
.search-item mark { background: rgba(29,185,84,.25); color: var(--text); border-radius: 2px; padding: 0 1px; }
|
|
.search-item .search-enter { color: var(--text-dim); font-size: .72rem; opacity: 0; transition: opacity .15s; }
|
|
.search-item.active .search-enter { opacity: 1; }
|
|
.search-empty { padding: 32px 16px; text-align: center; color: var(--text-dim); font-size: .9rem; }
|
|
.search-footer {
|
|
padding: 10px 16px; border-top: 1px solid rgba(255,255,255,.06);
|
|
display: flex; align-items: center; gap: 16px; font-size: .72rem; color: #555;
|
|
}
|
|
.search-footer kbd {
|
|
background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.08);
|
|
border-radius: 3px; padding: 1px 4px; font-family: inherit; font-size: .68rem;
|
|
}
|
|
@media (max-width: 640px) {
|
|
.search-trigger kbd { display: none; }
|
|
.search-overlay { padding-top: 16px; }
|
|
.search-modal { max-height: 80vh; border-radius: 14px; }
|
|
}
|
|
</style>
|
|
<body>
|
|
|
|
<nav>
|
|
<div class="nav-inner">
|
|
<a class="nav-brand" href="index">
|
|
<img src="icon.png" alt="SpotiFLAC">
|
|
SpotiFLAC Mobile
|
|
</a>
|
|
<ul class="nav-links">
|
|
<li><a href="index#features">Features</a></li>
|
|
<li><a href="downloads">Downloads</a></li>
|
|
<li><a href="index#faq">FAQ</a></li>
|
|
<li><a href="partners" class="active">Partners</a></li>
|
|
<li><a href="docs">Docs</a></li>
|
|
<li class="nav-divider"></li>
|
|
<li><button class="search-trigger" onclick="openSearch()" aria-label="Search documentation">
|
|
<svg viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
|
|
Search
|
|
<kbd id="searchShortcutHint">Ctrl K</kbd>
|
|
</button></li>
|
|
<li><a href="https://github.com/zarzet/SpotiFLAC-Mobile" target="_blank" class="nav-icon" aria-label="GitHub"><svg viewBox="0 0 24 24"><path d="M12 .3a12 12 0 0 0-3.8 23.38c.6.12.82-.26.82-.57L9 20.86c-3.34.72-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.09-.74.08-.73.08-.73 1.2.09 1.84 1.24 1.84 1.24 1.07 1.84 2.81 1.3 3.5 1 .1-.78.42-1.31.76-1.61-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.13-.3-.54-1.52.12-3.18 0 0 1-.32 3.3 1.23a11.5 11.5 0 0 1 6.02 0c2.28-1.55 3.29-1.23 3.29-1.23.66 1.66.25 2.88.12 3.18.77.84 1.24 1.91 1.24 3.22 0 4.61-2.81 5.63-5.48 5.92.43.37.81 1.1.81 2.22l-.01 3.29c0 .31.21.69.82.57A12 12 0 0 0 12 .3"/></svg></a></li>
|
|
<li><a href="https://t.me/spotiflac" target="_blank" class="nav-icon" aria-label="Telegram"><svg viewBox="0 0 24 24"><path d="M11.94 24c6.6 0 12-5.4 12-12s-5.4-12-12-12-12 5.4-12 12 5.4 12 12 12zm-3.2-8.69l-.37-3.04 8.52-5.18c.38-.23.73.09.45.35l-6.96 6.4-.29 2.97c-.04.35-.48.43-.64.12l-1.64-3.33-3.6-1.17c-.78-.24-.8-.78-.02-1.14l14.04-5.4c.65-.25 1.25.15 1.04.83l-2.39 11.28c-.18.81-.7 1.01-1.42.63l-3.92-2.89-1.89 1.82c-.21.2-.39.38-.65.38l.28-3.06z"/></svg></a></li>
|
|
</ul>
|
|
<button class="nav-burger" onclick="toggleMenu()" aria-label="Menu">
|
|
<span class="bar"></span><span class="bar"></span><span class="bar"></span>
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- MOBILE MENU -->
|
|
<div class="mobile-overlay" id="mobileOverlay" onclick="toggleMenu()"></div>
|
|
<div class="mobile-menu" id="mobileMenu">
|
|
<a href="index#features">Features</a>
|
|
<a href="downloads">Downloads</a>
|
|
<a href="index#faq">FAQ</a>
|
|
<a href="partners" class="active">Partners</a>
|
|
<a href="docs">Docs</a>
|
|
<a href="javascript:void(0)" onclick="toggleMenu();openSearch()" style="display:flex;align-items:center;gap:6px;color:var(--text-dim)"><svg viewBox="0 0 24 24" style="width:16px;height:16px;fill:currentColor"><path d="M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>Search Docs</a>
|
|
<div class="mobile-divider"></div>
|
|
<div class="mobile-icons">
|
|
<a href="https://github.com/zarzet/SpotiFLAC-Mobile" target="_blank" aria-label="GitHub">
|
|
<svg viewBox="0 0 24 24"><path d="M12 .3a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.5-1.4-1.3-1.8-1.3-1.8-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.7-1.6-2.7-.3-5.5-1.3-5.5-6 0-1.3.5-2.3 1.2-3.2-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.4 1.2a11.5 11.5 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.7 1.7.3 2.9.1 3.2.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0012 .3z"/></svg>
|
|
</a>
|
|
<a href="https://t.me/spotiflac" target="_blank" aria-label="Telegram">
|
|
<svg viewBox="0 0 24 24"><path d="M11.94 24c6.6 0 12-5.4 12-12s-5.4-12-12-12-12 5.4-12 12 5.4 12 12 12zm-3.2-8.69l-.37-3.04 8.52-5.18c.38-.23.73.09.45.35l-6.96 6.4-.29 2.97c-.04.35-.48.43-.64.12l-1.64-3.33-3.6-1.17c-.78-.24-.8-.78-.02-1.14l14.04-5.4c.65-.25 1.25.15 1.04.83l-2.39 11.28c-.18.81-.7 1.01-1.42.63l-3.92-2.89-1.89 1.82c-.21.2-.39.38-.65.38l.28-3.06z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="page-header">
|
|
<h1>Partners & Services</h1>
|
|
<p>The behind-the-scenes APIs and tools that power SpotiFLAC Mobile. We appreciate every one of them.</p>
|
|
</div>
|
|
|
|
<!-- INFRASTRUCTURE -->
|
|
<section>
|
|
<div class="section-inner">
|
|
<div class="section-label">Infrastructure</div>
|
|
<h2 class="section-title">APIs & Tools</h2>
|
|
<p class="section-sub">The services that handle link resolution, lyrics, audio extraction, and more.</p>
|
|
|
|
<div class="infra-grid">
|
|
|
|
<!-- === TRACK LINKING === -->
|
|
|
|
<!-- Odesli / song.link (no GitHub — globe) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(99,102,241,.1); color: #6366f1;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">Odesli / song.link</div>
|
|
<div class="infra-desc">Cross-platform link resolution. Translates any Spotify, Deezer, or streaming URL into matching Tidal, Qobuz, Amazon, and YouTube IDs — enabling SpotiFLAC to find the best lossless source for every track.</div>
|
|
<a class="infra-link" href="https://odesli.co" target="_blank">
|
|
odesli.co
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- I Don't Have Spotify (GitHub) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(255,255,255,.08); color: #e8e8e8;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 .3a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.5-1.4-1.3-1.8-1.3-1.8-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.7-1.6-2.7-.3-5.5-1.3-5.5-6 0-1.3.5-2.3 1.2-3.2-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.4 1.2a11.5 11.5 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.7 1.7.3 2.9.1 3.2.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0012 .3z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">I Don't Have Spotify</div>
|
|
<div class="infra-desc">Fallback link resolution service. When Odesli is rate-limited or unavailable, IDHS provides an alternative way to match Spotify links to Tidal, Qobuz, and other streaming platforms.</div>
|
|
<a class="infra-link" href="https://github.com/sjdonado/idonthavespotify" target="_blank">
|
|
sjdonado/idonthavespotify
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- LRCLIB (GitHub) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(255,255,255,.08); color: #e8e8e8;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 .3a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.5-1.4-1.3-1.8-1.3-1.8-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.7-1.6-2.7-.3-5.5-1.3-5.5-6 0-1.3.5-2.3 1.2-3.2-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.4 1.2a11.5 11.5 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.7 1.7.3 2.9.1 3.2.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0012 .3z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">LRCLIB</div>
|
|
<div class="infra-desc">Open synced lyrics database. Provides time-stamped lyrics that get embedded directly into downloaded FLAC files, so your music player can display lyrics in sync with the music.</div>
|
|
<a class="infra-link" href="https://github.com/tranxuanthang/lrclib" target="_blank">
|
|
tranxuanthang/lrclib
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Paxsenix (lyrics proxy) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(59,130,246,.1); color: #3b82f6;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 2c2.4 0 4.6 1.1 6 3 1.4 1.9 1.8 4.3 1.2 6.6-.7 2.2-2.3 4-4.4 5v2.4h-6V17c-2.1-1-3.7-2.8-4.4-5C3.8 9.3 4.2 6.9 5.6 5 7 3.1 9.2 2 11.6 2H12zm-1 18h2v2h-2v-2zm-.2-5h2.4c1.9-.7 3.3-2.2 3.9-4.1.5-1.7.2-3.5-.8-4.9-1-1.4-2.6-2.2-4.3-2.2H12c-1.7 0-3.3.8-4.3 2.2-1 1.4-1.3 3.2-.8 4.9.6 1.9 2 3.4 3.9 4.1z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">Paxsenix</div>
|
|
<div class="infra-desc">Lyrics proxy partner used for Apple Music and QQ Music lyric retrieval, including word-by-word synced formats consumed by SpotiFLAC.</div>
|
|
<a class="infra-link" href="https://lyrics.paxsenix.org" target="_blank">
|
|
lyrics.paxsenix.org
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- === TIDAL STREAM APIs === -->
|
|
|
|
<!-- hifi-api / Binimum (GitHub) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(255,255,255,.08); color: #e8e8e8;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 .3a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.5-1.4-1.3-1.8-1.3-1.8-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.7-1.6-2.7-.3-5.5-1.3-5.5-6 0-1.3.5-2.3 1.2-3.2-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.4 1.2a11.5 11.5 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.7 1.7.3 2.9.1 3.2.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0012 .3z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">hifi-api / Binimum</div>
|
|
<div class="infra-desc">Primary Tidal lossless stream API. Accepts a track ID and quality parameter, returns hi-res download URLs and DASH manifests. Also deployed at music.binimum.org.</div>
|
|
<a class="infra-link" href="https://github.com/binimum/hifi-api" target="_blank">
|
|
binimum/hifi-api
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- QQDL (no GitHub — globe) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(244,63,94,.1); color: #f43f5e;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">QQDL</div>
|
|
<div class="infra-desc">Redundant Tidal API mirror cluster. Operates five parallel endpoints (vogel, maus, hund, katze, wolf) for high-availability lossless track downloads across the API pool.</div>
|
|
<a class="infra-link" href="https://qqdl.site" target="_blank">
|
|
qqdl.site
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Squid (no GitHub — globe) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(6,182,212,.1); color: #06b6d4;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">Squid</div>
|
|
<div class="infra-desc">Dual-purpose download API serving both Tidal and Qobuz streams. Supports multi-region retrieval (US/FR fallback for Qobuz) to maximize track availability across catalogs.</div>
|
|
<a class="infra-link" href="https://squid.wtf" target="_blank">
|
|
squid.wtf
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SpotiSaver (no GitHub — globe) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(245,158,11,.1); color: #f59e0b;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">SpotiSaver</div>
|
|
<div class="infra-desc">Tidal hi-fi download endpoints. Hosts two parallel instances (hifi-one, hifi-two) that provide additional redundancy in the 10-API parallel race pool.</div>
|
|
<a class="infra-link" href="https://spotisaver.net" target="_blank">
|
|
spotisaver.net
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- === QOBUZ STREAM APIs === -->
|
|
|
|
<!-- DabMusic (no GitHub — globe) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(139,92,246,.1); color: #8b5cf6;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">DabMusic</div>
|
|
<div class="infra-desc">Primary Qobuz lossless stream API. Provides direct download URLs for FLAC audio at up to 24-bit/192kHz quality. Queried in parallel alongside squid.wtf for fastest response.</div>
|
|
<a class="infra-link" href="https://dabmusic.xyz" target="_blank">
|
|
dabmusic.xyz
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Jumo DL (no GitHub — globe) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(56,189,248,.1); color: #38bdf8;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">Jumo DL</div>
|
|
<div class="infra-desc">Qobuz final fallback. A Cloudflare Pages worker tried after all standard Qobuz APIs fail, with automatic quality downgrade cascade (hi-res → CD → MP3) to maximize success rate.</div>
|
|
<a class="infra-link" href="https://jumo-dl.pages.dev" target="_blank">
|
|
jumo-dl.pages.dev
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- === QOBUZ & DEEZER API (Ruubiiiii) === -->
|
|
|
|
<!-- Ruubiiiii / MusicDL (GitHub) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(255,255,255,.08); color: #e8e8e8;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 .3a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.5-1.4-1.3-1.8-1.3-1.8-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.7-1.6-2.7-.3-5.5-1.3-5.5-6 0-1.3.5-2.3 1.2-3.2-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.4 1.2a11.5 11.5 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.7 1.7.3 2.9.1 3.2.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0012 .3z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">Ruubiiiii</div>
|
|
<div class="infra-desc">Qobuz and Deezer download API provider. Hosts the MusicDL API that powers both Qobuz lossless (up to 24-bit/192kHz) and Deezer FLAC (CD Quality) downloads in SpotiFLAC.</div>
|
|
<a class="infra-link" href="https://github.com/Ruubiiiii" target="_blank">
|
|
Ruubiiiii
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- === AMAZON === -->
|
|
|
|
<!-- AfkarXYZ (GitHub) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(255,255,255,.08); color: #e8e8e8;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 .3a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.5-1.4-1.3-1.8-1.3-1.8-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.7-1.6-2.7-.3-5.5-1.3-5.5-6 0-1.3.5-2.3 1.2-3.2-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.4 1.2a11.5 11.5 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.7 1.7.3 2.9.1 3.2.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0012 .3z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">AfkarXYZ</div>
|
|
<div class="infra-desc">Sole Amazon Music download API with stream decryption support. Also provides a SpotFetch-compatible Spotify metadata proxy used when direct API access is blocked.</div>
|
|
<a class="infra-link" href="https://github.com/afkarxyz" target="_blank">
|
|
afkarxyz
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- === YOUTUBE AUDIO === -->
|
|
|
|
<!-- Cobalt (GitHub) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(255,255,255,.08); color: #e8e8e8;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 .3a12 12 0 00-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.5-1.4-1.3-1.8-1.3-1.8-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.7-1.6-2.7-.3-5.5-1.3-5.5-6 0-1.3.5-2.3 1.2-3.2-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.4 1.2a11.5 11.5 0 016 0c2.3-1.5 3.3-1.2 3.3-1.2.7 1.7.3 2.9.1 3.2.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0012 .3z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">Cobalt</div>
|
|
<div class="infra-desc">Privacy-focused media extraction tool. The core engine behind YouTube Music downloads — accepts a video URL and returns a tunnel URL to the audio stream in opus or mp3 format.</div>
|
|
<a class="infra-link" href="https://github.com/imputnet/cobalt" target="_blank">
|
|
imputnet/cobalt
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Qwkuns (no GitHub — globe) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(16,185,129,.1); color: #10b981;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">Qwkuns</div>
|
|
<div class="infra-desc">Cobalt-compatible API for YouTube audio extraction. Serves as the fallback download engine when the primary SpotubeDL proxy is unavailable, using the standard Cobalt protocol.</div>
|
|
<a class="infra-link" href="https://qwkuns.me" target="_blank">
|
|
qwkuns.me
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SpotubeDL (no GitHub — globe) -->
|
|
<div class="infra-card">
|
|
<div class="infra-icon" style="background: rgba(244,63,94,.1); color: #f43f5e;">
|
|
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
|
|
</div>
|
|
<div class="infra-info">
|
|
<div class="infra-name">SpotubeDL</div>
|
|
<div class="infra-desc">Primary YouTube download proxy. Handles authentication to Cobalt download instances and serves as the first-choice engine for YouTube Music audio extraction.</div>
|
|
<a class="infra-link" href="https://spotubedl.com" target="_blank">
|
|
spotubedl.com
|
|
<svg viewBox="0 0 24 24"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- DISCLAIMER -->
|
|
<div class="disclaimer">
|
|
<p>SpotiFLAC Mobile is not affiliated with, endorsed by, or connected to any of the services listed above. All trademarks and logos belong to their respective owners. This page is meant to acknowledge and appreciate the platforms that make this project possible.</p>
|
|
</div>
|
|
|
|
<!-- SEARCH MODAL -->
|
|
<div class="search-overlay" id="searchOverlay" onclick="if(event.target===this)closeSearch()">
|
|
<div class="search-modal">
|
|
<div class="search-header">
|
|
<svg viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
|
|
<input class="search-input" id="searchInput" type="text" placeholder="Search documentation..." autocomplete="off" spellcheck="false">
|
|
<button class="search-esc" onclick="closeSearch()">Esc</button>
|
|
</div>
|
|
<div class="search-body" id="searchBody">
|
|
<div class="search-empty">Type to search across all documentation sections</div>
|
|
</div>
|
|
<div class="search-footer">
|
|
<span><kbd>↑</kbd> <kbd>↓</kbd> to navigate</span>
|
|
<span><kbd>Enter</kbd> to select</span>
|
|
<span><kbd>Esc</kbd> to close</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FOOTER -->
|
|
<footer>
|
|
<div class="footer-inner">
|
|
<div class="footer-links">
|
|
<a href="index">Home</a>
|
|
<a href="downloads">Downloads</a>
|
|
<a href="docs">Documentation</a>
|
|
<a href="https://github.com/zarzet/SpotiFLAC-Mobile" target="_blank">GitHub</a>
|
|
<a href="https://t.me/spotiflac" target="_blank">Telegram</a>
|
|
<a href="https://ko-fi.com/zarzet" target="_blank">Support / Ko-fi</a>
|
|
</div>
|
|
<p class="footer-copy">SpotiFLAC is for educational and private use only. Not affiliated with any streaming service.</p>
|
|
<p class="footer-copy">© 2026 SpotiFLAC · Open Source · <a href="https://opensource.org/license/mit" target="_blank" style="color:inherit;text-decoration:underline">MIT Licensed</a></p>
|
|
</div>
|
|
</footer>
|
|
|
|
<script>
|
|
function toggleMenu() {
|
|
document.getElementById('mobileMenu').classList.toggle('open');
|
|
document.getElementById('mobileOverlay').classList.toggle('open');
|
|
document.querySelector('.nav-burger').classList.toggle('active');
|
|
}
|
|
document.getElementById('mobileMenu').addEventListener('click', function(e) {
|
|
if (e.target.closest('a')) toggleMenu();
|
|
});
|
|
</script>
|
|
<script>
|
|
/* ── DOCS SEARCH ── */
|
|
(function() {
|
|
var overlay = document.getElementById('searchOverlay');
|
|
var input = document.getElementById('searchInput');
|
|
var body = document.getElementById('searchBody');
|
|
var hintEl = document.getElementById('searchShortcutHint');
|
|
var activeIdx = -1;
|
|
var results = [];
|
|
|
|
if (navigator.platform && navigator.platform.indexOf('Mac') > -1) {
|
|
if (hintEl) hintEl.textContent = '\u2318 K';
|
|
}
|
|
|
|
var searchIndex = [{"id":"table-of-contents","title":"Table of Contents","level":2,"section":"Table of Contents"},{"id":"introduction","title":"Introduction","level":2,"section":"Introduction"},{"id":"requirements","title":"Requirements","level":3,"section":"Introduction"},{"id":"extension-structure","title":"Extension Structure","level":2,"section":"Extension Structure"},{"id":"manifest-file","title":"Manifest File","level":2,"section":"Manifest File"},{"id":"complete-manifest-example","title":"Complete Manifest Example","level":3,"section":"Manifest File"},{"id":"manifest-fields","title":"Manifest Fields","level":3,"section":"Manifest File"},{"id":"quality-options","title":"Quality Options","level":3,"section":"Manifest File"},{"id":"quality-specific-settings","title":"Quality-Specific Settings","level":3,"section":"Manifest File"},{"id":"permissions","title":"Permissions","level":3,"section":"Manifest File"},{"id":"extension-types","title":"Extension Types","level":3,"section":"Manifest File"},{"id":"settings","title":"Settings","level":3,"section":"Manifest File"},{"id":"button-setting-type","title":"Button Setting Type","level":3,"section":"Manifest File"},{"id":"custom-search-behavior","title":"Custom Search Behavior","level":3,"section":"Manifest File"},{"id":"thumbnail-ratio-presets","title":"Thumbnail Ratio Presets","level":4,"section":"Manifest File"},{"id":"custom-url-handler","title":"Custom URL Handler","level":3,"section":"Manifest File"},{"id":"album--playlist-functions-v301","title":"Album & Playlist Functions (v3.0.1+)","level":3,"section":"Manifest File"},{"id":"artist-support","title":"Artist Support","level":3,"section":"Manifest File"},{"id":"home-feed-support","title":"Home Feed Support","level":3,"section":"Manifest File"},{"id":"track-enrichment","title":"Track Enrichment","level":3,"section":"Manifest File"},{"id":"custom-track-matching","title":"Custom Track Matching","level":3,"section":"Manifest File"},{"id":"post-processing-hooks","title":"Post-Processing Hooks","level":3,"section":"Manifest File"},{"id":"post-process-api-v2-recommended","title":"Post-Process API v2 (Recommended)","level":4,"section":"Manifest File"},{"id":"main-script","title":"Main Script","level":2,"section":"Main Script"},{"id":"basic-structure","title":"Basic Structure","level":3,"section":"Main Script"},{"id":"important-registerextension","title":"Important: registerExtension()","level":3,"section":"Main Script"},{"id":"api-reference","title":"API Reference","level":2,"section":"API Reference"},{"id":"http-api","title":"HTTP API","level":3,"section":"API Reference"},{"id":"request-headers","title":"Request Headers","level":4,"section":"API Reference"},{"id":"response-object","title":"Response Object","level":4,"section":"API Reference"},{"id":"form-encoded-post-applicationx-www-form-urlencoded","title":"Form-Encoded POST (application/x-www-form-urlencoded)","level":4,"section":"API Reference"},{"id":"cookie-jar","title":"Cookie Jar","level":4,"section":"API Reference"},{"id":"youtube-music--innertube-api-example","title":"YouTube Music / Innertube API Example","level":4,"section":"API Reference"},{"id":"browser-like-polyfills","title":"Browser-like Polyfills","level":3,"section":"API Reference"},{"id":"fetch-api","title":"fetch() API","level":4,"section":"API Reference"},{"id":"atob--btoa","title":"atob() / btoa()","level":4,"section":"API Reference"},{"id":"textencoder--textdecoder","title":"TextEncoder / TextDecoder","level":4,"section":"API Reference"},{"id":"url--urlsearchparams","title":"URL / URLSearchParams","level":4,"section":"API Reference"},{"id":"porting-browser-libraries","title":"Porting Browser Libraries","level":4,"section":"API Reference"},{"id":"storage-api","title":"Storage API","level":3,"section":"API Reference"},{"id":"file-api","title":"File API","level":3,"section":"API Reference"},{"id":"logging-api","title":"Logging API","level":3,"section":"API Reference"},{"id":"utility-api","title":"Utility API","level":3,"section":"API Reference"},{"id":"hmac-sha1-for-totp","title":"HMAC-SHA1 for TOTP","level":4,"section":"API Reference"},{"id":"hmac-sha256-example-api-signing","title":"HMAC-SHA256 Example (API Signing)","level":4,"section":"API Reference"},{"id":"go-backend-api","title":"Go Backend API","level":3,"section":"API Reference"},{"id":"using-getlocaltime-for-time-based-greeting","title":"Using getLocalTime() for Time-Based Greeting","level":4,"section":"API Reference"},{"id":"using-getlocaltime-for-timezone-in-api-calls","title":"Using getLocalTime() for Timezone in API Calls","level":4,"section":"API Reference"},{"id":"credentials-api-encrypted","title":"Credentials API (Encrypted)","level":3,"section":"API Reference"},{"id":"auth-api-oauth-support","title":"Auth API (OAuth Support)","level":3,"section":"API Reference"},{"id":"pkce-oauth-flow-recommended","title":"PKCE OAuth Flow (Recommended)","level":3,"section":"API Reference"},{"id":"quick-start-high-level-api","title":"Quick Start (High-Level API)","level":4,"section":"API Reference"},{"id":"low-level-api-manual-control","title":"Low-Level API (Manual Control)","level":4,"section":"API Reference"},{"id":"pkce-api-reference","title":"PKCE API Reference","level":4,"section":"API Reference"},{"id":"complete-oauth-example","title":"Complete OAuth Example","level":4,"section":"API Reference"},{"id":"crypto-utilities","title":"Crypto Utilities","level":3,"section":"API Reference"},{"id":"ffmpeg-api-post-processing","title":"FFmpeg API (Post-Processing)","level":3,"section":"API Reference"},{"id":"track-matching-api","title":"Track Matching API","level":3,"section":"API Reference"},{"id":"extension-examples","title":"Extension Examples","level":2,"section":"Extension Examples"},{"id":"example-1-simple-metadata-provider","title":"Example 1: Simple Metadata Provider","level":3,"section":"Extension Examples"},{"id":"example-2-download-provider-with-auth","title":"Example 2: Download Provider with Auth","level":3,"section":"Extension Examples"},{"id":"packaging--distribution","title":"Packaging & Distribution","level":2,"section":"Packaging & Distribution"},{"id":"project-structure","title":"Project Structure","level":3,"section":"Packaging & Distribution"},{"id":"module-system-limitation","title":"Module System Limitation","level":3,"section":"Packaging & Distribution"},{"id":"creating-extension-file","title":"Creating Extension File","level":3,"section":"Packaging & Distribution"},{"id":"installing-extension","title":"Installing Extension","level":3,"section":"Packaging & Distribution"},{"id":"upgrading-extension","title":"Upgrading Extension","level":3,"section":"Packaging & Distribution"},{"id":"troubleshooting","title":"Troubleshooting","level":2,"section":"Troubleshooting"},{"id":"error-extension-did-not-call-registerextension","title":"Error: extension did not call registerExtension()","level":3,"section":"Troubleshooting"},{"id":"error-permission-denied-for-domain-x--network-access-denied","title":"Error: Permission denied for domain X / network access denied","level":3,"section":"Troubleshooting"},{"id":"error-post-body-is-object-object","title":"Error: POST body is [object Object]","level":3,"section":"Troubleshooting"},{"id":"error-function-x-is-not-defined","title":"Error: Function X is not defined","level":3,"section":"Troubleshooting"},{"id":"error-invalid-manifest","title":"Error: Invalid manifest","level":3,"section":"Troubleshooting"},{"id":"extension-doesnt-appear-after-install","title":"Extension doesn't appear after install","level":3,"section":"Troubleshooting"},{"id":"http-request-fails","title":"HTTP request fails","level":3,"section":"Troubleshooting"},{"id":"download-fails","title":"Download fails","level":3,"section":"Troubleshooting"},{"id":"error-file-access-denied-extension-does-not-have-file-permission","title":"Error: file access denied: extension does not have file permission","level":3,"section":"Troubleshooting"},{"id":"error-file-access-denied-absolute-paths-are-not-allowed","title":"Error: file access denied: absolute paths are not allowed","level":3,"section":"Troubleshooting"},{"id":"error-file-access-denied-path-x-is-outside-sandbox","title":"Error: file access denied: path X is outside sandbox","level":3,"section":"Troubleshooting"},{"id":"error-cannot-downgrade-extension","title":"Error: Cannot downgrade extension","level":3,"section":"Troubleshooting"},{"id":"error-extension-is-already-installed","title":"Error: Extension is already installed","level":3,"section":"Troubleshooting"},{"id":"error-timeout-extension-took-too-long-to-respond","title":"Error: timeout: extension took too long to respond","level":3,"section":"Troubleshooting"},{"id":"thumbnails-not-showing-correctly-in-search-results","title":"Thumbnails not showing correctly in search results","level":3,"section":"Troubleshooting"},{"id":"technical-details--behavior","title":"Technical Details & Behavior","level":2,"section":"Technical Details & Behavior"},{"id":"token-refresh-handling","title":"Token Refresh Handling","level":3,"section":"Technical Details & Behavior"},{"id":"storage-limits","title":"Storage Limits","level":3,"section":"Technical Details & Behavior"},{"id":"file-api-path-resolution","title":"File API Path Resolution","level":3,"section":"Technical Details & Behavior"},{"id":"http-redirect-handling","title":"HTTP Redirect Handling","level":3,"section":"Technical Details & Behavior"},{"id":"standard-error-types","title":"Standard Error Types","level":3,"section":"Technical Details & Behavior"},{"id":"http-timeout","title":"HTTP Timeout","level":3,"section":"Technical Details & Behavior"},{"id":"tips--best-practices","title":"Tips & Best Practices","level":2,"section":"Tips & Best Practices"},{"id":"authentication-api","title":"Authentication API","level":2,"section":"Authentication API"},{"id":"auth-api-reference","title":"Auth API Reference","level":3,"section":"Authentication API"},{"id":"credentials-api-encrypted-storage","title":"Credentials API (Encrypted Storage)","level":3,"section":"Authentication API"},{"id":"crypto-utilities-1","title":"Crypto Utilities","level":3,"section":"Authentication API"},{"id":"oauth-flow-example","title":"OAuth Flow Example","level":3,"section":"Authentication API"},{"id":"data-schema-reference","title":"Data Schema Reference","level":2,"section":"Data Schema Reference"},{"id":"track-object","title":"Track Object","level":3,"section":"Data Schema Reference"},{"id":"album-object","title":"Album Object","level":3,"section":"Data Schema Reference"},{"id":"artist-object","title":"Artist Object","level":3,"section":"Data Schema Reference"},{"id":"download-result-object","title":"Download Result Object","level":3,"section":"Data Schema Reference"},{"id":"skip-metadata-enrichment","title":"Skip Metadata Enrichment","level":3,"section":"Data Schema Reference"},{"id":"changelog","title":"Changelog","level":2,"section":"Changelog"},{"id":"support","title":"Support","level":2,"section":"Support"}];
|
|
|
|
function escHtml(s) {
|
|
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
}
|
|
|
|
function highlight(text, query) {
|
|
if (!query) return escHtml(text);
|
|
var escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
var re = new RegExp('(' + escaped + ')', 'gi');
|
|
return escHtml(text).replace(re, '<mark>$1</mark>');
|
|
}
|
|
|
|
function doSearch(query) {
|
|
query = query.trim().toLowerCase();
|
|
if (!query) {
|
|
body.innerHTML = '<div class="search-empty">Type to search across all documentation sections</div>';
|
|
results = []; activeIdx = -1; return;
|
|
}
|
|
var tokens = query.split(/\s+/).filter(Boolean);
|
|
var scored = [];
|
|
searchIndex.forEach(function(item) {
|
|
var titleLow = item.title.toLowerCase();
|
|
var sectionLow = item.section.toLowerCase();
|
|
var score = 0;
|
|
for (var i = 0; i < tokens.length; i++) {
|
|
var t = tokens[i];
|
|
if (titleLow.includes(t)) {
|
|
score += titleLow === t ? 100 : titleLow.startsWith(t) ? 60 : 30;
|
|
} else if (sectionLow.includes(t)) {
|
|
score += 10;
|
|
}
|
|
}
|
|
if (score > 0 && item.level === 2) score += 8;
|
|
if (score > 0 && item.level === 1) score += 15;
|
|
if (score > 0) scored.push({ item: item, score: score });
|
|
});
|
|
scored.sort(function(a, b) { return b.score - a.score; });
|
|
results = scored.slice(0, 20);
|
|
activeIdx = results.length > 0 ? 0 : -1;
|
|
if (!results.length) {
|
|
body.innerHTML = '<div class="search-empty">No results found for "' + escHtml(query) + '"</div>';
|
|
return;
|
|
}
|
|
var hashIcon = '<svg viewBox="0 0 24 24"><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg>';
|
|
var html = '';
|
|
results.forEach(function(r, i) {
|
|
var cls = i === activeIdx ? ' active' : '';
|
|
var sectionHint = r.item.section && r.item.section !== r.item.title ? r.item.section : '';
|
|
html += '<div class="search-item' + cls + '" data-idx="' + i + '" data-id="' + r.item.id + '">' +
|
|
hashIcon +
|
|
'<div class="search-item-text">' +
|
|
'<div class="search-item-title">' + highlight(r.item.title, query) + '</div>' +
|
|
(sectionHint ? '<div class="search-item-section">' + escHtml(sectionHint) + '</div>' : '') +
|
|
'</div>' +
|
|
'<span class="search-enter">\u21B5</span>' +
|
|
'</div>';
|
|
});
|
|
body.innerHTML = html;
|
|
body.querySelectorAll('.search-item').forEach(function(el) {
|
|
el.addEventListener('click', function() { navigateTo(el.dataset.id); });
|
|
el.addEventListener('mouseenter', function() { setActive(parseInt(el.dataset.idx)); });
|
|
});
|
|
}
|
|
|
|
function setActive(idx) {
|
|
if (idx === activeIdx) return;
|
|
var items = body.querySelectorAll('.search-item');
|
|
if (items[activeIdx]) items[activeIdx].classList.remove('active');
|
|
activeIdx = idx;
|
|
if (items[activeIdx]) {
|
|
items[activeIdx].classList.add('active');
|
|
items[activeIdx].scrollIntoView({ block: 'nearest' });
|
|
}
|
|
}
|
|
|
|
function navigateTo(id) {
|
|
closeSearch();
|
|
window.location.href = 'docs#' + id;
|
|
}
|
|
|
|
window.openSearch = function() {
|
|
overlay.classList.add('open');
|
|
document.body.style.overflow = 'hidden';
|
|
input.value = '';
|
|
doSearch('');
|
|
setTimeout(function() { input.focus(); }, 50);
|
|
};
|
|
|
|
window.closeSearch = function() {
|
|
overlay.classList.remove('open');
|
|
document.body.style.overflow = '';
|
|
activeIdx = -1;
|
|
};
|
|
|
|
input.addEventListener('input', function() { doSearch(input.value); });
|
|
|
|
input.addEventListener('keydown', function(e) {
|
|
if (e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
if (results.length) setActive(Math.min(activeIdx + 1, results.length - 1));
|
|
} else if (e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
if (results.length) setActive(Math.max(activeIdx - 1, 0));
|
|
} else if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
if (results[activeIdx]) navigateTo(results[activeIdx].item.id);
|
|
} else if (e.key === 'Escape') {
|
|
e.preventDefault();
|
|
closeSearch();
|
|
}
|
|
});
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
e.preventDefault();
|
|
if (overlay.classList.contains('open')) { closeSearch(); } else { openSearch(); }
|
|
}
|
|
if (e.key === '/' && document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'TEXTAREA') {
|
|
e.preventDefault();
|
|
openSearch();
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|