Skip to content

Commit 64eac3d

Browse files
committed
feat: [#115] contact page
1 parent e4d6117 commit 64eac3d

File tree

6 files changed

+249
-8
lines changed

6 files changed

+249
-8
lines changed
576 KB
Loading

src/content/contact/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { ContactPageContentStructure } from "../../views";
2+
3+
import PROFILE_PIC from './assets/charles-doucet-profile-picture.jpg';
4+
5+
6+
export const contactContent: ContactPageContentStructure = {
7+
bio: {
8+
profilePictureUrl: PROFILE_PIC,
9+
title: 'Hi, I’m **CD**.',
10+
bio: [
11+
"I’m a Montreal-based creative developer, fully dedicated to my project, cd-labs. I personally read every message that comes through here and will get back to you as soon as possible."
12+
],
13+
},
14+
form: {
15+
formTitle: "**Get in Touch**",
16+
formInstructions: "Whether you have a general question or an interesting project you'd like to discuss, I'd love to hear from you.",
17+
embedCode:'<iframe src="https://docs.google.com/forms/d/e/1FAIpQLSe8QcAd36IhUt2G2Icbab-1KgxJvCnC-XYApVINyr2UkdzwVQ/viewform?embedded=true" width="640" height="860" frameborder="0" marginheight="0" marginwidth="0">Loading…</iframe>',
18+
}
19+
};

src/styles/base/variables.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
--text-5xl: clamp(2.5rem, 8vw, 6rem);
3232

3333
/* Spacing */
34+
--space-mobile-top-safe: 200px;
35+
--space-desktop-top-safe: 150px;
3436
--space-xxs: clamp(5px, 1.5vw, 10px);
3537
--space-xs: clamp(10px, 2.5vw, 15px);
3638
--space-sm: clamp(15px, 3vw, 20px);

src/styles/views/contact.css

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,115 @@
22
Contact Page Styles
33
========================================================================== */
44

5-
/* Add contact-specific styles here */
5+
#contact-page {
6+
min-height: 100vh;
7+
background-color: var(--color-background-alt);
8+
padding-top: var(--space-desktop-top-safe);
9+
10+
#wrapper {
11+
z-index: var(--z-content);
12+
}
13+
14+
.pixel-grid {
15+
top: 0;
16+
left: 0;
17+
width: 100%;
18+
height: 100%;
19+
z-index: var(--z-base);
20+
}
21+
22+
.bio-container {
23+
position: relative; /* Establishes positioning context for z-index */
24+
display: flex;
25+
flex-direction: row;
26+
align-items: center;
27+
justify-content: center; /* Centers the bio content as a unit */
28+
gap: var(--space-lg);
29+
z-index: var(--z-content);
30+
31+
.profile-picture {
32+
flex-shrink: 0; /* Prevents image from shrinking */
33+
order: 1; /* Ensures image stays on left */
34+
35+
img {
36+
max-width: 400px;
37+
height: auto;
38+
border-radius: 10px;
39+
40+
}
41+
}
42+
43+
.bio-text {
44+
flex: 0 1 auto; /* Allow natural sizing instead of forced 50% */
45+
max-width: 700px; /* Constrain text width for readability */
46+
order: 2; /* Ensures text stays on right */
47+
48+
p {
49+
font-size: var(--text-lg);
50+
margin-bottom: var(--space-lg);
51+
}
52+
}
53+
}
54+
55+
.contact-form-container {
56+
position: relative;
57+
max-width: 800px;
58+
margin: var(--space-xl) auto 0 auto;
59+
padding-bottom: var(--space-xl);
60+
text-align: center;
61+
62+
p {
63+
max-width: 640px;
64+
text-align: center;
65+
margin: 0 auto;
66+
margin-bottom: var(--space-lg);
67+
}
68+
69+
h3 {
70+
margin-bottom: var(--space-lg);
71+
color: var(--color-text);
72+
}
73+
74+
.contact-form {
75+
width: 100%;
76+
min-height: 600px;
77+
border: none;
78+
border-radius: var(--radius-sm);
79+
overflow: hidden;
80+
}
81+
}
82+
}
83+
84+
/* ==========================================================================
85+
Responsive Styles
86+
========================================================================== */
87+
88+
@media (max-width: 1024px) {
89+
#contact-page {
90+
.bio-container {
91+
flex-direction: column;
92+
justify-content: center;
93+
text-align: center;
94+
gap: var(--space-xl);
95+
96+
.profile-picture {
97+
order: 1; /* Profile picture first */
98+
99+
img {
100+
max-width: 250px; /* Slightly smaller on mobile */
101+
}
102+
}
103+
104+
.bio-text {
105+
order: 2; /* Bio text second */
106+
max-width: 100%; /* Allow full width on smaller screens */
107+
}
108+
}
109+
}
110+
}
111+
112+
@media (max-width: 768px) {
113+
#contact-page {
114+
padding-top: var(--space-mobile-top-safe);
115+
}
116+
}

src/views/contact/form.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { writeParagraph, writeTitle } from '../utils/';
2+
3+
export type ContactFormOptions = {
4+
readonly formTitle: string;
5+
readonly formInstructions: string;
6+
readonly embedCode: string; // Google form embed HTML code
7+
}
8+
9+
// --------------------------------------------------------------------------------
10+
11+
export function createContactForm(options: ContactFormOptions) {
12+
const formContainer = document.createElement('div');
13+
formContainer.className = 'contact-form-container';
14+
15+
const formIntro = document.createElement('div');
16+
const formTitle = writeTitle("h3", options.formTitle);
17+
const formInstructions = writeParagraph(options.formInstructions);
18+
formIntro.className = 'form-intro';
19+
formIntro.appendChild(formTitle);
20+
formIntro.appendChild(formInstructions);
21+
22+
formContainer.appendChild(formIntro);
23+
formContainer.appendChild(form(options.embedCode));
24+
25+
return formContainer;
26+
}
27+
28+
// --------------------------------------------------------------------------------
29+
30+
function form(embedCode: string) {
31+
// Create a wrapper div and insert the Google Forms embed code directly
32+
const iframeWrapper = document.createElement('div');
33+
iframeWrapper.className = 'iframe-wrapper';
34+
iframeWrapper.innerHTML = embedCode;
35+
36+
// Add custom styling to the embedded iframe
37+
const iframe = iframeWrapper.querySelector('iframe');
38+
if (iframe) {
39+
iframe.className = 'contact-form';
40+
iframe.setAttribute('loading', 'lazy');
41+
iframe.setAttribute('scrolling', 'no'); // Try to disable scrolling
42+
43+
// Try to modify the URL to hide scrollbars
44+
const currentSrc = iframe.src;
45+
if (currentSrc && !currentSrc.includes('scrolling=no')) {
46+
const separator = currentSrc.includes('?') ? '&' : '?';
47+
iframe.src = `${currentSrc}${separator}scrolling=no&chrome=false`;
48+
}
49+
}
50+
51+
return iframeWrapper;
52+
}

src/views/contact/index.ts

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,69 @@
1-
import {writeLink, writeParagraph, writeTitle} from "../utils";
1+
import {createWrapper, writeParagraph, writeTitle} from "../utils";
2+
import { createPixelGridBackground } from "../utils";
3+
import { createContactForm, ContactFormOptions } from "./form";
4+
import { contactContent } from "../../content/contact";
5+
import { insertAllSocials } from "../../components/socials";
6+
7+
export type ContactPageContentStructure = {
8+
readonly bio: BioContent;
9+
readonly form: ContactFormOptions;
10+
}
11+
12+
type BioContent = {
13+
readonly profilePictureUrl: string;
14+
readonly title: string;
15+
readonly bio: string[];
16+
}
17+
18+
19+
// --------------------------------------------------------------------------------
220

321
export function contactView() {
4-
const article = document.createElement('article');
5-
const pageTitle = writeTitle("h1", "contact");
6-
const pageSubtitle = writeTitle("h5", "let's connect");
22+
const page = document.createElement('div');
23+
24+
const pixelGrid = createPixelGridBackground('top', {
25+
columns: 10,
26+
colors: ['#0f0f0f', '#2a2a2a', '#181818']
27+
});
28+
29+
page.id = 'contact-page';
30+
31+
const wrapper = createWrapper();
32+
33+
wrapper.appendChild(bioHeader(contactContent.bio));
34+
wrapper.appendChild(createContactForm(contactContent.form));
35+
36+
page.appendChild(pixelGrid);
37+
page.appendChild(wrapper);
38+
return page;
39+
}
40+
41+
// --------------------------------------------------------------------------------
42+
43+
function bioHeader(content: BioContent) {
44+
const bioContainer = document.createElement('div');
45+
bioContainer.className = 'bio-container';
46+
47+
const profileConatiner = document.createElement('div');
48+
profileConatiner.className = 'profile-picture';
49+
const profileImg = document.createElement('img');
50+
profileImg.src = content.profilePictureUrl;
51+
profileImg.alt = "Profile Picture";
52+
profileConatiner.appendChild(profileImg);
53+
54+
const bioTextContainer = document.createElement('div');
55+
bioTextContainer.className = 'bio-text';
56+
const bioTitle = writeTitle("h2", content.title);
57+
bioTextContainer.appendChild(bioTitle);
58+
59+
content.bio.forEach(paragraph => {
60+
bioTextContainer.appendChild(writeParagraph(paragraph));
61+
});
62+
63+
bioTextContainer.appendChild(insertAllSocials());
764

8-
article.appendChild(pageTitle);
9-
article.appendChild(pageSubtitle);
65+
bioContainer.appendChild(profileConatiner);
66+
bioContainer.appendChild(bioTextContainer);
1067

11-
return article;
68+
return bioContainer;
1269
}

0 commit comments

Comments
 (0)