This patch contains the changes to Pod::Simple::HTML that are in use on search.cpan.org
The rest is all done with CSS.

For those who use Mason, an example component

<%args>
  $filename
</%args>
<%perl>
  my $pod = Pod::Simple::HTML->new;
  # This adds the link to the top of the page next to the headers
  $pod->{'Tagmap'}{'/head1'} = " <a href='#_top'><img alt='^' src='/s/img/up.gif'></a></h1>\n";
  $pod->set_source($filename);
  $pod->output_string(\my $html);
  $pod->do_middle;
</%perl>
<a name="_top"></a>
<div class=pod>
<div class=toc><% $pod->contents %></div>
<% $html %>
</div>


diff -ur Pod-Simple-2.04.orig/lib/Pod/Simple/HTML.pm Pod-Simple-2.04/lib/Pod/Simple/HTML.pm
--- Pod-Simple-2.04.orig/lib/Pod/Simple/HTML.pm	Tue Sep  2 07:30:45 2003
+++ Pod-Simple-2.04/lib/Pod/Simple/HTML.pm	Wed Oct 15 07:56:26 2003
@@ -3,17 +3,16 @@
 package Pod::Simple::HTML;
 use strict;
 use Pod::Simple::PullParser ();
-use vars qw(@ISA %Tagmap $Computerese $Lame $Linearization_Limit $VERSION);
+use vars qw(@ISA %Tagmap $Computerese $Linearization_Limit $VERSION);
 @ISA = ('Pod::Simple::PullParser');
-$VERSION = '2.01';
+$VERSION = '2.01_01';
 
 use UNIVERSAL ();
 sub DEBUG () {0}
 
 use utf8;
 
-$Computerese =  " lang='und' xml:lang='und'" unless defined $Computerese;
-$Lame = ' class="pad"' unless defined $Lame;
+$Computerese =  "" unless defined $Computerese;
 
 $Linearization_Limit = 90 unless defined $Linearization_Limit;
  # headings/items longer than that won't get an <a name="...">
@@ -41,10 +40,10 @@
   'head2' => "\n<h2>",  #  ''
   'head3' => "\n<h3>",  #  ''
   'head4' => "\n<h4>",  #  ''
-  '/head1' => "</a></h1>\n",
-  '/head2' => "</a></h2>\n",
-  '/head3' => "</a></h3>\n",
-  '/head4' => "</a></h4>\n",
+  '/head1' => "</h1>\n",
+  '/head2' => "</h2>\n",
+  '/head3' => "</h3>\n",
+  '/head4' => "</h4>\n",
 
   'X'  => "<!--\n\tINDEX: ",
   '/X' => "\n-->",
@@ -79,11 +78,11 @@
     ]  # no point in providing a way to get <q>...</q>, I think
   ),
   
-  '/item-bullet' => "</li><p$Lame></p>\n",
-  '/item-number' => "</li><p$Lame></p>\n",
-  '/item-text'   => "</a></dt><p$Lame></p>\n",
-  'Para_item'    => "\n<dd>",
-  '/Para_item'   => "</dd><p$Lame></p>\n",
+  '/item-bullet' => "</li>\n",
+  '/item-number' => "</li>\n",
+  '/item-text'   => "</dt>\n",
+  'item-body'    => "\n<dd>",
+  '/item-body'   => "</dd>\n",
 
   'B'      =>  "<b>",                  '/B'     =>  "</b>",
   'I'      =>  "<i>",                  '/I'     =>  "</i>",
@@ -128,6 +127,15 @@
 
 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+sub escape_url {
+    $_[0] =~ s/([^\x00-\xFF])/join '', map sprintf('%%%02X',$_), unpack 'C*', $1/eg;
+    $_[0] =~ s/([^\._abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789])/sprintf('%%%02X',ord($1))/eg;
+      # Yes, stipulate the list without a range, so that this can work right on
+      #  all charsets that this module happens to run under.
+      # Altho, hmm, what about that ord?  Presumably that won't work right
+      #  under non-ASCII charsets.  Something should be done about that.
+}
+
 sub do_pod_link {
   my($self, $link) = @_;
   my $to = $link->attr('to');
@@ -138,6 +146,7 @@
   );
 
   if(defined $to and length $to) {
+    # resolve_pod_page_link must return a properly escaped URL
     $to = $self->resolve_pod_page_link($to, $section);
     return undef unless defined $to and length $to;
      # resolve_pod_page_link returning undef is how it
@@ -152,22 +161,10 @@
     $section = $self->unicode_escape_url($section);
      # Turn char 1234 into "(1234)"
     $section = '_' unless length $section;
+    escape_url($section);
   }
   
-  
-
-  foreach my $it ($to, $section) {
-    if( defined $it ) {
-      $it =~ s/([^\x00-\xFF])/join '', map sprintf('%%%02X',$_), unpack 'C*', $1/eg;
-      $it =~ s/([^\._abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789])/sprintf('%%%02X',ord($1))/eg;
-       # Yes, stipulate the list without a range, so that this can work right on
-       #  all charsets that this module happens to run under.
-       # Altho, hmm, what about that ord?  Presumably that won't work right
-       #  under non-ASCII charsets.  Something should be done about that.
-    }
-  }
-  
-  my $out = $to if defined $to and length $to;
+  my $out = (defined $to) ? $to : '';
   $out .= "#" . $section if defined $section and length $section;
   return undef unless length $out;
   return $out;  
@@ -175,9 +172,10 @@
 
 
 sub resolve_pod_page_link {
-  my($self, $to) = @_;
-  
-  return 'TODO';
+  escape_url(my $to = $_[1]);
+
+  "http://search.cpan.org/perldoc?$to";
+    # We should make this configurable, but this is a good default
 }
 
 sub do_url_link { return $_[1]->attr('to') }
@@ -203,10 +201,15 @@
 }
 
 
+sub do_anchor { $_[1] }
+
 sub do_middle {      # the main work
   my $self = $_[0];
   my $fh = $self->{'output_fh'};
+  my $tagmap = $self->{'Tagmap'};
   
+  print $fh $self->version_tag_comment, "<!-- start doc -->\n";
+
   my($token, $type, $tagname);
   my @stack;
   my $dont_wrap = 0;
@@ -223,7 +226,11 @@
         }
 
       } elsif ($tagname eq 'item-text' or $tagname =~ m/^head\d$/s) {
-        print $fh $self->{'Tagmap'}{$tagname} || next;
+        if( $stack[-1] and $tagname eq 'item-text' ) {
+          print $fh $stack[-1];
+          $stack[-1] = '';
+        }
+        print $fh $tagmap->{$tagname} || next;
 
         my @to_unget;
         while(1) {
@@ -232,14 +239,16 @@
               and $to_unget[-1]->tagname eq $tagname;
         }
         my $name = $self->linearize_tokens(@to_unget);
+	$name = $self->do_anchor($name, $token) if defined $name;
         
         if(defined $name) { # ludicrously long, so nevermind
+          push @{$self->{contents}}, [ $1, $name ]
+            if $tagname =~ m/^head(\d)$/;
           $name =~ tr/ /_/;
-          print $fh "<a name=\"", esc($name), "\"\n>";
+          print $fh "<a name=\"", esc($name), "\"></a\n>";
           DEBUG and print "Linearized ", scalar(@to_unget),
            " tokens as \"$name\".\n";
         } else {
-          print $fh "<a\n>";  # Yes, an 'a' element with no attributes!
           DEBUG and print "Linearized ", scalar(@to_unget),
            " tokens, but it was too long, so nevermind.\n";
         }
@@ -257,12 +266,13 @@
         next;
        
       } else {
-        if( $tagname =~ m/^over-(.+)$/s ) {
-          push @stack, $1;
-        } elsif( $tagname eq 'Para') {
-          $tagname = 'Para_item' if @stack and $stack[-1] eq 'text';
+        if( $tagname =~ m/^over-/s ) {
+          push @stack, '';
+        } elsif( $tagname =~ m/^item-/s and @stack and $stack[-1] ) {
+          print $fh $stack[-1];
+          $stack[-1] = '';
         }
-        print $fh $self->{'Tagmap'}{$tagname} || next;
+        print $fh $tagmap->{$tagname} || next;
         ++$dont_wrap if $tagname eq 'Verbatim' or $tagname eq "VerbatimFormatted"
           or $tagname eq 'X';
       }
@@ -270,11 +280,21 @@
     # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     } elsif( $type eq 'end' ) {
       if( ($tagname = $token->tagname) =~ m/^over-/s ) {
-        pop @stack;
-      } elsif( $tagname eq 'Para' ) {
-        $tagname = 'Para_item' if @stack and $stack[-1] eq 'text';
+        if( my $end = pop @stack ) {
+          print $fh $end;
+        }
+      } elsif( $tagname =~ m/^item-/s and @stack) {
+        $stack[-1] = $tagmap->{"/$tagname"};
+        if( $tagname eq 'item-text' and defined(my $next = $self->get_token) ) {
+          $self->unget_token($next);
+          if( $next->type eq 'start' and $next->tagname !~ m/^item-/s ) {
+            print $fh $tagmap->{"/item-text"},$tagmap->{"item-body"};
+            $stack[-1] = $tagmap->{"/item-body"};
+          }
+        }
+        next;
       }
-      print $fh $self->{'Tagmap'}{"/$tagname"} || next;
+      print $fh $tagmap->{"/$tagname"} || next;
       --$dont_wrap if $tagname eq 'Verbatim' or $tagname eq 'X';
 
     # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -285,27 +305,42 @@
     }
 
   }
+  print $fh "<!-- end doc -->\n";
   return 1;
 }
 
 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
+sub do_head {
+  my $self = $_[0];
+  esc(my $title = $self->{'Title'});
+
+  print {$self->{'output_fh'}} <<HEAD;
+<title>$title</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+HEAD
+
+}
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
 sub do_beginning {
   my $self = $_[0];
 
-  my $title = $self->get_short_title();
+  $self->{'Title'} = $self->get_short_title();
   unless($self->content_seen) {
     DEBUG and print "No content seen in search for title.\n";
     return;
   }
-  $self->{'Title'} = $title;
 
-  esc($title);
   print {$self->{'output_fh'}}
-   "<html><head>\n<title>$title</title>\n</head>\n<body>\n", 
-   $self->version_tag_comment,
-   "<!-- start doc -->\n",
-  ;
+    qq{<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"\n},
+    qq{  "http://www.w3.org/TR/html4/strict.dtd">\n},
+    qq{<html><head>\n};
+
+  $self->do_head;
+  print {$self->{'output_fh'}} "</head><body><div class=pod>\n";
+
    # TODO: more configurability there
 
   DEBUG and print "Returning from do_beginning...\n";
@@ -325,7 +360,7 @@
 
 sub do_end {
   my $self = $_[0];
-  print {$self->{'output_fh'}} "\n<!-- end doc -->\n</body></html>\n";
+  print {$self->{'output_fh'}} "</div>\n</body></html>\n";
    # TODO: allow for a footer
   return 1;
 }
@@ -397,6 +432,30 @@
 }
 
 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+sub contents {
+  my $self = shift;
+  my $contents = $self->{contents}
+    or return '';
+  my $str = '';
+  my $lvl = 0;
+  foreach my $entry (@$contents) {
+    my ($nlvl, $label) = @$entry;
+    if (my $n = $nlvl - $lvl) {
+      if ($n > 0) {
+	$str .= substr("<li><ul>" x $n, 4);
+      }
+      else {
+	$str .= "</ul>\n" x -$n;
+      }
+      $lvl = $nlvl;
+    }
+    (my $name = $label) =~ tr/ /_/;
+    $str .= sprintf qq{<li><a href="#%s">%s</a>\n}, esc($name),esc($label);
+  }
+  $str .= "</ul>\n" x $lvl;
+  $str;
+}
 
 1;
 __END__
