hyper_render 1.6.0 copy "hyper_render: ^1.6.0" to clipboard
hyper_render: ^1.6.0 copied to clipboard

Render HTML/Markdown/Delta at 60 FPS. The only Flutter renderer with CSS float layout, crash-free text selection, and CJK Ruby typography. Drop-in flutter_html alternative.

example/lib/main.dart

import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart' as flutter_html;
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart' as fwfh;
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'
    as fwfh_core;
import 'package:hyper_render/hyper_render.dart';

import 'html_preview_helper.dart';
import 'v2_1_showcase.dart';
import 'ultra_showcase_2026.dart';
import 'security_demo.dart';
import 'accessibility_demo.dart';
import 'video_demo_improved.dart';
import 'enhanced_selection_demo.dart';
import 'css_properties_demo.dart';
import 'css_mastery_demo.dart';
import 'essence_tour_demo.dart';
import 'flexbox_demo.dart';
import 'demo_colors.dart';
import 'performance_deep_dive_demo.dart';
import 'animation_demo.dart';
import 'sprint3_demo.dart';
import 'html_heuristics_demo.dart';
import 'smart_table_demo.dart';
import 'formula_demo.dart';
import 'manga_demo.dart';
import 'cjk_languages_demo.dart';
import 'email_demo.dart';
import 'stress_test_demo.dart';
import 'enterprise_features_demo.dart';
import 'paged_mode_demo.dart';
import 'plugin_api_demo.dart';
import 'reader_app/library_screen.dart';
import 'float_hell_demo.dart';
import 'zero_padding_image_demo.dart';
import 'base_url_demo.dart';

/// Optimized base TextStyle for better readability.
///
/// Intentionally has no `color` so it inherits from the surrounding
/// DefaultTextStyle / Theme — keeps demos legible in both light and dark mode.
/// - fontSize: 16 (comfortable reading size)
/// - height: 1.6 (generous line spacing for readability)
/// - letterSpacing: 0.15 (slight spacing for clarity)
const kOptimizedTextStyle = TextStyle(
  fontSize: 16,
  height: 1.6,
  letterSpacing: 0.15,
);

void main() {
  // Ensure Flutter binding is initialized
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize image cache after the first frame is rendered to prevent startup hangs
  WidgetsBinding.instance.addPostFrameCallback((_) {
    PaintingBinding.instance.imageCache.maximumSizeBytes = 150 << 20; // 150 MB
  });

  runApp(const HyperRenderDemoApp());
}

class HyperRenderDemoApp extends StatelessWidget {
  const HyperRenderDemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    const seed = Color(0xFF1A56DB);
    return MaterialApp(
      title: 'HyperRender Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: seed),
        useMaterial3: true,
        appBarTheme: const AppBarTheme(elevation: 0, centerTitle: false),
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: seed,
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
        appBarTheme: const AppBarTheme(elevation: 0, centerTitle: false),
      ),
      themeMode: ThemeMode.system,
      scrollBehavior: const MaterialScrollBehavior().copyWith(
        dragDevices: {
          ui.PointerDeviceKind.mouse,
          ui.PointerDeviceKind.touch,
          ui.PointerDeviceKind.stylus,
          ui.PointerDeviceKind.trackpad,
        },
      ),
      home: const DemoHomePage(),
    );
  }
}

// =============================================================================
// HOME PAGE - Navigation to demos
// =============================================================================

class DemoHomePage extends StatelessWidget {
  const DemoHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final scheme = Theme.of(context).colorScheme;
    return Scaffold(
      appBar: AppBar(
        title: const Text('HyperRender'),
        backgroundColor: scheme.primary,
        foregroundColor: scheme.onPrimary,
      ),
      backgroundColor: scheme.surface,
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _buildHeader(context),
          const SizedBox(height: 16),
          _buildWhyCard(context),
          const SizedBox(height: 8),
          // ── Signature Features ────────────────────────────────────────────
          _buildSectionHeader(context, 'Signature Features'),
          _buildDemoCard(
            context,
            icon: Icons.explore,
            title: 'Start here — the 7-step tour',
            subtitle:
                'Why HyperRender exists: float, CJK ruby, whole-document selection, '
                'real CSS, layout, a11y, safety — each shown live',
            color: const Color(0xFF1A56DB),
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const EssenceTourDemo())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.auto_awesome,
            title: 'Flagship Demo',
            subtitle:
                'Float layout · CJK ruby · virtualized rendering · plugin injection — all at once',
            color: const Color(0xFF1A56DB),
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const UltraShowcase2026())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.view_quilt,
            title: 'CSS Float Layout',
            subtitle:
                'Text wraps around floated images — impossible in any widget-tree-based renderer',
            color: const Color(0xFF1A56DB),
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const FloatLayoutDemo())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.email_outlined,
            title: 'HTML Email',
            subtitle:
                'Real HTML emails rendered natively — no WebView, <1 MB overhead vs ~20 MB',
            color: const Color(0xFF1A56DB),
            onTap: () => Navigator.push(
                context, MaterialPageRoute(builder: (_) => const EmailDemo())),
          ),
          // ── Applications ──────────────────────────────────────────────────
          _buildSectionHeader(context, 'Real-World Applications'),
          _buildDemoCard(
            context,
            icon: Icons.auto_stories,
            title: 'HyperReader App',
            subtitle:
                'Full e-book solution with paged mode, themes, and library management',
            color: Colors.deepPurple,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const LibraryScreen())),
          ),
          // ── Layout ────────────────────────────────────────────────────────
          _buildSectionHeader(context, 'Layout'),
          _buildDemoCard(
            context,
            icon: Icons.table_chart,
            title: 'Tables',
            subtitle:
                'Simple, wide, nested tables — plus strategies for tables wider than the screen',
            color: DemoColors.secondary,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const _TablesHubPage())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.view_column,
            title: 'Flexbox',
            subtitle:
                'CSS flexbox in pure Flutter — row/column, wrapping, alignment, gap',
            color: DemoColors.secondary,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const FlexboxDemo())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.rocket_launch,
            title: 'CSS Grid & Advanced Layout',
            subtitle:
                'CSS variables, grid, calc(), SVG, RTL/BiDi text, screenshot export',
            color: DemoColors.secondary,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const Sprint3Demo())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.menu_book_outlined,
            title: 'Paged Mode',
            subtitle:
                'PageView-based e-book / reader UI — one section per page with HyperPageController navigation',
            color: DemoColors.secondary,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const PagedModeDemo())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.extension,
            title: 'Plugin API',
            subtitle:
                'Render custom HTML tags as Flutter widgets — block and inline tiers (v1.2.0)',
            color: DemoColors.secondary,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const PluginApiDemo())),
          ),
          // ── Text & Typography ─────────────────────────────────────────────
          _buildSectionHeader(context, 'Text & Typography'),
          _buildDemoCard(
            context,
            icon: Icons.tune,
            title: 'CSS Mastery (interactive)',
            subtitle:
                'Drag to change the CSS live — border-spacing, justify, text-indent, '
                '%-widths, text scaling, animation-play-state',
            color: const Color(0xFF00695C),
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const CssMasteryDemo())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.select_all,
            title: 'Text Selection',
            subtitle:
                'Long-press to select, drag handles to resize, Copy/Share/Search menu',
            color: DemoColors.accent,
            onTap: () => Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (_) => const EnhancedSelectionDemo())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.menu_book,
            title: 'Japanese & Manga Typography',
            subtitle:
                'Furigana (ruby), vertical text, manga panel grid — Japanese content',
            color: const Color(0xFFB71C1C),
            onTap: () => Navigator.push(
                context, MaterialPageRoute(builder: (_) => const MangaDemo())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.language,
            title: '中文 · 繁體 · 한국어',
            subtitle:
                'Simplified Chinese, Traditional Chinese poetry, Korean tech article — CJK rendering',
            color: const Color(0xFF1565C0),
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const CjkLanguagesDemo())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.style,
            title: 'CSS Properties',
            subtitle:
                'text-shadow, text-overflow, border styles, writing direction, 60+ properties',
            color: DemoColors.accent,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const CssPropertiesDemo())),
          ),
          // ── Media & Integration ───────────────────────────────────────────
          _buildSectionHeader(context, 'Media & Integration'),
          _buildDemoCard(
            context,
            icon: Icons.perm_media,
            title: 'Images & Video',
            subtitle:
                'Image loading/fallback, pinch-to-zoom and pan, video thumbnail player',
            color: DemoColors.warning,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const _MediaHubPage())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.fullscreen,
            title: 'Zero Padding Images',
            subtitle:
                'Edge-to-edge images with no padding relative to the device edges',
            color: DemoColors.warning,
            onTap: () => Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (_) => const ZeroPaddingImageDemo())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.widgets,
            title: 'Widget Injection & Animation',
            subtitle:
                'Embed live Flutter widgets and animated components inside HTML content',
            color: DemoColors.warning,
            onTap: () => Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (_) => const _WidgetIntegrationHubPage())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.data_object,
            title: 'Input Formats',
            subtitle:
                'Render Markdown and Quill Delta (rich-text editor JSON output)',
            color: DemoColors.warning,
            onTap: () => Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (_) => const _InputFormatsHubPage())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.calculate,
            title: 'Math Formulas',
            subtitle:
                'Greek letters, fractions, physics equations via custom widget builder',
            color: DemoColors.warning,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const FormulaDemo())),
          ),
          // ── Engineering & Quality ─────────────────────────────────────────
          _buildSectionHeader(context, 'Engineering & Quality'),
          _buildDemoCard(
            context,
            icon: Icons.speed,
            title: 'Float Layout Stress Test',
            subtitle:
                '2 000 blocks, randomised left/right floats, live width animation',
            color: DemoColors.success,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const FloatHellDemo())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.compare_arrows_rounded,
            title: 'Comparison & Performance',
            subtitle:
                'Side-by-side vs other libraries, render pipeline deep-dive, benchmark numbers',
            color: DemoColors.success,
            onTap: () => Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (_) => const _ComparisonPerfHubPage())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.dark_mode,
            title: 'Dark Mode & Visual Quality',
            subtitle:
                'Theme switching, skeleton loading, error boundaries, crisp retina rendering',
            color: DemoColors.success,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const V21Showcase())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.shield_outlined,
            title: 'Security & Accessibility',
            subtitle:
                'XSS sanitization, WCAG 2.1 AA screen reader support, WebView fallback',
            color: DemoColors.success,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const _QualityHubPage())),
          ),
          _buildDemoCard(
            context,
            icon: Icons.business_center_outlined,
            title: 'Enterprise Features',
            subtitle:
                'GPU safety, error routing, memory pressure handling, deeplink security, zoom',
            color: DemoColors.success,
            onTap: () => Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (_) => const EnterpriseFeaturesDemo())),
          ),
        ],
      ),
    );
  }

  Widget _buildWhyCard(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        gradient: const LinearGradient(
          colors: [Color(0xFF0D3B8E), Color(0xFF1A56DB)],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(14),
        boxShadow: [
          BoxShadow(
            color: const Color(0xFF1A56DB).withValues(alpha: 0.35),
            blurRadius: 16,
            offset: const Offset(0, 4),
          ),
        ],
      ),
      child: Material(
        color: Colors.transparent,
        borderRadius: BorderRadius.circular(14),
        child: InkWell(
          // Points at the guided tour: it covers the same argument (why this
          // renderer exists) with a live demo per point, which the former
          // standalone "Why HyperRender" screen duplicated.
          onTap: () => Navigator.push(context,
              MaterialPageRoute(builder: (_) => const EssenceTourDemo())),
          borderRadius: BorderRadius.circular(14),
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
            child: Row(
              children: [
                Container(
                  width: 40,
                  height: 40,
                  decoration: BoxDecoration(
                    color: Colors.white.withValues(alpha: 0.15),
                    borderRadius: BorderRadius.circular(10),
                  ),
                  child: const Icon(Icons.compare_arrows_rounded,
                      color: Colors.white, size: 22),
                ),
                const SizedBox(width: 14),
                const Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Why HyperRender?',
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: 15,
                          fontWeight: FontWeight.w800,
                          letterSpacing: -0.2,
                        ),
                      ),
                      SizedBox(height: 3),
                      Text(
                        'Live feature demos · side-by-side comparison · 16 vs 3 exclusive features',
                        style: TextStyle(
                            color: Color(0xFFB8CEFC),
                            fontSize: 12,
                            height: 1.4),
                      ),
                    ],
                  ),
                ),
                const SizedBox(width: 8),
                Container(
                  padding:
                      const EdgeInsets.symmetric(horizontal: 9, vertical: 5),
                  decoration: BoxDecoration(
                    color: Colors.white.withValues(alpha: 0.15),
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(
                        color: Colors.white.withValues(alpha: 0.3), width: 1),
                  ),
                  child: const Text(
                    '16 / 16',
                    style: TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.w800,
                        fontSize: 13),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildHeader(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(24),
      decoration: const BoxDecoration(
        gradient: LinearGradient(
          colors: [Color(0xFF0D3B8E), Color(0xFF1A56DB)],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.all(Radius.circular(20)),
        boxShadow: [
          BoxShadow(
            color: Color(0x591A56DB),
            blurRadius: 24,
            offset: Offset(0, 8),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Container(
                width: 52,
                height: 52,
                decoration: BoxDecoration(
                  color: Colors.white.withValues(alpha: 0.15),
                  borderRadius: BorderRadius.circular(14),
                  border: Border.all(
                      color: Colors.white.withValues(alpha: 0.2), width: 1),
                ),
                child: const Center(
                  child: Text(
                    'HR',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 18,
                      fontWeight: FontWeight.w900,
                      letterSpacing: 1,
                    ),
                  ),
                ),
              ),
              const SizedBox(width: 16),
              const Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'HyperRender',
                      style: TextStyle(
                        fontSize: 26,
                        fontWeight: FontWeight.w800,
                        color: Colors.white,
                        letterSpacing: -0.5,
                      ),
                    ),
                    SizedBox(height: 3),
                    Text(
                      'The only Flutter HTML renderer\nwith CSS float layout.',
                      style: TextStyle(
                        fontSize: 13,
                        color: Color(0xFFB8CEFC),
                        height: 1.45,
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
          const SizedBox(height: 20),
          // ── Stat row ──────────────────────────────────────────────────────
          Row(
            children: [
              // Every figure here must be independently checkable — no
              // unverified performance numbers (see doc/COMPARISON_MATRIX.md,
              // which deliberately does not claim a measured 60 FPS).
              _buildStatChip('1 981', 'tests'),
              const SizedBox(width: 8),
              _buildStatChip('189', 'CSS props'),
              const SizedBox(width: 8),
              _buildStatChip('1', 'RenderObject'),
              const SizedBox(width: 8),
              _buildStatChip('0', 'Gradle config'),
            ],
          ),
          const SizedBox(height: 14),
          // ── Feature chips ─────────────────────────────────────────────────
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              _buildChip('CSS Float', Icons.view_quilt_rounded),
              _buildChip('Ruby / CJK', Icons.translate_rounded),
              _buildChip('@keyframes', Icons.animation_rounded),
              _buildChip('CSS Grid', Icons.grid_view_rounded),
              _buildChip('Selection', Icons.select_all_rounded),
              _buildChip('Markdown', Icons.description_outlined),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildStatChip(String value, String label) {
    return Expanded(
      child: Container(
        padding: const EdgeInsets.symmetric(vertical: 8),
        decoration: BoxDecoration(
          color: Colors.white.withValues(alpha: 0.12),
          borderRadius: BorderRadius.circular(10),
          border:
              Border.all(color: Colors.white.withValues(alpha: 0.2), width: 1),
        ),
        child: Column(
          children: [
            Text(
              value,
              style: const TextStyle(
                color: Colors.white,
                fontSize: 14,
                fontWeight: FontWeight.w800,
                letterSpacing: -0.3,
              ),
            ),
            const SizedBox(height: 1),
            Text(
              label,
              style: const TextStyle(
                color: Color(0xFFB8CEFC),
                fontSize: 10,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildChip(String label, IconData icon) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
      decoration: BoxDecoration(
        color: Colors.white.withValues(alpha: 0.12),
        borderRadius: BorderRadius.circular(20),
        border:
            Border.all(color: Colors.white.withValues(alpha: 0.25), width: 1),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(icon, color: Colors.white, size: 13),
          const SizedBox(width: 5),
          Text(label,
              style: const TextStyle(
                  color: Colors.white,
                  fontSize: 12,
                  fontWeight: FontWeight.w500)),
        ],
      ),
    );
  }

  Widget _buildSectionHeader(BuildContext context, String title) {
    final primary = Theme.of(context).colorScheme.primary;
    return Padding(
      padding: const EdgeInsets.only(bottom: 12, top: 16),
      child: Row(
        children: [
          Container(
            width: 4,
            height: 20,
            decoration: BoxDecoration(
              color: primary,
              borderRadius: BorderRadius.circular(2),
            ),
          ),
          const SizedBox(width: 10),
          Text(
            title,
            style: TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.w700,
              color: primary,
              letterSpacing: 0.3,
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildDemoCard(
    BuildContext context, {
    required IconData icon,
    required String title,
    required String subtitle,
    required Color color,
    required VoidCallback onTap,
  }) {
    final theme = Theme.of(context);
    final scheme = theme.colorScheme;
    final accent = DemoColors.forBrightness(color, theme.brightness);
    final isDark = theme.brightness == Brightness.dark;
    return Semantics(
      button: true,
      label: title,
      hint: subtitle,
      child: Container(
        margin: const EdgeInsets.only(bottom: 10),
        decoration: BoxDecoration(
          color: scheme.surfaceContainerHighest,
          borderRadius: BorderRadius.circular(14),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withValues(alpha: isDark ? 0.25 : 0.06),
              blurRadius: 10,
              offset: const Offset(0, 2),
            ),
          ],
        ),
        child: ClipRRect(
          borderRadius: BorderRadius.circular(14),
          child: Material(
            color: Colors.transparent,
            child: InkWell(
              onTap: onTap,
              child: IntrinsicHeight(
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    // Left accent bar
                    Container(
                      width: 4,
                      color: accent,
                    ),
                    Expanded(
                      child: Padding(
                        padding: const EdgeInsets.symmetric(
                            horizontal: 14, vertical: 14),
                        child: Row(
                          children: [
                            Container(
                              width: 44,
                              height: 44,
                              decoration: BoxDecoration(
                                color: accent.withValues(alpha: 0.12),
                                borderRadius: BorderRadius.circular(11),
                              ),
                              child: Icon(icon, color: accent, size: 22),
                            ),
                            const SizedBox(width: 13),
                            Expanded(
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  Text(
                                    title,
                                    style: TextStyle(
                                      fontSize: 15,
                                      fontWeight: FontWeight.w600,
                                      color: scheme.onSurface,
                                      letterSpacing: -0.1,
                                    ),
                                  ),
                                  const SizedBox(height: 3),
                                  Text(
                                    subtitle,
                                    style: TextStyle(
                                      fontSize: 12.5,
                                      color: scheme.onSurfaceVariant,
                                      height: 1.35,
                                    ),
                                  ),
                                ],
                              ),
                            ),
                            const SizedBox(width: 6),
                            Icon(Icons.chevron_right_rounded,
                                color: scheme.onSurfaceVariant
                                    .withValues(alpha: 0.5),
                                size: 20),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

// =============================================================================
// FLOAT LAYOUT DEMO
// =============================================================================

class FloatLayoutDemo extends StatelessWidget {
  const FloatLayoutDemo({super.key});

  static const html = '''
<div style="font-family: sans-serif; line-height: 1.6;">
  <h2 style="color: #1976D2;">Float Left</h2>
  <div style="margin: 16px 0;">
    <img src="https://picsum.photos/120/120?random=10" style="float: left; width: 120px; height: 120px; margin: 0 16px 8px 0; border-radius: 12px; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
    <p>
      This is an example of <strong>float: left</strong>. Text will automatically wrap around the image on the left.
      When the text is long enough, it will continue below the image naturally. This is a feature
      that flutter_html and flutter_widget_from_html do NOT support.
    </p>
    <p>
      HyperRender uses the IFC (Inline Formatting Context) algorithm like real web browsers
      to calculate the remaining space of each line and fill it with text fragments.
    </p>
  </div>

  <div style="clear: both; height: 32px;"></div>

  <h2 style="color: #9C27B0;">Float Right</h2>
  <div style="margin: 16px 0;">
    <img src="https://picsum.photos/100/100?random=11" style="float: right; width: 100px; height: 100px; margin: 0 0 8px 16px; border-radius: 50%; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
    <p>
      Float also works on the <strong>right side</strong>! This circle floats right and text
      will fill the empty space on the left naturally.
    </p>
    <p>
      Try rotating the screen to see how smoothly the layout adapts.
    </p>
  </div>

  <div style="clear: both; height: 32px;"></div>

  <h2 style="color: #E91E63;">Left + Right</h2>
  <div style="margin: 16px 0;">
    <img src="https://picsum.photos/90/90?random=20" style="float: left; width: 90px; height: 90px; margin: 0 14px 8px 0; border-radius: 8px; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
    <img src="https://picsum.photos/90/90?random=21" style="float: right; width: 90px; height: 90px; margin: 0 0 8px 14px; border-radius: 8px; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
    <p>
      Two images on <strong>opposite sides</strong> — one float left, one float right. The text
      automatically fills the gap in between. The layout engine calculates both float boundaries
      simultaneously to determine the valid region for each line of text.
    </p>
    <p>
      This is a <em>magazine-style</em> layout — images pinned to both corners with content flowing in the middle.
    </p>
  </div>

  <div style="clear: both; height: 32px;"></div>

  <h2 style="color: #FF5722;">Multiple Left Floats</h2>
  <div style="margin: 16px 0;">
    <img src="https://picsum.photos/80/80?random=12" style="float: left; width: 80px; height: 80px; margin: 0 12px 8px 0; border-radius: 8px; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
    <img src="https://picsum.photos/80/80?random=13" style="float: left; width: 80px; height: 80px; margin: 0 12px 8px 0; border-radius: 8px; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
    <p>
      Multiple images floated left side-by-side. Text wraps around the entire group.
      This is a common way to display horizontal image galleries within an article.
    </p>
  </div>
</div>
''';

  @override
  Widget build(BuildContext context) {
    return DemoScaffold(
      title: 'Float Layout Demo',
      html: html,
      child: const Padding(
        padding: EdgeInsets.all(16.0),
        child: HyperViewer(html: html, selectable: true),
      ),
    );
  }
}

// =============================================================================
// RUBY DEMO
// =============================================================================

// MED-04: RubyDemo removed — it was dead code (never navigated to from any
// screen). Ruby annotation content is covered by MangaDemo and CjkLanguagesDemo.

// =============================================================================
// WIDGET INJECTION DEMO
// =============================================================================

class WidgetInjectionDemo extends StatefulWidget {
  const WidgetInjectionDemo({super.key});

  @override
  State<WidgetInjectionDemo> createState() => _WidgetInjectionDemoState();
}

class _WidgetInjectionDemoState extends State<WidgetInjectionDemo> {
  int _likeCount = 42;
  bool _isSubscribed = false;
  double _rating = 4.0;

  static const html = '''
<div style="font-family: sans-serif; line-height: 1.6;">
  <h2 style="color: #9C27B0;">Widget Injection</h2>
  <p>You can embed <strong>any Flutter Widget</strong> into HTML using custom tags.</p>

  <div style="background: #F3E5F5; padding: 16px; border-radius: 12px; margin: 16px 0; text-align: center;">
    <p style="margin: 0 0 12px 0; font-weight: bold;">🔔 Subscribe Channel</p>
    <subscribe-button></subscribe-button>
  </div>

  <div style="background: #FCE4EC; padding: 16px; border-radius: 12px; margin: 16px 0;">
    <p style="margin: 0 0 12px 0; font-weight: bold;">❤️ Like this post</p>
    <like-button></like-button>
  </div>

  <div style="background: #E8F5E9; padding: 16px; border-radius: 12px; margin: 16px 0;">
    <p style="margin: 0 0 12px 0; font-weight: bold;">⭐ Rate this article</p>
    <rating-widget></rating-widget>
  </div>

  <div style="background: #E3F2FD; padding: 16px; border-radius: 12px; margin: 16px 0;">
    <p style="margin: 0 0 12px 0; font-weight: bold;">📤 Share</p>
    <share-buttons></share-buttons>
  </div>
</div>
''';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Widget Injection Demo'),
        backgroundColor: Theme.of(context).colorScheme.primary,
        foregroundColor: Theme.of(context).colorScheme.onPrimary,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: HyperViewer(
          html: html,
          widgetBuilder: (node) {
            if (node is AtomicNode) {
              switch (node.tagName) {
                case 'subscribe-button':
                  return _buildSubscribeButton();
                case 'like-button':
                  return _buildLikeButton();
                case 'rating-widget':
                  return _buildRatingWidget();
                case 'share-buttons':
                  return _buildShareButtons();
              }
            }
            return null;
          },
        ),
      ),
    );
  }

  Widget _buildSubscribeButton() {
    return ElevatedButton.icon(
      onPressed: () => setState(() => _isSubscribed = !_isSubscribed),
      icon: Icon(
          _isSubscribed ? Icons.notifications_off : Icons.notifications_active),
      label: Text(_isSubscribed ? 'Subscribed ✓' : 'Subscribe'),
      style: ElevatedButton.styleFrom(
        backgroundColor: _isSubscribed ? Colors.grey : Colors.red,
        foregroundColor: Colors.white,
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
      ),
    );
  }

  Widget _buildLikeButton() {
    return Semantics(
      container: true,
      label: 'Like this post, currently $_likeCount likes',
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          IconButton(
            tooltip: 'Like',
            onPressed: () => setState(() => _likeCount++),
            icon: const Icon(Icons.favorite, color: Colors.pink),
            iconSize: 32,
          ),
          Text('$_likeCount',
              style:
                  const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
        ],
      ),
    );
  }

  Widget _buildRatingWidget() {
    return Semantics(
      container: true,
      label: 'Rate this article, $_rating out of 5 stars',
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: List.generate(5, (i) {
          return IconButton(
            tooltip: 'Rate ${i + 1} star${i == 0 ? '' : 's'}',
            onPressed: () => setState(() => _rating = i + 1.0),
            icon: Icon(
              i < _rating ? Icons.star : Icons.star_border,
              color: Colors.amber,
            ),
            iconSize: 32,
          );
        }),
      ),
    );
  }

  Widget _buildShareButtons() {
    return Semantics(
      container: true,
      label: 'Share this post',
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          IconButton(
            tooltip: 'Share to Facebook',
            onPressed: () => _showSnackBar('Share to Facebook'),
            icon: const Icon(Icons.facebook, color: Colors.blue),
            iconSize: 32,
          ),
          IconButton(
            tooltip: 'Share to Twitter',
            onPressed: () => _showSnackBar('Share to Twitter'),
            icon: const Icon(Icons.alternate_email, color: Colors.lightBlue),
            iconSize: 32,
          ),
          IconButton(
            tooltip: 'Copy link',
            onPressed: () => _showSnackBar('Copy link'),
            icon: const Icon(Icons.link, color: Colors.grey),
            iconSize: 32,
          ),
        ],
      ),
    );
  }

  void _showSnackBar(String msg) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(msg), behavior: SnackBarBehavior.floating),
    );
  }
}

// =============================================================================
// TABLE DEMO
// =============================================================================

class TableDemo extends StatelessWidget {
  const TableDemo({super.key});

  static const html = '''
<div style="font-family: sans-serif; line-height: 1.6;">

  <!-- ── 1. Basic styled table ─────────────────────────────────────────── -->
  <h2 style="color: #1976D2;">Basic Table</h2>
  <p>Header row, alternating row colors, full-width.</p>
  <table style="border-collapse: collapse; width: 100%;">
    <thead>
      <tr style="background: #1976D2; color: white;">
        <th style="padding: 10px 14px; text-align: left;">Name</th>
        <th style="padding: 10px 14px; text-align: left;">Role</th>
        <th style="padding: 10px 14px; text-align: left;">Status</th>
        <th style="padding: 10px 14px; text-align: left;">Joined</th>
      </tr>
    </thead>
    <tbody>
      <tr style="background: #ffffff;">
        <td style="padding: 8px 14px; border-bottom: 1px solid #e0e0e0;">Alice Chen</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e0e0e0;">Engineer</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e0e0e0;"><span style="background:#e8f5e9;color:#2e7d32;padding:2px 8px;border-radius:12px;font-size:13px;">Active</span></td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e0e0e0;">2022-03</td>
      </tr>
      <tr style="background: #f8f9fa;">
        <td style="padding: 8px 14px; border-bottom: 1px solid #e0e0e0;">Bob Kim</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e0e0e0;">Designer</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e0e0e0;"><span style="background:#fff8e1;color:#f57f17;padding:2px 8px;border-radius:12px;font-size:13px;">Away</span></td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e0e0e0;">2021-11</td>
      </tr>
      <tr style="background: #ffffff;">
        <td style="padding: 8px 14px; border-bottom: 1px solid #e0e0e0;">Carol Smith</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e0e0e0;">Product</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e0e0e0;"><span style="background:#e8f5e9;color:#2e7d32;padding:2px 8px;border-radius:12px;font-size:13px;">Active</span></td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e0e0e0;">2023-01</td>
      </tr>
      <tr style="background: #f8f9fa;">
        <td style="padding: 8px 14px;">Dan Park</td>
        <td style="padding: 8px 14px;">DevOps</td>
        <td style="padding: 8px 14px;"><span style="background:#fce4ec;color:#c62828;padding:2px 8px;border-radius:12px;font-size:13px;">Offline</span></td>
        <td style="padding: 8px 14px;">2020-06</td>
      </tr>
    </tbody>
  </table>

  <!-- ── 2. Colspan & Rowspan ───────────────────────────────────────────── -->
  <h2 style="color: #7B1FA2; margin-top: 32px;">Colspan &amp; Rowspan</h2>
  <p>Cells can span multiple columns or rows — used in schedules, reports, invoices.</p>
  <table style="border-collapse: collapse; width: 100%; border: 1px solid #ce93d8;">
    <tr style="background: #7B1FA2; color: white;">
      <th style="padding: 10px; border: 1px solid #ce93d8;" colspan="3">Q1 Sales Report</th>
      <th style="padding: 10px; border: 1px solid #ce93d8;" rowspan="2">YoY Change</th>
    </tr>
    <tr style="background: #f3e5f5;">
      <th style="padding: 8px 12px; border: 1px solid #ce93d8;">Region</th>
      <th style="padding: 8px 12px; border: 1px solid #ce93d8;">Jan</th>
      <th style="padding: 8px 12px; border: 1px solid #ce93d8;">Feb</th>
    </tr>
    <tr>
      <td style="padding: 8px 12px; border: 1px solid #e0e0e0;">North</td>
      <td style="padding: 8px 12px; border: 1px solid #e0e0e0;">\$12,000</td>
      <td style="padding: 8px 12px; border: 1px solid #e0e0e0;">\$14,500</td>
      <td style="padding: 8px 12px; border: 1px solid #e0e0e0; color: #2e7d32; font-weight: bold;">+21%</td>
    </tr>
    <tr style="background: #fafafa;">
      <td style="padding: 8px 12px; border: 1px solid #e0e0e0;">South</td>
      <td style="padding: 8px 12px; border: 1px solid #e0e0e0;">\$9,800</td>
      <td style="padding: 8px 12px; border: 1px solid #e0e0e0;">\$11,200</td>
      <td style="padding: 8px 12px; border: 1px solid #e0e0e0; color: #2e7d32; font-weight: bold;">+14%</td>
    </tr>
    <tr>
      <td style="padding: 8px 12px; border: 1px solid #e0e0e0; font-weight: bold;" colspan="2">Total</td>
      <td style="padding: 8px 12px; border: 1px solid #e0e0e0; font-weight: bold;">\$25,700</td>
      <td style="padding: 8px 12px; border: 1px solid #e0e0e0; font-weight: bold; color: #2e7d32;">+18%</td>
    </tr>
  </table>

  <!-- ── 3. Inline rich content in cells ───────────────────────────────── -->
  <h2 style="color: #E65100; margin-top: 32px;">Rich Content in Cells</h2>
  <p>Cells can contain <strong>bold</strong>, <em>italic</em>, <a href="#">links</a>, <code>code</code>, and inline badges.</p>
  <table style="border-collapse: collapse; width: 100%; border: 1px solid #ffccbc;">
    <thead>
      <tr style="background: #E65100; color: white;">
        <th style="padding: 10px 14px; text-align: left;">Package</th>
        <th style="padding: 10px 14px; text-align: left;">Version</th>
        <th style="padding: 10px 14px; text-align: left;">Notes</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td style="padding: 8px 14px; border-bottom: 1px solid #ffe0b2;"><code style="background:#fff3e0;padding:2px 6px;border-radius:4px;">hyper_render</code></td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #ffe0b2;"><strong>4.0.0</strong></td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #ffe0b2;">Current — <em>no WebView needed</em></td>
      </tr>
      <tr style="background: #fafafa;">
        <td style="padding: 8px 14px; border-bottom: 1px solid #ffe0b2;"><code style="background:#fff3e0;padding:2px 6px;border-radius:4px;">flutter_html</code></td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #ffe0b2;">3.0.0</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #ffe0b2;"><span style="background:#fce4ec;color:#c62828;padding:2px 8px;border-radius:12px;font-size:12px;">No float support</span></td>
      </tr>
      <tr>
        <td style="padding: 8px 14px;"><code style="background:#fff3e0;padding:2px 6px;border-radius:4px;">fwfh</code></td>
        <td style="padding: 8px 14px;">0.15.0</td>
        <td style="padding: 8px 14px;"><span style="background:#fce4ec;color:#c62828;padding:2px 8px;border-radius:12px;font-size:12px;">No ruby / details</span></td>
      </tr>
    </tbody>
  </table>

  <!-- ── 4. Nested table ───────────────────────────────────────────────── -->
  <h2 style="color: #00695C; margin-top: 32px;">Nested Table</h2>
  <p>A full table inside a table cell — the inner table renders completely.</p>
  <table style="border-collapse: collapse; width: 100%; border: 1px solid #b2dfdb;">
    <thead>
      <tr style="background: #00695C; color: white;">
        <th style="padding: 10px 14px; text-align: left;">Department</th>
        <th style="padding: 10px 14px; text-align: left;">Members</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td style="padding: 10px 14px; border-bottom: 1px solid #e0f2f1; vertical-align: top; font-weight: bold;">Engineering</td>
        <td style="padding: 10px 14px; border-bottom: 1px solid #e0f2f1;">
          <table style="border-collapse: collapse; width: 100%; background: #f0fffe;">
            <tr style="background: #b2dfdb;">
              <th style="padding: 5px 10px; text-align: left; font-size: 13px;">Name</th>
              <th style="padding: 5px 10px; text-align: left; font-size: 13px;">Level</th>
            </tr>
            <tr>
              <td style="padding: 5px 10px; border-top: 1px solid #e0f2f1; font-size: 13px;">Alice Chen</td>
              <td style="padding: 5px 10px; border-top: 1px solid #e0f2f1; font-size: 13px;">Senior</td>
            </tr>
            <tr>
              <td style="padding: 5px 10px; border-top: 1px solid #e0f2f1; font-size: 13px;">Bob Kim</td>
              <td style="padding: 5px 10px; border-top: 1px solid #e0f2f1; font-size: 13px;">Mid</td>
            </tr>
          </table>
        </td>
      </tr>
      <tr style="background: #fafafa;">
        <td style="padding: 10px 14px; vertical-align: top; font-weight: bold;">Design</td>
        <td style="padding: 10px 14px;">
          <table style="border-collapse: collapse; width: 100%; background: #fffde7;">
            <tr style="background: #fff9c4;">
              <th style="padding: 5px 10px; text-align: left; font-size: 13px;">Name</th>
              <th style="padding: 5px 10px; text-align: left; font-size: 13px;">Level</th>
            </tr>
            <tr>
              <td style="padding: 5px 10px; border-top: 1px solid #f0e0a0; font-size: 13px;">Carol Smith</td>
              <td style="padding: 5px 10px; border-top: 1px solid #f0e0a0; font-size: 13px;">Lead</td>
            </tr>
          </table>
        </td>
      </tr>
    </tbody>
  </table>

  <!-- ── 5. Pricing / comparison table ─────────────────────────────────── -->
  <h2 style="color: #1565C0; margin-top: 32px;">Pricing Table</h2>
  <p>Common real-world table pattern with mixed alignment and styled cells.</p>
  <table style="border-collapse: collapse; width: 100%; border: 1px solid #bbdefb;">
    <thead>
      <tr style="background: #1565C0; color: white;">
        <th style="padding: 10px 14px; text-align: left;">Feature</th>
        <th style="padding: 10px 14px; text-align: center;">Free</th>
        <th style="padding: 10px 14px; text-align: center; background: #1976D2;">Pro</th>
        <th style="padding: 10px 14px; text-align: center;">Enterprise</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e3f2fd;">HTML rendering</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e3f2fd; text-align: center; color: #2e7d32;">✓</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e3f2fd; text-align: center; color: #2e7d32; background: #e3f2fd;">✓</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e3f2fd; text-align: center; color: #2e7d32;">✓</td>
      </tr>
      <tr style="background: #fafafa;">
        <td style="padding: 8px 14px; border-bottom: 1px solid #e3f2fd;">Float layout</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e3f2fd; text-align: center; color: #c62828;">✗</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e3f2fd; text-align: center; color: #2e7d32; background: #e3f2fd;">✓</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e3f2fd; text-align: center; color: #2e7d32;">✓</td>
      </tr>
      <tr>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e3f2fd;">Widget injection</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e3f2fd; text-align: center; color: #c62828;">✗</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e3f2fd; text-align: center; color: #2e7d32; background: #e3f2fd;">✓</td>
        <td style="padding: 8px 14px; border-bottom: 1px solid #e3f2fd; text-align: center; color: #2e7d32;">✓</td>
      </tr>
      <tr style="background: #fafafa;">
        <td style="padding: 8px 14px;">Priority support</td>
        <td style="padding: 8px 14px; text-align: center; color: #c62828;">✗</td>
        <td style="padding: 8px 14px; text-align: center; color: #c62828; background: #e3f2fd;">✗</td>
        <td style="padding: 8px 14px; text-align: center; color: #2e7d32;">✓</td>
      </tr>
    </tbody>
  </table>

</div>
''';

  @override
  Widget build(BuildContext context) {
    return DemoScaffold(
      title: 'Table Demo',
      html: html,
      child: const Padding(
        padding: EdgeInsets.all(16.0),
        child: HyperViewer(html: html, selectable: true),
      ),
    );
  }
}

// =============================================================================
// IMAGE HANDLING DEMO
// =============================================================================

class ImageHandlingDemo extends StatelessWidget {
  const ImageHandlingDemo({super.key});

  static const html = '''
<div style="font-family: sans-serif; line-height: 1.8; padding: 4px;">

  <h2 style="color: #00ACC1; margin-top: 0;">Image Handling</h2>
  <p style="color: #555;">HyperRender shows a shimmer skeleton while loading and a broken-image placeholder on failure.</p>

  <!-- ── Success: inline images, no flex/gap needed ── -->
  <h3 style="color: #00838F; margin-top: 20px;">✅ Network Images (loading + success)</h3>
  <p style="font-size:13px; color:#666;">Shimmer placeholder appears while each image loads:</p>

  <img src="https://picsum.photos/seed/hr1/180/130"
       style="width:180px; height:130px; border-radius:8px; margin:0 10px 10px 0;"
       alt="Image 1">
  <img src="https://picsum.photos/seed/hr2/180/130"
       style="width:180px; height:130px; border-radius:8px; margin:0 10px 10px 0;"
       alt="Image 2">
  <img src="https://picsum.photos/seed/hr3/180/130"
       style="width:180px; height:130px; border-radius:8px; margin:0 0 10px 0;"
       alt="Image 3">

  <!-- ── Intentional error ── -->
  <h3 style="color: #D32F2F; margin-top: 20px;">❌ Error Placeholder (intentional 404)</h3>
  <div style="background:#FFF3E0; padding:10px 14px; border-left:4px solid #FF9800; border-radius:4px; margin-bottom:12px;">
    <p style="margin:0; font-size:13px; color:#E65100;">
      The URL below intentionally returns 404 — showing the broken-image placeholder.
    </p>
  </div>
  <img src="https://example.com/nonexistent-image-404.jpg"
       style="width:180px; height:130px; border-radius:8px;"
       alt="Intentional 404 error">
  <p style="font-size:12px; color:#999; margin-top:4px;">
    ↑ Error placeholder: gray background + broken-image icon, dimensions preserved.
  </p>

  <!-- ── Float layout: good left + bad right ── -->
  <h3 style="color: #00838F; margin-top: 20px;">🖼 Float Layout + Mixed Results</h3>
  <div style="border:1px solid #E0E0E0; padding:14px; border-radius:8px; overflow:hidden;">

    <img src="https://picsum.photos/seed/hr4/160/120"
         style="float:left; width:160px; height:120px; border-radius:8px; margin:0 14px 8px 0;"
         alt="Float left — success">
    <p style="margin:0; font-size:14px;">
      <strong>Left:</strong> network image, loads successfully. Text wraps around it using HyperRender's float engine.
    </p>
    <div style="clear:both; height:10px;"></div>

    <img src="https://invalid-domain-xyz-fail.com/missing.jpg"
         style="float:right; width:160px; height:120px; border-radius:8px; margin:0 0 8px 14px;"
         alt="Float right — fail">
    <p style="margin:0; font-size:14px;">
      <strong>Right:</strong> invalid domain — shows error placeholder. Layout stays intact; placeholder holds the specified 160×120 space.
    </p>
    <div style="clear:both;"></div>
  </div>

  <!-- ── Full-width image — explicit height, NOT height:auto ── -->
  <h3 style="color: #00838F; margin-top: 20px;">📐 Full-Width Image</h3>
  <p style="font-size:13px; color:#666; margin-bottom:8px;">
    Use explicit <code>height</code> values — <code>height:auto</code> is not supported and renders as 0px.
  </p>
  <img src="https://picsum.photos/seed/hr5/800/240"
       style="width:100%; height:200px; border-radius:8px;"
       alt="Wide image">

  <!-- ── Summary ── -->
  <div style="background:#E8F5E9; padding:14px; border-left:4px solid #4CAF50; margin-top:20px; border-radius:4px;">
    <strong style="color:#2E7D32;">✨ Automatic Benefits</strong>
    <p style="margin:8px 0 0; font-size:13px; color:#424242; line-height:1.8;">
      • Shimmer skeleton while loading<br>
      • Broken-image placeholder on failure<br>
      • Dimensions preserved — no layout shift<br>
      • Works with float, inline, and block images<br>
      • <strong>Note:</strong> always use explicit <code>height</code>, not <code>height:auto</code>
    </p>
  </div>
</div>
''';

  @override
  Widget build(BuildContext context) {
    return DemoScaffold(
      title: 'Image Handling Demo',
      html: html,
      child: const Padding(
        padding: EdgeInsets.all(16.0),
        child: HyperViewer(html: html, selectable: true),
      ),
    );
  }
}

// =============================================================================
// ZOOM & PAN DEMO
// =============================================================================

class ZoomDemo extends StatelessWidget {
  const ZoomDemo({super.key});

  static const html = '''
<div style="font-family: sans-serif; line-height: 1.8; max-width: 800px;">
  <h2 style="color: #0288D1;">Zoom & Pan Demo</h2>
  <p>Use pinch-to-zoom or trackpad gestures to zoom in/out. Pan by dragging while zoomed.</p>

  <div style="background: #E1F5FE; padding: 16px; border-left: 4px solid #0288D1; margin: 16px 0; border-radius: 4px;">
    <p style="margin: 0; font-weight: bold; color: #01579B;">🔍 Zoom Controls</p>
    <p style="margin: 8px 0 0 0;">
      • <strong>Mobile:</strong> Pinch with two fingers to zoom in/out<br>
      • <strong>Desktop:</strong> Ctrl + Mouse Wheel to zoom<br>
      • <strong>Trackpad:</strong> Pinch gesture (two fingers)<br>
      • <strong>Pan:</strong> Drag with one finger/mouse while zoomed
    </p>
  </div>

  <h3 style="color: #0277BD; margin-top: 24px;">Try Zooming on This Content</h3>

  <img src="https://picsum.photos/600/400?random=10"
       style="width: 100%; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); margin: 16px 0;"
       alt="High resolution test image">

  <h3 style="color: #0277BD; margin-top: 24px;">Small Text Test</h3>
  <p style="font-size: 12px;">
    This paragraph uses smaller font size (12px). Zoom in to read it comfortably.
    Zoom functionality is especially useful for:
  </p>
  <ul style="font-size: 12px;">
    <li>Reading fine print or detailed text</li>
    <li>Viewing high-resolution images up close</li>
    <li>Inspecting code blocks or technical diagrams</li>
    <li>Accessibility for users with visual impairments</li>
  </ul>

  <h3 style="color: #0277BD; margin-top: 24px;">Code Block with Small Font</h3>
  <pre style="background: #263238; color: #EEFFFF; padding: 16px; border-radius: 8px; overflow-x: auto; font-family: 'Courier New', monospace; font-size: 11px; line-height: 1.4;"><code>// Zoom in to read this small code
class HyperViewer extends StatefulWidget {
  final bool enableZoom;
  final double minScale;
  final double maxScale;

  const HyperViewer({
    this.enableZoom = false,
    this.minScale = 0.5,
    this.maxScale = 4.0,
  });
}</code></pre>

  <h3 style="color: #0277BD; margin-top: 24px;">Float Layout with Zoom</h3>
  <img src="https://picsum.photos/200/200?random=11"
       style="float: left; margin: 0 16px 16px 0; border-radius: 50%; box-shadow: 0 2px 8px rgba(0,0,0,0.1);"
       alt="Circular image">
  <p>
    Zoom functionality works perfectly with float layouts. This circular image is floated to the left,
    and you can zoom in to see details while the text wrapping is preserved.
  </p>
  <p>
    The zoom feature uses Flutter's InteractiveViewer widget, which provides smooth pinch-to-zoom
    and pan gestures across all platforms. It's integrated seamlessly with HyperRender's custom
    rendering engine.
  </p>
  <div style="clear: both;"></div>

  <h3 style="color: #0277BD; margin-top: 24px;">Usage Example</h3>
  <pre style="background: #f5f5f5; padding: 16px; border-radius: 8px; border: 1px solid #e0e0e0; overflow-x: auto; font-family: 'Courier New', monospace; font-size: 14px;"><code>HyperViewer(
  html: htmlContent,
  enableZoom: true,      // Enable zoom
  minScale: 0.5,         // Min zoom level
  maxScale: 4.0,         // Max zoom level
  selectable: true,      // Works with selection!
)</code></pre>

  <div style="background: #E8F5E9; padding: 16px; border-left: 4px solid #4CAF50; margin-top: 24px; border-radius: 4px;">
    <p style="margin: 0; font-weight: bold; color: #2E7D32;">✨ Key Features</p>
    <p style="margin: 8px 0 0 0;">
      • Smooth pinch-to-zoom on all platforms<br>
      • Configurable min/max scale levels<br>
      • Works with text selection<br>
      • Compatible with float layouts<br>
      • Pan to navigate while zoomed<br>
      • Zero performance impact when disabled
    </p>
  </div>

  <h3 style="color: #0277BD; margin-top: 24px;">Table with Zoom</h3>
  <table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 14px;">
    <thead>
      <tr style="background: #0277BD; color: white;">
        <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Feature</th>
        <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Mobile</th>
        <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Desktop</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td style="border: 1px solid #ddd; padding: 8px;">Zoom In</td>
        <td style="border: 1px solid #ddd; padding: 8px;">Pinch out (2 fingers)</td>
        <td style="border: 1px solid #ddd; padding: 8px;">Ctrl + Mouse Wheel Up</td>
      </tr>
      <tr style="background: #f5f5f5;">
        <td style="border: 1px solid #ddd; padding: 8px;">Zoom Out</td>
        <td style="border: 1px solid #ddd; padding: 8px;">Pinch in (2 fingers)</td>
        <td style="border: 1px solid #ddd; padding: 8px;">Ctrl + Mouse Wheel Down</td>
      </tr>
      <tr>
        <td style="border: 1px solid #ddd; padding: 8px;">Pan</td>
        <td style="border: 1px solid #ddd; padding: 8px;">Drag with 1 finger</td>
        <td style="border: 1px solid #ddd; padding: 8px;">Click and drag</td>
      </tr>
      <tr style="background: #f5f5f5;">
        <td style="border: 1px solid #ddd; padding: 8px;">Reset</td>
        <td style="border: 1px solid #ddd; padding: 8px;">Double tap</td>
        <td style="border: 1px solid #ddd; padding: 8px;">Double click</td>
      </tr>
    </tbody>
  </table>

  <p style="font-size: 12px; color: #666; margin-top: 32px;">
    Zoom in on this tiny text to test accessibility. Users with visual impairments can benefit greatly
    from zoom functionality when reading small print or detailed content.
  </p>
</div>
''';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Zoom & Pan Demo'),
        backgroundColor: Theme.of(context).colorScheme.primary,
        foregroundColor: Theme.of(context).colorScheme.onPrimary,
        actions: [
          const Padding(
            padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
            child: Center(
              child: Text(
                'Pinch to Zoom',
                style: TextStyle(fontSize: 12, fontStyle: FontStyle.italic),
              ),
            ),
          ),
        ],
      ),
      body: const Padding(
        padding: EdgeInsets.all(16.0),
        child: HyperViewer(
          html: html,
          selectable: true,
          enableZoom: true,
          minScale: 0.5,
          maxScale: 4.0,
        ),
      ),
    );
  }
}

// =============================================================================
// LIBRARY COMPARISON DEMO
// =============================================================================

class LibraryComparisonDemo extends StatefulWidget {
  const LibraryComparisonDemo({super.key});

  @override
  State<LibraryComparisonDemo> createState() => _LibraryComparisonDemoState();
}

class _LibraryComparisonDemoState extends State<LibraryComparisonDemo>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;

  /// Side-by-side is the DEFAULT view: with tabs the user has to switch back
  /// and forth and hold the previous rendering in memory to spot a difference.
  /// Showing both at once makes it immediate — text wrapping around the float
  /// on the left, stacked above it on the right, in one glance.
  bool _splitView = true;

  /// Which library the split view compares HyperRender against.
  int _rival = 0; // index into _rivalNames
  static const _rivalNames = ['flutter_html', 'fwfh', 'fwfh_core'];

  // Test cases for comparison
  static const List<Map<String, String>> testCases = [
    {
      'name': 'Float Layout',
      'description':
          'Text wrapping around floated images (HyperRender exclusive)',
      'html': '''
<div style="font-family: sans-serif; line-height: 1.6;">
  <img src="https://picsum.photos/100/100?random=50" style="float: left; width: 100px; height: 100px; margin: 0 16px 8px 0; border-radius: 12px; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
  <p>
    This is an example of <strong>float: left</strong>. Text should wrap around the image on the left side naturally.
    When the text is long enough, it continues below the image seamlessly.
  </p>
  <p>
    Additional paragraph that should also respect the float and continue wrapping correctly.
  </p>
</div>
''',
    },
    {
      'name': 'Table with Colspan/Rowspan',
      'description': 'Complex table layout with spanning cells',
      'html': '''
<div style="font-family: sans-serif;">
  <table border="1" style="border-collapse: collapse; width: 100%;">
    <tr style="background: #f5f5f5;">
      <th colspan="2">User Info</th>
      <th rowspan="2">Status</th>
    </tr>
    <tr style="background: #f5f5f5;">
      <th>Name</th>
      <th>Email</th>
    </tr>
    <tr>
      <td>John Doe</td>
      <td>john@example.com</td>
      <td rowspan="2" style="text-align: center; color: green;">Active</td>
    </tr>
    <tr>
      <td>Jane Smith</td>
      <td>jane@example.com</td>
    </tr>
  </table>
</div>
''',
    },
    {
      'name': 'Ruby Annotation',
      'description':
          'Furigana for Japanese — HyperRender exclusive (fwfh & flutter_html show raw text)',
      'html': '''
<div style="font-family: sans-serif; line-height: 2;">
  <p style="font-size: 22px;">
    <ruby>日本語<rt>にほんご</rt></ruby>を<ruby>勉強<rt>べんきょう</rt></ruby>しています。
  </p>
  <p style="font-size: 20px;">
    <ruby>東京<rt>とうきょう</rt></ruby> • <ruby>大阪<rt>おおさか</rt></ruby> • <ruby>京都<rt>きょうと</rt></ruby>
  </p>
</div>
''',
    },
    {
      'name': 'Multiple Floats',
      'description': 'Left and right floats in same paragraph',
      'html': '''
<div style="font-family: sans-serif; line-height: 1.6;">
  <img src="https://picsum.photos/80/80?random=1" style="float: left; width: 80px; height: 80px; margin: 0 12px 8px 0; border-radius: 50%; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
  <img src="https://picsum.photos/80/80?random=2" style="float: right; width: 80px; height: 80px; margin: 0 0 8px 12px; border-radius: 50%; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
  <p>
    This paragraph has images floating on <strong>both sides</strong>. The text should wrap between them naturally, creating a magazine-style layout. This is a challenging layout scenario that tests the rendering engine's float handling capabilities. Additional text to make the wrapping more visible.
  </p>
</div>
''',
    },
    {
      'name': '4-Corner Floats',
      'description':
          '4 images pinned to each corner with text filling the middle',
      'html': '''
<div style="font-family: sans-serif; line-height: 1.6;">
  <img src="https://picsum.photos/90/90?random=41" style="float: left; width: 90px; height: 90px; margin: 0 14px 10px 0; border-radius: 8px; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
  <img src="https://picsum.photos/90/90?random=42" style="float: right; width: 90px; height: 90px; margin: 0 0 10px 14px; border-radius: 8px; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
  <p>
    Two images anchor the <strong>top corners</strong>. Text flows naturally in the space between them, respecting both left and right float boundaries at the same time. This tests simultaneous multi-float layout.
  </p>
  <img src="https://picsum.photos/90/90?random=43" style="float: left; width: 90px; height: 90px; margin: 0 14px 0 0; border-radius: 8px; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
  <img src="https://picsum.photos/90/90?random=44" style="float: right; width: 90px; height: 90px; margin: 0 0 0 14px; border-radius: 8px; padding: 4px; background: white; border: 1px solid #e0e0e0;" />
  <p>
    Two more images anchor the <strong>bottom corners</strong>. The middle column of text continues to wrap correctly even when four floats are active across two rows. This is the most complex float scenario.
  </p>
</div>
''',
    },
    {
      'name': 'Inline Background',
      'description': 'Background wrapping across lines (HyperRender exclusive)',
      'html': '''
<div style="font-family: sans-serif; line-height: 1.8;">
  <p>
    Normal text with
    <span style="background: #E1BEE7; padding: 4px 8px; border-radius: 4px;">
      a highlighted span that wraps to multiple lines when the text is long enough to demonstrate proper inline background behavior
    </span>
    and continues with normal text.
  </p>
</div>
''',
    },
    {
      'name': 'CSS Specificity',
      'description': 'Cascade and inheritance test',
      'html': '''
<div style="font-family: sans-serif; color: #333;">
  <style>
    p { color: blue; }
    .special { color: red; }
    #unique { color: green; }
  </style>
  <p>Normal paragraph (should be blue)</p>
  <p class="special">Class paragraph (should be red)</p>
  <p id="unique">ID paragraph (should be green)</p>
  <p style="color: purple;">Inline style (should be purple)</p>
</div>
''',
    },
    {
      'name': 'Selection Stress',
      'description': 'Large text for selection testing',
      'html': '''
<div style="font-family: sans-serif; line-height: 1.6;">
  <p><strong>Try selecting this text!</strong> The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.</p>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
  <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
  <p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
  <p><em>Selection here spans every paragraph as one document — widget-tree renderers select per widget, so a drag fragments at each boundary.</em></p>
</div>
''',
    },
    {
      'name': 'Wide Table Scroll',
      'description': 'Very wide table (tests horizontal scroll)',
      'html': '''
<div style="font-family: sans-serif;">
  <p style="font-size: 12px; color: #666; margin-bottom: 8px;">This table is wider than screen - try scrolling horizontally</p>
  <table border="1" style="border-collapse: collapse;">
    <tr style="background: #f5f5f5;">
      <th>Column 1</th><th>Column 2</th><th>Column 3</th><th>Column 4</th>
      <th>Column 5</th><th>Column 6</th><th>Column 7</th><th>Column 8</th>
    </tr>
    <tr>
      <td>Data 1.1</td><td>Data 1.2</td><td>Data 1.3</td><td>Data 1.4</td>
      <td>Data 1.5</td><td>Data 1.6</td><td>Data 1.7</td><td>Data 1.8</td>
    </tr>
    <tr>
      <td>Data 2.1</td><td>Data 2.2</td><td>Data 2.3</td><td>Data 2.4</td>
      <td>Data 2.5</td><td>Data 2.6</td><td>Data 2.7</td><td>Data 2.8</td>
    </tr>
  </table>
</div>
''',
    },
    {
      'name': 'Nested Lists',
      'description': 'Multi-level ordered and unordered lists',
      'html': '''
<div style="font-family: sans-serif; line-height: 1.6;">
  <ul>
    <li>First item</li>
    <li>Second item
      <ul>
        <li>Nested item 1</li>
        <li>Nested item 2</li>
      </ul>
    </li>
    <li>Third item</li>
  </ul>
  <ol>
    <li>Ordered first</li>
    <li>Ordered second</li>
    <li>Ordered third</li>
  </ol>
</div>
''',
    },
    {
      'name': '<details>/<summary>',
      'description':
          'Collapsible sections — HyperRender exclusive (fwfh & flutter_html show flat text)',
      'html': '''
<div style="font-family: sans-serif; line-height: 1.6;">
  <details>
    <summary>What is HyperRender?</summary>
    <p>HyperRender is a high-performance HTML/Markdown/Delta rendering engine for Flutter, built on a custom RenderObject rather than the Flutter widget tree.</p>
  </details>
  <details open>
    <summary>Why not use flutter_html?</summary>
    <p>flutter_html does not support CSS floats, ruby annotations, or the &lt;details&gt; element. It also has performance issues with large documents.</p>
  </details>
  <details>
    <summary>When to use fwfh?</summary>
    <p>Use flutter_widget_from_html for moderate complexity HTML where you need a stable, plugin-extensible library. Selection is per-widget, so a drag across mixed content fragments at each widget boundary.</p>
  </details>
</div>
''',
    },
    // ── Real-world page scenarios ──────────────────────────────────────────
    // Moved here from the former standalone "Features Other Libraries Miss"
    // screen. These are whole realistic pages rather than isolated feature
    // probes, and each is tied to a filed FWFH issue — which makes them far
    // more convincing rendered SIDE BY SIDE against the library in question
    // than they were on a screen of their own.
    {
      'name': 'CSS Class Styling',
      'description':
          'Travel blog post with <style> tag rules — FWFH #1525 — <style> tag ignored',
      'html': '''
<style>
  .hero-label {
    background: #1565C0;
    color: white;
    padding: 3px 10px;
    border-radius: 12px;
    font-size: 12px;
    font-weight: bold;
    letter-spacing: 1px;
    text-transform: uppercase;
  }
  .pull-quote {
    border-left: 4px solid #FF6F00;
    background: #FFF8E1;
    padding: 12px 16px;
    margin: 12px 0;
    border-radius: 0 8px 8px 0;
    font-style: italic;
    color: #4E342E;
    font-size: 15px;
  }
  .tag {
    display: inline-block;
    background: #E3F2FD;
    color: #1565C0;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: 11px;
    margin: 2px;
  }
  .highlight { background: #FFF176; padding: 1px 3px; border-radius: 2px; }
</style>

<p><span class="hero-label">✈ Destination</span></p>
<h3 style="margin:8px 0 4px 0;color:#1A237E;">Hội An Ancient Town</h3>
<p style="color:#555;font-size:13px;margin:0 0 10px 0;">Vietnam · UNESCO World Heritage Site</p>

<p>Walking through <span class="highlight">Hội An's lantern-lit streets</span> at dusk feels
like stepping back five centuries. The ancient trading port retains its remarkably intact
historic architecture — a living museum of merchant houses, temples, and assembly halls.</p>

<div class="pull-quote">
  "Every alley holds a story. Every doorway, a dynasty."
</div>

<p>The town's famous <strong>Thu Bồn River</strong> glows amber at sunset as silk lanterns
are released from wooden boats. The culinary scene alone — <em>Cao Lầu, White Rose dumplings,
Bánh Mì</em> — is worth the journey.</p>

<p>
  <span class="tag">🏮 Lanterns</span>
  <span class="tag">🍜 Street Food</span>
  <span class="tag">🛶 River Cruise</span>
  <span class="tag">🎨 Tailoring</span>
</p>
''',
    },
    {
      'name': 'Image Alignment',
      'description':
          'Photography showcase — center, left, right — FWFH #1535 — margin:auto ignored',
      'html': '''
<h4 style="color:#283593;margin:0 0 12px 0;">🌄 Landscape Gallery</h4>

<p style="font-size:13px;color:#555;margin:0 0 8px 0;">Centered — <code>display:block; margin:0 auto</code></p>
<img src="https://picsum.photos/280/140?random=81"
  style="display:block;margin:0 auto;border-radius:8px;border:3px solid #3F51B5;"
  alt="Mountain lake at dawn"/>
<p style="text-align:center;font-size:11px;color:#9E9E9E;margin:4px 0 14px 0;">Mountain lake at dawn</p>

<p style="font-size:13px;color:#555;margin:0 0 8px 0;">Left — <code>margin:0 auto 0 0</code></p>
<img src="https://picsum.photos/180/100?random=82"
  style="display:block;margin:0 auto 0 0;border-radius:8px;border:3px solid #4CAF50;"
  alt="Forest path"/>
<p style="font-size:11px;color:#9E9E9E;margin:4px 0 14px 0;">Forest path, morning mist</p>

<p style="font-size:13px;color:#555;margin:0 0 8px 0;">Right — <code>margin:0 0 0 auto</code></p>
<img src="https://picsum.photos/180/100?random=83"
  style="display:block;margin:0 0 0 auto;border-radius:8px;border:3px solid #FF5722;"
  alt="Coastal sunset"/>
<p style="text-align:right;font-size:11px;color:#9E9E9E;margin:4px 0 0 0;">Coastal sunset, golden hour</p>
''',
    },
    {
      'name': 'Table Alignment & Styling',
      'description':
          'Smartphone comparison table — FWFH #1534, #1446 — text-align in cells broken',
      'html': '''
<h4 style="color:#00695C;margin:0 0 10px 0;">📱 Flagship Comparison 2024</h4>
<table style="width:100%;border-collapse:collapse;font-size:13px;">
  <thead>
    <tr style="background:#004D40;color:white;">
      <th style="padding:10px 8px;text-align:left;border-radius:6px 0 0 0;">Spec</th>
      <th style="padding:10px 8px;text-align:center;">Pixel 9 Pro</th>
      <th style="padding:10px 8px;text-align:center;">iPhone 16 Pro</th>
      <th style="padding:10px 8px;text-align:right;border-radius:0 6px 0 0;">Galaxy S25</th>
    </tr>
  </thead>
  <tbody>
    <tr style="background:#E0F2F1;">
      <td style="padding:9px 8px;text-align:left;font-weight:bold;color:#004D40;">Display</td>
      <td style="padding:9px 8px;text-align:center;">6.3" LTPO OLED</td>
      <td style="padding:9px 8px;text-align:center;">6.3" Super Retina</td>
      <td style="padding:9px 8px;text-align:right;">6.2" Dynamic AMOLED</td>
    </tr>
    <tr>
      <td style="padding:9px 8px;text-align:left;font-weight:bold;color:#004D40;">Camera</td>
      <td style="padding:9px 8px;text-align:center;">50 + 48 + 48 MP</td>
      <td style="padding:9px 8px;text-align:center;">48 + 12 + 12 MP</td>
      <td style="padding:9px 8px;text-align:right;">200 + 10 + 50 MP</td>
    </tr>
    <tr style="background:#E0F2F1;">
      <td style="padding:9px 8px;text-align:left;font-weight:bold;color:#004D40;">Battery</td>
      <td style="padding:9px 8px;text-align:center;">4,700 mAh</td>
      <td style="padding:9px 8px;text-align:center;">3,274 mAh</td>
      <td style="padding:9px 8px;text-align:right;">4,900 mAh</td>
    </tr>
    <tr>
      <td style="padding:9px 8px;text-align:left;font-weight:bold;color:#004D40;">Price</td>
      <td style="padding:9px 8px;text-align:center;color:#2E7D32;font-weight:bold;">\$999</td>
      <td style="padding:9px 8px;text-align:center;color:#1565C0;font-weight:bold;">\$1,199</td>
      <td style="padding:9px 8px;text-align:right;color:#6A1B9A;font-weight:bold;">\$1,099</td>
    </tr>
    <tr style="background:#E0F2F1;">
      <td style="padding:9px 8px;text-align:left;font-weight:bold;color:#004D40;">Rating</td>
      <td style="padding:9px 8px;text-align:center;">⭐⭐⭐⭐⭐</td>
      <td style="padding:9px 8px;text-align:center;">⭐⭐⭐⭐⭐</td>
      <td style="padding:9px 8px;text-align:right;">⭐⭐⭐⭐½</td>
    </tr>
  </tbody>
</table>
''',
    },
    {
      'name': 'Float Layout — Text Wraps Images',
      'description':
          'Magazine-style article. FWFH can\'t do this. — FWFH #1449 — float layout not supported',
      'html': '''
<article style="font-family:Georgia,serif;line-height:1.7;">
  <p style="font-size:11px;color:#999;margin:0 0 6px 0;letter-spacing:1px;text-transform:uppercase;">
    🚀 Space Exploration · June 2025
  </p>
  <h3 style="color:#B71C1C;margin:0 0 12px 0;font-size:17px;">
    NASA's Artemis III: Humanity Returns to the Moon
  </h3>

  <img src="https://picsum.photos/130/130?random=91"
    style="float:left;width:120px;height:120px;margin:2px 14px 8px 0;border-radius:8px;border:2px solid #FFCDD2;"
    alt="Astronaut on lunar surface"/>

  <p style="margin:0 0 10px 0;font-size:14px;">
    For the first time since Apollo 17 in 1972, human footprints mark the lunar regolith.
    Mission commander <strong>Anne McClain</strong> stepped onto the South Pole crater rim
    at 03:47 UTC, greeted by a sky full of stars undimmed by any atmosphere.
  </p>
  <p style="font-size:14px;margin:0 0 10px 0;">
    The crew deployed a <em>portable science station</em> and collected 12 kg of ice core
    samples from permanently shadowed craters — the first direct evidence of accessible
    water ice that could sustain a permanent lunar base.
  </p>

  <div style="clear:both;"></div>

  <img src="https://picsum.photos/130/90?random=92"
    style="float:right;width:140px;margin:2px 0 8px 14px;border-radius:8px;border:2px solid #BBDEFB;"
    alt="Lunar gateway station"/>

  <p style="font-size:14px;margin:0 0 10px 0;">
    The <strong>Lunar Gateway</strong> — orbiting the Moon at a near-rectilinear halo orbit —
    served as a staging point. Unlike the Apollo missions, Artemis III used fully
    <em>reusable hardware</em>, cutting mission cost by an estimated 60%.
  </p>
  <p style="font-size:14px;margin:0;">
    Ground controllers at Johnson Space Center watched the 9-hour surface EVA in real time,
    relayed via a new Ka-band communications satellite in lunar orbit.
    The next mission, Artemis IV, will carry a six-person crew and begin constructing
    the first permanent lunar outpost.
  </p>
  <div style="clear:both;"></div>
</article>
''',
    },
    {
      'name': 'List Styles',
      'description':
          'Recipe card — ingredients + numbered steps — list-style-type variants (circle, square, roman)',
      'html': '''
<div style="font-family:sans-serif;">
  <h3 style="color:#E65100;margin:0 0 4px 0;">🍜 Phở Bò — Beef Noodle Soup</h3>
  <p style="color:#888;font-size:12px;margin:0 0 14px 0;">Prep 30 min · Cook 4 hrs · Serves 4</p>

  <h4 style="color:#BF360C;margin:0 0 6px 0;font-size:14px;">Broth Ingredients</h4>
  <ul style="list-style-type:disc;padding-left:18px;margin:0 0 10px 0;font-size:14px;">
    <li>1.5 kg beef marrow bones, blanched</li>
    <li>500 g beef brisket</li>
    <li>1 large onion, charred</li>
    <li>5 cm fresh ginger, charred</li>
  </ul>

  <h4 style="color:#BF360C;margin:0 0 6px 0;font-size:14px;">Aromatics</h4>
  <ul style="list-style-type:circle;padding-left:18px;margin:0 0 10px 0;font-size:14px;">
    <li>3 star anise · 4 cloves</li>
    <li>1 cinnamon stick · 1 tsp coriander seeds</li>
    <li>2 tbsp fish sauce · rock sugar to taste</li>
  </ul>

  <h4 style="color:#BF360C;margin:0 0 6px 0;font-size:14px;">Toppings</h4>
  <ul style="list-style-type:square;padding-left:18px;margin:0 0 14px 0;font-size:14px;">
    <li>Bean sprouts · Thai basil · lime wedges</li>
    <li>Thinly sliced beef eye round (raw, to cook in broth)</li>
    <li>Hoisin sauce · Sriracha</li>
  </ul>

  <h4 style="color:#1B5E20;margin:0 0 6px 0;font-size:14px;">Steps</h4>
  <ol style="list-style-type:decimal;padding-left:18px;margin:0 0 10px 0;font-size:14px;">
    <li>Blanch bones in boiling water 10 min, rinse.</li>
    <li>Char onion and ginger directly over open flame until blackened.</li>
    <li>Simmer bones in 4 L water for 3 hours, skimming scum.</li>
    <li>Toast aromatics in a dry pan; add to broth with fish sauce.</li>
    <li>Strain broth; season to taste with salt and sugar.</li>
    <li>Cook rice noodles; arrange toppings; ladle hot broth over.</li>
  </ol>

  <p style="background:#FFF8E1;border-left:3px solid #FFC107;padding:8px 12px;
     margin:0;border-radius:0 6px 6px 0;font-size:13px;color:#5D4037;">
    💡 <strong>Tip:</strong> The longer you simmer the bones, the richer the broth.
    Overnight in a slow cooker gives the best results.
  </p>
</div>
''',
    },
    {
      'name': 'Complex Layout — Float + Table + Lists',
      'description':
          'E-commerce product page layout — nested float + table inside float',
      'html': '''
<div style="font-family:sans-serif;font-size:14px;">
  <!-- Product image floated left -->
  <div style="float:left;width:42%;margin-right:14px;background:#F3E5F5;
              border-radius:12px;padding:12px;text-align:center;">
    <img src="https://picsum.photos/160/160?random=77"
      style="width:100%;border-radius:8px;display:block;margin-bottom:8px;"
      alt="Product"/>
    <span style="background:#7B1FA2;color:white;padding:3px 10px;border-radius:10px;
                 font-size:11px;font-weight:bold;">NEW ARRIVAL</span>
  </div>

  <!-- Product details on the right -->
  <div>
    <h3 style="margin:0 0 4px 0;color:#4A148C;">Sony WH-1000XM6</h3>
    <p style="color:#AB47BC;font-size:18px;font-weight:bold;margin:0 0 8px 0;">\$349</p>

    <ul style="list-style-type:none;padding:0;margin:0 0 10px 0;">
      <li style="padding:3px 0;color:#555;">✅ Industry-best ANC</li>
      <li style="padding:3px 0;color:#555;">✅ 40-hour battery life</li>
      <li style="padding:3px 0;color:#555;">✅ LDAC Hi-Res Audio</li>
      <li style="padding:3px 0;color:#555;">✅ Multipoint Bluetooth 5.3</li>
    </ul>

    <!-- Specs mini-table -->
    <table style="width:100%;border-collapse:collapse;font-size:12px;">
      <tr style="background:#EDE7F6;">
        <td style="padding:5px 7px;color:#4A148C;font-weight:bold;">Driver</td>
        <td style="padding:5px 7px;text-align:right;">30 mm</td>
      </tr>
      <tr>
        <td style="padding:5px 7px;color:#4A148C;font-weight:bold;">Weight</td>
        <td style="padding:5px 7px;text-align:right;">250 g</td>
      </tr>
      <tr style="background:#EDE7F6;">
        <td style="padding:5px 7px;color:#4A148C;font-weight:bold;">Codec</td>
        <td style="padding:5px 7px;text-align:right;">LDAC / AAC / SBC</td>
      </tr>
    </table>
  </div>

  <div style="clear:both;"></div>
  <p style="margin:12px 0 0 0;color:#666;font-size:13px;">
    Rated <strong>#1</strong> in over-ear noise-cancelling headphones by
    <em>RTINGS.com</em>, <em>The Verge</em>, and <em>What Hi-Fi?</em> for 2024.
  </p>
</div>
''',
    },
  ];

  int _currentTestIndex = 0;
  final Map<String, Duration> _renderTimes = {};
  bool _showInfoPanel = false;

  String _getExpectedBehavior(int index) {
    switch (index) {
      case 0: // Float Layout
        return '✅ HyperRender: Text wraps around image | ❌ flutter_html, fwfh: No float support — image stacks above text';
      case 1: // Table colspan/rowspan
        return '✅ HyperRender, fwfh: Proper colspan/rowspan | ⚠️ flutter_html: Basic cells only — spanning cells may break';
      case 2: // Ruby Annotation
        return '✅ HyperRender only: rt text renders above base | ❌ flutter_html: rt text appears inline (garbled) | ❌ fwfh: ruby treated as plain text';
      case 3: // Multiple Floats
        return '✅ HyperRender: Text wraps between both floats | ❌ flutter_html, fwfh: Both images stack vertically';
      case 4: // Inline Background
        return '✅ HyperRender: Highlight wraps across lines | ❌ flutter_html, fwfh: Background applied to full block (rectangular)';
      case 5: // CSS Specificity
        return '✅ HyperRender: Full cascade (element → class → ID → inline) | ⚠️ fwfh: Class selectors work, ID unreliable | ❌ flutter_html: <style> tag often ignored';
      case 6: // Selection Stress
        return '✅ HyperRender: continuous selection across the whole document | ⚠️ flutter_html & fwfh: per-widget selection, fragments at each widget boundary';
      case 7: // Wide Table Scroll
        return '✅ HyperRender: Table auto-scales down (FittedBox, min 60%) | ❌ flutter_html, fwfh: Table overflows container';
      case 8: // Nested Lists
        return '✅ HyperRender, fwfh: Proper indentation and markers | ⚠️ flutter_html: Indentation may be inconsistent';
      case 9: // Details/Summary
        return '✅ HyperRender only: Interactive collapsible with open/close | ❌ flutter_html, fwfh: <details> not supported — content shown as plain text';
      default:
        return 'Compare rendering across libraries';
    }
  }

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 4, vsync: this);
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final testCase = testCases[_currentTestIndex];

    return Scaffold(
      appBar: AppBar(
        title: const Text('Library Comparison'),
        backgroundColor: Theme.of(context).colorScheme.primary,
        foregroundColor: Theme.of(context).colorScheme.onPrimary,
        // NOT isScrollable: with four long labels the strip overflowed and
        // scrolled the FIRST tab — HyperRender's own — off screen, so the
        // comparison opened showing only the competitors. Short labels let all
        // four fit, which also makes switching between them one tap away.
        // Material 3 defaults the selected label to colorScheme.primary — the
        // same colour as this AppBar's background — so the SELECTED tab's text
        // was invisible. Since the comparison opens on HyperRender's own tab,
        // the effect was that the app appeared to show only the competitors.
        // Explicit on-primary colours fix it; the labels are also not scrollable
        // so all four stay visible and one tap apart.
        bottom: TabBar(
          controller: _tabController,
          labelColor: Theme.of(context).colorScheme.onPrimary,
          unselectedLabelColor:
              Theme.of(context).colorScheme.onPrimary.withValues(alpha: 0.7),
          indicatorColor: Theme.of(context).colorScheme.onPrimary,
          labelPadding: const EdgeInsets.symmetric(horizontal: 4),
          labelStyle:
              const TextStyle(fontSize: 11, fontWeight: FontWeight.bold),
          unselectedLabelStyle: const TextStyle(fontSize: 11),
          tabs: const [
            Tab(text: 'HyperRender'),
            Tab(text: 'flutter_html'),
            Tab(text: 'fwfh'),
            Tab(text: 'fwfh_core'),
          ],
        ),
      ),
      body: Column(
        children: [
          // Test case selector
          Container(
            padding: const EdgeInsets.all(12),
            color: Colors.grey.shade100,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  children: [
                    Expanded(
                      child: DropdownButton<int>(
                        value: _currentTestIndex,
                        isExpanded: true,
                        items: testCases.asMap().entries.map((e) {
                          return DropdownMenuItem(
                            value: e.key,
                            child: Text(
                                '${e.key + 1}/${testCases.length}: ${e.value['name']!}'),
                          );
                        }).toList(),
                        onChanged: (v) =>
                            setState(() => _currentTestIndex = v!),
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 4),
                Text(
                  testCase['description']!,
                  style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
                ),
                const SizedBox(height: 8),
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: Colors.blue.shade50,
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(color: Colors.blue.shade200),
                  ),
                  child: Row(
                    children: [
                      Icon(Icons.info_outline,
                          size: 16, color: Colors.blue.shade700),
                      const SizedBox(width: 8),
                      Expanded(
                        child: Text(
                          _getExpectedBehavior(_currentTestIndex),
                          style: TextStyle(
                              fontSize: 11, color: Colors.blue.shade900),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),

          // View-mode switch: side-by-side (default) or one library at a time.
          _buildViewModeBar(),

          // Content
          Expanded(
            child: _splitView
                ? _buildSplitView(testCase['html']!)
                : TabBarView(
                    controller: _tabController,
                    children: [
                      _buildHyperRenderTab(testCase['html']!),
                      _buildFlutterHtmlTab(testCase['html']!),
                      _buildFwfhTab(testCase['html']!),
                      _buildFwfhCoreTab(testCase['html']!),
                    ],
                  ),
          ),

          // Feature comparison table (collapsible)
          Container(
            decoration: BoxDecoration(
              color: Colors.grey.shade50,
              border: Border(top: BorderSide(color: Colors.grey.shade300)),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // Toggle button row
                InkWell(
                  onTap: () => setState(() => _showInfoPanel = !_showInfoPanel),
                  child: Padding(
                    padding:
                        const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
                    child: Row(
                      children: [
                        Icon(
                          Icons.table_chart_outlined,
                          size: 16,
                          color: Colors.grey.shade700,
                        ),
                        const SizedBox(width: 6),
                        Text(
                          'Feature Comparison',
                          style: TextStyle(
                            fontWeight: FontWeight.bold,
                            fontSize: 13,
                            color: Colors.grey.shade800,
                          ),
                        ),
                        const Spacer(),
                        Icon(
                          _showInfoPanel
                              ? Icons.expand_less
                              : Icons.expand_more,
                          color: Colors.grey.shade600,
                        ),
                      ],
                    ),
                  ),
                ),
                if (_showInfoPanel)
                  ConstrainedBox(
                    constraints: const BoxConstraints(maxHeight: 300),
                    child: SingleChildScrollView(
                      padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          _buildFeatureTable(),
                          const SizedBox(height: 12),
                          _buildPerformanceChart(),
                        ],
                      ),
                    ),
                  ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  /// Toggle between the side-by-side view and the one-library-at-a-time tabs,
  /// plus (in split mode) which rival occupies the right-hand column.
  Widget _buildViewModeBar() {
    final scheme = Theme.of(context).colorScheme;
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      color: scheme.surfaceContainerHighest.withValues(alpha: 0.4),
      child: Row(
        children: [
          SegmentedButton<bool>(
            style: const ButtonStyle(
              visualDensity: VisualDensity.compact,
              tapTargetSize: MaterialTapTargetSize.shrinkWrap,
            ),
            segments: const [
              ButtonSegment(
                value: true,
                icon: Icon(Icons.vertical_split, size: 16),
                label: Text('Side by side', style: TextStyle(fontSize: 12)),
              ),
              ButtonSegment(
                value: false,
                icon: Icon(Icons.tab, size: 16),
                label: Text('Tabs', style: TextStyle(fontSize: 12)),
              ),
            ],
            selected: {_splitView},
            onSelectionChanged: (s) => setState(() => _splitView = s.first),
          ),
          if (_splitView) ...[
            const SizedBox(width: 12),
            Expanded(
              child: DropdownButton<int>(
                value: _rival,
                isExpanded: true,
                isDense: true,
                style: TextStyle(fontSize: 12, color: scheme.onSurface),
                items: [
                  for (var i = 0; i < _rivalNames.length; i++)
                    DropdownMenuItem(
                      value: i,
                      child: Text('vs ${_rivalNames[i]}',
                          overflow: TextOverflow.ellipsis),
                    ),
                ],
                onChanged: (v) => setState(() => _rival = v!),
              ),
            ),
          ],
        ],
      ),
    );
  }

  /// The same HTML rendered by HyperRender and by the selected rival, at the
  /// same time, in two columns. Seeing both simultaneously is the whole point:
  /// a difference like "text flows around the image" vs "image sits on its own
  /// line" reads instantly, with nothing to remember between taps.
  Widget _buildSplitView(String html) {
    final rivalWidget = switch (_rival) {
      0 => flutter_html.Html(data: html),
      1 => fwfh.HtmlWidget(html),
      _ => fwfh_core.HtmlWidget(html),
    };

    return Row(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Expanded(
          child: _splitColumn(
            title: 'HyperRender',
            accent: const Color(0xFF1A56DB),
            child: HyperViewer(
              html: html,
              mode: HyperRenderMode.sync,
              selectable: true,
            ),
          ),
        ),
        const VerticalDivider(width: 1, thickness: 1),
        Expanded(
          child: _splitColumn(
            title: _rivalNames[_rival],
            accent: Colors.grey.shade600,
            child: SingleChildScrollView(
              padding: const EdgeInsets.all(8),
              child: rivalWidget,
            ),
          ),
        ),
      ],
    );
  }

  Widget _splitColumn({
    required String title,
    required Color accent,
    required Widget child,
  }) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Container(
          padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
          color: accent.withValues(alpha: 0.12),
          child: Text(
            title,
            textAlign: TextAlign.center,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(
                fontSize: 12, fontWeight: FontWeight.bold, color: accent),
          ),
        ),
        // ClipRect: a rival that overflows its column must not paint over the
        // other one — otherwise the comparison itself would be misleading.
        Expanded(child: ClipRect(child: child)),
      ],
    );
  }

  Widget _buildHyperRenderTab(String html) {
    return _buildTimedWidget(
      'HyperRender',
      () {
        return HyperViewer(
          html: html,
          mode: HyperRenderMode.sync,
          selectable: true,
        );
      },
    );
  }

  Widget _buildFlutterHtmlTab(String html) {
    return _buildTimedWidget(
      'flutter_html',
      () => ClipRect(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: flutter_html.Html(data: html),
        ),
      ),
    );
  }

  Widget _buildFwfhTab(String html) {
    return _buildTimedWidget(
      'fwfh',
      () => ClipRect(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: fwfh.HtmlWidget(html),
        ),
      ),
    );
  }

  Widget _buildFwfhCoreTab(String html) {
    return _buildTimedWidget(
      'fwfh_core',
      () => ClipRect(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: fwfh_core.HtmlWidget(html),
        ),
      ),
    );
  }

  Widget _buildTimedWidget(String name, Widget Function() builder) {
    final stopwatch = Stopwatch()..start();
    final widget = builder();
    stopwatch.stop();
    _renderTimes[name] = stopwatch.elapsed;

    return Column(
      children: [
        Container(
          padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
          color: Colors.blue.shade50,
          child: Row(
            children: [
              Icon(Icons.timer, size: 16, color: Colors.blue.shade700),
              const SizedBox(width: 4),
              Text(
                'Build: ${stopwatch.elapsedMicroseconds}µs',
                style: TextStyle(fontSize: 12, color: Colors.blue.shade700),
              ),
            ],
          ),
        ),
        Expanded(child: widget),
      ],
    );
  }

  Widget _buildFeatureTable() {
    // Format: (Feature, HyperRender, flutter_html, fwfh, fwfh_core)
    // Accuracy: verified against library source, GitHub issues, and live rendering
    const features = [
      ('Float layout', true, false, false, false),
      ('Table colspan/rowspan', true, false, true, true),
      (
        'Ruby / furigana',
        true,
        false,
        false,
        false
      ), // fwfh #1449 — not supported
      ('Multiple floats', true, false, false, false),
      ('Inline bg wrap', true, false, false, false),
      (
        '<style> tag CSS',
        true,
        false,
        true,
        true
      ), // flutter_html ignores; fwfh partial
      (
        'CSS specificity',
        true,
        false,
        true,
        true
      ), // fwfh partial; flutter_html minimal
      (
        '<details>/<summary>',
        true,
        false,
        false,
        false
      ), // HyperRender exclusive
      (
        'Whole-document selection',
        true,
        false,
        false,
        false
      ), // one hit-test tree vs per-widget selection in the others
      ('Custom widgets', true, true, true, true),
    ];

    return Table(
      columnWidths: const {
        0: FlexColumnWidth(2),
        1: FlexColumnWidth(1),
        2: FlexColumnWidth(1),
        3: FlexColumnWidth(1),
        4: FlexColumnWidth(1),
      },
      children: [
        TableRow(
          decoration: BoxDecoration(color: Colors.grey.shade200),
          children: const [
            Padding(
              padding: EdgeInsets.all(4),
              child: Text('Feature',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 11)),
            ),
            Center(
                child: Text('HR',
                    style:
                        TextStyle(fontWeight: FontWeight.bold, fontSize: 11))),
            Center(
                child: Text('f_h',
                    style:
                        TextStyle(fontWeight: FontWeight.bold, fontSize: 11))),
            Center(
                child: Text('fwfh',
                    style:
                        TextStyle(fontWeight: FontWeight.bold, fontSize: 11))),
            Center(
                child: Text('core',
                    style:
                        TextStyle(fontWeight: FontWeight.bold, fontSize: 11))),
          ],
        ),
        ...features.map((f) => TableRow(
              children: [
                Padding(
                  padding: const EdgeInsets.all(4),
                  child: Text(f.$1, style: const TextStyle(fontSize: 11)),
                ),
                _buildCheckmark(f.$2),
                _buildCheckmark(f.$3),
                _buildCheckmark(f.$4),
                _buildCheckmark(f.$5),
              ],
            )),
      ],
    );
  }

  Widget _buildCheckmark(bool supported) {
    return Center(
      child: Icon(
        supported ? Icons.check_circle : Icons.cancel,
        size: 16,
        color: supported ? Colors.green : Colors.red.shade300,
      ),
    );
  }

  Widget _buildPerformanceChart() {
    if (_renderTimes.isEmpty) return const SizedBox.shrink();
    final hyperTime = _renderTimes['HyperRender'];
    final htmlTime = _renderTimes['flutter_html'];
    final fwfhTime = _renderTimes['fwfh'];
    final fwfhCoreTime = _renderTimes['fwfh_core'];
    final maxTime = [hyperTime, htmlTime, fwfhTime, fwfhCoreTime]
        .whereType<Duration>()
        .map((d) => d.inMicroseconds)
        .fold(1, (a, b) => a > b ? a : b);

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'Widget-tree build time (µs):',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        const Text(
          'Measures Dart widget construction only — actual layout/paint is async.',
          style: TextStyle(fontSize: 10, color: Colors.black54),
        ),
        const SizedBox(height: 8),
        _buildLibBar('HyperRender', hyperTime, maxTime, Colors.green),
        _buildLibBar('flutter_html', htmlTime, maxTime, Colors.orange),
        _buildLibBar('fwfh', fwfhTime, maxTime, Colors.blue),
        _buildLibBar('fwfh_core', fwfhCoreTime, maxTime, Colors.purple),
      ],
    );
  }

  Widget _buildLibBar(String name, Duration? time, int maxUs, Color color) {
    final us = time?.inMicroseconds ?? 0;
    final ratio = maxUs > 0 ? us / maxUs : 0.0;
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 3),
      child: Row(
        children: [
          SizedBox(
            width: 90,
            child: Text(name, style: const TextStyle(fontSize: 11)),
          ),
          Expanded(
            child: Stack(
              children: [
                Container(
                  height: 16,
                  decoration: BoxDecoration(
                    color: Colors.grey.shade200,
                    borderRadius: BorderRadius.circular(3),
                  ),
                ),
                FractionallySizedBox(
                  widthFactor: ratio.clamp(0.02, 1.0),
                  child: Container(
                    height: 16,
                    decoration: BoxDecoration(
                      color: color,
                      borderRadius: BorderRadius.circular(3),
                    ),
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(width: 6),
          SizedBox(
            width: 52,
            child: Text('$usµs',
                style: const TextStyle(fontSize: 10),
                textAlign: TextAlign.right),
          ),
        ],
      ),
    );
  }
}

// =============================================================================
// QUILL DELTA DEMO
// =============================================================================

class QuillDeltaDemo extends StatelessWidget {
  const QuillDeltaDemo({super.key});

  /// Sample Quill Delta JSON demonstrating various features
  static const deltaJson = '''
{
  "ops": [
    { "insert": "Building a Real-time Chat App with Flutter\\n", "attributes": { "header": 1 } },
    { "insert": "A comprehensive guide to implementing WebSocket-based messaging\\n", "attributes": { "color": "#666666", "italic": true } },
    { "insert": "\\n" },

    { "insert": "Introduction\\n", "attributes": { "header": 2 } },
    { "insert": "In this tutorial, we'll build a " },
    { "insert": "production-ready", "attributes": { "bold": true, "color": "#E91E63" } },
    { "insert": " chat application using " },
    { "insert": "Flutter", "attributes": { "bold": true, "color": "#02569B" } },
    { "insert": " and " },
    { "insert": "WebSockets", "attributes": { "bold": true, "color": "#FF6F00" } },
    { "insert": ". This is the same architecture used by apps like " },
    { "insert": "Slack", "attributes": { "italic": true, "link": "https://slack.com" } },
    { "insert": ", " },
    { "insert": "Discord", "attributes": { "italic": true, "link": "https://discord.com" } },
    { "insert": ", and " },
    { "insert": "WhatsApp", "attributes": { "italic": true, "link": "https://whatsapp.com" } },
    { "insert": ".\\n\\n" },

    { "insert": "Prerequisites\\n", "attributes": { "header": 2 } },
    { "insert": "Flutter SDK 3.0+", "attributes": { "bold": true } },
    { "insert": " - Latest stable version recommended" },
    { "insert": "\\n", "attributes": { "list": "bullet" } },
    { "insert": "Dart 3.0+", "attributes": { "bold": true } },
    { "insert": " - With null safety enabled" },
    { "insert": "\\n", "attributes": { "list": "bullet" } },
    { "insert": "Basic knowledge of ", "attributes": {} },
    { "insert": "async/await", "attributes": { "background": "#FFF3E0", "color": "#E65100" } },
    { "insert": " patterns" },
    { "insert": "\\n", "attributes": { "list": "bullet" } },
    { "insert": "Familiarity with ", "attributes": {} },
    { "insert": "Provider", "attributes": { "background": "#E3F2FD", "color": "#1565C0" } },
    { "insert": " or " },
    { "insert": "Riverpod", "attributes": { "background": "#E8F5E9", "color": "#2E7D32" } },
    { "insert": " state management" },
    { "insert": "\\n", "attributes": { "list": "bullet" } },
    { "insert": "\\n" },

    { "insert": "Key Features We'll Implement\\n", "attributes": { "header": 2 } },
    { "insert": "Real-time messaging with WebSocket", "attributes": {} },
    { "insert": "\\n", "attributes": { "list": "ordered" } },
    { "insert": "Message persistence with SQLite", "attributes": {} },
    { "insert": "\\n", "attributes": { "list": "ordered" } },
    { "insert": "Push notifications (FCM)", "attributes": {} },
    { "insert": "\\n", "attributes": { "list": "ordered" } },
    { "insert": "Typing indicators & read receipts", "attributes": {} },
    { "insert": "\\n", "attributes": { "list": "ordered" } },
    { "insert": "Image & file sharing", "attributes": {} },
    { "insert": "\\n", "attributes": { "list": "ordered" } },
    { "insert": "End-to-end encryption ", "attributes": {} },
    { "insert": "(E2EE)", "attributes": { "bold": true, "color": "#D32F2F" } },
    { "insert": "\\n", "attributes": { "list": "ordered" } },
    { "insert": "\\n" },

    { "insert": "The beauty of simplicity is that it allows complexity to emerge naturally.\\n", "attributes": {} },
    { "insert": "— John Maeda, The Laws of Simplicity", "attributes": { "italic": true } },
    { "insert": "\\n", "attributes": { "blockquote": true } },
    { "insert": "\\n" },

    { "insert": "Architecture Overview\\n", "attributes": { "header": 2 } },
    { "insert": "Our app follows the ", "attributes": {} },
    { "insert": "Clean Architecture", "attributes": { "bold": true } },
    { "insert": " pattern with three main layers:\\n\\n" },

    { "insert": "Presentation Layer", "attributes": { "header": 3 } },
    { "insert": "\\nWidgets, Pages, and State Management. Uses " },
    { "insert": "BLoC pattern", "attributes": { "background": "#FCE4EC", "color": "#C2185B" } },
    { "insert": " for reactive UI updates.\\n\\n" },

    { "insert": "Domain Layer", "attributes": { "header": 3 } },
    { "insert": "\\nBusiness logic, Use Cases, and Entities. " },
    { "insert": "Framework-independent", "attributes": { "underline": true } },
    { "insert": " - can be tested without Flutter.\\n\\n" },

    { "insert": "Data Layer", "attributes": { "header": 3 } },
    { "insert": "\\nRepositories, Data Sources, and Models. Handles " },
    { "insert": "API calls", "attributes": { "italic": true } },
    { "insert": ", " },
    { "insert": "caching", "attributes": { "italic": true } },
    { "insert": ", and " },
    { "insert": "local storage", "attributes": { "italic": true } },
    { "insert": ".\\n\\n" },

    { "insert": "Core Implementation\\n", "attributes": { "header": 2 } },
    { "insert": "Here's our WebSocket service implementation:\\n\\n" },
    { "insert": "class ChatWebSocket {\\n  late WebSocketChannel _channel;\\n  final _messageController = StreamController<Message>.broadcast();\\n  \\n  Stream<Message> get messages => _messageController.stream;\\n  \\n  Future<void> connect(String url, String token) async {\\n    _channel = WebSocketChannel.connect(\\n      Uri.parse(url),\\n      protocols: ['chat-protocol'],\\n    );\\n    \\n    // Authenticate\\n    _channel.sink.add(jsonEncode({'type': 'auth', 'token': token}));\\n    \\n    // Listen for messages\\n    _channel.stream.listen(\\n      (data) => _handleMessage(jsonDecode(data)),\\n      onError: (e) => _handleError(e),\\n      onDone: () => _handleDisconnect(),\\n    );\\n  }\\n  \\n  void sendMessage(String roomId, String content) {\\n    _channel.sink.add(jsonEncode({\\n      'type': 'message',\\n      'roomId': roomId,\\n      'content': content,\\n      'timestamp': DateTime.now().toIso8601String(),\\n    }));\\n  }\\n}" },
    { "insert": "\\n", "attributes": { "code-block": "dart" } },
    { "insert": "\\n" },

    { "insert": "Performance Metrics\\n", "attributes": { "header": 2 } },
    { "insert": "Our implementation achieves impressive benchmarks:\\n\\n" },

    { "insert": "Message Latency", "attributes": { "bold": true } },
    { "insert": "\\n" },
    { "insert": "< 50ms", "attributes": { "size": "large", "color": "#4CAF50", "bold": true } },
    { "insert": " average round-trip time" },
    { "insert": "\\n", "attributes": { "align": "center" } },
    { "insert": "\\n" },

    { "insert": "Memory Usage", "attributes": { "bold": true } },
    { "insert": "\\n" },
    { "insert": "~15MB", "attributes": { "size": "large", "color": "#2196F3", "bold": true } },
    { "insert": " with 10,000 cached messages" },
    { "insert": "\\n", "attributes": { "align": "center" } },
    { "insert": "\\n" },

    { "insert": "Battery Impact", "attributes": { "bold": true } },
    { "insert": "\\n" },
    { "insert": "< 2%", "attributes": { "size": "large", "color": "#FF9800", "bold": true } },
    { "insert": " per hour of active use" },
    { "insert": "\\n", "attributes": { "align": "center" } },
    { "insert": "\\n\\n" },

    { "insert": "Important Security Note\\n", "attributes": { "header": 2 } },
    { "insert": "Never store API keys or tokens in client-side code!", "attributes": { "bold": true, "color": "#D32F2F" } },
    { "insert": " Use secure token exchange via your backend server. All sensitive operations should be validated server-side." },
    { "insert": "\\n", "attributes": { "blockquote": true } },
    { "insert": "\\n" },

    { "insert": { "image": "https://picsum.photos/600/300" } },
    { "insert": "\\n" },
    { "insert": "Figure 1: App architecture diagram showing data flow between layers", "attributes": { "italic": true, "color": "#666666", "size": "small" } },
    { "insert": "\\n", "attributes": { "align": "center" } },
    { "insert": "\\n\\n" },

    { "insert": "What's Next?\\n", "attributes": { "header": 2 } },
    { "insert": "In " },
    { "insert": "Part 2", "attributes": { "bold": true, "link": "#part2" } },
    { "insert": ", we'll implement:\\n" },
    { "insert": "Message encryption with ", "attributes": {} },
    { "insert": "libsodium", "attributes": { "background": "#FFEBEE", "color": "#B71C1C" } },
    { "insert": "\\n", "attributes": { "list": "bullet" } },
    { "insert": "Offline-first sync with ", "attributes": {} },
    { "insert": "Drift", "attributes": { "background": "#E8EAF6", "color": "#283593" } },
    { "insert": "\\n", "attributes": { "list": "bullet" } },
    { "insert": "Push notifications via ", "attributes": {} },
    { "insert": "Firebase Cloud Messaging", "attributes": { "background": "#FFF8E1", "color": "#FF6F00" } },
    { "insert": "\\n", "attributes": { "list": "bullet" } },
    { "insert": "\\n\\n" },

    { "insert": "This Delta content was rendered with ", "attributes": { "color": "#666666" } },
    { "insert": "HyperRender", "attributes": { "bold": true, "color": "#6200EE" } },
    { "insert": " - demonstrating full Quill.js compatibility!", "attributes": { "color": "#666666" } },
    { "insert": "\\n" }
  ]
}
''';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Quill Delta Demo'),
        backgroundColor: Theme.of(context).colorScheme.primary,
        foregroundColor: Theme.of(context).colorScheme.onPrimary,
        actions: [
          IconButton(
            icon: const Icon(Icons.code),
            tooltip: 'View Delta JSON',
            onPressed: () {
              showDialog(
                context: context,
                builder: (context) => AlertDialog(
                  title: const Text('Delta JSON'),
                  content: SizedBox(
                    width: double.maxFinite,
                    height: 400,
                    child: SingleChildScrollView(
                      child: SelectableText(
                        deltaJson,
                        style: const TextStyle(
                            fontFamily: 'monospace', fontSize: 12),
                      ),
                    ),
                  ),
                  actions: [
                    TextButton(
                      onPressed: () => Navigator.pop(context),
                      child: const Text('Close'),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: HyperViewer.delta(
          delta: deltaJson,
          selectable: true,
          showSelectionMenu: false,
          onError: (e, st) => debugPrint('QuillDeltaDemo error: $e\n$st'),
        ),
      ),
    );
  }
}

// =============================================================================
// MARKDOWN DEMO
// =============================================================================

class MarkdownDemo extends StatelessWidget {
  const MarkdownDemo({super.key});

  static const markdown = '''
# Flutter State Management: A Complete Guide

> **Last updated:** December 2024 | **Reading time:** 12 min | **Level:** Intermediate

State management is one of the most discussed topics in Flutter development. This guide covers everything from basic concepts to advanced patterns used in production apps.

---

## Table of Contents

1. [Understanding State](#understanding-state)
2. [Built-in Solutions](#built-in-solutions)
3. [Popular Libraries](#popular-libraries)
4. [Comparison & Benchmarks](#comparison)
5. [Best Practices](#best-practices)

---

## Understanding State

In Flutter, **state** refers to any data that can change over time and affects what the UI displays. There are two types:

### Ephemeral State
- *Local* to a single widget
- Examples: current page in PageView, animation progress
- Solution: `StatefulWidget` + `setState()`

### App State
- *Shared* across multiple widgets
- Examples: user authentication, shopping cart, preferences
- Solution: State management libraries

> 💡 **Rule of thumb:** If you need to access the same state from multiple places in your widget tree, it's probably app state.

---

## Built-in Solutions

### InheritedWidget

The foundation of Flutter's reactivity system:

```dart
class AppState extends InheritedWidget {
  final int counter;
  final VoidCallback increment;

  const AppState({
    required this.counter,
    required this.increment,
    required Widget child,
  }) : super(child: child);

  static AppState of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<AppState>()!;
  }

  @override
  bool updateShouldNotify(AppState oldWidget) {
    return counter != oldWidget.counter;
  }
}
```

### ValueNotifier + ValueListenableBuilder

Great for simple reactive values:

```dart
final counter = ValueNotifier<int>(0);

ValueListenableBuilder<int>(
  valueListenable: counter,
  builder: (context, value, child) {
    return Text('Count: \$value');
  },
)
```

---

## Popular Libraries

### Provider / Riverpod

The **recommended** solution by the Flutter team:

| Feature | Provider | Riverpod |
|---------|----------|----------|
| Compile-time safety | ❌ | ✅ |
| No BuildContext needed | ❌ | ✅ |
| Auto-dispose | Manual | ✅ |
| Testing | Good | Excellent |
| Learning curve | Low | Medium |

```dart
// Riverpod example
final counterProvider = StateProvider<int>((ref) => 0);

class CounterWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Text('Count: \$count');
  }
}
```

### BLoC / Cubit

**Business Logic Component** - great for complex apps:

```dart
class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
  void reset() => emit(0);
}

// In widget
BlocBuilder<CounterCubit, int>(
  builder: (context, count) {
    return Text('Count: \$count');
  },
)
```

### GetX

Minimalist approach with maximum features:

```dart
class Controller extends GetxController {
  var count = 0.obs;
  void increment() => count++;
}

// In widget - no builder needed!
Obx(() => Text('Count: \${controller.count}'))
```

---

## Performance Benchmarks

We tested each solution with 10,000 state updates:

| Library | Avg. Rebuild Time | Memory | Bundle Size |
|---------|-------------------|--------|-------------|
| setState | 0.8ms | Low | 0 KB |
| Provider | 1.2ms | Low | +12 KB |
| Riverpod | 1.1ms | Low | +45 KB |
| BLoC | 1.5ms | Medium | +89 KB |
| GetX | 0.9ms | Medium | +120 KB |
| MobX | 1.8ms | High | +200 KB |

> ⚠️ **Note:** These benchmarks are synthetic. Real-world performance depends on your specific use case.

---

## Best Practices

### ✅ Do

- **Keep state minimal** - Only store what you need
- **Separate concerns** - UI state vs business logic
- **Use selectors** - Rebuild only what changed
- **Test your state** - Unit test state logic separately

### ❌ Don't

- ~~Put everything in global state~~
- ~~Mix UI logic with business logic~~
- ~~Ignore memory leaks~~ (dispose your controllers!)
- ~~Over-engineer simple apps~~

---

## Decision Flowchart

```
Is state used by single widget?
├─ YES → setState() or ValueNotifier
└─ NO → Is it a simple app?
        ├─ YES → Provider
        └─ NO → Do you need strong typing?
                ├─ YES → Riverpod or BLoC
                └─ NO → GetX (if you prefer simplicity)
```

---

## Real-world Example

Here's how **Instagram-like** feed would be structured with Riverpod:

```dart
// Providers
final feedProvider = FutureProvider<List<Post>>((ref) async {
  final api = ref.read(apiProvider);
  return api.fetchFeed();
});

final likedPostsProvider = StateProvider<Set<String>>((ref) => {});

// Derived state
final isLikedProvider = Provider.family<bool, String>((ref, postId) {
  final likedPosts = ref.watch(likedPostsProvider);
  return likedPosts.contains(postId);
});

// Widget
class FeedScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final feedAsync = ref.watch(feedProvider);

    return feedAsync.when(
      data: (posts) => ListView.builder(
        itemCount: posts.length,
        itemBuilder: (_, i) => PostCard(post: posts[i]),
      ),
      loading: () => const ShimmerList(),
      error: (e, _) => ErrorWidget(message: e.toString()),
    );
  }
}
```

---

## Additional Resources

- 📚 [Official Flutter Docs](https://flutter.dev/docs/development/data-and-backend/state-mgmt)
- 🎥 [Flutter State Management - Video Course](https://example.com)
- 💬 [Flutter Community Discord](https://discord.gg/flutter)
- 📦 [Awesome Flutter](https://github.com/Solido/awesome-flutter)

---

![Architecture Diagram](https://picsum.photos/600/350)
*Figure: Clean Architecture with State Management layers*

---

*This Markdown content was rendered with* ***HyperRender*** *- demonstrating full GitHub Flavored Markdown support including tables, code blocks, task lists, and more!*
''';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Markdown Demo'),
        backgroundColor: Theme.of(context).colorScheme.primary,
        foregroundColor: Theme.of(context).colorScheme.onPrimary,
        actions: [
          IconButton(
            icon: const Icon(Icons.code),
            tooltip: 'View Markdown Source',
            onPressed: () {
              showDialog(
                context: context,
                builder: (context) => AlertDialog(
                  title: const Text('Markdown Source'),
                  content: SizedBox(
                    width: double.maxFinite,
                    height: 400,
                    child: SingleChildScrollView(
                      child: SelectableText(
                        markdown,
                        style: const TextStyle(
                            fontFamily: 'monospace', fontSize: 12),
                      ),
                    ),
                  ),
                  actions: [
                    TextButton(
                      onPressed: () => Navigator.pop(context),
                      child: const Text('Close'),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: HyperViewer.markdown(
          markdown: markdown,
          selectable: true,
          showSelectionMenu: false,
          onError: (e, st) => debugPrint('MarkdownDemo error: $e\n$st'),
        ),
      ),
    );
  }
}

// =============================================================================
// HUB PAGES — sub-navigation screens grouping related demos
// =============================================================================

Widget _hubCard(
  BuildContext context, {
  required IconData icon,
  required String title,
  required String subtitle,
  required Color color,
  required VoidCallback onTap,
}) {
  return Container(
    margin: const EdgeInsets.only(bottom: 10),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(14),
      boxShadow: [
        BoxShadow(
            color: Colors.black.withValues(alpha: 0.05),
            blurRadius: 8,
            offset: const Offset(0, 2)),
      ],
    ),
    child: Material(
      color: Colors.transparent,
      borderRadius: BorderRadius.circular(14),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(14),
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
          child: Row(
            children: [
              Container(
                width: 48,
                height: 48,
                decoration: BoxDecoration(
                  color: color.withValues(alpha: 0.12),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Icon(icon, color: color, size: 24),
              ),
              const SizedBox(width: 14),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(title,
                        style: const TextStyle(
                            fontSize: 15,
                            fontWeight: FontWeight.w600,
                            color: Color(0xFF1A1A2E),
                            letterSpacing: -0.1)),
                    const SizedBox(height: 3),
                    Text(subtitle,
                        style: TextStyle(
                            fontSize: 13,
                            color: Colors.grey.shade500,
                            height: 1.3)),
                  ],
                ),
              ),
              const SizedBox(width: 8),
              Icon(Icons.chevron_right_rounded,
                  color: Colors.grey.shade300, size: 22),
            ],
          ),
        ),
      ),
    ),
  );
}

AppBar _hubAppBar(BuildContext context, String title) => AppBar(
      title: Text(title),
      backgroundColor: Theme.of(context).colorScheme.primary,
      foregroundColor: Theme.of(context).colorScheme.onPrimary,
      automaticallyImplyLeading: true,
    );

class _TablesHubPage extends StatelessWidget {
  const _TablesHubPage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _hubAppBar(context, 'Tables'),
      backgroundColor: Theme.of(context).colorScheme.surface,
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _hubCard(context,
              icon: Icons.table_chart,
              title: 'Basic Tables',
              subtitle:
                  'Simple, wide, nested, and complex tables with auto column sizing',
              color: DemoColors.primary,
              onTap: () => Navigator.push(context,
                  MaterialPageRoute(builder: (_) => const TableDemo()))),
          _hubCard(context,
              icon: Icons.table_chart_outlined,
              title: 'Wide Table Strategies',
              subtitle:
                  'Tables wider than the screen — scroll, shrink-to-fit, or auto scale',
              color: Colors.teal,
              onTap: () => Navigator.push(context,
                  MaterialPageRoute(builder: (_) => const SmartTableDemo()))),
        ],
      ),
    );
  }
}

class _MediaHubPage extends StatelessWidget {
  const _MediaHubPage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _hubAppBar(context, 'Images & Video'),
      backgroundColor: Theme.of(context).colorScheme.surface,
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _hubCard(context,
              icon: Icons.broken_image,
              title: 'Images',
              subtitle:
                  'Loading placeholders, error fallback, network and asset images',
              color: DemoColors.warning,
              onTap: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (_) => const ImageHandlingDemo()))),
          _hubCard(context,
              icon: Icons.zoom_in,
              title: 'Zoom & Pan',
              subtitle:
                  'Pinch to zoom and pan images — works with float and inline images',
              color: DemoColors.warning,
              onTap: () => Navigator.push(context,
                  MaterialPageRoute(builder: (_) => const ZoomDemo()))),
          _hubCard(context,
              icon: Icons.play_circle_filled,
              title: 'Video',
              subtitle:
                  'Video thumbnail with play button — tap to open in external player',
              color: DemoColors.warning,
              onTap: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (_) => const ImprovedVideoDemo()))),
        ],
      ),
    );
  }
}

class _WidgetIntegrationHubPage extends StatelessWidget {
  const _WidgetIntegrationHubPage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _hubAppBar(context, 'Widget Injection & Animation'),
      backgroundColor: Theme.of(context).colorScheme.surface,
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _hubCard(context,
              icon: Icons.widgets,
              title: 'Widget Injection',
              subtitle:
                  'Embed live Flutter widgets (charts, buttons, sliders) inside HTML content',
              color: DemoColors.secondary,
              onTap: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (_) => const WidgetInjectionDemo()))),
          _hubCard(context,
              icon: Icons.animation,
              title: 'Animated Widgets',
              subtitle:
                  'Injected widgets can animate — fade, slide, bounce inside HTML',
              color: DemoColors.accent,
              onTap: () => Navigator.push(context,
                  MaterialPageRoute(builder: (_) => const AnimationDemo()))),
        ],
      ),
    );
  }
}

class _InputFormatsHubPage extends StatelessWidget {
  const _InputFormatsHubPage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _hubAppBar(context, 'Input Formats'),
      backgroundColor: Theme.of(context).colorScheme.surface,
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _hubCard(context,
              icon: Icons.text_snippet,
              title: 'Markdown',
              subtitle:
                  'Render .md content — headings, lists, bold, code, links',
              color: DemoColors.accent,
              onTap: () => Navigator.push(context,
                  MaterialPageRoute(builder: (_) => const MarkdownDemo()))),
          _hubCard(context,
              icon: Icons.data_object,
              title: 'Quill Delta',
              subtitle: 'Render JSON output from the Quill.js rich text editor',
              color: DemoColors.accent,
              onTap: () => Navigator.push(context,
                  MaterialPageRoute(builder: (_) => const QuillDeltaDemo()))),
          // Registered rather than deleted during the demo cleanup: baseUrl
          // resolution is a real feature and this was its ONLY coverage, but
          // the screen had never been wired to any navigation.
          _hubCard(context,
              icon: Icons.link,
              title: 'Base URL & Links',
              subtitle:
                  'Resolve relative src/href against a base URL; handle link taps',
              color: DemoColors.accent,
              onTap: () => Navigator.push(context,
                  MaterialPageRoute(builder: (_) => const BaseUrlDemo()))),
        ],
      ),
    );
  }
}

class _ComparisonPerfHubPage extends StatelessWidget {
  const _ComparisonPerfHubPage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _hubAppBar(context, 'Comparison & Performance'),
      backgroundColor: Theme.of(context).colorScheme.surface,
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _hubCard(context,
              icon: Icons.compare,
              title: 'vs flutter_html & fwfh',
              subtitle:
                  'Side-by-side rendering of the same HTML in 3 libraries',
              color: DemoColors.success,
              onTap: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (_) => const LibraryComparisonDemo()))),
          _hubCard(context,
              icon: Icons.speed,
              title: 'Stress Test — 1000-Page Book',
              subtitle:
                  'Render and scroll a very long document — measure frame time and DOM node count',
              color: DemoColors.error,
              onTap: () => Navigator.push(context,
                  MaterialPageRoute(builder: (_) => const StressTestDemo()))),
          _hubCard(context,
              icon: Icons.insights,
              title: 'Performance Deep Dive',
              subtitle:
                  'Step-by-step render pipeline breakdown — parse, tokenize, layout, paint',
              color: DemoColors.success,
              onTap: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (_) => const PerformanceDeepDiveDemo()))),
        ],
      ),
    );
  }
}

class _QualityHubPage extends StatelessWidget {
  const _QualityHubPage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _hubAppBar(context, 'Security & Accessibility'),
      backgroundColor: Theme.of(context).colorScheme.surface,
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _hubCard(context,
              icon: Icons.security,
              title: 'XSS Protection',
              subtitle:
                  'Malicious <script> and event handlers are stripped before rendering',
              color: DemoColors.error,
              onTap: () => Navigator.push(context,
                  MaterialPageRoute(builder: (_) => const SecurityDemo()))),
          _hubCard(context,
              icon: Icons.accessibility,
              title: 'Accessibility',
              subtitle:
                  'Semantic labels for screen readers — VoiceOver and TalkBack',
              color: DemoColors.success,
              onTap: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (_) => const AccessibilityDemo()))),
          _hubCard(context,
              icon: Icons.auto_fix_high,
              title: 'WebView Fallback',
              subtitle:
                  'Detect HTML that is too complex and fall back to a WebView automatically',
              color: DemoColors.warning,
              onTap: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (_) => const HtmlHeuristicsDemo()))),
        ],
      ),
    );
  }
}
18
likes
160
points
347
downloads
screenshot

Documentation

API reference

Publisher

verified publisherbrewkits.dev

Weekly Downloads

Render HTML/Markdown/Delta at 60 FPS. The only Flutter renderer with CSS float layout, crash-free text selection, and CJK Ruby typography. Drop-in flutter_html alternative.

Repository (GitHub)
View/report issues

Topics

#html #markdown #rendering #richtext #css

License

MIT (license)

Dependencies

csslib, flutter, flutter_highlight, flutter_svg, highlight, html, hyper_render_core, markdown, vector_math

More

Packages that depend on hyper_render