diff --git a/index.html b/index.html
index 81ff06b..6689bd9 100644
--- a/index.html
+++ b/index.html
@@ -31,6 +31,10 @@
+
+
+
+
diff --git a/src/components/sections/Footer.scss b/src/components/sections/Footer.scss
index 6980484..88ba871 100644
--- a/src/components/sections/Footer.scss
+++ b/src/components/sections/Footer.scss
@@ -12,15 +12,11 @@
gap: $space-md;
padding-block: $space-lg;
- @media (min-width: 1100px) {
- flex-direction: row;
- flex-wrap: wrap;
- align-items: center;
- gap: $space-lg $space-xl;
- }
-
@include desktop-up {
- grid-template-columns: 1fr auto auto auto 1fr;
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
+ grid-template-areas: "brand copyright socials";
+ align-items: center;
+ column-gap: clamp(1.5rem, 4vw, 3rem);
}
}
@@ -28,6 +24,11 @@
display: flex;
flex-direction: column;
gap: 0.25rem;
+
+ @include desktop-up {
+ grid-area: brand;
+ justify-self: start;
+ }
}
&__wordmark {
@@ -96,12 +97,14 @@
&__meta {
display: flex;
align-items: center;
+ justify-content: center;
gap: $space-md;
+ text-align: center;
- @media (min-width: 1100px) {
- flex: 0 0 auto;
- flex-direction: column;
- gap: 0.4rem;
+ @include desktop-up {
+ grid-area: copyright;
+ align-self: center;
+ justify-self: center;
}
}
@@ -111,6 +114,8 @@
gap: 0.625rem;
@include desktop-up {
+ grid-area: socials;
+ justify-self: end;
justify-content: flex-end;
}
diff --git a/src/components/sections/Navbar.scss b/src/components/sections/Navbar.scss
index c1b1a23..89a7e1e 100644
--- a/src/components/sections/Navbar.scss
+++ b/src/components/sections/Navbar.scss
@@ -7,15 +7,10 @@
left: 0;
right: 0;
z-index: $z-fixed;
- border-bottom: 1px solid transparent;
- background: rgba(1, 24, 28, 0.94);
- transition:
- background-color $transition-base,
- border-color $transition-base;
// Blur-only backdrop (no darkening tint) with a feathered bottom edge.
// Lives on a pseudo-element so the logo/links stay crisp and unmasked.
- &__backdrop {
+ &::before {
content: "";
position: absolute;
inset: 0 0 -24px 0;
@@ -32,16 +27,12 @@
transition: opacity 320ms ease;
}
- &--scrolled &__backdrop {
+ &--scrolled::before {
opacity: 1;
}
- &--scrolled {
- border-color: $color-border;
- background: rgba(1, 24, 28, 0.98);
- }
-
&__container {
+ position: relative;
@include container;
display: flex;
align-items: center;
@@ -78,8 +69,9 @@
@media (max-width: #{$bp-desktop - 1px}) {
position: fixed;
inset: 68px 0 0 0;
- background: $color-bg;
- border-top: 1px solid $color-border;
+ background: rgba(0, 22, 29, 0.97);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
display: flex;
flex-direction: column;
align-items: stretch;
diff --git a/src/components/sections/Services.scss b/src/components/sections/Services.scss
index 937cd39..cb90d69 100644
--- a/src/components/sections/Services.scss
+++ b/src/components/sections/Services.scss
@@ -1,28 +1,22 @@
@use '../../styles/variables' as *;
@use '../../styles/mixins' as *;
-#services.section {
- padding-bottom: clamp(2.5rem, 4vw, 4rem);
-
- .sec-head {
- margin-bottom: clamp(1.5rem, 2.5vw, 2.25rem);
- }
-}
-
.services {
&__grid {
display: grid;
- grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
- gap: clamp(0.9rem, 1.5vw, 1.25rem);
- max-width: 54rem;
+ grid-template-columns: 1fr;
+ gap: $space-md;
+
+ @include mobile-up {
+ grid-template-columns: repeat(2, 1fr);
+ }
}
&__card {
position: relative;
@include engineered-panel(30px);
- min-height: clamp(11.25rem, 14vw, 12.75rem);
- padding: clamp(1.35rem, 2vw, 1.75rem);
- padding-right: clamp(4.75rem, 7vw, 6rem);
+ padding: $space-lg;
+ padding-right: clamp(5.25rem, 10vw, 7rem);
&:hover {
border-color: $color-glass-edge;
@@ -66,23 +60,21 @@
&__title {
@include display;
- font-size: clamp(1.45rem, 2vw, 1.65rem);
+ font-size: $text-2xl;
line-height: 1.04;
letter-spacing: $tracking-tight;
- margin-bottom: 0.5rem;
+ margin-bottom: 0.65rem;
transition: color $transition-base;
}
&__desc {
- margin: 0;
- max-width: 36ch;
- font-size: clamp(0.92rem, 1.1vw, 0.98rem);
- line-height: 1.48;
+ font-size: $text-sm;
+ line-height: $leading-normal;
color: $color-text-2;
}
&__marquee {
- margin-top: clamp(1.75rem, 3vw, 2.75rem);
+ margin-top: $space-xl;
overflow: hidden;
mask-image: linear-gradient(to right, transparent 0, #000 11%, #000 89%, transparent 100%);
-webkit-mask-image: linear-gradient(to right, transparent 0, #000 11%, #000 89%, transparent 100%);
@@ -139,7 +131,6 @@
}
&__card {
- min-height: auto;
padding: 1.35rem;
padding-right: 4rem;
}
@@ -161,7 +152,7 @@
}
&__marquee {
- margin-top: 1.5rem;
+ margin-top: 2rem;
}
&__tag {
diff --git a/src/components/sections/Team.scss b/src/components/sections/Team.scss
index c4fd823..f6810e6 100644
--- a/src/components/sections/Team.scss
+++ b/src/components/sections/Team.scss
@@ -15,15 +15,11 @@
&__card {
position: relative;
- border: 1px solid $color-border-2;
- border-radius: 30px;
- background: $color-surface;
- box-shadow:
- inset 0 1px 0 rgba(255, 255, 255, 0.06),
- 0 12px 36px rgba(0, 0, 0, 0.24);
+ @include engineered-panel(30px);
overflow: hidden;
display: flex;
flex-direction: column;
+ scroll-margin-top: 5.75rem;
width: 100%;
transition:
border-color $transition-base,
@@ -31,8 +27,9 @@
transform 300ms $ease-spring;
&:hover {
- border-color: $color-border-2;
- background: $color-surface-2;
+ border-color: $color-glass-edge;
+ background: $color-glass-2;
+ transform: translateY(-4px);
.team__avatar { filter: grayscale(0) contrast(1); }
@@ -50,6 +47,8 @@
&__photo {
position: relative;
+ aspect-ratio: 1 / 1;
+ overflow: hidden;
border-bottom: 1px solid $color-glass-border;
}
@@ -92,6 +91,7 @@
font-size: clamp(0.82rem, 2.8vw, $text-sm);
line-height: 1.48;
color: $color-text-2;
+ flex: 1;
}
&__github {
@@ -124,8 +124,6 @@
}
&__github-arrow {
- margin-left: 0.15rem;
- color: $color-text-3;
transition: transform $transition-base, color $transition-base;
}
}
diff --git a/src/components/ui/Button.scss b/src/components/ui/Button.scss
index 36520a4..e964494 100644
--- a/src/components/ui/Button.scss
+++ b/src/components/ui/Button.scss
@@ -1,6 +1,7 @@
@use '../../styles/variables' as *;
.btn {
+ position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss
index 696449f..fba1396 100644
--- a/src/styles/_mixins.scss
+++ b/src/styles/_mixins.scss
@@ -129,8 +129,8 @@
// Display type (headlines) — Tilt Warp
@mixin display {
font-family: $font-display;
- font-weight: 600;
- letter-spacing: $tracking-tight;
+ font-weight: 400;
+ letter-spacing: 0;
line-height: 1.08;
color: $color-text;
}
diff --git a/src/styles/_reset.scss b/src/styles/_reset.scss
index 1929f84..2bef29d 100644
--- a/src/styles/_reset.scss
+++ b/src/styles/_reset.scss
@@ -17,6 +17,7 @@ html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
+ font-synthesis-weight: none;
text-rendering: optimizeLegibility;
}
diff --git a/tests/acceptance/content.spec.ts b/tests/acceptance/content.spec.ts
index 8664330..7ca31e9 100644
--- a/tests/acceptance/content.spec.ts
+++ b/tests/acceptance/content.spec.ts
@@ -136,4 +136,44 @@ test.describe('landing page content', () => {
await expect(footer.locator(`a[href="${url}"]`)).toBeVisible();
}
});
+
+ test('footer layout keeps copyright centered and socials on the right', async ({ page }) => {
+ await page.goto('/');
+ await page.locator('.footer').scrollIntoViewIfNeeded();
+
+ const layout = await page.evaluate(() => {
+ const rect = (selector: string) => {
+ const element = document.querySelector(selector);
+ const box = element?.getBoundingClientRect();
+
+ if (!box) return null;
+
+ return {
+ left: box.left,
+ right: box.right,
+ center: box.left + box.width / 2,
+ };
+ };
+
+ return {
+ viewportCenter: window.innerWidth / 2,
+ viewportWidth: window.innerWidth,
+ copyright: rect('.footer__copyright'),
+ socials: rect('.footer__socials'),
+ firstSocial: rect('.footer__socials .social-icon'),
+ };
+ });
+
+ expect(layout.copyright).not.toBeNull();
+ expect(layout.socials).not.toBeNull();
+ expect(layout.firstSocial).not.toBeNull();
+ expect(Math.abs((layout.copyright?.center ?? 0) - layout.viewportCenter)).toBeLessThanOrEqual(1);
+
+ if (layout.viewportWidth >= 1024) {
+ expect(layout.socials?.left).toBeGreaterThan((layout.copyright?.right ?? 0) + 32);
+ expect(layout.socials?.right).toBeGreaterThanOrEqual(layout.viewportWidth - 96);
+ } else {
+ expect(layout.firstSocial?.left).toBeLessThanOrEqual(32);
+ }
+ });
});
diff --git a/tests/acceptance/design-baseline.spec.ts b/tests/acceptance/design-baseline.spec.ts
index a6f2dd7..67b6d7a 100644
--- a/tests/acceptance/design-baseline.spec.ts
+++ b/tests/acceptance/design-baseline.spec.ts
@@ -1,5 +1,13 @@
import { expect, type Page, test } from '@playwright/test';
+function alphaFromRgb(color: string) {
+ const match = color.match(/rgba?\(([^)]+)\)/);
+ if (!match) return 1;
+
+ const parts = match[1].split(',').map((part) => part.trim());
+ return parts.length === 4 ? Number(parts[3]) : 1;
+}
+
async function findRuleText(page: Page, selector: string) {
return page.evaluate((targetSelector) => {
const matches: string[] = [];
@@ -33,17 +41,16 @@ async function waitForScrolledNavbarEffect(page: Page) {
await expect(page.locator('.navbar')).toHaveClass(/navbar--scrolled/);
await expect
.poll(() =>
- page.locator('.navbar').evaluate((element) => getComputedStyle(element).backgroundColor),
+ page.locator('.navbar').evaluate((element) => Number(getComputedStyle(element, '::before').opacity)),
)
- .not.toBe('rgba(0, 0, 0, 0)');
+ .toBeGreaterThan(0.95);
}
async function waitForVisualReady(page: Page) {
- await page.evaluate(() => document.fonts.ready);
- // Freeze animated mesh so Linux/Windows captures stay aligned.
await page.addStyleTag({
- content: '.App::before { animation: none !important; transform: none !important; }',
+ content: 'canvas { visibility: hidden !important; } .App__background { animation: none !important; transform: none !important; }',
});
+ await page.evaluate(() => document.fonts.ready);
await page.waitForTimeout(300);
}
@@ -90,7 +97,7 @@ test.describe('design baseline', () => {
await expect(page.locator('.navbar')).toHaveScreenshot(`${testInfo.project.name}-navbar.png`, {
animations: 'disabled',
caret: 'hide',
- maxDiffPixelRatio: 0.02,
+ maxDiffPixelRatio: 0.03,
});
});
@@ -103,81 +110,62 @@ test.describe('design baseline', () => {
await expect(page.locator('.navbar')).toHaveScreenshot(`${testInfo.project.name}-navbar-scrolled.png`, {
animations: 'disabled',
caret: 'hide',
- maxDiffPixelRatio: 0.02,
+ maxDiffPixelRatio: 0.03,
});
});
- test('team cards match approved baselines', async ({ page }, testInfo) => {
+ test('glass surfaces match approved baselines', async ({ page }, testInfo) => {
await page.goto('/');
await lockViewportOn(page, '#team');
await waitForVisualReady(page);
+ await page.addStyleTag({ content: '.navbar { visibility: hidden !important; }' });
- await expect(page.locator('.team__card').first()).toHaveScreenshot(`${testInfo.project.name}-team-card.png`, {
+ await expect(page.locator('.team__card').first()).toHaveScreenshot(`${testInfo.project.name}-team-glass.png`, {
animations: 'disabled',
caret: 'hide',
- maxDiffPixelRatio: 0.03,
+ maxDiffPixelRatio: 0.035,
});
await page.locator('.team__card').first().hover();
await page.waitForTimeout(300);
- await expect(page.locator('.team__card').first()).toHaveScreenshot(`${testInfo.project.name}-team-card-hover.png`, {
+ await expect(page.locator('.team__card').first()).toHaveScreenshot(`${testInfo.project.name}-team-glass-hover.png`, {
animations: 'disabled',
caret: 'hide',
- maxDiffPixelRatio: 0.03,
+ maxDiffPixelRatio: 0.035,
});
});
- test('solid surfaces are used instead of glass', async ({ page }) => {
+ test('glass and transparency effects are active', async ({ page }) => {
await page.goto('/');
await lockViewportOn(page, '#team');
- const cardStyles = await page.locator('.team__card').first().evaluate((element) => {
+ const glassStyles = await page.locator('.team__card').first().evaluate((element) => {
const style = getComputedStyle(element);
return {
backgroundColor: style.backgroundColor,
- backdropFilter: style.backdropFilter,
};
});
const teamCardRule = await findRuleText(page, '.team__card');
- expect(cardStyles.backgroundColor, 'team card should use an opaque surface').toMatch(/rgb/);
- expect(cardStyles.backdropFilter, 'team card should not use backdrop blur').toBe('none');
- expect(teamCardRule, 'team card should not ship glass blur rules').not.toContain('backdrop-filter');
+ expect(alphaFromRgb(glassStyles.backgroundColor), 'team card should keep a translucent glass background').toBeLessThan(1);
+ expect(teamCardRule, 'team card should keep a backdrop blur CSS rule').toContain('backdrop-filter');
+ expect(teamCardRule, 'team card should keep a backdrop blur CSS rule').toContain('blur');
await page.evaluate(() => window.scrollTo(0, 360));
await waitForScrolledNavbarEffect(page);
+ const navbarEffect = await page.locator('.navbar').evaluate((element) => {
+ const style = getComputedStyle(element, '::before');
+ return {
+ opacity: style.opacity,
+ };
+ });
const navbarRule = await findRuleText(page, '.navbar::before');
- expect(navbarRule, 'navbar should not rely on a blur pseudo-layer').toBe('');
- });
- test('team photos keep square framing', async ({ page }) => {
- await page.goto('/');
- await lockViewportOn(page, '#team');
- await waitForVisualReady(page);
-
- const frames = await page.locator('.team__photo').evaluateAll((elements) =>
- elements.map((element) => {
- const frame = element.getBoundingClientRect();
- const image = element.querySelector('img')?.getBoundingClientRect();
-
- return {
- frameWidth: frame.width,
- frameHeight: frame.height,
- imageWidth: image?.width ?? 0,
- imageHeight: image?.height ?? 0,
- };
- }),
- );
-
- expect(frames).toHaveLength(3);
-
- for (const frame of frames) {
- expect(Math.abs(frame.frameWidth - frame.frameHeight), 'team photo frame should stay square').toBeLessThan(2);
- expect(Math.abs(frame.imageWidth - frame.frameWidth), 'team image should fill frame width').toBeLessThan(2);
- expect(Math.abs(frame.imageHeight - frame.frameHeight), 'team image should fill frame height').toBeLessThan(2);
- }
+ expect(Number(navbarEffect.opacity), 'scrolled navbar blur layer should be visible').toBeGreaterThan(0.9);
+ expect(navbarRule, 'scrolled navbar should keep backdrop blur CSS rule').toContain('backdrop-filter');
+ expect(navbarRule, 'scrolled navbar should keep backdrop blur CSS rule').toContain('blur');
});
for (const state of states) {
@@ -189,7 +177,7 @@ test.describe('design baseline', () => {
animations: 'disabled',
caret: 'hide',
fullPage: false,
- maxDiffPixelRatio: 0.025,
+ maxDiffPixelRatio: 0.03,
});
});
}
diff --git a/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-contact.png b/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-contact.png
index 5162ffb..ea62450 100644
Binary files a/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-contact.png and b/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-contact.png differ
diff --git a/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-navbar-scrolled.png b/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-navbar-scrolled.png
index 035b085..deb81b1 100644
Binary files a/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-navbar-scrolled.png and b/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-navbar-scrolled.png differ
diff --git a/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-navbar.png b/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-navbar.png
index c5bf23f..5c65e70 100644
Binary files a/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-navbar.png and b/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-navbar.png differ
diff --git a/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-team-glass-hover.png b/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-team-glass-hover.png
index e971eb8..a04584b 100644
Binary files a/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-team-glass-hover.png and b/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-team-glass-hover.png differ
diff --git a/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-team-glass.png b/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-team-glass.png
index bdb09b7..551d310 100644
Binary files a/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-team-glass.png and b/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-team-glass.png differ
diff --git a/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-team.png b/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-team.png
index 1818f6b..969bbba 100644
Binary files a/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-team.png and b/tests/acceptance/design-baseline.spec.ts-snapshots/desktop-team.png differ
diff --git a/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-contact.png b/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-contact.png
index 00d40e0..00fbc6e 100644
Binary files a/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-contact.png and b/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-contact.png differ
diff --git a/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-navbar-scrolled.png b/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-navbar-scrolled.png
index bc4ef52..2fa9c91 100644
Binary files a/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-navbar-scrolled.png and b/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-navbar-scrolled.png differ
diff --git a/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-navbar.png b/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-navbar.png
index 9f3c770..a179717 100644
Binary files a/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-navbar.png and b/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-navbar.png differ
diff --git a/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-team-glass-hover.png b/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-team-glass-hover.png
index f786080..d2a5386 100644
Binary files a/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-team-glass-hover.png and b/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-team-glass-hover.png differ
diff --git a/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-team-glass.png b/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-team-glass.png
index cfd0eb8..9d779f8 100644
Binary files a/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-team-glass.png and b/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-team-glass.png differ
diff --git a/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-team.png b/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-team.png
index 9600a5e..e7af2f7 100644
Binary files a/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-team.png and b/tests/acceptance/design-baseline.spec.ts-snapshots/mobile-team.png differ