feat(frontend): admin dashboard + içerik kataloğu UI, realtime sync
- index.css: IBM Plex Sans + Bricolage Grotesque font'ları import edildi;
CSS custom property sistemi (--bg-base, --accent-main vb.) tanımlandı;
body'ye fixed radial gradient + grid overlay arka plan eklendi.
- main.tsx: MantineProvider tema güncellendi — IBM Plex Sans/Bricolage
Grotesque font ailesi, responsive heading boyutları, özel radius/shadow
değerleri ayarlandı.
- App.css: Gereksiz yorum temizlendi, stil yönetimi route-level CSS'e taşındı.
- MoviesPage.tsx (büyük güncelleme):
• Katalog görünümü: film/dizi grid, arama, sıralama, backdrop modal.
• Admin Dashboard görünümü: cache özeti, content istatistikleri, job
durum sayaçları, failed job listesi, cache expiry tablosu, metrics
(hit/miss oranı, kaynak dağılımı).
• Admin aksiyonlar: cache temizleme, cache ısıtma, başarısız job
yeniden kuyruklama, eski içerik yenileme.
• Socket.IO entegrasyonu: content:event dinlenerek katalog anlık
güncelleniyor; metrics:updated ile dashboard metrikleri canlı akıyor.
• Reconnect davranışı: bağlantı kopunca her görünüm kendi snapshot'ını
otomatik yeniliyor.
- movies-page.css (yeni): MoviesPage'e özel kart, backdrop, istatistik
kutusu ve animasyon stilleri.
- vite.config.ts: /socket.io proxy kuralı eklendi (ws: true) — dev
sunucusu üzerinden WebSocket bağlantısı backend'e yönlendiriliyor.
- frontend/.env.example (yeni): VITE_API_BASE_URL, VITE_WEB_API_KEY,
VITE_ADMIN_API_KEY değişken şablonu eklendi.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,2 +1 @@
|
||||
/* App styles - Mantine handles most styling */
|
||||
|
||||
/* Page-level styles are collocated in route-level css files. */
|
||||
|
||||
@@ -1,10 +1,51 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #1a1a1b;
|
||||
min-height: 100vh;
|
||||
@import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:wght@500;700;800&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--bg-base: #090a0e;
|
||||
--bg-alt: #10131d;
|
||||
--surface: rgba(19, 22, 33, 0.72);
|
||||
--surface-strong: rgba(27, 33, 48, 0.94);
|
||||
--text-main: #f4f5f8;
|
||||
--text-muted: #adb4c2;
|
||||
--line-soft: rgba(255, 255, 255, 0.12);
|
||||
--accent-main: #eb2338;
|
||||
--accent-warm: #ff9b42;
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100vh;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
scrollbar-gutter: stable both-edges;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--text-main);
|
||||
font-family: 'IBM Plex Sans', sans-serif;
|
||||
background:
|
||||
radial-gradient(1200px 700px at 18% -8%, rgba(235, 35, 56, 0.22), transparent 58%),
|
||||
radial-gradient(900px 640px at 90% 10%, rgba(255, 155, 66, 0.14), transparent 58%),
|
||||
linear-gradient(140deg, var(--bg-base), var(--bg-alt));
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
opacity: 0.22;
|
||||
z-index: -1;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
|
||||
background-size: 42px 42px, 42px 42px;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,28 @@ import { createRoot } from 'react-dom/client'
|
||||
import { MantineProvider, createTheme } from '@mantine/core'
|
||||
import '@mantine/core/styles.css'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
import App from './App'
|
||||
|
||||
const theme = createTheme({
|
||||
primaryColor: 'red',
|
||||
fontFamily: 'Inter, system-ui, Avenir, Helvetica, Arial, sans-serif',
|
||||
fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
headings: {
|
||||
fontFamily: '"Bricolage Grotesque", "IBM Plex Sans", sans-serif',
|
||||
sizes: {
|
||||
h1: { fontSize: 'clamp(2rem, 4vw, 3.6rem)', lineHeight: '1.02', fontWeight: '700' },
|
||||
h2: { fontSize: 'clamp(1.4rem, 2.6vw, 2.1rem)', lineHeight: '1.1', fontWeight: '700' },
|
||||
h3: { fontSize: '1.12rem', lineHeight: '1.2', fontWeight: '650' },
|
||||
},
|
||||
},
|
||||
radius: {
|
||||
md: '14px',
|
||||
lg: '20px',
|
||||
xl: '28px',
|
||||
},
|
||||
shadows: {
|
||||
md: '0 18px 45px rgba(0, 0, 0, 0.28)',
|
||||
xl: '0 30px 70px rgba(0, 0, 0, 0.36)',
|
||||
},
|
||||
})
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
431
frontend/src/pages/movies-page.css
Normal file
431
frontend/src/pages/movies-page.css
Normal file
@@ -0,0 +1,431 @@
|
||||
.catalog-page {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.catalog-hero {
|
||||
background:
|
||||
linear-gradient(130deg, rgba(235, 35, 56, 0.14), rgba(13, 17, 28, 0.96) 48%),
|
||||
radial-gradient(circle at 80% 18%, rgba(255, 155, 66, 0.2), transparent 45%);
|
||||
border-color: rgba(255, 255, 255, 0.14);
|
||||
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.68);
|
||||
}
|
||||
|
||||
.stats-wrap {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.stat-pill {
|
||||
min-width: 88px;
|
||||
text-align: right;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
position: sticky;
|
||||
top: 16px;
|
||||
z-index: 20;
|
||||
backdrop-filter: blur(8px);
|
||||
background: rgba(14, 18, 28, 0.82);
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.admin-panel {
|
||||
background: linear-gradient(150deg, rgba(15, 20, 32, 0.88), rgba(11, 14, 23, 0.92));
|
||||
border-color: rgba(255, 255, 255, 0.13);
|
||||
}
|
||||
|
||||
.view-nav {
|
||||
position: sticky;
|
||||
top: 12px;
|
||||
z-index: 30;
|
||||
backdrop-filter: blur(10px);
|
||||
background: rgba(10, 15, 26, 0.84);
|
||||
border-color: rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
|
||||
.catalog-view-shell {
|
||||
padding-inline: clamp(0.5rem, 1.4vw, 1.35rem);
|
||||
}
|
||||
|
||||
.nav-pill {
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
transition: transform 180ms ease, border-color 180ms ease, background-color 180ms ease;
|
||||
}
|
||||
|
||||
.nav-pill .mantine-Button-label,
|
||||
.nav-pill .mantine-Button-section {
|
||||
transition: color 180ms ease, opacity 180ms ease;
|
||||
}
|
||||
|
||||
.nav-pill.is-active {
|
||||
border-color: rgba(235, 35, 56, 0.95);
|
||||
box-shadow: 0 0 0 1px rgba(235, 35, 56, 0.26) inset;
|
||||
}
|
||||
|
||||
.nav-pill.is-active .mantine-Button-label,
|
||||
.nav-pill.is-active .mantine-Button-section {
|
||||
color: #f8fbff;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.nav-pill.is-inactive {
|
||||
border-color: rgba(255, 255, 255, 0.24);
|
||||
}
|
||||
|
||||
.nav-pill.is-inactive .mantine-Button-label,
|
||||
.nav-pill.is-inactive .mantine-Button-section {
|
||||
color: rgba(218, 226, 240, 0.86);
|
||||
opacity: 0.94;
|
||||
}
|
||||
|
||||
.nav-pill:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: rgba(255, 155, 66, 0.45);
|
||||
}
|
||||
|
||||
.nav-pill.is-inactive:hover .mantine-Button-label,
|
||||
.nav-pill.is-inactive:hover .mantine-Button-section {
|
||||
color: rgba(246, 250, 255, 0.96);
|
||||
}
|
||||
|
||||
.admin-card {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.list-row {
|
||||
padding: 8px 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.01);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.cache-key-text {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
min-width: 260px;
|
||||
}
|
||||
|
||||
.genre-chip {
|
||||
cursor: pointer;
|
||||
border-color: rgba(255, 255, 255, 0.22);
|
||||
transition: transform 140ms ease, box-shadow 140ms ease, border-color 140ms ease;
|
||||
}
|
||||
|
||||
.genre-chip:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.genre-chip:focus-visible {
|
||||
outline: 2px solid rgba(255, 155, 66, 0.95);
|
||||
outline-offset: 2px;
|
||||
border-color: rgba(255, 155, 66, 0.95);
|
||||
}
|
||||
|
||||
.catalog-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background: linear-gradient(165deg, rgba(29, 33, 44, 0.95), rgba(15, 19, 30, 0.96));
|
||||
border-color: rgba(255, 255, 255, 0.11);
|
||||
transition: transform 220ms ease, box-shadow 220ms ease;
|
||||
}
|
||||
|
||||
.catalog-card:hover {
|
||||
transform: translateY(-7px);
|
||||
box-shadow: 0 28px 65px rgba(0, 0, 0, 0.42);
|
||||
}
|
||||
|
||||
.catalog-card:focus-visible {
|
||||
outline: 2px solid rgba(255, 155, 66, 0.92);
|
||||
outline-offset: 3px;
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 24px 54px rgba(0, 0, 0, 0.42);
|
||||
}
|
||||
|
||||
.live-fade-in {
|
||||
animation: live-fade-in 1500ms ease;
|
||||
}
|
||||
|
||||
@keyframes live-fade-in {
|
||||
0% {
|
||||
opacity: 0.55;
|
||||
transform: translateY(10px) scale(0.985);
|
||||
box-shadow: 0 0 0 rgba(235, 35, 56, 0), 0 0 0 rgba(255, 214, 102, 0);
|
||||
}
|
||||
20% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1.004);
|
||||
box-shadow: 0 0 0 2px rgba(255, 214, 102, 0.56), 0 0 28px rgba(255, 214, 102, 0.42);
|
||||
}
|
||||
55% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
box-shadow: 0 0 0 2px rgba(235, 35, 56, 0.35), 0 0 20px rgba(235, 35, 56, 0.22);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
box-shadow: 0 0 0 rgba(235, 35, 56, 0), 0 0 0 rgba(255, 214, 102, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.live-ttl-flash {
|
||||
animation: ttl-flash 1050ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
@keyframes ttl-flash {
|
||||
0% {
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
box-shadow: inset 0 0 0 rgba(0, 0, 0, 0);
|
||||
background: rgba(255, 255, 255, 0.01);
|
||||
}
|
||||
38% {
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.34), inset 0 0 28px rgba(0, 0, 0, 0.42);
|
||||
background: linear-gradient(90deg, rgba(0, 0, 0, 0.32), rgba(255, 255, 255, 0.012));
|
||||
}
|
||||
100% {
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
box-shadow: inset 0 0 0 rgba(0, 0, 0, 0);
|
||||
background: rgba(255, 255, 255, 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
.media-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-shell {
|
||||
position: relative;
|
||||
height: 190px;
|
||||
}
|
||||
|
||||
.image-skeleton {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.lazy-image {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.lazy-image img {
|
||||
filter: blur(14px);
|
||||
transform: scale(1.05);
|
||||
opacity: 0.86;
|
||||
transition: filter 320ms ease, transform 420ms ease, opacity 240ms ease;
|
||||
}
|
||||
|
||||
.lazy-image.is-loaded img {
|
||||
filter: blur(0);
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.media-fallback {
|
||||
background: linear-gradient(160deg, rgba(35, 38, 52, 0.95), rgba(24, 28, 39, 0.95));
|
||||
color: rgba(255, 255, 255, 0.34);
|
||||
}
|
||||
|
||||
.media-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(to top, rgba(8, 10, 16, 0.86), rgba(8, 10, 16, 0.05) 55%);
|
||||
}
|
||||
|
||||
.detail-modal-content {
|
||||
background: linear-gradient(150deg, rgba(16, 20, 31, 0.97), rgba(11, 14, 24, 0.97));
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.detail-modal-header {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.detail-modal-body {
|
||||
padding-top: 0.6rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.detail-media-wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.detail-media-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(to top, rgba(8, 10, 16, 0.92), rgba(8, 10, 16, 0.1) 58%),
|
||||
radial-gradient(circle at 76% 22%, rgba(235, 35, 56, 0.22), transparent 42%);
|
||||
}
|
||||
|
||||
.detail-title-group {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
bottom: 14px;
|
||||
right: 16px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
color: #f5f8fc;
|
||||
text-shadow: 0 5px 18px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.detail-plot {
|
||||
line-height: 1.62;
|
||||
}
|
||||
|
||||
.detail-brand-stamp {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: 16px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
object-fit: contain;
|
||||
opacity: 0.96;
|
||||
filter: drop-shadow(0 7px 16px rgba(0, 0, 0, 0.44));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-family: 'Bricolage Grotesque', 'IBM Plex Sans', sans-serif;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.brand-stamp {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
object-fit: contain;
|
||||
opacity: 0.92;
|
||||
filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.38));
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
background: linear-gradient(145deg, rgba(22, 26, 37, 0.9), rgba(14, 17, 26, 0.95));
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.admin-toast {
|
||||
position: fixed;
|
||||
right: 22px;
|
||||
bottom: 22px;
|
||||
z-index: 80;
|
||||
max-width: min(420px, calc(100vw - 24px));
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(84, 224, 187, 0.4);
|
||||
background: linear-gradient(145deg, rgba(8, 40, 42, 0.95), rgba(10, 20, 32, 0.95));
|
||||
color: #dffcf2;
|
||||
font-size: 0.92rem;
|
||||
box-shadow: 0 18px 38px rgba(0, 0, 0, 0.35);
|
||||
animation: toast-in 180ms ease;
|
||||
}
|
||||
|
||||
@keyframes toast-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
animation: reveal-in 420ms ease forwards;
|
||||
}
|
||||
|
||||
@keyframes reveal-in {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
.view-nav {
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.view-nav > div {
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.catalog-view-shell {
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
.stats-wrap {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.admin-toast {
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.reveal,
|
||||
.catalog-card,
|
||||
.genre-chip,
|
||||
.lazy-image img,
|
||||
.live-fade-in,
|
||||
.live-ttl-flash {
|
||||
animation: none;
|
||||
transition: none;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user