diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..e9957c9
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,33 @@
+# AGENTS.md
+
+## Project Overview
+
+Personal digital business card / portfolio website for Solomon Laing. A static, framework-less site with zero build steps — plain HTML, CSS, and vanilla JavaScript.
+
+## Architecture
+
+- **index.html** — Single-page site structure (header, bio sections, social links, contact info, footer)
+- **style.css** — All styling; mobile-first with a `600px` breakpoint
+- **main.js** — Text word-split animation on click (using anime.js)
+- **cards.js** — Card entrance animation and hover effects (using anime.js)
+- **demo.js** — Stagger grid animation loop (using anime.js)
+- **assets/** — SVG icons and resume PDF
+
+## Key Conventions
+
+- **No build tools or bundlers.** All JS is loaded via `
+
diff --git a/style.css b/style.css
index 931c42f..8da5ba5 100644
--- a/style.css
+++ b/style.css
@@ -1,3 +1,48 @@
+:root,
+[data-theme="dark"] {
+ --bg: #17171A;
+ --card-bg: #17171A;
+ --card-border: #CFD1D4;
+ --text-primary: #F7F9FC;
+ --text-secondary: #999BA4;
+ --accent-blue: #0083E2;
+ --divider: #CFD1D4;
+ --icon-color: #F7F9FC;
+ --part-color: #F7F9FC;
+ --part-bg-color: rgba(247, 249, 252, 0.06);
+ --card-shadow: none;
+}
+
+[data-theme="light"] {
+ --bg: #F7F9FC;
+ --card-bg: #F7F9FC;
+ --card-border: #C9CBCE;
+ --text-primary: #393F53;
+ --text-secondary: #6F7588;
+ --accent-blue: #0075CB;
+ --divider: #C9CBCE;
+ --icon-color: #393F53;
+ --part-color: #393F53;
+ --part-bg-color: rgba(57, 63, 83, 0.06);
+ --card-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
+}
+
+@media (prefers-color-scheme: light) {
+ :root:not([data-theme="dark"]) {
+ --bg: #F7F9FC;
+ --card-bg: #F7F9FC;
+ --card-border: #C9CBCE;
+ --text-primary: #393F53;
+ --text-secondary: #6F7588;
+ --accent-blue: #0075CB;
+ --divider: #C9CBCE;
+ --icon-color: #393F53;
+ --part-color: #393F53;
+ --part-bg-color: rgba(57, 63, 83, 0.06);
+ --card-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
+ }
+}
+
*,
*::before,
*::after {
@@ -7,13 +52,19 @@
body,
html {
overflow-x: hidden;
+ margin: 0;
+ padding: 0;
}
body {
- margin: 0;
font-family: Roboto, sans-serif;
- color: #d7d6d4;
- background: #1a1617;
+ color: var(--text-primary);
+ background: var(--bg);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 100vh;
+ padding: 2rem 1rem;
}
p,
@@ -26,140 +77,319 @@ h4 {
img {
max-width: 100%;
-}
-
-.accent1 {
- color: #d7d6d4;
- background: #6B5C4A;
-}
-
-.accent2 {
- color: #d7d6d4;
- background: #564945;
-}
-
-.accent3 {
- color: #d7d6d4;
- background: #262527;
-}
-
-.container {
- width: 80%;
- max-width: 800px;
- margin: 0 auto;
- position: relative;
-}
-
-.title,
-.region {
- padding: 2rem 0;
-}
-
-.foot {
- padding-top: 2rem;
- padding-bottom: 1rem;
-}
-
-.row {
display: block;
}
-.f-row {
- display: flex;
- flex-wrap: wrap;
-}
-
-.top-gap {
- margin-top: 2rem;
-}
-
-.col:not(:first-child) {
- padding-top: .5rem;
-}
-
-.center {
- display: flex;
- justify-content: center;
-}
-
-.cards {
- display: flex;
- gap: 1rem;
- align-items: center;
-}
-
-.card {
- display: block;
- flex: 1;
-
- >img {
- max-width: 100px !important;
- }
-}
-
-.make-icon-smaller {
- display: flex;
-
- a {
- display: flex;
- flex: 1;
-
- svg {
- margin: auto;
- height: 75%;
- padding-bottom: 5%;
- }
- }
-}
-
-h2 {
- span {
- display: inline-block;
- }
-}
-
a,
a:hover,
a:active,
a:link,
a:focus,
a:visited {
- color: #d7d6d4;
+ color: inherit;
+ text-decoration: none;
}
-@media (min-width:600px) {
- .row {
+.theme-transitioning,
+.theme-transitioning *,
+.theme-transitioning *::before,
+.theme-transitioning *::after {
+ transition: color 500ms ease, background-color 500ms ease, border-color 500ms ease, box-shadow 500ms ease, opacity 500ms ease !important;
+}
+
+.vcard {
+ position: relative;
+ z-index: 1;
+ max-width: 1000px;
+ width: 100%;
+ border: 1px solid var(--card-border);
+ border-radius: 12px;
+ background: var(--card-bg);
+ box-shadow: var(--card-shadow);
+ padding: 24px;
+ display: flex;
+ flex-direction: column;
+ gap: 1.25rem;
+}
+
+.vcard-header {
+ display: flex;
+ flex-flow: column;
+}
+
+.vcard-header-right {
+ flex: 1;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+}
+
+.vcard-header-title {
+ display: flex;
+ flex-flow: column;
+ gap: 0.25rem;
+ text-align: left;
+}
+
+.vcard-name {
+ font-size: 1.75rem;
+ font-weight: 800;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ line-height: 1.2;
+}
+
+.name-first {
+ color: var(--accent-blue);
+}
+
+.name-last {
+ color: var(--text-primary);
+}
+
+.vcard-subtitle {
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--text-secondary);
+}
+
+.theme-toggle {
+ background: none;
+ border: none;
+ color: var(--text-primary);
+ cursor: pointer;
+ padding: 6px;
+ border-radius: 6px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ transition: opacity 0.2s;
+
+ &:hover {
+ opacity: 0.7;
+ }
+}
+
+.vcard-divider {
+ border: none;
+ border-top: 1px solid var(--divider);
+ margin: 0;
+}
+
+.vcard-body {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+}
+
+.vcard-image {
+ display: flex;
+ justify-content: center;
+
+ img {
+ width: 100%;
+ aspect-ratio: 1;
+ max-width: 280px;
+ border-radius: 10px;
+ object-fit: cover;
+ }
+}
+
+.vcard-content {
+ display: flex;
+ flex-flow: column-reverse;
+ gap: 1.25rem;
+}
+
+.vcard-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+ justify-content: center;
+ font-size: 0.65rem;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--text-primary);
+}
+
+.tag {
+ border: 1px solid var(--divider);
+ padding: 0.25rem 0.6rem;
+ border-radius: 5px;
+}
+
+.tag-sep {
+ display: none;
+}
+
+.vcard-bio {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ font-size: 0.9rem;
+ line-height: 1.6;
+ color: var(--text-primary);
+}
+
+.animate-letter {
+ display: inline-block;
+}
+
+.vcard-section {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.section-title {
+ font-size: 1rem;
+ font-weight: 700;
+ color: var(--accent-blue);
+}
+
+.contact-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.6rem;
+
+ li {
display: flex;
- gap: 3rem;
+ align-items: center;
+ gap: 0.6rem;
+ font-size: 0.9rem;
+ color: var(--text-secondary);
+
+ svg {
+ flex-shrink: 0;
+ color: var(--icon-color);
+ }
+
+ a {
+ color: var(--text-primary);
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+}
+
+.social-links {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.social-link {
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+ font-size: 0.85rem;
+ color: var(--text-primary);
+
+ &:hover {
+ opacity: 0.7;
}
- .col {
- flex: 1;
- padding-top: 0 !important;
+ svg {
+ flex-shrink: 0;
+ color: var(--icon-color);
}
- .cards {
- gap: unset;
- justify-content: space-between;
+ span {
+ white-space: nowrap;
}
+}
- .card {
- max-width: 115px;
- }
+.vcard-footer {
+ font-size: 0.8rem;
+ color: var(--text-secondary);
+ text-align: left;
+ margin-top: 0.5rem;
}
.stagger-visualizer {
+ position: fixed;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
- width: 12rem;
- height: 6rem;
+ width: 60rem;
+ height: 30rem;
+ top: 0;
+ rotate: 90deg;
}
.part {
- width: 1rem;
- height: 1rem;
- border: 1px solid #d7d6d4;
- background-color: #d7d6d4;
+ width: 5rem;
+ height: 5rem;
+ border: 1px solid var(--part-bg-color);
+ background-color: var(--part-bg-color);
+}
+
+@media (min-width: 768px) {
+ .vcard {
+ padding: 32px 40px;
+ }
+
+ .vcard-header {
+ flex-flow: row;
+ gap: .5rem;
+ }
+
+ .vcard-header-text {
+ text-align: left;
+ }
+
+ .vcard-body {
+ flex-direction: row;
+ gap: 2rem;
+ }
+
+ .vcard-image {
+ flex: 0 0 auto;
+ min-width: 220px;
+ align-self: stretch;
+
+ img {
+ height: 100%;
+ object-fit: cover;
+ border-radius: 10px;
+ }
+ }
+
+ .vcard-content {
+ flex: 1;
+ min-width: 0;
+ flex-flow: column;
+ }
+
+ .vcard-tags {
+ justify-content: flex-start;
+ }
+
+ .social-links {
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 1rem;
+ }
+
+
+ .stagger-visualizer {
+ width: 120rem;
+ height: 60rem;
+ rotate: unset;
+ }
+
+ .part {
+ width: 10rem;
+ height: 10rem;
+ }
}
diff --git a/theme.js b/theme.js
new file mode 100644
index 0000000..6077d95
--- /dev/null
+++ b/theme.js
@@ -0,0 +1,28 @@
+const toggle = document.querySelector('.theme-toggle');
+const iconMoon = toggle.querySelector('.icon-moon');
+const iconSun = toggle.querySelector('.icon-sun');
+
+function getPreferredTheme() {
+ const stored = localStorage.getItem('theme');
+ if (stored) return stored;
+ return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
+}
+
+function applyTheme(theme) {
+ document.documentElement.setAttribute('data-theme', theme);
+ iconMoon.style.display = theme === 'light' ? 'block' : 'none';
+ iconSun.style.display = theme === 'dark' ? 'block' : 'none';
+}
+
+applyTheme(getPreferredTheme());
+
+toggle.addEventListener('click', () => {
+ const current = document.documentElement.getAttribute('data-theme');
+ const next = current === 'dark' ? 'light' : 'dark';
+ document.documentElement.classList.add('theme-transitioning');
+ localStorage.setItem('theme', next);
+ applyTheme(next);
+ setTimeout(() => {
+ document.documentElement.classList.remove('theme-transitioning');
+ }, 500);
+});