Compare commits

..

2 Commits

Author SHA1 Message Date
spinline
87ddd3bb93 fix: iOS Dark Mode ve Tema Değişimi Düzeltildi
All checks were successful
Build MIPS Binary / build (push) Successful in 5m17s
- tailwind.config.js: darkMode: 'class' olarak ayarlandı (iOS sistem tercihi yerine .dark class kullanımı için)
- statusbar.rs: Tema değişiminde .dark class ekleme mantığı tüm dark temaları kapsayacak şekilde genişletildi
- index.html: Sayfa yüklenirken .dark class ekleyen inline script güncellendi
- public/tailwind.css: PostCSS ile yeniden derlendi (nesting düzleştirildi + .dark seçiciler eklendi)
2026-02-11 01:00:04 +03:00
spinline
463249982c fix: iOS Safari uyumluluk - CSS nesting düzleştirildi
All checks were successful
Build MIPS Binary / build (push) Successful in 5m16s
- PostCSS tabanlı build'e geçildi (@tailwindcss/postcss + postcss-preset-env)
- CSS native nesting (&) düzleştirilerek eski Safari desteği sağlandı
- iOS 15+ ve Safari 15+ desteği eklendi
2026-02-11 00:54:44 +03:00
8 changed files with 6603 additions and 2830 deletions

1
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules/

View File

@@ -1,101 +1,102 @@
<!doctype html> <!doctype html>
<html> <html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<title>VibeTorrent</title>
<!-- PWA & Mobile Capable --> <head>
<meta name="mobile-web-app-capable" content="yes" /> <meta charset="utf-8" />
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="viewport"
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta name="apple-mobile-web-app-title" content="VibeTorrent" /> <title>VibeTorrent</title>
<meta name="theme-color" content="#111827" />
<link rel="manifest" href="manifest.json" />
<link rel="icon" type="image/png" href="icon-192.png" />
<link rel="apple-touch-icon" href="icon-192.png" />
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
<!-- Trunk Assets --> <!-- PWA & Mobile Capable -->
<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="0" /> <meta name="mobile-web-app-capable" content="yes" />
<link data-trunk rel="css" href="public/tailwind.css" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<link data-trunk rel="copy-file" href="manifest.json" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link data-trunk rel="copy-file" href="icon-192.png" /> <meta name="apple-mobile-web-app-title" content="VibeTorrent" />
<link data-trunk rel="copy-file" href="icon-512.png" /> <meta name="theme-color" content="#111827" />
<link data-trunk rel="copy-file" href="sw.js" /> <link rel="manifest" href="manifest.json" />
<script> <link rel="icon" type="image/png" href="icon-192.png" />
(function () { <link rel="apple-touch-icon" href="icon-192.png" />
var localTheme = localStorage.getItem("vibetorrent_theme"); <link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
var t = localTheme || "dark"; <link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
if (t === "Amoled") t = "black";
if (t === "Light") t = "light";
if (t === "Dark" || t === "Midnight") t = "dark";
var theme = t.toLowerCase(); <!-- Trunk Assets -->
document.documentElement.setAttribute("data-theme", theme); <link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="0" />
if (!localTheme) { <link data-trunk rel="css" href="public/tailwind.css" />
localStorage.setItem("vibetorrent_theme", "dark"); <link data-trunk rel="copy-file" href="manifest.json" />
} <link data-trunk rel="copy-file" href="icon-192.png" />
<link data-trunk rel="copy-file" href="icon-512.png" />
<link data-trunk rel="copy-file" href="sw.js" />
<script>
(function () {
var localTheme = localStorage.getItem("vibetorrent_theme");
var t = localTheme || "dark";
if (t === "Amoled") t = "black";
if (t === "Light") t = "light";
if (t === "Dark" || t === "Midnight") t = "dark";
var meta = document.querySelector('meta[name="theme-color"]'); var theme = t.toLowerCase();
if (meta) { document.documentElement.setAttribute("data-theme", theme);
var colorMap = { // Shadcn dark mode CSS değişkenleri .dark class ile çalışıyor
light: "#ffffff", var darkThemes = ["dark", "black", "night", "coffee", "luxury", "business", "dracula", "halloween", "forest", "synthwave", "dim", "nord", "sunset", "cyberpunk", "abyss"];
cupcake: "#faf7f5", if (darkThemes.indexOf(theme) !== -1) {
bumblebee: "#ffffff", document.documentElement.classList.add("dark");
emerald: "#ffffff", } else {
corporate: "#ffffff", document.documentElement.classList.remove("dark");
synthwave: "#2d1b69", }
retro: "#ece3ca", if (!localTheme) {
cyberpunk: "#ffee00", localStorage.setItem("vibetorrent_theme", "dark");
valentine: "#f0d6e8", }
halloween: "#212121",
garden: "#e9e7e7",
forest: "#171212",
aqua: "#345da7",
lofi: "#ffffff",
pastel: "#ffffff",
fantasy: "#ffffff",
wireframe: "#ffffff",
black: "#000000",
luxury: "#09090b",
dracula: "#282a36",
cmyk: "#ffffff",
autumn: "#8C0327",
business: "#202020",
acid: "#fafafa",
lemonade: "#F1F8E8",
night: "#0f1729",
coffee: "#20161f",
winter: "#ffffff",
dark: "#1d232a",
};
var color = colorMap[theme] || "#1d232a";
meta.setAttribute("content", color);
}
})();
</script>
</head>
<body style="cursor: pointer;"> var meta = document.querySelector('meta[name="theme-color"]');
<div if (meta) {
id="app-loading" var colorMap = {
style=" light: "#ffffff",
cupcake: "#faf7f5",
bumblebee: "#ffffff",
emerald: "#ffffff",
corporate: "#ffffff",
synthwave: "#2d1b69",
retro: "#ece3ca",
cyberpunk: "#ffee00",
valentine: "#f0d6e8",
halloween: "#212121",
garden: "#e9e7e7",
forest: "#171212",
aqua: "#345da7",
lofi: "#ffffff",
pastel: "#ffffff",
fantasy: "#ffffff",
wireframe: "#ffffff",
black: "#000000",
luxury: "#09090b",
dracula: "#282a36",
cmyk: "#ffffff",
autumn: "#8C0327",
business: "#202020",
acid: "#fafafa",
lemonade: "#F1F8E8",
night: "#0f1729",
coffee: "#20161f",
winter: "#ffffff",
dark: "#1d232a",
};
var color = colorMap[theme] || "#1d232a";
meta.setAttribute("content", color);
}
})();
</script>
</head>
<body style="cursor: pointer;">
<div id="app-loading" style="
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100vh; height: 100vh;
font-family: sans-serif; font-family: sans-serif;
" ">
> <div id="app-loading-spinner" style="
<div
id="app-loading-spinner"
style="
width: 40px; width: 40px;
height: 40px; height: 40px;
border: 3px solid currentColor; border: 3px solid currentColor;
@@ -103,21 +104,15 @@
border-radius: 50%; border-radius: 50%;
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
opacity: 0.5; opacity: 0.5;
" "></div>
></div> <div id="app-loading-error" style="display: none; text-align: center; margin-top: 20px; padding: 0 20px">
<div <p style="color: #ef4444; font-weight: bold; margin-bottom: 8px">
id="app-loading-error" Uygulama yüklenemedi
style="display: none; text-align: center; margin-top: 20px; padding: 0 20px" </p>
> <p style="font-size: 14px; opacity: 0.7">
<p style="color: #ef4444; font-weight: bold; margin-bottom: 8px"> Bağlantınız yavaş olabilir veya bir sistem hatası oluşmuş olabilir.
Uygulama yüklenemedi </p>
</p> <button onclick="location.reload()" style="
<p style="font-size: 14px; opacity: 0.7">
Bağlantınız yavaş olabilir veya bir sistem hatası oluşmuş olabilir.
</p>
<button
onclick="location.reload()"
style="
margin-top: 16px; margin-top: 16px;
padding: 8px 16px; padding: 8px 16px;
background: #3b82f6; background: #3b82f6;
@@ -126,104 +121,105 @@
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
font-weight: 500; font-weight: 500;
" ">
> Sayfayı Yenile
Sayfayı Yenile </button>
</button>
</div>
</div> </div>
<style> </div>
@keyframes spin { <style>
to { @keyframes spin {
transform: rotate(360deg); to {
transform: rotate(360deg);
}
}
body.app-loaded #app-loading {
display: none !important;
}
/* iOS Safari Click Fixes */
body {
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
summary {
list-style: none;
}
summary::-webkit-details-marker {
display: none;
}
</style>
<script>
// App loading timeout handler
(function () {
var timeout = setTimeout(function () {
if (!document.body.classList.contains("app-loaded")) {
var spinner = document.getElementById("app-loading-spinner");
var error = document.getElementById("app-loading-error");
if (spinner) spinner.style.display = "none";
if (error) error.style.display = "block";
} }
} }, 15000); // 15 seconds timeout
body.app-loaded #app-loading { // Clean up timeout if app loads
display: none !important; var observer = new MutationObserver(function (mutations) {
} mutations.forEach(function (mutation) {
if (
/* iOS Safari Click Fixes */ mutation.attributeName === "class" &&
body { document.body.classList.contains("app-loaded")
cursor: pointer; ) {
-webkit-tap-highlight-color: transparent; clearTimeout(timeout);
} observer.disconnect();
summary {
list-style: none;
}
summary::-webkit-details-marker {
display: none;
}
</style>
<script>
// App loading timeout handler
(function () {
var timeout = setTimeout(function () {
if (!document.body.classList.contains("app-loaded")) {
var spinner = document.getElementById("app-loading-spinner");
var error = document.getElementById("app-loading-error");
if (spinner) spinner.style.display = "none";
if (error) error.style.display = "block";
}
}, 15000); // 15 seconds timeout
// Clean up timeout if app loads
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (
mutation.attributeName === "class" &&
document.body.classList.contains("app-loaded")
) {
clearTimeout(timeout);
observer.disconnect();
}
});
});
observer.observe(document.body, { attributes: true });
})();
</script>
<!-- Service Worker Registration & PWA Setup -->
<script>
// Global Dropdown Closer for iOS/Mobile
document.addEventListener('click', function(event) {
const details = document.querySelectorAll('details[open]');
details.forEach(detail => {
// Eğer tıklanan yer bu details'in içinde değilse kapat
if (!detail.contains(event.target)) {
detail.removeAttribute('open');
} }
}); });
}, true); // Use capture phase for better mobile support });
observer.observe(document.body, { attributes: true });
})();
</script>
if ("serviceWorker" in navigator) { <!-- Service Worker Registration & PWA Setup -->
window.addEventListener("load", () => { <script>
navigator.serviceWorker // Global Dropdown Closer for iOS/Mobile
.register("/sw.js") document.addEventListener('click', function (event) {
.then((registration) => { const details = document.querySelectorAll('details[open]');
console.log("✅ Service Worker registered:", registration); details.forEach(detail => {
// Eğer tıklanan yer bu details'in içinde değilse kapat
// Request notification permission after a delay (better UX) if (!detail.contains(event.target)) {
setTimeout(() => { detail.removeAttribute('open');
if ("Notification" in window && Notification.permission === "default") { }
// Only request if user hasn't decided yet });
const shouldRequest = localStorage.getItem("vibetorrent_notification_prompt_shown"); }, true); // Use capture phase for better mobile support
if (!shouldRequest) {
Notification.requestPermission().then((permission) => { if ("serviceWorker" in navigator) {
console.log("Notification permission:", permission); window.addEventListener("load", () => {
localStorage.setItem("vibetorrent_notification_prompt_shown", "true"); navigator.serviceWorker
}); .register("/sw.js")
} .then((registration) => {
console.log("✅ Service Worker registered:", registration);
// Request notification permission after a delay (better UX)
setTimeout(() => {
if ("Notification" in window && Notification.permission === "default") {
// Only request if user hasn't decided yet
const shouldRequest = localStorage.getItem("vibetorrent_notification_prompt_shown");
if (!shouldRequest) {
Notification.requestPermission().then((permission) => {
console.log("Notification permission:", permission);
localStorage.setItem("vibetorrent_notification_prompt_shown", "true");
});
} }
}, 3000); // Wait 3 seconds before asking }
}) }, 3000); // Wait 3 seconds before asking
.catch((error) => { })
console.warn("⚠️ Service Worker registration failed:", error); .catch((error) => {
}); console.warn("⚠️ Service Worker registration failed:", error);
}); });
} });
</script> }
</body> </script>
</html> </body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -10,8 +10,11 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.18",
"autoprefixer": "^10.4.23", "autoprefixer": "^10.4.23",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"postcss-preset-env": "^11.1.3",
"tailwindcss": "^4.1.18" "tailwindcss": "^4.1.18"
}, },
"dependencies": { "dependencies": {

View File

@@ -0,0 +1,15 @@
module.exports = {
plugins: {
"@tailwindcss/postcss": {},
"postcss-preset-env": {
features: {
"nesting-rules": true,
},
browsers: [
"last 2 versions",
"iOS >= 15",
"Safari >= 15",
],
},
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -44,7 +44,7 @@ pub fn StatusBar() -> impl IntoView {
if let Some(doc) = document().document_element() { if let Some(doc) = document().document_element() {
let _ = doc.set_attribute("data-theme", &theme); let _ = doc.set_attribute("data-theme", &theme);
// Also set class for Shadcn dark mode support // Also set class for Shadcn dark mode support
if theme == "dark" || theme == "dracula" || theme == "dim" || theme == "abyss" { if theme == "dark" || theme == "dracula" || theme == "dim" || theme == "abyss" || theme == "sunset" || theme == "cyberpunk" || theme == "nord" || theme == "business" || theme == "night" || theme == "black" || theme == "luxury" || theme == "coffee" || theme == "forest" || theme == "halloween" || theme == "synthwave" {
let _ = doc.class_list().add_1("dark"); let _ = doc.class_list().add_1("dark");
} else { } else {
let _ = doc.class_list().remove_1("dark"); let _ = doc.class_list().remove_1("dark");

View File

@@ -9,6 +9,7 @@ const cargoRegistry = path.join(
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
darkMode: "class",
content: [ content: [
"./index.html", "./index.html",
"./src/**/*.{rs,html}", "./src/**/*.{rs,html}",