Refine homepage layout: install, Stay updated, Changelog/FAQ, Visual Mode

- Restructure install section into a split primary card with the
  recommended path on the left and other install methods on the right
- Drop the prefix toggle UI; bundle name is now baked into data-bundle
- Balance the Stay updated step as paired Substack iframe + X follow card
  with matching height, border, and treatment
- Pair Changelog and FAQ side-by-side on desktop with a centered divider
  and shared border-top; section nav highlights both when in view
- Tone down the Visual Mode demo by removing side-tab borders, stacked
  card icons, and tiny body text so the overlay reads cleanly
- Refine the Chrome extension callout: drop the pill badge, use an
  editorial eyebrow above the title, enlarge the thumbnail
- Exclude private evals/ directory from version control
This commit is contained in:
Paul Bakaus
2026-04-07 18:37:08 -07:00
parent d63855341a
commit d85efab51f
8 changed files with 824 additions and 519 deletions
+3
View File
@@ -29,3 +29,6 @@ extension/detector/
# User design context
.impeccable.md
# Evals (private, commercial)
evals/
@@ -118,39 +118,32 @@
background: rgba(255,255,255,0.06);
border-radius: 12px;
padding: 20px 16px;
border-left: 4px solid #8b5cf6;
backdrop-filter: blur(8px);
}
.card-icon {
width: 32px;
height: 32px;
border-radius: 8px;
background: linear-gradient(135deg, #8b5cf6, #a855f7);
margin-bottom: 12px;
.card-heading {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
gap: 10px;
margin-bottom: 8px;
}
.card-icon {
font-size: 18px;
line-height: 1;
}
.card h3 {
font-size: 14px;
font-weight: 700;
margin-bottom: 6px;
}
.card p {
font-size: 11px;
color: rgba(255,255,255,0.5);
font-size: 13px;
color: rgba(255,255,255,0.55);
line-height: 1.5;
}
.card:nth-child(2) { border-left-color: #ec4899; }
.card:nth-child(2) .card-icon { background: linear-gradient(135deg, #ec4899, #f472b6); }
.card:nth-child(3) { border-left-color: #06b6d4; }
.card:nth-child(3) .card-icon { background: linear-gradient(135deg, #06b6d4, #22d3ee); }
/* Stats */
.stats {
display: flex;
@@ -195,18 +188,24 @@
<div class="cards">
<div class="card">
<div class="card-icon">&#9889;</div>
<h3>Fast Performance</h3>
<div class="card-heading">
<span class="card-icon">&#9889;</span>
<h3>Fast Performance</h3>
</div>
<p>Blazing fast inference with our optimized pipeline.</p>
</div>
<div class="card">
<div class="card-icon">&#128274;</div>
<h3>Secure & Safe</h3>
<div class="card-heading">
<span class="card-icon">&#128274;</span>
<h3>Secure & Safe</h3>
</div>
<p>Enterprise-grade security with encrypted data.</p>
</div>
<div class="card">
<div class="card-icon">&#128200;</div>
<h3>Analytics</h3>
<div class="card-heading">
<span class="card-icon">&#128200;</span>
<h3>Analytics</h3>
</div>
<p>Powerful insights to help you make better decisions.</p>
</div>
</div>
+4 -19
View File
@@ -169,28 +169,13 @@ function renderPatternsWithTabs(patterns, antipatterns) {
// EVENT HANDLERS
// ============================================
// Sync prefix radio buttons to hidden checkbox + update download button label
document.querySelectorAll('input[name="prefix-choice"]').forEach((radio) => {
radio.addEventListener('change', () => {
const prefixToggle = document.getElementById('prefix-toggle');
if (prefixToggle) prefixToggle.checked = radio.value === 'prefixed';
const btnLabel = document.querySelector('#download-zip-btn span');
if (btnLabel) {
btnLabel.textContent = radio.value === 'prefixed'
? 'Download prefixed zip'
: 'Download universal zip';
}
});
});
// Handle bundle download clicks via event delegation
// Handle bundle download clicks via event delegation.
// Each download button carries the full bundle name in data-bundle (e.g.
// "universal" or "universal-prefixed") so the handler is just a redirect.
document.addEventListener("click", (e) => {
const bundleBtn = e.target.closest("[data-bundle]");
if (bundleBtn) {
const provider = bundleBtn.dataset.bundle;
const prefixToggle = document.getElementById('prefix-toggle');
const usePrefixed = prefixToggle && prefixToggle.checked;
const bundleName = usePrefixed ? `${provider}-prefixed` : provider;
const bundleName = bundleBtn.dataset.bundle;
window.location.href = `/api/download/bundle/${bundleName}`;
}
+342 -187
View File
@@ -2633,12 +2633,14 @@ code {
gap: var(--spacing-md);
text-decoration: none;
color: inherit;
padding: var(--spacing-sm) 0;
border-top: 1px solid var(--color-mist);
}
.detection-callout-image {
display: block;
width: 88px;
height: 88px;
width: 132px;
height: 112px;
flex-shrink: 0;
object-fit: cover;
object-position: top left;
@@ -2657,20 +2659,29 @@ code {
flex-direction: column;
align-items: flex-start;
justify-content: center;
gap: 8px;
gap: 6px;
}
.detection-badge {
.detection-eyebrow {
font-family: var(--font-mono);
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
letter-spacing: 0.08em;
color: var(--color-accent);
}
.detection-callout-title {
font-family: var(--font-display);
font-size: 1.25rem;
font-weight: 600;
color: var(--color-ink);
letter-spacing: -0.01em;
line-height: 1.15;
}
.detection-callout:hover .detection-callout-title {
color: var(--color-accent);
border: 1px solid var(--color-accent);
padding: 3px 10px;
border-radius: 99px;
flex-shrink: 0;
}
.detection-desc {
@@ -2820,12 +2831,118 @@ code {
}
/* Two-path install layout */
.install-paths {
/* Row 1: full-width primary install card with internal 2-column split */
.install-row-primary {
display: grid;
grid-template-columns: 5fr 3fr 3fr;
gap: 0 var(--spacing-xl);
grid-template-columns: 1.1fr 0.9fr;
gap: var(--spacing-xl);
align-items: start;
margin: 0 0 var(--spacing-xl);
background: white;
border-radius: 16px;
padding: var(--spacing-lg);
min-width: 0;
}
.install-primary-main {
display: flex;
flex-direction: column;
min-width: 0;
}
.install-primary-alts {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
min-width: 0;
padding-left: var(--spacing-xl);
border-left: 1px solid var(--color-mist);
}
.install-alts-label {
display: block;
font-family: var(--font-mono);
font-size: 0.625rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.14em;
color: var(--color-ash);
}
.install-alt-sublabel {
font-family: var(--font-body);
font-size: 0.75rem;
font-weight: 400;
color: var(--color-ash);
text-transform: none;
letter-spacing: 0;
margin-left: 4px;
}
/* Compact zip download links — replaces the old big black download button.
Two equal-weight options stacked vertically, each is its own affordance. */
.install-zip-links {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: var(--spacing-xs);
}
.install-zip-link {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: 8px 12px;
font-family: var(--font-body);
font-size: 0.8125rem;
color: var(--color-charcoal);
background: transparent;
border: 1px solid var(--color-mist);
border-radius: 6px;
cursor: pointer;
text-align: left;
transition: border-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.install-zip-link:hover {
border-color: var(--color-accent);
color: var(--color-accent);
}
.install-zip-link svg {
flex-shrink: 0;
color: var(--color-ash);
}
.install-zip-link:hover svg {
color: var(--color-accent);
}
.install-zip-link-name {
font-weight: 600;
color: var(--color-ink);
white-space: nowrap;
}
.install-zip-link:hover .install-zip-link-name {
color: var(--color-accent);
}
.install-zip-link-examples {
display: flex;
flex-wrap: nowrap;
gap: 4px;
margin-left: auto;
}
.install-alt-method .install-zip-link-examples code {
font-family: var(--font-mono);
font-size: 0.6875rem;
background: var(--color-accent-dim);
color: var(--color-accent);
padding: 2px 6px;
border-radius: 3px;
white-space: nowrap;
}
.install-path {
@@ -2840,28 +2957,82 @@ code {
padding: var(--spacing-lg);
}
.install-path-label {
font-family: var(--font-mono);
font-size: 0.625rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.14em;
color: var(--color-accent);
/* Steps 2-4: vertical accordion. Native <details name="install-steps">
gives mutual exclusion (opening one closes the others). Items are
separated by hairlines, not card chrome. */
.install-accordion {
display: flex;
flex-direction: column;
margin: 0 0 var(--spacing-xl);
}
.install-path:not(.install-path-primary) {
padding-top: var(--spacing-lg);
.install-step {
border-top: 1px solid var(--color-mist);
}
.install-path-secondary .install-path-label {
.install-step:last-child {
border-bottom: 1px solid var(--color-mist);
}
.install-step-summary {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-md) var(--spacing-xs);
cursor: pointer;
list-style: none;
transition: background var(--duration-fast) var(--ease-out);
}
.install-step-summary::-webkit-details-marker {
display: none;
}
.install-step-summary:hover {
background: var(--color-cream);
}
.install-step-summary h3 {
flex: 1;
margin: 0;
}
.install-step-arrow {
flex-shrink: 0;
color: var(--color-ash);
transition: transform var(--duration-base) var(--ease-out);
}
.install-step[open] .install-step-arrow {
transform: rotate(90deg);
}
.install-step-body {
padding: 0 var(--spacing-xs) var(--spacing-md);
display: flex;
flex-direction: column;
}
.install-step-body .install-path-desc {
margin-top: 0;
}
.install-step-status {
font-size: 0.8125rem;
color: var(--color-ash);
margin: 0 0 var(--spacing-sm);
font-style: italic;
}
.install-path-title {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
font-family: var(--font-display);
font-size: 1.5rem;
font-weight: 600;
margin: var(--spacing-xs) 0 var(--spacing-sm);
margin: 0 0 var(--spacing-sm);
line-height: 1.2;
}
@@ -2985,6 +3156,66 @@ code {
text-decoration: underline;
}
/* Lightweight terminal block — used in row 2 (CLI) where the heavy
skeuomorphic glass-terminal would be visually too much. Just a thin
bordered code line with a copy button and an optional note below. */
.install-cmd-block {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
margin-bottom: var(--spacing-sm);
}
.install-cmd-line {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: 10px 14px;
background: var(--color-cream);
border: 1px solid var(--color-mist);
border-radius: 6px;
min-width: 0;
}
.install-cmd-line .terminal-prompt {
flex-shrink: 0;
color: var(--color-accent);
font-family: var(--font-mono);
font-size: 0.8125rem;
}
.install-cmd-line code {
flex: 1;
font-family: var(--font-mono);
font-size: 0.8125rem;
color: var(--color-ink);
background: transparent;
padding: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.install-cmd-line .copy-btn {
flex-shrink: 0;
}
.install-cmd-note {
font-size: 0.75rem;
color: var(--color-ash);
line-height: 1.5;
}
.install-cmd-note code {
font-family: var(--font-mono);
font-size: 0.6875rem;
background: var(--color-mist);
padding: 2px 5px;
border-radius: 3px;
color: var(--color-ink);
}
/* Better together callout */
.install-together {
max-width: 960px;
@@ -3021,47 +3252,90 @@ code {
margin: 0;
}
/* Inline step numbers in path labels */
/* Inline step numbers — sit to the left of the title text inside each h3.
The h3 uses flex with align-items: center so the step is centered with
the text without needing vertical-align fiddling. Margin/gap is handled
by the parent flex's gap. */
.install-path-step {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
width: 22px;
height: 22px;
border-radius: 50%;
border: 1.5px solid currentColor;
font-size: 0.5rem;
border: 1.5px solid var(--color-accent);
color: var(--color-accent);
font-family: var(--font-mono);
font-size: 0.625rem;
font-weight: 700;
line-height: 1;
margin-right: 4px;
vertical-align: 1px;
flex-shrink: 0;
}
/* Stay updated (third column) */
.install-updated-subscribe {
display: flex;
flex-direction: column;
display: grid;
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
gap: var(--spacing-sm);
margin-bottom: var(--spacing-md);
align-items: stretch;
}
.install-updated-subscribe iframe {
.install-updated-substack {
display: block;
max-width: 100%;
width: 100%;
min-width: 0;
height: 130px;
border: 1px solid var(--color-mist);
border-radius: 8px;
background: var(--color-paper);
}
.install-updated-x {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.8125rem;
font-weight: 600;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
gap: var(--spacing-xs);
height: 130px;
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--color-mist);
border-radius: 8px;
background: var(--color-paper);
color: var(--color-ink);
text-decoration: none;
transition: color var(--duration-fast) var(--ease-out);
transition: border-color var(--duration-fast) var(--ease-out),
background var(--duration-fast) var(--ease-out),
transform var(--duration-fast) var(--ease-out);
}
.install-updated-x:hover {
border-color: var(--color-ink);
background: var(--color-cream);
}
.install-updated-x-icon {
color: var(--color-ink);
flex-shrink: 0;
}
.install-updated-x-label {
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--color-ash);
font-family: var(--font-mono);
}
.install-updated-x-handle {
font-size: 1rem;
font-weight: 600;
color: var(--color-ink);
letter-spacing: -0.01em;
}
.install-updated-x:hover .install-updated-x-handle {
color: var(--color-accent);
}
@@ -3123,12 +3397,19 @@ a.install-updated-ref:hover {
}
@media (max-width: 900px) {
.install-paths {
.install-row-primary {
grid-template-columns: 1fr;
gap: var(--spacing-xl);
gap: var(--spacing-lg);
max-width: calc(100vw - var(--spacing-md) * 2);
}
.install-primary-alts {
padding-left: 0;
padding-top: var(--spacing-lg);
border-left: none;
border-top: 1px solid var(--color-mist);
}
.install-path-primary {
margin-bottom: var(--spacing-sm);
padding: var(--spacing-md);
@@ -3149,10 +3430,27 @@ a.install-updated-ref:hover {
gap: var(--spacing-xs);
}
.install-updated-subscribe iframe {
.install-updated-subscribe {
grid-template-columns: 1fr;
}
.install-updated-substack {
height: 130px;
}
.install-updated-x {
height: auto;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
}
.install-updated-x-label {
display: none;
}
.install-terminal-cmd code {
font-size: 0.75rem;
white-space: normal;
@@ -3420,142 +3718,7 @@ a.install-updated-ref:hover {
color: var(--color-ink);
}
.install-alt-download {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.install-alt-divider {
height: 1px;
background: var(--color-mist);
}
.install-prefix-options {
display: flex;
align-items: flex-start;
gap: var(--spacing-sm);
margin-top: var(--spacing-md);
}
.install-prefix-option {
flex: 1;
display: flex;
align-items: flex-start;
gap: 8px;
padding: 10px 12px;
border: 1.5px solid var(--color-mist);
border-radius: 8px;
cursor: pointer;
transition: all var(--duration-fast) var(--ease-out);
line-height: 1;
}
.install-prefix-option:hover {
border-color: var(--color-charcoal);
}
.install-prefix-option:has(input:checked) {
border-color: var(--color-accent);
}
.install-prefix-option input {
appearance: none;
-webkit-appearance: none;
width: 16px;
height: 16px;
border: 1.5px solid var(--color-mist);
border-radius: 50%;
flex-shrink: 0;
margin: 0;
transition: all var(--duration-fast) var(--ease-out);
position: relative;
}
.install-prefix-option input:checked {
border-color: var(--color-accent);
background: var(--color-accent);
}
.install-prefix-option input:checked::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 6px;
height: 6px;
background: var(--color-paper);
border-radius: 50%;
}
.install-prefix-option input:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
.install-prefix-option-content {
display: flex;
flex-direction: column;
gap: 3px;
padding-top: 1px;
}
.install-prefix-option-label {
font-size: 0.8125rem;
font-weight: 600;
color: var(--color-ink);
line-height: 1;
}
.install-prefix-option-examples {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.install-prefix-option-examples code {
font-family: var(--font-mono);
font-size: 0.6875rem;
background: var(--color-accent-dim);
color: var(--color-accent);
padding: 2px 6px;
border-radius: 3px;
}
.install-prefix-option-wrap {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
align-self: flex-start;
}
.install-prefix-reason {
font-size: 0.6875rem;
color: var(--color-ash);
line-height: 1.4;
padding-left: 2px;
}
.install-prefix-option-label code {
font-family: var(--font-mono);
font-size: 0.6875rem;
background: var(--color-accent-dim);
color: var(--color-accent);
padding: 1px 5px;
border-radius: 3px;
}
.install-alt-method .install-prefix-options {
margin-top: var(--spacing-sm);
}
.install-alt-method .install-alt-download {
margin-top: var(--spacing-sm);
}
/* Hidden prefix checkbox for download logic */
/* Visually hidden helper (kept for accessibility patterns elsewhere) */
.sr-only {
position: absolute;
width: 1px;
@@ -3569,14 +3732,6 @@ a.install-updated-ref:hover {
}
@media (max-width: 600px) {
.install-prefix-options {
flex-direction: column;
}
.install-alt-method {
padding: var(--spacing-sm) var(--spacing-md);
}
.install-alt-method code {
font-size: 0.75rem;
}
+305 -164
View File
@@ -1005,6 +1005,25 @@
color: var(--color-accent);
border-bottom-color: var(--color-accent);
}
.changelog-faq-row {
position: relative;
}
@media (min-width: 1024px) {
.changelog-faq-row {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
column-gap: calc(var(--spacing-xl) * 1.5);
border-top: 1px solid var(--color-mist);
}
.changelog-faq-row > .changelog-section, .changelog-faq-row > .faq-section {
border-top: none;
}
.changelog-faq-row > .faq-section {
border-left: 1px solid var(--color-mist);
padding-left: calc(var(--spacing-xl) * 0.75);
margin-left: calc(var(--spacing-xl) * -0.75);
}
}
.changelog-section {
position: relative;
padding: var(--spacing-xl) 0;
@@ -4715,11 +4734,13 @@ code {
gap: var(--spacing-md);
text-decoration: none;
color: inherit;
padding: var(--spacing-sm) 0;
border-top: 1px solid var(--color-mist);
}
.detection-callout-image {
display: block;
width: 88px;
height: 88px;
width: 132px;
height: 112px;
flex-shrink: 0;
object-fit: cover;
object-position: top left;
@@ -4736,19 +4757,26 @@ code {
flex-direction: column;
align-items: flex-start;
justify-content: center;
gap: 8px;
gap: 6px;
}
.detection-badge {
.detection-eyebrow {
font-family: var(--font-mono);
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
letter-spacing: 0.08em;
color: var(--color-accent);
}
.detection-callout-title {
font-family: var(--font-display);
font-size: 1.25rem;
font-weight: 600;
color: var(--color-ink);
letter-spacing: -0.01em;
line-height: 1.15;
}
.detection-callout:hover .detection-callout-title {
color: var(--color-accent);
border: 1px solid var(--color-accent);
padding: 3px 10px;
border-radius: 99px;
flex-shrink: 0;
}
.detection-desc {
flex: 1;
@@ -4864,12 +4892,102 @@ code {
.platforms-section .section-subtitle {
max-width: 60ch;
}
.install-paths {
.install-row-primary {
display: grid;
grid-template-columns: 5fr 3fr 3fr;
gap: 0 var(--spacing-xl);
grid-template-columns: 1.1fr 0.9fr;
gap: var(--spacing-xl);
align-items: start;
margin: 0 0 var(--spacing-xl);
background: white;
border-radius: 16px;
padding: var(--spacing-lg);
min-width: 0;
}
.install-primary-main {
display: flex;
flex-direction: column;
min-width: 0;
}
.install-primary-alts {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
min-width: 0;
padding-left: var(--spacing-xl);
border-left: 1px solid var(--color-mist);
}
.install-alts-label {
display: block;
font-family: var(--font-mono);
font-size: 0.625rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.14em;
color: var(--color-ash);
}
.install-alt-sublabel {
font-family: var(--font-body);
font-size: 0.75rem;
font-weight: 400;
color: var(--color-ash);
text-transform: none;
letter-spacing: 0;
margin-left: 4px;
}
.install-zip-links {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: var(--spacing-xs);
}
.install-zip-link {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: 8px 12px;
font-family: var(--font-body);
font-size: 0.8125rem;
color: var(--color-charcoal);
background: transparent;
border: 1px solid var(--color-mist);
border-radius: 6px;
cursor: pointer;
text-align: left;
transition: border-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.install-zip-link:hover {
border-color: var(--color-accent);
color: var(--color-accent);
}
.install-zip-link svg {
flex-shrink: 0;
color: var(--color-ash);
}
.install-zip-link:hover svg {
color: var(--color-accent);
}
.install-zip-link-name {
font-weight: 600;
color: var(--color-ink);
white-space: nowrap;
}
.install-zip-link:hover .install-zip-link-name {
color: var(--color-accent);
}
.install-zip-link-examples {
display: flex;
flex-wrap: nowrap;
gap: 4px;
margin-left: auto;
}
.install-alt-method .install-zip-link-examples code {
font-family: var(--font-mono);
font-size: 0.6875rem;
background: var(--color-accent-dim);
color: var(--color-accent);
padding: 2px 6px;
border-radius: 3px;
white-space: nowrap;
}
.install-path {
display: flex;
@@ -4881,25 +4999,67 @@ code {
border-radius: 16px;
padding: var(--spacing-lg);
}
.install-path-label {
font-family: var(--font-mono);
font-size: 0.625rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.14em;
color: var(--color-accent);
.install-accordion {
display: flex;
flex-direction: column;
margin: 0 0 var(--spacing-xl);
}
.install-path:not(.install-path-primary) {
padding-top: var(--spacing-lg);
.install-step {
border-top: 1px solid var(--color-mist);
}
.install-path-secondary .install-path-label {
.install-step:last-child {
border-bottom: 1px solid var(--color-mist);
}
.install-step-summary {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-md) var(--spacing-xs);
cursor: pointer;
list-style: none;
transition: background var(--duration-fast) var(--ease-out);
}
.install-step-summary::-webkit-details-marker {
display: none;
}
.install-step-summary:hover {
background: var(--color-cream);
}
.install-step-summary h3 {
flex: 1;
margin: 0;
}
.install-step-arrow {
flex-shrink: 0;
color: var(--color-ash);
transition: transform var(--duration-base) var(--ease-out);
}
.install-step[open] .install-step-arrow {
transform: rotate(90deg);
}
.install-step-body {
padding: 0 var(--spacing-xs) var(--spacing-md);
display: flex;
flex-direction: column;
}
.install-step-body .install-path-desc {
margin-top: 0;
}
.install-step-status {
font-size: 0.8125rem;
color: var(--color-ash);
margin: 0 0 var(--spacing-sm);
font-style: italic;
}
.install-path-title {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
font-family: var(--font-display);
font-size: 1.5rem;
font-weight: 600;
margin: var(--spacing-xs) 0 var(--spacing-sm);
margin: 0 0 var(--spacing-sm);
line-height: 1.2;
}
.install-path-badge {
@@ -5000,6 +5160,55 @@ code {
.install-path-link a:hover {
text-decoration: underline;
}
.install-cmd-block {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
margin-bottom: var(--spacing-sm);
}
.install-cmd-line {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: 10px 14px;
background: var(--color-cream);
border: 1px solid var(--color-mist);
border-radius: 6px;
min-width: 0;
}
.install-cmd-line .terminal-prompt {
flex-shrink: 0;
color: var(--color-accent);
font-family: var(--font-mono);
font-size: 0.8125rem;
}
.install-cmd-line code {
flex: 1;
font-family: var(--font-mono);
font-size: 0.8125rem;
color: var(--color-ink);
background: transparent;
padding: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.install-cmd-line .copy-btn {
flex-shrink: 0;
}
.install-cmd-note {
font-size: 0.75rem;
color: var(--color-ash);
line-height: 1.5;
}
.install-cmd-note code {
font-family: var(--font-mono);
font-size: 0.6875rem;
background: var(--color-mist);
padding: 2px 5px;
border-radius: 3px;
color: var(--color-ink);
}
.install-together {
max-width: 960px;
margin: 0 0 var(--spacing-xl);
@@ -5035,37 +5244,71 @@ code {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
width: 22px;
height: 22px;
border-radius: 50%;
border: 1.5px solid currentColor;
font-size: 0.5rem;
border: 1.5px solid var(--color-accent);
color: var(--color-accent);
font-family: var(--font-mono);
font-size: 0.625rem;
font-weight: 700;
line-height: 1;
margin-right: 4px;
vertical-align: 1px;
flex-shrink: 0;
}
.install-updated-subscribe {
display: flex;
flex-direction: column;
display: grid;
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
gap: var(--spacing-sm);
margin-bottom: var(--spacing-md);
align-items: stretch;
}
.install-updated-subscribe iframe {
.install-updated-substack {
display: block;
max-width: 100%;
width: 100%;
min-width: 0;
height: 130px;
border: 1px solid var(--color-mist);
border-radius: 8px;
background: var(--color-paper);
}
.install-updated-x {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.8125rem;
font-weight: 600;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
gap: var(--spacing-xs);
height: 130px;
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--color-mist);
border-radius: 8px;
background: var(--color-paper);
color: var(--color-ink);
text-decoration: none;
transition: color var(--duration-fast) var(--ease-out);
transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
.install-updated-x:hover {
border-color: var(--color-ink);
background: var(--color-cream);
}
.install-updated-x-icon {
color: var(--color-ink);
flex-shrink: 0;
}
.install-updated-x-label {
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--color-ash);
font-family: var(--font-mono);
}
.install-updated-x-handle {
font-size: 1rem;
font-weight: 600;
color: var(--color-ink);
letter-spacing: -0.01em;
}
.install-updated-x:hover .install-updated-x-handle {
color: var(--color-accent);
}
.install-updated-refs {
@@ -5119,11 +5362,17 @@ a.install-updated-ref:hover {
color: var(--color-ink);
}
@media (max-width: 900px) {
.install-paths {
.install-row-primary {
grid-template-columns: 1fr;
gap: var(--spacing-xl);
gap: var(--spacing-lg);
max-width: calc(100vw - var(--spacing-md) * 2);
}
.install-primary-alts {
padding-left: 0;
padding-top: var(--spacing-lg);
border-left: none;
border-top: 1px solid var(--color-mist);
}
.install-path-primary {
margin-bottom: var(--spacing-sm);
padding: var(--spacing-md);
@@ -5140,9 +5389,23 @@ a.install-updated-ref:hover {
align-items: flex-start;
gap: var(--spacing-xs);
}
.install-updated-subscribe iframe {
.install-updated-subscribe {
grid-template-columns: 1fr;
}
.install-updated-substack {
height: 130px;
}
.install-updated-x {
height: auto;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
}
.install-updated-x-label {
display: none;
}
.install-terminal-cmd code {
font-size: 0.75rem;
white-space: normal;
@@ -5371,122 +5634,6 @@ a.install-updated-ref:hover {
border-radius: 3px;
color: var(--color-ink);
}
.install-alt-download {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.install-alt-divider {
height: 1px;
background: var(--color-mist);
}
.install-prefix-options {
display: flex;
align-items: flex-start;
gap: var(--spacing-sm);
margin-top: var(--spacing-md);
}
.install-prefix-option {
flex: 1;
display: flex;
align-items: flex-start;
gap: 8px;
padding: 10px 12px;
border: 1.5px solid var(--color-mist);
border-radius: 8px;
cursor: pointer;
transition: all var(--duration-fast) var(--ease-out);
line-height: 1;
}
.install-prefix-option:hover {
border-color: var(--color-charcoal);
}
.install-prefix-option:has(input:checked) {
border-color: var(--color-accent);
}
.install-prefix-option input {
appearance: none;
-webkit-appearance: none;
width: 16px;
height: 16px;
border: 1.5px solid var(--color-mist);
border-radius: 50%;
flex-shrink: 0;
margin: 0;
transition: all var(--duration-fast) var(--ease-out);
position: relative;
}
.install-prefix-option input:checked {
border-color: var(--color-accent);
background: var(--color-accent);
}
.install-prefix-option input:checked::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 6px;
height: 6px;
background: var(--color-paper);
border-radius: 50%;
}
.install-prefix-option input:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
.install-prefix-option-content {
display: flex;
flex-direction: column;
gap: 3px;
padding-top: 1px;
}
.install-prefix-option-label {
font-size: 0.8125rem;
font-weight: 600;
color: var(--color-ink);
line-height: 1;
}
.install-prefix-option-examples {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.install-prefix-option-examples code {
font-family: var(--font-mono);
font-size: 0.6875rem;
background: var(--color-accent-dim);
color: var(--color-accent);
padding: 2px 6px;
border-radius: 3px;
}
.install-prefix-option-wrap {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
align-self: flex-start;
}
.install-prefix-reason {
font-size: 0.6875rem;
color: var(--color-ash);
line-height: 1.4;
padding-left: 2px;
}
.install-prefix-option-label code {
font-family: var(--font-mono);
font-size: 0.6875rem;
background: var(--color-accent-dim);
color: var(--color-accent);
padding: 1px 5px;
border-radius: 3px;
}
.install-alt-method .install-prefix-options {
margin-top: var(--spacing-sm);
}
.install-alt-method .install-alt-download {
margin-top: var(--spacing-sm);
}
.sr-only {
position: absolute;
width: 1px;
@@ -5499,12 +5646,6 @@ a.install-updated-ref:hover {
border: 0;
}
@media (max-width: 600px) {
.install-prefix-options {
flex-direction: column;
}
.install-alt-method {
padding: var(--spacing-sm) var(--spacing-md);
}
.install-alt-method code {
font-size: 0.75rem;
}
+28
View File
@@ -1068,6 +1068,34 @@
border-bottom-color: var(--color-accent);
}
/* ============================================
CHANGELOG + FAQ ROW (side-by-side on desktop)
============================================ */
.changelog-faq-row {
position: relative;
}
@media (min-width: 1024px) {
.changelog-faq-row {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
column-gap: calc(var(--spacing-xl) * 1.5);
border-top: 1px solid var(--color-mist);
}
.changelog-faq-row > .changelog-section,
.changelog-faq-row > .faq-section {
border-top: none;
}
.changelog-faq-row > .faq-section {
border-left: 1px solid var(--color-mist);
padding-left: calc(var(--spacing-xl) * 0.75);
margin-left: calc(var(--spacing-xl) * -0.75);
}
}
/* ============================================
CHANGELOG SECTION
============================================ */
+105 -125
View File
@@ -289,11 +289,12 @@
<span class="visual-mode-feature-label">Three ways to use it</span>
<p>The <strong>Chrome extension</strong> on any site, embedded in <code>/critique</code> during an AI design review, or standalone via <code>npx impeccable live</code>.</p>
</div>
<a href="https://chromewebstore.google.com/" class="detection-callout" target="_blank" rel="noopener">
<a href="https://impeccablestyle.substack.com" class="detection-callout" target="_blank" rel="noopener">
<img src="assets/extension-detection.png" alt="Impeccable Chrome extension panel listing detected anti-patterns" class="detection-callout-image" loading="lazy" width="600" height="508">
<div class="detection-callout-inner">
<span class="detection-badge">New!</span>
<span class="detection-cmd">Get the Chrome extension &rarr;</span>
<span class="detection-eyebrow">Coming soon</span>
<span class="detection-callout-title">Chrome extension</span>
<span class="detection-cmd">Get notified &rarr;</span>
</div>
</a>
</div>
@@ -308,11 +309,10 @@
<h2 class="section-title">Get Started</h2>
</div>
<div class="install-paths" data-reveal>
<!-- Skills path (primary) -->
<div class="install-path install-path-primary">
<span class="install-path-label"><span class="install-path-step">1</span></span>
<h3 class="install-path-title">Install the skills <span class="install-path-badge">Recommended</span></h3>
<!-- Row 1: Primary install (full width, white card with internal 2-col split) -->
<div class="install-row-primary" data-reveal>
<div class="install-primary-main">
<h3 class="install-path-title"><span class="install-path-step">1</span>Install the skills <span class="install-path-badge">Recommended</span></h3>
<p class="install-path-desc">21 commands that steer your AI toward better design, in real time. The full Impeccable experience.</p>
<div class="install-path-terminal">
@@ -343,135 +343,114 @@
<div class="install-path-next">
Then run <code><span class="install-path-slash">/</span>impeccable teach</code> to set up your project's design context.
</div>
<!-- Alternatives (collapsible) -->
<details class="install-alternatives">
<summary class="install-alternatives-toggle">Other install methods</summary>
<div class="install-alternatives-content">
<div class="install-alt-method">
<span class="install-alt-label">Claude Code plugin</span>
<div class="install-terminal-cmd">
<span class="terminal-prompt">$</span>
<code>/plugin marketplace add pbakaus/impeccable</code>
<button class="copy-btn" aria-label="Copy command" data-copy="/plugin marketplace add pbakaus/impeccable">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
</svg>
</button>
</div>
<span class="install-alt-note">Then open <code>/plugin</code> to install from Discover tab</span>
</div>
<div class="install-alt-divider"></div>
<div class="install-alt-method">
<span class="install-alt-label">Manual download</span>
<span class="install-alt-note">Contains all provider directories. Extract to your project root.</span>
<div class="install-prefix-options">
<label class="install-prefix-option" data-prefix="default">
<input type="radio" name="prefix-choice" value="default" checked />
<div class="install-prefix-option-content">
<span class="install-prefix-option-label">Default names</span>
<span class="install-prefix-option-examples"><code>/polish</code> <code>/audit</code> <code>/typeset</code></span>
</div>
</label>
<div class="install-prefix-option-wrap">
<label class="install-prefix-option" data-prefix="prefixed">
<input type="radio" name="prefix-choice" value="prefixed" />
<div class="install-prefix-option-content">
<span class="install-prefix-option-label">Prefixed with <code>/i-</code></span>
<span class="install-prefix-option-examples"><code>/i-polish</code> <code>/i-audit</code> <code>/i-typeset</code></span>
</div>
</label>
<span class="install-prefix-reason">Avoids conflicts with built-in commands or other skill packs</span>
</div>
</div>
<div class="install-alt-download">
<button class="btn btn-primary" data-bundle="universal" aria-label="Download universal zip" id="download-zip-btn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/>
</svg>
<span>Download universal zip</span>
</button>
</div>
<input type="checkbox" id="prefix-toggle" class="sr-only" />
</div>
</div>
</details>
</div>
<!-- CLI path (secondary) -->
<div class="install-path install-path-secondary">
<span class="install-path-label"><span class="install-path-step">2</span></span>
<h3 class="install-path-title">Add the CLI <span class="install-path-badge install-path-badge-muted">Beta</span></h3>
<p class="install-path-desc">Deterministic anti-pattern detection, cheatsheet and more. Use in CI and beyond.</p>
<!-- Other install methods -->
<div class="install-primary-alts">
<span class="install-alts-label">Other install methods</span>
<div class="install-path-terminal">
<div class="glass-terminal">
<div class="terminal-header">
<span class="terminal-dot red"></span>
<span class="terminal-dot yellow"></span>
<span class="terminal-dot green"></span>
</div>
<div class="terminal-body">
<div class="install-terminal-row">
<div class="install-terminal-cmd">
<span class="terminal-prompt">$</span>
<code>npm i -g impeccable</code>
<button class="copy-btn" aria-label="Copy command" data-copy="npm i -g impeccable">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
</svg>
</button>
</div>
<span class="install-terminal-note">Or run directly: <code>npx impeccable ...</code></span>
</div>
</div>
</div>
</div>
<div class="install-path-subcommands">
<span class="install-path-subcommand"><code>detect</code> Scan files, dirs, or URLs</span>
<span class="install-path-subcommand"><code>skills install</code> Install skills into your project</span>
<span class="install-path-subcommand"><code>skills update</code> Update to latest version</span>
</div>
<p class="install-path-link"><a href="https://www.npmjs.com/package/impeccable">View on npm</a></p>
</div>
<!-- Stay sharp (third column) -->
<div class="install-path install-updated">
<span class="install-path-label"><span class="install-path-step">3</span></span>
<h3 class="install-path-title">Stay updated</h3>
<div class="install-updated-subscribe">
<iframe src="https://impeccablestyle.substack.com/embed" width="100%" height="150" style="border: 1px solid #e8e4df; border-radius: 8px; background: white;" frameborder="0" scrolling="no"></iframe>
<a href="https://x.com/impeccable_ai" class="install-updated-x" target="_blank" rel="noopener">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
<span>Follow @impeccable_ai</span>
</a>
</div>
<div class="install-updated-refs">
<span class="install-updated-refs-label">Quick reference</span>
<a href="/cheatsheet" class="install-updated-ref">Command cheatsheet &rarr;</a>
<div class="install-updated-ref install-updated-ref-terminal">
<div class="install-alt-method">
<span class="install-alt-label">Claude Code plugin</span>
<div class="install-terminal-cmd">
<span class="terminal-prompt">$</span>
<code>impeccable skills help</code>
<code>/plugin marketplace add pbakaus/impeccable</code>
<button class="copy-btn" aria-label="Copy command" data-copy="/plugin marketplace add pbakaus/impeccable">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
</svg>
</button>
</div>
<span class="install-alt-note">Then open <code>/plugin</code> in Claude Code</span>
</div>
<div class="install-alt-method">
<span class="install-alt-label">Manual download <span class="install-alt-sublabel">all 11 providers</span></span>
<div class="install-zip-links">
<button class="install-zip-link" data-bundle="universal" aria-label="Download universal zip with default command names">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/>
</svg>
<span class="install-zip-link-name">Default zip</span>
<span class="install-zip-link-examples"><code>/polish</code> <code>/audit</code></span>
</button>
<button class="install-zip-link" data-bundle="universal-prefixed" aria-label="Download universal zip with prefixed command names">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/>
</svg>
<span class="install-zip-link-name">Prefixed zip</span>
<span class="install-zip-link-examples"><code>/i-polish</code> <code>/i-audit</code></span>
</button>
</div>
</div>
</div>
</div>
<!-- Better together callout -->
<div class="install-together" data-reveal>
<div class="install-together-inner">
<span class="install-together-badge">Best together</span>
<p class="install-together-text">Skills give your AI the full design toolkit. The CLI adds automated quality gates for reviews and CI.</p>
</div>
<!-- Steps 2-4: vertical accordion, mutually exclusive (only one open at a time) -->
<div class="install-accordion" data-reveal>
<!-- Step 2: CLI -->
<details name="install-steps" class="install-step">
<summary class="install-step-summary">
<h3 class="install-path-title"><span class="install-path-step">2</span>Add the CLI <span class="install-path-badge install-path-badge-muted">Beta</span></h3>
<svg class="install-step-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg>
</summary>
<div class="install-step-body">
<p class="install-path-desc">Anti-pattern detection from the terminal. Scans HTML, CSS, JSX/TSX, Vue, and Svelte &mdash; perfect for CI, pre-commit hooks, and one-off audits.</p>
<div class="install-cmd-block">
<div class="install-cmd-line">
<span class="terminal-prompt">$</span>
<code>npm i -g impeccable</code>
<button class="copy-btn" aria-label="Copy command" data-copy="npm i -g impeccable">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
</svg>
</button>
</div>
<span class="install-cmd-note">Or use <code>npx impeccable</code> directly without installing.</span>
</div>
<p class="install-path-link"><a href="https://www.npmjs.com/package/impeccable" target="_blank" rel="noopener">Full command reference on npm &rarr;</a></p>
</div>
</details>
<!-- Step 3: Browser extension -->
<details name="install-steps" class="install-step">
<summary class="install-step-summary">
<h3 class="install-path-title"><span class="install-path-step">3</span>Browser extension <span class="install-path-badge install-path-badge-muted">Coming soon</span></h3>
<svg class="install-step-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg>
</summary>
<div class="install-step-body">
<p class="install-path-desc">One-click anti-pattern detection on any live page &mdash; yours, staging, production, or anyone else's. Same detection engine as <code>/critique</code>, in your browser.</p>
<p class="install-step-status">Currently in Chrome Web Store review.</p>
<p class="install-path-link"><a href="https://impeccablestyle.substack.com" target="_blank" rel="noopener">Get notified when it lands &rarr;</a></p>
</div>
</details>
<!-- Step 4: Stay updated -->
<details name="install-steps" class="install-step">
<summary class="install-step-summary">
<h3 class="install-path-title"><span class="install-path-step">4</span>Stay updated</h3>
<svg class="install-step-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg>
</summary>
<div class="install-step-body">
<p class="install-path-desc">Roughly monthly notes on new commands, anti-patterns, and the design thinking behind Impeccable.</p>
<div class="install-updated-subscribe">
<iframe class="install-updated-substack" src="https://impeccablestyle.substack.com/embed" width="100%" height="130" frameborder="0" scrolling="no"></iframe>
<a href="https://x.com/impeccable_ai" class="install-updated-x" target="_blank" rel="noopener">
<svg class="install-updated-x-icon" width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
<span class="install-updated-x-label">Follow on X</span>
<span class="install-updated-x-handle">@impeccable_ai</span>
</a>
</div>
</div>
</details>
</div>
</section>
<!-- 5+6. CHANGELOG + FAQ (side-by-side on desktop) -->
<div class="changelog-faq-row">
<!-- 5. CHANGELOG -->
<section class="changelog-section" id="changelog">
<div class="section-header" data-reveal>
@@ -654,6 +633,7 @@
</details>
</div>
</section>
</div><!-- /.changelog-faq-row -->
<!-- PARTNERSHIPS -->
<section class="consulting-section" id="consulting">
+15 -1
View File
@@ -42,9 +42,23 @@ export function initSectionNav() {
}
}
// If the current section shares its top row with siblings (e.g. side-by-side
// changelog + FAQ on desktop), treat all of them as active.
const activeSections = new Set();
if (currentSection) {
const currentEl = document.getElementById(currentSection);
const currentTop = currentEl?.offsetTop ?? 0;
sectionIds.forEach(id => {
const el = document.getElementById(id);
if (el && Math.abs(el.offsetTop - currentTop) < 4) {
activeSections.add(id);
}
});
}
// Update active state
items.forEach(item => {
if (item.dataset.section === currentSection) {
if (activeSections.has(item.dataset.section)) {
item.classList.add('is-active');
} else {
item.classList.remove('is-active');