Skip to content

Refactor desktop codebase to reduce LOC and dead code - #4

Open
radroid wants to merge 221 commits into
mainfrom
refactor/reduce-loc
Open

Refactor desktop codebase to reduce LOC and dead code#4
radroid wants to merge 221 commits into
mainfrom
refactor/reduce-loc

Conversation

@radroid

@radroid radroid commented Apr 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • Reduce code size across the Mac desktop app by inlining one-off helpers, collapsing repetitive style objects, and removing dead/unused code paths.
  • Simplify shared UI internals (window, window-manager, menu/context helpers, and app registry wiring) without changing product behavior.
  • Clean up supporting docs/config by removing stale planning artifacts and obsolete files while keeping the branch focused on maintainability.

Test plan

  • Run pnpm lint
  • Run pnpm typecheck
  • Smoke test desktop apps (Finder, Music, Browser, Notepad, World Map) for behavioral parity

Made with Cursor

radroid and others added 30 commits April 8, 2026 21:21
…lans

CLAUDE.md: correct boot description, BootSequence→BootScreen component name,
and .woff2→.woff font path. Add pointer to DESKTOP-PLAN.md.

DESKTOP-PLAN.md: fix container-type (inline-size→size for cqh support),
correct Phase 4 branching (apps branch from mac-os-1984-desktop, not
pre-app-foundation), fix branch tree diagram, drop redundant isSelected
from WindowState, collapse isMaximized two-step into direct context usage.

MOBILE-PLAN.md: initial planning document for iPhone OS 1 mobile experience.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…mobile gate

- Window positions stored as container percentages (0–1) instead of pixels
  so they scale automatically on maximize toggle
- isMaximized state lifted from WindowManager context to page.tsx,
  passed as props to IMacG3Frame and Desktop
- Maximize trigger simplified to chin button only (removed menu bar option)
- Clarified mobile never advances past welcome phase
- Drag listeners attached to document to prevent fast-drag cursor loss

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
POSTHOG-RESEARCH.md: architectural reference notes on PostHog.com's
desktop-OS implementation (state model, window chrome, desktop shell,
styling primitives). Compiled via parallel subagent exploration of
the posthog.com repo.

DESKTOP-PLAN.md updates inspired by the research:
- Derived focused window (no stored activeWindowId) — top of z-stack
  IS the focused window via useMemo
- Contiguous zIndex reshuffle on focusApp (no counter drift)
- 5px click-vs-drag threshold on draggable title bars
- Lazy content mount after window entry animation
- Zoom-from-origin open animation using clicked-icon Rect
- Cascade-on-open position spec and close-normalizes-zIndex rule
- Menu bar z-index sits above window stack
- Reduced-motion covers maximize + open + close animations
- Inspiration & Anti-patterns section (7 patterns adopted, 9 rejected,
  4 future enhancements)
- Readiness Checklist marking PR #1 ready and app content blockers

CLAUDE.md: note that dev server is always running and should not
be started by automation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces the `'desktop'` screen phase and maximize mode as the
foundation for the interactive Mac OS 1984 desktop experience.

Screen phases:
- ScreenPhase gains `'desktop'`
- Welcome screen renders without its menu bar (menu bar moves to the
  desktop phase in Phase 2+3)
- Desktop viewports auto-advance from `'welcome'` → `'desktop'` after
  a brief hold; mobile stays on welcome permanently

Maximize mode:
- `isMaximized` state lifted to `page.tsx`, passed to IMacG3Frame and
  CRTScreen as props (per DESKTOP-PLAN architecture)
- Chin button on the iMac frame toggles maximize; a floating Restore
  button appears when maximized so the control stays reachable after
  the chin hides
- IMacG3Frame hides body/chin/stand when maximized; CRTScreen expands
  to fill the viewport
- CRTScreen gains `container-type: size` to enable cqw/cqh-based
  window sizing in later phases
- All transitions respect prefers-reduced-motion

Containing-block fix:
- The initial intro scale animation is released after 700ms (new
  `introDone` state), so a persistent `transform: scale(1)` no longer
  creates a containing block that traps the maximized IMacG3Frame's
  `position: fixed, inset: 0`.

Assets (user-provided):
- public/app-icons/: 1-bit SVGs for calculator, control-panel, finder,
  journal, music, notepad, trash, world-map (browser icon pending)
- public/apple-icon.svg
- public/cv/Raj_Dholakia_Resume_FullStack.pdf

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces the full interactive desktop — draggable windows,
placeholder apps, icon grid, and a dynamic menu bar that adapts to
the focused app. This is the last piece of the foundation PR before
app PRs can branch off in parallel.

Window manager (app/components/desktop/window-manager.tsx)
- React Context holding a windows record, selectedIconId, and an
  hasOpenedAnyApp flag
- Derived activeWindowId via useMemo — top of z-stack is the focused
  window; no separate focus field to sync
- focusApp does a contiguous zIndex reshuffle (no counter drift)
- closeApp re-normalizes zIndex so gaps never appear
- Cascade-on-open: first window at {12%, 14%}, +3% per next, wraps
  back to base past {60%, 55%}
- moveWindow clears fromOrigin so the zoom animation can't replay

App registry (app/components/desktop/app-registry.tsx)
- Typed AppDefinition/MenuConfig/MenuItem/Rect
- All 8 apps registered with the user-provided 1-bit SVG icons
  (browser.svg now included)
- clamp() + cqw/cqh sizes per the DESKTOP-PLAN
- Shared ComingSoon placeholder component until Phase 4 replaces it
- FINDER_DEFAULT_MENUS exported for the fallback menu bar state

Window component (app/components/desktop/window.tsx)
- Title bar with striped-active pattern, close box with an inline-SVG
  X, focus-on-click
- Drag uses direct DOM transform: translate3d() during the move,
  rAF-throttled; only the final position is committed via moveWindow
  on mouseup. Avoids re-rendering every context consumer per frame.
- 5px click-vs-drag threshold so taps don't jitter the window
- One-frame `transition: none` skip on drag commit so the window
  snaps to the new position without a ghost animation
- Zoom-from-origin open animation: first render at the clicked icon's
  CRT-relative rect, then flipped to the cascaded position on the
  next frame so CSS interpolates the delta
- Lazy content mount — app component only renders after the entry
  animation completes (skipped under prefers-reduced-motion)
- Drag is constrained to the CRT screen bounds via container ref

DesktopIcon (app/components/desktop/desktop-icon.tsx)
- Single click selects (invert colors), double click opens and
  captures the icon's rect for the zoom-from-origin animation
- `large` prop scales the icon/label up when the desktop is maximized

Desktop shell (app/components/desktop/desktop.tsx)
- Wraps children in WindowManagerProvider
- 2-column icon grid in the top-right so all 8 apps fit in the small
  CRT; icons scale up in maximize mode
- Decorative Trash bottom-right with pointer-events: none
- Windows layer renders every open window; click on bare desktop
  deselects the active icon

MaximizeNudge (app/components/desktop/maximize-nudge.tsx)
- Small Mac-style tip dialog that appears once after the first app
  opens, suggesting Full Screen. Auto-dismisses when user maximizes
  or clicks Not Now. Persisted to sessionStorage.

Menu bar refactor (app/components/menu-bar.tsx)
- Now reads derived activeWindowId from WindowManagerContext
- Apple-glyph menu always leftmost (SVG apple)
- Falls back to Finder defaults when no window is focused
- Per-app menuItems (when provided) override the defaults
- z-index 9999 so dropdowns cover windows
- No longer mounted in welcome-screen.tsx

page.tsx
- Desktop phase now renders <Desktop /> (the real shell) instead of
  the Phase 1 placeholder text
- Under-construction indicator removed from the welcome screen

Assets
- public/app-icons/browser.svg added to round out all 8 app icons

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Trash was previously a decorative element pinned bottom-right with
pointer-events: none. It's now a real entry in APP_REGISTRY with a
Coming Soon placeholder component and a Finder-style status bar.

Desktop shell renders Trash as a normal DesktopIcon at bottom-right
(filtered out of the top-right 2-column grid so layout is preserved),
so single-click selects and double-click opens with the usual
zoom-from-origin animation.

DESKTOP-PLAN.md Decision #11 updated to match — Trash is clickable
but not functional beyond opening.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scaffolds the shadcn/ui context-menu component (app/components/ui/
context-menu.tsx) and wires it into desktop icons and the bare
desktop surface.

shadcn component
- Installed via `shadcn add context-menu` which added
  @radix-ui/react-context-menu to package.json
- File rewritten to drop the lucide-react dependency and the
  Tailwind v3 theme tokens (bg-popover, rounded-md, animate-in, …)
  that don't exist in our Tailwind v4 alpha setup
- Same export surface as stock shadcn, restyled inline with Mac OS 1
  aesthetics: Chicago font, white bg, 1px black border, 2px black
  drop shadow, text glyphs (▶, ✓, ●) in place of lucide icons
- Hover/keyboard-focus highlighting driven by Radix's data-highlighted
  attribute via a small rule in global.css

Right-click handlers
- DesktopIcon: App name label → Open, Get Info, Duplicate, Move to
  Trash. Right-click also selects the icon so it shows the highlight
  state before the menu opens. Icon is split into DesktopIcon
  (handles context menu + data) and IconButton (forwardRef button
  used by ContextMenuTrigger asChild).
- Desktop surface: Curly OS label → Go Full Screen / Restore Screen
  (live-toggles based on isMaximized), Clean Up Desktop, Change
  Wallpaper, About This Macintosh. Wrapped the desktop container in
  ContextMenu/ContextMenuTrigger so empty-area right-clicks work.

Icon highlight fixes
- whiteSpace: normal on the label so two-word names (Curly Browser,
  Control Panel, Note Pad, World Map) wrap at the space and the
  black selection background covers the whole label instead of
  overflowing the button width
- outline: none on the icon button so the browser's default blue
  focus ring doesn't appear after a right-click
- Removed the filter: invert(1) on icon images when selected — the
  label highlight alone is enough; the icon art stays untouched

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Boot icon
- SIZING.macIconSize reduced ~30% (100 → 70 desktop, 60 → 42 mobile)
  so the Happy Mac on the boot screen sits more comfortably in the
  CRT frame.

Chin toggle
- The chin used to be stacked (CD slot on top, separate circular
  button below) and the button was only rendered on welcome/desktop
  phases. This caused a visible chin-height shift when the screen
  transitioned out of boot. Now there is no separate button — the
  CD slot itself IS the maximize toggle. Always rendered (disabled
  during boot) so the chin height is constant across phases.
- Hover brightens the slot with a soft white glow to hint at
  interactivity without breaking the iMac hardware aesthetic.
- aria-label flips between "Maximize screen" and "Restore screen".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Foundation: Mac OS 1984 desktop — maximize, windows, shell, menus, context menus
Lock in Browser (bookmarks launcher, no iframes), Finder (public/
mirror with Mac-styled folders), World Map (react-simple-maps + 24
visited countries), and Music (Spotify live now-playing with vinyl
presentation). Update readiness checklist — all 8 apps unblocked;
Music gated only on Spotify OAuth setup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-installed on base so all app branches inherit the deps without
each needing its own install commit. react-simple-maps wraps d3-geo
and handles TopoJSON rendering; world-atlas provides the country
TopoJSON data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the original parallel-worktree-per-app strategy with a single
branch (feat/desktop-apps → feat/mac-os-1984-desktop) containing one
commit per app. The previous plan assumed subagents could run git/tsc/
gh inside isolated worktrees; in practice the session sandbox denies
Bash for subagents, so the main agent drives every git operation while
subagents write component files only.

Also document the Spotify integration architecture in App 8: refresh-
token grant for runtime (no per-URL redirect registration), one-time
OAuth helper on 127.0.0.1:8888/callback for dev-time capture, and a
per-environment env-var table (.env.local / Cloudflare Preview /
Production) so the same route.ts works everywhere.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Node 18+ ESM helper. Reads SPOTIFY_CLIENT_ID/SECRET from .env.local,
starts a loopback server on 127.0.0.1:8888, opens the browser to
Spotify's authorize endpoint, and on /callback exchanges the auth
code for a refresh token. Appends SPOTIFY_REFRESH_TOKEN to .env.local
if not already present.

The callback URL is used only for this one-time ritual — at runtime
the Music app uses the refresh token server-side via the refresh_token
grant, so no redirect URI registration is needed for Cloudflare preview
or production deploys.

Run: node scripts/spotify-oauth.mjs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Four-column grid with C / ÷ × − + operators, = spanning two rows on
the right, left-to-right chain evaluation matching the original Mac
calculator (2 + 3 * 4 = 20), division-by-zero → "Error", keyboard
shortcuts, and authentic 1-bit black/white button invert on :active.
No chrome, lives inside the generic Window.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inline SVG zigzag for the torn top edge (stretches across any width
via viewBox + preserveAspectRatio), repeating-linear-gradient for the
lined paper background, and an auto-saving textarea persisted to
localStorage key `curly-os-notepad`. Pre-populates with a welcome
note on first load; empty string is respected once the user clears.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stacked panel sections: user card (avatar, name, role, location,
contact links), live clock updating every second, system diagnostics
(browser, OS, screen resolution, window size, language, connection
type, timezone), Battery Manager readings with charging/levelchange
listeners, and derived location from Intl.DateTimeFormat timezone.
All runtime data comes from browser APIs — no network calls. Battery
section degrades gracefully on Firefox/Safari where the API is absent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Classic icon grid with folder navigation (path stack + Back button),
Mac-styled labels (Applications ← app-icons, Documents ← cv, Fonts,
Developer ← tech-icons), and an internal status bar that stays in
sync with the current folder. Double-click opens a file preview
overlay inside the Finder window: images/SVGs via <img>, audio via
<audio>, PDFs via <iframe> (browser native viewer), fonts show a
sample, unknowns fall back to "not available". Right-click provides
Open, Download, Get Info (disabled), Rename (disabled) via the
existing Shadcn ContextMenu.

Also hoists FINDER_DEFAULT_MENUS above APP_REGISTRY so the finder
entry can reference it without a temporal-dead-zone error, and drops
the unused `statusBar` fallback since the component renders its own.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
User feedback on the first live screenshot was that the content was
hard to read at the default sizing. Bumps all shell and app font
sizes up one Chicago-friendly step (~10%):

  shell
  - desktop-icon: label 10→11 / 12→13, icon image 30→33 / 44→48,
    tile width 82→90 / 96→106, gaps 2→3 / 4→5
  - window title bar: 11→12, status bar: 10→11
  - menu bar default fontSize 12→13, height 22→24

  apps
  - calculator: button 13→14, display 20→22
  - note-pad: textarea 13→14, line-height 20→22, lined-paper
    gradient bumped to match
  - control-panel: section-header / label / value 11→12,
    user name 13→14, link buttons 10→11
  - finder: folder/file labels 12→13, secondary 11→12, heading
    18→20, status bar 10→11, breadcrumb 13→14

Stays inside the 1-bit Chicago aesthetic — no hierarchy changes,
just a single step up on every hard-coded px size.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Classic Mac Scrapbook-style paged viewer. Extracted the 3 blog posts
from CONTENT-ARCHIVE.md as inline data (no markdown renderer added —
body is rendered via <pre style="white-space:pre-wrap"> for a period-
accurate look). Nav footer with ◀ / ▶ arrows disabled at the ends, a
"1 / N" page counter, and Left/Right keyboard navigation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the inline-drawn folder SVG with the user-provided 1-bit
folder asset. Keeps the Mac 1 aesthetic consistent with the rest of
the app icons and fixes the earlier too-cartoony rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The active title bar's horizontal-stripes pattern used to go
edge-to-edge and touched the close box, making focused windows look
crowded and unlike the classic Mac OS 1 reference. Restructure the
title bar into an outer white wrapper with a 2px inset and an inner
row that carries the stripes; the close box and title text sit in
their own white-padded spans so the stripes never crowd them, and
the right side mirrors the close-box cluster width so the title
stays visually centered.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The three blog posts were originally mis-dated on the previous
website — they should all be 2026. Fixes the inlined Scrapbook data
and the source CONTENT-ARCHIVE.md so anything pulling from it in the
future picks up the right year.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Control Panel avatar 56→84px so the user card reads at a glance
- Title bar stripes now run edge-to-edge horizontally (removed the
  outer left/right padding) so they meet the window border like the
  Mac OS 1 reference. Close box has 4px of white against the window
  edge and 6px against the stripes; title text has 10px cushion each
  side. Right side mirrors the close-box footprint to keep the title
  visually centered.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Marks commits 1-6 (plan, Spotify helper, Calculator, Note Pad, Control
Panel, Finder) as done, logs the extra commits surfaced from reviewing
the live preview (font/icon polish, folder icon swap, title-bar stripe
fix, blog year correction, avatar bump), and leaves Browser / World
Map / Music as the remaining work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous fix still had a white cushion span wrapping the close box,
which created a solid white block from the window edge inward before
the stripes began. In the Mac OS 1 reference, the stripes fill the
title bar edge-to-edge horizontally and the close box is an opaque
white element sitting ON the stripes, with only a tiny padding inset
(4px) from the window border.

This rewire puts the padding on the stripe container itself — since
background-image extends through padding, stripes fill the 4px inset
too, and the close box + its transparent right-side mirror sit as
opaque / transparent elements on that background. The title text
still cuts a white hole via its own background.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
No iframes. Only view is a bookmarks home with 11 tiles: 8 projects
(Penguin Mail, ARK Experience, Bridger, Stella 56 Diamonds, Playground,
Couples Budget, 75 Creates, KayVee Gems) + 3 tools (Google, Claude,
ChatGPT). Click opens in a new real-browser tab via window.open;
right-click exposes Open in New Tab + Copy Link via the existing
Shadcn ContextMenu. Disabled Back/Forward buttons and a decorative
read-only address bar match the Mac 1 chrome. Tan info banner below
the toolbar explains the "iframes get blocked" quirk up front.
Favicons pulled from Google's S2 service and pixelated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses react-simple-maps + world-atlas (TopoJSON via jsdelivr CDN) to
render the Natural Earth country borders. Visited countries match
against geo.properties.name (with both common name variants for the
USA and Czechia to cover either world-atlas naming convention).

Visited fill is a 4×4 checkerboard SVG <pattern> (two 1×1 black
rects) — the canonical Mac OS 1 50% stipple, matching the worldmap
reference aesthetic. A hidden 0×0 <svg><defs> hosts a second pattern
id so the bottom-right legend swatch (which lives in a <div> outside
the map's SVG) can resolve its url(#id) fill. Hover tooltip follows
the cursor via position: fixed (survives the window's transform
container in maximized mode).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hovering a visited country now shows an inverted tooltip — black
background, white text, with a ✓ prefix — so the "you've been there"
status is instantly visible without having to match the dither fill
against the legend. Unvisited countries keep the plain white tooltip.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drops the bottom-right legend (and the hidden legend-pattern SVG that
supported it) to reclaim that space. Switches from geoMercator to
geoEqualEarth which has a more compact aspect ratio — no wasted
polar stretch — and bumps the scale to 175 plus a small center shift
([10, 15]) so the populated latitudes fill the window. Sets
width/height to 800×400 so the viewBox matches the compact
projection shape instead of the default 800×600 which left ~200px
of empty vertical space.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
radroid added 28 commits April 17, 2026 10:41
getFilename was used once; inlined as file.path.split('/').pop(). The
middle breadcrumb branch was unreachable — pathStack only ever holds
valid folder ids (handleOpen only pushes on kind==='folder').
Removes the 3 inline <style> tags and 3 fragment wrappers from
music.tsx. The reduced-motion override is already handled globally
(animation-duration: 0.01ms) and by shouldSpin gating, so the local
@media rule was redundant. className="curly-vinyl" also dropped —
animation is driven entirely by inline style.
Trivial one-liners (handleOpen, openInNewTab, copyLink) inlined.
The inline 'opacity: 0.4, cursor: default' on disabled menu items was
inconsistent with every other disabled ContextMenuItem in the app
(finder/desktop/desktop-icon all use bare 'disabled'); global CSS
already handles the look via [data-disabled] + pointer-events: none.
…ech-icons

textareaRef was assigned but never read; the onFocus handler duplicated
outline: none already in the textarea's style.

public/tech-icons/ was empty; removed from filesystem and from the
finder folder-rename list in DESKTOP-PLAN (never implemented).
makePlaceholder was used once (for Trash); inlined as component:
() => <ComingSoon name="Trash" />. The bearer-auth + no-store options
pattern was duplicated 3 times in the spotify route; extracted as
bearerOpts(token) so multi-line fetch calls collapse to one line.
Phase 5 polish items were all marked done per commit 3939e93. The file
was never referenced from code or other docs and duplicated commit
history.
Both the <pattern> tag's 4 attrs (world-map) and the outer flex <div>
(not-found) fit comfortably on one line.
Sweep across 10 files — chicago, nav/chrome/tile base styles, content/item
styles in the context-menu wrapper, calculator btnBase, etc. All were
4–6 lines with no CSSProperties value that needed per-line separation.
Implementation step-by-step tables (Phases 1–5) and Subagent Contracts
template were execution artifacts for work now complete. Design
Decisions, Architecture, Applications specs, and Inspiration/Anti-
patterns retained. Updated ToC to match.
The PR #3 workflow steps and "Why This Strategy" rationale were
execution notes for completed work. Branch tree retained as the
canonical merge path.
Spotify route returns genres already sorted descending by count; the
client's defensive re-sort added no behavior. Pass data.genres directly.
…ne line

Across curly-browser/music/scrapbook/crt-screen/maximize-nudge/imac-frame/layout.
The verbose 'destructure + type' block form gained no clarity vs the
one-line signature.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
raj-portfolio 97931b8 Commit Preview URL

Branch Preview URL
Apr 17 2026, 07:12 PM

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