From 42f8ad742b5b6f3109f46267c6f95d087c2d244e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 19 Sep 2026 01:12:23 +0000 Subject: [PATCH] Fill hero controls so the grid stops crossing them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A hero with showHeroHalo paints a 56px lattice behind its content — HeroGrid in light mode, hero-beam__grid in dark. Controls filled with bg-transparent let a 1px line read straight through them, which cuts the control in half. This affected the tag pills in the article and project heroes across 26 pages in both colour modes, the project hero's repository button, and the three jump buttons in the services hero. Every affected hero paints bg-background, so filling with that token gives the colour the transparent control already shows and leaves nothing to see through. On plain ground the controls are unchanged. Applied at the call site rather than in the Button variants or the tag list components. Both are used away from a hero as well, and several of those usages sit on bg-background-secondary, where this fill would leave a pale patch. Two button classes, because the outline variants hover differently. `outline` hovers to --secondary, which is opaque already and is restated so the base fill cannot win the cascade against it. `brand-outline` hovers to bg-brand-500/10, which is translucent and would let the line back in, so the hover mixes that same 10% into the same ground. The tag pills need no hover rule: they change only their border. The mix is in sRGB rather than OKLCH. --color-background is oklch(100% 0 0) in light mode, whose hue is powerless, so mixing toward it in OKLCH drags the hue off the brand; sRGB is also what the browser composites a translucent fill in. Verified across all 88 built pages in both colour modes: no control without an opaque fill of its own now sits inside a painted lattice. The tag lists on the blog and project index and tag pages keep their transparent fill, and the selected pill keeps bg-brand-500. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BwJB1u6U3vdEZ9LrHRebST --- src/components/blog/ArticleHero.astro | 2 +- src/components/pages/views/ServicesView.astro | 6 +- src/components/projects/ProjectHero.astro | 4 +- src/styles/global.css | 56 +++++++++++++++++++ 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/components/blog/ArticleHero.astro b/src/components/blog/ArticleHero.astro index 84a849ef1..3e223843f 100644 --- a/src/components/blog/ArticleHero.astro +++ b/src/components/blog/ArticleHero.astro @@ -61,7 +61,7 @@ const readingTime = getReadingTime(body ?? description); {tags.length > 0 && ( - + )} diff --git a/src/components/pages/views/ServicesView.astro b/src/components/pages/views/ServicesView.astro index cd8e3b49c..d3359206b 100644 --- a/src/components/pages/views/ServicesView.astro +++ b/src/components/pages/views/ServicesView.astro @@ -91,15 +91,15 @@ const aboutUrl = localizedPath('/about', locale);

- - - diff --git a/src/components/projects/ProjectHero.astro b/src/components/projects/ProjectHero.astro index cf04d5aa8..e90f48954 100644 --- a/src/components/projects/ProjectHero.astro +++ b/src/components/projects/ProjectHero.astro @@ -69,7 +69,7 @@ const locale = getLocaleFromPath(Astro.url.pathname);

{tags.length > 0 && ( - + )} {meta.length > 0 && ( @@ -94,7 +94,7 @@ const locale = getLocaleFromPath(Astro.url.pathname); )} {repo && ( - diff --git a/src/styles/global.css b/src/styles/global.css index bec077b48..d4e553126 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1325,6 +1325,62 @@ border-color: transparent !important; } +/* Controls standing on a hero that draws a grid. + + A hero with `showHeroHalo` paints a 56px lattice behind its content — + `HeroGrid` in light mode, `hero-beam__grid` in dark. Any control filled with + `bg-transparent` lets a 1px line read straight through it, which cuts the + control in half. + + Every affected hero paints `bg-background`, so filling with that token gives + the colour the transparent control already shows and leaves nothing to see + through. On plain ground nothing about the control changes. + + Applied at the call site rather than in the variant or the component. Both + the outline variants and the tag lists are used away from a hero as well, + and several of those sit on `bg-background-secondary` — a different colour, + which this fill would turn into a pale patch. + + Two button classes because the two outline variants hover differently: + `outline` hovers to `--secondary`, which is opaque already and is restated + here so the base fill cannot win the cascade against it, while + `brand-outline` hovers to `bg-brand-500/10`, which is translucent and would + let the line straight back in. Mixing that same 10% into the same ground + gives the hover its colour with nothing left to see through. + + `!important`, matching `.cta-btn-brand` above, because the utility being + overridden comes from the same variant and carries the same weight. It is on + the hover rules too, so the hover still wins over the base fill. + + In sRGB rather than OKLCH: `--color-background` is `oklch(100% 0 0)` in light + mode, whose hue is powerless, so mixing toward it in OKLCH drags the hue off + the brand. sRGB is also what the browser composites a translucent fill in, so + this lands where the hover sits today. */ +.hero-btn-opaque { + background-color: var(--color-background) !important; +} +.hero-btn-opaque:hover { + background-color: var(--color-secondary) !important; +} +.hero-btn-opaque-brand { + background-color: var(--color-background) !important; +} +.hero-btn-opaque-brand:hover { + background-color: color-mix(in srgb, var(--color-brand-500) 10%, var(--color-background)) !important; +} + +/* The same fix for the tag pills a hero carries. + + Only the unselected pill: the selected one fills with `bg-brand-500` and + carries `aria-current="page"`. Neither hero passes an active tag today, but + the exclusion keeps the rule honest if one ever does. + + No hover rule — these pills change only their border on hover, so there is + no second translucent state to close. */ +.tag-pills-opaque a:not([aria-current]) { + background-color: var(--color-background); +} + /* Primary button inside an inverted section. The primary variant is `bg-foreground text-background`, but the Button's own light-mode style wins the cascade for background-color and fills with