Added image modal
This commit is contained in:
@@ -11,30 +11,38 @@
|
|||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
|
|
||||||
let menuOpen = false;
|
let menuOpen = false;
|
||||||
|
|
||||||
|
// Menü aç/kapat (hamburger butonuyla)
|
||||||
const toggleMenu = () => {
|
const toggleMenu = () => {
|
||||||
menuOpen = !menuOpen;
|
menuOpen = !menuOpen;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 🔹 Sidebar'ı kapatma fonksiyonu
|
||||||
|
function closeSidebar() {
|
||||||
|
menuOpen = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if token}
|
{#if token}
|
||||||
<Router>
|
<Router>
|
||||||
<div class="app">
|
<div class="app">
|
||||||
<Sidebar {menuOpen} />
|
<!-- Sidebar -->
|
||||||
|
<Sidebar {menuOpen} on:closeMenu={closeSidebar} />
|
||||||
|
|
||||||
|
<!-- İçerik -->
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<Topbar on:toggleMenu={toggleMenu} />
|
<Topbar on:toggleMenu={toggleMenu} />
|
||||||
|
|
||||||
<Route path="/" component={Files} />
|
<Route path="/" component={Files} />
|
||||||
<Route path="/files" component={Files} />
|
<Route path="/files" component={Files} />
|
||||||
<Route path="/transfers" component={Transfers} />
|
<Route path="/transfers" component={Transfers} />
|
||||||
<Route path="/sharing" component={Sharing} />
|
<Route path="/sharing" component={Sharing} />
|
||||||
<Route path="/trash" component={Trash} />
|
<Route path="/trash" component={Trash} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar dışına tıklayınca kapanma -->
|
||||||
{#if menuOpen}
|
{#if menuOpen}
|
||||||
<div
|
<div class="backdrop show" on:click={closeSidebar}></div>
|
||||||
class="backdrop show"
|
|
||||||
on:click={() => {
|
|
||||||
menuOpen = false;
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@@ -1,22 +1,66 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Link } from "svelte-routing";
|
import { Link } from "svelte-routing";
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
export let menuOpen = false;
|
export let menuOpen = false;
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
// Menü öğesine tıklanınca sidebar'ı kapat
|
||||||
|
function handleLinkClick() {
|
||||||
|
dispatch("closeMenu");
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="sidebar" class:open={menuOpen}>
|
<div class="sidebar" class:open={menuOpen}>
|
||||||
<div class="logo">du.pe</div>
|
<div class="logo">du.pe</div>
|
||||||
|
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<Link to="/" class="item" getProps={({ isCurrent }) => ({ class: isCurrent ? "item active" : "item" })}>
|
<Link
|
||||||
<i class="fa-solid fa-folder icon"></i> Files
|
to="/"
|
||||||
|
class="item"
|
||||||
|
getProps={({ isCurrent }) => ({
|
||||||
|
class: isCurrent ? "item active" : "item",
|
||||||
|
})}
|
||||||
|
on:click={handleLinkClick}
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-folder icon"></i>
|
||||||
|
Files
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/transfers" class="item" getProps={({ isCurrent }) => ({ class: isCurrent ? "item active" : "item" })}>
|
|
||||||
<i class="fa-solid fa-arrow-down icon"></i> Transfers
|
<Link
|
||||||
|
to="/transfers"
|
||||||
|
class="item"
|
||||||
|
getProps={({ isCurrent }) => ({
|
||||||
|
class: isCurrent ? "item active" : "item",
|
||||||
|
})}
|
||||||
|
on:click={handleLinkClick}
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-arrow-down icon"></i>
|
||||||
|
Transfers
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/sharing" class="item" getProps={({ isCurrent }) => ({ class: isCurrent ? "item active" : "item" })}>
|
|
||||||
<i class="fa-solid fa-share-nodes icon"></i> Sharing
|
<Link
|
||||||
|
to="/sharing"
|
||||||
|
class="item"
|
||||||
|
getProps={({ isCurrent }) => ({
|
||||||
|
class: isCurrent ? "item active" : "item",
|
||||||
|
})}
|
||||||
|
on:click={handleLinkClick}
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-share-nodes icon"></i>
|
||||||
|
Sharing
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/trash" class="item" getProps={({ isCurrent }) => ({ class: isCurrent ? "item active" : "item" })}>
|
|
||||||
<i class="fa-solid fa-trash icon"></i> Trash
|
<Link
|
||||||
|
to="/trash"
|
||||||
|
class="item"
|
||||||
|
getProps={({ isCurrent }) => ({
|
||||||
|
class: isCurrent ? "item active" : "item",
|
||||||
|
})}
|
||||||
|
on:click={handleLinkClick}
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-trash icon"></i>
|
||||||
|
Trash
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
let search = "";
|
let search = "";
|
||||||
export let placeholder = "Search files...";
|
export let placeholder = "Search files...";
|
||||||
|
|
||||||
const onToggle = () => dispatch("toggleMenu");
|
const onToggle = () => dispatch("toggleMenu");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<!-- Mobilde görünen hamburger -->
|
<!-- 🔹 Hamburger butonu sadece küçük ekranlarda gösterilir -->
|
||||||
<button class="menu-toggle" on:click={onToggle} aria-label="Toggle menu">
|
<button
|
||||||
|
class="menu-toggle"
|
||||||
|
on:click={onToggle}
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
>
|
||||||
<i class="fa-solid fa-bars"></i>
|
<i class="fa-solid fa-bars"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -18,3 +23,51 @@
|
|||||||
<input placeholder={placeholder} bind:value={search} />
|
<input placeholder={placeholder} bind:value={search} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.topbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: #f8f8f8;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search input {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 🟡 Hamburger sadece küçük ekranlarda görünsün */
|
||||||
|
.menu-toggle {
|
||||||
|
display: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.menu-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount, tick } from "svelte";
|
||||||
import { API, apiFetch } from "../utils/api.js";
|
import { API, apiFetch } from "../utils/api.js";
|
||||||
import { cleanFileName } from "../utils/filename.js";
|
import { cleanFileName } from "../utils/filename.js";
|
||||||
|
|
||||||
@@ -16,6 +16,10 @@
|
|||||||
let currentTime = 0;
|
let currentTime = 0;
|
||||||
let duration = 0;
|
let duration = 0;
|
||||||
let volume = 1;
|
let volume = 1;
|
||||||
|
let currentIndex;
|
||||||
|
|
||||||
|
let showImageModal = false;
|
||||||
|
let selectedImage = null;
|
||||||
|
|
||||||
// ✅ REACTIVE: selectedVideo güvenli kullanımlar
|
// ✅ REACTIVE: selectedVideo güvenli kullanımlar
|
||||||
$: selectedName = selectedVideo?.name ?? "";
|
$: selectedName = selectedVideo?.name ?? "";
|
||||||
@@ -42,23 +46,81 @@
|
|||||||
return (bytes / 1e9).toFixed(2) + " GB";
|
return (bytes / 1e9).toFixed(2) + " GB";
|
||||||
}
|
}
|
||||||
|
|
||||||
function openModal(f) {
|
async function openModal(f) {
|
||||||
selectedVideo = f;
|
stopCurrentVideo();
|
||||||
showModal = true;
|
videoEl = null;
|
||||||
|
isPlaying = false;
|
||||||
|
currentTime = 0;
|
||||||
|
duration = 0;
|
||||||
|
subtitleURL = null; // ← eklendi
|
||||||
|
|
||||||
|
const index = files.findIndex((file) => file.name === f.name);
|
||||||
|
currentIndex = index;
|
||||||
|
|
||||||
|
if (f.type?.startsWith("video/")) {
|
||||||
|
selectedImage = null;
|
||||||
|
showImageModal = false;
|
||||||
|
selectedVideo = f;
|
||||||
|
await tick(); // DOM güncellensin
|
||||||
|
showModal = true; // video {#key} ile yeniden mount edilecek
|
||||||
|
} else if (f.type?.startsWith("image/")) {
|
||||||
|
selectedVideo = null;
|
||||||
|
showModal = false;
|
||||||
|
selectedImage = f;
|
||||||
|
await tick();
|
||||||
|
showImageModal = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopCurrentVideo() {
|
||||||
|
if (videoEl) {
|
||||||
|
try {
|
||||||
|
videoEl.pause();
|
||||||
|
videoEl.src = "";
|
||||||
|
videoEl.load();
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Video stop error:", err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showNext() {
|
||||||
|
if (files.length === 0) return;
|
||||||
|
stopCurrentVideo();
|
||||||
|
currentIndex = (currentIndex + 1) % files.length;
|
||||||
|
await openModal(files[currentIndex]); // ← await
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showPrev() {
|
||||||
|
if (files.length === 0) return;
|
||||||
|
stopCurrentVideo();
|
||||||
|
currentIndex = (currentIndex - 1 + files.length) % files.length;
|
||||||
|
await openModal(files[currentIndex]); // ← await
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
|
stopCurrentVideo(); // 🔴 video tamamen durur
|
||||||
showModal = false;
|
showModal = false;
|
||||||
selectedVideo = null;
|
selectedVideo = null;
|
||||||
subtitleURL = null;
|
subtitleURL = null;
|
||||||
|
isPlaying = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🎞️ Video kontrolleri
|
// 🎞️ Video kontrolleri
|
||||||
function togglePlay() {
|
async function togglePlay() {
|
||||||
if (!videoEl) return;
|
if (!videoEl) return;
|
||||||
if (isPlaying) videoEl.pause();
|
if (videoEl.paused) {
|
||||||
else videoEl.play();
|
try {
|
||||||
isPlaying = !isPlaying;
|
await videoEl.play();
|
||||||
|
isPlaying = true;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Play rejected:", err?.message || err);
|
||||||
|
isPlaying = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
videoEl.pause();
|
||||||
|
isPlaying = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateProgress() {
|
function updateProgress() {
|
||||||
@@ -126,19 +188,23 @@
|
|||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEsc(e) {
|
|
||||||
if (e.key === "Escape" && showModal) closeModal();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
loadFiles();
|
loadFiles();
|
||||||
const slider = document.querySelector(".volume-slider");
|
// ✅ Tek event handler içinde hem Esc hem ok tuşlarını kontrol et
|
||||||
if (slider) {
|
function handleKey(e) {
|
||||||
slider.value = volume;
|
if (e.key === "Escape") {
|
||||||
slider.style.setProperty("--fill", slider.value * 100);
|
if (showModal) closeModal();
|
||||||
|
if (showImageModal) showImageModal = false;
|
||||||
|
} else if (showModal || showImageModal) {
|
||||||
|
if (e.key === "ArrowRight") showNext();
|
||||||
|
if (e.key === "ArrowLeft") showPrev();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
window.addEventListener("keydown", onEsc);
|
|
||||||
return () => window.removeEventListener("keydown", onEsc);
|
window.addEventListener("keydown", handleKey);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", handleKey);
|
||||||
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -153,9 +219,15 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="gallery">
|
<div class="gallery">
|
||||||
{#each files as f}
|
{#each files as f}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div class="media-card" on:click={() => openModal(f)}>
|
<div class="media-card" on:click={() => openModal(f)}>
|
||||||
{#if f.thumbnail}
|
{#if f.thumbnail}
|
||||||
<img src={`${API}${f.thumbnail}`} alt={f.name} class="thumb" />
|
<img
|
||||||
|
src={`${API}${f.thumbnail}?token=${localStorage.getItem("token")}`}
|
||||||
|
alt={f.name}
|
||||||
|
class="thumb"
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="thumb placeholder">
|
<div class="thumb placeholder">
|
||||||
<i class="fa-regular fa-image"></i>
|
<i class="fa-regular fa-image"></i>
|
||||||
@@ -165,6 +237,13 @@
|
|||||||
<div class="name">{cleanFileName(f.name)}</div>
|
<div class="name">{cleanFileName(f.name)}</div>
|
||||||
<div class="size">{formatSize(f.size)}</div>
|
<div class="size">{formatSize(f.size)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="media-type-icon">
|
||||||
|
{#if f.type?.startsWith("video/")}
|
||||||
|
<i class="fa-solid fa-film"></i>
|
||||||
|
{:else if f.type?.startsWith("image/")}
|
||||||
|
<i class="fa-solid fa-image"></i>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -172,39 +251,69 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{#if showModal && selectedVideo}
|
{#if showModal && selectedVideo}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="modal-overlay" on:click={closeModal}>
|
<div class="modal-overlay" on:click={closeModal}>
|
||||||
|
<button class="global-close-btn" on:click|stopPropagation={closeModal}
|
||||||
|
>✕</button
|
||||||
|
>
|
||||||
|
<button class="nav-btn left" on:click|stopPropagation={showPrev}>
|
||||||
|
<i class="fa-solid fa-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
<button class="nav-btn right" on:click|stopPropagation={showNext}>
|
||||||
|
<i class="fa-solid fa-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div class="modal-content" on:click|stopPropagation>
|
<div class="modal-content" on:click|stopPropagation>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<div class="video-title">{selectedName}</div>
|
<div class="video-title">{cleanFileName(selectedName)}</div>
|
||||||
<button class="close-btn" on:click={closeModal}>✕</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="custom-player">
|
<div class="custom-player">
|
||||||
<!-- ✅ selectedVideo yokken boş src -->
|
<!-- ✅ selectedVideo yokken boş src -->
|
||||||
<video
|
<!-- svelte-ignore a11y-media-has-caption -->
|
||||||
bind:this={videoEl}
|
{#key encName}
|
||||||
src={getVideoURL()}
|
<!-- svelte-ignore a11y-media-has-caption -->
|
||||||
class="video-element"
|
<video
|
||||||
on:timeupdate={updateProgress}
|
bind:this={videoEl}
|
||||||
on:loadedmetadata={() => {
|
src={getVideoURL()}
|
||||||
updateDuration();
|
class="video-element"
|
||||||
const slider = document.querySelector(".volume-slider");
|
playsinline
|
||||||
if (slider) {
|
on:timeupdate={updateProgress}
|
||||||
slider.value = volume;
|
on:loadedmetadata={async () => {
|
||||||
slider.style.setProperty("--fill", slider.value * 100);
|
// her yeni videoda state’i sıfırla
|
||||||
}
|
isPlaying = false;
|
||||||
}}
|
currentTime = 0;
|
||||||
>
|
updateDuration();
|
||||||
{#if subtitleURL}
|
|
||||||
<track
|
const slider = document.querySelector(".volume-slider");
|
||||||
kind="subtitles"
|
if (slider) {
|
||||||
src={subtitleURL}
|
slider.value = volume;
|
||||||
srclang={subtitleLang}
|
slider.style.setProperty("--fill", slider.value * 100);
|
||||||
label={subtitleLabel}
|
}
|
||||||
default
|
|
||||||
/>
|
// 🎬 Otomatik oynatma (tarayıcı izin verirse)
|
||||||
{/if}
|
try {
|
||||||
</video>
|
await videoEl.play();
|
||||||
|
isPlaying = true;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Autoplay engellendi:", err?.message || err);
|
||||||
|
isPlaying = false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
on:ended={() => (isPlaying = false)}
|
||||||
|
>
|
||||||
|
{#if subtitleURL}
|
||||||
|
<track
|
||||||
|
kind="subtitles"
|
||||||
|
src={subtitleURL}
|
||||||
|
srclang={subtitleLang}
|
||||||
|
label={subtitleLabel}
|
||||||
|
default
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</video>
|
||||||
|
{/key}
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="top-controls">
|
<div class="top-controls">
|
||||||
@@ -272,6 +381,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if showImageModal && selectedImage}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<div class="image-modal-overlay" on:click={() => (showImageModal = false)}>
|
||||||
|
<button
|
||||||
|
class="image-close-btn"
|
||||||
|
on:click|stopPropagation={() => (showImageModal = false)}>✕</button
|
||||||
|
>
|
||||||
|
<button class="nav-btn left" on:click|stopPropagation={showPrev}>
|
||||||
|
<i class="fa-solid fa-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
<button class="nav-btn right" on:click|stopPropagation={showNext}>
|
||||||
|
<i class="fa-solid fa-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<div class="image-modal-content" on:click|stopPropagation>
|
||||||
|
<img
|
||||||
|
src={`${API}${selectedImage.url}?token=${localStorage.getItem("token")}`}
|
||||||
|
alt={selectedImage.name}
|
||||||
|
class="image-modal-img"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* === GALERİ === */
|
/* === GALERİ === */
|
||||||
@@ -330,246 +464,67 @@
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === MODAL & PLAYER (Transfers.svelte ile birebir) === */
|
.nav-btn {
|
||||||
.modal-overlay {
|
position: absolute;
|
||||||
position: fixed;
|
top: 50%;
|
||||||
inset: 0;
|
transform: translateY(-50%);
|
||||||
backdrop-filter: blur(10px);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
background: rgba(0, 0, 0, 0.8);
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-size: 28px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2100;
|
||||||
|
width: 50px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 999;
|
transition:
|
||||||
|
background 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.nav-btn:hover {
|
||||||
width: 70%;
|
background: rgba(255, 255, 255, 0.2);
|
||||||
height: 70%;
|
transform: translateY(-50%) scale(1.05);
|
||||||
background: #1a1a1a;
|
|
||||||
border-radius: 12px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
.nav-btn.left {
|
||||||
display: flex;
|
left: 15px;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
background: #2a2a2a;
|
|
||||||
padding: 10px 16px;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-title {
|
.nav-btn.right {
|
||||||
flex: 1;
|
right: 15px;
|
||||||
text-align: center;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-btn {
|
.media-card {
|
||||||
background: transparent;
|
position: relative; /* ikonun pozisyonlanması için gerekli */
|
||||||
border: none;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-player {
|
.media-type-icon {
|
||||||
flex: 1;
|
position: absolute;
|
||||||
display: flex;
|
bottom: 6px;
|
||||||
flex-direction: column;
|
right: 8px;
|
||||||
justify-content: space-between;
|
color: rgba(0, 0, 0, 0.45); /* sönük gri ton */
|
||||||
background: #000;
|
font-size: 14px;
|
||||||
|
pointer-events: none; /* tıklamayı engelle */
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-element {
|
.media-type-icon i {
|
||||||
flex: 1;
|
filter: drop-shadow(0 1px 1px rgba(255, 255, 255, 0.3));
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
background: #000;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-element:focus {
|
|
||||||
outline: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === Kontroller === */
|
|
||||||
.controls {
|
|
||||||
background: #1c1c1c;
|
|
||||||
padding: 10px 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
border-top: 1px solid #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-controls {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-btn:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === Ses Kaydırıcısı === */
|
|
||||||
.volume-slider {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
width: 100px;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: linear-gradient(
|
|
||||||
to right,
|
|
||||||
#ff3b30 calc(var(--fill, 100%) * 1%),
|
|
||||||
rgba(255, 255, 255, 0.3) calc(var(--fill, 100%) * 1%)
|
|
||||||
);
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume-slider::-webkit-slider-runnable-track {
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume-slider::-webkit-slider-thumb {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: -4px;
|
|
||||||
transition: transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume-slider::-webkit-slider-thumb:hover {
|
|
||||||
transform: scale(1.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume-slider::-moz-range-thumb {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume-slider::-moz-range-thumb:hover {
|
|
||||||
transform: scale(1.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume-slider::-moz-range-progress {
|
|
||||||
height: 4px;
|
|
||||||
background: #ff3b30;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === Alt Kontroller === */
|
|
||||||
.bottom-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-slider {
|
|
||||||
flex: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
accent-color: #27ae60;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time {
|
|
||||||
color: #ccc;
|
|
||||||
font-size: 13px;
|
|
||||||
min-width: 90px;
|
|
||||||
text-align: right;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === Responsive === */
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
.modal-content {
|
|
||||||
width: 90%;
|
|
||||||
height: 75%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === RESPONSIVE === */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.gallery {
|
.gallery {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
width: 95%;
|
|
||||||
height: 70%;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
padding: 6px 10px;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume-slider {
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time {
|
|
||||||
font-size: 11px;
|
|
||||||
min-width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-title {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.modal-content {
|
.gallery {
|
||||||
width: 98%;
|
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
||||||
height: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume-slider {
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-controls {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -428,7 +428,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* --- Torrent liste & satırları (eski App.svelte ile bire bir) --- */
|
/* --- Torrent Listeleme --- */
|
||||||
|
.torrent-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.torrent {
|
.torrent {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 100px 1fr;
|
grid-template-columns: 100px 1fr;
|
||||||
@@ -441,11 +447,7 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.torrent-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
.thumb {
|
.thumb {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
@@ -454,6 +456,7 @@
|
|||||||
background: #ddd;
|
background: #ddd;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder {
|
.placeholder {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
@@ -464,21 +467,25 @@
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent-info {
|
.torrent-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent-header {
|
.torrent-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent-name {
|
.torrent-name {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove-btn {
|
.remove-btn {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -486,9 +493,11 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.15s;
|
transition: transform 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove-btn:hover {
|
.remove-btn:hover {
|
||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent-hash {
|
.torrent-hash {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #777;
|
color: #777;
|
||||||
@@ -500,12 +509,14 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-row {
|
.file-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-row button {
|
.file-row button {
|
||||||
background: #eee;
|
background: #eee;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -514,17 +525,21 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-row button:hover {
|
.file-row button:hover {
|
||||||
background: #ddd;
|
background: #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filename {
|
.filename {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filesize {
|
.filesize {
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- İlerleme Çubuğu --- */
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
@@ -532,11 +547,13 @@
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(90deg, #27ae60, #2ecc71);
|
background: linear-gradient(90deg, #27ae60, #2ecc71);
|
||||||
transition: width 0.3s;
|
transition: width 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-text {
|
.progress-text {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #444;
|
color: #444;
|
||||||
@@ -544,209 +561,7 @@
|
|||||||
padding: 3px 0 8px 0;
|
padding: 3px 0 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Modal & Player (eski ile bire bir) --- */
|
/* --- Responsive Düzenlemeler --- */
|
||||||
.modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
background: rgba(0, 0, 0, 0.8);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
.modal-content {
|
|
||||||
width: 70%;
|
|
||||||
height: 70%;
|
|
||||||
background: #1a1a1a;
|
|
||||||
border-radius: 12px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
.modal-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
background: #2a2a2a;
|
|
||||||
padding: 10px 16px;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.video-title {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.close-btn {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-player {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
.video-element {
|
|
||||||
flex: 1;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
background: #000;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
.video-element:focus {
|
|
||||||
outline: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
background: #1c1c1c;
|
|
||||||
padding: 10px 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
border-top: 1px solid #333;
|
|
||||||
}
|
|
||||||
.top-controls {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.control-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
.control-btn:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
.right-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Volume slider — kırmızı dolum, beyaz knob */
|
|
||||||
.volume-slider {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
width: 100px;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: linear-gradient(
|
|
||||||
to right,
|
|
||||||
#ff3b30 calc(var(--fill, 100%) * 1%),
|
|
||||||
rgba(255, 255, 255, 0.3) calc(var(--fill, 100%) * 1%)
|
|
||||||
);
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
|
||||||
.volume-slider::-webkit-slider-runnable-track {
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.volume-slider::-webkit-slider-thumb {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: -4px;
|
|
||||||
transition: transform 0.2s ease;
|
|
||||||
}
|
|
||||||
.volume-slider::-webkit-slider-thumb:hover {
|
|
||||||
transform: scale(1.3);
|
|
||||||
}
|
|
||||||
.volume-slider::-moz-range-track {
|
|
||||||
height: 4px;
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
.volume-slider::-moz-range-progress {
|
|
||||||
height: 4px;
|
|
||||||
background: #ff3b30;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
.volume-slider::-moz-range-thumb {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: transform 0.2s ease;
|
|
||||||
}
|
|
||||||
.volume-slider::-moz-range-thumb:hover {
|
|
||||||
transform: scale(1.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle-icon {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.bottom-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
.progress-slider {
|
|
||||||
flex: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
accent-color: #27ae60;
|
|
||||||
}
|
|
||||||
.time {
|
|
||||||
color: #ccc;
|
|
||||||
font-size: 13px;
|
|
||||||
min-width: 90px;
|
|
||||||
text-align: right;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* NEW TRANSFER / Magnet düğmeleri */
|
|
||||||
.btn-primary {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
background: #fdce45;
|
|
||||||
border: none;
|
|
||||||
color: #000;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 8px 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 13px;
|
|
||||||
transition: background 0.2s;
|
|
||||||
height: 36px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
.btn-primary:hover {
|
|
||||||
background: #fdce45;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.modal-content {
|
|
||||||
width: 95%;
|
|
||||||
height: 75%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🔹 Responsive Düzenlemeler (hiçbir mevcut stili bozmadan eklenmiştir) */
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.torrent {
|
.torrent {
|
||||||
grid-template-columns: 80px 1fr;
|
grid-template-columns: 80px 1fr;
|
||||||
@@ -767,25 +582,9 @@
|
|||||||
.torrent-files .file-row {
|
.torrent-files .file-row {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 6px 10px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
width: 90%;
|
|
||||||
height: 75%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.files {
|
|
||||||
margin: 0 8px 12px 8px;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.torrent {
|
.torrent {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@@ -815,51 +614,12 @@
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
flex: 1;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.torrent-list {
|
.torrent-list {
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 🎬 Modal video oynatıcı mobil optimizasyonu */
|
|
||||||
.modal-content {
|
|
||||||
width: 95%;
|
|
||||||
height: 70%;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
padding: 6px 10px;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume-slider {
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time {
|
|
||||||
font-size: 11px;
|
|
||||||
min-width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-title {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.btn-primary {
|
|
||||||
font-size: 11px;
|
|
||||||
padding: 6px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.torrent-header {
|
.torrent-header {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
@@ -867,20 +627,5 @@
|
|||||||
.torrent-hash {
|
.torrent-hash {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
width: 98%;
|
|
||||||
height: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.volume-slider {
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-controls {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
/* =======================================================
|
||||||
|
🎨 RENK DEĞİŞKENLERİ VE TEMEL STİLLER
|
||||||
|
======================================================= */
|
||||||
:root {
|
:root {
|
||||||
--yellow: #f5b333;
|
--yellow: #f5b333;
|
||||||
--yellow-dark: #e2a62f;
|
--yellow-dark: #e2a62f;
|
||||||
@@ -6,9 +9,11 @@
|
|||||||
--muted: #666;
|
--muted: #666;
|
||||||
--green: #4caf50;
|
--green: #4caf50;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#app {
|
#app {
|
||||||
@@ -19,27 +24,43 @@ body,
|
|||||||
color: #222;
|
color: #222;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =======================================================
|
||||||
|
📐 GENEL YERLEŞİM
|
||||||
|
======================================================= */
|
||||||
.app {
|
.app {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 220px 1fr;
|
grid-template-columns: 220px 1fr;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
/* Sidebar */
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =======================================================
|
||||||
|
🧭 SIDEBAR
|
||||||
|
======================================================= */
|
||||||
.sidebar {
|
.sidebar {
|
||||||
background: var(--sidebar);
|
background: var(--sidebar);
|
||||||
border-right: 1px solid var(--border);
|
border-right: 1px solid var(--border);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .logo {
|
.sidebar .logo {
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .menu {
|
.sidebar .menu {
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .menu .item {
|
.sidebar .menu .item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -48,22 +69,37 @@ body,
|
|||||||
color: #222;
|
color: #222;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
transition: background 0.2s ease, color 0.2s ease;
|
||||||
}
|
}
|
||||||
.sidebar .menu .item.active {
|
|
||||||
background: #fff;
|
|
||||||
border-left: 3px solid var(--yellow);
|
|
||||||
}
|
|
||||||
.sidebar .menu .item .icon {
|
.sidebar .menu .item .icon {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
/* Content */
|
|
||||||
.content {
|
/* Hover efekti */
|
||||||
display: flex;
|
.sidebar .menu .item:hover {
|
||||||
flex-direction: column;
|
background: #f0f0f0;
|
||||||
height: 100%;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Aktif menü öğesi */
|
||||||
|
.sidebar .menu .item.active {
|
||||||
|
background: #e5e5e5;
|
||||||
|
border-left: 3px solid var(--yellow);
|
||||||
|
font-weight: 600;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .menu .item:hover .icon,
|
||||||
|
.sidebar .menu .item.active .icon {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =======================================================
|
||||||
|
🔍 TOPBAR VE ARAMA
|
||||||
|
======================================================= */
|
||||||
.topbar {
|
.topbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -71,6 +107,7 @@ body,
|
|||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -81,12 +118,17 @@ body,
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search input {
|
.search input {
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =======================================================
|
||||||
|
🟨 BUTONLAR
|
||||||
|
======================================================= */
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: var(--yellow);
|
background: var(--yellow);
|
||||||
border: 1px solid var(--yellow-dark);
|
border: 1px solid var(--yellow-dark);
|
||||||
@@ -95,20 +137,37 @@ body,
|
|||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
gap: 6px;
|
||||||
|
height: 36px;
|
||||||
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--yellow-dark);
|
||||||
|
}
|
||||||
|
|
||||||
.btn-primary:active {
|
.btn-primary:active {
|
||||||
transform: translateY(1px);
|
transform: translateY(1px);
|
||||||
}
|
}
|
||||||
/* Files */
|
|
||||||
|
/* =======================================================
|
||||||
|
📂 FILES SAYFASI
|
||||||
|
======================================================= */
|
||||||
.files {
|
.files {
|
||||||
margin: 0 16px 16px 16px;
|
margin: 0 16px 16px 16px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border-top: 2px solid #f0c24d;
|
border-top: 2px solid #f0c24d;
|
||||||
padding-top: 14px;
|
padding-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.files h2 {
|
.files h2 {
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -119,6 +178,7 @@ body,
|
|||||||
border: 2px dashed var(--border);
|
border: 2px dashed var(--border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-folder {
|
.create-folder {
|
||||||
background: var(--yellow);
|
background: var(--yellow);
|
||||||
border: 1px solid var(--yellow-dark);
|
border: 1px solid var(--yellow-dark);
|
||||||
@@ -127,7 +187,10 @@ body,
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
/* Transfers Page */
|
|
||||||
|
/* =======================================================
|
||||||
|
📦 TRANSFERS SAYFASI
|
||||||
|
======================================================= */
|
||||||
.torrent {
|
.torrent {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -135,9 +198,11 @@ body,
|
|||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent:last-child {
|
.torrent:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
@@ -145,28 +210,155 @@ body,
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress > div {
|
.progress > div {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--green);
|
background: var(--green);
|
||||||
transition: width 0.3s;
|
transition: width 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small {
|
.small {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
/* ====== Responsive & Off-Canvas Sidebar (EKLENDİ) ====== */
|
|
||||||
|
|
||||||
/* Hamburger butonunu varsayılan gizle; mobilde göstereceğiz */
|
/* =======================================================
|
||||||
.menu-toggle {
|
🎞️ MODAL & PLAYER (ORTAK)
|
||||||
display: none;
|
======================================================= */
|
||||||
background: none;
|
.modal-overlay {
|
||||||
border: none;
|
position: fixed;
|
||||||
font-size: 20px;
|
inset: 0;
|
||||||
color: #333;
|
backdrop-filter: blur(10px);
|
||||||
cursor: pointer;
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
width: 70%;
|
||||||
|
height: 70%;
|
||||||
|
background: #1a1a1a;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Video Player === */
|
||||||
|
.custom-player {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-element {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #000;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-element:focus {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Kontroller === */
|
||||||
|
.controls {
|
||||||
|
background: #1c1c1c;
|
||||||
|
padding: 10px 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
border-top: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Ses Kaydırıcısı === */
|
||||||
|
.volume-slider {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 100px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
#ff3b30 calc(var(--fill, 100%) * 1%),
|
||||||
|
rgba(255, 255, 255, 0.3) calc(var(--fill, 100%) * 1%)
|
||||||
|
);
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: -4px;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider::-webkit-slider-thumb:hover {
|
||||||
|
transform: scale(1.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Alt Kontroller === */
|
||||||
|
.bottom-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-slider {
|
||||||
|
flex: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: #27ae60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 13px;
|
||||||
|
min-width: 90px;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar arkası için tıklanabilir backdrop (mobilde sidebar açıkken) */
|
|
||||||
.backdrop {
|
.backdrop {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -174,14 +366,95 @@ body,
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
z-index: 999; /* sidebar’ın üstünde */
|
z-index: 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.backdrop.show {
|
.backdrop.show {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tablet ve aşağısında grid tek sütun; sidebar off-canvas olur */
|
/* === Global Close Button (Resim + Video) === */
|
||||||
|
.global-close-btn,
|
||||||
|
.image-close-btn {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 30px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 36px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2100;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 46px;
|
||||||
|
height: 46px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-close-btn:hover,
|
||||||
|
.image-close-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =======================================================
|
||||||
|
🖼️ RESİM MODALI
|
||||||
|
======================================================= */
|
||||||
|
.image-modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-modal-content {
|
||||||
|
position: relative;
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 90%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-modal-img {
|
||||||
|
max-width: 75vw;
|
||||||
|
max-height: 75vh;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 25px rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Modal Başlığı (Ortak: Files + Transfers) === */
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: #2a2a2a;
|
||||||
|
padding: 10px 16px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-title {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =======================================================
|
||||||
|
📱 RESPONSIVE
|
||||||
|
======================================================= */
|
||||||
|
|
||||||
|
/* Tablet ve aşağısı */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.app {
|
.app {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -193,6 +466,11 @@ body,
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
@@ -206,48 +484,16 @@ body,
|
|||||||
transition: left 0.25s ease;
|
transition: left 0.25s ease;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar.open {
|
.sidebar.open {
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Genel içerik kenar boşluklarını sıkılaştır */
|
|
||||||
.files {
|
.files {
|
||||||
margin: 0 10px 14px 10px;
|
margin: 0 10px 14px 10px;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Transfers ve diğer sayfalar için liste öğelerini dikeyleştir */
|
|
||||||
.torrent {
|
|
||||||
/* Transfers’teki kutular */
|
|
||||||
grid-template-columns: 1fr !important;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
.thumb {
|
|
||||||
width: 100% !important;
|
|
||||||
height: 180px !important;
|
|
||||||
}
|
|
||||||
.torrent-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
.torrent-hash {
|
|
||||||
word-break: break-word;
|
|
||||||
white-space: normal;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
.file-row {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
.progress-text {
|
|
||||||
text-align: left;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Butonlar eş görünsün ve kolay dokunulsun */
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -256,64 +502,48 @@ body,
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modal video oynatıcı mobil uyum */
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
width: 95% !important;
|
width: 95% !important;
|
||||||
height: 72% !important;
|
height: 72% !important;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.volume-slider {
|
.volume-slider {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
min-width: 78px;
|
min-width: 78px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === Sidebar Hover & Active Effects === */
|
/* Küçük telefonlar */
|
||||||
|
|
||||||
/* Hover efekti: hafif gri arka plan */
|
|
||||||
.sidebar .menu .item:hover {
|
|
||||||
background: #f0f0f0;
|
|
||||||
color: #000;
|
|
||||||
transition: background 0.2s ease, color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Aktif item: Daha koyu gri arka plan */
|
|
||||||
.sidebar .menu .item.active {
|
|
||||||
background: #e5e5e5; /* aktif olan menü item */
|
|
||||||
font-weight: 600;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hover ve aktif durumlarda ikon da koyulaşsın */
|
|
||||||
.sidebar .menu .item:hover .icon,
|
|
||||||
.sidebar .menu .item.active .icon {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Daha küçük telefonlar */
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent-hash {
|
.torrent-hash {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
width: 98% !important;
|
width: 98% !important;
|
||||||
height: 76% !important;
|
height: 76% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.volume-slider {
|
.volume-slider {
|
||||||
width: 56px;
|
width: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-controls {
|
.bottom-controls {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// utils/filename.js
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dosya adını temizler ve sadeleştirir.
|
* Dosya adını temizler ve sadeleştirir.
|
||||||
* Örnek:
|
* Örnek:
|
||||||
* The.Astronaut.2025.1080p.WEBRip.x265-KONTRAST
|
* The.Astronaut.2025.1080p.WEBRip.x265-KONTRAST
|
||||||
* → "The Astronaut (2025)"
|
* → "The Astronaut (2025)"
|
||||||
|
* 1761244874124/Gen.V.S02E08.Cavallo.di.Troia.ITA.ENG.1080p.AMZN.WEB-DL.DDP5.1.H.264-MeM.GP.mkv
|
||||||
|
* → "Gen V S02E08 Cavallo Di Troia"
|
||||||
*/
|
*/
|
||||||
export function cleanFileName(fullPath) {
|
export function cleanFileName(fullPath) {
|
||||||
if (!fullPath) return "";
|
if (!fullPath) return "";
|
||||||
@@ -15,8 +15,8 @@ export function cleanFileName(fullPath) {
|
|||||||
// 2️⃣ Uzantıyı kaldır
|
// 2️⃣ Uzantıyı kaldır
|
||||||
name = name.replace(/\.[^.]+$/, "");
|
name = name.replace(/\.[^.]+$/, "");
|
||||||
|
|
||||||
// 3️⃣ Noktaları boşluğa çevir
|
// 3️⃣ Noktaları ve alt tireleri boşluğa çevir
|
||||||
name = name.replace(/\./g, " ");
|
name = name.replace(/[._]+/g, " ");
|
||||||
|
|
||||||
// 4️⃣ Gereksiz etiketleri kaldır
|
// 4️⃣ Gereksiz etiketleri kaldır
|
||||||
const trashWords = [
|
const trashWords = [
|
||||||
@@ -25,7 +25,7 @@ export function cleanFileName(fullPath) {
|
|||||||
"2160p",
|
"2160p",
|
||||||
"4k",
|
"4k",
|
||||||
"bluray",
|
"bluray",
|
||||||
"web-dl",
|
"web[- ]?dl",
|
||||||
"webrip",
|
"webrip",
|
||||||
"hdrip",
|
"hdrip",
|
||||||
"x264",
|
"x264",
|
||||||
@@ -34,6 +34,7 @@ export function cleanFileName(fullPath) {
|
|||||||
"aac",
|
"aac",
|
||||||
"h264",
|
"h264",
|
||||||
"h265",
|
"h265",
|
||||||
|
"ddp5",
|
||||||
"dvdrip",
|
"dvdrip",
|
||||||
"brrip",
|
"brrip",
|
||||||
"remux",
|
"remux",
|
||||||
@@ -41,6 +42,8 @@ export function cleanFileName(fullPath) {
|
|||||||
"sub",
|
"sub",
|
||||||
"subs",
|
"subs",
|
||||||
"turkce",
|
"turkce",
|
||||||
|
"ita",
|
||||||
|
"eng",
|
||||||
"dublado",
|
"dublado",
|
||||||
"dubbed",
|
"dubbed",
|
||||||
"extended",
|
"extended",
|
||||||
@@ -54,57 +57,42 @@ export function cleanFileName(fullPath) {
|
|||||||
"hdtv",
|
"hdtv",
|
||||||
"amzn",
|
"amzn",
|
||||||
"nf",
|
"nf",
|
||||||
"netflix"
|
"netflix",
|
||||||
|
"mem",
|
||||||
|
"gp"
|
||||||
];
|
];
|
||||||
const trashRegex = new RegExp(`\\b(${trashWords.join("|")})\\b`, "gi");
|
const trashRegex = new RegExp(`\\b(${trashWords.join("|")})\\b`, "gi");
|
||||||
name = name.replace(trashRegex, " ");
|
name = name.replace(trashRegex, " ");
|
||||||
|
|
||||||
// 5️⃣ Köşeli parantez içindekileri kaldır
|
// 5️⃣ Parantez veya köşeli parantez içindekileri kaldır
|
||||||
name = name.replace(/\[[^\]]*\]/g, "");
|
name = name.replace(/[\[\(].*?[\]\)]/g, " ");
|
||||||
|
|
||||||
// 6️⃣ Parantez içindeki tarihleri kaldır
|
// 6️⃣ Fazla tireleri ve sayıları temizle
|
||||||
name = name
|
name = name
|
||||||
.replace(/\(\d{2}\.\d{2}\.\d{2,4}\)/g, "")
|
.replace(/[-]+/g, " ")
|
||||||
.replace(/\(\d{4}(-\d{2})?(-\d{2})?\)/g, "");
|
.replace(/\b\d{3,4}\b/g, " ") // tek başına 1080, 2025 gibi
|
||||||
|
.replace(/\s{2,}/g, " ")
|
||||||
// 7️⃣ Fazla boşlukları temizle
|
|
||||||
name = name.replace(/\s{2,}/g, " ").trim();
|
|
||||||
|
|
||||||
// 8️⃣ Yılı tespit et (ör. 2024, 1999)
|
|
||||||
const yearMatch = name.match(/\b(19|20)\d{2}\b/);
|
|
||||||
let year = "";
|
|
||||||
if (yearMatch) {
|
|
||||||
year = yearMatch[0];
|
|
||||||
name = name.replace(year, "").trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 9️⃣ Dizi formatı (S03E01) varsa koru
|
|
||||||
const match = name.match(/(.+?)\s*-\s*(S\d{2}E\d{2})/i);
|
|
||||||
if (match) {
|
|
||||||
const formatted = `${match[1].trim()} - ${match[2].toUpperCase()}`;
|
|
||||||
return year ? `${formatted} (${year})` : formatted;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔟 Fazla tireleri ve tire + parantez boşluklarını düzelt
|
|
||||||
name = name
|
|
||||||
.replace(/[-_]+/g, " ") // birden fazla tireyi temizle
|
|
||||||
.replace(/\s-\s*\(/g, " (") // " - (" → " ("
|
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
// 11️⃣ Baş harfleri büyüt
|
// 7️⃣ Dizi formatını (S02E08) koru
|
||||||
|
const episodeMatch = name.match(/(S\d{1,2}E\d{1,2})/i);
|
||||||
|
if (episodeMatch) {
|
||||||
|
const epTag = episodeMatch[0].toUpperCase();
|
||||||
|
// örnek: Gen V S02E08 Cavallo di Troia
|
||||||
|
name = name.replace(episodeMatch[0], epTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8️⃣ Baş harfleri büyüt (küçük kelimeleri koruyarak)
|
||||||
name = name
|
name = name
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map(
|
.filter((w) => w.length > 0)
|
||||||
(w) =>
|
.map((w) => {
|
||||||
w.length > 1
|
if (["di", "da", "de", "of", "and", "the"].includes(w.toLowerCase()))
|
||||||
? w[0].toUpperCase() + w.slice(1).toLowerCase()
|
return w.toLowerCase();
|
||||||
: w.toUpperCase()
|
return w[0].toUpperCase() + w.slice(1).toLowerCase();
|
||||||
)
|
})
|
||||||
.join(" ")
|
.join(" ")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
// 12️⃣ Yıl varsa sonuna ekle
|
return name;
|
||||||
if (year) name += ` (${year})`;
|
|
||||||
|
|
||||||
return name.trim();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ app.use(express.json());
|
|||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
app.use("/downloads", express.static(DOWNLOAD_DIR));
|
app.use("/downloads", express.static(DOWNLOAD_DIR));
|
||||||
|
|
||||||
|
|
||||||
// --- En uygun video dosyasını seç ---
|
// --- En uygun video dosyasını seç ---
|
||||||
function pickBestVideoFile(torrent) {
|
function pickBestVideoFile(torrent) {
|
||||||
const videoExts = [".mp4", ".webm", ".mkv", ".mov", ".m4v"];
|
const videoExts = [".mp4", ".webm", ".mkv", ".mov", ".m4v"];
|
||||||
@@ -70,8 +69,8 @@ function snapshot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Basit kimlik doğrulama sistemi ---
|
// --- Basit kimlik doğrulama sistemi ---
|
||||||
const USERNAME = process.env.USERNAME
|
const USERNAME = process.env.USERNAME;
|
||||||
const PASSWORD = process.env.PASSWORD
|
const PASSWORD = process.env.PASSWORD;
|
||||||
let activeTokens = new Set();
|
let activeTokens = new Set();
|
||||||
|
|
||||||
app.post("/api/login", (req, res) => {
|
app.post("/api/login", (req, res) => {
|
||||||
@@ -85,14 +84,12 @@ app.post("/api/login", (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function requireAuth(req, res, next) {
|
function requireAuth(req, res, next) {
|
||||||
const token =
|
const token = req.headers.authorization?.split(" ")[1] || req.query.token;
|
||||||
req.headers.authorization?.split(" ")[1] || req.query.token;
|
|
||||||
if (!token || !activeTokens.has(token))
|
if (!token || !activeTokens.has(token))
|
||||||
return res.status(401).json({ error: "Unauthorized" });
|
return res.status(401).json({ error: "Unauthorized" });
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- Torrent veya magnet ekleme ---
|
// --- Torrent veya magnet ekleme ---
|
||||||
app.post("/api/transfer", requireAuth, upload.single("torrent"), (req, res) => {
|
app.post("/api/transfer", requireAuth, upload.single("torrent"), (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -131,8 +128,8 @@ app.post("/api/transfer", requireAuth, upload.single("torrent"), (req, res) => {
|
|||||||
infoHash: torrent.infoHash,
|
infoHash: torrent.infoHash,
|
||||||
name: torrent.name,
|
name: torrent.name,
|
||||||
selectedIndex,
|
selectedIndex,
|
||||||
tracker: torrent.announce?.[0] || null, // 🆕
|
tracker: torrent.announce?.[0] || null,
|
||||||
added, // 🆕
|
added,
|
||||||
files: torrent.files.map((f, i) => ({
|
files: torrent.files.map((f, i) => ({
|
||||||
index: i,
|
index: i,
|
||||||
name: f.name,
|
name: f.name,
|
||||||
@@ -206,15 +203,19 @@ app.delete("/api/torrents/:hash", requireAuth, (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- GENEL MEDYA SUNUMU (🆕 resimler + videolar) ---
|
||||||
app.get("/media/:path(*)", requireAuth, (req, res) => {
|
app.get("/media/:path(*)", requireAuth, (req, res) => {
|
||||||
const fullPath = path.join(DOWNLOAD_DIR, req.params.path);
|
const relPath = req.params.path;
|
||||||
|
const fullPath = path.join(DOWNLOAD_DIR, relPath);
|
||||||
if (!fs.existsSync(fullPath)) return res.status(404).send("File not found");
|
if (!fs.existsSync(fullPath)) return res.status(404).send("File not found");
|
||||||
|
|
||||||
const stat = fs.statSync(fullPath);
|
const stat = fs.statSync(fullPath);
|
||||||
const fileSize = stat.size;
|
const fileSize = stat.size;
|
||||||
|
const type = mime.lookup(fullPath) || "application/octet-stream";
|
||||||
|
const isVideo = String(type).startsWith("video/");
|
||||||
const range = req.headers.range;
|
const range = req.headers.range;
|
||||||
|
|
||||||
if (range) {
|
if (isVideo && range) {
|
||||||
const [startStr, endStr] = range.replace(/bytes=/, "").split("-");
|
const [startStr, endStr] = range.replace(/bytes=/, "").split("-");
|
||||||
const start = parseInt(startStr, 10);
|
const start = parseInt(startStr, 10);
|
||||||
const end = endStr ? parseInt(endStr, 10) : fileSize - 1;
|
const end = endStr ? parseInt(endStr, 10) : fileSize - 1;
|
||||||
@@ -224,21 +225,22 @@ app.get("/media/:path(*)", requireAuth, (req, res) => {
|
|||||||
"Content-Range": `bytes ${start}-${end}/${fileSize}`,
|
"Content-Range": `bytes ${start}-${end}/${fileSize}`,
|
||||||
"Accept-Ranges": "bytes",
|
"Accept-Ranges": "bytes",
|
||||||
"Content-Length": chunkSize,
|
"Content-Length": chunkSize,
|
||||||
"Content-Type": "video/mp4"
|
"Content-Type": type
|
||||||
};
|
};
|
||||||
res.writeHead(206, head);
|
res.writeHead(206, head);
|
||||||
file.pipe(res);
|
file.pipe(res);
|
||||||
} else {
|
} else {
|
||||||
const head = {
|
const head = {
|
||||||
"Content-Length": fileSize,
|
"Content-Length": fileSize,
|
||||||
"Content-Type": "video/mp4"
|
"Content-Type": type,
|
||||||
|
"Accept-Ranges": isVideo ? "bytes" : "none"
|
||||||
};
|
};
|
||||||
res.writeHead(200, head);
|
res.writeHead(200, head);
|
||||||
fs.createReadStream(fullPath).pipe(res);
|
fs.createReadStream(fullPath).pipe(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 📁 Dosya gezgini: /downloads altındaki dosyaları listele ---
|
// --- 📁 Dosya gezgini (🆕 type ve url alanları eklendi; resim thumb'ı) ---
|
||||||
app.get("/api/files", requireAuth, (req, res) => {
|
app.get("/api/files", requireAuth, (req, res) => {
|
||||||
const walk = (dir) => {
|
const walk = (dir) => {
|
||||||
let result = [];
|
let result = [];
|
||||||
@@ -251,21 +253,35 @@ app.get("/api/files", requireAuth, (req, res) => {
|
|||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
result = result.concat(walk(full));
|
result = result.concat(walk(full));
|
||||||
} else {
|
} else {
|
||||||
// thumbnail.jpg dosyasını listeleme
|
|
||||||
if (entry.name.toLowerCase() === "thumbnail.jpg") continue;
|
if (entry.name.toLowerCase() === "thumbnail.jpg") continue;
|
||||||
const size = fs.statSync(full).size;
|
|
||||||
const parts = rel.split(path.sep);
|
|
||||||
const rootHash = parts[0]; // ilk klasör adı
|
|
||||||
const thumbPath = path.join(DOWNLOAD_DIR, rootHash, "thumbnail.jpg");
|
|
||||||
|
|
||||||
// ✅ Thumbnail dosyası gerçekten varsa ekle
|
const size = fs.statSync(full).size;
|
||||||
const thumb = fs.existsSync(thumbPath)
|
const type = mime.lookup(full) || "application/octet-stream";
|
||||||
|
|
||||||
|
// kök klasör (thumbnail varsa video kartlarında kullanıyoruz)
|
||||||
|
const parts = rel.split(path.sep);
|
||||||
|
const rootHash = parts[0];
|
||||||
|
const videoThumbPath = path.join(DOWNLOAD_DIR, rootHash, "thumbnail.jpg");
|
||||||
|
const hasVideoThumb = fs.existsSync(videoThumbPath);
|
||||||
|
|
||||||
|
// URL (segment bazlı encode → / işaretlerini koru)
|
||||||
|
const urlPath = encodeURIComponent(rel).replace(/%2F/g, "/");
|
||||||
|
const url = `/media/${urlPath}`;
|
||||||
|
|
||||||
|
// Resimler için küçük önizleme: kendi dosyasını thumbnail yap
|
||||||
|
const isImage = String(type).startsWith("image/");
|
||||||
|
const isVideo = String(type).startsWith("video/");
|
||||||
|
const thumb = isImage
|
||||||
|
? url
|
||||||
|
: hasVideoThumb
|
||||||
? `/downloads/${rootHash}/thumbnail.jpg`
|
? `/downloads/${rootHash}/thumbnail.jpg`
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
name: rel,
|
name: rel,
|
||||||
size,
|
size,
|
||||||
|
type, // 🆕 "image/jpeg", "video/mp4", vs.
|
||||||
|
url, // 🆕 doğrudan görüntüleme/oynatma için
|
||||||
thumbnail: thumb
|
thumbnail: thumb
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -283,7 +299,7 @@ app.get("/api/files", requireAuth, (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Stream endpoint ---
|
// --- Stream endpoint (torrent içinden) ---
|
||||||
app.get("/stream/:hash", requireAuth, (req, res) => {
|
app.get("/stream/:hash", requireAuth, (req, res) => {
|
||||||
const entry = torrents.get(req.params.hash);
|
const entry = torrents.get(req.params.hash);
|
||||||
if (!entry) return res.status(404).end();
|
if (!entry) return res.status(404).end();
|
||||||
@@ -331,8 +347,6 @@ const server = app.listen(PORT, () =>
|
|||||||
const publicDir = path.join(__dirname, "public");
|
const publicDir = path.join(__dirname, "public");
|
||||||
if (fs.existsSync(publicDir)) {
|
if (fs.existsSync(publicDir)) {
|
||||||
app.use(express.static(publicDir));
|
app.use(express.static(publicDir));
|
||||||
|
|
||||||
// Frontend route'larını index.html'e yönlendir
|
|
||||||
app.get("*", (req, res, next) => {
|
app.get("*", (req, res, next) => {
|
||||||
if (req.path.startsWith("/api")) return next();
|
if (req.path.startsWith("/api")) return next();
|
||||||
res.sendFile(path.join(publicDir, "index.html"));
|
res.sendFile(path.join(publicDir, "index.html"));
|
||||||
|
|||||||
Reference in New Issue
Block a user