refactor(css): add text-2xs and small-caps utilities, replace inline styles
- Add @utility text-2xs (0.65rem) and small-caps (font-variant) to input.css - Replace inline style="font-variant: small-caps" with .small-caps class across all list partials and base.html footer - Add gap support to .tab and .tab-mobile for icon+label layout - Reduce mobile top padding (py-4 md:py-10) on main container - Rename CSS section header from "Scrollbar hide" to "Utilities"
This commit is contained in:
parent
0258ca6840
commit
af9344202d
9 changed files with 106 additions and 70 deletions
|
|
@ -88,7 +88,7 @@ a {
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ── Scrollbar hide ───────────────────────────────────────────── */
|
||||
/* ── Utilities ────────────────────────────────────────────────── */
|
||||
|
||||
@utility scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
|
|
@ -98,6 +98,15 @@ a {
|
|||
}
|
||||
}
|
||||
|
||||
@utility text-2xs {
|
||||
font-size: 0.65rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
@utility small-caps {
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
/* ── Form controls ─────────────────────────────────────────────── */
|
||||
|
||||
.input-field {
|
||||
|
|
@ -319,6 +328,7 @@ input.input-field[type="number"] {
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
flex: 1;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.375rem 0.875rem;
|
||||
|
|
@ -340,7 +350,9 @@ input.input-field[type="number"] {
|
|||
}
|
||||
|
||||
.tab-mobile {
|
||||
display: block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en" data-star-root>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta
|
||||
name="description"
|
||||
content="{% block description %}Self-hosted coffee logging — track roasters, roasts, bags, brews, and gear.{% endblock %}"
|
||||
/>
|
||||
<title>{% block title %}Brewlog{% endblock %}</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
<link rel="icon" id="favicon" type="image/svg+xml" href="/favicon-light.svg" />
|
||||
<script>
|
||||
(() => {
|
||||
const stored = localStorage.getItem("theme");
|
||||
const isDark = stored === "dark" || (!stored && matchMedia("(prefers-color-scheme: dark)").matches);
|
||||
;(() => {
|
||||
const stored = localStorage.getItem("theme")
|
||||
const isDark =
|
||||
stored === "dark" || (!stored && matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
if (isDark) {
|
||||
document.documentElement.setAttribute("data-theme", "dark");
|
||||
document.getElementById("favicon").href = "/favicon-dark.svg";
|
||||
document.documentElement.setAttribute("data-theme", "dark")
|
||||
document.getElementById("favicon").href = "/favicon-dark.svg"
|
||||
}
|
||||
})();
|
||||
})()
|
||||
</script>
|
||||
<script
|
||||
type="module"
|
||||
|
|
@ -25,98 +30,117 @@
|
|||
{% block head %}{% endblock %}
|
||||
<script>
|
||||
const toggleRow = (event) => {
|
||||
if (event.target.closest('.card-detail')) return;
|
||||
const row = event.currentTarget;
|
||||
row.classList.toggle('expanded');
|
||||
row.querySelector('.card-detail')?.classList.toggle('hidden');
|
||||
const r = row.nextElementSibling;
|
||||
if (r?.classList.contains('detail-row')) r.classList.toggle('hidden');
|
||||
row.querySelector('.icon-expand')?.classList.toggle('hidden');
|
||||
row.querySelector('.icon-collapse')?.classList.toggle('hidden');
|
||||
};
|
||||
if (event.target.closest(".card-detail")) return
|
||||
const row = event.currentTarget
|
||||
row.classList.toggle("expanded")
|
||||
row.querySelector(".card-detail")?.classList.toggle("hidden")
|
||||
const r = row.nextElementSibling
|
||||
if (r?.classList.contains("detail-row")) r.classList.toggle("hidden")
|
||||
row.querySelector(".icon-expand")?.classList.toggle("hidden")
|
||||
row.querySelector(".icon-collapse")?.classList.toggle("hidden")
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const mobileQuery = window.matchMedia("(max-width: 767px)");
|
||||
if (!mobileQuery.matches) return;
|
||||
const mobileQuery = window.matchMedia("(max-width: 767px)")
|
||||
if (!mobileQuery.matches) return
|
||||
|
||||
const setupInfiniteScroll = () => {
|
||||
document.querySelectorAll("[data-infinite-scroll]").forEach((container) => {
|
||||
const sentinel = container.querySelector(".infinite-scroll-sentinel");
|
||||
if (!sentinel || sentinel.dataset.observed) return;
|
||||
sentinel.dataset.observed = "true";
|
||||
const sentinel = container.querySelector(".infinite-scroll-sentinel")
|
||||
if (!sentinel || sentinel.dataset.observed) return
|
||||
sentinel.dataset.observed = "true"
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.isIntersecting) return;
|
||||
if (!entry.isIntersecting) return
|
||||
|
||||
const nextUrl = container.getAttribute("data-next-url");
|
||||
const targetSelector = container.getAttribute("data-target");
|
||||
const nextUrl = container.getAttribute("data-next-url")
|
||||
const targetSelector = container.getAttribute("data-target")
|
||||
if (!nextUrl || !targetSelector) {
|
||||
observer.disconnect();
|
||||
return;
|
||||
observer.disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
observer.disconnect();
|
||||
sentinel.remove();
|
||||
observer.disconnect()
|
||||
sentinel.remove()
|
||||
|
||||
fetch(nextUrl, {
|
||||
headers: { "datastar-request": "true" },
|
||||
})
|
||||
.then((res) => res.text())
|
||||
.then((html) => {
|
||||
const doc = new DOMParser().parseFromString(html, "text/html");
|
||||
const newTarget = doc.querySelector(targetSelector);
|
||||
if (!newTarget) return;
|
||||
const doc = new DOMParser().parseFromString(html, "text/html")
|
||||
const newTarget = doc.querySelector(targetSelector)
|
||||
if (!newTarget) return
|
||||
|
||||
// Append new rows to existing tbody
|
||||
const existingTbody = container.querySelector("tbody");
|
||||
const newTbody = newTarget.querySelector("tbody");
|
||||
const existingTbody = container.querySelector("tbody")
|
||||
const newTbody = newTarget.querySelector("tbody")
|
||||
if (existingTbody && newTbody) {
|
||||
Array.from(newTbody.children).forEach((row) => {
|
||||
existingTbody.appendChild(document.adoptNode(row));
|
||||
});
|
||||
existingTbody.appendChild(document.adoptNode(row))
|
||||
})
|
||||
}
|
||||
|
||||
// Check if there are more pages
|
||||
const newContainer = newTarget.querySelector("[data-infinite-scroll]");
|
||||
const newNextUrl = newContainer && newContainer.getAttribute("data-next-url");
|
||||
const newContainer = newTarget.querySelector("[data-infinite-scroll]")
|
||||
const newNextUrl = newContainer && newContainer.getAttribute("data-next-url")
|
||||
if (newNextUrl) {
|
||||
container.setAttribute("data-next-url", newNextUrl);
|
||||
const newSentinel = document.createElement("div");
|
||||
newSentinel.className = "infinite-scroll-sentinel h-4 md:hidden";
|
||||
newSentinel.setAttribute("aria-hidden", "true");
|
||||
container.appendChild(newSentinel);
|
||||
setupInfiniteScroll();
|
||||
container.setAttribute("data-next-url", newNextUrl)
|
||||
const newSentinel = document.createElement("div")
|
||||
newSentinel.className = "infinite-scroll-sentinel h-4 md:hidden"
|
||||
newSentinel.setAttribute("aria-hidden", "true")
|
||||
container.appendChild(newSentinel)
|
||||
setupInfiniteScroll()
|
||||
} else {
|
||||
container.removeAttribute("data-next-url");
|
||||
container.removeAttribute("data-infinite-scroll");
|
||||
container.removeAttribute("data-next-url")
|
||||
container.removeAttribute("data-infinite-scroll")
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
},
|
||||
{ rootMargin: "200px" }
|
||||
);
|
||||
)
|
||||
|
||||
observer.observe(sentinel);
|
||||
});
|
||||
};
|
||||
observer.observe(sentinel)
|
||||
})
|
||||
}
|
||||
|
||||
setupInfiniteScroll();
|
||||
setupInfiniteScroll()
|
||||
|
||||
const bodyObserver = new MutationObserver(() => {
|
||||
setupInfiniteScroll();
|
||||
});
|
||||
bodyObserver.observe(document.body, { childList: true, subtree: true });
|
||||
});
|
||||
setupInfiniteScroll()
|
||||
})
|
||||
bodyObserver.observe(document.body, { childList: true, subtree: true })
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body class="min-h-screen bg-page text-text">
|
||||
<main class="mx-auto flex w-full max-w-5xl flex-col gap-8 px-6 py-10">
|
||||
<main class="mx-auto flex w-full max-w-5xl flex-col gap-8 px-6 py-4 md:py-10">
|
||||
{% include "partials/nav.html" %} {% block content %}{% endblock %}
|
||||
<footer class="mt-8 text-center text-xs tracking-widest text-text-muted" style="font-variant: small-caps">
|
||||
<p>B{rew}log by <a href="https://jnsgr.uk" class="text-text-muted hover:text-accent transition" target="_blank" rel="noreferrer noopener">jnsgruk</a> · <a href="https://github.com/jnsgruk/brewlog/commit/{{ version_info.commit }}" class="text-text-muted hover:text-accent transition" target="_blank" rel="noreferrer noopener">{{ version_info.commit }}</a></p>
|
||||
<footer
|
||||
class="mt-8 text-center text-xs tracking-widest text-text-muted small-caps"
|
||||
>
|
||||
<p>
|
||||
B{rew}log by
|
||||
<a
|
||||
href="https://jnsgr.uk"
|
||||
class="text-text-muted hover:text-accent transition"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>jnsgruk</a
|
||||
>
|
||||
·
|
||||
<a
|
||||
href="https://github.com/jnsgruk/brewlog/commit/{{ version_info.commit }}"
|
||||
class="text-text-muted hover:text-accent transition"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>{{ version_info.commit }}</a
|
||||
>
|
||||
</p>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
{{ table::search_header(navigator, "#bag-list") }}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="responsive-table min-w-full divide-y text-left text-sm text-text">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text" style="font-variant: small-caps">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text small-caps">
|
||||
<tr>
|
||||
{{ table::sortable_header("Added", "created-at", navigator, "#bag-list") }}
|
||||
{{ table::sortable_header("Roaster", "roaster", navigator, "#bag-list") }}
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
<div class="h-1.5 rounded-full bg-surface-alt overflow-hidden">
|
||||
<div class="h-full rounded-full bg-accent" style="width: {{ bag.used_percent }}%"></div>
|
||||
</div>
|
||||
<div class="mt-0.5 text-text-muted" style="font-variant: small-caps; font-size: 0.7rem">{{ bag.remaining }} / {{ bag.amount }}g</div>
|
||||
<div class="mt-0.5 text-text-muted small-caps" style="font-size: 0.7rem">{{ bag.remaining }} / {{ bag.amount }}g</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
<div class="h-1.5 rounded-full bg-surface-alt overflow-hidden">
|
||||
<div class="h-full rounded-full bg-accent" style="width: {{ bag.used_percent }}%"></div>
|
||||
</div>
|
||||
<div class="mt-0.5 text-right text-text-muted" style="font-variant: small-caps; font-size: 0.7rem">{{ bag.remaining }} / {{ bag.amount }}g</div>
|
||||
<div class="mt-0.5 text-right text-text-muted small-caps" style="font-size: 0.7rem">{{ bag.remaining }} / {{ bag.amount }}g</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td data-label="Finished" class="mobile-hidden whitespace-nowrap px-4 py-3 font-medium text-text-secondary">
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="responsive-table min-w-full divide-y text-left text-sm text-text">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text" style="font-variant: small-caps">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text small-caps">
|
||||
<tr>
|
||||
{{ table::sortable_header("Added", "created-at", navigator, "#brew-list") }}
|
||||
<th scope="col" class="px-4 py-3">Coffee</th>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
{{ table::search_header(navigator, "#cafe-list") }}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="responsive-table min-w-full divide-y text-left text-sm text-text">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text" style="font-variant: small-caps">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text small-caps">
|
||||
<tr>
|
||||
{{ table::sortable_header("Added", "created-at", navigator, "#cafe-list") }}
|
||||
{{ table::sortable_header("Name", "name", navigator, "#cafe-list") }}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
{{ table::search_header(navigator, "#cup-list") }}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="responsive-table min-w-full divide-y text-left text-sm text-text">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text" style="font-variant: small-caps">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text small-caps">
|
||||
<tr>
|
||||
{{ table::sortable_header("Added", "created-at", navigator, "#cup-list") }}
|
||||
{{ table::sortable_header("Roast", "roast", navigator, "#cup-list") }}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="responsive-table min-w-full divide-y text-left text-sm text-text">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text" style="font-variant: small-caps">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text small-caps">
|
||||
<tr>
|
||||
{{ table::sortable_header("Added", "created-at", navigator, "#gear-list") }}
|
||||
{{ table::sortable_header("Category", "category", navigator, "#gear-list") }}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
{{ table::search_header(navigator, "#roast-list") }}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="responsive-table min-w-full divide-y text-left text-sm text-text">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text" style="font-variant: small-caps">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text small-caps">
|
||||
<tr>
|
||||
{{ table::sortable_header("Added", "created-at", navigator, "#roast-list") }}
|
||||
{{ table::sortable_header("Roast", "name", navigator, "#roast-list") }}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
{{ table::search_header(navigator, "#roaster-list") }}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="responsive-table min-w-full divide-y text-left text-sm text-text">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text" style="font-variant: small-caps">
|
||||
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text small-caps">
|
||||
<tr>
|
||||
{{ table::sortable_header("Added", "created-at", navigator, "#roaster-list") }}
|
||||
{{ table::sortable_header("Name", "name", navigator, "#roaster-list") }}
|
||||
|
|
|
|||
Loading…
Reference in a new issue