Add mineral search and permalink pages
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Search bar toggle in header (magnifying glass icon) - /daily-stone/search/?q= endpoint with results list - /daily-stone/mineral/<id>/ permalink for each mineral - Mineral count shown in footer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
220
dailystone/templates/dailystone/search.html
Normal file
220
dailystone/templates/dailystone/search.html
Normal file
@@ -0,0 +1,220 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Search — Daily Stone</title>
|
||||
<style>
|
||||
:root {
|
||||
--stone-color: #6b7280;
|
||||
--stone-muted: #f0f0ec;
|
||||
--stone-text: #3d4250;
|
||||
--bg: #faf9f6;
|
||||
--text: #2c2c2c;
|
||||
--text-secondary: #5a5a5a;
|
||||
--border: #d0d0d0;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.accent-bar {
|
||||
height: 6px;
|
||||
background: linear-gradient(90deg, #4a5568, #6b7280, #4a5568);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 640px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1.25rem 3rem;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
padding: 2rem 0 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.page-header .label {
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: normal;
|
||||
color: var(--stone-text);
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.search-form input[type="text"] {
|
||||
flex: 1;
|
||||
padding: 0.6rem 0.75rem;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 0.95rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-form input[type="text"]:focus {
|
||||
border-color: var(--stone-color);
|
||||
}
|
||||
|
||||
.search-form button {
|
||||
padding: 0.6rem 1rem;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 0.9rem;
|
||||
color: var(--stone-text);
|
||||
background: var(--stone-muted);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-form button:hover {
|
||||
background: #e5e5e0;
|
||||
}
|
||||
|
||||
.result-count {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.results-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.results-list li {
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
|
||||
}
|
||||
|
||||
.results-list a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 0.25rem;
|
||||
text-decoration: none;
|
||||
color: var(--text);
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.results-list a:hover {
|
||||
background: var(--stone-muted);
|
||||
}
|
||||
|
||||
.result-swatch {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.result-name {
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.result-formula {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
text-align: center;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid var(--border);
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.page-footer a {
|
||||
color: var(--stone-text);
|
||||
text-decoration: none;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.page-footer a:hover { text-decoration: underline; }
|
||||
|
||||
.page-footer .home-link {
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.page-footer .mineral-count {
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="accent-bar"></div>
|
||||
|
||||
<div class="container">
|
||||
<header class="page-header">
|
||||
<div class="label">Daily Stone</div>
|
||||
<h1>Search Minerals</h1>
|
||||
</header>
|
||||
|
||||
<form class="search-form" action="{% url 'dailystone:search' %}" method="get">
|
||||
<input type="text" name="q" value="{{ query }}" placeholder="Mineral name..." autofocus>
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
|
||||
{% if results %}
|
||||
<div class="result-count">{{ results|length }} result{{ results|length|pluralize }} for "{{ query }}"</div>
|
||||
<ul class="results-list">
|
||||
{% for m in results %}
|
||||
<li>
|
||||
<a href="{% url 'dailystone:mineral_detail' pk=m.pk %}">
|
||||
<span class="result-swatch" style="background: {{ m.color_hex }};"></span>
|
||||
<span class="result-name">{{ m.name }}</span>
|
||||
{% if m.formula %}<span class="result-formula">{{ m.formula }}</span>{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="no-results">
|
||||
<p>No minerals found for "{{ query }}"</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="{% url 'dailystone:daily_stone' %}">← Today's stone</a>
|
||||
<span class="mineral-count">{{ total_minerals }} minerals in collection</span>
|
||||
<br>
|
||||
<a href="/" class="home-link">k-boris.tech</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -60,14 +60,90 @@
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.random-btn {
|
||||
.header-actions {
|
||||
position: absolute;
|
||||
top: 1.5rem;
|
||||
right: 0;
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
font-family: 'Georgia', serif;
|
||||
color: var(--stone-text);
|
||||
background: var(--stone-muted);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.search-toggle:hover {
|
||||
background: color-mix(in srgb, var(--stone-color) 20%, #f5f5f0);
|
||||
}
|
||||
|
||||
.search-toggle svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: none;
|
||||
margin: -0.5rem 0 1.5rem;
|
||||
}
|
||||
|
||||
.search-bar.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search-bar form {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.search-bar input[type="text"] {
|
||||
flex: 1;
|
||||
padding: 0.5rem 0.7rem;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-bar input[type="text"]:focus {
|
||||
border-color: var(--stone-color);
|
||||
}
|
||||
|
||||
.search-bar button {
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 0.85rem;
|
||||
color: var(--stone-text);
|
||||
background: var(--stone-muted);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-bar button:hover {
|
||||
background: color-mix(in srgb, var(--stone-color) 20%, #f5f5f0);
|
||||
}
|
||||
|
||||
.random-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.4rem 0.75rem;
|
||||
padding: 0.4rem 0.65rem;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 0.8rem;
|
||||
color: var(--stone-text);
|
||||
@@ -271,6 +347,13 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.page-footer .mineral-count {
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.page-footer .home-link {
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
@@ -313,20 +396,34 @@
|
||||
{% if mineral %}
|
||||
|
||||
<header class="page-header">
|
||||
<a href="{% url 'dailystone:random_stone' %}" class="random-btn" title="Random mineral">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="16 3 21 3 21 8"></polyline><line x1="4" y1="20" x2="21" y2="3"></line>
|
||||
<polyline points="21 16 21 21 16 21"></polyline><line x1="15" y1="15" x2="21" y2="21"></line>
|
||||
<line x1="4" y1="4" x2="9" y2="9"></line>
|
||||
</svg>
|
||||
Random
|
||||
</a>
|
||||
<div class="header-actions">
|
||||
<a href="#" class="search-toggle" title="Search minerals" onclick="document.getElementById('searchBar').classList.toggle('open');document.getElementById('searchInput').focus();return false;">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="{% url 'dailystone:random_stone' %}" class="random-btn" title="Random mineral">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="16 3 21 3 21 8"></polyline><line x1="4" y1="20" x2="21" y2="3"></line>
|
||||
<polyline points="21 16 21 21 16 21"></polyline><line x1="15" y1="15" x2="21" y2="21"></line>
|
||||
<line x1="4" y1="4" x2="9" y2="9"></line>
|
||||
</svg>
|
||||
Random
|
||||
</a>
|
||||
</div>
|
||||
<div class="label">{% if is_random %}Random Stone{% else %}Daily Stone{% endif %}</div>
|
||||
<h1 class="mineral-name">{{ mineral.name }}</h1>
|
||||
{% if mineral.formula %}<div class="formula">{{ mineral.formula|chem_formula }}</div>{% endif %}
|
||||
<div class="date">{{ today|date:"F j, Y" }}</div>
|
||||
</header>
|
||||
|
||||
<div class="search-bar" id="searchBar">
|
||||
<form action="{% url 'dailystone:search' %}" method="get">
|
||||
<input type="text" id="searchInput" name="q" placeholder="Search minerals...">
|
||||
<button type="submit">Go</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Gallery -->
|
||||
<div class="gallery">
|
||||
{% if mineral.image_urls %}
|
||||
@@ -431,6 +528,9 @@
|
||||
Read more on Wikipedia →
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if total_minerals %}
|
||||
<span class="mineral-count">{{ total_minerals }} minerals in collection</span>
|
||||
{% endif %}
|
||||
<br>
|
||||
<a href="/" class="home-link">k-boris.tech</a>
|
||||
</footer>
|
||||
|
||||
Reference in New Issue
Block a user