performance: avoid using innerHTML