From 9577bc9c1c94b01ab7c93f57137fe2bd5a9a3e02 Mon Sep 17 00:00:00 2001 From: Dinesh <13635627+HumbleBee14@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:29:58 -0700 Subject: [PATCH 1/4] Extract shared IconLinkCard; add .card/.card--interactive primitives About and Contact rendered a byte-identical icon+label+action link card with ~100 lines of duplicated CSS each. Replace both with a single IconLinkCard.astro, and add global .card / .card--interactive utilities (surface + hover-lift) so the recipe lives in one place. Verified both pages render identically in light and dark. Also point the homepage's middle CTA at /write ('Write an article') instead of the redundant style-guide anchor. --- src/components/IconLinkCard.astro | 125 ++++++++++++++++++++++++++++ src/pages/about.astro | 130 +----------------------------- src/pages/contact.astro | 128 +---------------------------- src/pages/index.astro | 2 +- src/styles/global.css | 23 ++++++ 5 files changed, 153 insertions(+), 255 deletions(-) create mode 100644 src/components/IconLinkCard.astro diff --git a/src/components/IconLinkCard.astro b/src/components/IconLinkCard.astro new file mode 100644 index 0000000..10c23e1 --- /dev/null +++ b/src/components/IconLinkCard.astro @@ -0,0 +1,125 @@ +--- +// Icon + label + action link card. Shared by About and Contact. +// Surface + hover-lift come from the global .card / .card--interactive classes. +interface Props { + href?: string | null; + /** SVG path `d` for a 24×24 viewBox icon */ + icon: string; + label: string; + action: string; +} +const { href, icon, label, action } = Astro.props; +const external = !!href && href.startsWith('http'); +--- + + + + {label} + {action} + + + + diff --git a/src/pages/about.astro b/src/pages/about.astro index ef3017a..d6be806 100644 --- a/src/pages/about.astro +++ b/src/pages/about.astro @@ -1,5 +1,6 @@ --- import BaseLayout from '@/layouts/BaseLayout.astro'; +import IconLinkCard from '@/components/IconLinkCard.astro'; import { SITE } from '@/lib/site'; const GITHUB_URL = 'https://github.com/HumbleBee14'; @@ -85,23 +86,7 @@ const links = [ @@ -205,122 +190,11 @@ const links = [ grid-template-columns: repeat(2, 1fr); gap: 14px; } - .about-card { - position: relative; - display: grid; - grid-template-columns: 56px 1fr 28px; - grid-template-areas: - 'icon label arrow' - 'icon action arrow'; - column-gap: 16px; - row-gap: 2px; - align-items: center; - padding: 22px; - border: 1px solid var(--line); - border-radius: var(--radius-lg); - background: var(--paper-2); - color: inherit !important; - text-decoration: none; - overflow: hidden; - transition: - transform 0.18s ease, - border-color 0.18s ease, - box-shadow 0.18s ease, - background 0.18s ease; - } - .about-card { - border-bottom: 1px solid var(--line); - } - .about-card::before { - content: ''; - position: absolute; - inset: 0 auto 0 0; - width: 3px; - background: var(--accent); - transform: scaleY(0); - transform-origin: top; - transition: transform 0.22s ease; - } - .about-card:hover { - transform: translateY(-2px); - border-color: var(--accent); - background: var(--paper); - box-shadow: 0 8px 24px -14px color-mix(in srgb, var(--accent) 40%, transparent); - } - .about-card:hover::before { - transform: scaleY(1); - } - .about-card:hover .about-action, - .about-card:hover .about-arrow, - .about-card:hover .about-icon { - color: var(--accent); - } - .about-card:hover .about-icon { - border-color: var(--accent); - } - .about-card:hover .about-arrow { - transform: translateX(4px); - } - - .about-icon { - grid-area: icon; - display: inline-flex; - align-items: center; - justify-content: center; - width: 48px; - height: 48px; - border-radius: 10px; - background: var(--paper); - border: 1px solid var(--line); - color: var(--ink-2); - transition: - color 0.15s ease, - border-color 0.15s ease; - } - .about-icon svg { - display: block; - fill: currentColor; - } - .about-label { - grid-area: label; - font-family: var(--font-mono); - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.1em; - color: var(--ink-3); - align-self: end; - } - .about-action { - grid-area: action; - font-family: var(--font-display); - font-size: 20px; - color: var(--ink); - line-height: 1.15; - word-break: break-word; - align-self: start; - transition: color 0.15s ease; - } - .about-arrow { - grid-area: arrow; - display: inline-flex; - align-items: center; - justify-content: center; - font-family: var(--font-mono); - font-size: 18px; - color: var(--ink-3); - transition: - transform 0.18s ease, - color 0.15s ease; - } - @media (max-width: 620px) { .about-links { grid-template-columns: 1fr; gap: 10px; } - .about-card { - padding: 18px; - } .about-body { font-size: 16px; } diff --git a/src/pages/contact.astro b/src/pages/contact.astro index 8b9cef9..0f74d2f 100644 --- a/src/pages/contact.astro +++ b/src/pages/contact.astro @@ -1,5 +1,6 @@ --- import BaseLayout from '@/layouts/BaseLayout.astro'; +import IconLinkCard from '@/components/IconLinkCard.astro'; import { SITE, SOCIALS } from '@/lib/site'; const channels = [ @@ -56,23 +57,7 @@ const channels = [
{ channels.map((c) => ( - - - {c.label} - {c.action} - - + )) }
@@ -109,112 +94,6 @@ const channels = [ gap: 14px; } - .contact-card { - position: relative; - display: grid; - grid-template-columns: 56px 1fr 28px; - grid-template-areas: - 'icon label arrow' - 'icon action arrow'; - column-gap: 16px; - row-gap: 2px; - align-items: center; - padding: 22px 22px; - border: 1px solid var(--line); - border-radius: var(--radius-lg); - background: var(--paper-2); - color: inherit; - text-decoration: none; - overflow: hidden; - transition: - transform 0.18s ease, - border-color 0.18s ease, - box-shadow 0.18s ease, - background 0.18s ease; - } - .contact-card::before { - content: ''; - position: absolute; - inset: 0 auto 0 0; - width: 3px; - background: var(--accent); - transform: scaleY(0); - transform-origin: top; - transition: transform 0.22s ease; - } - .contact-card:hover { - transform: translateY(-2px); - border-color: var(--accent); - background: var(--paper); - box-shadow: 0 8px 24px -14px color-mix(in srgb, var(--accent) 40%, transparent); - } - .contact-card:hover::before { - transform: scaleY(1); - } - .contact-card:hover .contact-action { - color: var(--accent); - } - .contact-card:hover .contact-arrow { - color: var(--accent); - transform: translateX(4px); - } - .contact-card:hover .contact-icon { - color: var(--accent); - border-color: var(--accent); - } - - .contact-icon { - grid-area: icon; - display: inline-flex; - align-items: center; - justify-content: center; - width: 48px; - height: 48px; - border-radius: 10px; - background: var(--paper); - border: 1px solid var(--line); - color: var(--ink-2); - transition: - color 0.15s ease, - border-color 0.15s ease; - } - .contact-icon svg { - display: block; - fill: currentColor; - } - - .contact-label { - grid-area: label; - font-family: var(--font-mono); - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.1em; - color: var(--ink-3); - align-self: end; - } - .contact-action { - grid-area: action; - font-family: var(--font-display); - font-size: 20px; - color: var(--ink); - line-height: 1.15; - transition: color 0.15s ease; - word-break: break-word; - align-self: start; - } - .contact-arrow { - grid-area: arrow; - display: inline-flex; - align-items: center; - justify-content: center; - font-family: var(--font-mono); - font-size: 18px; - color: var(--ink-3); - transition: - transform 0.18s ease, - color 0.15s ease; - } - @media (max-width: 1000px) { .contact-grid { grid-template-columns: repeat(2, 1fr); @@ -225,8 +104,5 @@ const channels = [ grid-template-columns: 1fr; gap: 10px; } - .contact-card { - padding: 18px 18px; - } } diff --git a/src/pages/index.astro b/src/pages/index.astro index 1e4d854..1d793c8 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -307,7 +307,7 @@ const featuredTools = allTools
Pitch an article → - Read the style guide + Write an article Browse on GitHub
diff --git a/src/styles/global.css b/src/styles/global.css index 3a48d69..2925def 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -725,6 +725,29 @@ main { grid-template-columns: repeat(3, 1fr); gap: var(--row-gap); } +/* Shared card surface + hover lift. Used by IconLinkCard and the page cards + (contributor, tool, community). Distinct layouts stay per-page; only the + surface + hover recipe live here. */ +.card { + border: 1px solid var(--line); + background: var(--paper-2); + color: inherit; + text-decoration: none; +} +.card--interactive { + transition: + transform 0.18s ease, + border-color 0.18s ease, + background 0.18s ease, + box-shadow 0.18s ease; +} +.card--interactive:hover { + transform: translateY(-2px); + border-color: var(--accent); + background: var(--paper); + box-shadow: var(--shadow-hover); +} + .article-card { display: flex; flex-direction: column; From b133d4d473cc84a8c47c39d59c1bd700c77c8a4d Mon Sep 17 00:00:00 2001 From: Dinesh <13635627+HumbleBee14@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:33:24 -0700 Subject: [PATCH 2/4] Adopt shared .card/.card--interactive on the remaining page cards contributor-card (authors), tool-card (playground), community-channel (community) all repeated the same surface + hover-lift recipe. Point them at the shared .card/.card--interactive classes and keep only their distinct layout rules (grid/flex, padding, radius, accent bar). One hover recipe now drives every card. Also correct the stale MIT/CC license line in the About copy. --- src/pages/about.astro | 5 ++--- src/pages/authors/index.astro | 17 +---------------- src/pages/community/index.astro | 22 ++++++---------------- src/pages/playground/index.astro | 19 ++----------------- 4 files changed, 11 insertions(+), 52 deletions(-) diff --git a/src/pages/about.astro b/src/pages/about.astro index d6be806..e44f131 100644 --- a/src/pages/about.astro +++ b/src/pages/about.astro @@ -78,9 +78,8 @@ const links = [

- This site is open source, MIT-licensed for the code, and Creative Commons / All Rights - Reserved per author for the writing. No paywalls, no popups, no "10x your ML career" - funnels. Just notes from the workbench. + Everything here is © its respective author, all rights reserved. No paywalls, no popups, no + "10x your ML career" funnels. Just notes from the workbench.