Consolidate examples into unified production config with static CSS
- New sqlite_serve.conf: all features on single port 8080 - New zenburn.css: hex color palette, all styles in one cacheable file - New Zenburn templates: header/footer/card partials - New start.sh: unified launcher with all endpoint docs - Removed 3 separate example configs and start scripts 15 files changed, 980 insertions(+), 258 deletions(-)
This commit is contained in:
@ -1,140 +1,50 @@
|
||||
{{> header}}
|
||||
{{> zenburn_header}}
|
||||
|
||||
<style>
|
||||
.book-detail {
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
.book-cover {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
height: 300px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 3rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.book-title {
|
||||
font-size: 2.5rem;
|
||||
color: #2d3748;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.book-author {
|
||||
font-size: 1.5rem;
|
||||
color: #667eea;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.book-meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.meta-item {
|
||||
background: #f8f9fa;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
.meta-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.meta-value {
|
||||
font-size: 1.25rem;
|
||||
color: #2d3748;
|
||||
font-weight: 600;
|
||||
}
|
||||
.book-description {
|
||||
background: #f8f9fa;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
line-height: 1.8;
|
||||
font-size: 1.125rem;
|
||||
color: #4a5568;
|
||||
}
|
||||
.rating {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.stars {
|
||||
color: #fbbf24;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
margin-top: 2rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.back-link:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.not-found {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
</style>
|
||||
<div class="detail-container">
|
||||
<div class="breadcrumb">
|
||||
<a href="/books">← Back to catalog</a>
|
||||
</div>
|
||||
|
||||
{{#if results.[0]}}
|
||||
{{#with results.[0]}}
|
||||
<div class="book-detail">
|
||||
<div class="book-cover">
|
||||
📖
|
||||
</div>
|
||||
|
||||
<h1 class="book-title">{{title}}</h1>
|
||||
<p class="book-author">by {{author}}</p>
|
||||
|
||||
<div class="book-meta">
|
||||
<div class="meta-item">
|
||||
<div class="meta-label">Rating</div>
|
||||
<div class="meta-value rating">
|
||||
<span class="stars">⭐</span>
|
||||
{{rating}} / 5.0
|
||||
{{#if results.[0]}}
|
||||
{{#with results.[0]}}
|
||||
<div class="book-hero">
|
||||
<h1 class="book-hero-title">{{title}}</h1>
|
||||
<p class="book-hero-author">by {{author}}</p>
|
||||
|
||||
<div class="meta-grid">
|
||||
<div class="meta-item">
|
||||
<div class="meta-label">Rating</div>
|
||||
<div class="meta-value rating">⭐ {{rating}}</div>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<div class="meta-label">Published</div>
|
||||
<div class="meta-value">{{year}}</div>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<div class="meta-label">Genre</div>
|
||||
<div class="meta-value">{{genre}}</div>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<div class="meta-label">ISBN</div>
|
||||
<div class="meta-value" style="font-size: 1rem; font-family: monospace;">{{isbn}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<div class="meta-label">Published</div>
|
||||
<div class="meta-value">{{year}}</div>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<div class="meta-label">Genre</div>
|
||||
<div class="meta-value">{{genre}}</div>
|
||||
</div>
|
||||
<div class="meta-item">
|
||||
<div class="meta-label">ISBN</div>
|
||||
<div class="meta-value" style="font-family: monospace; font-size: 1rem;">{{isbn}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="book-description">
|
||||
<strong>About this book:</strong><br><br>
|
||||
{{description}}
|
||||
|
||||
<div class="description-section">
|
||||
<h3>About This Book</h3>
|
||||
<p class="description-text">{{description}}</p>
|
||||
</div>
|
||||
|
||||
<a href="/books/all" class="back-link">← Back to Catalog</a>
|
||||
</div>
|
||||
{{/with}}
|
||||
{{else}}
|
||||
<div class="not-found">
|
||||
<h2>Book Not Found</h2>
|
||||
<p>The requested book could not be found in our catalog.</p>
|
||||
<a href="/books/all" class="back-link">← Back to Catalog</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{> footer}}
|
||||
<a href="/books" class="back-button">← Back to Catalog</a>
|
||||
{{/with}}
|
||||
{{else}}
|
||||
<div class="not-found">
|
||||
<h2>Book Not Found</h2>
|
||||
<p>The requested book could not be found in our catalog.</p>
|
||||
<a href="/books" class="back-button">← Back to Catalog</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{> zenburn_footer}}
|
||||
|
||||
37
server_root/books/catalog.hbs
Normal file
37
server_root/books/catalog.hbs
Normal file
@ -0,0 +1,37 @@
|
||||
{{> zenburn_header}}
|
||||
|
||||
<div class="page-header">
|
||||
<h2>Book Catalog</h2>
|
||||
<p>Browse our curated collection of technical literature</p>
|
||||
</div>
|
||||
|
||||
{{#if results}}
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{#each results}}{{#if @last}}{{@index}}{{/if}}{{/each}}</span>
|
||||
<span class="stat-label">Books Found</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">📚</span>
|
||||
<span class="stat-label">Categories</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">⭐</span>
|
||||
<span class="stat-label">Curated</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="book-grid">
|
||||
{{#each results}}
|
||||
{{> zenburn_card}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<h3>No Books Found</h3>
|
||||
<p>Try adjusting your search or <a href="/books">browse all books</a></p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{> zenburn_footer}}
|
||||
|
||||
37
server_root/era/catalog.hbs
Normal file
37
server_root/era/catalog.hbs
Normal file
@ -0,0 +1,37 @@
|
||||
{{> zenburn_header}}
|
||||
|
||||
<div class="page-header">
|
||||
<h2>Book Catalog</h2>
|
||||
<p>Browse our curated collection of technical literature</p>
|
||||
</div>
|
||||
|
||||
{{#if results}}
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{#each results}}{{#if @last}}{{@index}}{{/if}}{{/each}}</span>
|
||||
<span class="stat-label">Books Found</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">📚</span>
|
||||
<span class="stat-label">Categories</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">⭐</span>
|
||||
<span class="stat-label">Curated</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="book-grid">
|
||||
{{#each results}}
|
||||
{{> zenburn_card}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<h3>No Books Found</h3>
|
||||
<p>Try adjusting your search or <a href="/books">browse all books</a></p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{> zenburn_footer}}
|
||||
|
||||
37
server_root/genre/catalog.hbs
Normal file
37
server_root/genre/catalog.hbs
Normal file
@ -0,0 +1,37 @@
|
||||
{{> zenburn_header}}
|
||||
|
||||
<div class="page-header">
|
||||
<h2>Book Catalog</h2>
|
||||
<p>Browse our curated collection of technical literature</p>
|
||||
</div>
|
||||
|
||||
{{#if results}}
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{#each results}}{{#if @last}}{{@index}}{{/if}}{{/each}}</span>
|
||||
<span class="stat-label">Books Found</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">📚</span>
|
||||
<span class="stat-label">Categories</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">⭐</span>
|
||||
<span class="stat-label">Curated</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="book-grid">
|
||||
{{#each results}}
|
||||
{{> zenburn_card}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<h3>No Books Found</h3>
|
||||
<p>Try adjusting your search or <a href="/books">browse all books</a></p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{> zenburn_footer}}
|
||||
|
||||
15
server_root/global_templates/zenburn_card.hbs
Normal file
15
server_root/global_templates/zenburn_card.hbs
Normal file
@ -0,0 +1,15 @@
|
||||
<article class="book-card">
|
||||
<div class="book-header">
|
||||
<h3 class="book-title">{{title}}</h3>
|
||||
<div class="book-rating">⭐ {{rating}}</div>
|
||||
</div>
|
||||
<p class="book-author">{{author}}</p>
|
||||
<p class="book-description">{{description}}</p>
|
||||
<div class="book-meta">
|
||||
<span class="genre-badge">{{genre}}</span>
|
||||
<span class="year-badge">{{year}}</span>
|
||||
{{#if isbn}}<span class="isbn">{{isbn}}</span>{{/if}}
|
||||
</div>
|
||||
<a href="/book?id={{id}}" class="detail-link">View Details →</a>
|
||||
</article>
|
||||
|
||||
9
server_root/global_templates/zenburn_footer.hbs
Normal file
9
server_root/global_templates/zenburn_footer.hbs
Normal file
@ -0,0 +1,9 @@
|
||||
</main>
|
||||
<footer>
|
||||
<p>sqlite-serve v0.1.0</p>
|
||||
<p>Rust + NGINX + SQLite + Handlebars</p>
|
||||
<p>45 tests | Type-safe | Read-only</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
31
server_root/global_templates/zenburn_header.hbs
Normal file
31
server_root/global_templates/zenburn_header.hbs
Normal file
@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>sqlite-serve | Book Catalog</title>
|
||||
<link rel="stylesheet" href="/static/zenburn.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<h1>📚 sqlite-serve</h1>
|
||||
<p>Dynamic content from SQLite via NGINX</p>
|
||||
</div>
|
||||
</header>
|
||||
<nav>
|
||||
<div class="nav-content">
|
||||
<a href="/books">All Books</a>
|
||||
<a href="/genre?genre=Programming">Programming</a>
|
||||
<a href="/genre?genre=Databases">Databases</a>
|
||||
<a href="/genre?genre=Computer%20Science">Computer Science</a>
|
||||
<a href="/top?min=4.7">Top Rated</a>
|
||||
<a href="/era?from=2015&to=2024">Recent (2015-2024)</a>
|
||||
<form class="search-form" action="/search" method="get">
|
||||
<input type="text" name="q" placeholder="Search titles..." />
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
<main>
|
||||
|
||||
37
server_root/search/catalog.hbs
Normal file
37
server_root/search/catalog.hbs
Normal file
@ -0,0 +1,37 @@
|
||||
{{> zenburn_header}}
|
||||
|
||||
<div class="page-header">
|
||||
<h2>Book Catalog</h2>
|
||||
<p>Browse our curated collection of technical literature</p>
|
||||
</div>
|
||||
|
||||
{{#if results}}
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{#each results}}{{#if @last}}{{@index}}{{/if}}{{/each}}</span>
|
||||
<span class="stat-label">Books Found</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">📚</span>
|
||||
<span class="stat-label">Categories</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">⭐</span>
|
||||
<span class="stat-label">Curated</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="book-grid">
|
||||
{{#each results}}
|
||||
{{> zenburn_card}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<h3>No Books Found</h3>
|
||||
<p>Try adjusting your search or <a href="/books">browse all books</a></p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{> zenburn_footer}}
|
||||
|
||||
509
server_root/static/zenburn.css
Normal file
509
server_root/static/zenburn.css
Normal file
@ -0,0 +1,509 @@
|
||||
/* Zenburn theme for sqlite-serve */
|
||||
/* Generated palette via: zenburn -j -e oklab */
|
||||
|
||||
:root {
|
||||
--fg-plus-2: #FFFFEF;
|
||||
--fg-plus-1: #F5F5D6;
|
||||
--fg: #DCDCCC;
|
||||
--fg-1: #A6A689;
|
||||
--fg-2: #656555;
|
||||
--black: #000000;
|
||||
--bg-2: #000000;
|
||||
--bg-1: #111112;
|
||||
--bg-05: #383838;
|
||||
--bg: #2A2B2E;
|
||||
--bg-plus-05: #494949;
|
||||
--bg-plus-1: #4F4F4F;
|
||||
--bg-plus-2: #5F5F5F;
|
||||
--bg-plus-3: #6F6F6F;
|
||||
--red-plus-2: #ECB3B3;
|
||||
--red-plus-1: #DCA3A3;
|
||||
--red: #CC9393;
|
||||
--red-1: #BC8383;
|
||||
--red-2: #AC7373;
|
||||
--red-3: #9C6363;
|
||||
--red-4: #8C5353;
|
||||
--red-5: #7C4343;
|
||||
--red-6: #6C3333;
|
||||
--orange: #DFAF8F;
|
||||
--yellow: #F0DFAF;
|
||||
--yellow-1: #E0CF9F;
|
||||
--yellow-2: #D0BF8F;
|
||||
--green-5: #2F4F2F;
|
||||
--green-4: #3F5F3F;
|
||||
--green-3: #4F6F4F;
|
||||
--green-2: #5F7F5F;
|
||||
--green-1: #6F8F6F;
|
||||
--green: #7F9F7F;
|
||||
--green-plus-1: #8FB28F;
|
||||
--green-plus-2: #9FC59F;
|
||||
--green-plus-3: #AFD8AF;
|
||||
--green-plus-4: #BFEBBF;
|
||||
--cyan: #93E0E3;
|
||||
--blue-plus-3: #BDE0F3;
|
||||
--blue-plus-2: #ACE0E3;
|
||||
--blue-plus-1: #94BFF3;
|
||||
--blue: #8CD0D3;
|
||||
--blue-1: #7CB8BB;
|
||||
--blue-2: #6CA0A3;
|
||||
--blue-3: #5C888B;
|
||||
--blue-4: #4C7073;
|
||||
--blue-5: #366060;
|
||||
--magenta: #DC8CC3;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
line-height: 1.6;
|
||||
color: var(--fg);
|
||||
background: var(--bg);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
header {
|
||||
background: var(--bg-1);
|
||||
border-bottom: 2px solid var(--blue-1);
|
||||
padding: 1.5rem 2rem;
|
||||
box-shadow: 0 2px 8px oklab(0.000% 0.000 0.000 / 0.5);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
color: var(--yellow);
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
header p {
|
||||
color: var(--fg-1);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
nav {
|
||||
background: var(--bg-plus-1);
|
||||
padding: 0.75rem 2rem;
|
||||
border-bottom: 1px solid var(--bg-1);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
box-shadow: 0 2px 4px oklab(0.000% 0.000 0.000 / 0.3);
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: var(--blue-1);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
color: var(--green-plus-1);
|
||||
}
|
||||
|
||||
nav a.active {
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.search-form input {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--fg-2);
|
||||
color: var(--fg);
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 4px;
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.search-form input:focus {
|
||||
outline: none;
|
||||
border-color: var(--blue-1);
|
||||
}
|
||||
|
||||
.search-form button {
|
||||
background: var(--blue-1);
|
||||
color: var(--bg-1);
|
||||
border: none;
|
||||
padding: 0.4rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.search-form button:hover {
|
||||
background: var(--green-plus-1);
|
||||
}
|
||||
|
||||
/* Main content */
|
||||
main {
|
||||
padding: 2rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
background: var(--bg-1);
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
border-top: 2px solid var(--blue-1);
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
footer p:first-child {
|
||||
color: var(--yellow);
|
||||
font-weight: 600;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
footer p:not(:first-child) {
|
||||
color: var(--fg-1);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Page header */
|
||||
.page-header {
|
||||
background: var(--bg-plus-1);
|
||||
border: 1px solid var(--bg-1);
|
||||
border-left: 3px solid var(--green-plus-1);
|
||||
border-radius: 6px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
color: var(--yellow);
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
color: var(--fg-1);
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
/* Stats grid */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-plus-1);
|
||||
border: 1px solid var(--bg-1);
|
||||
border-radius: 6px;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
color: var(--orange);
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--fg-1);
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* Book grid and cards */
|
||||
.book-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.book-card {
|
||||
background: var(--bg-plus-1);
|
||||
border: 1px solid var(--bg-1);
|
||||
border-left: 3px solid var(--blue-1);
|
||||
border-radius: 6px;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.book-card:hover {
|
||||
border-left-color: var(--green-plus-1);
|
||||
box-shadow: 0 4px 12px oklab(0.000% 0.000 0.000 / 0.5);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.book-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.book-title {
|
||||
color: var(--yellow);
|
||||
font-size: 1.15rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.book-rating {
|
||||
background: var(--bg-1);
|
||||
color: var(--orange);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.book-author {
|
||||
color: var(--blue-1);
|
||||
font-weight: 500;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.book-description {
|
||||
color: var(--fg-1);
|
||||
line-height: 1.5;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.book-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.genre-badge {
|
||||
background: var(--green);
|
||||
color: var(--bg-1);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.year-badge {
|
||||
background: var(--bg-1);
|
||||
color: var(--fg-1);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.isbn {
|
||||
color: var(--fg-1);
|
||||
font-family: monospace;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.detail-link {
|
||||
color: var(--blue-1);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 0.5rem;
|
||||
align-self: flex-start;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.detail-link:hover {
|
||||
color: var(--green-plus-1);
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
color: var(--fg-1);
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
color: var(--orange);
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.empty-state a {
|
||||
color: var(--blue-1);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty-state a:hover {
|
||||
color: var(--green-plus-1);
|
||||
}
|
||||
|
||||
/* Book detail page */
|
||||
.detail-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
color: var(--fg-1);
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.breadcrumb a {
|
||||
color: var(--blue-1);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.breadcrumb a:hover {
|
||||
color: var(--green-plus-1);
|
||||
}
|
||||
|
||||
.book-hero {
|
||||
background: var(--bg-plus-1);
|
||||
border: 1px solid var(--bg-1);
|
||||
border-left: 4px solid var(--orange);
|
||||
border-radius: 6px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.book-hero-title {
|
||||
color: var(--yellow);
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.75rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.book-hero-author {
|
||||
color: var(--blue-1);
|
||||
font-size: 1.3rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.meta-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
background: var(--bg-1);
|
||||
padding: 1.25rem;
|
||||
border-radius: 6px;
|
||||
border-left: 3px solid var(--blue-1);
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
color: var(--fg-1);
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.meta-value {
|
||||
color: var(--yellow);
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.meta-value.rating {
|
||||
color: var(--orange);
|
||||
}
|
||||
|
||||
.description-section {
|
||||
background: var(--bg-plus-1);
|
||||
border: 1px solid var(--bg-1);
|
||||
border-radius: 6px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.description-section h3 {
|
||||
color: var(--green-plus-1);
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.description-text {
|
||||
color: var(--fg);
|
||||
line-height: 1.8;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: inline-block;
|
||||
background: var(--blue-1);
|
||||
color: var(--bg-1);
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: var(--green-plus-1);
|
||||
transform: translateX(-4px);
|
||||
}
|
||||
|
||||
.not-found {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
}
|
||||
|
||||
.not-found h2 {
|
||||
color: var(--red);
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.not-found p {
|
||||
color: var(--fg-1);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
37
server_root/top/catalog.hbs
Normal file
37
server_root/top/catalog.hbs
Normal file
@ -0,0 +1,37 @@
|
||||
{{> zenburn_header}}
|
||||
|
||||
<div class="page-header">
|
||||
<h2>Book Catalog</h2>
|
||||
<p>Browse our curated collection of technical literature</p>
|
||||
</div>
|
||||
|
||||
{{#if results}}
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{#each results}}{{#if @last}}{{@index}}{{/if}}{{/each}}</span>
|
||||
<span class="stat-label">Books Found</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">📚</span>
|
||||
<span class="stat-label">Categories</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">⭐</span>
|
||||
<span class="stat-label">Curated</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="book-grid">
|
||||
{{#each results}}
|
||||
{{> zenburn_card}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<h3>No Books Found</h3>
|
||||
<p>Try adjusting your search or <a href="/books">browse all books</a></p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{> zenburn_footer}}
|
||||
|
||||
Reference in New Issue
Block a user