mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-04 07:06:58 +02:00
chore: move website to spotiflacapp/Website repo
This commit is contained in:
1 parent
ef52332b8b
commit
897388853b
9 files changed
-7914
No files matched your search
-5863
File diff suppressed because it is too large.
Load diff
@@ -1,722 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Downloads - SpotiFLAC Mobile</title>
|
||||
<meta name="description" content="Download the latest version of SpotiFLAC Mobile. Changelog and release history included.">
|
||||
<meta name="theme-color" content="#0a0a0a">
|
||||
<link rel="icon" href="icon.png" type="image/png">
|
||||
|
||||
<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>
|
||||
:root {
|
||||
--green: #1DB954;
|
||||
--green-dim: #1aa34a;
|
||||
--bg: #0a0a0a;
|
||||
--bg-card: #1a1a1a;
|
||||
--bg-card-hover: #222222;
|
||||
--surface: #121212;
|
||||
--text: #e8e8e8;
|
||||
--text-dim: #999;
|
||||
--max-w: 900px;
|
||||
}
|
||||
|
||||
* { 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 {
|
||||
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: 1100px; 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 {
|
||||
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; }
|
||||
|
||||
.latest-hero {
|
||||
max-width: var(--max-w); margin: 0 auto; padding: 0 24px 40px;
|
||||
}
|
||||
.latest-card {
|
||||
background: var(--bg-card-hover); border-radius: 20px;
|
||||
padding: 32px; position: relative; overflow: hidden;
|
||||
}
|
||||
.latest-header {
|
||||
display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 20px;
|
||||
}
|
||||
.latest-tag { font-size: 1.6rem; font-weight: 800; }
|
||||
.latest-badge {
|
||||
font-size: .7rem; font-weight: 700; text-transform: uppercase;
|
||||
padding: 4px 12px; border-radius: 999px;
|
||||
background: var(--green); color: #000;
|
||||
}
|
||||
.latest-date { font-size: .85rem; color: var(--text-dim); margin-left: auto; }
|
||||
.latest-assets {
|
||||
display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 10px; margin-bottom: 24px;
|
||||
}
|
||||
.latest-asset {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 14px 18px; border-radius: 16px;
|
||||
background: rgba(29,185,84,.08);
|
||||
color: var(--text); transition: background .2s; text-decoration: none;
|
||||
}
|
||||
.latest-asset:hover { background: rgba(29,185,84,.15); text-decoration: none; }
|
||||
.latest-asset-icon { color: var(--green); flex-shrink: 0; }
|
||||
.latest-asset-info { min-width: 0; }
|
||||
.latest-asset-name { font-size: .85rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.latest-asset-meta { font-size: .75rem; color: var(--text-dim); }
|
||||
.latest-changelog-toggle {
|
||||
background: var(--bg-card); border: none; border-radius: 16px;
|
||||
color: var(--text-dim); padding: 10px 16px; font-size: .85rem;
|
||||
cursor: pointer; transition: background .2s; width: 100%;
|
||||
font-family: inherit;
|
||||
}
|
||||
.latest-changelog-toggle:hover { background: var(--surface); color: var(--text); }
|
||||
.latest-changelog {
|
||||
display: none; margin-top: 16px; padding-top: 16px;
|
||||
border-top: 1px solid rgba(255,255,255,.06);
|
||||
}
|
||||
.latest-changelog.show { display: block; }
|
||||
|
||||
.older-section {
|
||||
max-width: var(--max-w); margin: 0 auto; padding: 40px 24px 80px;
|
||||
}
|
||||
.older-title {
|
||||
font-size: 1.1rem; font-weight: 600; color: var(--text-dim);
|
||||
margin-bottom: 16px; padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.release-card {
|
||||
background: var(--bg-card); border-radius: 16px;
|
||||
margin-bottom: 8px; transition: background .2s;
|
||||
}
|
||||
.release-card:hover { background: var(--bg-card-hover); }
|
||||
.release-summary {
|
||||
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
|
||||
padding: 16px 20px; cursor: pointer; list-style: none;
|
||||
}
|
||||
.release-summary::-webkit-details-marker { display: none; }
|
||||
.release-tag { font-size: 1rem; font-weight: 700; }
|
||||
.release-badge {
|
||||
font-size: .65rem; font-weight: 700; text-transform: uppercase;
|
||||
padding: 2px 8px; border-radius: 999px;
|
||||
}
|
||||
.release-badge-pre { background: #f59e0b; color: #000; }
|
||||
.release-date { font-size: .8rem; color: var(--text-dim); margin-left: auto; }
|
||||
.release-expand { color: var(--text-dim); font-size: .8rem; transition: transform .2s; }
|
||||
details[open] .release-expand { transform: rotate(180deg); }
|
||||
.release-detail { padding: 0 20px 20px; }
|
||||
.release-assets { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
|
||||
.release-asset {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
padding: 7px 14px; border-radius: 12px; font-size: .82rem; font-weight: 500;
|
||||
background: rgba(29,185,84,.08);
|
||||
color: var(--green); transition: background .2s; text-decoration: none;
|
||||
}
|
||||
.release-asset:hover { background: rgba(29,185,84,.15); text-decoration: none; }
|
||||
.release-asset-size { color: var(--text-dim); font-size: .72rem; }
|
||||
|
||||
.release-body {
|
||||
font-size: .85rem; color: var(--text-dim); line-height: 1.7;
|
||||
max-height: 400px; overflow-y: auto;
|
||||
scrollbar-width: thin; scrollbar-color: #333 transparent;
|
||||
}
|
||||
.release-body h1, .release-body h2, .release-body h3 {
|
||||
color: var(--text); font-size: .95rem; margin: 16px 0 8px;
|
||||
}
|
||||
.release-body h1:first-child, .release-body h2:first-child, .release-body h3:first-child { margin-top: 0; }
|
||||
.release-body ul { padding-left: 20px; margin: 4px 0; }
|
||||
.release-body li { margin: 4px 0; }
|
||||
.release-body code { background: var(--bg-card-hover); padding: 2px 6px; border-radius: 4px; font-size: .8rem; }
|
||||
.release-body a { color: var(--green); }
|
||||
|
||||
footer {
|
||||
background: var(--surface);
|
||||
padding: 40px 24px; text-align: center;
|
||||
}
|
||||
.footer-inner { max-width: 1100px; 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; }
|
||||
|
||||
.loading { text-align: center; color: var(--text-dim); padding: 60px 0; }
|
||||
.loading-spinner {
|
||||
width: 32px; height: 32px; margin: 0 auto 12px;
|
||||
border: 3px solid var(--surface); border-top-color: var(--green);
|
||||
border-radius: 50%; animation: spin .8s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.all-releases-link {
|
||||
display: block; text-align: center; padding: 24px;
|
||||
color: var(--text-dim); font-size: .9rem;
|
||||
}
|
||||
|
||||
.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; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.nav-links { display: none; }
|
||||
.nav-burger { display: flex; }
|
||||
.page-header { padding: 80px 16px 32px; }
|
||||
.latest-hero { padding: 0 16px 32px; }
|
||||
.latest-card { padding: 20px; }
|
||||
.latest-header { flex-direction: column; align-items: flex-start; gap: 6px; }
|
||||
.latest-date { margin-left: 0; }
|
||||
.latest-assets { grid-template-columns: 1fr; }
|
||||
.older-section { padding: 32px 16px 60px; }
|
||||
.release-summary { flex-direction: row; gap: 8px; }
|
||||
.release-date { margin-left: auto; }
|
||||
}
|
||||
|
||||
.icon-svg { width: 20px; height: 20px; fill: currentColor; }
|
||||
|
||||
.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>
|
||||
</head>
|
||||
<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" class="active">Downloads</a></li>
|
||||
<li><a href="index#faq">FAQ</a></li>
|
||||
<li><a href="partners">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>
|
||||
|
||||
<div class="mobile-overlay" id="mobileOverlay" onclick="toggleMenu()"></div>
|
||||
<div class="mobile-menu" id="mobileMenu">
|
||||
<a href="index#features">Features</a>
|
||||
<a href="downloads" class="active">Downloads</a>
|
||||
<a href="index#faq">FAQ</a>
|
||||
<a href="partners">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>Downloads</h1>
|
||||
<p>Latest releases with changelog and direct download links.</p>
|
||||
</div>
|
||||
|
||||
<div class="latest-hero" id="latest-hero">
|
||||
<div class="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
Loading latest release...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="older-section" id="older-section" style="display:none">
|
||||
<div class="older-title">Previous Releases</div>
|
||||
<div id="older-releases"></div>
|
||||
<a class="all-releases-link" href="https://github.com/zarzet/SpotiFLAC-Mobile/releases" target="_blank">
|
||||
View all releases on GitHub →
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<div class="footer-inner">
|
||||
<div class="footer-links">
|
||||
<a href="index">Home</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>
|
||||
(async () => {
|
||||
const REPO = 'zarzet/SpotiFLAC-Mobile';
|
||||
const latestEl = document.getElementById('latest-hero');
|
||||
const olderEl = document.getElementById('older-releases');
|
||||
const olderSection = document.getElementById('older-section');
|
||||
|
||||
try {
|
||||
const res = await fetch(`https://api.github.com/repos/${REPO}/releases?per_page=10`);
|
||||
if (!res.ok) throw new Error(res.status);
|
||||
const releases = await res.json();
|
||||
if (!releases.length) { latestEl.innerHTML = '<p style="text-align:center;color:#999;padding:40px">No releases found.</p>'; return; }
|
||||
|
||||
// Latest release
|
||||
const latest = releases[0];
|
||||
const latestDate = fmtDate(latest.published_at);
|
||||
const latestBody = md(latest.body || '');
|
||||
const latestAssets = (latest.assets || []).filter(a => !a.name.endsWith('.sha256'));
|
||||
|
||||
latestEl.innerHTML = `
|
||||
<div class="latest-card">
|
||||
<div class="latest-header">
|
||||
<span class="latest-tag">${latest.tag_name}</span>
|
||||
<span class="latest-badge">${latest.prerelease ? 'Pre-release' : 'Latest Release'}</span>
|
||||
<span class="latest-date">${latestDate}</span>
|
||||
</div>
|
||||
<div class="latest-assets">
|
||||
${latestAssets.map(a => `
|
||||
<a class="latest-asset" href="${a.browser_download_url}">
|
||||
<svg class="latest-asset-icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
|
||||
</svg>
|
||||
<div class="latest-asset-info">
|
||||
<div class="latest-asset-name">${a.name}</div>
|
||||
<div class="latest-asset-meta">${fmtSize(a.size)} · ${fmtCount(a.download_count)} downloads</div>
|
||||
</div>
|
||||
</a>
|
||||
`).join('')}
|
||||
</div>
|
||||
${latestBody ? `
|
||||
<button class="latest-changelog-toggle" onclick="this.nextElementSibling.classList.toggle('show'); this.textContent = this.nextElementSibling.classList.contains('show') ? 'Hide changelog' : 'Show changelog'">
|
||||
Show changelog
|
||||
</button>
|
||||
<div class="latest-changelog">
|
||||
<div class="release-body">${latestBody}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Older releases
|
||||
const older = releases.slice(1);
|
||||
if (older.length) {
|
||||
olderSection.style.display = '';
|
||||
olderEl.innerHTML = older.map(r => {
|
||||
const date = fmtDate(r.published_at);
|
||||
const body = md(r.body || '');
|
||||
const assets = (r.assets || []).filter(a => !a.name.endsWith('.sha256'));
|
||||
|
||||
return `
|
||||
<details class="release-card">
|
||||
<summary class="release-summary">
|
||||
<span class="release-tag">${r.tag_name}</span>
|
||||
${r.prerelease ? '<span class="release-badge release-badge-pre">Pre-release</span>' : ''}
|
||||
<span class="release-date">${date}</span>
|
||||
<span class="release-expand">▼</span>
|
||||
</summary>
|
||||
<div class="release-detail">
|
||||
${assets.length ? `
|
||||
<div class="release-assets">
|
||||
${assets.map(a => `
|
||||
<a class="release-asset" href="${a.browser_download_url}" target="_blank">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>
|
||||
${a.name}
|
||||
<span class="release-asset-size">${fmtSize(a.size)}</span>
|
||||
</a>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
${body ? `<div class="release-body">${body}</div>` : ''}
|
||||
</div>
|
||||
</details>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
} catch (e) {
|
||||
latestEl.innerHTML = `<p style="text-align:center;color:#999;padding:40px">Failed to load releases. <a href="https://github.com/${REPO}/releases" target="_blank">View on GitHub</a></p>`;
|
||||
}
|
||||
|
||||
function fmtDate(d) { return new Date(d).toLocaleDateString('en-US', { year:'numeric', month:'short', day:'numeric' }); }
|
||||
function fmtSize(b) { return b < 1048576 ? (b/1024).toFixed(1)+' KB' : (b/1048576).toFixed(1)+' MB'; }
|
||||
function fmtCount(n) { return n >= 1000 ? (n/1000).toFixed(1)+'k' : n; }
|
||||
function md(s) {
|
||||
return s
|
||||
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
||||
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
||||
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
||||
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/`(.+?)`/g, '<code>$1</code>')
|
||||
.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank">$1</a>')
|
||||
.replace(/^[-*] (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')
|
||||
.replace(/\n{2,}/g, '<br>')
|
||||
.replace(/(^<ul>)|(<\/ul>$)/g, '')
|
||||
.replace(/(<li>[\s\S]*?<\/li>)(?=\s*<h|$|\s*<br>)/g, '<ul>$1</ul>');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<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>
|
||||
(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":"QuiLine truncated
|
||||
|
||||
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>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 71 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 539 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 811 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 104 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 122 KiB |
-694
@@ -1,694 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SpotiFLAC Mobile - Lossless Music Downloader</title>
|
||||
<meta name="description" content="Mobile music utility built with Flutter and Go. High-quality audio management for your personal library.">
|
||||
<meta name="theme-color" content="#0a0a0a">
|
||||
|
||||
<meta property="og:title" content="SpotiFLAC Mobile">
|
||||
<meta property="og:description" content="Mobile music utility built with Flutter and Go. High-quality audio management for your personal library.">
|
||||
<meta property="og:image" content="icon.png">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
<link rel="icon" href="icon.png" type="image/png">
|
||||
|
||||
<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>
|
||||
:root {
|
||||
--green: #1DB954;
|
||||
--green-dim: #1aa34a;
|
||||
--bg: #0a0a0a; /* surfaceContainerLow */
|
||||
--bg-card: #1a1a1a; /* surfaceContainerHigh */
|
||||
--bg-card-hover: #222222; /* surfaceContainerHighest */
|
||||
--surface: #121212; /* surfaceContainer */
|
||||
--text: #e8e8e8; /* onSurface */
|
||||
--text-dim: #999; /* onSurfaceVariant */
|
||||
--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 {
|
||||
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;
|
||||
}
|
||||
|
||||
.hero {
|
||||
min-height: 100vh;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
text-align: center; padding: 100px 24px 60px;
|
||||
background: radial-gradient(ellipse at 50% 0%, rgba(29,185,84,.05) 0%, transparent 50%);
|
||||
}
|
||||
.hero h1 { font-size: clamp(2.2rem, 5vw, 3.5rem); font-weight: 800; letter-spacing: -1px; margin-bottom: 12px; }
|
||||
.hero h1 span { color: var(--green); }
|
||||
.hero p { font-size: 1.15rem; color: var(--text-dim); max-width: 520px; margin-bottom: 8px; }
|
||||
.hero-badges { display: flex; gap: 8px; justify-content: center; margin: 16px 0 32px; flex-wrap: wrap; }
|
||||
.badge {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
padding: 6px 16px; border-radius: 999px;
|
||||
font-size: .8rem; font-weight: 600;
|
||||
background: var(--surface); color: var(--text-dim);
|
||||
}
|
||||
.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
padding: 12px 28px; border-radius: 16px;
|
||||
font-size: .95rem; font-weight: 600;
|
||||
transition: background .2s; cursor: pointer; border: none;
|
||||
}
|
||||
.btn-primary { background: var(--green); color: #000; }
|
||||
.btn-primary:hover { background: var(--green-dim); text-decoration: none; }
|
||||
.btn-secondary { background: var(--bg-card); color: var(--text); }
|
||||
.btn-secondary:hover { background: var(--bg-card-hover); text-decoration: none; }
|
||||
|
||||
section { padding: 80px 24px; }
|
||||
.section-inner { max-width: var(--max-w); margin: auto; }
|
||||
.section-title { font-size: 1.8rem; font-weight: 700; text-align: center; margin-bottom: 12px; }
|
||||
.section-sub { text-align: center; color: var(--text-dim); max-width: 560px; margin: 0 auto 48px; }
|
||||
|
||||
.features-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.feature-card {
|
||||
background: var(--bg-card); border-radius: 20px;
|
||||
padding: 28px 24px; transition: background .2s;
|
||||
}
|
||||
.feature-card:hover { background: var(--bg-card-hover); }
|
||||
.feature-icon {
|
||||
width: 40px; height: 40px; border-radius: 12px;
|
||||
background: rgba(29,185,84,.12); color: var(--green);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
margin-bottom: 16px; font-size: 1.2rem;
|
||||
}
|
||||
.feature-card h3 { font-size: 1.05rem; margin-bottom: 6px; }
|
||||
.feature-card p { color: var(--text-dim); font-size: .9rem; }
|
||||
|
||||
.faq-list { max-width: 700px; margin: auto; display: flex; flex-direction: column; gap: 8px; }
|
||||
.faq-item {
|
||||
background: var(--bg-card); border-radius: 16px;
|
||||
}
|
||||
.faq-item summary {
|
||||
cursor: pointer; font-weight: 600; font-size: 1rem;
|
||||
list-style: none; display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 18px 20px;
|
||||
}
|
||||
.faq-item summary::-webkit-details-marker { display: none; }
|
||||
.faq-item summary::after { content: "+"; font-size: 1.4rem; color: var(--text-dim); transition: transform .2s; }
|
||||
.faq-item[open] summary::after { content: "\2212"; }
|
||||
.faq-item .faq-answer { padding: 0 20px 18px; color: var(--text-dim); font-size: .92rem; line-height: 1.7; }
|
||||
|
||||
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; }
|
||||
|
||||
.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; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.nav-links { display: none; }
|
||||
.nav-burger { display: flex; }
|
||||
.hero { padding: 80px 16px 40px; }
|
||||
section { padding: 60px 16px; }
|
||||
}
|
||||
|
||||
.hero-mockups {
|
||||
display: flex; gap: 20px; justify-content: center; align-items: flex-end;
|
||||
margin-top: 48px; perspective: 800px;
|
||||
}
|
||||
.phone-frame {
|
||||
width: 180px; border-radius: 28px; overflow: hidden;
|
||||
border: 3px solid #333; background: #000;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,.5);
|
||||
transition: transform .3s;
|
||||
}
|
||||
.phone-frame:hover { transform: translateY(-4px); }
|
||||
.phone-frame img { width: 100%; display: block; }
|
||||
.phone-frame.phone-center {
|
||||
width: 210px;
|
||||
border-color: var(--green);
|
||||
box-shadow: 0 24px 70px rgba(0,0,0,.6), 0 0 40px rgba(29,185,84,.1);
|
||||
}
|
||||
.phone-frame.phone-side { opacity: .7; }
|
||||
@media (max-width: 640px) {
|
||||
.hero-mockups { gap: 10px; margin-top: 32px; }
|
||||
.phone-frame { width: 120px; border-radius: 20px; border-width: 2px; }
|
||||
.phone-frame.phone-center { width: 150px; }
|
||||
}
|
||||
@media (max-width: 420px) {
|
||||
.phone-frame.phone-side { display: none; }
|
||||
.phone-frame.phone-center { width: 200px; }
|
||||
}
|
||||
|
||||
.icon-svg { width: 20px; height: 20px; fill: currentColor; }
|
||||
|
||||
.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>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<div class="nav-inner">
|
||||
<a class="nav-brand" href="#">
|
||||
<img src="icon.png" alt="SpotiFLAC">
|
||||
SpotiFLAC Mobile
|
||||
</a>
|
||||
<ul class="nav-links">
|
||||
<li><a href="#features">Features</a></li>
|
||||
<li><a href="downloads">Downloads</a></li>
|
||||
<li><a href="#faq">FAQ</a></li>
|
||||
<li><a href="partners">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>
|
||||
|
||||
<div class="mobile-overlay" id="mobileOverlay" onclick="toggleMenu()"></div>
|
||||
<div class="mobile-menu" id="mobileMenu">
|
||||
<a href="#features">Features</a>
|
||||
<a href="downloads">Downloads</a>
|
||||
<a href="#faq">FAQ</a>
|
||||
<a href="partners">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>
|
||||
|
||||
<section class="hero">
|
||||
<h1>Spoti<span>FLAC</span> Mobile</h1>
|
||||
<p>Mobile music utility built with Flutter and Go. High-quality audio management for your personal library.</p>
|
||||
<div class="hero-badges">
|
||||
<span class="badge">
|
||||
<svg class="icon-svg" viewBox="0 0 24 24"><path d="M6 18c0 .55.45 1 1 1h1v3.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5V19h2v3.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5V19h1c.55 0 1-.45 1-1V7H6v11zM3.5 7C2.67 7 2 7.67 2 8.5v7c0 .83.67 1.5 1.5 1.5S5 16.33 5 15.5v-7C5 7.67 4.33 7 3.5 7zm17 0c-.83 0-1.5.67-1.5 1.5v7c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5v-7c0-.83-.67-1.5-1.5-1.5zm-4.97-5.84l1.3-1.3c.2-.2.2-.51 0-.71-.2-.2-.51-.2-.71 0l-1.48 1.48A5.84 5.84 0 0012 0c-.96 0-1.86.23-2.66.63L7.85.15c-.2-.2-.51-.2-.71 0-.2.2-.2.51 0 .71l1.31 1.31A5.983 5.983 0 006 6h12c0-2.21-1.2-4.15-2.97-5.18-.25-.14-.4-.24-.5-.36v-.3zM10 4H9V3h1v1zm5 0h-1V3h1v1z"/></svg>
|
||||
Android 7.0+
|
||||
</span>
|
||||
<span class="badge">
|
||||
<svg class="icon-svg" viewBox="0 0 24 24"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
|
||||
iOS 14.0+
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<div class="hero-actions">
|
||||
<a class="btn btn-primary" href="downloads">
|
||||
<svg class="icon-svg" viewBox="0 0 24 24"><path fill="#000" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>
|
||||
Download
|
||||
</a>
|
||||
<a class="btn btn-secondary" href="docs">
|
||||
<svg class="icon-svg" viewBox="0 0 24 24"><path fill="currentColor" d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></svg>
|
||||
Docs
|
||||
</a>
|
||||
</div>
|
||||
<div class="hero-mockups">
|
||||
<div class="phone-frame phone-side"><img src="images/2.jpg" alt="Search" loading="lazy"></div>
|
||||
<div class="phone-frame phone-center"><img src="images/1.jpg" alt="Home screen" loading="lazy"></div>
|
||||
<div class="phone-frame phone-side"><img src="images/3.jpg" alt="History" loading="lazy"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="features">
|
||||
<div class="section-inner">
|
||||
<h2 class="section-title">Features</h2>
|
||||
<p class="section-sub">Everything you need to build a high-quality music library on your phone.</p>
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<svg class="icon-svg" viewBox="0 0 24 24"><path fill="currentColor" d="M12 3v10.55c-.59-.34-1.27-.55-2-.55C7.79 13 6 14.79 6 17s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></svg>
|
||||
</div>
|
||||
<h3>True Lossless FLAC</h3>
|
||||
<p>Download in up to 24-bit/192kHz quality. No transcoding, no quality loss. Pure studio-grade audio files.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<svg class="icon-svg" viewBox="0 0 24 24"><path fill="currentColor" 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>
|
||||
<h3>Multiple Providers</h3>
|
||||
<p>Download from Tidal, Qobuz, Deezer, and more via extensions. Automatic fallback if a source is unavailable.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<svg class="icon-svg" viewBox="0 0 24 24"><path fill="currentColor" d="M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5a2.5 2.5 0 00-5 0V5H4c-1.1 0-2 .9-2 2v3.8h1.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7s2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5a2.5 2.5 0 000-5z"/></svg>
|
||||
</div>
|
||||
<h3>Extensions</h3>
|
||||
<p>Community-built extensions add new music sources and features. Install from the built-in Store with one tap.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<svg class="icon-svg" viewBox="0 0 24 24"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27A6.47 6.47 0 0016 9.5 6.5 6.5 0 109.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>
|
||||
</div>
|
||||
<h3>Search by Link or Name</h3>
|
||||
<p>Paste a Spotify, Tidal, Qobuz, or Deezer link. Or just search by song name — it handles the rest.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<svg class="icon-svg" viewBox="0 0 24 24"><path fill="currentColor" d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H8V4h12v12zm-6-1l-4-4.8h3V5h2v4.2h3L14 14z"/></svg>
|
||||
</div>
|
||||
<h3>Batch & Playlist Download</h3>
|
||||
<p>Download entire albums and playlists at once. Smart queue management that downloads one track at a time.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<svg class="icon-svg" viewBox="0 0 24 24"><path fill="currentColor" d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM5 15h14v3H5z"/></svg>
|
||||
</div>
|
||||
<h3>Rich Metadata</h3>
|
||||
<p>Full metadata embedding — album art, lyrics, genre, label, copyright, and more. All embedded in the FLAC file.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section id="faq">
|
||||
<div class="section-inner">
|
||||
<h2 class="section-title">FAQ</h2>
|
||||
<p class="section-sub">Common questions about SpotiFLAC Mobile.</p>
|
||||
<div class="faq-list">
|
||||
<details class="faq-item">
|
||||
<summary>Why is my download failing with "Song not found"?</summary>
|
||||
<div class="faq-answer">The track may not be available on the streaming services. Try enabling more download services in Settings > Download > Provider Priority, or install additional extensions like Amazon Music from the Store.</div>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary>Why are some tracks downloading in lower quality?</summary>
|
||||
<div class="faq-answer">Quality depends on what's available from the streaming service and extensions. Built-in providers: Tidal offers up to 24-bit/192kHz, Qobuz up to 24-bit/192kHz, and Deezer up to 16-bit/44.1kHz.</div>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary>Can I download entire playlists?</summary>
|
||||
<div class="faq-answer">Yes! Just paste the playlist URL in the search bar. The app will fetch all tracks and queue them for download.</div>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary>Why do I need to grant storage permission?</summary>
|
||||
<div class="faq-answer">The app needs permission to save downloaded files to your device. On Android 13+, you may need to grant "All files access" in Settings > Apps > SpotiFLAC > Permissions.</div>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary>Is this app safe?</summary>
|
||||
<div class="faq-answer">Yes, the app is fully open source. You can verify the code yourself on GitHub. Each release is scanned with VirusTotal.</div>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary>Download not working in my country?</summary>
|
||||
<div class="faq-answer">Some countries have restricted access to certain streaming service APIs. If downloads are failing, try using a VPN to connect through a different region.</div>
|
||||
</details>
|
||||
<details class="faq-item">
|
||||
<summary>How do I create my own extension?</summary>
|
||||
<div class="faq-answer">Check out the <a href="docs">Extension Development Guide</a> for complete documentation on building custom extensions.</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
<div class="footer-inner">
|
||||
<div class="footer-links">
|
||||
<a href="https://github.com/zarzet/SpotiFLAC-Mobile/releases" target="_blank">Download</a>
|
||||
<a href="docs">Documentation</a>
|
||||
<a href="https://github.com/zarzet/SpotiFLAC-Mobile" target="_blank">GitHub</a>
|
||||
<a href="https://github.com/afkarxyz/SpotiFLAC" target="_blank">Desktop Version</a>
|
||||
<a href="https://t.me/spotiflac" target="_blank">Telegram Channel</a>
|
||||
<a href="https://t.me/spotiflac_chat" target="_blank">Community</a>
|
||||
<a href="https://ko-fi.com/zarzet" target="_blank">Support / Ko-fi</a>
|
||||
<a href="https://crowdin.com/project/spotiflac-mobile" target="_blank">Help Translate</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>
|
||||
(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":"QuiLine truncated
|
||||
|
||||
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>
|
||||
@@ -1,635 +0,0 @@
|
||||
<!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">
|
||||
|
||||
<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>
|
||||
: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 {
|
||||
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 {
|
||||
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; }
|
||||
|
||||
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-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 {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
.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; }
|
||||
|
||||
@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-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>
|
||||
|
||||
<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>
|
||||
|
||||
<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">
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
<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 Mobile.</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>
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
(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":"QuiLine truncated
|
||||
|
||||
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>
|
||||
Reference in new issue
Block a user