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); +});