diff --git a/.gitignore b/.gitignore
index 6b78d50..76c9f06 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
spectra-lab-local.png
spectra-lab-redesign.png
+spectra-lab-polish.png
.DS_Store
diff --git a/app.js b/app.js
index 9520e09..c0da016 100644
--- a/app.js
+++ b/app.js
@@ -4,12 +4,41 @@ const workItems = document.querySelectorAll('.work-item');
const workIndex = document.getElementById('workIndex');
const workTitle = document.getElementById('workTitle');
const workCopy = document.getElementById('workCopy');
+const stageCard = document.querySelector('.stage-card');
const revealBlocks = document.querySelectorAll('.reveal');
+const storedMode = window.localStorage.getItem('spectra-mode');
+
+if (storedMode === 'light') {
+ document.body.classList.add('light-mode');
+}
function applyMode() {
const isLight = document.body.classList.contains('light-mode');
modeToggle.textContent = isLight ? 'Dark Mode' : 'Light Mode';
modeLabel.textContent = isLight ? 'Editorial Light' : 'Editorial Dark';
+ window.localStorage.setItem('spectra-mode', isLight ? 'light' : 'dark');
+}
+
+function updateWorkStage(item) {
+ workItems.forEach((button) => {
+ button.classList.remove('is-active');
+ button.setAttribute('aria-selected', 'false');
+ });
+
+ item.classList.add('is-active');
+ item.setAttribute('aria-selected', 'true');
+
+ stageCard?.classList.remove('is-refreshing');
+ requestAnimationFrame(() => {
+ stageCard?.classList.add('is-refreshing');
+ window.setTimeout(() => {
+ stageCard?.classList.remove('is-refreshing');
+ }, 340);
+ });
+
+ workIndex.textContent = item.dataset.index || '';
+ workTitle.textContent = item.dataset.title || '';
+ workCopy.textContent = item.dataset.copy || '';
}
modeToggle?.addEventListener('click', () => {
@@ -19,11 +48,7 @@ modeToggle?.addEventListener('click', () => {
workItems.forEach((item) => {
item.addEventListener('click', () => {
- workItems.forEach((button) => button.classList.remove('is-active'));
- item.classList.add('is-active');
- workIndex.textContent = item.dataset.index || '';
- workTitle.textContent = item.dataset.title || '';
- workCopy.textContent = item.dataset.copy || '';
+ updateWorkStage(item);
});
});
@@ -40,4 +65,7 @@ const observer = new IntersectionObserver(
);
revealBlocks.forEach((block) => observer.observe(block));
+if (workItems[0]) {
+ updateWorkStage(workItems[0]);
+}
applyMode();
diff --git a/index.html b/index.html
index b406f4d..bd0bfbb 100644
--- a/index.html
+++ b/index.html
@@ -61,6 +61,20 @@
+
+
+ Design principle
+ Less gesture, more judgment.
+
+
+ Material cue
+ 纸感、墨感、少量暗红,而不是数字炫光。
+
+
+ Reading rhythm
+ 先标题,再说明,再留下停顿。
+
+
@@ -187,6 +201,10 @@
如果第一页就把所有招式用完,观者只会记得“效果很多”。但如果页面知道何时收、何时留白、何时只用一条线和一块色面说话,它才更像真正完成度高的作品。
+
+ Spectra Lab / Editorial pass
+ Built for calm impact.
+
diff --git a/styles.css b/styles.css
index 6ce3b7e..bdf144f 100644
--- a/styles.css
+++ b/styles.css
@@ -86,11 +86,16 @@ a {
}
.topbar {
+ position: sticky;
+ top: 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
- padding: 28px 0 20px;
+ padding: 22px 0 18px;
+ background: linear-gradient(180deg, color-mix(in srgb, var(--bg) 82%, transparent 18%), color-mix(in srgb, var(--bg) 58%, transparent 42%));
+ backdrop-filter: blur(14px);
+ z-index: 8;
}
.brand {
@@ -141,10 +146,47 @@ a {
color: var(--text);
}
+.mode-toggle:hover {
+ border-color: var(--line-strong);
+ background: var(--accent-soft);
+}
+
.hero {
padding: 42px 0 72px;
}
+.hero-ledger {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 16px;
+ margin-top: 30px;
+ padding-top: 18px;
+ border-top: 1px solid var(--line);
+}
+
+.ledger-item {
+ padding: 18px 18px 20px;
+ border: 1px solid var(--line);
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01));
+}
+
+.ledger-item span,
+.closing-signoff span {
+ display: block;
+ text-transform: uppercase;
+ letter-spacing: 0.16em;
+ font-size: 0.72rem;
+ color: var(--muted);
+}
+
+.ledger-item strong {
+ display: block;
+ margin-top: 14px;
+ font-size: 1rem;
+ line-height: 1.6;
+ font-weight: 600;
+}
+
.hero-meta,
.section-head,
.footer {
@@ -274,6 +316,11 @@ a {
background: var(--accent-soft);
}
+.work-item.is-active .work-number,
+.work-item.is-active small {
+ color: var(--text);
+}
+
.work-number {
font-size: 0.8rem;
color: var(--muted);
@@ -299,17 +346,32 @@ a {
}
.stage-card {
+ position: relative;
min-height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 34px;
+ overflow: hidden;
background:
linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.06) 100%),
linear-gradient(135deg, rgba(255,255,255,0.02), transparent 48%),
linear-gradient(180deg, var(--panel), color-mix(in srgb, var(--panel) 80%, black 20%));
}
+.stage-card::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ background: linear-gradient(180deg, transparent 0%, rgba(255, 255, 255, 0.04) 100%);
+ opacity: 0.5;
+}
+
+.stage-card.is-refreshing {
+ animation: stageRefresh 320ms ease;
+}
+
.stage-index {
font-size: clamp(4.2rem, 8vw, 8rem);
line-height: 0.92;
@@ -354,6 +416,15 @@ a {
color: rgba(20, 20, 20, 0.7);
}
+.closing-signoff {
+ display: flex;
+ justify-content: space-between;
+ gap: 16px;
+ margin-top: 30px;
+ padding-top: 18px;
+ border-top: 1px solid rgba(20, 20, 20, 0.12);
+}
+
.footer {
padding: 22px 0 42px;
border-top: 1px solid var(--line);
@@ -370,12 +441,24 @@ a {
transform: translateY(0);
}
+@keyframes stageRefresh {
+ 0% {
+ transform: translateY(8px);
+ opacity: 0.86;
+ }
+ 100% {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
@media (max-width: 1100px) {
.topbar,
.hero-meta,
.section-head,
.works-layout,
.hero-grid,
+ .hero-ledger,
.statement-grid,
.system-grid,
.footer {
@@ -435,4 +518,8 @@ a {
.closing-panel {
padding: 24px;
}
+
+ .closing-signoff {
+ flex-direction: column;
+ }
}