v0.5.1: Ohli24 레이아웃 표준화 및 Anilife 폴백 체인 개선
This commit is contained in:
16
README.md
16
README.md
@@ -71,6 +71,22 @@
|
|||||||
|
|
||||||
## 📝 변경 이력 (Changelog)
|
## 📝 변경 이력 (Changelog)
|
||||||
|
|
||||||
|
### v0.5.1 (2026-01-04)
|
||||||
|
- **Ohli24 레이아웃 표준화**:
|
||||||
|
- 모든 Ohli24 페이지(Setting, Search, Queue, List, Request)에 일관된 1400px max-width 및 중앙 정렬 적용
|
||||||
|
- `ohli24.css`에 공통 wrapper(`.ohli24-common-wrapper`) 및 헤더 스타일 추가
|
||||||
|
- List/Queue 페이지 `visible` 클래스 누락 수정 (content-cloak 트리거 추가)
|
||||||
|
- Request 페이지 에피소드 카드 순차 렌더링 문제 해결 (`requestAnimationFrame` 사용)
|
||||||
|
- **Anilife 폴백 체인 개선**:
|
||||||
|
- `get_html` 함수에 **Zendriver subprocess 폴백** 추가 (Daemon 실패 시 자동 전환)
|
||||||
|
- Playwright 폴백을 **Camoufox**로 변경 (더 강력한 안티봇 우회)
|
||||||
|
- 3단계 폴백: Zendriver Daemon → Zendriver Subprocess → Camoufox
|
||||||
|
- **Anilife 반응형 레이아웃**:
|
||||||
|
- Request 페이지에 `anilife-common-wrapper` 클래스 적용
|
||||||
|
- 모바일(`<992px`): 100% 너비 / 데스크탑(`≥992px`): 1400px max-width
|
||||||
|
- **Linkkf CSS 준비**:
|
||||||
|
- `linkkf.css`에 공통 wrapper 스타일 추가 (향후 레이아웃 표준화 대비)
|
||||||
|
|
||||||
### v0.5.0 (2026-01-03)
|
### v0.5.0 (2026-01-03)
|
||||||
- **Ohli24 비디오 플레이어 UI 전면 개편**:
|
- **Ohli24 비디오 플레이어 UI 전면 개편**:
|
||||||
- **프리미엄 글래스모피즘 디자인**: 플레이어 모달 및 플레이리스트 컨트롤에 투명 유리 테마 적용
|
- **프리미엄 글래스모피즘 디자인**: 플레이어 모달 및 플레이리스트 컨트롤에 투명 유리 테마 적용
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
title: "애니 다운로더"
|
title: "애니 다운로더"
|
||||||
version: "0.5.15"
|
version: "0.5.16"
|
||||||
package_name: "anime_downloader"
|
package_name: "anime_downloader"
|
||||||
developer: "projectdx"
|
developer: "projectdx"
|
||||||
description: "anime downloader"
|
description: "anime downloader"
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ class LogicAniLife(AnimeModuleBase):
|
|||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
data = ""
|
data = ""
|
||||||
try:
|
try:
|
||||||
# --- Zendriver Daemon 최적 최적화 (v0.5.0) ---
|
# --- Layer 1: Zendriver Daemon (최적) ---
|
||||||
from .mod_ohli24 import LogicOhli24
|
from .mod_ohli24 import LogicOhli24
|
||||||
if LogicOhli24.is_zendriver_daemon_running():
|
if LogicOhli24.is_zendriver_daemon_running():
|
||||||
logger.info(f"[Anilife] Trying Zendriver Daemon: {url}")
|
logger.info(f"[Anilife] Trying Zendriver Daemon: {url}")
|
||||||
@@ -229,17 +229,86 @@ class LogicAniLife(AnimeModuleBase):
|
|||||||
else:
|
else:
|
||||||
logger.warning(f"[Anilife] Daemon failed in {elapsed:.2f}s: {daemon_res.get('error', 'Unknown')}")
|
logger.warning(f"[Anilife] Daemon failed in {elapsed:.2f}s: {daemon_res.get('error', 'Unknown')}")
|
||||||
|
|
||||||
# --- Fallback: Playwright ---
|
# --- Layer 2: Zendriver Subprocess Fallback (Ohli24와 동일) ---
|
||||||
logger.info("[Anilife] Falling back to Playwright...")
|
logger.info(f"[Anilife] Trying Zendriver subprocess: {url}")
|
||||||
from .lib.crawler import Crawler
|
if LogicOhli24.ensure_zendriver_installed():
|
||||||
res = asyncio.run(
|
try:
|
||||||
Crawler().get_html_playwright(
|
import subprocess
|
||||||
url, engine="chromium", headless=headless
|
script_path = os.path.join(os.path.dirname(__file__), "lib", "zendriver_ohli24.py")
|
||||||
)
|
browser_path = P.ModelSetting.get("ohli24_zendriver_browser_path")
|
||||||
)
|
|
||||||
elapsed = time.time() - start_time
|
cmd = [sys.executable, script_path, url, str(30)]
|
||||||
logger.info(f"[Anilife] Playwright finished in {elapsed:.2f}s")
|
if browser_path:
|
||||||
return res
|
cmd.append(browser_path)
|
||||||
|
|
||||||
|
with open(os.devnull, 'w') as devnull:
|
||||||
|
old_stderr = sys.stderr
|
||||||
|
sys.stderr = devnull
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=False,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True,
|
||||||
|
timeout=60
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
sys.stderr = old_stderr
|
||||||
|
|
||||||
|
if result.returncode == 0 and result.stdout.strip():
|
||||||
|
import json as json_lib
|
||||||
|
zd_result = json_lib.loads(result.stdout.strip())
|
||||||
|
if zd_result.get("success") and zd_result.get("html"):
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
logger.info(f"[Anilife] Zendriver subprocess success in {elapsed:.2f}s, HTML len: {len(zd_result['html'])}")
|
||||||
|
return zd_result["html"]
|
||||||
|
else:
|
||||||
|
logger.warning(f"[Anilife] Zendriver subprocess failed: {zd_result.get('error', 'Unknown')}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"[Anilife] Zendriver subprocess returncode: {result.returncode}")
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
logger.warning(f"[Anilife] Zendriver subprocess timed out")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[Anilife] Zendriver subprocess exception: {e}")
|
||||||
|
|
||||||
|
# --- Layer 3: Camoufox Fallback (최후 수단) ---
|
||||||
|
logger.info("[Anilife] Falling back to Camoufox...")
|
||||||
|
try:
|
||||||
|
import subprocess
|
||||||
|
script_path = os.path.join(os.path.dirname(__file__), "lib", "camoufox_ohli24.py")
|
||||||
|
|
||||||
|
with open(os.devnull, 'w') as devnull:
|
||||||
|
old_stderr = sys.stderr
|
||||||
|
sys.stderr = devnull
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, script_path, url, str(30)],
|
||||||
|
capture_output=False,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True,
|
||||||
|
timeout=60
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
sys.stderr = old_stderr
|
||||||
|
|
||||||
|
if result.returncode == 0 and result.stdout.strip():
|
||||||
|
import json as json_lib
|
||||||
|
cf_result = json_lib.loads(result.stdout.strip())
|
||||||
|
if cf_result.get("success") and cf_result.get("html"):
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
logger.info(f"[Anilife] Camoufox success in {elapsed:.2f}s, HTML len: {len(cf_result['html'])}")
|
||||||
|
return cf_result["html"]
|
||||||
|
else:
|
||||||
|
logger.warning(f"[Anilife] Camoufox failed: {cf_result.get('error', 'Unknown')}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"[Anilife] Camoufox returncode: {result.returncode}")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
logger.warning("[Anilife] Camoufox timed out")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[Anilife] Camoufox exception: {e}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Exception:%s", e)
|
logger.error("Exception:%s", e)
|
||||||
|
|||||||
@@ -1305,9 +1305,17 @@ class LogicOhli24(AnimeModuleBase):
|
|||||||
# logger.debug(item.xpath(".//div[@class='post-title']/text()").join())
|
# logger.debug(item.xpath(".//div[@class='post-title']/text()").join())
|
||||||
entity["title"] = "".join(item.xpath(".//div[@class='post-title']/text()")).strip()
|
entity["title"] = "".join(item.xpath(".//div[@class='post-title']/text()")).strip()
|
||||||
|
|
||||||
original_img = item.xpath(".//div[@class='img-item']/img/@src")[0].replace(
|
# Use multiple image attributes for lazy-loading support
|
||||||
"..", P.ModelSetting.get("ohli24_url")
|
img_attributes = [".//div[@class='img-item']/img/@src", ".//div[@class='img-item']/img/@data-src", ".//div[@class='img-item']/img/@data-ezsrc"]
|
||||||
)
|
original_img = ""
|
||||||
|
for attr in img_attributes:
|
||||||
|
matches = item.xpath(attr)
|
||||||
|
if matches and matches[0].strip():
|
||||||
|
original_img = matches[0].replace("..", P.ModelSetting.get("ohli24_url"))
|
||||||
|
break
|
||||||
|
|
||||||
|
if not original_img:
|
||||||
|
original_img = "https://via.placeholder.com/200x300?text=No+Image"
|
||||||
|
|
||||||
# Use Image Proxy
|
# Use Image Proxy
|
||||||
entity["image_link"] = "/%s/api/%s/image_proxy?url=%s" % (
|
entity["image_link"] = "/%s/api/%s/image_proxy?url=%s" % (
|
||||||
@@ -1712,17 +1720,17 @@ class LogicOhli24(AnimeModuleBase):
|
|||||||
epi_no = int(match.group("epi_no"))
|
epi_no = int(match.group("epi_no"))
|
||||||
|
|
||||||
# Use glob pattern for quality: *-OHNI24.mp4 matches any quality
|
# Use glob pattern for quality: *-OHNI24.mp4 matches any quality
|
||||||
|
# Sanitize ONLY the fixed text parts to avoid breaking glob wildcards (*)
|
||||||
|
if match:
|
||||||
|
content_title_clean = Util.change_text_for_use_filename(content_title)
|
||||||
filename_pattern = "%s.S%sE%s.*-OHNI24.mp4" % (
|
filename_pattern = "%s.S%sE%s.*-OHNI24.mp4" % (
|
||||||
content_title,
|
content_title_clean,
|
||||||
"0%s" % season if season < 10 else season,
|
"0%s" % season if season < 10 else season,
|
||||||
"0%s" % epi_no if epi_no < 10 else epi_no,
|
"0%s" % epi_no if epi_no < 10 else epi_no,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Fallback pattern for non-standard titles
|
title_clean = Util.change_text_for_use_filename(title)
|
||||||
filename_pattern = "%s.*-OHNI24.mp4" % title
|
filename_pattern = "%s.*-OHNI24.mp4" % title_clean
|
||||||
|
|
||||||
# Sanitize pattern (but keep glob wildcards)
|
|
||||||
filename_pattern = Util.change_text_for_use_filename(filename_pattern)
|
|
||||||
|
|
||||||
# Get save path
|
# Get save path
|
||||||
savepath = P.ModelSetting.get("ohli24_download_path")
|
savepath = P.ModelSetting.get("ohli24_download_path")
|
||||||
|
|||||||
@@ -6,3 +6,26 @@
|
|||||||
--notify-warning-bg: rgba(120, 53, 15, 0.95);
|
--notify-warning-bg: rgba(120, 53, 15, 0.95);
|
||||||
--notify-danger-bg: rgba(127, 29, 29, 0.95);
|
--notify-danger-bg: rgba(127, 29, 29, 0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Common Layout Wrapper - Responsive */
|
||||||
|
.anilife-common-wrapper {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
transition: opacity 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop: 1400px max-width */
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.anilife-common-wrapper {
|
||||||
|
max-width: 1400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.anilife-common-wrapper.content-cloak {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anilife-common-wrapper.visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,3 +35,19 @@ ul.nav.nav-pills .nav-link.active {
|
|||||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
|
||||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3) !important;
|
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Common Layout Wrapper */
|
||||||
|
.linkkf-common-wrapper {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
transition: opacity 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkkf-common-wrapper.content-cloak {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkkf-common-wrapper.visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,22 +49,44 @@
|
|||||||
overflow-x: hidden !important;
|
overflow-x: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Global Navigation Pills Fix */
|
/* Global Navigation Pills Fix & Premium Styling */
|
||||||
ul.nav.nav-pills.bg-light {
|
ul.nav.nav-pills.bg-light {
|
||||||
margin-top: 50px !important;
|
background-color: rgba(30, 41, 59, 0.6) !important;
|
||||||
margin-bottom: 10px !important;
|
backdrop-filter: blur(10px);
|
||||||
width: 100% !important;
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
display: flex !important;
|
border-radius: 50rem !important; /* Pill shape container */
|
||||||
flex-wrap: wrap !important;
|
padding: 6px !important;
|
||||||
justify-content: center !important;
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2) !important;
|
||||||
border-radius: 10px !important;
|
display: inline-flex !important; /* Fit content */
|
||||||
padding: 4px !important;
|
flex-wrap: wrap; /* allow wrap on small screens */
|
||||||
gap: 2px !important;
|
justify-content: center;
|
||||||
|
width: auto !important; /* Prevent full width */
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-top: 50px !important; /* Mobile spacing */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.nav.nav-pills .nav-item {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
ul.nav.nav-pills .nav-link {
|
ul.nav.nav-pills .nav-link {
|
||||||
padding: 5px 12px !important;
|
border-radius: 50rem !important;
|
||||||
font-size: 13px !important;
|
padding: 8px 20px !important;
|
||||||
|
color: #94a3b8 !important; /* Muted text */
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.nav.nav-pills .nav-link:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #fff !important;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.nav.nav-pills .nav-link.active {
|
||||||
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Layout Expansion on Mobile */
|
/* Layout Expansion on Mobile */
|
||||||
|
|||||||
@@ -21,6 +21,21 @@ body {
|
|||||||
color: #f1f5f9;
|
color: #f1f5f9;
|
||||||
background-image: linear-gradient(135deg, #1f2937, #111827, #0f172a) !important;
|
background-image: linear-gradient(135deg, #1f2937, #111827, #0f172a) !important;
|
||||||
background-attachment: fixed !important;
|
background-attachment: fixed !important;
|
||||||
|
font-family: 'NamumSquareNeo', system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Noto Sans, Liberation Sans, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glass Card Container */
|
||||||
|
.glass-card {
|
||||||
|
background: rgba(30, 41, 59, 0.7) !important;
|
||||||
|
backdrop-filter: blur(12px) !important;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08) !important;
|
||||||
|
border-radius: 16px !important;
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-card:hover {
|
||||||
|
border-color: rgba(96, 165, 250, 0.5) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* General Layout Fixes */
|
/* General Layout Fixes */
|
||||||
@@ -30,6 +45,59 @@ body {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ohli24-common-wrapper {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
transition: opacity 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ohli24-common-wrapper.content-cloak {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ohli24-common-wrapper.visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Common Header Styles */
|
||||||
|
.ohli24-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ohli24-header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ohli24-icon-box {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
border-radius: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ohli24-header-title {
|
||||||
|
margin-bottom: 0;
|
||||||
|
color: white;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ohli24-header-subtitle {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
/* Nav Pills Customization */
|
/* Nav Pills Customization */
|
||||||
ul.nav.nav-pills.bg-light {
|
ul.nav.nav-pills.bg-light {
|
||||||
background-color: rgba(30, 41, 59, 0.6) !important;
|
background-color: rgba(30, 41, 59, 0.6) !important;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div id="yommi_wrapper" class="container-fluid mt-2 mt-md-4 mx-auto content-cloak" style="max-width: 100%;">
|
<div id="yommi_wrapper" class="container-fluid mt-2 mt-md-4 mx-auto anilife-common-wrapper content-cloak">
|
||||||
<div id="preloader" class="loader">
|
<div id="preloader" class="loader">
|
||||||
<div class="loader-inner">
|
<div class="loader-inner">
|
||||||
<div class="loader-line-wrap"><div class="loader-line"></div></div>
|
<div class="loader-line-wrap"><div class="loader-line"></div></div>
|
||||||
|
|||||||
@@ -4,8 +4,21 @@
|
|||||||
<link rel="stylesheet" href="{{ url_for('.static', filename='css/' ~ arg['sub'] ~ '.css') }}"/>
|
<link rel="stylesheet" href="{{ url_for('.static', filename='css/' ~ arg['sub'] ~ '.css') }}"/>
|
||||||
|
|
||||||
|
|
||||||
<div class="content-cloak ohli24-list-page">
|
<div id="ohli24_list_wrapper" class="ohli24-common-wrapper container-fluid mt-4 content-cloak ohli24-list-page">
|
||||||
<form id="form_search" class="form-inline" style="text-align:left; width:100%;">
|
<div class="glass-card p-4">
|
||||||
|
<div class="ohli24-header">
|
||||||
|
<div class="ohli24-header-left">
|
||||||
|
<div class="ohli24-icon-box">
|
||||||
|
<i class="bi bi-collection-play-fill text-primary" style="font-size: 1.5rem;"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="ohli24-header-title">다운로드 목록</h3>
|
||||||
|
<span class="ohli24-header-subtitle">성공적으로 완료된 에피소드 라이브러리</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr style="border-color: rgba(255,255,255,0.05); margin-bottom: 25px;">
|
||||||
|
<form id="form_search" class="form-inline mb-4" style="text-align:left; width:100%;">
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<div class="search-group-left">
|
<div class="search-group-left">
|
||||||
<select id="order" name="order" class="form-control custom-select">
|
<select id="order" name="order" class="form-control custom-select">
|
||||||
@@ -24,16 +37,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div id='page1'></div>
|
<div id='page1' class="mb-3"></div>
|
||||||
{# {{ macros.m_hr_head_top() }} #}
|
|
||||||
{# {{ macros.m_row_start('0') }} #}
|
|
||||||
{# {{ macros.m_col(2, macros.m_strong('Poster')) }} #}
|
|
||||||
{# {{ macros.m_col(10, macros.m_strong('Info')) }} #}
|
|
||||||
{# {{ macros.m_row_end() }} #}
|
|
||||||
{# {{ macros.m_hr_head_bottom() }} #}
|
|
||||||
<div id="list_div"></div>
|
<div id="list_div"></div>
|
||||||
<div id='page2'></div>
|
<div id='page2' class="mt-3"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Custom Confirmation Modal -->
|
<!-- Custom Confirmation Modal -->
|
||||||
<div class="modal fade" id="confirmModal" tabindex="-1" role="dialog" aria-hidden="true">
|
<div class="modal fade" id="confirmModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
@@ -120,6 +128,14 @@
|
|||||||
var current_data = null;
|
var current_data = null;
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
// Force parent container to be fluid to allow full width
|
||||||
|
$("#main_container").removeClass("container").addClass("container-fluid");
|
||||||
|
|
||||||
|
// Smooth Load Trigger
|
||||||
|
setTimeout(function() {
|
||||||
|
$('.content-cloak, #menu_module_div, #menu_page_div').addClass('visible');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
global_sub_request_search('1');
|
global_sub_request_search('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,46 +4,232 @@
|
|||||||
<link rel="stylesheet" href="{{ url_for('.static', filename='css/' ~ arg['sub'] ~ '.css') }}"/>
|
<link rel="stylesheet" href="{{ url_for('.static', filename='css/' ~ arg['sub'] ~ '.css') }}"/>
|
||||||
|
|
||||||
|
|
||||||
<div class="queue-container content-cloak ohli24-queue-page">
|
<div id="ohli24_queue_wrapper" class="ohli24-common-wrapper container-fluid mt-4 content-cloak ohli24-queue-page">
|
||||||
<!-- 헤더 버튼 그룹 -->
|
<div class="glass-card p-4">
|
||||||
<div class="queue-header">
|
<!-- 헤더 버튼 그룹 -->
|
||||||
<div class="header-title">
|
<div class="ohli24-header">
|
||||||
<i class="fa fa-download"></i>
|
<div class="ohli24-header-left">
|
||||||
<span>다운로드 큐</span>
|
<div class="ohli24-icon-box">
|
||||||
<span class="queue-count" id="queue_count">0</span>
|
<i class="bi bi-cloud-download-fill text-primary" style="font-size: 1.5rem;"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="ohli24-header-title">다운로드 큐</h3>
|
||||||
|
<span class="ohli24-header-subtitle">총 <span id="queue_count" class="text-info font-weight-bold">0</span>개의 항목</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ohli24-header-right d-flex flex-wrap">
|
||||||
|
<button id="reset_btn" class="btn-modern btn-modern-danger mr-2 mb-2">
|
||||||
|
<i class="bi bi-arrow-counterclockwise mr-1"></i> 전체 초기화
|
||||||
|
</button>
|
||||||
|
<button id="delete_completed_btn" class="btn-modern btn-modern-warning mr-2 mb-2">
|
||||||
|
<i class="bi bi-trash mr-1"></i> 완료 항목 삭제
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-buttons">
|
|
||||||
<button id="reset_btn" class="queue-btn queue-btn-danger">
|
<!-- 다운로드 목록 -->
|
||||||
<i class="fa fa-refresh"></i> 초기화
|
<div id="download_list_div" class="queue-list d-flex flex-column gap-3"></div>
|
||||||
</button>
|
|
||||||
<button id="delete_completed_btn" class="queue-btn queue-btn-warning">
|
<!-- 빈 상태 표시 -->
|
||||||
<i class="fa fa-trash"></i> 완료 삭제
|
<div id="empty_state" class="empty-state d-flex flex-column align-items-center justify-content-center py-5" style="display: none; min-height: 300px;">
|
||||||
</button>
|
<div class="empty-icon-wrapper mb-4">
|
||||||
<button id="go_ffmpeg_btn" class="queue-btn queue-btn-info">
|
<i class="bi bi-inbox-fill text-muted opacity-20" style="font-size: 5rem;"></i>
|
||||||
<i class="fa fa-external-link"></i> FFMPEG
|
</div>
|
||||||
</button>
|
<h4 class="text-white-50 font-weight-bold mb-2">다운로드 대기 항목 없음</h4>
|
||||||
|
<p class="text-muted mb-0">새로운 에피소드를 추가하면 이곳에 실시간으로 나타납니다.</p>
|
||||||
|
<div class="mt-4">
|
||||||
|
<span class="badge badge-pill badge-dark py-2 px-3" style="background: rgba(255,255,255,0.05); color: #64748b; font-weight: 500;">
|
||||||
|
<i class="bi bi-info-circle mr-1"></i> 에피소드 목록에서 '다운로드'를 눌러보세요
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 다운로드 목록 -->
|
|
||||||
<div id="download_list_div" class="queue-list"></div>
|
|
||||||
|
|
||||||
<!-- 빈 상태 표시 -->
|
|
||||||
<div id="empty_state" class="empty-state" style="display: none;">
|
|
||||||
<i class="fa fa-inbox"></i>
|
|
||||||
<p>다운로드 대기 중인 항목이 없습니다</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Queue specific tweaks */
|
/* Premium Glassmorphism UI */
|
||||||
#download_list_div { display: flex; flex-direction: column; gap: 10px; width: 100%; }
|
.ohli24-queue-page {
|
||||||
.queue-item { margin-bottom: 5px; }
|
animation: fadeIn 0.5s ease-out;
|
||||||
@media (max-width: 768px) {
|
}
|
||||||
.queue-item { padding: 10px; }
|
|
||||||
.item-info { flex: 1; }
|
@keyframes fadeIn {
|
||||||
.progress-wrapper { height: 24px; border-radius: 12px; }
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
.progress-label { font-size: 11px; }
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-box {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
border-radius: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modern Buttons */
|
||||||
|
.btn-modern {
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
text-transform: none;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-modern:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
filter: brightness(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-modern:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-modern-danger { background: linear-gradient(135deg, #f43f5e 0%, #e11d48 100%); }
|
||||||
|
.btn-modern-warning { background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); }
|
||||||
|
.btn-modern-info { background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%); }
|
||||||
|
.btn-modern-secondary { background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); }
|
||||||
|
|
||||||
|
/* Queue Items */
|
||||||
|
.queue-item {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border-color: rgba(255, 255, 255, 0.1);
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-number {
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-filename {
|
||||||
|
color: #f1f5f9;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-meta {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modern Progress Bar */
|
||||||
|
.progress-container {
|
||||||
|
flex: 1.5;
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-wrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 28px;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
border-radius: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 14px;
|
||||||
|
transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-waiting { background: linear-gradient(90deg, #64748b, #475569); }
|
||||||
|
.status-downloading {
|
||||||
|
background: linear-gradient(90deg, #3b82f6, #2563eb);
|
||||||
|
box-shadow: 0 0 10px rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
.status-completed {
|
||||||
|
background: linear-gradient(90deg, #10b981, #059669);
|
||||||
|
box-shadow: 0 0 10px rgba(16, 185, 129, 0.3);
|
||||||
|
}
|
||||||
|
.status-failed { background: linear-gradient(90deg, #ef4444, #dc2626); }
|
||||||
|
|
||||||
|
.progress-label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; width: 100%; height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
|
||||||
|
pointer-events: none;
|
||||||
|
padding: 0 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
.cancel-btn {
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||||
|
color: #f87171;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.queue-item { flex-direction: column; align-items: stretch; }
|
||||||
|
.progress-container { flex: none; width: 100%; }
|
||||||
|
.item-actions { display: flex; justify-content: flex-end; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
animation: fadeIn 0.8s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon-wrapper i {
|
||||||
|
display: inline-block;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state:hover .empty-icon-wrapper i {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
color: #3b82f6 !important;
|
||||||
|
opacity: 0.4 !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -54,6 +240,14 @@
|
|||||||
var current_data = null;
|
var current_data = null;
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
// Force parent container to be fluid to allow full width
|
||||||
|
$("#main_container").removeClass("container").addClass("container-fluid");
|
||||||
|
|
||||||
|
// Smooth Load Trigger
|
||||||
|
setTimeout(function() {
|
||||||
|
$('.content-cloak, #menu_module_div, #menu_page_div').addClass('visible');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
var socket = io.connect(window.location.href);
|
var socket = io.connect(window.location.href);
|
||||||
socket.on('on_start', (data) => {})
|
socket.on('on_start', (data) => {})
|
||||||
socket.on('start', function (data) {
|
socket.on('start', function (data) {
|
||||||
@@ -128,11 +322,23 @@
|
|||||||
|
|
||||||
var progressBar = document.getElementById("progress_" + entity_id);
|
var progressBar = document.getElementById("progress_" + entity_id);
|
||||||
if (progressBar != null) {
|
if (progressBar != null) {
|
||||||
|
// Update percentage and label
|
||||||
progressBar.style.width = percent + '%';
|
progressBar.style.width = percent + '%';
|
||||||
var label = status_kor;
|
var label = status_kor;
|
||||||
if (percent != 0) label += " (" + percent + "%)";
|
if (percent != 0) label += " (" + percent + "%)";
|
||||||
if (speed) label += " " + speed;
|
if (speed) label += " " + speed;
|
||||||
document.getElementById("progress_" + entity_id + "_label").innerHTML = label;
|
document.getElementById("progress_" + entity_id + "_label").innerHTML = label;
|
||||||
|
|
||||||
|
// Real-time status class (color) update
|
||||||
|
var statusClass = getStatusClass(status_kor);
|
||||||
|
$(progressBar).removeClass('status-waiting status-downloading status-completed status-failed').addClass(statusClass);
|
||||||
|
|
||||||
|
// Auto-refresh list if completed to show 'Watch' button or other actions
|
||||||
|
if (status_kor === '완료' || status_kor === 'completed') {
|
||||||
|
// Throttle refresh to avoid flickering if multiple complete
|
||||||
|
if (window.statusRefreshTimeout) clearTimeout(window.statusRefreshTimeout);
|
||||||
|
window.statusRefreshTimeout = setTimeout(on_start, 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,21 @@
|
|||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="yommi_wrapper" class="container-fluid ohli24-request-page mt-2 mt-md-4 mx-auto" style="max-width: 100%;">
|
<div id="ohli24_request_wrapper" class="ohli24-common-wrapper container-fluid mt-4 content-cloak ohli24-request-page">
|
||||||
<form id="program_list">
|
<form id="program_list">
|
||||||
<div class="card p-2 p-md-4 mb-2 mb-md-4 border-light" style="background: rgba(30,30,40,0.6); backdrop-filter: blur(10px); box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
|
<div class="glass-card p-4 mb-4">
|
||||||
|
<div class="ohli24-header">
|
||||||
|
<div class="ohli24-header-left">
|
||||||
|
<div class="ohli24-icon-box">
|
||||||
|
<i class="bi bi-play-circle-fill text-primary" style="font-size: 1.5rem;"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="ohli24-header-title">Ohli24 요청</h3>
|
||||||
|
<span class="ohli24-header-subtitle">작품 분석 및 개별 에피소드 다운로드</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr style="border-color: rgba(255,255,255,0.05); margin-bottom: 25px;">
|
||||||
<div class="form-group mb-0">
|
<div class="form-group mb-0">
|
||||||
<label for="code" class="text-white font-weight-bold mb-2"><i class="fa fa-search mr-2"></i>작품 Code / 제목</label>
|
<label for="code" class="text-white font-weight-bold mb-2"><i class="fa fa-search mr-2"></i>작품 Code / 제목</label>
|
||||||
<div class="input-group input-group-lg">
|
<div class="input-group input-group-lg">
|
||||||
@@ -126,7 +138,7 @@
|
|||||||
let str = "";
|
let str = "";
|
||||||
|
|
||||||
// 1. Info Card (Top)
|
// 1. Info Card (Top)
|
||||||
str += `<div class='card p-2 p-md-4 mb-3 mb-md-4 border-light' style="background: rgba(30,30,40,0.6); backdrop-filter: blur(10px);">`;
|
str += `<div class='glass-card p-4 mb-4'>`;
|
||||||
str += `<div class="row">`;
|
str += `<div class="row">`;
|
||||||
|
|
||||||
// Poster
|
// Poster
|
||||||
@@ -224,8 +236,13 @@
|
|||||||
}
|
}
|
||||||
str += '</div>';
|
str += '</div>';
|
||||||
|
|
||||||
|
// 먼저 HTML을 삽입하고 레이아웃이 완료된 후 bootstrapToggle 적용
|
||||||
document.getElementById("episode_list").innerHTML = str;
|
document.getElementById("episode_list").innerHTML = str;
|
||||||
$('input[id^="checkbox_"]').bootstrapToggle()
|
|
||||||
|
// 다음 프레임에서 bootstrapToggle 적용 (모든 카드가 먼저 표시됨)
|
||||||
|
requestAnimationFrame(function() {
|
||||||
|
$('input[id^="checkbox_"]').bootstrapToggle();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
@@ -746,16 +763,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ===== 상단 정보 카드 ===== */
|
/* ===== 상단 정보 카드 ===== */
|
||||||
.card.p-lg-5, .card.border-light {
|
.glass-card {
|
||||||
width: calc(100% - 10px) !important;
|
width: calc(100% - 10px) !important;
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
padding: 6px !important; /* Reduced from 15px */
|
padding: 10px !important;
|
||||||
margin: 5px !important;
|
margin: 5px !important;
|
||||||
border-radius: 12px !important;
|
border-radius: 12px !important;
|
||||||
box-sizing: border-box !important;
|
box-sizing: border-box !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card.p-lg-5 > .row {
|
.glass-card > .row {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
flex-direction: column !important;
|
flex-direction: column !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
|
|||||||
@@ -17,9 +17,23 @@
|
|||||||
<div id="ajax_loader" class="ajax-loader-container" style="display: none;">
|
<div id="ajax_loader" class="ajax-loader-container" style="display: none;">
|
||||||
<div class="ajax-spinner"></div>
|
<div class="ajax-spinner"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="yommi_wrapper" class="container-fluid mt-4 mx-auto content-cloak" style="max-width: 100%;">
|
<div id="ohli24_search_wrapper" class="ohli24-common-wrapper container-fluid mt-4 content-cloak ohli24-search-page">
|
||||||
<!-- Search Section -->
|
<!-- Search Section -->
|
||||||
<div class="card p-4 mb-4 border-0" style="background: rgba(30,30,40,0.6); backdrop-filter: blur(10px); border-radius: 16px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
|
<div class="glass-card p-4 mb-4">
|
||||||
|
<div class="ohli24-header">
|
||||||
|
<div class="ohli24-header-left">
|
||||||
|
<div class="ohli24-icon-box">
|
||||||
|
<i class="bi bi-search text-primary" style="font-size: 1.5rem;"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="ohli24-header-title">Ohli24 검색</h3>
|
||||||
|
<span class="ohli24-header-subtitle">애니메이션 검색 및 탐색</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr style="border-color: rgba(255,255,255,0.05); margin-bottom: 25px;">
|
||||||
|
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col-md-8 mx-auto">
|
<div class="col-md-8 mx-auto">
|
||||||
<div class="input-group input-group-lg">
|
<div class="input-group input-group-lg">
|
||||||
@@ -42,23 +56,23 @@
|
|||||||
|
|
||||||
<!-- Category Buttons -->
|
<!-- Category Buttons -->
|
||||||
<div class="row mt-4 justify-content-center">
|
<div class="row mt-4 justify-content-center">
|
||||||
<div id="anime_category" class="d-flex flex-wrap justify-content-center gap-2" role="group">
|
<div id="anime_category" class="d-flex flex-wrap justify-content-center" role="group">
|
||||||
<button id="ing" type="button" class="btn btn-success btn-pill px-4 mx-1 active active-glow">방영중</button>
|
<button id="ing" type="button" class="btn btn-success btn-pill px-4 mx-1 active active-glow mb-2">방영중</button>
|
||||||
<button id="fin" type="button" class="btn btn-outline-light btn-pill px-4 mx-1 active-glow">완결</button>
|
<button id="fin" type="button" class="btn btn-outline-light btn-pill px-4 mx-1 active-glow mb-2">완결</button>
|
||||||
<button id="theater" type="button" class="btn btn-outline-primary btn-pill px-4 mx-1 active-glow">극장판</button>
|
<button id="theater" type="button" class="btn btn-outline-primary btn-pill px-4 mx-1 active-glow mb-2">극장판</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Year Selection (Sub-category) -->
|
<!-- Year Selection (Sub-category) -->
|
||||||
<div id="year_filter_row" class="row mt-3 justify-content-center animate__animated animate__fadeIn" style="display: none;">
|
<div id="year_filter_row" class="row mt-3 justify-content-center animate__animated animate__fadeIn" style="display: none;">
|
||||||
<div id="year_category" class="d-flex flex-wrap justify-content-center gap-2" role="group">
|
<div id="year_category" class="d-flex flex-wrap justify-content-center" role="group">
|
||||||
<button data-sca="" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3 active">전체</button>
|
<button data-sca="" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3 mx-1 mb-2 active">전체</button>
|
||||||
<button data-sca="2025" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3">2025</button>
|
<button data-sca="2025" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3 mx-1 mb-2">2025</button>
|
||||||
<button data-sca="2024" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3">2024</button>
|
<button data-sca="2024" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3 mx-1 mb-2">2024</button>
|
||||||
<button data-sca="2023" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3">2023</button>
|
<button data-sca="2023" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3 mx-1 mb-2">2023</button>
|
||||||
<button data-sca="2022" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3">2022</button>
|
<button data-sca="2022" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3 mx-1 mb-2">2022</button>
|
||||||
<button data-sca="2021" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3">2021</button>
|
<button data-sca="2021" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3 mx-1 mb-2">2021</button>
|
||||||
<button data-sca="2020" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3">2020</button>
|
<button data-sca="2020" type="button" class="btn btn-sm btn-outline-secondary btn-pill px-3 mx-1 mb-2">2020</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,7 +82,7 @@
|
|||||||
<div id="airing_list"></div>
|
<div id="airing_list"></div>
|
||||||
</form>
|
</form>
|
||||||
<form id="screen_movie_list_form">
|
<form id="screen_movie_list_form">
|
||||||
<div id="screen_movie_list" class="container-fluid px-0"></div>
|
<div id="screen_movie_list"></div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form id="program_auto_form">
|
<form id="program_auto_form">
|
||||||
@@ -633,12 +647,7 @@
|
|||||||
document.addEventListener("scroll", debounce(onScroll, 300));
|
document.addEventListener("scroll", debounce(onScroll, 300));
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
/* Responsive Grid System */
|
||||||
font-family: 'NamumSquareNeo', system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Noto Sans, Liberation Sans, Arial, sans-serif;
|
|
||||||
background-image: linear-gradient(135deg, #1f2937, #111827, #0f172a);
|
|
||||||
color: #e2e8f0;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive Grid System */
|
/* Responsive Grid System */
|
||||||
.anime-grid {
|
.anime-grid {
|
||||||
@@ -661,55 +670,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Navigation Menu Override */
|
|
||||||
ul.nav.nav-pills.bg-light {
|
|
||||||
background-color: rgba(30, 41, 59, 0.6) !important;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
||||||
border-radius: 50rem !important;
|
|
||||||
padding: 6px !important;
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2) !important;
|
|
||||||
display: inline-flex !important;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
width: auto !important;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.nav.nav-pills .nav-item {
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.nav.nav-pills .nav-link {
|
|
||||||
border-radius: 50rem !important;
|
|
||||||
padding: 8px 20px !important;
|
|
||||||
color: #94a3b8 !important;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.nav.nav-pills .nav-link:hover {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
color: #fff !important;
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.nav.nav-pills .nav-link.active {
|
|
||||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
|
|
||||||
color: #fff !important;
|
|
||||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Glassmorphism Cards */
|
|
||||||
.glass-card {
|
|
||||||
background: rgba(30, 41, 59, 0.7) !important;
|
|
||||||
backdrop-filter: blur(12px) !important;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08) !important;
|
|
||||||
border-radius: 16px !important;
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease !important;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.glass-card:hover {
|
.glass-card:hover {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-5px);
|
||||||
@@ -894,39 +854,6 @@
|
|||||||
transition-delay: 300ms;
|
transition-delay: 300ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Navigation Menu Override (Top Sub-menu) */
|
|
||||||
ul.nav.nav-pills.bg-light {
|
|
||||||
background-color: rgba(30, 41, 59, 0.6) !important;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
||||||
border-radius: 50rem !important;
|
|
||||||
padding: 6px !important;
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2) !important;
|
|
||||||
display: inline-flex !important;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
width: auto !important;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.nav.nav-pills .nav-item { margin: 0 2px; }
|
|
||||||
ul.nav.nav-pills .nav-link {
|
|
||||||
border-radius: 50rem !important;
|
|
||||||
padding: 8px 20px !important;
|
|
||||||
color: #94a3b8 !important;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
ul.nav.nav-pills .nav-link:hover {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
color: #fff !important;
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
ul.nav.nav-pills .nav-link.active {
|
|
||||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
|
|
||||||
color: #fff !important;
|
|
||||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@@ -941,7 +868,7 @@ $(document).ready(function(){
|
|||||||
/* Mobile Margin Fix */
|
/* Mobile Margin Fix */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
body { overflow-x: hidden !important; padding: 0 !important; margin: 0 !important; }
|
body { overflow-x: hidden !important; padding: 0 !important; margin: 0 !important; }
|
||||||
.container, .container-fluid, .row, form, #program_list, #program_auto_form, #episode_list, .queue-container, #yommi_wrapper, #main_container {
|
.container, .container-fluid, .row, form, #program_list, #program_auto_form, #episode_list, .queue-container, #ohli24_search_wrapper, #main_container {
|
||||||
width: 100% !important; max-width: 100% !important;
|
width: 100% !important; max-width: 100% !important;
|
||||||
padding-left: 4px !important; padding-right: 4px !important;
|
padding-left: 4px !important; padding-right: 4px !important;
|
||||||
margin-left: 0 !important; margin-right: 0 !important;
|
margin-left: 0 !important; margin-right: 0 !important;
|
||||||
|
|||||||
@@ -3,12 +3,22 @@
|
|||||||
<link rel="stylesheet" href="{{ url_for('.static', filename='css/mobile_custom.css') }}"/>
|
<link rel="stylesheet" href="{{ url_for('.static', filename='css/mobile_custom.css') }}"/>
|
||||||
<link rel="stylesheet" href="{{ url_for('.static', filename='css/' ~ arg['sub'] ~ '.css') }}"/>
|
<link rel="stylesheet" href="{{ url_for('.static', filename='css/' ~ arg['sub'] ~ '.css') }}"/>
|
||||||
|
|
||||||
<div id="ohli24_setting_wrapper" class="container-fluid mt-4 mx-auto content-cloak" style="max-width: 100%;">
|
<div id="ohli24_setting_wrapper" class="ohli24-common-wrapper container-fluid mt-4 content-cloak">
|
||||||
|
|
||||||
<div class="glass-card p-4">
|
<div class="glass-card p-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="ohli24-header">
|
||||||
<h2 class="text-white font-weight-bold"><i class="bi bi-gear-fill mr-2"></i>Ohli24 설정</h2>
|
<div class="ohli24-header-left">
|
||||||
{{ macros.m_button_group([['globalSettingSaveBtn', '설정 저장']])}}
|
<div class="ohli24-icon-box">
|
||||||
|
<i class="bi bi-gear-fill text-primary" style="font-size: 1.5rem;"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="ohli24-header-title">Ohli24 설정</h3>
|
||||||
|
<span class="ohli24-header-subtitle">플러그인 설정 및 시스템 상태</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ohli24-header-right">
|
||||||
|
{{ macros.m_button_group([['globalSettingSaveBtn', '설정 저장']])}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ macros.m_row_start('5') }}
|
{{ macros.m_row_start('5') }}
|
||||||
@@ -161,105 +171,6 @@
|
|||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Smooth Load Transition */
|
|
||||||
.content-cloak,
|
|
||||||
#menu_module_div,
|
|
||||||
#menu_page_div {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.5s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Staggered Delays for Natural Top-Down Flow */
|
|
||||||
#menu_module_div.visible {
|
|
||||||
opacity: 1;
|
|
||||||
transition-delay: 0ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu_page_div.visible {
|
|
||||||
opacity: 1;
|
|
||||||
transition-delay: 150ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-cloak.visible {
|
|
||||||
opacity: 1;
|
|
||||||
transition-delay: 300ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navigation Menu Override (Top Sub-menu) */
|
|
||||||
ul.nav.nav-pills.bg-light {
|
|
||||||
background-color: rgba(30, 41, 59, 0.6) !important;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
||||||
border-radius: 50rem !important; /* Pill shape container */
|
|
||||||
padding: 6px !important;
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2) !important;
|
|
||||||
display: inline-flex !important; /* Fit content */
|
|
||||||
flex-wrap: wrap; /* allow wrap on small screens */
|
|
||||||
justify-content: center;
|
|
||||||
width: auto !important; /* Prevent full width */
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.nav.nav-pills .nav-item {
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.nav.nav-pills .nav-link {
|
|
||||||
border-radius: 50rem !important;
|
|
||||||
padding: 8px 20px !important;
|
|
||||||
color: #94a3b8 !important; /* Muted text */
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.nav.nav-pills .nav-link:hover {
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
color: #fff !important;
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.nav.nav-pills .nav-link.active {
|
|
||||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
|
|
||||||
color: #fff !important;
|
|
||||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Global Background */
|
|
||||||
body {
|
|
||||||
font-family: 'NamumSquareNeo', system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Noto Sans, Liberation Sans, Arial, sans-serif;
|
|
||||||
background-image: linear-gradient(135deg, #1f2937, #111827, #0f172a);
|
|
||||||
color: #e2e8f0;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Glass Card Container */
|
|
||||||
.glass-card {
|
|
||||||
background: rgba(30, 41, 59, 0.7);
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tabs Styling */
|
|
||||||
.nav-tabs {
|
|
||||||
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-tabs .nav-link {
|
|
||||||
color: #94a3b8;
|
|
||||||
border: none;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-tabs .nav-link:hover {
|
|
||||||
color: #e2e8f0;
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-tabs .nav-link.active {
|
.nav-tabs .nav-link.active {
|
||||||
color: #60a5fa !important;
|
color: #60a5fa !important;
|
||||||
background: rgba(30, 41, 59, 0.8) !important;
|
background: rgba(30, 41, 59, 0.8) !important;
|
||||||
|
|||||||
Reference in New Issue
Block a user