UI reset to top on every reload/code update. Now: - selectedCampaignId persisted via localStorage - selectedEncounterId persisted via localStorage - scrollY saved (beforeunload + pagehide + visibilitychange + 2s interval for Android Chrome reliability) - scroll restored after campaigns data loads (300ms delay for mobile render)