Skip to content

Latest commit

 

History

History
422 lines (315 loc) · 23.1 KB

File metadata and controls

422 lines (315 loc) · 23.1 KB

Design Context

Users

Experienced gamers planning skill tree builds for Run! Goddess. They use the app in two modes: quick mid-game checks (speed and glanceability) and deliberate pre-game planning sessions (depth and comparison). They expect expertise-level tooling — no hand-holding, no tutorials they can't skip, no dumbed-down interfaces.

Brand Personality

Minimal, Playful, Sharp — The foundation is a developer-tool level of efficiency and respect for the user's time (Linear, Raycast energy). But the surface layer should be fun — gaming-aware, with wit and juice. It knows the domain intimately and doesn't take itself too seriously. Think: a speedrunner's favorite tool.

Emotional Goals

  • Confidence & control — "I know exactly what my build does." Clarity, mastery, zero ambiguity.
  • Flow & satisfaction — "This is enjoyable to plan with." Smooth interactions, rewarding feedback loops, satisfying micro-moments.
  • Trust & efficiency — "This tool just works." Fast, reliable, no friction.

Aesthetic Direction

  • Visual tone: Clean utility with playful energy. The skeleton is minimal and purposeful (content over chrome), but interactions should feel alive — snappy transitions, satisfying state changes, moments of delight that reward engagement.
  • Theme: OKLCH-based dynamic theming from a single hue+chroma source. Dark mode default. Six presets (Sky, Cyan, Green, Rose, Amber, Neutral) plus custom picker.
  • References: Mobalytics, PoE Ninja, D2 Armor Picker (utility focus); Linear, Raycast, Arc (quality bar and interaction polish).
  • Anti-references: Generic gaming dashboards, cluttered portals, template aesthetics, AI-generated sameness.

Design Principles

  1. Content over chrome — The skill tree IS the interface. Everything else serves it.
  2. Juice without jank — Add energy and delight through motion, feedback, and micro-interactions — but never at the cost of performance or clarity. Every animation should feel purposeful.
  3. Respect the player — Assume expertise. Surface power features, don't hide them behind progressive disclosure. Keyboard shortcuts, gesture systems, and compact displays are features, not complexity.
  4. Accessible by default — Colorblind palettes, reduced motion, keyboard navigation, screen readers. Accessibility is non-negotiable and already first-class.
  5. Earned playfulness — Delight comes from polish, not decoration. A perfectly-timed spring animation > a gratuitous particle effect. The tool earns its personality through craft.

Tech Stack

  • Svelte 5 + TypeScript, hand-built CSS (no component libraries)
  • OKLCH color engine generating 100+ CSS variables from source hue/chroma
  • Phosphor icons, Inter font, vanilla CSS custom properties
  • Client-only PWA, no backend

Design Tokens

All design tokens are CSS custom properties on :root. The dynamic color tokens are generated at runtime by themeEngine.ts from a source {h, c} pair; the static layout/typography tokens live in theme.css.

Static Tokens (theme.css)

Spacing — Tight 4-point scale used for padding, gaps, and margins.

Token Value Typical use
--spacing-xs 2px Hairline gaps, kbd badge padding
--spacing-sm 4px Inner padding, compact gaps
--spacing-md 8px Standard gap between sibling elements
--spacing-lg 12px Section padding, modal padding, outer gaps

Typography — Whole-pixel rem values (at 16px root) to avoid sub-pixel rendering.

Token Value Typical use
--font-xxs 0.625rem (10px) Micro labels
--font-xs 0.6875rem (11px) Keyboard shortcut badges, small captions
--font-sm 0.8125rem (13px) Section titles, descriptions, secondary text
--font-base 0.875rem (14px) Body text, button labels, toast messages
--font-lg 1.125rem (18px) Modal headings
--font-xl 1.375rem (22px) Page-level headings
--weight-semibold 600 Section titles, toast messages
--weight-bold bold Emphasis, amounts
--leading-none 1 Button labels, single-line elements
--leading 1.3 Body text, descriptions
--tracking 0.02em Default letter spacing
--tracking-wide 0.06em Uppercase section titles

Border radius — Three tiers plus a pill.

Token Value Use
--radius-sm 6px Toasts, kbd badges, inner elements
--radius 12px Buttons, modals, cards, accordions, toggles
--radius-lg 19px HUD pill displays (half of height)
--radius-full 999px Circular/pill shapes, scrollbar thumbs

Backdrop blur

Token Value
--blur-xs 2px
--blur-sm 4px
--blur-md 8px (modal backdrops)
--blur-lg 12px

Transitions — Named easing curves. Components use these for all motion.

Token Value Use
--ease 0.2s ease Default hover/active transitions
--ease-emphasis 0.3s cubic-bezier(0.16, 1, 0.3, 1) Expand/collapse, spring-like
--ease-decel 0.2s cubic-bezier(0.05, 0.7, 0.1, 1) Entry animations (toast, fab)
--ease-accel 0.15s cubic-bezier(0.3, 0, 0.8, 0.15) Exit animations
--ease-standard 0.2s cubic-bezier(0.2, 0, 0, 1) Arrow rotations, general motion

Other

Token Value
--border-width 1px
--opacity-disabled 0.5

Dynamic Color Tokens (themeEngine.ts)

Generated at runtime from a source ThemeSource { h, c, l? }. All colors are computed in OKLCH and emitted as hex values. Dark and light mode produce different lightness values from the same hue+chroma.

Neutral surfaces — Tinted with the source hue at reduced chroma.

Token Dark L Light L Use
--bg 0.10 0.97 Page background
--bg-panel 0.14 0.94 Side menu, modal backgrounds
--bg-input 0.18 0.91 Input fields, header areas
--surface 0.22 0.88 Accordion body, elevated content
--bg-raised 0.28 0.84 Buttons, toasts, card backgrounds
--bg-modal 0.24 0.86 Modal shell background
--border-subtle 0.34 0.78 Low-emphasis borders
--border 0.42 0.68 Standard borders

Text

Token Dark L Light L Use
--text 0.93 0.18 Primary content
--text-muted 0.78 0.38 Secondary labels, descriptions
--text-disabled 0.55 0.55 Disabled controls, subtle hints

Accent — Full source chroma, the most vibrant color in the palette.

Token Use
--accent Primary accent: active states, progress fills, selected indicators
--accent-light Brighter variant: gradient endpoints, open-state arrow icons
--border-focus Focus rings on interactive elements

Tinted containers — Accent-colored surface/text pair for highlighted regions.

Token Use
--bg-tinted Accent-tinted background (keyboard shortcut flash, selected chips)
--text-on-tinted Text color readable against --bg-tinted

Semantic: Danger — Warm red/orange, lightly harmonized toward theme hue.

Token Use
--accent-danger Danger accent (destructive button text, error icons)
--danger-bg Danger container background
--danger-border Danger container border
--danger-text Text on danger containers

Semantic: Success — Theme hue at boosted chroma (neutral themes use blue).

Token Use
--accent-success Success accent
--success-bg Success container background
--success-border Success container border
--success-text Text on success containers

Region palettes — Fixed-hue palettes for the three skill tree regions. Not harmonized to theme. Each region (orange, yellow, blue) gets:

  • --region-{name}-accent — Region accent color
  • --region-{name}-light — Brighter variant
  • --region-{name}-bg-available / bg-active / bg-maxed — Node state backgrounds
  • --region-{name}-text / text-maxed — Region text colors

Colorblind mode shifts orange to red-orange (hue 20) and yellow to cleaner yellow (hue 105) for wider separation.

Node states

Token Use
--node-locked-bg/border/text Locked (unlearnable) nodes
--node-flash-color Brief flash on level change
--node-badge-text Level badge text (hard white/dark)

Shadows — Dark mode uses --bg hex with alpha; light mode uses rgba(0,0,0,...).

Token Use
--shadow Standard elevation (toasts, dropdowns)
--shadow-sm Subtle elevation
--shadow-lg Deep elevation (modals)
--shadow-node Skill tree nodes
--shadow-node-hex Hex grid nodes
--shadow-lateral Side menu lateral shadow
--shadow-inset Inset depth
--shadow-drop-icon CSS filter: drop-shadow for icons

Overlays

Token Use
--backdrop-overlay Modal backdrop (rgba 0.35-0.4 black)
--backdrop-overlay-heavy Action sheet backdrop (rgba 0.5-0.55 black)
--backdrop-overlay-context Context menu backdrop (rgba 0.25 black)

Mode-dependent filters

Token Dark Light Use
--brightness-hover brightness(1.12) brightness(0.92) Hover/active brightness shift
--node-brightness-locked brightness(0.7) brightness(1.1) saturate(0.3) Locked node dimming
--shadow-text Heavy multi-shadow Lighter multi-shadow Text shadow on tree nodes

Layout Tokens (app.css)

Token Value Use
--bar-pad = --spacing-lg HUD safe-area padding
--tab-height 38px Bottom nav bar tab height
--safe-top/right/bottom/left env(safe-area-inset-*) Device safe areas

Z-index scale — Strict layering, no arbitrary values.

Token Value Layer
--z-index-hud 5 HUD controls
--z-index-side-menu 10 Side menu panel
--z-index-context-menu-backdrop 18 Context menu backdrop
--z-index-context-menu 20 Context menus
--z-index-modal 30 Modals
--z-index-toast 40 Toast notifications
--z-index-tooltip 50 Tooltips (topmost)

Design Patterns

Recurring visual and interaction conventions used across the codebase. These are not abstracted into shared utilities — they are hand-applied per component. This section documents the conventions so new components match existing ones.

Interactive Element Pattern

Almost every tappable/clickable element follows this exact recipe:

  1. Base: background: var(--bg-raised), border: var(--border-width) solid var(--border), border-radius: var(--radius), color: var(--text-muted).
  2. Hover (mouse only): @media (hover: hover) { :hover { filter: var(--brightness-hover); } } — brightness shift, never a background color swap.
  3. Active/pressed: filter: var(--brightness-hover); transform: scale(0.96); — a subtle press-in with the same brightness as hover. Some components use scale(0.97) or scale(0.985) for lighter press feel.
  4. Focus-visible: outline: 2px solid var(--border-focus); outline-offset: 2px; — always 2px solid, always --border-focus, always 2px offset. Some inset contexts use outline-offset: -2px instead.
  5. Disabled: opacity: var(--opacity-disabled); cursor: not-allowed; border-color: var(--border-subtle); background: var(--bg-input); color: var(--text-disabled); filter: none; transform: none;
  6. Tap highlight: -webkit-tap-highlight-color: transparent; on all custom interactive elements.

The global focus policy (app.css) strips outline from all elements, then re-applies it only to text inputs via :focus-visible. Component-level :focus-visible rules handle buttons, links, and custom controls individually.

Accent-Tinted Background Pattern

A consistent way to add theme-color warmth to surfaces without making them feel like accent buttons. Uses color-mix():

  • Subtle tint (~6-8%): color-mix(in srgb, var(--accent) 8%, var(--bg-raised)) — open accordion headers, collapsible section open states, code blocks.
  • Medium tint (~12-20%): color-mix(in srgb, var(--accent) 18%, var(--border)) — onboarding cards, scrollbar thumbs, selected segment backgrounds.
  • Strong tint (~50%+): color-mix(in srgb, var(--accent) 55%, var(--border)) — accent-blended border used for modal shells, scrollbar thumbs on hover, active left-accent borders.

The modal shell border uses a double color-mix: color-mix(in srgb, color-mix(in srgb, var(--accent) 55%, var(--border)) 50%, transparent) — accent blended into border, then faded to 50% opacity.

Section Title Pattern

Used in SideMenuSection, CollapsibleSection, and Accordion for group headers:

  • Font: --font-sm (13px), --weight-semibold (600), letter-spacing: 0.06em (or --tracking-wide), text-transform: uppercase.
  • Color: --text-muted, shifting to --text when open/active.
  • Layout: flex row with justify-content: space-between, gap: var(--spacing-md).

Expand/Collapse Animation

Used by Accordion and CollapsibleSection for smooth open/close:

  • Body: display: grid; grid-template-rows: 0fr; collapsed, 1fr expanded. Transition: var(--ease-emphasis) (0.3s spring-like).
  • Content: min-height: 0; opacity: 0; collapsed, opacity: 1 expanded with a slight delay (0.2s 0.05s ease).
  • Arrow: Rotates from -90deg (closed) to 0deg (open) via var(--ease-standard). Color shifts from --text-muted at ~0.4-0.5 opacity to --accent-light at ~0.7-0.8 opacity.
  • Reduced motion: All three transitions set to none via @media (prefers-reduced-motion: reduce).

Backdrop Pattern

Used by modals, context menus, side menu, and onboarding overlays:

  • Position: position: fixed; left: 0; top: 0; width: 100%; height: 100% (or var(--vv-height) for visual viewport).
  • Background: var(--backdrop-overlay) for modals, --backdrop-overlay-heavy for action sheets, --backdrop-overlay-context for context menus.
  • Blur: backdrop-filter: blur(var(--blur-md)) on modals; context menus and side menu use lighter or no blur.
  • Safe area padding: calc(var(--spacing-lg) + var(--safe-*)) on all four sides.
  • Entry: transition: fade with 140ms duration.

Reduced Motion & Animations Off

Two independent systems:

  1. OS-level (@media (prefers-reduced-motion: reduce)) — Sets all animation-duration and transition-duration to 0.01ms. Applied globally in app.css.
  2. App-level (.no-animations class on <html>) — Sets animation: none; transition: none; scroll-behavior: auto on all elements. Additionally hides decorative elements: .node-flash, .node-ring, .level-splash, .tree-link-base. Exception: toast progress bars keep their animation so duration is still communicated.

Components check prefersNoAnimations() or $animationsDisabled in script to skip transition configs.

Theme Transition

When the user switches theme presets, themeApply.ts adds .theme-transitioning to <html> for 400ms. During this window, a global CSS rule applies transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, fill 0.3s ease, stroke 0.3s ease to all elements. This creates a smooth crossfade between theme palettes without requiring per-component transition rules.


Component Catalog

Button (Button.svelte)

The primary interactive element. Accepts an optional icon (Phosphor component), text via slot, and an optional arrow indicator.

Sizes:

  • button-md (default): min-height: 38px; min-width: 38px; padding: --spacing-sm --spacing-lg; font-size: --font-base.
  • button-sm (small prop): height: 32px; min-width: 32px; font-size: --font-sm. Used in compact HUD toolbars.

Tone variants (mutually exclusive):

  • Default: --bg-raised background, --border border, --text-muted text.
  • negative: --danger-bg / --danger-border / --danger-text. For destructive actions.
  • positive: --success-bg / --success-border / --success-text. For confirmations.
  • accent: --accent background and border, --bg text (inverted). For primary CTAs.

Modifiers:

  • ghost: Transparent background. Can combine with tones (e.g., negative ghost shows no background until hover/active).
  • button-flash: Animated scale-up + accent glow triggered by keyboard shortcut. transform: scale(1.08); background: var(--bg-tinted); border-color: var(--accent); box-shadow: 0 0 12px accent@40%.
  • with-icon: Flex layout with gap: --spacing-md.
  • with-arrow: Right-aligned caret (right or down).

Features:

  • toastMessage / toastNegative: Auto-shows a toast on click.
  • shortcut / flashOnAction: Keyboard shortcut badge in tooltip and flash animation on trigger.
  • description + descriptionIcon: Two-line button with subtitle row.
  • Tooltip integration via use:tooltip.

ButtonGroup (ButtonGroup.svelte)

Joins adjacent buttons with shared borders, suppressing inner radii. Two modes:

  • separatorSide="left" (default): left button keeps its right border.
  • separatorSide="right": right button keeps its left border.
  • fill mode: children flex equally, text wraps, icons collapse at small widths.

Toast System (toast.ts + Toasts.svelte)

Displays at the bottom of the viewport, stacked, max 3 visible. Auto-dismisses after 2600ms by default (configurable). Deduplicates by message or key.

Tones:

  • positive (default): --bg-raised background, --border-subtle border, --text-muted text. Icon: CheckCircleIcon in --accent color.
  • negative: --danger-bg / --danger-border / --danger-text. Icon: WarningCircleIcon. Uses a distinct shake-like entry animation (toast-enter-negative).

Features:

  • Progress bar: 2px ::after pseudo-element at the bottom, animating scaleX(1) to scaleX(0) over the toast duration.
  • Action toasts: Two-row layout with dismiss (X) and action button. Action button is pill-shaped with accent tint.
  • Permanent toasts: durationMs: 0 hides the progress bar.
  • Spinner variant: Shows a Spinner component instead of/alongside the status icon.
  • HUD overlap avoidance: Measures overlap with .bot-right-actions and .bot-left-actions and shifts upward.

Modal System (modalStore.ts + ModalHost.svelte)

Single-modal-at-a-time system driven by a writable store. Five modal types:

  1. ConfirmModal — Title + optional message + Cancel/Confirm buttons. Supports confirmNegative (danger tone) and confirmPositive (success tone). Width: narrow (320px floor).
  2. InputModal — Number input with label, optional label detail, stepper. Width: narrow.
  3. TextInputModal — Text input with label, placeholder, maxLength. Width: narrow.
  4. LoadBuildModal — Build import with paste/file input. Width: standard (380px floor).
  5. ResetTreeChoicesModal (Action Sheet) — Bottom-anchored sheet with choice cards. Swipe-to-dismiss via pointer events. Uses rounded border-radius: 28px, gradient background blending --surface and --accent, and heavier backdrop overlay. Width: wide (up to 40rem).

Shell styling: --bg-panel background, accent-blended border, double shadow (--shadow + --shadow-lg). Centered vertically with margin: auto. Entry animation: scale from 0.96 + translateY(8px) with cubicOut easing.

Keyboard: Focus trap (Tab/Shift+Tab), Escape to cancel, Enter to confirm. Auto-focuses confirm button on open.

Action Sheet (ActionSheet.svelte)

Rich choice cards used inside modals (currently ResetTreeChoicesModal). Each choice has:

  • An icon in a tinted badge (--choice-accent at 20% with inset highlight)
  • Label + description text
  • Optional amount highlighting

Tone variants (via --choice-accent CSS property):

  • orange: --region-orange-accent
  • blue: --region-blue-accent
  • yellow: --region-yellow-accent
  • danger: --accent-danger with --danger-bg/border/text overrides

Staggered entry animation: each choice and header animates in with increasing delay (action-sheet-item-in).

SegmentedControl (SegmentedControl.svelte)

Radio-group-style selector with equal-width segments. Supports an optional header row with icon, label, description, and keyboard shortcut badge.

  • Segment selected: color: --text; background: color-mix(in srgb, var(--surface) 78%, var(--accent)) — surface tinted with accent.
  • Segment unselected hover: color-mix(in srgb, var(--bg-raised) 80%, var(--text) 20%).
  • Leading icon mode: Icon sits outside the segments in a --bg-input area, separated by a subtle border.

ToggleSwitch (ToggleSwitch.svelte)

Full-width button-style row with label, optional description, optional icon, and a toggle track.

  • Track off: color-mix(in srgb, var(--border) 80%, transparent) with matching border.
  • Track on: color-mix(in srgb, var(--accent-light) 90%, transparent) with --accent-light border.
  • Thumb: 24px circle, white (oklch(0.97 0 0)), slides 20px on toggle. Uses --ease-emphasis for spring-like motion. Shadow via --shadow.

Accordion (Accordion.svelte)

Expandable section with bordered container. Similar to CollapsibleSection but wrapped in its own --bg-raised box with --radius corners.

  • Open state: Header gets subtle accent tint (accent 6%), bottom border appears, icon shifts to --accent-light.
  • Body: --surface background (slightly darker/lighter than the header).
  • Uses the standard expand/collapse animation pattern (grid-template-rows).

CollapsibleSection (CollapsibleSection.svelte)

Borderless expandable section for use inside scrolling panels (side menu pages). Sticky header with top/bottom borders.

  • Header background: --bg-raised, shifts to accent-tinted (8%) when open.
  • Hover: color-mix(in srgb, var(--text) 8%, var(--bg-raised)).
  • Active: color-mix(in srgb, var(--text) 12%, var(--bg-raised)) + scale(0.96).

ProgressBar (ProgressBar.svelte)

Horizontal fill bar with tick marks at quintile boundaries.

  • Track: --bg-raised, 8px tall, 4px radius.
  • Fill: linear-gradient(90deg, var(--accent), var(--accent-light)), scales from 0 to 1 via scaleX().
  • Ticks: Generated as a linear-gradient overlay, 2px wide lines in --bg-panel color.

Spinner (Spinner.svelte)

Rotating arc indicator. Two tones:

  • default: Arc color --accent, track --text-muted at 22%.
  • negative: Arc color --danger-text, track --danger-text at 22%.

Tooltip (app.css)

Portal-positioned, centered text bubble. Entry: tooltip-in 0.12s scale+fade animation.

  • Background: --bg-panel, border: --border-subtle, shadow: --shadow.
  • Supports multi-line (.tooltip-line), keyboard shortcut badges (.tooltip-shortcut + .kbd), and action grids (.tooltip-action-grid) for node level previews.
  • Touch devices: no transform adjustments; keyboard-only shortcut badges hidden via @media (hover: none).

Kbd Badge (app.css)

Keyboard shortcut key display: --surface/--bg-input blended background, --border at 60%, --radius-sm, --text-disabled text. Groups via .kbd-group, separated by .kbd-sep.

FullscreenModal (FullscreenModal.svelte)

Full-viewport overlay for tabbed content (settings, controls). Uses --bg-modal background with BottomNavBar for tab switching.

  • Keyboard: Escape to close, Ctrl+Tab / Ctrl+Shift+Tab to cycle tabs.
  • Safe area padding: --safe-top on content area.