From 631ff4e1d02c6381c53a8116278ebe309dc1f133 Mon Sep 17 00:00:00 2001 From: Keith Solomon Date: Sun, 16 Aug 2026 12:30:50 -0500 Subject: [PATCH] feat(projects): add reusable project-card component + styles --- styles/components/index.css | 1 + styles/components/project-card.css | 119 +++++++++++++++++++++++++++++ views/components/project-card.php | 77 +++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 styles/components/project-card.css create mode 100644 views/components/project-card.php diff --git a/styles/components/index.css b/styles/components/index.css index fb03dae..fe42cf9 100644 --- a/styles/components/index.css +++ b/styles/components/index.css @@ -6,3 +6,4 @@ @import "./sidebar.css"; @import "./pagination.css"; @import "./site-footer.css"; +@import "./project-card.css"; diff --git a/styles/components/project-card.css b/styles/components/project-card.css new file mode 100644 index 0000000..7e76b0e --- /dev/null +++ b/styles/components/project-card.css @@ -0,0 +1,119 @@ +/* Project card — Carbon Blue surface */ +.project-card { + background: var(--color-surface-container, #1e1f25); + border: 1px solid var(--color-outline-variant, #444651); + border-radius: 1rem; + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + transition: border-color 200ms ease, transform 200ms ease; +} + +.project-card:hover, +.project-card:focus-within { + border-color: var(--color-primary, #b6c4ff); +} + +.project-card:focus-within { + outline: 2px solid var(--color-primary, #b6c4ff); + outline-offset: 3px; +} + +@media (prefers-reduced-motion: reduce) { + .project-card { transition: none; } +} + +.project-card__head { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 0.75rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.project-card__type { + background: rgba(182, 196, 255, 0.12); + color: var(--color-primary, #b6c4ff); + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + font-weight: 600; +} + +.project-card__provider { color: var(--color-on-surface-variant, #c5c5d3); } + +.project-card__title { + font-family: var(--font-sans); + font-weight: 700; + font-size: 1.125rem; + line-height: 1.4; + margin: 0; + color: var(--color-on-surface, #e3e1e9); +} + +.project-card__title-link { + color: inherit; + text-decoration: none; +} + +.project-card__title-link:hover { text-decoration: underline; } + +.project-card__excerpt { + color: var(--color-on-surface-variant, #c5c5d3); + font-size: 0.9375rem; + line-height: 1.6; + margin: 0; + display: -webkit-box; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.project-card__foot { + margin-top: auto; + padding-top: 0.75rem; + border-top: 1px solid var(--color-outline-variant, #444651); + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.75rem; +} + +.project-card__meta { display: inline-flex; gap: 0.75rem; align-items: center; color: var(--color-on-surface-variant, #c5c5d3); font-size: 0.875rem; } + +.project-card__actions { display: inline-flex; gap: 0.5rem; } + +.project-card__action { + display: inline-flex; + align-items: center; + padding: 0.5rem 0.75rem; + border-radius: 0.5rem; + font-weight: 600; + font-size: 0.875rem; + line-height: 1.1; + text-decoration: none; + border: 2px solid transparent; + transition: color 200ms ease, background-color 200ms ease, border-color 200ms ease; +} + +.project-card__action--primary { + background: var(--color-primary, #b6c4ff); + color: var(--color-on-primary, #05297a); + border-color: var(--color-primary, #b6c4ff); +} + +.project-card__action--primary:hover { background: #dce1ff; border-color: #dce1ff; } + +.project-card__action--secondary { + color: var(--color-on-surface, #e3e1e9); + border-color: var(--color-outline, #8f909d); +} + +.project-card__action--secondary:hover { border-color: var(--color-primary, #b6c4ff); color: var(--color-primary, #b6c4ff); } + +.project-card__action:focus-visible { + outline: 2px solid var(--color-primary, #b6c4ff); + outline-offset: 3px; +} diff --git a/views/components/project-card.php b/views/components/project-card.php new file mode 100644 index 0000000..42530a7 --- /dev/null +++ b/views/components/project-card.php @@ -0,0 +1,77 @@ +ID; +$type_label = ''; +$type_terms = get_the_terms( $post_id, 'project-type' ); +if ( ! empty( $type_terms ) && ! is_wp_error( $type_terms ) ) { + $type_label = strtoupper( $type_terms[0]->name ); +} +$release_url = Projects::get_release_url( $post_id ); +$repo_url = Projects::get_repo_browse_url( $post_id ); +$repo_data = Projects::get_repo_data( $post_id ); +$language = is_array( $repo_data ) && ! empty( $repo_data['language'] ) ? (string) $repo_data['language'] : ''; +$stars = is_array( $repo_data ) && isset( $repo_data['stargazers_count'] ) ? (int) $repo_data['stargazers_count'] : 0; +$excerpt = has_excerpt( $post_id ) ? get_the_excerpt( $post_id ) : wp_trim_words( wp_strip_all_tags( $post->post_content ), 30, '…' ); +$card_classes = 'project-card group'; +?> + +
+
+ + + + +
+ +

+ + + +

+ + +

+ + + +