Skip to content

prototype: 7 screens + app shell + Faris's review copy - #1

Closed
abullaisi wants to merge 1 commit into
mainfrom
prototype-v2
Closed

prototype: 7 screens + app shell + Faris's review copy#1
abullaisi wants to merge 1 commit into
mainfrom
prototype-v2

Conversation

@abullaisi

@abullaisi abullaisi commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Updates prototype/ from the 2026-07-05 six-screen version to current. Only touches prototype/ — no packages/ files, so it should not collide with the app work.

Applies Faris's review (App items 1–4)

  • 1. Hero copy → "Single subscription. Multiple benefits." + his expected subcopy
  • 2. External-link rows: Open used to do nothing — now opens a real URL in a new tab. (Courses/videos already open a real content page.)
  • 3. "Content" → "Packages"
  • 4. Adds the "Have premium resources?" partner CTA

What else is new since 07-05

  • content.html: real content detail page — course modules with completion, certificate state, video/ebook views. Replaces the old dead-end preview panes.
  • App shell: left sidenav (member nav + partners) with a mobile hamburger drawer; funnel pages keep the topbar.
  • Light + dark mode from one token contract (accent split into text vs fill so it stays legible in both).
  • Geist / Geist Mono; monospace is now scoped to blockchain identifiers only (wallet, contract, tx hash).
  • Feather icons for content types, nav, and the theme toggle.
  • Working buy flow on member-price listings + a personal activity feed.
  • Disconnect control; ?demo=fresh|wallet|sub URL state seeding (handy for demo capture and the demo video).

prototype/SPEC.md documents all of it.

Note on the app copy: the same items Faris flagged also live in packages/web/components/dashboard/ (dashboard-shell.tsx, subscription-card.tsx, content-grid.tsx). I deliberately did not touch those — @yoms07 shout if you want me to take them.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a redesigned dashboard with grouped libraries, resume/review actions, progress tracking, certificates, and personal activity history.
    • Added interactive course, video, and ebook content pages with completion controls.
    • Added member-price purchasing with ownership tracking and purchase activity.
    • Added subscription activity logging and seeded demo states.
    • Added light/dark themes, responsive mobile navigation, and wallet disconnect controls.
    • Updated partner pages and navigation with clearer package links and progress indicators.
  • Style

    • Refreshed typography, content tiles, icons, spacing, motion, and responsive layouts.

Updates prototype/ from the 2026-07-05 six-screen version to current:

- content.html: real content detail page (course modules + completion,
  certificate, video/ebook), replacing the dead-end preview panes
- app shell: left sidenav (member nav + partners) with a mobile hamburger
  drawer; funnel pages keep the topbar
- light + dark mode via a single token contract (accent split into
  text vs fill so it stays legible on both)
- Geist / Geist Mono; mono is now scoped to blockchain identifiers only
- Feather icon set for content types, nav, and the theme toggle
- working buy flow on member-price listings + a personal activity feed
- disconnect control; ?demo=fresh|wallet|sub URL state seeding

Applies Faris's review (App items 1-4):
- hero: 'Single subscription. Multiple benefits.' + his expected subcopy
- 'Content' -> 'Packages'
- external-link rows now open a real URL (previously Open did nothing)
- adds the 'Have premium resources?' partner CTA

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stellar-hackathon-web Ready Ready Preview, Comment Jul 14, 2026 12:31pm

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The prototype adds persisted progress, ownership, last-viewed, and activity state; introduces content detail interactions; updates dashboard, partner, benefits, subscription, and traction flows; and standardizes navigation, themes, responsive layout, content tiles, and activity presentation.

Changes

Prototype feature-flow expansion

Layer / File(s) Summary
Persisted state and prototype data
prototype/app.js, prototype/SPEC.md
Partner data, activity records, localStorage-backed progress, ownership, last-viewed state, theme initialization, and demo seeding are expanded for working member flows.
App shell and visual system
prototype/styles.css, prototype/app.js, prototype/{benefits,dashboard,partner,traction,index}.html, prototype/SPEC.md
Member pages use a sidenav shell with injected controls, theme switching, responsive navigation, updated tokens, content tiles, and revised landing-page messaging.
Content detail and progress flow
prototype/content.html
Subscription-gated content pages resolve partner items, persist progress, support course module completion and certificates, and provide video and ebook completion actions.
Member library, purchase, and activity flows
prototype/{dashboard,partner,benefits,subscribe,traction}.html
Dashboard navigation is grouped and progress-aware, partner previews link to content pages, benefits support buying and ownership, subscription logs activity, and traction displays addresses in activity rows.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Member
  participant Dashboard
  participant ContentPage
  participant KState
  participant LocalStorage
  Member->>Dashboard: Select resume or open content
  Dashboard->>KState: Read lastViewed and progress
  KState->>LocalStorage: Load persisted member state
  Dashboard->>ContentPage: Navigate to content.html
  Member->>ContentPage: Complete content action
  ContentPage->>KState: Save progress and activity
  KState->>LocalStorage: Persist updated state
  ContentPage-->>Member: Show updated progress or certificate
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: the seven-screen prototype app shell plus Faris’s review-copy updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch prototype-v2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@abullaisi

Copy link
Copy Markdown
Collaborator Author

Superseded: pushed directly to main (b64d6f2). prototype/ is ours and touches no packages/ files, so no collision risk.

@abullaisi abullaisi closed this Jul 14, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
prototype/content.html (1)

8-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inline <style> block explicitly flagged for promotion.

The comment already notes these rules are candidate for promotion to styles.css. Worth moving before this pattern is copy-pasted into more pages.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prototype/content.html` around lines 8 - 15, Move the rules from the inline
style block marked “candidate for promotion” into styles.css, preserving the
selectors and declarations for .stack, .module-line .sm, `#c-pct`, `#progress-card`
.progress, and .media-ph.hero; then remove the inline block from
prototype/content.html.
prototype/SPEC.md (1)

172-182: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Original "Screen 5: partner.html" section is now stale.

This amendment (and the earlier Mobbin-revisions note) describes partner.html's real, current behavior, but the older "Screen 5" section at Lines 118-126 still documents the superseded design: 06 MEMBER CONTENT label, preview-pane toggles, and dummy href="#" link rows. The PR renames this to "Packages" and wires real URLs (target="_blank"), so the original section now contradicts the implementation and every later amendment. Since this file is the stated build source-of-truth, worth updating/striking the old section rather than layering amendments indefinitely.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prototype/SPEC.md` around lines 172 - 182, The stale original “Screen 5:
partner.html” section should no longer document preview panes, the “06 MEMBER
CONTENT” label, or dummy href="#" rows. Replace or remove that section so it
describes the current “Packages” layout with real content.html links and
unchanged external link rows using target="_blank", consistent with the later
partner.html behavior and build source of truth.
prototype/styles.css (1)

8-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stylelint violations to clean up (notation/casing/quoting/keyframe naming).

Static analysis flags several convention mismatches introduced in this diff: @import url(...) should be a bare string (Line 8), SFMono-Regular/Menlo should be lowercase in the --font-mono value (Line 55), 'Geist' shouldn't be quoted in the body font stack (Line 92), and @keyframes screenIn should be kebab-case (Line 776). None of these change behavior, but they'll keep the Stylelint config passing.

🧹 Proposed fixes
-@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700;800&family=Geist+Mono:wght@400;700&display=swap');
+@import 'https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700;800&family=Geist+Mono:wght@400;700&display=swap';
-  --font-mono: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
+  --font-mono: 'Geist Mono', ui-monospace, sfmono-regular, menlo, monospace;
-  font-family: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+  font-family: Geist, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-@keyframes screenIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
-.shell { animation: screenIn 320ms cubic-bezier(0.22, 1, 0.36, 1) both; }
+@keyframes screen-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
+.shell { animation: screen-in 320ms cubic-bezier(0.22, 1, 0.36, 1) both; }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prototype/styles.css` around lines 8 - 15, Clean up the Stylelint convention
violations in the stylesheet: use a bare string for the Google Fonts `@import`,
lowercase the SFMono-Regular and Menlo entries in --font-mono, remove quotes
around Geist in the body font stack, and rename the screenIn keyframes
identifier to kebab-case while updating all references.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@prototype/benefits.html`:
- Around line 143-181: Track in-flight purchase indices separately from K.owned,
and update renderListings so pending listings render a disabled “Confirming…”
button instead of an active Buy button. In the rows click handler, mark the
listing pending before starting the timeout, ignore repeated clicks for pending
or owned indices, and clear the pending state when the purchase completes before
re-rendering.

In `@prototype/content.html`:
- Line 86: Update the num-label markup to derive the displayed number from the
current item index or associated data instead of hardcoding “08”; preserve the
existing c-type-label rendering and ensure each rendered content item shows its
correct index.

In `@prototype/styles.css`:
- Around line 718-762: Update the mobile navigation open state around
body.nav-open to lock document scrolling while the drawer is open, preventing
underlying .app-main content from scrolling on touch devices. Preserve normal
page scrolling when nav-open is removed.

---

Nitpick comments:
In `@prototype/content.html`:
- Around line 8-15: Move the rules from the inline style block marked “candidate
for promotion” into styles.css, preserving the selectors and declarations for
.stack, .module-line .sm, `#c-pct`, `#progress-card` .progress, and .media-ph.hero;
then remove the inline block from prototype/content.html.

In `@prototype/SPEC.md`:
- Around line 172-182: The stale original “Screen 5: partner.html” section
should no longer document preview panes, the “06 MEMBER CONTENT” label, or dummy
href="#" rows. Replace or remove that section so it describes the current
“Packages” layout with real content.html links and unchanged external link rows
using target="_blank", consistent with the later partner.html behavior and build
source of truth.

In `@prototype/styles.css`:
- Around line 8-15: Clean up the Stylelint convention violations in the
stylesheet: use a bare string for the Google Fonts `@import`, lowercase the
SFMono-Regular and Menlo entries in --font-mono, remove quotes around Geist in
the body font stack, and rename the screenIn keyframes identifier to kebab-case
while updating all references.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fae7e079-fc06-4a7e-a911-bc2adead3a8e

📥 Commits

Reviewing files that changed from the base of the PR and between 7cec1f2 and 631e903.

⛔ Files ignored due to path filters (7)
  • prototype/assets/ccl-0.jpg is excluded by !**/*.jpg
  • prototype/assets/ccl-1.jpg is excluded by !**/*.jpg
  • prototype/assets/dwb-0.jpg is excluded by !**/*.jpg
  • prototype/assets/dwb-1.jpg is excluded by !**/*.jpg
  • prototype/assets/hero-community.jpg is excluded by !**/*.jpg
  • prototype/assets/sid-0.jpg is excluded by !**/*.jpg
  • prototype/assets/sid-1.jpg is excluded by !**/*.jpg
📒 Files selected for processing (10)
  • prototype/SPEC.md
  • prototype/app.js
  • prototype/benefits.html
  • prototype/content.html
  • prototype/dashboard.html
  • prototype/index.html
  • prototype/partner.html
  • prototype/styles.css
  • prototype/subscribe.html
  • prototype/traction.html

Comment thread prototype/benefits.html
Comment on lines +143 to +181
// Listing rows: member price + a working buy flow (owned state persists).
var rows = document.getElementById("listing-rows");
KDATA.listings.forEach(function (l) {
var row = document.createElement("div");
row.className = "benefit-line";
row.innerHTML =
'<div>' +
'<div class="listing-name">' + l.name + '</div>' +
'<div class="listing-sub">' + l.kind + ' · ' + l.partner + '</div>' +
'</div>' +
'<div class="row tight">' +
'<span class="price-was">' + fmtXlm(l.priceXlm) + '</span>' +
'<span class="price-now">' + fmtXlm(l.memberPriceXlm) + '</span>' +
'</div>';
rows.appendChild(row);
function renderListings() {
rows.innerHTML = "";
var owned = K.owned;
KDATA.listings.forEach(function (l, i) {
var row = document.createElement("div");
row.className = "benefit-line";
var right;
if (owned.indexOf(i) !== -1) {
right = '<span class="pill ok">OWNED</span>';
} else {
right =
'<span class="price-was">' + fmtXlm(l.priceXlm) + '</span>' +
'<span class="price-now">' + fmtXlm(l.memberPriceXlm) + '</span>' +
'<button class="ghost sm" data-buy="' + i + '">Buy</button>';
}
row.innerHTML =
'<div>' +
'<div class="listing-name">' + l.name + '</div>' +
'<div class="listing-sub">' + l.kind + ' · ' + l.partner + '</div>' +
'</div>' +
'<div class="row tight">' + right + '</div>';
rows.appendChild(row);
});
}
renderListings();

rows.addEventListener("click", function (e) {
var btn = e.target.closest("button[data-buy]");
if (!btn) return;
var i = parseInt(btn.getAttribute("data-buy"), 10);
btn.disabled = true;
btn.textContent = "Confirming…";
window.setTimeout(function () {
K.addOwned(i);
K.logActivity("Purchased", KDATA.listings[i].name + " · " + fmtXlm(KDATA.listings[i].memberPriceXlm));
renderListings();
}, 1200);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Concurrent buys clobber each other's "Confirming…" state.

renderListings() rebuilds every row from K.owned only. If a second "Buy" is clicked while a first purchase is still mid-setTimeout, the first purchase's completion re-renders all rows and reverts the second (still-pending) button back to a clickable "Buy" state, since it isn't in K.owned yet. The user can re-click it, and when its original timeout later fires, K.logActivity runs again for the same listing, producing a duplicate "Purchased" entry.

Track in-flight purchases and have renderListings() render a disabled "Confirming…" state for pending indices instead of unconditionally falling back to "Buy".

🛠️ Proposed fix to preserve pending-buy state across re-renders
     var rows = document.getElementById("listing-rows");
+    var pendingBuys = {};
     function renderListings() {
       rows.innerHTML = "";
       var owned = K.owned;
       KDATA.listings.forEach(function (l, i) {
         var row = document.createElement("div");
         row.className = "benefit-line";
         var right;
         if (owned.indexOf(i) !== -1) {
           right = '<span class="pill ok">OWNED</span>';
+        } else if (pendingBuys[i]) {
+          right = '<button class="ghost sm" disabled>Confirming…</button>';
         } else {
           right =
             '<span class="price-was">' + fmtXlm(l.priceXlm) + '</span>' +
             '<span class="price-now">' + fmtXlm(l.memberPriceXlm) + '</span>' +
             '<button class="ghost sm" data-buy="' + i + '">Buy</button>';
         }
         ...
       });
     }
     renderListings();

     rows.addEventListener("click", function (e) {
       var btn = e.target.closest("button[data-buy]");
       if (!btn) return;
       var i = parseInt(btn.getAttribute("data-buy"), 10);
+      pendingBuys[i] = true;
       btn.disabled = true;
       btn.textContent = "Confirming…";
       window.setTimeout(function () {
         K.addOwned(i);
         K.logActivity("Purchased", KDATA.listings[i].name + " · " + fmtXlm(KDATA.listings[i].memberPriceXlm));
+        delete pendingBuys[i];
         renderListings();
       }, 1200);
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Listing rows: member price + a working buy flow (owned state persists).
var rows = document.getElementById("listing-rows");
KDATA.listings.forEach(function (l) {
var row = document.createElement("div");
row.className = "benefit-line";
row.innerHTML =
'<div>' +
'<div class="listing-name">' + l.name + '</div>' +
'<div class="listing-sub">' + l.kind + ' · ' + l.partner + '</div>' +
'</div>' +
'<div class="row tight">' +
'<span class="price-was">' + fmtXlm(l.priceXlm) + '</span>' +
'<span class="price-now">' + fmtXlm(l.memberPriceXlm) + '</span>' +
'</div>';
rows.appendChild(row);
function renderListings() {
rows.innerHTML = "";
var owned = K.owned;
KDATA.listings.forEach(function (l, i) {
var row = document.createElement("div");
row.className = "benefit-line";
var right;
if (owned.indexOf(i) !== -1) {
right = '<span class="pill ok">OWNED</span>';
} else {
right =
'<span class="price-was">' + fmtXlm(l.priceXlm) + '</span>' +
'<span class="price-now">' + fmtXlm(l.memberPriceXlm) + '</span>' +
'<button class="ghost sm" data-buy="' + i + '">Buy</button>';
}
row.innerHTML =
'<div>' +
'<div class="listing-name">' + l.name + '</div>' +
'<div class="listing-sub">' + l.kind + ' · ' + l.partner + '</div>' +
'</div>' +
'<div class="row tight">' + right + '</div>';
rows.appendChild(row);
});
}
renderListings();
rows.addEventListener("click", function (e) {
var btn = e.target.closest("button[data-buy]");
if (!btn) return;
var i = parseInt(btn.getAttribute("data-buy"), 10);
btn.disabled = true;
btn.textContent = "Confirming…";
window.setTimeout(function () {
K.addOwned(i);
K.logActivity("Purchased", KDATA.listings[i].name + " · " + fmtXlm(KDATA.listings[i].memberPriceXlm));
renderListings();
}, 1200);
// Listing rows: member price + a working buy flow (owned state persists).
var rows = document.getElementById("listing-rows");
var pendingBuys = {};
function renderListings() {
rows.innerHTML = "";
var owned = K.owned;
KDATA.listings.forEach(function (l, i) {
var row = document.createElement("div");
row.className = "benefit-line";
var right;
if (owned.indexOf(i) !== -1) {
right = '<span class="pill ok">OWNED</span>';
} else if (pendingBuys[i]) {
right = '<button class="ghost sm" disabled>Confirming…</button>';
} else {
right =
'<span class="price-was">' + fmtXlm(l.priceXlm) + '</span>' +
'<span class="price-now">' + fmtXlm(l.memberPriceXlm) + '</span>' +
'<button class="ghost sm" data-buy="' + i + '">Buy</button>';
}
row.innerHTML =
'<div>' +
'<div class="listing-name">' + l.name + '</div>' +
'<div class="listing-sub">' + l.kind + ' · ' + l.partner + '</div>' +
'</div>' +
'<div class="row tight">' + right + '</div>';
rows.appendChild(row);
});
}
renderListings();
rows.addEventListener("click", function (e) {
var btn = e.target.closest("button[data-buy]");
if (!btn) return;
var i = parseInt(btn.getAttribute("data-buy"), 10);
pendingBuys[i] = true;
btn.disabled = true;
btn.textContent = "Confirming…";
window.setTimeout(function () {
K.addOwned(i);
K.logActivity("Purchased", KDATA.listings[i].name + " · " + fmtXlm(KDATA.listings[i].memberPriceXlm));
delete pendingBuys[i];
renderListings();
}, 1200);
});
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 159-164: Avoid assigning untrusted data to innerHTML/outerHTML or document.write
Context: row.innerHTML =
'

' +
'
' + l.name + '
' +
'
' + l.kind + ' · ' + l.partner + '
' +
'
' +
'
' + right + '
'
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(inner-outer-html)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prototype/benefits.html` around lines 143 - 181, Track in-flight purchase
indices separately from K.owned, and update renderListings so pending listings
render a disabled “Confirming…” button instead of an active Buy button. In the
rows click handler, mark the listing pending before starting the timeout, ignore
repeated clicks for pending or owned indices, and clear the pending state when
the purchase completes before re-rendering.

Comment thread prototype/content.html

<div class="card">
<div class="thumb hero" id="c-thumb"></div>
<div class="num-label"><span class="num">08</span> <span id="c-type-label"></span></div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Hardcoded "08" label doesn't reflect the displayed content.

<span class="num">08</span> is static regardless of which content item/type is rendered, so every content page shows "08 COURSE"/"08 VIDEO"/"08 EBOOK". If this is meant to be a per-item index, it should be derived from idx/data rather than hardcoded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prototype/content.html` at line 86, Update the num-label markup to derive the
displayed number from the current item index or associated data instead of
hardcoding “08”; preserve the existing c-type-label rendering and ensure each
rendered content item shows its correct index.

Comment thread prototype/styles.css
Comment on lines 718 to +762
@media (max-width: 900px) {
.grid-2, .grid-3, .grid-dash, .grid-detail { grid-template-columns: 1fr; }
.grid-dash .stat-strip { flex-direction: row; }

/* sidenav becomes an off-canvas drawer */
.sidenav {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 264px;
height: 100vh;
transform: translateX(-100%);
transition: transform 200ms ease;
z-index: 50;
background: var(--color-bg-primary);
border-right: 1px solid var(--color-border-medium);
}
body.nav-open .sidenav { transform: translateX(0); }

.nav-overlay {
display: block;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
opacity: 0;
pointer-events: none;
transition: opacity 200ms ease;
z-index: 40;
}
body.nav-open .nav-overlay { opacity: 1; pointer-events: auto; }

.mobile-topbar {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border-medium);
position: sticky;
top: 0;
z-index: 30;
background: var(--color-bg-primary);
}
.mobile-topbar .logo { font-size: 18px; }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Background scrolls behind the open mobile drawer.

body.nav-open toggles the .sidenav/.nav-overlay visuals but never locks page scroll. On a touch device, a user can still scroll the underlying .app-main content while the drawer/overlay is open.

🔧 Proposed fix
   body.nav-open .sidenav { transform: translateX(0); }
+  body.nav-open { overflow: hidden; }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@media (max-width: 900px) {
.grid-2, .grid-3, .grid-dash, .grid-detail { grid-template-columns: 1fr; }
.grid-dash .stat-strip { flex-direction: row; }
/* sidenav becomes an off-canvas drawer */
.sidenav {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 264px;
height: 100vh;
transform: translateX(-100%);
transition: transform 200ms ease;
z-index: 50;
background: var(--color-bg-primary);
border-right: 1px solid var(--color-border-medium);
}
body.nav-open .sidenav { transform: translateX(0); }
.nav-overlay {
display: block;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
opacity: 0;
pointer-events: none;
transition: opacity 200ms ease;
z-index: 40;
}
body.nav-open .nav-overlay { opacity: 1; pointer-events: auto; }
.mobile-topbar {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border-medium);
position: sticky;
top: 0;
z-index: 30;
background: var(--color-bg-primary);
}
.mobile-topbar .logo { font-size: 18px; }
}
`@media` (max-width: 900px) {
.grid-2, .grid-3, .grid-dash, .grid-detail { grid-template-columns: 1fr; }
.grid-dash .stat-strip { flex-direction: row; }
/* sidenav becomes an off-canvas drawer */
.sidenav {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 264px;
height: 100vh;
transform: translateX(-100%);
transition: transform 200ms ease;
z-index: 50;
background: var(--color-bg-primary);
border-right: 1px solid var(--color-border-medium);
}
body.nav-open .sidenav { transform: translateX(0); }
body.nav-open { overflow: hidden; }
.nav-overlay {
display: block;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
opacity: 0;
pointer-events: none;
transition: opacity 200ms ease;
z-index: 40;
}
body.nav-open .nav-overlay { opacity: 1; pointer-events: auto; }
.mobile-topbar {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border-medium);
position: sticky;
top: 0;
z-index: 30;
background: var(--color-bg-primary);
}
.mobile-topbar .logo { font-size: 18px; }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prototype/styles.css` around lines 718 - 762, Update the mobile navigation
open state around body.nav-open to lock document scrolling while the drawer is
open, preventing underlying .app-main content from scrolling on touch devices.
Preserve normal page scrolling when nav-open is removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant