20 CSS Cards14 / 20
Portfolio Project Card
Portfolio tile with a CSS-drawn dashboard mockup (top bar, side nav, KPI row, bar chart), category chip, project title, description, tags, and a client + "View project →" footer.
Published Updated
The code
<section class="card-14">
<article class="card-14__card">
<div class="card-14__thumb">
<div class="card-14__mock" aria-hidden="true">
<div class="card-14__mock-top"></div>
<div class="card-14__mock-body">
<div class="card-14__mock-side">
<span></span><span></span><span></span><span></span>
</div>
<div class="card-14__mock-main">
<div class="card-14__mock-kpi"><span></span><span></span><span></span></div>
<div class="card-14__mock-chart">
<span style="height:35%"></span>
<span style="height:55%"></span>
<span style="height:40%"></span>
<span style="height:70%"></span>
<span style="height:60%"></span>
<span style="height:85%"></span>
<span style="height:50%"></span>
<span style="height:75%"></span>
</div>
</div>
</div>
</div>
<div class="card-14__overlay" aria-hidden="true"></div>
</div>
<div class="card-14__body">
<span class="card-14__chip">SAAS · DASHBOARD</span>
<h3 class="card-14__title">Meridian Analytics Rebrand</h3>
<p class="card-14__desc">Redesigned the analytics dashboard for 12k+ enterprise users — 41% faster time-to-insight.</p>
<ul class="card-14__tags">
<li>Figma</li>
<li>Design System</li>
<li>Data Viz</li>
</ul>
<div class="card-14__foot">
<span class="card-14__client">Meridian Labs</span>
<a class="card-14__link" href="#project">View project <span aria-hidden="true">→</span></a>
</div>
</div>
</article>
</section><section class="card-14">
<article class="card-14__card">
<div class="card-14__thumb">
<div class="card-14__mock" aria-hidden="true">
<div class="card-14__mock-top"></div>
<div class="card-14__mock-body">
<div class="card-14__mock-side">
<span></span><span></span><span></span><span></span>
</div>
<div class="card-14__mock-main">
<div class="card-14__mock-kpi"><span></span><span></span><span></span></div>
<div class="card-14__mock-chart">
<span style="height:35%"></span>
<span style="height:55%"></span>
<span style="height:40%"></span>
<span style="height:70%"></span>
<span style="height:60%"></span>
<span style="height:85%"></span>
<span style="height:50%"></span>
<span style="height:75%"></span>
</div>
</div>
</div>
</div>
<div class="card-14__overlay" aria-hidden="true"></div>
</div>
<div class="card-14__body">
<span class="card-14__chip">SAAS · DASHBOARD</span>
<h3 class="card-14__title">Meridian Analytics Rebrand</h3>
<p class="card-14__desc">Redesigned the analytics dashboard for 12k+ enterprise users — 41% faster time-to-insight.</p>
<ul class="card-14__tags">
<li>Figma</li>
<li>Design System</li>
<li>Data Viz</li>
</ul>
<div class="card-14__foot">
<span class="card-14__client">Meridian Labs</span>
<a class="card-14__link" href="#project">View project <span aria-hidden="true">→</span></a>
</div>
</div>
</article>
</section>.card-14 {
width: 100%;
min-height: 100vh;
display: block;
padding: clamp(28px, 5vw, 56px) clamp(20px, 4vw, 40px);
box-sizing: border-box;
background: #f8f7f4;
font-family: 'Inter', system-ui, sans-serif;
--focus: #b8613b;
}
.card-14 *,
.card-14 *::before,
.card-14 *::after {
box-sizing: border-box;
}
.card-14__card {
width: 100%;
max-width: 340px;
margin-left: auto;
margin-right: auto;
background: #fff;
border-radius: 16px;
overflow: hidden;
border: 1px solid rgba(0,0,0,0.06);
box-shadow: 0 2px 12px rgba(0,0,0,0.04);
transition: transform 0.25s, box-shadow 0.25s;
}
.card-14__card:hover {
transform: translateY(-3px);
box-shadow: 0 14px 32px rgba(184,97,59,0.14);
}
.card-14__thumb {
position: relative;
aspect-ratio: 16/9;
background: linear-gradient(135deg, #f7edd6, #e8c9a0);
overflow: hidden;
}
.card-14__mock {
position: absolute;
inset: 14px;
background: #fff;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(60,30,10,0.15);
transition: transform 0.35s ease;
}
.card-14__card:hover .card-14__mock {
transform: scale(1.04);
}
.card-14__mock-top {
height: 12px;
background: #f0eae1;
border-bottom: 1px solid rgba(0,0,0,0.06);
}
.card-14__mock-body {
display: grid;
grid-template-columns: 40px 1fr;
height: calc(100% - 12px);
}
.card-14__mock-side {
background: #faf7f2;
padding: 8px 6px;
display: flex;
flex-direction: column;
gap: 5px;
}
.card-14__mock-side span {
display: block;
height: 5px;
border-radius: 2px;
background: #e0d5c4;
}
.card-14__mock-side span:first-child {
background: #b8613b;
}
.card-14__mock-main {
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.card-14__mock-kpi {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
}
.card-14__mock-kpi span {
height: 20px;
background: #f0eae1;
border-radius: 3px;
}
.card-14__mock-chart {
flex: 1;
display: flex;
align-items: flex-end;
gap: 3px;
padding-top: 4px;
}
.card-14__mock-chart span {
flex: 1;
background: linear-gradient(180deg, #d97e4a, #b8451f);
border-radius: 2px 2px 0 0;
}
.card-14__overlay {
position: absolute;
inset: 0;
background: linear-gradient(180deg, transparent 60%, rgba(0,0,0,0.05));
transition: opacity 0.3s;
}
.card-14__card:hover .card-14__overlay {
opacity: 0;
}
.card-14__body {
padding: 16px 18px 18px;
}
.card-14__chip {
display: inline-block;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.14em;
color: #b8613b;
background: rgba(184,97,59,0.1);
padding: 4px 9px;
border-radius: 999px;
margin-bottom: 10px;
}
.card-14__title {
font-size: 16px;
font-weight: 700;
margin: 0 0 6px;
color: #1a1a1a;
letter-spacing: -0.01em;
}
.card-14__desc {
font-size: 12.5px;
color: #6b6459;
margin: 0 0 12px;
line-height: 1.55;
}
.card-14__tags {
list-style: none;
padding: 0;
margin: 0 0 14px;
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.card-14__tags li {
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 4px;
background: #f5f2ec;
color: #6b6459;
}
.card-14__foot {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 12px;
border-top: 1px solid rgba(0,0,0,0.06);
}
.card-14__client {
font-size: 12px;
color: #6b6459;
font-weight: 600;
}
.card-14__link {
display: inline-flex;
align-items: center;
gap: 5px;
min-height: 44px;
font-size: 12.5px;
font-weight: 600;
color: #b8613b;
text-decoration: none;
}
.card-14__link:hover span {
transform: translateX(3px);
}
.card-14__link span {
display: inline-block;
transition: transform 0.2s;
}
.card-14__link:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 3px;
border-radius: 4px;
}
@media (prefers-reduced-motion: reduce) {
.card-14__card,
.card-14__mock,
.card-14__overlay,
.card-14__link span {
transition: none;
}
.card-14__card:hover {
transform: none;
}
.card-14__card:hover .card-14__mock {
transform: none;
}
.card-14__link:hover span {
transform: none;
}
}.card-14 {
width: 100%;
min-height: 100vh;
display: block;
padding: clamp(28px, 5vw, 56px) clamp(20px, 4vw, 40px);
box-sizing: border-box;
background: #f8f7f4;
font-family: 'Inter', system-ui, sans-serif;
--focus: #b8613b;
}
.card-14 *,
.card-14 *::before,
.card-14 *::after {
box-sizing: border-box;
}
.card-14__card {
width: 100%;
max-width: 340px;
margin-left: auto;
margin-right: auto;
background: #fff;
border-radius: 16px;
overflow: hidden;
border: 1px solid rgba(0,0,0,0.06);
box-shadow: 0 2px 12px rgba(0,0,0,0.04);
transition: transform 0.25s, box-shadow 0.25s;
}
.card-14__card:hover {
transform: translateY(-3px);
box-shadow: 0 14px 32px rgba(184,97,59,0.14);
}
.card-14__thumb {
position: relative;
aspect-ratio: 16/9;
background: linear-gradient(135deg, #f7edd6, #e8c9a0);
overflow: hidden;
}
.card-14__mock {
position: absolute;
inset: 14px;
background: #fff;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(60,30,10,0.15);
transition: transform 0.35s ease;
}
.card-14__card:hover .card-14__mock {
transform: scale(1.04);
}
.card-14__mock-top {
height: 12px;
background: #f0eae1;
border-bottom: 1px solid rgba(0,0,0,0.06);
}
.card-14__mock-body {
display: grid;
grid-template-columns: 40px 1fr;
height: calc(100% - 12px);
}
.card-14__mock-side {
background: #faf7f2;
padding: 8px 6px;
display: flex;
flex-direction: column;
gap: 5px;
}
.card-14__mock-side span {
display: block;
height: 5px;
border-radius: 2px;
background: #e0d5c4;
}
.card-14__mock-side span:first-child {
background: #b8613b;
}
.card-14__mock-main {
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.card-14__mock-kpi {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
}
.card-14__mock-kpi span {
height: 20px;
background: #f0eae1;
border-radius: 3px;
}
.card-14__mock-chart {
flex: 1;
display: flex;
align-items: flex-end;
gap: 3px;
padding-top: 4px;
}
.card-14__mock-chart span {
flex: 1;
background: linear-gradient(180deg, #d97e4a, #b8451f);
border-radius: 2px 2px 0 0;
}
.card-14__overlay {
position: absolute;
inset: 0;
background: linear-gradient(180deg, transparent 60%, rgba(0,0,0,0.05));
transition: opacity 0.3s;
}
.card-14__card:hover .card-14__overlay {
opacity: 0;
}
.card-14__body {
padding: 16px 18px 18px;
}
.card-14__chip {
display: inline-block;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.14em;
color: #b8613b;
background: rgba(184,97,59,0.1);
padding: 4px 9px;
border-radius: 999px;
margin-bottom: 10px;
}
.card-14__title {
font-size: 16px;
font-weight: 700;
margin: 0 0 6px;
color: #1a1a1a;
letter-spacing: -0.01em;
}
.card-14__desc {
font-size: 12.5px;
color: #6b6459;
margin: 0 0 12px;
line-height: 1.55;
}
.card-14__tags {
list-style: none;
padding: 0;
margin: 0 0 14px;
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.card-14__tags li {
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 4px;
background: #f5f2ec;
color: #6b6459;
}
.card-14__foot {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 12px;
border-top: 1px solid rgba(0,0,0,0.06);
}
.card-14__client {
font-size: 12px;
color: #6b6459;
font-weight: 600;
}
.card-14__link {
display: inline-flex;
align-items: center;
gap: 5px;
min-height: 44px;
font-size: 12.5px;
font-weight: 600;
color: #b8613b;
text-decoration: none;
}
.card-14__link:hover span {
transform: translateX(3px);
}
.card-14__link span {
display: inline-block;
transition: transform 0.2s;
}
.card-14__link:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 3px;
border-radius: 4px;
}
@media (prefers-reduced-motion: reduce) {
.card-14__card,
.card-14__mock,
.card-14__overlay,
.card-14__link span {
transition: none;
}
.card-14__card:hover {
transform: none;
}
.card-14__card:hover .card-14__mock {
transform: none;
}
.card-14__link:hover span {
transform: none;
}
}Here's a working CSS Card from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Portfolio Project Card
Source: https://codefronts.com/components/css-cards/portfolio-project-card/
Portfolio tile with a CSS-drawn dashboard mockup (top bar, side nav, KPI row, bar chart), category chip, project title, description, tags, and a client + "View project →" footer.
## HTML
```html
<section class="card-14">
<article class="card-14__card">
<div class="card-14__thumb">
<div class="card-14__mock" aria-hidden="true">
<div class="card-14__mock-top"></div>
<div class="card-14__mock-body">
<div class="card-14__mock-side">
<span></span><span></span><span></span><span></span>
</div>
<div class="card-14__mock-main">
<div class="card-14__mock-kpi"><span></span><span></span><span></span></div>
<div class="card-14__mock-chart">
<span style="height:35%"></span>
<span style="height:55%"></span>
<span style="height:40%"></span>
<span style="height:70%"></span>
<span style="height:60%"></span>
<span style="height:85%"></span>
<span style="height:50%"></span>
<span style="height:75%"></span>
</div>
</div>
</div>
</div>
<div class="card-14__overlay" aria-hidden="true"></div>
</div>
<div class="card-14__body">
<span class="card-14__chip">SAAS · DASHBOARD</span>
<h3 class="card-14__title">Meridian Analytics Rebrand</h3>
<p class="card-14__desc">Redesigned the analytics dashboard for 12k+ enterprise users — 41% faster time-to-insight.</p>
<ul class="card-14__tags">
<li>Figma</li>
<li>Design System</li>
<li>Data Viz</li>
</ul>
<div class="card-14__foot">
<span class="card-14__client">Meridian Labs</span>
<a class="card-14__link" href="#project">View project <span aria-hidden="true">→</span></a>
</div>
</div>
</article>
</section>
```
## CSS
```css
.card-14 {
width: 100%;
min-height: 100vh;
display: block;
padding: clamp(28px, 5vw, 56px) clamp(20px, 4vw, 40px);
box-sizing: border-box;
background: #f8f7f4;
font-family: 'Inter', system-ui, sans-serif;
--focus: #b8613b;
}
.card-14 *,
.card-14 *::before,
.card-14 *::after {
box-sizing: border-box;
}
.card-14__card {
width: 100%;
max-width: 340px;
margin-left: auto;
margin-right: auto;
background: #fff;
border-radius: 16px;
overflow: hidden;
border: 1px solid rgba(0,0,0,0.06);
box-shadow: 0 2px 12px rgba(0,0,0,0.04);
transition: transform 0.25s, box-shadow 0.25s;
}
.card-14__card:hover {
transform: translateY(-3px);
box-shadow: 0 14px 32px rgba(184,97,59,0.14);
}
.card-14__thumb {
position: relative;
aspect-ratio: 16/9;
background: linear-gradient(135deg, #f7edd6, #e8c9a0);
overflow: hidden;
}
.card-14__mock {
position: absolute;
inset: 14px;
background: #fff;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(60,30,10,0.15);
transition: transform 0.35s ease;
}
.card-14__card:hover .card-14__mock {
transform: scale(1.04);
}
.card-14__mock-top {
height: 12px;
background: #f0eae1;
border-bottom: 1px solid rgba(0,0,0,0.06);
}
.card-14__mock-body {
display: grid;
grid-template-columns: 40px 1fr;
height: calc(100% - 12px);
}
.card-14__mock-side {
background: #faf7f2;
padding: 8px 6px;
display: flex;
flex-direction: column;
gap: 5px;
}
.card-14__mock-side span {
display: block;
height: 5px;
border-radius: 2px;
background: #e0d5c4;
}
.card-14__mock-side span:first-child {
background: #b8613b;
}
.card-14__mock-main {
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.card-14__mock-kpi {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
}
.card-14__mock-kpi span {
height: 20px;
background: #f0eae1;
border-radius: 3px;
}
.card-14__mock-chart {
flex: 1;
display: flex;
align-items: flex-end;
gap: 3px;
padding-top: 4px;
}
.card-14__mock-chart span {
flex: 1;
background: linear-gradient(180deg, #d97e4a, #b8451f);
border-radius: 2px 2px 0 0;
}
.card-14__overlay {
position: absolute;
inset: 0;
background: linear-gradient(180deg, transparent 60%, rgba(0,0,0,0.05));
transition: opacity 0.3s;
}
.card-14__card:hover .card-14__overlay {
opacity: 0;
}
.card-14__body {
padding: 16px 18px 18px;
}
.card-14__chip {
display: inline-block;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.14em;
color: #b8613b;
background: rgba(184,97,59,0.1);
padding: 4px 9px;
border-radius: 999px;
margin-bottom: 10px;
}
.card-14__title {
font-size: 16px;
font-weight: 700;
margin: 0 0 6px;
color: #1a1a1a;
letter-spacing: -0.01em;
}
.card-14__desc {
font-size: 12.5px;
color: #6b6459;
margin: 0 0 12px;
line-height: 1.55;
}
.card-14__tags {
list-style: none;
padding: 0;
margin: 0 0 14px;
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.card-14__tags li {
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 4px;
background: #f5f2ec;
color: #6b6459;
}
.card-14__foot {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 12px;
border-top: 1px solid rgba(0,0,0,0.06);
}
.card-14__client {
font-size: 12px;
color: #6b6459;
font-weight: 600;
}
.card-14__link {
display: inline-flex;
align-items: center;
gap: 5px;
min-height: 44px;
font-size: 12.5px;
font-weight: 600;
color: #b8613b;
text-decoration: none;
}
.card-14__link:hover span {
transform: translateX(3px);
}
.card-14__link span {
display: inline-block;
transition: transform 0.2s;
}
.card-14__link:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 3px;
border-radius: 4px;
}
@media (prefers-reduced-motion: reduce) {
.card-14__card,
.card-14__mock,
.card-14__overlay,
.card-14__link span {
transition: none;
}
.card-14__card:hover {
transform: none;
}
.card-14__card:hover .card-14__mock {
transform: none;
}
.card-14__link:hover span {
transform: none;
}
}
```Here's a working CSS Card from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Portfolio Project Card
Source: https://codefronts.com/components/css-cards/portfolio-project-card/
Portfolio tile with a CSS-drawn dashboard mockup (top bar, side nav, KPI row, bar chart), category chip, project title, description, tags, and a client + "View project →" footer.
## HTML
```html
<section class="card-14">
<article class="card-14__card">
<div class="card-14__thumb">
<div class="card-14__mock" aria-hidden="true">
<div class="card-14__mock-top"></div>
<div class="card-14__mock-body">
<div class="card-14__mock-side">
<span></span><span></span><span></span><span></span>
</div>
<div class="card-14__mock-main">
<div class="card-14__mock-kpi"><span></span><span></span><span></span></div>
<div class="card-14__mock-chart">
<span style="height:35%"></span>
<span style="height:55%"></span>
<span style="height:40%"></span>
<span style="height:70%"></span>
<span style="height:60%"></span>
<span style="height:85%"></span>
<span style="height:50%"></span>
<span style="height:75%"></span>
</div>
</div>
</div>
</div>
<div class="card-14__overlay" aria-hidden="true"></div>
</div>
<div class="card-14__body">
<span class="card-14__chip">SAAS · DASHBOARD</span>
<h3 class="card-14__title">Meridian Analytics Rebrand</h3>
<p class="card-14__desc">Redesigned the analytics dashboard for 12k+ enterprise users — 41% faster time-to-insight.</p>
<ul class="card-14__tags">
<li>Figma</li>
<li>Design System</li>
<li>Data Viz</li>
</ul>
<div class="card-14__foot">
<span class="card-14__client">Meridian Labs</span>
<a class="card-14__link" href="#project">View project <span aria-hidden="true">→</span></a>
</div>
</div>
</article>
</section>
```
## CSS
```css
.card-14 {
width: 100%;
min-height: 100vh;
display: block;
padding: clamp(28px, 5vw, 56px) clamp(20px, 4vw, 40px);
box-sizing: border-box;
background: #f8f7f4;
font-family: 'Inter', system-ui, sans-serif;
--focus: #b8613b;
}
.card-14 *,
.card-14 *::before,
.card-14 *::after {
box-sizing: border-box;
}
.card-14__card {
width: 100%;
max-width: 340px;
margin-left: auto;
margin-right: auto;
background: #fff;
border-radius: 16px;
overflow: hidden;
border: 1px solid rgba(0,0,0,0.06);
box-shadow: 0 2px 12px rgba(0,0,0,0.04);
transition: transform 0.25s, box-shadow 0.25s;
}
.card-14__card:hover {
transform: translateY(-3px);
box-shadow: 0 14px 32px rgba(184,97,59,0.14);
}
.card-14__thumb {
position: relative;
aspect-ratio: 16/9;
background: linear-gradient(135deg, #f7edd6, #e8c9a0);
overflow: hidden;
}
.card-14__mock {
position: absolute;
inset: 14px;
background: #fff;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(60,30,10,0.15);
transition: transform 0.35s ease;
}
.card-14__card:hover .card-14__mock {
transform: scale(1.04);
}
.card-14__mock-top {
height: 12px;
background: #f0eae1;
border-bottom: 1px solid rgba(0,0,0,0.06);
}
.card-14__mock-body {
display: grid;
grid-template-columns: 40px 1fr;
height: calc(100% - 12px);
}
.card-14__mock-side {
background: #faf7f2;
padding: 8px 6px;
display: flex;
flex-direction: column;
gap: 5px;
}
.card-14__mock-side span {
display: block;
height: 5px;
border-radius: 2px;
background: #e0d5c4;
}
.card-14__mock-side span:first-child {
background: #b8613b;
}
.card-14__mock-main {
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.card-14__mock-kpi {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
}
.card-14__mock-kpi span {
height: 20px;
background: #f0eae1;
border-radius: 3px;
}
.card-14__mock-chart {
flex: 1;
display: flex;
align-items: flex-end;
gap: 3px;
padding-top: 4px;
}
.card-14__mock-chart span {
flex: 1;
background: linear-gradient(180deg, #d97e4a, #b8451f);
border-radius: 2px 2px 0 0;
}
.card-14__overlay {
position: absolute;
inset: 0;
background: linear-gradient(180deg, transparent 60%, rgba(0,0,0,0.05));
transition: opacity 0.3s;
}
.card-14__card:hover .card-14__overlay {
opacity: 0;
}
.card-14__body {
padding: 16px 18px 18px;
}
.card-14__chip {
display: inline-block;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.14em;
color: #b8613b;
background: rgba(184,97,59,0.1);
padding: 4px 9px;
border-radius: 999px;
margin-bottom: 10px;
}
.card-14__title {
font-size: 16px;
font-weight: 700;
margin: 0 0 6px;
color: #1a1a1a;
letter-spacing: -0.01em;
}
.card-14__desc {
font-size: 12.5px;
color: #6b6459;
margin: 0 0 12px;
line-height: 1.55;
}
.card-14__tags {
list-style: none;
padding: 0;
margin: 0 0 14px;
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.card-14__tags li {
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 4px;
background: #f5f2ec;
color: #6b6459;
}
.card-14__foot {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 12px;
border-top: 1px solid rgba(0,0,0,0.06);
}
.card-14__client {
font-size: 12px;
color: #6b6459;
font-weight: 600;
}
.card-14__link {
display: inline-flex;
align-items: center;
gap: 5px;
min-height: 44px;
font-size: 12.5px;
font-weight: 600;
color: #b8613b;
text-decoration: none;
}
.card-14__link:hover span {
transform: translateX(3px);
}
.card-14__link span {
display: inline-block;
transition: transform 0.2s;
}
.card-14__link:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 3px;
border-radius: 4px;
}
@media (prefers-reduced-motion: reduce) {
.card-14__card,
.card-14__mock,
.card-14__overlay,
.card-14__link span {
transition: none;
}
.card-14__card:hover {
transform: none;
}
.card-14__card:hover .card-14__mock {
transform: none;
}
.card-14__link:hover span {
transform: none;
}
}
```How this works
This portfolio project card is a 340px article with a 16px border-radius, a 16:9 CSS-drawn dashboard thumbnail sitting above a body block with a chip, title, description, tag row, client name, and view-project arrow-link. The thumbnail paints a warm linear-gradient(135deg, #f7edd6, #e8c9a0) as its background wash, then insets a 14px white mock rectangle on top — the mock is a mini analytics dashboard drawn entirely in flex and grid with no images.
The mock is a grid of grid-template-columns: 40px 1fr: a left rail rendered as four horizontal bars (the first painted #b8613b for the active-state hint), and a main area with a 3-column KPI row followed by an 8-bar chart. Each chart bar is a span with an inline style height between 35% and 85%, and every bar carries a linear-gradient(180deg, #d97e4a, #b8451f) fill with border-radius: 2px 2px 0 0. The whole mock scales to transform: scale(1.04) on card-hover while a rgba(0,0,0,0.05) overlay fades to transparent — two coordinated micro-motions that read as a project preview.
The body follows a strict Read.cv-style pattern: a 10px monospaced-tracking chip (letter-spacing: 0.14em) painted at color: #b8613b on rgba(184,97,59,0.1), a 16px title, a 12.5px description, three tag pills, and a footer that separates client name from the View project arrow-link with a top border. The arrow slides translateX(3px) on link-hover, the anchor keeps a 44px min-height for WCAG 2.5.5, and every transition collapses under prefers-reduced-motion.
Make it yours
- Retint the entire palette by swapping the --focus token from #b8613b to your brand hue and updating the three rgba(184, 97, 59, ...) references on the chip, hover shadow, and link — the mock chart gradient d97e4a-to-b8451f stays independent so the thumbnail keeps its warm signature.
- Replace the CSS-drawn dashboard mock with a real screenshot by dropping an img tag at aspect-ratio 16/9 inside the thumb div; keep object-fit: cover on the img and the 14px inset white padding disappears cleanly for photo-based portfolios.
- Reflow tags into overflow rows by keeping flex-wrap: wrap on the tags list — the 6px gap and 4px border-radius stay stable at any count, and long project stacks like Figma, Design System, Data Viz, Motion, Prototyping wrap without container-query gymnastics.
- Turn the whole card into an anchor by wrapping the article in an a tag with display: block; text-decoration: none — move the outline focus ring from the inner link up to the outer anchor, and the entire tile becomes one keyboard target with the arrow still animating on hover.
- Compose a portfolio grid by parent-setting grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)) and gap: 24px — the 340px card cap plus 16:9 thumbnail produces the same visual rhythm Cargo and Read.cv portfolios ship on their case-study pages.
Gotchas — read before shipping
- The chip color #b8613b on rgba(184,97,59,0.1) computed against #fff resolves to roughly 4.6:1 — clears WCAG 1.4.3 AA at the boundary. Any darker background (light-tan surface, off-white paper) drops the effective ratio below 4.5:1. Always test the chip against the parent grid page background, not against #fff in isolation.
- The mock chart uses inline style attributes for heights on span children — React strict JSX will complain about style objects if you paste this into a component. Convert every span to style={{ height: '35%' }} or store the eight heights in an array and map — never leave string-style inline attributes in a JSX file.
- The transform: scale(1.04) on the inner mock during card-hover is composited on top of the border-radius clip — Safari occasionally leaks pixels outside the 6px rounded corner during the scale keyframe. Add will-change: transform on the mock, or drop the scale to 1.02 to keep the seam clean.
- The View project link uses a right arrow character followed by aria-hidden="true" — screen readers do not hear the arrow, which is correct WCAG 4.1.2. Never expose the arrow as content: assistive users already know the link goes somewhere from the anchor role.
- The 44px min-height on the anchor exists purely to hit WCAG 2.5.5 — if you tighten padding on the footer to fit a denser grid, keep the min-height even if visible content is only 20px tall. The touch target size regulation is about the hit area, not the visible glyph.
- The card-hover animation runs on the card, mock, and overlay simultaneously — three concurrent property transitions per card. On a 30-card portfolio index page this saturates the compositor thread on 60Hz Android and drops INP well past 200ms. Use content-visibility: auto on the outer card so offscreen cards skip the transition graph entirely.