Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,15 @@ Known divergence is limited to content density: the visit page can use a shorter

### Visit Landing Hero
- **Structure**: compact brand link, left-aligned value proposition, direct in-page CTAs, and a small proof/pass panel.
- **Layout**: page-grid composition with a dominant form and supporting notes rail on desktop; one-column source order on mobile. The hero keeps its two CTAs in one horizontal row and compresses the Cafe pass into a horizontal flow so the guestbook card is visible before scrolling on mobile.
- **Layout**: page-grid composition with a dominant form and supporting notes rail on desktop; one-column source order on mobile. The hero keeps its two CTAs in one horizontal row and renders the Cafe pass as a single-line, transform-only marquee so the steps never wrap into a taller block on mobile or desktop.
- **States**: CTAs use the primary/secondary button states and visible focus rings.
- **Accessibility**: hero actions link to real page sections, and the source order stays brand, promise, action, form, notes.

### Sponsor Brand Reel
- **Structure**: fixed bottom `aside`, centered divider-style label, reel viewport, and repeated brand asset cards.
- **Desktop layout**: large horizontal brand cards inside the central page track with a slow continuous reel so sponsor assets read as real placements.
- **Mobile layout**: each brand asset owns one full viewport width and advances every 2 seconds.
- **Logo treatment**: real sponsor image assets fill compact glass cards; light-background logo files may receive a small image radius while the rail height stays fixed.
- **Logo treatment**: real sponsor image assets fill compact glass cards; light-background logo files sit on a matching bright porcelain card and shelf while the rail height stays fixed.
- **Overlap safety**: page and anchor scrolling reserve `--sponsor-rail-height` plus safe-area inset so the fixed footer never hides the guestbook CTA or note content.
- **Motion**: transform-only animation, pauses on hover/focus, and stops under `prefers-reduced-motion`.

Expand Down
11 changes: 9 additions & 2 deletions tests/visit-page.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,14 @@ assert.match(html, /class=["']hero-actions["'][\s\S]*Sign guestbook[\s\S]*Browse
assert.match(css, /\.hero-actions\b[\s\S]*flex-wrap:\s*nowrap/, 'visit hero CTAs should stay in one horizontal row');
assert.match(css, /\.hero-button\b[\s\S]*flex:\s*1 1 0/, 'visit hero CTAs should share a single row on mobile');
assert.doesNotMatch(css, /@media\s+\(max-width:\s*640px\)[\s\S]*\.hero-actions\s*\{[^}]*display:\s*grid/, 'mobile hero CTAs should not stack into a vertical grid');
assert.match(css, /\.hero-pass\b[\s\S]*display:\s*flex[\s\S]*overflow-x:\s*auto/, 'hero pass should become a horizontal flow to reduce height');
assert.match(css, /\.hero-pass ol\b[\s\S]*display:\s*flex/, 'hero pass steps should flow horizontally');
assert.match(html, /<li aria-hidden=["']true["']><span>1<\/span><strong>Write a note<\/strong><\/li>/, 'hero pass should duplicate its steps for a seamless visual marquee without repeating them to assistive tech');
assert.match(css, /\.hero-pass\b[\s\S]*display:\s*grid[\s\S]*overflow:\s*hidden/, 'hero pass should keep the moving track clipped inside one row');
assert.match(html, /class=["']pass-marquee["'][\s\S]*<ol>/, 'hero pass should wrap the moving list in a clipped marquee viewport');
assert.match(css, /\.pass-marquee\s*\{[^}]*overflow:\s*hidden/, 'hero pass marquee viewport should clip the animated list away from the label');
assert.match(css, /\.hero-pass ol\s*\{[^}]*display:\s*flex[^}]*flex-wrap:\s*nowrap[^}]*animation:\s*pass-marquee/, 'hero pass steps should move as a single-line marquee');
assert.match(css, /@keyframes\s+pass-marquee[\s\S]*translateX\(-50%\)/, 'hero pass marquee should loop over the duplicated track');
assert.match(css, /\.hero-pass li:not\(:last-child\)::after[\s\S]*linear-gradient/, 'hero pass flow should visually connect steps across the row');
assert.doesNotMatch(css, /\.hero-pass ol\s*\{[^}]*flex-wrap:\s*wrap/, 'hero pass track should not wrap into multiple rows');
Comment on lines +93 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Update the test assertion to match the updated CSS rule (.hero-pass li::after instead of .hero-pass li:not(:last-child)::after) to support the seamless marquee loop fix.

assert.match(css, /@keyframes\s+pass-marquee[\s\S]*translateX\(-50%\)/, 'hero pass marquee should loop over the duplicated track');
assert.match(css, /\.hero-pass li::after[\s\S]*linear-gradient/, 'hero pass flow should visually connect steps across the row');
assert.doesNotMatch(css, /\.hero-pass ol\s*\{[^}]*flex-wrap:\s*wrap/, 'hero pass track should not wrap into multiple rows');

assert.match(html, /class=["'][^"']*guestbook-card[^"']*glass-panel[^"']*["'][^>]+id=["']formScreen["']/, 'visit form should reuse the shared glass-panel primitive');
assert.match(html, /class=["'][^"']*guestbook-card[^"']*glass-panel[^"']*["'][^>]+id=["']successScreen["']/, 'visit success state should reuse the shared glass-panel primitive');
assert.match(html, /class=["'][^"']*entries-card[^"']*glass-panel[^"']*["']/, 'visit guestbook wall should reuse the shared glass-panel primitive');
Expand Down Expand Up @@ -230,6 +235,8 @@ for (const sponsor of visitModule.SPONSORS) {
assert.match(js, /id:\s*['"]dalpha['"][\s\S]*logo:\s*['"]assets\/logos\/dalpha-logo\.png['"]/, 'Dalpha should use the supplied footer logo image');
assert.match(js, /id:\s*['"]minds['"][\s\S]*logo:\s*['"]assets\/logos\/minds-logo\.png['"]/, 'Minds should use the supplied footer logo image');
assert.match(css, /\.sponsor-card\[data-sponsor=["']arize["']\] img[\s\S]*border-radius:/, 'Arize logo image should receive a rounded image edge');
assert.match(css, /\.sponsor-carousel\b[\s\S]*rgba\(255, 255, 255, 0\.13\)/, 'sponsor footer shelf should pick up the bright logo backgrounds');
assert.match(css, /\.sponsor-card\[data-sponsor=["']arize["']\],[\s\S]*\.sponsor-card\[data-sponsor=["']minds["']\]\s*\{[\s\S]*rgba\(255, 255, 255, 0\.92\)/, 'Arize and Minds should sit on matching bright sponsor cards');
assert.match(css, /\.sponsor-card\.has-logo\b/, 'sponsor logo cards should expose a logo-specific style hook');
assert.match(css, /sponsor-mobile-cycle[\s\S]*8s[\s\S]*100vw/, 'mobile sponsor carousel should rotate one full-viewport brand asset every two seconds');
assert.match(css, /\.sponsor-carousel:hover \.sponsor-track|\.sponsor-carousel:focus-within \.sponsor-track/, 'sponsor carousel should pause on hover or focus');
Expand Down
96 changes: 83 additions & 13 deletions visit.css
Original file line number Diff line number Diff line change
Expand Up @@ -372,53 +372,76 @@ h2 {
}

.hero-pass {
display: flex;
position: relative;
display: grid;
align-items: center;
gap: 0.75rem;
grid-template-columns: auto minmax(0, 1fr);
gap: 0.82rem;
width: min(100%, 52rem);
min-width: 0;
overflow-x: auto;
min-inline-size: 0;
overflow: hidden;
padding: 0.78rem 0.85rem;
border: 1px solid rgba(152, 225, 255, 0.2);
border-radius: var(--radius-lg);
background: rgba(9, 10, 30, 0.36);
box-shadow: inset 0 0 24px rgba(152, 225, 255, 0.05);
scrollbar-width: none;
}

.hero-pass::-webkit-scrollbar {
display: none;
.hero-pass::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 3rem;
pointer-events: none;
content: "";
background: linear-gradient(90deg, transparent, rgba(9, 10, 30, 0.88));
}

.pass-label {
flex: 0 0 auto;
color: var(--accent-secondary);
font-family: "Space Grotesk", system-ui, sans-serif;
font-size: 0.78rem;
font-weight: 800;
line-height: 1;
white-space: nowrap;
}

.pass-marquee {
min-inline-size: 0;
overflow: hidden;
}

.hero-pass ol {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: 0.5rem;
width: max-content;
min-width: max-content;
margin: 0;
padding: 0;
animation: pass-marquee 22s linear infinite;
list-style: none;
will-change: transform;
}

.hero-pass:hover ol {
animation-play-state: paused;
}

.hero-pass li {
position: relative;
display: inline-flex;
gap: 0.45rem;
flex: 0 0 auto;
align-items: center;
gap: 0.42rem;
color: var(--text-secondary);
white-space: nowrap;
}

.hero-pass li:not(:last-child)::after {
width: 1.35rem;
width: 1.45rem;
height: 1px;
margin-left: 0.15rem;
content: "";
Comment on lines 415 to 447

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Visual Jump in Marquee Loop

There is a subtle but noticeable visual jump/jitter when the marquee animation loops (resets from translateX(-50%) to translateX(0)). This is caused by a width mismatch between the first half (original steps) and the second half (duplicated steps) of the marquee list:

  1. Connector Mismatch: Using .hero-pass li:not(:last-child)::after means the 6th item (last child of the ol) does not get a connector, but the 3rd item (last item of the first half) does. This makes the first half wider by exactly one connector width (1.45rem plus margins).
  2. Gap Mismatch: Using gap: 0.5rem on the flex container (ol) means the first half contains 3 gaps (between 1-2, 2-3, and 3-4), while the second half only contains 2 gaps (between 4-5 and 5-6). This introduces another 0.5 * gap (0.25rem) mismatch.

Combined, this causes a visual jump of nearly 1rem when the animation loops.

Solution

  • Remove gap: 0.5rem from .hero-pass ol and instead use margin-right: 0.5rem on .hero-pass li to ensure uniform spacing.
  • Change .hero-pass li:not(:last-child)::after to .hero-pass li::after so that every item has a connector. The trailing connector on the last item will be cleanly clipped by the container's overflow: hidden and ensures a mathematically perfect, seamless loop.
.hero-pass ol {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  width: max-content;
  min-width: max-content;
  margin: 0;
  padding: 0;
  animation: pass-marquee 22s linear infinite;
  list-style: none;
  will-change: transform;
}

.hero-pass:hover ol {
  animation-play-state: paused;
}

.hero-pass li {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 0.42rem;
  margin-right: 0.5rem;
  color: var(--text-secondary);
  white-space: nowrap;
}

.hero-pass li::after {
  width: 1.45rem;
  height: 1px;
  margin-left: 0.15rem;
  content: "";

Expand All @@ -442,6 +465,7 @@ h2 {
.hero-pass li strong {
color: var(--text-primary);
font-size: 0.82rem;
line-height: 1.22;
white-space: nowrap;
}

Expand Down Expand Up @@ -1054,8 +1078,16 @@ textarea:focus {
}

.sponsor-carousel {
scrollbar-width: none;
min-width: 0;
border: 1px solid rgba(255, 255, 255, 0.13);
border-radius: var(--radius-md);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.13), rgba(232, 238, 255, 0.06)),
rgba(8, 8, 29, 0.18);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.12),
inset 0 -18px 32px rgba(8, 8, 29, 0.14);
scrollbar-width: none;
}

.sponsor-carousel::-webkit-scrollbar {
Expand Down Expand Up @@ -1128,11 +1160,21 @@ textarea:focus {
border-radius: calc(var(--radius-sm) - 0.12rem);
}

.sponsor-card[data-sponsor="arize"],
.sponsor-card[data-sponsor="minds"] {
padding-block: 0.28rem;
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(231, 238, 255, 0.82)),
rgba(255, 255, 255, 0.86);
linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(235, 240, 255, 0.88)),
rgba(255, 255, 255, 0.92);
border-color: rgba(255, 255, 255, 0.42);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.78),
inset 0 -16px 28px rgba(55, 67, 113, 0.08),
0 10px 24px rgba(0, 0, 0, 0.18);
}

.sponsor-card[data-sponsor="arize"] img {
height: 2.8rem;
}

.sponsor-card[data-sponsor="minds"] img {
Expand All @@ -1158,6 +1200,16 @@ textarea:focus {
}
}

@keyframes pass-marquee {
from {
transform: translateX(0);
}

to {
transform: translateX(-50%);
}
}

@keyframes sponsor-mobile-cycle {
0%,
20% {
Expand Down Expand Up @@ -1195,9 +1247,14 @@ textarea:focus {
scroll-behavior: auto;
}

.hero-pass ol,
.sponsor-track {
animation: none;
}

.hero-pass ol {
transform: none;
}
}

@media (max-width: 860px) {
Expand Down Expand Up @@ -1262,10 +1319,19 @@ textarea:focus {
}

.hero-pass {
grid-template-columns: auto minmax(0, 1fr);
width: 100%;
padding: 0.56rem 0.65rem;
}

.hero-pass ol {
animation-duration: 18s;
}

.hero-pass li strong {
font-size: 0.76rem;
}

.guestbook-card,
.entries-card {
padding: 0.72rem;
Expand Down Expand Up @@ -1377,6 +1443,10 @@ textarea:focus {
height: 2.35rem;
}

.sponsor-card[data-sponsor="arize"] img {
height: 2.72rem;
}

.sponsor-card[data-sponsor="minds"] img {
height: 2.95rem;
}
Expand Down
15 changes: 10 additions & 5 deletions visit.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,16 @@ <h1>Meet the cafe crowd.</h1>

<aside class="hero-pass" aria-label="How the guestbook works">
<p class="pass-label">Cafe pass</p>
<ol>
<li><span>1</span><strong>Write a note</strong></li>
<li><span>2</span><strong>Show the unlocked screen</strong></li>
<li><span>3</span><strong>Start a hallway conversation</strong></li>
</ol>
<div class="pass-marquee">
<ol>
<li><span>1</span><strong>Write a note</strong></li>
<li><span>2</span><strong>Show the unlocked screen</strong></li>
<li><span>3</span><strong>Start a hallway conversation</strong></li>
<li aria-hidden="true"><span>1</span><strong>Write a note</strong></li>
<li aria-hidden="true"><span>2</span><strong>Show the unlocked screen</strong></li>
<li aria-hidden="true"><span>3</span><strong>Start a hallway conversation</strong></li>
</ol>
</div>
</aside>
</div>
</section>
Expand Down