mirror of
https://github.com/Nutlope/hallmark.git
synced 2026-08-14 12:35:33 +02:00
Polish — drop Bricolage, vertical section heads, edge-bleed examples, GitHub star button
Six items in one pass:
· **Bricolage Grotesque removed** entirely. Distinctive italic-a
forms (curl ear) read as "weird" at section-title scale across
several themes. Replaced per-theme with cleaner alternatives:
Sport → Inter Tight (italic). Riso → Space Grotesk. Almanac →
IBM Plex Sans. Plume → Geist. Manifesto's display fallback
chain swapped Bricolage out for Space Grotesk. Google Fonts
stylesheet URL updated — Bricolage dropped, Space Grotesk and
Anton (italic axis) added.
· **Section heads restructured.** Was: 10rem | 1fr two-column with
the section label ("04 / ANTI-PATTERNS") on the left and the
title on the right. Problem: at narrow widths (and on themes
that override to 6rem | 1fr like Sport), the label was forced
to wrap mid-word ("ANTI-/PATTERNS") and the title felt squeezed.
Now: single-column. Label stacks ABOVE the title with a small
gap. Label gets `white-space: nowrap` so it never breaks. Title
font-size grows from `clamp(2rem, 3vw + 1rem, 3rem)` to
`clamp(2rem, 4vw + 0.5rem, 3.5rem)` since it has full row width.
Sport's per-theme override (was 6rem | 1fr) now also 1fr.
· **Foundations card strokes — fixed.** The gap-as-border pattern
(gap: 0.5px, parent bg showing through) sub-pixel-rounded
inconsistently — col 2/3 boundaries dropped on some themes.
Replaced with explicit 1px right + bottom borders on each card,
plus left + top on the parent. Every divider now renders crisp
at every theme + viewport. Atelier/Salon use a dashed bottom
rule instead, no vertical dividers (matches their borderless
card aesthetic).
· **Worked examples — full bleed to viewport edges.** The rail had
`margin-inline: calc(-1 * var(--page-gutter))` which only
escaped the page gutter, not the page max-width. On wide
desktops the rail still capped at 76rem. Now uses
`width: 100vw; margin-inline: calc(50% - 50vw)` so cards extend
flush to the viewport edges no matter how wide the screen.
Internal scroll-padding still snaps the first/last card cleanly.
· **Study brief pill** — "Paste a design you admire." → "/hallmark
study this landing page". Reads as the actual command the user
would type, matches the Build row's brief style.
· **GitHub star button** added to the nav. Sits beside Install,
styled as an inverse pill (ink fill, accent star, paper text).
Live star count fetched from the GitHub API on first paint,
cached in localStorage for 1h to avoid rate-limit. Falls back
to "★" if the request fails. Counts ≥1000 render as "1.2k" etc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
c6f3b6cda3
commit
adf0da6fd4
@@ -104,6 +104,63 @@
|
||||
.banner__install { display: none; }
|
||||
}
|
||||
|
||||
/* GitHub star button — sits next to Install in the banner, shows
|
||||
the live star count fetched from the GitHub API on load (cached
|
||||
in localStorage for 1 hour to avoid rate-limit). Click → repo. */
|
||||
.banner__star {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
margin-inline-start: var(--space-2xs);
|
||||
padding: 0.18rem 0.65rem;
|
||||
font-family: var(--font-label);
|
||||
font-weight: 500;
|
||||
font-size: 0.6875rem;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--color-ink);
|
||||
text-decoration: none;
|
||||
background: var(--color-ink);
|
||||
color: var(--color-paper);
|
||||
border: 1px solid var(--color-ink);
|
||||
border-radius: var(--radius-pill, 999px);
|
||||
transition: background-color var(--dur-micro) var(--ease-out),
|
||||
color var(--dur-micro) var(--ease-out),
|
||||
border-color var(--dur-micro) var(--ease-out);
|
||||
}
|
||||
|
||||
.banner__star-icon {
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.banner__star-count {
|
||||
font-feature-settings: "tnum" 1;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.banner__star:hover {
|
||||
background: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-paper);
|
||||
}
|
||||
|
||||
.banner__star:hover .banner__star-icon {
|
||||
color: var(--color-paper);
|
||||
}
|
||||
|
||||
.banner__star:focus-visible {
|
||||
outline: 2px solid var(--color-focus);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 36rem) {
|
||||
.banner__star {
|
||||
padding: 0.18rem 0.5rem;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Genre label — small text after the theme name in the banner. */
|
||||
.banner__theme {
|
||||
display: inline;
|
||||
|
||||
+40
-25
@@ -23,32 +23,39 @@
|
||||
position: relative; /* anchors absolute children (hidden radios) */
|
||||
}
|
||||
|
||||
/* Section head — single-column always. Section label stacks ABOVE
|
||||
the title, separated by a small gap. No more 10rem fixed sidebar
|
||||
that forced the section label ("ANTI-PATTERNS") to wrap on
|
||||
themes that needed wider title columns. Section titles can grow
|
||||
bigger now since they have full width. */
|
||||
.section__head {
|
||||
display: grid;
|
||||
grid-template-columns: 10rem 1fr;
|
||||
gap: var(--space-xl);
|
||||
align-items: baseline;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-sm);
|
||||
padding-bottom: var(--section-head-gap);
|
||||
border-bottom: var(--rule-hair) solid var(--color-rule-2);
|
||||
}
|
||||
|
||||
/* Section label — small mono uppercase coordinate above the title.
|
||||
Hard rule: never wrap. White-space: nowrap forces single-line at
|
||||
every viewport. The label is short ("05 / Foundations") so this
|
||||
is safe. */
|
||||
.section-label,
|
||||
.section__head .section-label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.section__title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight, 400);
|
||||
font-style: var(--display-style, normal);
|
||||
font-size: var(--text-2xl);
|
||||
line-height: var(--lh-snug);
|
||||
font-size: clamp(2rem, 4vw + 0.5rem, 3.5rem);
|
||||
line-height: var(--lh-tight);
|
||||
letter-spacing: var(--tracking-display);
|
||||
max-width: 24ch;
|
||||
max-width: 28ch;
|
||||
color: var(--color-ink);
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
.section__head {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Narrow viewports — let headings wrap freely + cap display sizes so
|
||||
@@ -191,12 +198,13 @@
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
scroll-snap-type: x mandatory;
|
||||
/* Escape the page gutter so tiles can extend past the container edge */
|
||||
margin-inline: calc(-1 * var(--page-gutter));
|
||||
/* Full-bleed: escape both the page max-width AND the page gutter so
|
||||
the rail spans the full viewport regardless of how wide the
|
||||
screen is. Cards extend right up to the viewport's edge. */
|
||||
width: 100vw;
|
||||
margin-inline: calc(50% - 50vw);
|
||||
padding-inline: var(--page-gutter);
|
||||
padding-block: var(--space-md) var(--space-lg);
|
||||
/* Symmetric scroll-padding so the first AND last card snap clean to the
|
||||
gutter — without inline-end the last card always reads as clipped. */
|
||||
scroll-padding-inline: var(--page-gutter);
|
||||
scrollbar-color: var(--color-rule-2) transparent;
|
||||
}
|
||||
@@ -1452,10 +1460,9 @@ html[data-theme="almanac"] body {
|
||||
}
|
||||
[data-theme="sport"] .opening__sub { font-weight: 500; }
|
||||
[data-theme="sport"] .section__head {
|
||||
grid-template-columns: 6rem 1fr;
|
||||
grid-template-columns: 1fr;
|
||||
border-bottom: 2px solid var(--color-rule-2);
|
||||
padding-bottom: var(--space-md);
|
||||
align-items: end;
|
||||
}
|
||||
[data-theme="sport"] .section__title {
|
||||
font-style: italic;
|
||||
@@ -1678,15 +1685,21 @@ html[data-theme="almanac"] body {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Foundations cards — explicit per-card right + bottom borders so
|
||||
every column gap renders crisp at every theme + viewport. The
|
||||
prior gap-as-border pattern (gap: 0.5px, parent bg showing
|
||||
through) sub-pixel-rounded inconsistently — col 2/3 gaps
|
||||
sometimes vanished. The new approach uses real 1px borders on
|
||||
each card, parent gives top + left, cards give right + bottom.
|
||||
Result: every divider renders. */
|
||||
.found-cards {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: var(--rule-hair);
|
||||
background: var(--color-rule);
|
||||
border: var(--rule-hair) solid var(--color-rule);
|
||||
border-block-start: 1px solid var(--color-rule);
|
||||
border-inline-start: 1px solid var(--color-rule);
|
||||
}
|
||||
|
||||
.found-card {
|
||||
@@ -1696,6 +1709,8 @@ html[data-theme="almanac"] body {
|
||||
gap: var(--space-sm);
|
||||
align-content: start;
|
||||
min-height: 12rem;
|
||||
border-block-end: 1px solid var(--color-rule);
|
||||
border-inline-end: 1px solid var(--color-rule);
|
||||
}
|
||||
|
||||
.found-card__icon {
|
||||
@@ -1747,16 +1762,16 @@ html[data-theme="almanac"] body {
|
||||
[data-theme="atelier"] .found-card,
|
||||
[data-theme="salon"] .found-card {
|
||||
background: transparent;
|
||||
border-inline-end: 0;
|
||||
border-block-end: var(--rule-hair) dashed var(--color-rule);
|
||||
}
|
||||
|
||||
[data-theme="atelier"] .found-cards,
|
||||
[data-theme="salon"] .found-cards {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
gap: var(--space-md);
|
||||
border-block-start: var(--rule-hair) solid var(--color-rule);
|
||||
border-block-end: var(--rule-hair) solid var(--color-rule);
|
||||
padding-block: var(--space-lg);
|
||||
padding-block-start: var(--space-md);
|
||||
}
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
|
||||
+9
-9
@@ -404,9 +404,9 @@
|
||||
--color-accent-ink: oklch(58% 0.24 28);
|
||||
--color-focus: #E51A1A;
|
||||
|
||||
--font-display: "Anton", "Bebas Neue", "Bricolage Grotesque", "Space Grotesk", sans-serif;
|
||||
--font-display: "Anton", "Bebas Neue", "Space Grotesk", sans-serif;
|
||||
--font-body: "IBM Plex Sans", "Public Sans", "The Future", ui-sans-serif, sans-serif;
|
||||
--font-serif: "Anton", "Bricolage Grotesque", sans-serif;
|
||||
--font-serif: "Anton", "Space Grotesk", sans-serif;
|
||||
--font-label: "JetBrains Mono", ui-monospace, monospace;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, monospace;
|
||||
|
||||
@@ -550,7 +550,7 @@
|
||||
--color-accent-ink: oklch(40% 0.13 250);
|
||||
--color-focus: oklch(38% 0.135 250);
|
||||
|
||||
--font-display: "Bricolage Grotesque", "IBM Plex Sans", ui-sans-serif, sans-serif;
|
||||
--font-display: "IBM Plex Sans", "Hanken Grotesk", ui-sans-serif, sans-serif;
|
||||
--font-body: "IBM Plex Sans", "The Future", ui-sans-serif, sans-serif;
|
||||
--font-serif: "Newsreader", ui-serif, Georgia, serif;
|
||||
--font-label: "IBM Plex Mono", ui-monospace, monospace;
|
||||
@@ -606,9 +606,9 @@
|
||||
--color-accent-ink: oklch(54% 0.20 35);
|
||||
--color-focus: oklch(58% 0.190 35);
|
||||
|
||||
--font-display: "Bricolage Grotesque", "Space Grotesk", ui-sans-serif, sans-serif;
|
||||
--font-display: "Inter Tight", "Space Grotesk", ui-sans-serif, sans-serif;
|
||||
--font-body: "Plus Jakarta Sans", "Hanken Grotesk", "The Future", ui-sans-serif, sans-serif;
|
||||
--font-serif: "Bricolage Grotesque", ui-sans-serif, sans-serif;
|
||||
--font-serif: "Inter Tight", ui-sans-serif, sans-serif;
|
||||
--font-label: "JetBrains Mono", ui-monospace, monospace;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, monospace;
|
||||
|
||||
@@ -729,7 +729,7 @@
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────
|
||||
RISO — risograph print · Bricolage display, off-register
|
||||
RISO — risograph print · Space Grotesk display, off-register
|
||||
Inspiration: Are.na, e-flux, Frieze, indie magazine sites.
|
||||
Warm peach paper, riso cyan + yellow as accents, slight CMYK
|
||||
misregistration on display via text-shadow.
|
||||
@@ -750,10 +750,10 @@
|
||||
--color-accent-ink: oklch(58% 0.170 220);
|
||||
--color-focus: oklch(58% 0.170 220);
|
||||
|
||||
--font-display: "Bricolage Grotesque", "Inter Tight", system-ui, sans-serif;
|
||||
--font-display: "Space Grotesk", "Inter Tight", system-ui, sans-serif;
|
||||
--font-body: "Newsreader", "Tiempos", ui-serif, Georgia, serif;
|
||||
--font-serif: "Newsreader", ui-serif, Georgia, serif;
|
||||
--font-label: "Bricolage Grotesque", ui-monospace, monospace;
|
||||
--font-label: "Space Grotesk", ui-monospace, monospace;
|
||||
--font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
|
||||
|
||||
--display-weight: 800;
|
||||
@@ -1064,7 +1064,7 @@
|
||||
--color-accent-ink: oklch(96% 0.020 70);
|
||||
--color-focus: oklch(68% 0.130 18);
|
||||
|
||||
--font-display: "Bricolage Grotesque", "Geist", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-display: "Geist", "Inter Tight", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-body: "Geist", "Inter", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-serif: "Newsreader", ui-serif, Georgia, serif;
|
||||
--font-label: "Geist Mono", ui-monospace, monospace;
|
||||
|
||||
+10
-3
@@ -20,11 +20,11 @@
|
||||
<link rel="preload" href="fonts/the-future-medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="fonts/the-future-mono-regular.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<!-- Google Fonts — Fraunces, Geist, Bricolage, Newsreader, plus Inter Tight + Playfair Display for Editorial. -->
|
||||
<!-- Google Fonts — Fraunces, Geist, Inter Tight, Space Grotesk, Newsreader, Playfair Display, Anton. -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,500;12..96,700;12..96,800&family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;0,9..144,600;1,9..144,400;1,9..144,500&family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&family=Inter:wght@400;500;600;700&family=Inter+Tight:wght@500;600;700;800&family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;1,6..72,400;1,6..72,500&family=Playfair+Display:ital,wght@1,400;1,500;1,600&display=swap" />
|
||||
href="https://fonts.googleapis.com/css2?family=Anton&family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,500;0,9..144,600;1,9..144,400;1,9..144,500&family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&family=Inter:wght@400;500;600;700&family=Inter+Tight:ital,wght@0,500;0,600;0,700;0,800;1,500;1,600;1,700&family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;1,6..72,400;1,6..72,500&family=Playfair+Display:ital,wght@1,400;1,500;1,600&family=Space+Grotesk:wght@400;500;600;700&display=swap" />
|
||||
|
||||
<link rel="stylesheet" href="css/tokens.css" />
|
||||
<link rel="stylesheet" href="css/base.css" />
|
||||
@@ -53,6 +53,13 @@
|
||||
|
||||
<a class="banner__install" href="#install" aria-label="Jump to install section">Install</a>
|
||||
|
||||
<a class="banner__star" href="https://github.com/Luffixos/hallmark" target="_blank" rel="noopener" aria-label="Star Hallmark on GitHub">
|
||||
<svg class="banner__star-icon" viewBox="0 0 16 16" width="11" height="11" aria-hidden="true">
|
||||
<path fill="currentColor" d="M8 0.5l2.36 4.78 5.27.77-3.81 3.72.9 5.24L8 12.55 3.28 15.01l.9-5.24L.37 6.05l5.27-.77L8 .5z"/>
|
||||
</svg>
|
||||
<span class="banner__star-count tnum" data-star-count>—</span>
|
||||
</a>
|
||||
|
||||
<span class="banner__spacer" aria-hidden="true"></span>
|
||||
|
||||
<div class="banner__theme-wrap" data-theme-wrap>
|
||||
@@ -378,7 +385,7 @@
|
||||
|
||||
<div class="skill-row__body">
|
||||
<figure class="skill-row__demo skill-row__demo--build">
|
||||
<span class="brief-pill" aria-label="Example brief">"Paste a design you admire."</span>
|
||||
<span class="brief-pill" aria-label="Example brief">/hallmark study this landing page</span>
|
||||
<span class="brief-arrow" aria-hidden="true">↓</span>
|
||||
<span class="fig-corners brief-output">
|
||||
<span class="fig-corners__bl"></span><span class="fig-corners__br"></span>
|
||||
|
||||
@@ -707,6 +707,46 @@ function attachCopyButtons(scope = document) {
|
||||
}
|
||||
attachCopyButtons();
|
||||
|
||||
/* — GitHub star count — cached in localStorage for 1h ——————————
|
||||
Hits the public GitHub API once on first paint, caches the count
|
||||
to avoid rate-limiting. Falls back to "★" if the request fails. */
|
||||
(() => {
|
||||
const starEl = document.querySelector("[data-star-count]");
|
||||
if (!starEl) return;
|
||||
|
||||
const REPO = "Luffixos/hallmark";
|
||||
const CACHE_KEY = "hallmark-star-count";
|
||||
const TTL = 60 * 60 * 1000; // 1h
|
||||
|
||||
const format = (n) => (n >= 1000 ? (n / 1000).toFixed(1) + "k" : String(n));
|
||||
|
||||
// Read cache first — show instantly if fresh.
|
||||
try {
|
||||
const raw = localStorage.getItem(CACHE_KEY);
|
||||
if (raw) {
|
||||
const cached = JSON.parse(raw);
|
||||
if (cached && Date.now() - cached.t < TTL && typeof cached.n === "number") {
|
||||
starEl.textContent = format(cached.n);
|
||||
return; // skip network entirely
|
||||
}
|
||||
}
|
||||
} catch (e) { /* localStorage may throw on private mode */ }
|
||||
|
||||
// No fresh cache — fetch.
|
||||
fetch(`https://api.github.com/repos/${REPO}`, { headers: { Accept: "application/vnd.github+json" } })
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((d) => {
|
||||
if (!d || typeof d.stargazers_count !== "number") {
|
||||
starEl.textContent = "★";
|
||||
return;
|
||||
}
|
||||
const n = d.stargazers_count;
|
||||
starEl.textContent = format(n);
|
||||
try { localStorage.setItem(CACHE_KEY, JSON.stringify({ n, t: Date.now() })); } catch (e) { }
|
||||
})
|
||||
.catch(() => { starEl.textContent = "★"; });
|
||||
})();
|
||||
|
||||
/* — Theme application ————————————————————————————————— */
|
||||
/* Cached banner subnodes — populated once at startup. */
|
||||
const themeLabelEl = document.querySelector(".banner__theme");
|
||||
|
||||
Reference in New Issue
Block a user