From a2763df96d6bce8d5d8d0f91444227e95c7f550d Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 26 May 2026 15:41:41 -0700 Subject: [PATCH 01/18] Add design spec for lab view inset popup Co-Authored-By: Claude Sonnet 4.6 --- .../2026-05-26-lab-view-inset-popup-design.md | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-26-lab-view-inset-popup-design.md diff --git a/docs/superpowers/specs/2026-05-26-lab-view-inset-popup-design.md b/docs/superpowers/specs/2026-05-26-lab-view-inset-popup-design.md new file mode 100644 index 0000000..49e4246 --- /dev/null +++ b/docs/superpowers/specs/2026-05-26-lab-view-inset-popup-design.md @@ -0,0 +1,79 @@ +# Lab View Inset Popup + +**Date:** 2026-05-26 +**Branch:** feature/hover-lab-view + +## Problem + +When the user switches to "Lab view", the full-screen `LabView` component covers the entire center viewer, replacing the molecular simulation. The user cannot see the cuvette and the simulation at the same time. + +## Goal + +Show the cuvette as a small inset panel overlaid on the simulation viewer, so both are visible simultaneously. The intro page retains its existing full-screen cuvette behavior. + +## Approach + +Extend `LabView.tsx` to support two visual modes — full-screen and inset — derived from existing context values. No new components. `ViewSwitch.tsx` changes minimally. + +## Design + +### Mode switching + +`LabView` derives `isInset` from context: + +``` +isInset = viewportType === ViewType.Lab + && !(page === 1 && module === Module.A_B_AB) +``` + +- `isInset = false` → existing full-screen behavior (intro page only) +- `isInset = true` → inset panel (all other pages when lab view is active) + +### ViewSwitch changes + +The `Viewer` already renders unconditionally (it is last in the JSX stack). No change needed there. `LabView` continues to render when `viewportType === ViewType.Lab`. + +### Inset panel layout + +| Property | Value | +|---|---| +| Position | `absolute`, `top: 16px`, `right: 16px` | +| Width | ~160–180px (enough for scale bar + cuvette side-by-side) | +| Background | `ipub-bg_4.png` with blur applied (pseudo-element or filter) so the cuvette remains legible | +| Border | `1px solid var(--app-border-color)` | +| Label | "In the wet lab" text at top of panel | +| Contents | `ScaleBar` (left) + `Cuvette` (right) — components unchanged | +| z-index | `var(--bottom)` (z=10) — same as current full-screen container; already sufficient to sit above the `Viewer` but below side panels (z=100) | + +The blur intensity and opacity on the background image should be tuned during implementation so the cuvette and scale bar are clearly readable. + +### CSS changes + +`labview.module.css` gains an `inset` class. The background image and full-screen sizing styles remain on `container` only and are not applied when rendering in inset mode. + +``` +.inset { + position: absolute; + top: 16px; + right: 16px; + width: ~170px; + border: 1px solid var(--app-border-color); + overflow: hidden; + /* background image with blur via pseudo-element */ +} +``` + +### What does not change + +- `ScaleBar` and `Cuvette` components — no modifications +- The "Lab view" / "Molecular view" toggle button — behavior unchanged +- Intro page full-screen behavior — unchanged +- No animation on show/hide + +## Files to change + +| File | Change | +|---|---| +| `src/components/LabView.tsx` | Add `isInset` logic; conditional render of label and inset vs full-screen container | +| `src/components/labview.module.css` | Add `.inset` class with panel sizing, border, blurred background | +| `src/components/ViewSwitch.tsx` | Likely no change required | From ba6ef4cca6b6d13e7eca4c629916d36146760f7e Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 26 May 2026 15:47:57 -0700 Subject: [PATCH 02/18] Add implementation plan for lab view inset popup Co-Authored-By: Claude Sonnet 4.6 --- .../plans/2026-05-26-lab-view-inset-popup.md | 298 ++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-26-lab-view-inset-popup.md diff --git a/docs/superpowers/plans/2026-05-26-lab-view-inset-popup.md b/docs/superpowers/plans/2026-05-26-lab-view-inset-popup.md new file mode 100644 index 0000000..9823276 --- /dev/null +++ b/docs/superpowers/plans/2026-05-26-lab-view-inset-popup.md @@ -0,0 +1,298 @@ +# Lab View Inset Popup Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Convert `LabView` to render as a small inset popup overlaid on the simulation viewer rather than replacing the full center area, except on the intro page (Module A_B_AB page 1) which retains full-screen behavior. + +**Architecture:** `LabView.tsx` derives `isIntroPage` from context and conditionally renders either a compact inset panel or the existing full-screen layout. The inset uses a CSS `::before` pseudo-element with `filter: blur()` for the blurred lab background. `ScaleBar` receives a `className` prop so its container's absolute positioning can be overridden for the inset's flex layout. + +**Tech Stack:** React, TypeScript, CSS Modules (`classnames` package already in use) + +--- + +### Task 1: Add `className` prop to `ScaleBar` + +**Files:** +- Modify: `src/components/ScaleBar.tsx` + +- [ ] **Step 1: Update `ScaleBarProps` and forward `className` to the container div** + +Replace the file contents of `src/components/ScaleBar.tsx` with: + +```tsx +import React, { useContext } from "react"; +import classNames from "classnames"; + +import styles from "./scalebar.module.css"; +import { MICRO } from "../constants"; +import { SimulariumContext } from "../simulation/context"; + +interface ScaleBarProps { + productColor: string; + className?: string; +} + +const ScaleBar: React.FC = ({ productColor, className }) => { + const { maxConcentration } = useContext(SimulariumContext); + const labelArray = []; + const interval = maxConcentration / 5; + for (let i = maxConcentration; i >= 0; i = i - interval) { + labelArray.push(i); + } + return ( +
+
+ {labelArray.map((i) => ( +
+ {i} {MICRO}M -{" "} +
+ ))} +
+
+
+ ); +}; + +export default ScaleBar; +``` + +The only changes from the original: added `import classNames from "classnames"`, added `className?: string` to props, and changed `className={styles.container}` to `className={classNames(styles.container, className)}`. + +- [ ] **Step 2: Verify TypeScript compiles** + +Run: `npx tsc --noEmit` +Expected: no errors. + +- [ ] **Step 3: Verify existing lab view still works** + +Run: `npm run dev` +Navigate to a page with "Lab view" and confirm the full-screen lab view still renders correctly with the scale bar visible. + +- [ ] **Step 4: Commit** + +```bash +git add src/components/ScaleBar.tsx +git commit -m "feat: add optional className prop to ScaleBar" +``` + +--- + +### Task 2: Add inset CSS classes to `labview.module.css` + +**Files:** +- Modify: `src/components/labview.module.css` + +- [ ] **Step 1: Append the inset styles** + +Add the following to the end of `src/components/labview.module.css`: + +```css +/* ── Inset panel (all pages except intro) ──────────────── */ + +.inset { + position: absolute; + top: 16px; + right: 16px; + width: 210px; + border: 1px solid var(--app-border-color); + overflow: hidden; + z-index: var(--bottom); +} + +/* Blurred lab background image behind inset content */ +.inset::before { + content: ""; + position: absolute; + inset: 0; + background-image: url("../assets/ipub-bg_4.png"); + background-size: cover; + background-position: center; + filter: blur(3px); + transform: scale(1.1); /* prevents blurred edges from bleeding to border */ + z-index: 0; +} + +.insetLabel { + position: relative; + z-index: 1; + padding: 6px 8px 4px; + font-size: 12px; + font-weight: 600; + color: var(--primary-color); + border-bottom: 1px solid var(--app-border-color); +} + +.insetBody { + position: relative; + z-index: 1; + display: flex; + flex-direction: row; + align-items: flex-end; + padding: 8px; + gap: 8px; + /* 166px = scale bar height; 24px = top+bottom padding */ + height: 190px; +} + +/* Override ScaleBar's absolute positioning so it participates in flex layout */ +.scaleBarInset { + position: static !important; + bottom: unset !important; + right: unset !important; +} + +.insetCuvette { + width: 70px; + flex-shrink: 0; +} +``` + +The `!important` on `.scaleBarInset` is required because `ScaleBar`'s own CSS module class (`.container`) and this override class have identical CSS specificity — source order alone cannot guarantee which wins. + +- [ ] **Step 2: Verify no CSS compile errors** + +Run: `npm run dev` +Expected: dev server starts without errors. + +--- + +### Task 3: Update `LabView.tsx` to render inset or full-screen + +**Files:** +- Modify: `src/components/LabView.tsx` + +- [ ] **Step 1: Replace the full contents of `src/components/LabView.tsx`** + +```tsx +import Rainbow from "rainbowvis.js"; +import { useContext, useMemo } from "react"; +import { SimulariumContext } from "../simulation/context"; +import Cuvette from "./icons/Cuvette"; +import styles from "./labview.module.css"; +import classNames from "classnames"; +import ScaleBar from "./ScaleBar"; +import { Module } from "../types"; + +const LabView: React.FC = () => { + const { + currentProductionConcentration, + maxConcentration, + page, + getAgentColor, + productName, + module, + } = useContext(SimulariumContext); + const color = getAgentColor(productName); + const colorGradient = useMemo(() => { + const rainbow = new Rainbow(); + rainbow.setSpectrum("#FFFFFF", color); + return rainbow; + }, [color]); + const position = (currentProductionConcentration / maxConcentration) * 100; + const isIntroPage = page === 1 && module === Module.A_B_AB; + + if (!isIntroPage) { + return ( +
+
In the wet lab
+
+ +
+ +
+
+
+ ); + } + + return ( +
+
+ +
+
+ ); +}; + +export default LabView; +``` + +Changes from the original: +- `VisibilityControl` import removed — it was only used to hide the `ScaleBar` on the intro page; the `isIntroPage` branch now handles that by simply not rendering `ScaleBar` in full-screen mode. +- Full-screen branch always applies `styles.top` (z-index 200, above side panels) — the conditional was `page === 1 && module === Module.A_B_AB`, which is always true when `isIntroPage` is true. +- Inset branch renders a compact panel with label, `ScaleBar`, and `Cuvette`. + +- [ ] **Step 2: Verify TypeScript compiles** + +Run: `npx tsc --noEmit` +Expected: no errors. + +- [ ] **Step 3: Visual verification — inset mode** + +Run: `npm run dev` + +Navigate to any page **after** the intro page and click "Lab view": +1. Molecular simulation remains visible and running in the background ✓ +2. Inset panel appears in the upper-right corner of the center viewer ✓ +3. "In the wet lab" label appears at the top of the panel ✓ +4. Scale bar (concentration labels + color gradient) is visible in the panel ✓ +5. Cuvette is visible and updates color as concentration changes ✓ +6. Blurred lab background image is visible behind the panel content ✓ +7. Clicking "Molecular view" dismisses the panel ✓ + +- [ ] **Step 4: Visual verification — intro page full-screen mode** + +Navigate to page 1 of Module 1 (first module, first page). +The full-screen lab background covers the center area (existing behavior). No "In the wet lab" label. No inset panel. ✓ + +- [ ] **Step 5: Tune visual values if needed** + +The following values in `labview.module.css` can be adjusted to match the design: + +| Property | Location | Default | What it controls | +|---|---|---|---| +| `width` | `.inset` | `210px` | Overall inset panel width | +| `filter: blur(Xpx)` | `.inset::before` | `3px` | Background image blur intensity | +| `height` | `.insetBody` | `190px` | Body height (should match scale bar height + padding) | +| `width` | `.insetCuvette` | `70px` | Cuvette width (height scales proportionally) | + +Re-verify after any adjustments. + +- [ ] **Step 6: Commit** + +```bash +git add src/components/LabView.tsx src/components/labview.module.css +git commit -m "feat: convert lab view to inset popup overlay" +``` + +--- + +## Self-Review + +**Spec coverage:** +- ✅ Inset popup, upper-right corner of center viewer +- ✅ Molecular simulation visible and running behind inset +- ✅ "In the wet lab" label in inset +- ✅ Scale bar + cuvette in inset +- ✅ Blurred lab background image in inset (experimenting with `blur(3px)`, tunable) +- ✅ Intro page (page 1, Module A_B_AB) retains full-screen behavior unchanged +- ✅ Toggle controlled by existing "Lab view" button — no close button on popup +- ✅ No animation + +**Placeholder scan:** No TBDs. The visual tuning step (Task 3, Step 5) is intentional — blur intensity and panel sizing are aesthetic decisions best made against the running app. + +**Type consistency:** `ScaleBarProps.className?: string` defined in Task 1, passed as `styles.scaleBarInset` (a CSS Modules class reference, type `string`) in Task 3. Consistent. + +**Potential issue — `classnames` in ScaleBar:** The original `ScaleBar.tsx` did not import `classnames`. Task 1 adds the import. The package is already a dependency (`classnames` is used in multiple other components), so no `npm install` needed. From f31c9bd4b825fb2f49e1589902a968ae46513e1b Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 26 May 2026 15:50:44 -0700 Subject: [PATCH 03/18] feat: add optional className prop to ScaleBar --- src/components/ScaleBar.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ScaleBar.tsx b/src/components/ScaleBar.tsx index 7300644..8d26298 100644 --- a/src/components/ScaleBar.tsx +++ b/src/components/ScaleBar.tsx @@ -1,4 +1,5 @@ import React, { useContext } from "react"; +import classNames from "classnames"; import styles from "./scalebar.module.css"; import { MICRO } from "../constants"; @@ -6,9 +7,10 @@ import { SimulariumContext } from "../simulation/context"; interface ScaleBarProps { productColor: string; + className?: string; } -const ScaleBar: React.FC = ({ productColor }) => { +const ScaleBar: React.FC = ({ productColor, className }) => { const { maxConcentration } = useContext(SimulariumContext); const labelArray = []; const interval = maxConcentration / 5; @@ -17,7 +19,7 @@ const ScaleBar: React.FC = ({ productColor }) => { } return (
@@ -29,8 +31,6 @@ const ScaleBar: React.FC = ({ productColor }) => { ))}
Date: Tue, 26 May 2026 15:53:06 -0700 Subject: [PATCH 04/18] feat: add inset panel CSS to labview module --- src/components/labview.module.css | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/components/labview.module.css b/src/components/labview.module.css index 9eac86c..0968492 100644 --- a/src/components/labview.module.css +++ b/src/components/labview.module.css @@ -24,3 +24,62 @@ position: absolute; width: 95px; } + +/* ── Inset panel (all pages except intro) ──────────────── */ + +.inset { + position: absolute; + top: 16px; + right: 16px; + width: 210px; + border: 1px solid var(--app-border-color); + overflow: hidden; + z-index: var(--bottom); +} + +/* Blurred lab background image behind inset content */ +.inset::before { + content: ""; + position: absolute; + inset: 0; + background-image: url("../assets/ipub-bg_4.png"); + background-size: cover; + background-position: center; + filter: blur(3px); + transform: scale(1.1); /* prevents blurred edges from bleeding to border */ + z-index: 0; +} + +.insetLabel { + position: relative; + z-index: 1; + padding: 6px 8px 4px; + font-size: 12px; + font-weight: 600; + color: var(--primary-color); + border-bottom: 1px solid var(--app-border-color); +} + +.insetBody { + position: relative; + z-index: 1; + display: flex; + flex-direction: row; + align-items: flex-end; + padding: 8px; + gap: 8px; + /* 166px = scale bar height; 24px = top+bottom padding */ + height: 190px; +} + +/* Override ScaleBar's absolute positioning so it participates in flex layout */ +.scaleBarInset { + position: static !important; + bottom: unset !important; + right: unset !important; +} + +.insetCuvette { + width: 70px; + flex-shrink: 0; +} From b7abf67a33d40fcfc2356ccea30337aaad8d664a Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 26 May 2026 15:54:56 -0700 Subject: [PATCH 05/18] feat: convert lab view to inset popup overlay --- src/components/LabView.tsx | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/components/LabView.tsx b/src/components/LabView.tsx index 8137709..aa90822 100644 --- a/src/components/LabView.tsx +++ b/src/components/LabView.tsx @@ -5,7 +5,6 @@ import Cuvette from "./icons/Cuvette"; import styles from "./labview.module.css"; import classNames from "classnames"; import ScaleBar from "./ScaleBar"; -import VisibilityControl from "./shared/VisibilityControl"; import { Module } from "../types"; const LabView: React.FC = () => { @@ -24,16 +23,27 @@ const LabView: React.FC = () => { return rainbow; }, [color]); const position = (currentProductionConcentration / maxConcentration) * 100; + const isIntroPage = page === 1 && module === Module.A_B_AB; + + if (!isIntroPage) { + return ( +
+
In the wet lab
+
+ +
+ +
+
+
+ ); + } + return ( -
- - - +
From bd4621889cd35d96259a5825c0d22f3ec32f8c46 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 26 May 2026 16:32:31 -0700 Subject: [PATCH 06/18] =?UTF-8?q?feat:=20add=20=C3=97=20close=20button=20t?= =?UTF-8?q?o=20inset=20header,=20hide=20toggle=20button=20when=20inset=20i?= =?UTF-8?q?s=20open?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/components/LabView.tsx | 12 +++++++++- src/components/ViewSwitch.tsx | 39 ++++++++++++++++++------------- src/components/labview.module.css | 18 ++++++++++++++ 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/components/LabView.tsx b/src/components/LabView.tsx index aa90822..a7622c6 100644 --- a/src/components/LabView.tsx +++ b/src/components/LabView.tsx @@ -15,6 +15,7 @@ const LabView: React.FC = () => { getAgentColor, productName, module, + setViewportType, } = useContext(SimulariumContext); const color = getAgentColor(productName); const colorGradient = useMemo(() => { @@ -28,7 +29,16 @@ const LabView: React.FC = () => { if (!isIntroPage) { return (
-
In the wet lab
+
+ In the wet lab + +
{ const isFirstPageOfFirstModule = page === FIRST_PAGE[module] + 1 && module === Module.A_B_AB; + const showLabToggleButton = + viewportType !== ViewType.Lab || isFirstPageOfFirstModule; + let buttonStyle: React.CSSProperties = { top: 16, right: 16, @@ -43,22 +46,26 @@ const ViewSwitch: React.FC = () => { return (
- - - ) : ( - - ) - } - > - {viewportType === ViewType.Lab ? "Molecular" : "Lab"}{" "} - view - - + {showLabToggleButton && ( + + + ) : ( + + ) + } + > + {viewportType === ViewType.Lab + ? "Molecular" + : "Lab"}{" "} + view + + + )} {viewportType === ViewType.Lab ? : null} diff --git a/src/components/labview.module.css b/src/components/labview.module.css index 0968492..9b3a38a 100644 --- a/src/components/labview.module.css +++ b/src/components/labview.module.css @@ -58,6 +58,24 @@ font-weight: 600; color: var(--primary-color); border-bottom: 1px solid var(--app-border-color); + display: flex; + align-items: center; + justify-content: space-between; +} + +.insetClose { + background: none; + border: none; + color: var(--primary-color); + cursor: pointer; + font-size: 14px; + line-height: 1; + padding: 0 0 0 8px; + opacity: 0.7; +} + +.insetClose:hover { + opacity: 1; } .insetBody { From 0a8178143a8df9c53dc7643e647dca998ced5a3e Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 26 May 2026 16:45:58 -0700 Subject: [PATCH 07/18] fix: VisibilityControl includedPages now only restricts listed modules Previously, passing includedPages caused shouldRender = false for any module not in the map, because undefined?.includes() ?? false evaluates to false. This silently hid the EventsOverTimePlot on all pages of Module.A_B_AB. Now only applies the includedPages constraint when the current module is actually a key in the map. Co-Authored-By: Claude Sonnet 4.6 --- src/components/shared/VisibilityControl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/shared/VisibilityControl.tsx b/src/components/shared/VisibilityControl.tsx index 2eaf598..549144a 100644 --- a/src/components/shared/VisibilityControl.tsx +++ b/src/components/shared/VisibilityControl.tsx @@ -28,7 +28,7 @@ const VisibilityControl: React.FC = ({ let shouldRender = true; - if (includedPages) { + if (includedPages && module in includedPages) { shouldRender = includedPages[module]?.includes(page) ?? false; } else if (excludedPages) { shouldRender = !excludedPages[module]?.includes(page); From 91d23c464a481e090b73aea6646522d2301f8b05 Mon Sep 17 00:00:00 2001 From: meganrm Date: Wed, 27 May 2026 11:11:06 -0700 Subject: [PATCH 08/18] change content --- src/content/HighAffinity.tsx | 38 +++--------------------------------- 1 file changed, 3 insertions(+), 35 deletions(-) diff --git a/src/content/HighAffinity.tsx b/src/content/HighAffinity.tsx index 8f2218c..ead46b3 100644 --- a/src/content/HighAffinity.tsx +++ b/src/content/HighAffinity.tsx @@ -104,23 +104,6 @@ export const highAffinityContentArray: PageContent[] = [ layout: LayoutType.LiveSimulation, progressionElement: VIEW_SWITCH_ID, }, - { - content: ( - <> - The clear liquid is slowly turning yellow as the simulation - progresses, but it is taking a long time to change. Can you - estimate the concentration of ? - - ), - callToAction: ( - <> - Click Molecular view to switch back. - - ), - section: Section.Introduction, - layout: LayoutType.LiveSimulation, - progressionElement: VIEW_SWITCH_ID, - }, { content: ( <> @@ -161,21 +144,6 @@ export const highAffinityContentArray: PageContent[] = [ layout: LayoutType.LiveSimulation, progressionElement: PLAY_BUTTON_ID, }, - { - content: ( - <>Randomizing the positions is simulating a well-mixed solution. - ), - callToAction: ( - <> - Click Lab view to see what the cuvette looks - like now. - - ), - - section: Section.Introduction, - layout: LayoutType.LiveSimulation, - progressionElement: VIEW_SWITCH_ID, - }, { content: ( <> @@ -186,14 +154,14 @@ export const highAffinityContentArray: PageContent[] = [ ), callToAction: ( <> - Click Molecular view to switch back to the - simulation. + When you're done observing the color, pause the + simulation to move on. ), section: Section.Introduction, layout: LayoutType.LiveSimulation, - progressionElement: VIEW_SWITCH_ID, + progressionElement: PLAY_BUTTON_ID, }, { title: "Start the experiment", From 721f9c19dfb7e20375ebc46b3ae91d589246bf6b Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 11 Aug 2026 15:03:56 -0700 Subject: [PATCH 09/18] only show lab view when want user to use it --- src/components/ViewSwitch.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/ViewSwitch.tsx b/src/components/ViewSwitch.tsx index c17aefe..7e1abab 100644 --- a/src/components/ViewSwitch.tsx +++ b/src/components/ViewSwitch.tsx @@ -47,7 +47,10 @@ const ViewSwitch: React.FC = () => { return (
- + {showLabToggleButton && ( Date: Tue, 11 Aug 2026 15:22:31 -0700 Subject: [PATCH 10/18] adjust copy --- src/content/HighAffinity.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/HighAffinity.tsx b/src/content/HighAffinity.tsx index ead46b3..ceea7cc 100644 --- a/src/content/HighAffinity.tsx +++ b/src/content/HighAffinity.tsx @@ -154,8 +154,8 @@ export const highAffinityContentArray: PageContent[] = [ ), callToAction: ( <> - When you're done observing the color, pause the - simulation to move on. + When you're done observing the color in the Lab view,{" "} + pause the simulation to move on. ), From 083c82756d0bc7b8cc60c4eadb9b424b6861bdfb Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 11 Aug 2026 15:23:17 -0700 Subject: [PATCH 11/18] use visibility control --- src/components/LabView.tsx | 19 +++++++++--------- src/components/ViewSwitch.tsx | 37 ++++++++++++++++------------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/components/LabView.tsx b/src/components/LabView.tsx index 2b50e07..e8d68da 100644 --- a/src/components/LabView.tsx +++ b/src/components/LabView.tsx @@ -27,7 +27,16 @@ const LabView: React.FC = () => { const position = (currentProductionConcentration / maxConcentration) * 100; const isIntroPage = page === 1 && module === Module.A_B_AB; - if (!isIntroPage) { + if (isIntroPage) { + // shows the full screen illustrated cuvette on the first page of the first module + return ( +
+
+ +
+
+ ); + } else { return (
@@ -52,14 +61,6 @@ const LabView: React.FC = () => {
); } - - return ( -
-
- -
-
- ); }; export default LabView; diff --git a/src/components/ViewSwitch.tsx b/src/components/ViewSwitch.tsx index 7e1abab..afa334d 100644 --- a/src/components/ViewSwitch.tsx +++ b/src/components/ViewSwitch.tsx @@ -50,27 +50,24 @@ const ViewSwitch: React.FC = () => { - {showLabToggleButton && ( - - - ) : ( - - ) - } - > - {viewportType === ViewType.Lab - ? "Molecular" - : "Lab"}{" "} - view - - - )} + + + ) : ( + + ) + } + > + {viewportType === ViewType.Lab ? "Simulation" : "Lab"}{" "} + view + + {viewportType === ViewType.Lab ? : null} From 5913bba752f9e76f65223f52c54e02a4ace3c0ac Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 11 Aug 2026 15:27:30 -0700 Subject: [PATCH 12/18] Add design spec for icon-only lab toggle button Co-Authored-By: Claude Sonnet 5 --- .../2026-08-11-icon-only-lab-toggle-design.md | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-11-icon-only-lab-toggle-design.md diff --git a/docs/superpowers/specs/2026-08-11-icon-only-lab-toggle-design.md b/docs/superpowers/specs/2026-08-11-icon-only-lab-toggle-design.md new file mode 100644 index 0000000..dabb196 --- /dev/null +++ b/docs/superpowers/specs/2026-08-11-icon-only-lab-toggle-design.md @@ -0,0 +1,74 @@ +# Icon-Only Lab Toggle Button + +**Date:** 2026-08-11 +**Branch:** feature/hover-lab-view + +## Problem + +The lab-view toggle button (`ViewSwitch.tsx`) and the "In the wet lab" inset panel (`LabView.tsx`) are both anchored to `top: 16px; right: 16px`, so they render stacked in the same corner of the viewer and visually overlap instead of the panel sitting cleanly below the button. + +## Goal + +- The inset panel sits below the toggle button with a small gap, no overlap. +- The toggle button shows its icon only — no "Lab view" / "Simulation view" text. + +## Approach + +Two small, independent changes, no new components: + +1. Drop the button's text children, matching the icon-only `OverlayButton` pattern already used by `PlayButton.tsx`. +2. Move the inset panel's `top` offset down to clear the now-smaller button. + +## Design + +### Toggle button (`ViewSwitch.tsx`) + +Remove the text children currently rendered inside the `OverlayButton`: + +```tsx +{viewportType === ViewType.Lab ? "Simulation" : "Lab"} view +``` + +Leaving only the `icon` prop (`` / ``), the same shape as: + +```tsx + +``` + +in `PlayButton.tsx`. Ant Design's `Button` reduces to a compact square when it has an `icon` and no children, so no new sizing props are needed. Position (`top: 16, right: 16`, or centered on the intro page) is unchanged. + +### Inset panel (`labview.module.css`) + +Change `.inset`'s `top` from `16px` to `64px` (16px original offset + ~40px button height + 8px gap), keeping `right: 16px` so the panel remains right-aligned under the button. + +```css +.inset { + position: absolute; + top: 64px; + right: 16px; + ... +} +``` + +### Why the intro page is unaffected + +The panel and the centered button never render at the same time: both are gated by the same `page === 1 && module === Module.A_B_AB` condition (`isIntroPage` in `LabView.tsx`, `isFirstPageOfFirstModule` in `ViewSwitch.tsx`), just on opposite branches. When the button is centered, `LabView` renders its full-screen cuvette, not the inset panel. So the new `top: 64px` offset only ever applies alongside the top-right anchored button. + +### What does not change + +- Inset panel content — title text ("In the wet lab"), close button, `ScaleBar`, `Cuvette` — unchanged +- Intro page full-screen cuvette behavior — unchanged +- Button click behavior, icons used (`Molecules` / `LabIcon`) — unchanged + +## Files to change + +| File | Change | +|---|---| +| `src/components/ViewSwitch.tsx` | Remove text children from the `OverlayButton`, leaving icon-only | +| `src/components/labview.module.css` | Change `.inset`'s `top` from `16px` to `64px` | + +## Testing + +Visual check only: +- Toggle button renders icon-only in both Lab and Simulation states +- Inset panel sits below the button with a visible gap, no overlap, on a non-intro page From 8cbaaaba4f724bce970ef643686cf4ab820ff326 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 11 Aug 2026 15:35:07 -0700 Subject: [PATCH 13/18] Make lab toggle button icon-only and reposition inset panel below it Co-Authored-By: Claude Sonnet 5 --- src/components/ViewSwitch.tsx | 5 +---- src/components/labview.module.css | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/ViewSwitch.tsx b/src/components/ViewSwitch.tsx index afa334d..9c5227a 100644 --- a/src/components/ViewSwitch.tsx +++ b/src/components/ViewSwitch.tsx @@ -63,10 +63,7 @@ const ViewSwitch: React.FC = () => { ) } - > - {viewportType === ViewType.Lab ? "Simulation" : "Lab"}{" "} - view - + /> diff --git a/src/components/labview.module.css b/src/components/labview.module.css index 9b3a38a..f83ac37 100644 --- a/src/components/labview.module.css +++ b/src/components/labview.module.css @@ -29,7 +29,7 @@ .inset { position: absolute; - top: 16px; + top: 64px; right: 16px; width: 210px; border: 1px solid var(--app-border-color); From 5755d84e197e566f8b3005b7a14eefda042e8c11 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 11 Aug 2026 15:56:59 -0700 Subject: [PATCH 14/18] button styling and copy --- src/components/ViewSwitch.tsx | 17 ++++++++++------- src/components/shared/ButtonLibrary.tsx | 12 ++++++++++-- src/components/shared/button-library.module.css | 11 +++++++++++ src/content/HighAffinity.tsx | 4 ++-- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/components/ViewSwitch.tsx b/src/components/ViewSwitch.tsx index 9c5227a..7a8487a 100644 --- a/src/components/ViewSwitch.tsx +++ b/src/components/ViewSwitch.tsx @@ -23,9 +23,7 @@ const ViewSwitch: React.FC = () => { const isFirstPageOfFirstModule = page === FIRST_PAGE[module] + 1 && module === Module.A_B_AB; - - const showLabToggleButton = - viewportType !== ViewType.Lab || isFirstPageOfFirstModule; + const isLabViewOpen = viewportType === ViewType.Lab; let buttonStyle: React.CSSProperties = { top: 16, @@ -50,24 +48,29 @@ const ViewSwitch: React.FC = () => { ) : ( ) } - /> + > + {/* on the first page we show text, but on other pages we just show the icon */} + {isFirstPageOfFirstModule + ? `${isLabViewOpen ? "Simulation" : "Lab"} view` + : null} + - {viewportType === ViewType.Lab ? : null} + {isLabViewOpen ? : null} = (props) => { }; interface OverlayButtonProps extends React.ComponentProps { + active?: boolean; children?: React.ReactNode; style?: React.CSSProperties; } export const OverlayButton: React.FC = (props) => { - const { style } = props; + const { active, style } = props; let buttonStyle: React.CSSProperties = { position: "absolute", zIndex: zStacking.viewerOverlay, @@ -89,5 +90,12 @@ export const OverlayButton: React.FC = (props) => { if (style) { buttonStyle = { ...buttonStyle, ...style }; } - return ; + return ( + + ); }; diff --git a/src/components/shared/button-library.module.css b/src/components/shared/button-library.module.css index 86c429e..c109d9f 100644 --- a/src/components/shared/button-library.module.css +++ b/src/components/shared/button-library.module.css @@ -37,6 +37,17 @@ transition: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); } +/* Filled-in state for a toggle-style overlay button, e.g. to show it's currently active/open */ +.tertiary.active { + background: var(--light-purple) !important; + border-color: var(--light-purple) !important; + color: var(--background-color) !important; +} + +.tertiary.active :global(.custom-icon) { + color: var(--background-color) !important; +} + .icon { width: 12px !important; min-width: 12px !important; diff --git a/src/content/HighAffinity.tsx b/src/content/HighAffinity.tsx index ceea7cc..e224636 100644 --- a/src/content/HighAffinity.tsx +++ b/src/content/HighAffinity.tsx @@ -154,8 +154,8 @@ export const highAffinityContentArray: PageContent[] = [ ), callToAction: ( <> - When you're done observing the color in the Lab view,{" "} - pause the simulation to move on. + When you're done observing the color changes to the solution in + the cuvette, pause the simulation to move on. ), From f75a61eb360c2e91d26fcf2ae200cd38e9505b13 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 11 Aug 2026 15:59:38 -0700 Subject: [PATCH 15/18] remove docs --- .../plans/2026-05-26-lab-view-inset-popup.md | 298 ------------------ .../2026-05-26-lab-view-inset-popup-design.md | 79 ----- .../2026-08-11-icon-only-lab-toggle-design.md | 74 ----- 3 files changed, 451 deletions(-) delete mode 100644 docs/superpowers/plans/2026-05-26-lab-view-inset-popup.md delete mode 100644 docs/superpowers/specs/2026-05-26-lab-view-inset-popup-design.md delete mode 100644 docs/superpowers/specs/2026-08-11-icon-only-lab-toggle-design.md diff --git a/docs/superpowers/plans/2026-05-26-lab-view-inset-popup.md b/docs/superpowers/plans/2026-05-26-lab-view-inset-popup.md deleted file mode 100644 index 9823276..0000000 --- a/docs/superpowers/plans/2026-05-26-lab-view-inset-popup.md +++ /dev/null @@ -1,298 +0,0 @@ -# Lab View Inset Popup Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Convert `LabView` to render as a small inset popup overlaid on the simulation viewer rather than replacing the full center area, except on the intro page (Module A_B_AB page 1) which retains full-screen behavior. - -**Architecture:** `LabView.tsx` derives `isIntroPage` from context and conditionally renders either a compact inset panel or the existing full-screen layout. The inset uses a CSS `::before` pseudo-element with `filter: blur()` for the blurred lab background. `ScaleBar` receives a `className` prop so its container's absolute positioning can be overridden for the inset's flex layout. - -**Tech Stack:** React, TypeScript, CSS Modules (`classnames` package already in use) - ---- - -### Task 1: Add `className` prop to `ScaleBar` - -**Files:** -- Modify: `src/components/ScaleBar.tsx` - -- [ ] **Step 1: Update `ScaleBarProps` and forward `className` to the container div** - -Replace the file contents of `src/components/ScaleBar.tsx` with: - -```tsx -import React, { useContext } from "react"; -import classNames from "classnames"; - -import styles from "./scalebar.module.css"; -import { MICRO } from "../constants"; -import { SimulariumContext } from "../simulation/context"; - -interface ScaleBarProps { - productColor: string; - className?: string; -} - -const ScaleBar: React.FC = ({ productColor, className }) => { - const { maxConcentration } = useContext(SimulariumContext); - const labelArray = []; - const interval = maxConcentration / 5; - for (let i = maxConcentration; i >= 0; i = i - interval) { - labelArray.push(i); - } - return ( -
-
- {labelArray.map((i) => ( -
- {i} {MICRO}M -{" "} -
- ))} -
-
-
- ); -}; - -export default ScaleBar; -``` - -The only changes from the original: added `import classNames from "classnames"`, added `className?: string` to props, and changed `className={styles.container}` to `className={classNames(styles.container, className)}`. - -- [ ] **Step 2: Verify TypeScript compiles** - -Run: `npx tsc --noEmit` -Expected: no errors. - -- [ ] **Step 3: Verify existing lab view still works** - -Run: `npm run dev` -Navigate to a page with "Lab view" and confirm the full-screen lab view still renders correctly with the scale bar visible. - -- [ ] **Step 4: Commit** - -```bash -git add src/components/ScaleBar.tsx -git commit -m "feat: add optional className prop to ScaleBar" -``` - ---- - -### Task 2: Add inset CSS classes to `labview.module.css` - -**Files:** -- Modify: `src/components/labview.module.css` - -- [ ] **Step 1: Append the inset styles** - -Add the following to the end of `src/components/labview.module.css`: - -```css -/* ── Inset panel (all pages except intro) ──────────────── */ - -.inset { - position: absolute; - top: 16px; - right: 16px; - width: 210px; - border: 1px solid var(--app-border-color); - overflow: hidden; - z-index: var(--bottom); -} - -/* Blurred lab background image behind inset content */ -.inset::before { - content: ""; - position: absolute; - inset: 0; - background-image: url("../assets/ipub-bg_4.png"); - background-size: cover; - background-position: center; - filter: blur(3px); - transform: scale(1.1); /* prevents blurred edges from bleeding to border */ - z-index: 0; -} - -.insetLabel { - position: relative; - z-index: 1; - padding: 6px 8px 4px; - font-size: 12px; - font-weight: 600; - color: var(--primary-color); - border-bottom: 1px solid var(--app-border-color); -} - -.insetBody { - position: relative; - z-index: 1; - display: flex; - flex-direction: row; - align-items: flex-end; - padding: 8px; - gap: 8px; - /* 166px = scale bar height; 24px = top+bottom padding */ - height: 190px; -} - -/* Override ScaleBar's absolute positioning so it participates in flex layout */ -.scaleBarInset { - position: static !important; - bottom: unset !important; - right: unset !important; -} - -.insetCuvette { - width: 70px; - flex-shrink: 0; -} -``` - -The `!important` on `.scaleBarInset` is required because `ScaleBar`'s own CSS module class (`.container`) and this override class have identical CSS specificity — source order alone cannot guarantee which wins. - -- [ ] **Step 2: Verify no CSS compile errors** - -Run: `npm run dev` -Expected: dev server starts without errors. - ---- - -### Task 3: Update `LabView.tsx` to render inset or full-screen - -**Files:** -- Modify: `src/components/LabView.tsx` - -- [ ] **Step 1: Replace the full contents of `src/components/LabView.tsx`** - -```tsx -import Rainbow from "rainbowvis.js"; -import { useContext, useMemo } from "react"; -import { SimulariumContext } from "../simulation/context"; -import Cuvette from "./icons/Cuvette"; -import styles from "./labview.module.css"; -import classNames from "classnames"; -import ScaleBar from "./ScaleBar"; -import { Module } from "../types"; - -const LabView: React.FC = () => { - const { - currentProductionConcentration, - maxConcentration, - page, - getAgentColor, - productName, - module, - } = useContext(SimulariumContext); - const color = getAgentColor(productName); - const colorGradient = useMemo(() => { - const rainbow = new Rainbow(); - rainbow.setSpectrum("#FFFFFF", color); - return rainbow; - }, [color]); - const position = (currentProductionConcentration / maxConcentration) * 100; - const isIntroPage = page === 1 && module === Module.A_B_AB; - - if (!isIntroPage) { - return ( -
-
In the wet lab
-
- -
- -
-
-
- ); - } - - return ( -
-
- -
-
- ); -}; - -export default LabView; -``` - -Changes from the original: -- `VisibilityControl` import removed — it was only used to hide the `ScaleBar` on the intro page; the `isIntroPage` branch now handles that by simply not rendering `ScaleBar` in full-screen mode. -- Full-screen branch always applies `styles.top` (z-index 200, above side panels) — the conditional was `page === 1 && module === Module.A_B_AB`, which is always true when `isIntroPage` is true. -- Inset branch renders a compact panel with label, `ScaleBar`, and `Cuvette`. - -- [ ] **Step 2: Verify TypeScript compiles** - -Run: `npx tsc --noEmit` -Expected: no errors. - -- [ ] **Step 3: Visual verification — inset mode** - -Run: `npm run dev` - -Navigate to any page **after** the intro page and click "Lab view": -1. Molecular simulation remains visible and running in the background ✓ -2. Inset panel appears in the upper-right corner of the center viewer ✓ -3. "In the wet lab" label appears at the top of the panel ✓ -4. Scale bar (concentration labels + color gradient) is visible in the panel ✓ -5. Cuvette is visible and updates color as concentration changes ✓ -6. Blurred lab background image is visible behind the panel content ✓ -7. Clicking "Molecular view" dismisses the panel ✓ - -- [ ] **Step 4: Visual verification — intro page full-screen mode** - -Navigate to page 1 of Module 1 (first module, first page). -The full-screen lab background covers the center area (existing behavior). No "In the wet lab" label. No inset panel. ✓ - -- [ ] **Step 5: Tune visual values if needed** - -The following values in `labview.module.css` can be adjusted to match the design: - -| Property | Location | Default | What it controls | -|---|---|---|---| -| `width` | `.inset` | `210px` | Overall inset panel width | -| `filter: blur(Xpx)` | `.inset::before` | `3px` | Background image blur intensity | -| `height` | `.insetBody` | `190px` | Body height (should match scale bar height + padding) | -| `width` | `.insetCuvette` | `70px` | Cuvette width (height scales proportionally) | - -Re-verify after any adjustments. - -- [ ] **Step 6: Commit** - -```bash -git add src/components/LabView.tsx src/components/labview.module.css -git commit -m "feat: convert lab view to inset popup overlay" -``` - ---- - -## Self-Review - -**Spec coverage:** -- ✅ Inset popup, upper-right corner of center viewer -- ✅ Molecular simulation visible and running behind inset -- ✅ "In the wet lab" label in inset -- ✅ Scale bar + cuvette in inset -- ✅ Blurred lab background image in inset (experimenting with `blur(3px)`, tunable) -- ✅ Intro page (page 1, Module A_B_AB) retains full-screen behavior unchanged -- ✅ Toggle controlled by existing "Lab view" button — no close button on popup -- ✅ No animation - -**Placeholder scan:** No TBDs. The visual tuning step (Task 3, Step 5) is intentional — blur intensity and panel sizing are aesthetic decisions best made against the running app. - -**Type consistency:** `ScaleBarProps.className?: string` defined in Task 1, passed as `styles.scaleBarInset` (a CSS Modules class reference, type `string`) in Task 3. Consistent. - -**Potential issue — `classnames` in ScaleBar:** The original `ScaleBar.tsx` did not import `classnames`. Task 1 adds the import. The package is already a dependency (`classnames` is used in multiple other components), so no `npm install` needed. diff --git a/docs/superpowers/specs/2026-05-26-lab-view-inset-popup-design.md b/docs/superpowers/specs/2026-05-26-lab-view-inset-popup-design.md deleted file mode 100644 index 49e4246..0000000 --- a/docs/superpowers/specs/2026-05-26-lab-view-inset-popup-design.md +++ /dev/null @@ -1,79 +0,0 @@ -# Lab View Inset Popup - -**Date:** 2026-05-26 -**Branch:** feature/hover-lab-view - -## Problem - -When the user switches to "Lab view", the full-screen `LabView` component covers the entire center viewer, replacing the molecular simulation. The user cannot see the cuvette and the simulation at the same time. - -## Goal - -Show the cuvette as a small inset panel overlaid on the simulation viewer, so both are visible simultaneously. The intro page retains its existing full-screen cuvette behavior. - -## Approach - -Extend `LabView.tsx` to support two visual modes — full-screen and inset — derived from existing context values. No new components. `ViewSwitch.tsx` changes minimally. - -## Design - -### Mode switching - -`LabView` derives `isInset` from context: - -``` -isInset = viewportType === ViewType.Lab - && !(page === 1 && module === Module.A_B_AB) -``` - -- `isInset = false` → existing full-screen behavior (intro page only) -- `isInset = true` → inset panel (all other pages when lab view is active) - -### ViewSwitch changes - -The `Viewer` already renders unconditionally (it is last in the JSX stack). No change needed there. `LabView` continues to render when `viewportType === ViewType.Lab`. - -### Inset panel layout - -| Property | Value | -|---|---| -| Position | `absolute`, `top: 16px`, `right: 16px` | -| Width | ~160–180px (enough for scale bar + cuvette side-by-side) | -| Background | `ipub-bg_4.png` with blur applied (pseudo-element or filter) so the cuvette remains legible | -| Border | `1px solid var(--app-border-color)` | -| Label | "In the wet lab" text at top of panel | -| Contents | `ScaleBar` (left) + `Cuvette` (right) — components unchanged | -| z-index | `var(--bottom)` (z=10) — same as current full-screen container; already sufficient to sit above the `Viewer` but below side panels (z=100) | - -The blur intensity and opacity on the background image should be tuned during implementation so the cuvette and scale bar are clearly readable. - -### CSS changes - -`labview.module.css` gains an `inset` class. The background image and full-screen sizing styles remain on `container` only and are not applied when rendering in inset mode. - -``` -.inset { - position: absolute; - top: 16px; - right: 16px; - width: ~170px; - border: 1px solid var(--app-border-color); - overflow: hidden; - /* background image with blur via pseudo-element */ -} -``` - -### What does not change - -- `ScaleBar` and `Cuvette` components — no modifications -- The "Lab view" / "Molecular view" toggle button — behavior unchanged -- Intro page full-screen behavior — unchanged -- No animation on show/hide - -## Files to change - -| File | Change | -|---|---| -| `src/components/LabView.tsx` | Add `isInset` logic; conditional render of label and inset vs full-screen container | -| `src/components/labview.module.css` | Add `.inset` class with panel sizing, border, blurred background | -| `src/components/ViewSwitch.tsx` | Likely no change required | diff --git a/docs/superpowers/specs/2026-08-11-icon-only-lab-toggle-design.md b/docs/superpowers/specs/2026-08-11-icon-only-lab-toggle-design.md deleted file mode 100644 index dabb196..0000000 --- a/docs/superpowers/specs/2026-08-11-icon-only-lab-toggle-design.md +++ /dev/null @@ -1,74 +0,0 @@ -# Icon-Only Lab Toggle Button - -**Date:** 2026-08-11 -**Branch:** feature/hover-lab-view - -## Problem - -The lab-view toggle button (`ViewSwitch.tsx`) and the "In the wet lab" inset panel (`LabView.tsx`) are both anchored to `top: 16px; right: 16px`, so they render stacked in the same corner of the viewer and visually overlap instead of the panel sitting cleanly below the button. - -## Goal - -- The inset panel sits below the toggle button with a small gap, no overlap. -- The toggle button shows its icon only — no "Lab view" / "Simulation view" text. - -## Approach - -Two small, independent changes, no new components: - -1. Drop the button's text children, matching the icon-only `OverlayButton` pattern already used by `PlayButton.tsx`. -2. Move the inset panel's `top` offset down to clear the now-smaller button. - -## Design - -### Toggle button (`ViewSwitch.tsx`) - -Remove the text children currently rendered inside the `OverlayButton`: - -```tsx -{viewportType === ViewType.Lab ? "Simulation" : "Lab"} view -``` - -Leaving only the `icon` prop (`` / ``), the same shape as: - -```tsx - -``` - -in `PlayButton.tsx`. Ant Design's `Button` reduces to a compact square when it has an `icon` and no children, so no new sizing props are needed. Position (`top: 16, right: 16`, or centered on the intro page) is unchanged. - -### Inset panel (`labview.module.css`) - -Change `.inset`'s `top` from `16px` to `64px` (16px original offset + ~40px button height + 8px gap), keeping `right: 16px` so the panel remains right-aligned under the button. - -```css -.inset { - position: absolute; - top: 64px; - right: 16px; - ... -} -``` - -### Why the intro page is unaffected - -The panel and the centered button never render at the same time: both are gated by the same `page === 1 && module === Module.A_B_AB` condition (`isIntroPage` in `LabView.tsx`, `isFirstPageOfFirstModule` in `ViewSwitch.tsx`), just on opposite branches. When the button is centered, `LabView` renders its full-screen cuvette, not the inset panel. So the new `top: 64px` offset only ever applies alongside the top-right anchored button. - -### What does not change - -- Inset panel content — title text ("In the wet lab"), close button, `ScaleBar`, `Cuvette` — unchanged -- Intro page full-screen cuvette behavior — unchanged -- Button click behavior, icons used (`Molecules` / `LabIcon`) — unchanged - -## Files to change - -| File | Change | -|---|---| -| `src/components/ViewSwitch.tsx` | Remove text children from the `OverlayButton`, leaving icon-only | -| `src/components/labview.module.css` | Change `.inset`'s `top` from `16px` to `64px` | - -## Testing - -Visual check only: -- Toggle button renders icon-only in both Lab and Simulation states -- Inset panel sits below the button with a visible gap, no overlap, on a non-intro page From 0d1dfc3f5cda27510a64f8c2c9a91ab0360f6dc6 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 11 Aug 2026 16:05:37 -0700 Subject: [PATCH 16/18] styling changes --- src/components/labview.module.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/labview.module.css b/src/components/labview.module.css index f83ac37..1e1e861 100644 --- a/src/components/labview.module.css +++ b/src/components/labview.module.css @@ -29,12 +29,13 @@ .inset { position: absolute; - top: 64px; + top: 58px; right: 16px; width: 210px; border: 1px solid var(--app-border-color); overflow: hidden; z-index: var(--bottom); + border-radius: 4px; } /* Blurred lab background image behind inset content */ From 133ad3103142ea9075d3b35213ab7853700875fe Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 11 Aug 2026 16:13:33 -0700 Subject: [PATCH 17/18] alphabetize --- src/components/ScaleBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ScaleBar.tsx b/src/components/ScaleBar.tsx index 71a31f8..0915564 100644 --- a/src/components/ScaleBar.tsx +++ b/src/components/ScaleBar.tsx @@ -10,7 +10,7 @@ interface ScaleBarProps { className?: string; } -const ScaleBar: React.FC = ({ productColor, className }) => { +const ScaleBar: React.FC = ({ className, productColor }) => { const { maxConcentration } = useSimulariumSimulation(); const labelArray = []; const interval = maxConcentration / 5; From 525de1b458c13c8705719443cceb17889f64cf96 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 11 Aug 2026 16:15:12 -0700 Subject: [PATCH 18/18] alphabetize --- src/components/LabView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LabView.tsx b/src/components/LabView.tsx index e8d68da..d3f5a6a 100644 --- a/src/components/LabView.tsx +++ b/src/components/LabView.tsx @@ -17,7 +17,7 @@ const LabView: React.FC = () => { getAgentColor, productName, } = useSimulariumSimulation(); - const { page, module, setViewportType } = useSimulariumUi(); + const { module, page, setViewportType } = useSimulariumUi(); const color = getAgentColor(productName); const colorGradient = useMemo(() => { const rainbow = new Rainbow();