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:
Jon Seager 2026-02-07 22:36:28 +00:00
parent 0258ca6840
commit af9344202d
No known key found for this signature in database
9 changed files with 106 additions and 70 deletions

View file

@ -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;

View file

@ -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>

View file

@ -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">

View file

@ -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>

View file

@ -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") }}

View file

@ -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") }}

View file

@ -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") }}

View file

@ -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") }}

View file

@ -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") }}