Skip to content

Commit ee47d3e

Browse files
committed
Built the blog page.
1 parent afa8c5f commit ee47d3e

10 files changed

Lines changed: 566 additions & 0 deletions
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
@use 'variables' as *;
2+
3+
.portfolio-featured {
4+
padding: 4rem 0;
5+
background-color: $obsidian;
6+
7+
&__heading {
8+
font-family: $font-header;
9+
font-size: 1.75rem;
10+
font-weight: 600;
11+
margin-bottom: 1rem;
12+
}
13+
14+
.divider {
15+
animation: portfolio-featured-divider-expand 1.2s ease-out 0.4s forwards;
16+
width: 0;
17+
}
18+
19+
&__heading-accent {
20+
color: $deep-azure;
21+
}
22+
23+
&__heading-light {
24+
color: $frost;
25+
}
26+
27+
&__image {
28+
width: 100%;
29+
border-radius: 8px;
30+
border: 1px solid rgba($quicksilver, 0.1);
31+
}
32+
33+
&__content {
34+
display: flex;
35+
flex-direction: column;
36+
justify-content: center;
37+
}
38+
39+
&__type {
40+
display: block;
41+
font-family: $font-body;
42+
font-size: 0.75rem;
43+
font-weight: 600;
44+
text-transform: uppercase;
45+
letter-spacing: 0.1em;
46+
color: $pewter;
47+
margin-bottom: 0.5rem;
48+
}
49+
50+
&__title {
51+
font-family: $font-header;
52+
font-size: 1.35rem;
53+
font-weight: 600;
54+
color: $sky-blue;
55+
margin-bottom: 0.75rem;
56+
}
57+
58+
&__labels {
59+
display: flex;
60+
flex-wrap: wrap;
61+
gap: 0.5rem;
62+
margin-bottom: 0.75rem;
63+
}
64+
65+
&__description {
66+
font-family: $font-body;
67+
font-size: 0.95rem;
68+
color: $quicksilver;
69+
line-height: 1.6;
70+
margin-bottom: 0;
71+
}
72+
73+
&__button {
74+
margin-top: 1.25rem;
75+
align-self: flex-start;
76+
}
77+
}
78+
79+
@keyframes portfolio-featured-divider-expand {
80+
0% { width: 0%; }
81+
100% { width: 25%; }
82+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
@use 'variables' as *;
2+
3+
.portfolio-list {
4+
padding: 4rem 0;
5+
background-color: $charcoal;
6+
7+
&__heading {
8+
font-family: $font-header;
9+
font-size: 1.75rem;
10+
font-weight: 600;
11+
margin-bottom: 1rem;
12+
}
13+
14+
&__heading-accent {
15+
color: $deep-azure;
16+
}
17+
18+
&__heading-light {
19+
color: $frost;
20+
}
21+
22+
&__project {
23+
margin-bottom: 3rem;
24+
25+
&:last-child {
26+
margin-bottom: 0;
27+
}
28+
}
29+
30+
&__image {
31+
width: 100%;
32+
border-radius: 8px;
33+
border: 1px solid rgba($quicksilver, 0.1);
34+
}
35+
36+
&__content {
37+
display: flex;
38+
flex-direction: column;
39+
justify-content: center;
40+
}
41+
42+
&__type {
43+
display: block;
44+
font-family: $font-body;
45+
font-size: 0.75rem;
46+
font-weight: 600;
47+
text-transform: uppercase;
48+
letter-spacing: 0.1em;
49+
color: $pewter;
50+
margin-bottom: 0.5rem;
51+
}
52+
53+
&__title {
54+
font-family: $font-header;
55+
font-size: 1.35rem;
56+
font-weight: 600;
57+
color: $sky-blue;
58+
margin-bottom: 0.75rem;
59+
}
60+
61+
&__description {
62+
font-family: $font-body;
63+
font-size: 0.95rem;
64+
color: $quicksilver;
65+
line-height: 1.6;
66+
margin-bottom: 0;
67+
}
68+
69+
&__button {
70+
margin-top: 1.25rem;
71+
align-self: flex-start;
72+
}
73+
}

src/stories/assets/css/main.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
@use 'working-on';
2424
@use 'blog-featured';
2525
@use 'blog-list';
26+
@use 'portfolio-featured';
27+
@use 'portfolio-list';
2628
@use 'footer';
2729

2830
// 4. Utility classes

src/stories/assets/css/pages.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@
2121
background-color: $obsidian;
2222
min-height: 100vh;
2323
}
24+
25+
// Portfolio Page
26+
.page-portfolio {
27+
background-color: $obsidian;
28+
min-height: 100vh;
29+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { Meta, StoryObj } from '@storybook/react-vite';
2+
3+
import { PortfolioFeatured } from './PortfolioFeatured';
4+
5+
import pubOptionsImg from '../assets/pub-options-screenshot.svg';
6+
7+
const meta = {
8+
title: 'jorgecalderon.codes/Modules/Portfolio Featured',
9+
component: PortfolioFeatured,
10+
parameters: {
11+
layout: 'fullscreen',
12+
backgrounds: {
13+
default: 'obsidian',
14+
values: [{ name: 'obsidian', value: '#1A1A1A' }],
15+
},
16+
},
17+
tags: ['autodocs'],
18+
} satisfies Meta<typeof PortfolioFeatured>;
19+
20+
export default meta;
21+
type Story = StoryObj<typeof meta>;
22+
23+
const sampleProject = {
24+
title: 'Publishing Options',
25+
type: 'Contributed Drupal Module',
26+
description:
27+
'A Drupal module that lets administrators create custom publishing options beyond the built-in defaults. Integrates with Views for field, filter, and contextual filter support. Compatible with Drupal 10 and 11.',
28+
imageSrc: pubOptionsImg,
29+
imageAlt: 'Publishing Options module on Drupal.org',
30+
labels: [{ text: 'Drupal' }, { text: 'PHP' }],
31+
buttonLabel: 'View Module',
32+
buttonHref: 'https://www.drupal.org/project/pub_options',
33+
};
34+
35+
export const Default: Story = {
36+
render: () => (
37+
<PortfolioFeatured
38+
heading={
39+
<>
40+
<span className="portfolio-featured__heading-accent">Featured</span>{' '}
41+
<span className="portfolio-featured__heading-light">Project.</span>
42+
</>
43+
}
44+
project={sampleProject}
45+
/>
46+
),
47+
args: {
48+
project: sampleProject,
49+
},
50+
};
51+
52+
export const NoHeading: Story = {
53+
args: {
54+
project: sampleProject,
55+
},
56+
};
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react';
2+
import { Divider } from '../components/Divider';
3+
import { Label } from '../components/Label';
4+
import type { CardLabel } from '../components/Card';
5+
6+
export interface FeaturedProject {
7+
/** Project title */
8+
title: string;
9+
/** Project description */
10+
description: string;
11+
/** Project type label (e.g. "Contributed Drupal Module") */
12+
type: string;
13+
/** Screenshot image source */
14+
imageSrc: string;
15+
/** Alt text for the screenshot (defaults to title) */
16+
imageAlt?: string;
17+
/** Tech / category labels */
18+
labels?: CardLabel[];
19+
/** Button label (e.g. "View Project") */
20+
buttonLabel?: string;
21+
/** Button link URL */
22+
buttonHref?: string;
23+
}
24+
25+
export interface PortfolioFeaturedProps {
26+
/** Section heading — renders as h2 */
27+
heading?: React.ReactNode;
28+
/** The featured project to showcase */
29+
project: FeaturedProject;
30+
}
31+
32+
/** Hero-style featured project with image/text side-by-side layout. */
33+
export const PortfolioFeatured = ({ heading, project }: PortfolioFeaturedProps) => {
34+
return (
35+
<section className="portfolio-featured">
36+
<div className="container">
37+
{heading && (
38+
<h2 className="portfolio-featured__heading font-manrope fw-bold">
39+
{heading}
40+
</h2>
41+
)}
42+
<Divider className="divider--blue-100 mb-4" />
43+
44+
<div className="portfolio-featured__project row align-items-center g-4">
45+
<div className="col-12 col-md-6">
46+
<img
47+
className="portfolio-featured__image"
48+
src={project.imageSrc}
49+
alt={project.imageAlt ?? project.title}
50+
/>
51+
</div>
52+
<div className="col-12 col-md-6">
53+
<div className="portfolio-featured__content">
54+
<span className="portfolio-featured__type">{project.type}</span>
55+
<h3 className="portfolio-featured__title">{project.title}</h3>
56+
{project.labels && project.labels.length > 0 && (
57+
<div className="portfolio-featured__labels">
58+
{project.labels.map((label) => (
59+
<Label key={label.text} text={label.text} color={label.color} />
60+
))}
61+
</div>
62+
)}
63+
<p className="portfolio-featured__description">{project.description}</p>
64+
{project.buttonLabel && project.buttonHref && (
65+
<a
66+
className="btn btn-azure-bolt portfolio-featured__button"
67+
href={project.buttonHref}
68+
target="_blank"
69+
rel="noopener noreferrer"
70+
>
71+
{project.buttonLabel}
72+
</a>
73+
)}
74+
</div>
75+
</div>
76+
</div>
77+
</div>
78+
</section>
79+
);
80+
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import type { Meta, StoryObj } from '@storybook/react-vite';
2+
3+
import { PortfolioList } from './PortfolioList';
4+
5+
import bcuImg from '../assets/bcu-screenshot.svg';
6+
import imgStyling from '../assets/styling.png';
7+
import imgTheming from '../assets/theming.png';
8+
9+
const meta = {
10+
title: 'jorgecalderon.codes/Modules/Portfolio List',
11+
component: PortfolioList,
12+
parameters: {
13+
layout: 'fullscreen',
14+
backgrounds: {
15+
default: 'charcoal',
16+
values: [{ name: 'charcoal', value: '#2D2D2D' }],
17+
},
18+
},
19+
tags: ['autodocs'],
20+
} satisfies Meta<typeof PortfolioList>;
21+
22+
export default meta;
23+
type Story = StoryObj<typeof meta>;
24+
25+
const sampleProjects = [
26+
{
27+
title: 'Blood Cancer United',
28+
type: 'Full-Time Role',
29+
description:
30+
'The digital platform for Blood Cancer United, supporting patients and families through research, financial assistance, and advocacy resources. Built on Drupal, serving millions of visitors annually.',
31+
imageSrc: bcuImg,
32+
imageAlt: 'Blood Cancer United website homepage',
33+
buttonLabel: 'Visit Site',
34+
buttonHref: 'https://bloodcancerunited.org/',
35+
},
36+
{
37+
title: 'jorgecalderon.codes',
38+
type: 'Personal Project',
39+
description:
40+
'A component-driven personal website built with React, TypeScript, and Storybook. Every component is designed, documented, and tested inside Storybook before integration.',
41+
imageSrc: imgStyling,
42+
imageAlt: 'jorgecalderon.codes in Storybook',
43+
buttonLabel: 'View Source',
44+
buttonHref: 'https://github.com/jorgecalderon',
45+
},
46+
{
47+
title: 'Enterprise CMS Platform',
48+
type: 'Client Project',
49+
description:
50+
'A headless Drupal architecture powering a multi-site content platform with custom modules, automated deployments, and performance-tuned infrastructure on AWS.',
51+
imageSrc: imgTheming,
52+
imageAlt: 'Enterprise CMS dashboard',
53+
},
54+
];
55+
56+
export const Default: Story = {
57+
render: () => (
58+
<PortfolioList
59+
heading={
60+
<>
61+
<span className="portfolio-list__heading-accent">More</span>{' '}
62+
<span className="portfolio-list__heading-light">Projects.</span>
63+
</>
64+
}
65+
projects={sampleProjects}
66+
/>
67+
),
68+
args: {
69+
projects: sampleProjects,
70+
},
71+
};
72+
73+
export const NoHeading: Story = {
74+
args: {
75+
projects: sampleProjects,
76+
},
77+
};

0 commit comments

Comments
 (0)