Add shared web layout and scoped card styles

This commit is contained in:
Stu Leak 2025-11-30 12:39:22 -05:00
parent 324095c9e9
commit 99d72c4758
22 changed files with 401 additions and 318 deletions

13
docs/TODO.md Normal file
View File

@ -0,0 +1,13 @@
# Goondex TODO / DONE
## TODO
- [ ] Implement bulk studio import (`./goondex import all-studios`) with the same pagination/resume flow as the performer importer.
- [ ] Implement bulk scene import (`./goondex import all-scenes`) and wire the CLI/UI to the new data set.
- [ ] Build a movie ingest path (TPDB and/or Adult Empire) that feeds the `movies` tables and populates the movies pages.
- [ ] Align the web stack on a single CSS pipeline (deprecate legacy `style.css`, keep goondex + scoped component files).
- [ ] Add lightweight UI validation (lint/smoke tests) for navigation, modals, and search to catch regressions early.
## DONE
- [x] Split card styling into per-context files (base, performers, studios, scenes) and updated listing templates to use them.
- [x] Created shared task lists (`docs/TODO.md`, `docs/WEB_TODO.md`) to keep engineering and web work in sync.
- [x] Adult Empire scraper + TPDB merge support for performers (see `SESSION_SUMMARY_v0.1.0-dev4.md`).

11
docs/WEB_TODO.md Normal file
View File

@ -0,0 +1,11 @@
# Web TODO / DONE
## TODO
- [ ] Split remaining shared CSS (hero, navbar, search, stats, forms, modals) into scoped files and retire inline styles in templates.
- [ ] Migrate detail pages to the same CSS pipeline as listings (drop `style.css` in favor of scoped goondex files).
- [ ] Audit cards for movies once movie data lands and add a dedicated `cards-movie.css`.
- [ ] Add a short usage note for the GX component set (which classes/components we rely on and where).
## DONE
- [x] Added per-context card styles (`cards/card-base.css`, `cards/cards-performer.css`, `cards/cards-studio.css`, `cards/cards-scene.css`) and wired listing templates.
- [x] Cleaned up CSS imports in `internal/web/static/css/goondex.css` to reference real files only.

View File

@ -6,11 +6,11 @@ import (
"encoding/json"
"fmt"
"html/template"
"io/fs"
"net/http"
"os"
"strconv"
"time"
"io/fs"
"git.leaktechnologies.dev/stu/Goondex/internal/db"
import_service "git.leaktechnologies.dev/stu/Goondex/internal/import"
@ -59,10 +59,10 @@ func (s *Server) Start() error {
mux.Handle(
"/static/",
http.StripPrefix(
"/static/",
http.FileServer(http.FS(staticFS)),
),
http.StripPrefix(
"/static/",
http.FileServer(http.FS(staticFS)),
),
)
// ============================================================================
@ -127,6 +127,8 @@ func (s *Server) handleDashboard(w http.ResponseWriter, r *http.Request) {
movies, _ := movieStore.Search("")
data := map[string]interface{}{
"PageTitle": "Dashboard",
"ActivePage": "dashboard",
"PerformerCount": len(performers),
"StudioCount": len(studios),
"SceneCount": len(scenes),
@ -218,10 +220,12 @@ func (s *Server) handlePerformerList(w http.ResponseWriter, r *http.Request) {
}
data := map[string]interface{}{
"Performers": performersWithCounts,
"Query": query,
"Nationalities": nationalities,
"Genders": genders,
"PageTitle": "Performers",
"ActivePage": "performers",
"Performers": performersWithCounts,
"Query": query,
"Nationalities": nationalities,
"Genders": genders,
"SelectedNationality": nationalityFilter,
"SelectedGender": genderFilter,
}
@ -317,9 +321,12 @@ func (s *Server) handlePerformerDetail(w http.ResponseWriter, r *http.Request) {
scenes, _ := sceneStore.GetByPerformer(id)
data := map[string]interface{}{
"Performer": performer,
"SceneCount": sceneCount,
"Scenes": scenes,
"PageTitle": fmt.Sprintf("Performer: %s", performer.Name),
"ActivePage": "performers",
"Stylesheets": []string{"/static/css/style.css", "/static/css/goondex.css"},
"Performer": performer,
"SceneCount": sceneCount,
"Scenes": scenes,
}
s.templates.ExecuteTemplate(w, "performer_detail.html", data)
@ -344,12 +351,14 @@ func (s *Server) handleStudioList(w http.ResponseWriter, r *http.Request) {
for _, st := range studios {
count, _ := store.GetSceneCount(st.ID)
studiosWithCounts = append(studiosWithCounts,
StudioWithCount{Studio: st, SceneCount: count})
StudioWithCount{Studio: st, SceneCount: count})
}
data := map[string]interface{}{
"Studios": studiosWithCounts,
"Query": query,
"PageTitle": "Studios",
"ActivePage": "studios",
"Studios": studiosWithCounts,
"Query": query,
}
s.templates.ExecuteTemplate(w, "studios.html", data)
@ -373,8 +382,11 @@ func (s *Server) handleStudioDetail(w http.ResponseWriter, r *http.Request) {
sceneCount, _ := store.GetSceneCount(id)
data := map[string]interface{}{
"Studio": studio,
"SceneCount": sceneCount,
"PageTitle": fmt.Sprintf("Studio: %s", studio.Name),
"ActivePage": "studios",
"Stylesheets": []string{"/static/css/style.css", "/static/css/goondex.css"},
"Studio": studio,
"SceneCount": sceneCount,
}
s.templates.ExecuteTemplate(w, "studio_detail.html", data)
@ -408,12 +420,14 @@ func (s *Server) handleSceneList(w http.ResponseWriter, r *http.Request) {
}
scenesWithStudios = append(scenesWithStudios,
SceneWithStudio{Scene: sc, StudioName: studioName})
SceneWithStudio{Scene: sc, StudioName: studioName})
}
data := map[string]interface{}{
"Scenes": scenesWithStudios,
"Query": query,
"PageTitle": "Scenes",
"ActivePage": "scenes",
"Scenes": scenesWithStudios,
"Query": query,
}
s.templates.ExecuteTemplate(w, "scenes.html", data)
@ -448,11 +462,14 @@ func (s *Server) handleSceneDetail(w http.ResponseWriter, r *http.Request) {
}
data := map[string]interface{}{
"Scene": scene,
"Performers": performers,
"Tags": tags,
"Movies": movies,
"StudioName": studioName,
"PageTitle": fmt.Sprintf("Scene: %s", scene.Title),
"ActivePage": "scenes",
"Stylesheets": []string{"/static/css/style.css", "/static/css/goondex.css"},
"Scene": scene,
"Performers": performers,
"Tags": tags,
"Movies": movies,
"StudioName": studioName,
}
s.templates.ExecuteTemplate(w, "scene_detail.html", data)
@ -492,8 +509,10 @@ func (s *Server) handleMovieList(w http.ResponseWriter, r *http.Request) {
}
data := map[string]interface{}{
"Movies": moviesWithDetails,
"Query": query,
"PageTitle": "Movies",
"ActivePage": "movies",
"Movies": moviesWithDetails,
"Query": query,
}
s.templates.ExecuteTemplate(w, "movies.html", data)
@ -526,6 +545,8 @@ func (s *Server) handleMovieDetail(w http.ResponseWriter, r *http.Request) {
}
data := map[string]interface{}{
"PageTitle": fmt.Sprintf("Movie: %s", movie.Title),
"ActivePage": "movies",
"Movie": movie,
"Scenes": scenes,
"StudioName": studioName,
@ -594,7 +615,7 @@ func (s *Server) handleAPIImportPerformer(w http.ResponseWriter, r *http.Request
json.NewEncoder(w).Encode(APIResponse{
Success: true,
Message: fmt.Sprintf("Imported %d performer(s)", imported),
Data: map[string]int{"imported": imported, "found": len(performers)},
Data: map[string]int{"imported": imported, "found": len(performers)},
})
}
@ -644,7 +665,7 @@ func (s *Server) handleAPIImportStudio(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(APIResponse{
Success: true,
Message: fmt.Sprintf("Imported %d studio(s)", imported),
Data: map[string]int{"imported": imported, "found": len(studios)},
Data: map[string]int{"imported": imported, "found": len(studios)},
})
}
@ -737,7 +758,7 @@ func (s *Server) handleAPIImportScene(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(APIResponse{
Success: true,
Message: fmt.Sprintf("Imported %d scene(s)", imported),
Data: map[string]int{"imported": imported, "found": len(scenes)},
Data: map[string]int{"imported": imported, "found": len(scenes)},
})
}
@ -856,7 +877,7 @@ func (s *Server) handleAPIBulkImportPerformers(w http.ResponseWriter, r *http.Re
json.NewEncoder(w).Encode(APIResponse{
Success: true,
Message: fmt.Sprintf("Imported %d/%d performers", result.Imported, result.Total),
Data: result,
Data: result,
})
}
@ -886,7 +907,7 @@ func (s *Server) handleAPIBulkImportStudios(w http.ResponseWriter, r *http.Reque
json.NewEncoder(w).Encode(APIResponse{
Success: true,
Message: fmt.Sprintf("Imported %d/%d studios", result.Imported, result.Total),
Data: result,
Data: result,
})
}
@ -916,7 +937,7 @@ func (s *Server) handleAPIBulkImportScenes(w http.ResponseWriter, r *http.Reques
json.NewEncoder(w).Encode(APIResponse{
Success: true,
Message: fmt.Sprintf("Imported %d/%d scenes", result.Imported, result.Total),
Data: result,
Data: result,
})
}
@ -1084,6 +1105,6 @@ func (s *Server) handleAPIGlobalSearch(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(APIResponse{
Success: true,
Message: fmt.Sprintf("Found %d results", results["total"]),
Data: results,
Data: results,
})
}

View File

@ -0,0 +1,103 @@
/*
* GOONDEX CARD BASE
* Shared grid + card shell used by performer/studio/scene listings.
*/
:root {
--gx-card-thumb-ratio: 3 / 4;
--gx-card-min-width: 250px;
}
.gx-card-grid {
display: grid;
gap: 1.6rem;
padding: 1rem 0;
grid-template-columns: repeat(auto-fill, minmax(var(--gx-card-min-width), 1fr));
}
.gx-card {
background: var(--color-bg-card);
border: 1px solid var(--color-border-soft);
border-radius: var(--radius-soft);
overflow: hidden;
box-shadow: var(--shadow-elevated);
transition:
transform var(--transition),
box-shadow var(--transition),
border-color var(--transition);
cursor: pointer;
position: relative;
}
.gx-card:hover {
transform: translateY(-4px);
border-color: var(--color-brand);
box-shadow:
0 0 18px rgba(255, 79, 163, 0.28),
0 6px 24px rgba(0, 0, 0, 0.55);
}
.gx-card-thumb {
width: 100%;
aspect-ratio: var(--gx-card-thumb-ratio);
background-size: cover;
background-position: center;
filter: brightness(0.92);
transition: filter var(--transition-fast);
}
.gx-card:hover .gx-card-thumb {
filter: brightness(1);
}
.gx-card-body {
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.gx-card-title {
font-size: 1.1rem;
font-weight: 600;
background: linear-gradient(135deg, var(--color-text-primary), var(--color-header));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.gx-card-meta {
font-size: 0.85rem;
color: var(--color-text-secondary);
opacity: 0.9;
}
.gx-card-tags {
margin-top: 0.7rem;
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
}
.gx-card-tag {
padding: 0.2rem 0.55rem;
font-size: 0.75rem;
border-radius: var(--radius);
background: rgba(255, 79, 163, 0.08);
color: var(--color-brand);
border: 1px solid rgba(255, 79, 163, 0.25);
text-transform: uppercase;
letter-spacing: 0.03em;
}
@media (max-width: 550px) {
.gx-card-grid {
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
}
.gx-card-title {
font-size: 1rem;
}
}

View File

@ -0,0 +1,16 @@
/*
* GOONDEX MOVIE CARDS
* Poster-focused layout for movie listings.
*/
.movie-card-grid {
--gx-card-thumb-ratio: 2 / 3;
}
.movie-card .gx-card-meta + .gx-card-meta {
margin-top: 0.25rem;
}
.movie-card .gx-card-tags {
margin-top: 0.6rem;
}

View File

@ -0,0 +1,16 @@
/*
* GOONDEX PERFORMER CARDS
* Portrait-focused layout for performer listings.
*/
.performer-card-grid {
--gx-card-thumb-ratio: 3 / 4;
}
.performer-card .gx-card-meta + .gx-card-meta {
margin-top: 0.3rem;
}
.performer-card .gx-card-tags {
margin-top: 0.6rem;
}

View File

@ -0,0 +1,16 @@
/*
* GOONDEX SCENE CARDS
* Landscape-focused layout for scene listings.
*/
.scene-card-grid {
--gx-card-thumb-ratio: 16 / 9;
}
.scene-card .gx-card-meta + .gx-card-meta {
margin-top: 0.25rem;
}
.scene-card .gx-card-tags {
margin-top: 0.6rem;
}

View File

@ -0,0 +1,23 @@
/*
* GOONDEX STUDIO CARDS
* Studio listings with compact description support.
*/
.studio-card-grid {
--gx-card-thumb-ratio: 3 / 4;
}
.studio-card .studio-card-description {
margin-top: 0.5rem;
font-size: 0.85rem;
color: var(--color-text-secondary);
opacity: 0.85;
line-height: 1.4;
max-height: 2.8rem;
overflow: hidden;
text-overflow: ellipsis;
}
.studio-card .gx-card-tags {
margin-top: 0.6rem;
}

View File

@ -5,7 +5,6 @@
/* ===== GX COMPONENT LIBRARY ================================================= */
@import 'gx/GX_Button.css';
@import 'gx/GX_CardGrid.css';
@import 'gx/GX_Checkbox.css';
@import 'gx/GX_Input.css';
@import 'gx/GX_Loader.css';
@ -15,18 +14,18 @@
/* ===== LAYOUT & STRUCTURE =================================================== */
@import 'layout.css';
@import 'navbar.css';
@import 'sidepanels.css';
/* ===== PAGE-LEVEL COMPONENTS ================================================ */
@import 'hero.css';
@import 'stats.css';
/* ===== CORE COMPONENTS ===================================================== */
@import 'forms.css';
@import 'buttons.css';
@import 'components.css';
/* ===== CARDS (SCOPED BY CONTEXT) =========================================== */
@import 'cards/card-base.css';
@import 'cards/cards-performer.css';
@import 'cards/cards-studio.css';
@import 'cards/cards-scene.css';
@import 'cards/cards-movie.css';
/* ===== GLOBAL PAGE STYLES =================================================== */
@import 'pages.css';
/* ===== RESPONSIVE OVERRIDES (MOBILE/TABLET/HALF-SCREEN) ===================== */
@import 'responsive.css';

View File

@ -1,103 +1,11 @@
/*
* GX CARD GRID Performer / Studio / Scene cards
* Dark luxury aesthetic, Flamingo Pink medium glow, responsive columns
* GX CARD GRID (COMPAT)
* This file now re-exports the scoped card styles.
* Prefer importing /static/css/cards/*.css directly.
*/
/* WRAPPER */
.gx-card-grid {
display: grid;
gap: 1.6rem;
padding: 1rem 0;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
/* CARD */
.gx-card {
background: var(--color-bg-card);
border: 1px solid var(--color-border-soft);
border-radius: var(--radius-soft);
overflow: hidden;
box-shadow: var(--shadow-elevated);
transition: transform var(--transition),
box-shadow var(--transition),
border-color var(--transition);
cursor: pointer;
position: relative;
}
/* HOVER EFFECT */
.gx-card:hover {
transform: translateY(-4px);
border-color: var(--color-brand);
box-shadow: 0 0 18px rgba(255, 79, 163, 0.28),
0 6px 24px rgba(0, 0, 0, 0.55);
}
/* THUMBNAIL */
.gx-card-thumb {
width: 100%;
aspect-ratio: 3 / 4;
background-size: cover;
background-position: center;
filter: brightness(0.92);
transition: filter var(--transition-fast);
}
.gx-card:hover .gx-card-thumb {
filter: brightness(1);
}
/* CONTENT */
.gx-card-body {
padding: 1rem;
}
/* TITLE */
.gx-card-title {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.35rem;
background: linear-gradient(135deg, var(--color-text-primary), var(--color-header));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* SMALL META (scene count, category, etc.) */
.gx-card-meta {
font-size: 0.85rem;
color: var(--color-text-secondary);
opacity: 0.9;
}
/* TAGS inside cards (optional) */
.gx-card-tags {
margin-top: 0.8rem;
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
}
.gx-card-tag {
padding: 0.2rem 0.55rem;
font-size: 0.75rem;
border-radius: var(--radius);
background: rgba(255, 79, 163, 0.08);
color: var(--color-brand);
border: 1px solid rgba(255, 79, 163, 0.25);
text-transform: uppercase;
letter-spacing: 0.03em;
}
/* MOBILE OPTIMISATION */
@media (max-width: 550px) {
.gx-card-grid {
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
.gx-card-title {
font-size: 1rem;
}
}
@import '../cards/card-base.css';
@import '../cards/cards-performer.css';
@import '../cards/cards-studio.css';
@import '../cards/cards-scene.css';
@import '../cards/cards-movie.css';

View File

@ -1,7 +1,7 @@
<div class="gx-card-grid">
<div class="gx-card-grid performer-card-grid">
{{range .Performers}}
<div class="gx-card" onclick="location.href='/performers/{{.Performer.ID}}'">
<div class="gx-card performer-card" onclick="location.href='/performers/{{.Performer.ID}}'">
<div class="gx-card-thumb"
style="background-image: url('/static/img/performers/{{.Performer.ID}}.jpg');">

View File

@ -80,6 +80,20 @@ body {
justify-content: space-between;
}
/* Bootstrap navbar controls */
.navbar .navbar-toggler {
border-color: var(--color-border-soft);
padding: 0.35rem 0.5rem;
}
.navbar .navbar-toggler:focus {
box-shadow: none;
}
.navbar .navbar-toggler-icon {
filter: invert(1);
}
/* Logo image control */
.logo-img {
height: 42px;
@ -264,4 +278,17 @@ body {
.stats-grid {
grid-template-columns: 1fr;
}
.navbar .collapse {
padding-top: 0.75rem;
}
.nav-links {
flex-direction: column;
gap: 0.75rem;
}
.nav-links .nav-link {
padding: 0.35rem 0;
}
}

View File

@ -1,10 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Goondex - Dashboard</title>
<link rel="stylesheet" href="/static/css/goondex.css">
{{template "html-head" .}}
<style>
/* ==== LUXURY SIDE PANELS (A1 Medium 240px) ==== */
@ -89,18 +86,7 @@
<div class="main-wrapper">
<!-- NAVIGATION -->
<nav class="navbar">
<div class="container nav-inner">
<img src="/static/img/logo/GOONDEX_logo.png" class="logo-img">
<ul class="nav-links">
<li><a href="/" class="active">Dashboard</a></li>
<li><a href="/performers">Performers</a></li>
<li><a href="/studios">Studios</a></li>
<li><a href="/scenes">Scenes</a></li>
<li><a href="/movies">Movies</a></li>
</ul>
</div>
</nav>
{{template "navbar" .}}
<main class="container">
@ -254,6 +240,6 @@
<!-- EXISTING MODALS (unchanged, full code integrity kept) -->
{{/* Your modals remain exactly as before */}}
<script src="/static/js/app.js"></script>
{{template "html-scripts" .}}
</body>
</html>

View File

@ -0,0 +1,57 @@
{{define "html-head"}}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{if .PageTitle}}{{.PageTitle}} - Goondex{{else}}Goondex{{end}}</title>
<!-- Bootstrap (base layout) -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Goondex bespoke styles -->
{{if .Stylesheets}}
{{range .Stylesheets}}
<link rel="stylesheet" href="{{.}}">
{{end}}
{{else}}
<link rel="stylesheet" href="/static/css/goondex.css">
{{end}}
{{end}}
{{define "html-scripts"}}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/app.js"></script>
{{end}}
{{define "navbar"}}
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container nav-inner">
<a class="navbar-brand d-flex align-items-center" href="/">
<img src="/static/img/logo/GOONDEX_logo.png" class="logo-img" alt="Goondex logo">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav"
aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="mainNav">
<ul class="nav-links navbar-nav align-items-lg-center">
<li class="nav-item">
<a href="/" class="nav-link {{if eq .ActivePage "dashboard"}}active{{end}}">Dashboard</a>
</li>
<li class="nav-item">
<a href="/performers" class="nav-link {{if eq .ActivePage "performers"}}active{{end}}">Performers</a>
</li>
<li class="nav-item">
<a href="/studios" class="nav-link {{if eq .ActivePage "studios"}}active{{end}}">Studios</a>
</li>
<li class="nav-item">
<a href="/scenes" class="nav-link {{if eq .ActivePage "scenes"}}active{{end}}">Scenes</a>
</li>
<li class="nav-item">
<a href="/movies" class="nav-link {{if eq .ActivePage "movies"}}active{{end}}">Movies</a>
</li>
</ul>
</div>
</div>
</nav>
{{end}}

View File

@ -1,24 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Movie.Title}} - Goondex</title>
<link rel="stylesheet" href="/static/css/goondex.css">
{{template "html-head" .}}
</head>
<body>
<nav class="navbar">
<div class="container nav-inner">
<img src="/static/img/logo/GOONDEX_logo.png" class="logo-img">
<ul class="nav-links">
<li><a href="/">Dashboard</a></li>
<li><a href="/performers">Performers</a></li>
<li><a href="/studios">Studios</a></li>
<li><a href="/scenes">Scenes</a></li>
<li><a href="/movies" class="active">Movies</a></li>
</ul>
</div>
</nav>
{{template "navbar" .}}
<main class="container">
<div class="detail-header">
@ -85,11 +71,11 @@
{{if .Scenes}}
<section class="detail-section">
<h2>Scenes ({{len .Scenes}})</h2>
<div class="gx-card-grid scenes-grid">
<div class="gx-card-grid scene-card-grid">
{{range .Scenes}}
<div class="gx-card" onclick="location.href='/scenes/{{.ID}}'">
<div class="gx-card scene-card" onclick="location.href='/scenes/{{.ID}}'">
<div class="gx-card-thumb"
style="background-image: url('{{if .ImageURL}}{{.ImageURL}}{{else}}/static/img/placeholder-scene.jpg{{end}}'); aspect-ratio: 16/9; background-color: #1a1a1a;">
style="background-image: url('{{if .ImageURL}}{{.ImageURL}}{{else}}/static/img/placeholder-scene.jpg{{end}}'); background-color: #1a1a1a;">
</div>
<div class="gx-card-body">
@ -113,5 +99,6 @@
</section>
{{end}}
</main>
{{template "html-scripts" .}}
</body>
</html>

View File

@ -1,24 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Movies - Goondex</title>
<link rel="stylesheet" href="/static/css/goondex.css">
{{template "html-head" .}}
</head>
<body>
<nav class="navbar">
<div class="container nav-inner">
<img src="/static/img/logo/GOONDEX_logo.png" class="logo-img">
<ul class="nav-links">
<li><a href="/">Dashboard</a></li>
<li><a href="/performers">Performers</a></li>
<li><a href="/studios">Studios</a></li>
<li><a href="/scenes">Scenes</a></li>
<li><a href="/movies" class="active">Movies</a></li>
</ul>
</div>
</nav>
{{template "navbar" .}}
<main class="container">
<div class="page-header">
@ -30,9 +16,9 @@
</div>
{{if .Movies}}
<div class="gx-card-grid">
<div class="gx-card-grid movie-card-grid">
{{range .Movies}}
<div class="gx-card" onclick="location.href='/movies/{{.Movie.ID}}'">
<div class="gx-card movie-card" onclick="location.href='/movies/{{.Movie.ID}}'">
<div class="gx-card-thumb"
style="background-image: url('{{if .Movie.ImageURL}}{{.Movie.ImageURL}}{{else}}/static/img/placeholder-movie.jpg{{end}}'); background-color: #1a1a1a;">
</div>
@ -47,13 +33,13 @@
<div class="gx-card-meta">{{.SceneCount}} scenes</div>
{{if .StudioName}}
<div class="gx-card-meta" style="margin-top: 0.3rem;">
<div class="gx-card-meta">
🎬 {{.StudioName}}
</div>
{{end}}
{{if .Movie.Duration}}
<div class="gx-card-tags" style="margin-top: 0.6rem;">
<div class="gx-card-tags">
<span class="gx-card-tag">{{.Movie.Duration}} min</span>
{{if .Movie.Source}}
<span class="gx-card-tag">{{.Movie.Source}}</span>
@ -75,5 +61,6 @@
</div>
{{end}}
</main>
{{template "html-scripts" .}}
</body>
</html>

View File

@ -1,24 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Performer.Name}} - Goondex</title>
<link rel="stylesheet" href="/static/css/style.css">
{{template "html-head" .}}
</head>
<body>
<nav class="navbar">
<div class="container">
<h1 class="logo">Goondex</h1>
<ul class="nav-links">
<li><a href="/">Dashboard</a></li>
<li><a href="/performers" class="active">Performers</a></li>
<li><a href="/studios">Studios</a></li>
<li><a href="/scenes">Scenes</a></li>
<li><a href="/movies">Movies</a></li>
</ul>
</div>
</nav>
{{template "navbar" .}}
<main class="container">
<div class="breadcrumb">
@ -268,5 +254,6 @@
}
});
</script>
{{template "html-scripts" .}}
</body>
</html>

View File

@ -1,24 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Performers - Goondex</title>
<link rel="stylesheet" href="/static/css/goondex.css">
{{template "html-head" .}}
</head>
<body>
<nav class="navbar">
<div class="container nav-inner">
<img src="/static/img/logo/GOONDEX_logo.png" class="logo-img">
<ul class="nav-links">
<li><a href="/">Dashboard</a></li>
<li><a href="/performers" class="active">Performers</a></li>
<li><a href="/studios">Studios</a></li>
<li><a href="/scenes">Scenes</a></li>
<li><a href="/movies">Movies</a></li>
</ul>
</div>
</nav>
{{template "navbar" .}}
<main class="container">
<div class="page-header">
@ -45,9 +31,9 @@
</div>
{{if .Performers}}
<div class="gx-card-grid">
<div class="gx-card-grid performer-card-grid">
{{range .Performers}}
<div class="gx-card" onclick="location.href='/performers/{{.Performer.ID}}'">
<div class="gx-card performer-card" onclick="location.href='/performers/{{.Performer.ID}}'">
<div class="gx-card-thumb"
style="background-image: url('{{if .Performer.ImageURL}}{{.Performer.ImageURL}}{{else}}/static/img/placeholder-performer.jpg{{end}}'); background-color: #1a1a1a;">
</div>
@ -59,13 +45,13 @@
<div class="gx-card-meta">{{.SceneCount}} scenes</div>
{{if .Performer.Nationality}}
<div class="gx-card-meta" style="margin-top: 0.3rem;">
<div class="gx-card-meta">
{{if .CountryFlag}}{{.CountryFlag}}{{else}}🌍{{end}} {{.Performer.Nationality}}
</div>
{{end}}
{{if .Performer.Gender}}
<div class="gx-card-tags" style="margin-top: 0.6rem;">
<div class="gx-card-tags">
<span class="gx-card-tag">{{.Performer.Gender}}</span>
{{if .Performer.Source}}
<span class="gx-card-tag">{{.Performer.Source}}</span>
@ -87,5 +73,6 @@
</div>
{{end}}
</main>
{{template "html-scripts" .}}
</body>
</html>

View File

@ -1,24 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Scene.Title}} - Goondex</title>
<link rel="stylesheet" href="/static/css/style.css">
{{template "html-head" .}}
</head>
<body>
<nav class="navbar">
<div class="container">
<h1 class="logo">Goondex</h1>
<ul class="nav-links">
<li><a href="/">Dashboard</a></li>
<li><a href="/performers">Performers</a></li>
<li><a href="/studios">Studios</a></li>
<li><a href="/scenes" class="active">Scenes</a></li>
<li><a href="/movies">Movies</a></li>
</ul>
</div>
</nav>
{{template "navbar" .}}
<main class="container">
<div class="breadcrumb">
@ -136,6 +122,6 @@
</div>
</main>
<script src="/static/js/app.js"></script>
{{template "html-scripts" .}}
</body>
</html>

View File

@ -1,30 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Scenes - Goondex</title>
<link rel="stylesheet" href="/static/css/goondex.css">
<style>
/* Scene cards use 16:9 aspect ratio instead of 3:4 for performers */
.scenes-grid .gx-card-thumb {
aspect-ratio: 16 / 9;
}
</style>
{{template "html-head" .}}
</head>
<body>
<nav class="navbar">
<div class="container nav-inner">
<img src="/static/img/logo/GOONDEX_logo.png" class="logo-img">
<ul class="nav-links">
<li><a href="/">Dashboard</a></li>
<li><a href="/performers">Performers</a></li>
<li><a href="/studios">Studios</a></li>
<li><a href="/scenes" class="active">Scenes</a></li>
<li><a href="/movies">Movies</a></li>
</ul>
</div>
</nav>
{{template "navbar" .}}
<main class="container">
<div class="page-header">
@ -36,9 +16,9 @@
</div>
{{if .Scenes}}
<div class="gx-card-grid scenes-grid">
<div class="gx-card-grid scene-card-grid">
{{range .Scenes}}
<div class="gx-card" onclick="location.href='/scenes/{{.Scene.ID}}'">
<div class="gx-card scene-card" onclick="location.href='/scenes/{{.Scene.ID}}'">
<div class="gx-card-thumb"
style="background-image: url('{{if .Scene.ImageURL}}{{.Scene.ImageURL}}{{else}}/static/img/placeholder-scene.jpg{{end}}'); background-color: #1a1a1a;">
</div>
@ -51,12 +31,12 @@
{{end}}
{{if .StudioName}}
<div class="gx-card-meta" style="margin-top: 0.2rem;">
<div class="gx-card-meta">
🏢 {{.StudioName}}
</div>
{{end}}
<div class="gx-card-tags" style="margin-top: 0.6rem;">
<div class="gx-card-tags">
{{if .Scene.Code}}
<span class="gx-card-tag">{{.Scene.Code}}</span>
{{end}}
@ -79,5 +59,6 @@
</div>
{{end}}
</main>
{{template "html-scripts" .}}
</body>
</html>

View File

@ -1,24 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Studio.Name}} - Goondex</title>
<link rel="stylesheet" href="/static/css/style.css">
{{template "html-head" .}}
</head>
<body>
<nav class="navbar">
<div class="container">
<h1 class="logo">Goondex</h1>
<ul class="nav-links">
<li><a href="/">Dashboard</a></li>
<li><a href="/performers">Performers</a></li>
<li><a href="/studios" class="active">Studios</a></li>
<li><a href="/scenes">Scenes</a></li>
<li><a href="/movies">Movies</a></li>
</ul>
</div>
</nav>
{{template "navbar" .}}
<main class="container">
<div class="breadcrumb">
@ -73,5 +59,6 @@
{{end}}
</div>
</main>
{{template "html-scripts" .}}
</body>
</html>

View File

@ -1,24 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Studios - Goondex</title>
<link rel="stylesheet" href="/static/css/goondex.css">
{{template "html-head" .}}
</head>
<body>
<nav class="navbar">
<div class="container nav-inner">
<img src="/static/img/logo/GOONDEX_logo.png" class="logo-img">
<ul class="nav-links">
<li><a href="/">Dashboard</a></li>
<li><a href="/performers">Performers</a></li>
<li><a href="/studios" class="active">Studios</a></li>
<li><a href="/scenes">Scenes</a></li>
<li><a href="/movies">Movies</a></li>
</ul>
</div>
</nav>
{{template "navbar" .}}
<main class="container">
<div class="page-header">
@ -30,9 +16,9 @@
</div>
{{if .Studios}}
<div class="gx-card-grid">
<div class="gx-card-grid studio-card-grid">
{{range .Studios}}
<div class="gx-card" onclick="location.href='/studios/{{.Studio.ID}}'">
<div class="gx-card studio-card" onclick="location.href='/studios/{{.Studio.ID}}'">
<div class="gx-card-thumb"
style="background-image: url('{{if .Studio.ImageURL}}{{.Studio.ImageURL}}{{else}}/static/img/placeholder-studio.jpg{{end}}'); background-color: #1a1a1a;">
</div>
@ -42,13 +28,11 @@
<div class="gx-card-meta">{{.SceneCount}} scenes</div>
{{if .Studio.Description}}
<div class="gx-card-meta" style="margin-top: 0.5rem; font-size: 0.8rem; opacity: 0.8; line-height: 1.3; max-height: 2.6rem; overflow: hidden; text-overflow: ellipsis;">
{{.Studio.Description}}
</div>
<div class="gx-card-meta studio-card-description">{{.Studio.Description}}</div>
{{end}}
{{if .Studio.Source}}
<div class="gx-card-tags" style="margin-top: 0.6rem;">
<div class="gx-card-tags">
<span class="gx-card-tag">{{.Studio.Source}}</span>
</div>
{{end}}
@ -67,5 +51,6 @@
</div>
{{end}}
</main>
{{template "html-scripts" .}}
</body>
</html>