Add daily-stone page showing a different mineral each day
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
New dailystone app with 207 minerals scraped from Wikipedia. Each day displays a different mineral with photos, formula, properties, description, and history. Page theme color matches the mineral's typical appearance. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
412
dailystone/templates/dailystone/stone.html
Normal file
412
dailystone/templates/dailystone/stone.html
Normal file
@@ -0,0 +1,412 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% if mineral %}{{ mineral.name }} — Daily Stone{% else %}Daily Stone{% endif %}</title>
|
||||
<style>
|
||||
:root {
|
||||
--stone-color: {{ mineral.color_hex|default:"#6b7280" }};
|
||||
--stone-light: color-mix(in srgb, var(--stone-color) 15%, #ffffff);
|
||||
--stone-dark: color-mix(in srgb, var(--stone-color) 80%, #1a1a2e);
|
||||
--stone-muted: color-mix(in srgb, var(--stone-color) 30%, #f5f5f0);
|
||||
--stone-text: color-mix(in srgb, var(--stone-color) 60%, #1a1a2e);
|
||||
--bg: #faf9f6;
|
||||
--text: #2c2c2c;
|
||||
--text-secondary: #5a5a5a;
|
||||
--border: color-mix(in srgb, var(--stone-color) 25%, #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;
|
||||
}
|
||||
|
||||
/* Top accent bar */
|
||||
.accent-bar {
|
||||
height: 6px;
|
||||
background: linear-gradient(90deg, var(--stone-dark), var(--stone-color), var(--stone-dark));
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 640px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1.25rem 3rem;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.page-header {
|
||||
text-align: center;
|
||||
padding: 2rem 0 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.page-header .label {
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.page-header .date {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.mineral-name {
|
||||
font-size: 2.4rem;
|
||||
font-weight: normal;
|
||||
color: var(--stone-text);
|
||||
margin: 0.5rem 0 0.25rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.formula {
|
||||
font-size: 1.15rem;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 0.5rem;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Photo gallery */
|
||||
.gallery {
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.gallery-main {
|
||||
width: 100%;
|
||||
aspect-ratio: 4/3;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--stone-muted);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.gallery-thumbs {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.gallery-thumbs img {
|
||||
width: 100px;
|
||||
height: 75px;
|
||||
object-fit: cover;
|
||||
border-radius: 3px;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.gallery-thumbs img:hover,
|
||||
.gallery-thumbs img.active {
|
||||
border-color: var(--stone-color);
|
||||
}
|
||||
|
||||
/* Properties table */
|
||||
.properties {
|
||||
margin: 2rem 0;
|
||||
border-top: 2px solid var(--stone-color);
|
||||
}
|
||||
|
||||
.properties h2 {
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone-text);
|
||||
padding: 0.75rem 0 0.5rem;
|
||||
}
|
||||
|
||||
.prop-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.prop-item {
|
||||
padding: 0.6rem 0;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
|
||||
}
|
||||
|
||||
.prop-item:nth-child(odd) {
|
||||
padding-right: 1rem;
|
||||
border-right: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
|
||||
}
|
||||
|
||||
.prop-item:nth-child(even) {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.prop-label {
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-secondary);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.prop-value {
|
||||
font-size: 0.95rem;
|
||||
color: var(--text);
|
||||
margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
/* Color swatch */
|
||||
.color-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin: 1.5rem 0;
|
||||
padding: 0.75rem 1rem;
|
||||
background: var(--stone-muted);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.color-swatch {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: var(--stone-color);
|
||||
border: 2px solid color-mix(in srgb, var(--stone-color) 50%, #000);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.color-info {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.color-info .hex {
|
||||
font-family: 'Courier New', monospace;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Sections */
|
||||
.section {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone-text);
|
||||
border-top: 2px solid var(--stone-color);
|
||||
padding-top: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.section p {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.95rem;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.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);
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 4rem 1rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.empty-state h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* No images placeholder */
|
||||
.no-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 4/3;
|
||||
background: var(--stone-muted);
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="accent-bar"></div>
|
||||
|
||||
<div class="container">
|
||||
{% if mineral %}
|
||||
|
||||
<header class="page-header">
|
||||
<div class="label">Daily Stone</div>
|
||||
<h1 class="mineral-name">{{ mineral.name }}</h1>
|
||||
{% if mineral.formula %}<div class="formula">{{ mineral.formula }}</div>{% endif %}
|
||||
<div class="date">{{ today|date:"F j, Y" }}</div>
|
||||
</header>
|
||||
|
||||
<!-- Gallery -->
|
||||
<div class="gallery">
|
||||
{% if mineral.image_urls %}
|
||||
<img class="gallery-main" id="mainImage"
|
||||
src="{{ mineral.image_urls.0 }}"
|
||||
alt="{{ mineral.name }}">
|
||||
{% if mineral.image_urls|length > 1 %}
|
||||
<div class="gallery-thumbs">
|
||||
{% for url in mineral.image_urls %}
|
||||
<img src="{{ url }}"
|
||||
alt="{{ mineral.name }}"
|
||||
class="{% if forloop.first %}active{% endif %}"
|
||||
onclick="switchImage(this, '{{ url }}')">
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="no-image">No image available</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Color -->
|
||||
<div class="color-row">
|
||||
<div class="color-swatch"></div>
|
||||
<div class="color-info">
|
||||
{% if mineral.color_description %}
|
||||
{{ mineral.color_description }}
|
||||
{% else %}
|
||||
Typical color
|
||||
{% endif %}
|
||||
<br><span class="hex">{{ mineral.color_hex }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Properties -->
|
||||
<div class="properties">
|
||||
<h2>Properties</h2>
|
||||
<div class="prop-grid">
|
||||
{% if mineral.category %}
|
||||
<div class="prop-item">
|
||||
<span class="prop-label">Category</span>
|
||||
<span class="prop-value">{{ mineral.category }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if mineral.crystal_system %}
|
||||
<div class="prop-item">
|
||||
<span class="prop-label">Crystal System</span>
|
||||
<span class="prop-value">{{ mineral.crystal_system }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if mineral.mohs_hardness %}
|
||||
<div class="prop-item">
|
||||
<span class="prop-label">Hardness (Mohs)</span>
|
||||
<span class="prop-value">{{ mineral.mohs_hardness }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if mineral.luster %}
|
||||
<div class="prop-item">
|
||||
<span class="prop-label">Luster</span>
|
||||
<span class="prop-value">{{ mineral.luster }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if mineral.streak %}
|
||||
<div class="prop-item">
|
||||
<span class="prop-label">Streak</span>
|
||||
<span class="prop-value">{{ mineral.streak }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if mineral.specific_gravity %}
|
||||
<div class="prop-item">
|
||||
<span class="prop-label">Specific Gravity</span>
|
||||
<span class="prop-value">{{ mineral.specific_gravity }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
{% if mineral.description %}
|
||||
<div class="section">
|
||||
<h2>About</h2>
|
||||
{% for para in mineral.description.splitlines %}
|
||||
{% if para %}<p>{{ para }}</p>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- History -->
|
||||
{% if mineral.history %}
|
||||
<div class="section">
|
||||
<h2>History & Etymology</h2>
|
||||
{% for para in mineral.history.splitlines %}
|
||||
{% if para %}<p>{{ para }}</p>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="page-footer">
|
||||
{% if mineral.wikipedia_url %}
|
||||
<a href="{{ mineral.wikipedia_url }}" target="_blank" rel="noopener">
|
||||
Read more on Wikipedia →
|
||||
</a>
|
||||
{% endif %}
|
||||
<br>
|
||||
<a href="/" class="home-link">k-boris.tech</a>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
function switchImage(thumb, url) {
|
||||
document.getElementById('mainImage').src = url;
|
||||
document.querySelectorAll('.gallery-thumbs img').forEach(
|
||||
img => img.classList.remove('active')
|
||||
);
|
||||
thumb.classList.add('active');
|
||||
}
|
||||
</script>
|
||||
|
||||
{% else %}
|
||||
|
||||
<div class="empty-state">
|
||||
<h1>Daily Stone</h1>
|
||||
<p>No minerals have been loaded yet. Check back soon!</p>
|
||||
<a href="/" class="home-link">k-boris.tech</a>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user