Index: src/diff.c
==================================================================
--- src/diff.c
+++ src/diff.c
@@ -578,10 +578,194 @@
     free(c.aFrom);
     free(c.aTo);
     return c.aEdit;
   }
 }
+
+/*
+** Copy a line with a limit. Used for side-by-side diffs to enforce a maximum
+** line length limit.
+*/
+static char *copylimline(char *out, DLine *dl, int lim){
+  int len;
+  len = dl->h & LENGTH_MASK;
+  if( lim && len > lim ){
+    memcpy(out, dl->z, lim-3);
+    strcpy(&out[lim-3], "...");
+  }else{
+    memcpy(out, dl->z, len);
+    out[len] = '\0';
+  }
+  return out;
+}
+
+/*
+** Output table body of a side-by-side diff. Prior to the call, the caller
+** should have output:
+**   <table class="sbsdiff">
+**   <tr><th colspan="2" class="diffhdr">Old title</th><th/>
+**   <th colspan="2" class="diffhdr">New title</th></tr>
+**
+** And after the call, it should output:
+**   </table>
+**
+** Some good reference diffs in the fossil repository for testing:
+** /vdiff?from=080d27a&to=4b0f813&detail=1
+** /vdiff?from=636804745b&to=c1d78e0556&detail=1
+** /vdiff?from=c0b6c28d29&to=25169506b7&detail=1
+** /vdiff?from=e3d022dffa&to=48bcfbd47b&detail=1
+*/
+int html_sbsdiff(
+  Blob *pA_Blob,   /* FROM file */
+  Blob *pB_Blob,   /* TO file */
+  int nContext,    /* Amount of context to unified diff */
+  int ignoreEolWs  /* Ignore whitespace at the end of lines */
+){
+  DContext c;
+  int i;
+  int iFrom, iTo;
+  char *linebuf;
+  int collim=0; /* Currently not settable; allows a column limit for diffs */
+  int allowExp=0; /* Currently not settable; (dis)allow expansion of rows */
+
+  /* Prepare the input files */
+  memset(&c, 0, sizeof(c));
+  c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
+                             &c.nFrom, ignoreEolWs);
+  c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
+                           &c.nTo, ignoreEolWs);
+  if( c.aFrom==0 || c.aTo==0 ){
+    free(c.aFrom);
+    free(c.aTo);
+    /* Note: This would be generated within a table. */
+    @ <p class="generalError" style="white-space: nowrap">cannot compute
+    @ difference between binary files</p>
+    return 0;
+  }
+
+  collim = collim < 4 ? 0 : collim;
+
+  /* Compute the difference */
+  diff_all(&c);
+
+  linebuf = fossil_malloc(LENGTH_MASK+1);
+  if( !linebuf ){
+    free(c.aFrom);
+    free(c.aTo);
+    free(c.aEdit);
+    return 0;
+  }
+
+  iFrom=iTo=0;
+  i=0;
+  while( i<c.nEdit ){
+    int j;
+    /* Copied lines */
+    for( j=0; j<c.aEdit[i]; j++){
+      /* Hide lines which are copied and are further away from block boundaries
+      ** than nConext lines. For each block with hidden lines, show a row
+      ** notifying the user about the hidden rows.
+      */
+      if( j<nContext || j>c.aEdit[i]-nContext-1 ){
+        @ <tr>
+      }else if( j==nContext && j<c.aEdit[i]-nContext-1 ){
+        @ <tr>
+        @ <td class="meta" colspan="5" style="white-space: nowrap;">
+        @ %d(c.aEdit[i]-2*nContext) hidden lines</td>
+        @ </tr>
+        if( !allowExp )
+           continue;
+        @ <tr style="display:none;">
+      }else{
+        if( !allowExp )
+           continue;
+        @ <tr style="display:none;">
+      }
+
+      copylimline(linebuf, &c.aFrom[iFrom+j], collim);
+      @ <td class="lineno">%d(iFrom+j+1)</td>
+      @ <td class="srcline">%h(linebuf)</td>
+
+      @ <td> </td>
+
+      copylimline(linebuf, &c.aTo[iTo+j], collim);
+      @ <td class="lineno">%d(iTo+j+1)</td>
+      @ <td class="srcline">%h(linebuf)</td>
+
+      @ </tr>
+    }
+    iFrom+=c.aEdit[i];
+    iTo+=c.aEdit[i];
+
+    if( c.aEdit[i+1]!=0 && c.aEdit[i+2]!=0 ){
+      int lim;
+      lim = c.aEdit[i+1] > c.aEdit[i+2] ? c.aEdit[i+1] : c.aEdit[i+2];
+
+      /* Assume changed lines */
+      for( j=0; j<lim; j++ ){
+        @ <tr>
+
+        if( j<c.aEdit[i+1] ){
+          copylimline(linebuf, &c.aFrom[iFrom+j], collim);
+          @ <td class="changed lineno">%d(iFrom+j+1)</td>
+          @ <td class="changed srcline">%h(linebuf)</td>
+        }else{
+          @ <td colspan="2" class="changedvoid"/>
+        }
+
+        @ <td class="changed">|</td>
+
+        if( j<c.aEdit[i+2] ){
+          copylimline(linebuf, &c.aTo[iTo+j], collim);
+          @ <td class="changed lineno">%d(iTo+j+1)</td>
+          @ <td class="changed srcline">%h(linebuf)</td>
+        }else{
+          @ <td colspan="2" class="changedvoid"/>
+        }
+
+        @ </tr>
+      }
+      iFrom+=c.aEdit[i+1];
+      iTo+=c.aEdit[i+2];
+    }else{
+
+      /* Process deleted lines */
+      for( j=0; j<c.aEdit[i+1]; j++ ){
+        @ <tr>
+
+        copylimline(linebuf, &c.aFrom[iFrom+j], collim);
+        @ <td class="removed lineno">%d(iFrom+j+1)</td>
+        @ <td class="removed srcline">%h(linebuf)</td>
+        @ <td>&lt;</td>
+        @ <td colspan="2" class="removedvoid"/>
+        @ </tr>
+      }
+      iFrom+=c.aEdit[i+1];
+
+      /* Process inserted lines */
+      for( j=0; j<c.aEdit[i+2]; j++ ){
+        @ <tr>
+        @ <td colspan="2" class="addedvoid"/>
+        @ <td>&gt;</td>
+        copylimline(linebuf, &c.aTo[iTo+j], collim);
+        @ <td class="added lineno">%d(iTo+j+1)</td>
+        @ <td class="added srcline">%h(linebuf)</td>
+        @ </tr>
+      }
+      iTo+=c.aEdit[i+2];
+    }
+
+    i+=3;
+  }
+
+  free(linebuf);
+  free(c.aFrom);
+  free(c.aTo);
+  free(c.aEdit);
+  return 1;
+}
+
 
 /*
 ** COMMAND: test-rawdiff
 */
 void test_rawdiff_cmd(void){

Index: src/info.c
==================================================================
--- src/info.c
+++ src/info.c
@@ -276,10 +276,40 @@
   @ %h(blob_str(&out))
   blob_reset(&from);
   blob_reset(&to);
   blob_reset(&out);  
 }
+
+
+/*
+** Write the difference between two RIDs to the output
+*/
+static void generate_sbsdiff(const char *zFrom, const char *zTo){
+  int fromid;
+  int toid;
+  Blob from, to;
+  if( zFrom ){
+    fromid = uuid_to_rid(zFrom, 0);
+    content_get(fromid, &from);
+  }else{
+    blob_zero(&from);
+  }
+  if( zTo ){
+    toid = uuid_to_rid(zTo, 0);
+    content_get(toid, &to);
+  }else{
+    blob_zero(&to);
+  }
+  @ <table class="sbsdiff">
+  @ <tr><th colspan="2" class="diffhdr">Old (%S(zFrom))</th><th/>
+  @ <th colspan="2" class="diffhdr">New (%S(zTo))</th></tr>
+  html_sbsdiff(&from, &to, 5, 1);
+  @ </table>
+  blob_reset(&from);
+  blob_reset(&to);
+}
+
 
 /*
 ** Write a line of web-page output that shows changes that have occurred 
 ** to a file between two check-ins.
 */
@@ -287,10 +317,11 @@
   const char *zName,    /* Name of the file that has changed */
   const char *zOld,     /* blob.uuid before change.  NULL for added files */
   const char *zNew,     /* blob.uuid after change.  NULL for deletes */
   const char *zOldName, /* Prior name.  NULL if no name change. */
   int showDiff,         /* Show edit diffs if true */
+  int sideBySide,       /* Show diffs side-by-side */
   int mperm             /* executable or symlink permission for zNew */
 ){
   if( !g.perm.History ){
     if( zNew==0 ){
       @ <p>Deleted %h(zName)</p>
@@ -329,13 +360,17 @@
     }else{
       @ <p>Added <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
       @ version <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)]</a>
     }
     if( showDiff ){
-      @ <blockquote><pre>
-      append_diff(zOld, zNew);
-      @ </pre></blockquote>
+      if( sideBySide ){
+         generate_sbsdiff(zOld, zNew);
+      }else{
+        @ <blockquote><pre>
+        append_diff(zOld, zNew);
+        @ </pre></blockquote>
+      }
     }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
       @ &nbsp;&nbsp;
       @ <a href="%s(g.zTop)/fdiff?v1=%S(zOld)&amp;v2=%S(zNew)">[diff]</a>
     }
     @ </p>
@@ -361,10 +396,11 @@
 void ci_page(void){
   Stmt q;
   int rid;
   int isLeaf;
   int showDiff;
+  int sideBySide;
   const char *zName;   /* Name of the checkin to be displayed */
   const char *zUuid;   /* UUID of zName */
   const char *zParent; /* UUID of the parent checkin (if any) */
 
   login_check_credentials();
@@ -390,10 +426,11 @@
      "  FROM blob, event"
      " WHERE blob.rid=%d"
      "   AND event.objid=%d",
      rid, rid
   );
+  sideBySide = atoi(PD("sbs","1"));
   if( db_step(&q)==SQLITE_ROW ){
     const char *zUuid = db_column_text(&q, 0);
     char *zTitle = mprintf("Check-in [%.10s]", zUuid);
     char *zEUser, *zEComment;
     const char *zUser;
@@ -511,18 +548,42 @@
     showDiff = g.zPath[0]!='c';
     if( db_get_boolean("show-version-diffs", 0)==0 ){
       showDiff = !showDiff;
       if( showDiff ){
         @ <a href="%s(g.zTop)/vinfo/%T(zName)">[hide&nbsp;diffs]</a>
+        @ &nbsp;&nbsp;
+        if( sideBySide ){
+          @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=0">
+          @ [unified&nbsp;diffs]</a>
+        }else{
+          @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=1">
+          @ [side-by-side&nbsp;diffs]</a>
+        }
       }else{
-        @ <a href="%s(g.zTop)/ci/%T(zName)">[show&nbsp;diffs]</a>
+        @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=0">
+        @ [show&nbsp;unified&nbsp;diffs]</a>
+        @ &nbsp;&nbsp;
+        @ <a href="%s(g.zTop)/ci/%T(zName)?sbs=1">
+        @ [show&nbsp;side-by-side&nbsp;diffs]</a>
       }
     }else{
       if( showDiff ){
         @ <a href="%s(g.zTop)/ci/%T(zName)">[hide&nbsp;diffs]</a>
+        @ &nbsp;&nbsp;
+        if( sideBySide ){
+          @ <a href="%s(g.zTop)/info/%T(zName)?sbs=0">
+          @ [unified&nbsp;diffs]</a>
+        }else{
+          @ <a href="%s(g.zTop)/info/%T(zName)?sbs=1">
+          @ [side-by-side&nbsp;diffs]</a>
+        }
       }else{
-        @ <a href="%s(g.zTop)/vinfo/%T(zName)">[show&nbsp;diffs]</a>
+        @ <a href="%s(g.zTop)/vinfo/%T(zName)?sbs=0">
+        @ [show&nbsp;unified&nbsp;diffs]</a>
+        @ &nbsp;&nbsp;
+        @ <a href="%s(g.zTop)/vinfo/%T(zName)?sbs=1">
+        @ [show&nbsp;side-by-side&nbsp;diffs]</a>
       }
     }
     @ &nbsp;&nbsp;
     @ <a href="%s(g.zTop)/vpatch?from=%S(zParent)&to=%S(zUuid)">[patch]</a><br/>
     db_prepare(&q,
@@ -540,11 +601,12 @@
       const char *zName = db_column_text(&q,0);
       int mperm = db_column_int(&q, 1);
       const char *zOld = db_column_text(&q,2);
       const char *zNew = db_column_text(&q,3);
       const char *zOldName = db_column_text(&q, 4);
-      append_file_change_line(zName, zOld, zNew, zOldName, showDiff, mperm);
+      append_file_change_line(zName, zOld, zNew, zOldName, showDiff,
+            sideBySide, mperm);
     }
     db_finalize(&q);
   }
   style_footer();
 }
@@ -690,17 +752,18 @@
 }
 
 
 /*
 ** WEBPAGE: vdiff
-** URL: /vdiff?from=UUID&amp;to=UUID&amp;detail=BOOLEAN
+** URL: /vdiff?from=UUID&amp;to=UUID&amp;detail=BOOLEAN;sbs=BOOLEAN
 **
 ** Show all differences between two checkins.  
 */
 void vdiff_page(void){
   int ridFrom, ridTo;
   int showDetail = 0;
+  int sideBySide = 0;
   Manifest *pFrom, *pTo;
   ManifestFile *pFileFrom, *pFileTo;
 
   login_check_credentials();
   if( !g.perm.Read ){ login_needed(); return; }
@@ -709,10 +772,20 @@
   pFrom = vdiff_parse_manifest("from", &ridFrom);
   if( pFrom==0 ) return;
   pTo = vdiff_parse_manifest("to", &ridTo);
   if( pTo==0 ) return;
   showDetail = atoi(PD("detail","0"));
+  sideBySide = atoi(PD("sbs","1"));
+  if( !sideBySide ){
+    style_submenu_element("Side-by-side Diff", "sbsdiff",
+                          "%s/vdiff?from=%T&to=%T&detail=%d&sbs=1",
+                          g.zTop, P("from"), P("to"), showDetail);
+  }else{
+    style_submenu_element("Unified Diff", "udiff",
+                          "%s/vdiff?from=%T&to=%T&detail=%d&sbs=0",
+                          g.zTop, P("from"), P("to"), showDetail);
+  }
   style_header("Check-in Differences");
   @ <h2>Difference From:</h2><blockquote>
   checkin_description(ridFrom);
   @ </blockquote><h2>To:</h2><blockquote>
   checkin_description(ridTo);
@@ -731,25 +804,25 @@
     }else{
       cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName);
     }
     if( cmp<0 ){
       append_file_change_line(pFileFrom->zName, 
-                              pFileFrom->zUuid, 0, 0, 0, 0);
+                              pFileFrom->zUuid, 0, 0, 0, 0, 0);
       pFileFrom = manifest_file_next(pFrom, 0);
     }else if( cmp>0 ){
       append_file_change_line(pFileTo->zName, 
-                              0, pFileTo->zUuid, 0, 0,
+                              0, pFileTo->zUuid, 0, 0, 0,
                               manifest_file_mperm(pFileTo));
       pFileTo = manifest_file_next(pTo, 0);
     }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){
       /* No changes */
       pFileFrom = manifest_file_next(pFrom, 0);
       pFileTo = manifest_file_next(pTo, 0);
     }else{
       append_file_change_line(pFileFrom->zName, 
                               pFileFrom->zUuid,
-                              pFileTo->zUuid, 0, showDetail,
+                              pFileTo->zUuid, 0, showDetail, sideBySide,
                               manifest_file_mperm(pFileTo));
       pFileFrom = manifest_file_next(pFrom, 0);
       pFileTo = manifest_file_next(pTo, 0);
     }
   }
@@ -983,28 +1056,30 @@
 }
 
 
 /*
 ** WEBPAGE: fdiff
-** URL: fdiff?v1=UUID&v2=UUID&patch
+** URL: fdiff?v1=UUID&v2=UUID&patch&sbs=BOOLEAN
 **
-** Two arguments, v1 and v2, identify the files to be diffed.  Show the 
-** difference between the two artifacts.  Generate plaintext if "patch"
-** is present.
+** Two arguments, v1 and v2, identify the files to be diffed.  Show the
+** difference between the two artifacts.  Show diff side by side unless sbs
+** is 0.  Generate plaintext if "patch" is present.
 */
 void diff_page(void){
   int v1, v2;
   int isPatch;
+  int sideBySide;
   Blob c1, c2, diff, *pOut;
   char *zV1;
   char *zV2;
 
   login_check_credentials();
   if( !g.perm.Read ){ login_needed(); return; }
   v1 = name_to_rid_www("v1");
   v2 = name_to_rid_www("v2");
   if( v1==0 || v2==0 ) fossil_redirect_home();
+  sideBySide = atoi(PD("sbs","1"));
   zV1 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v1);
   zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2);
   isPatch = P("patch")!=0;
   if( isPatch ){
     pOut = cgi_output_blob();
@@ -1011,28 +1086,44 @@
     cgi_set_content_type("text/plain");
   }else{
     blob_zero(&diff);
     pOut = &diff;
   }
-  content_get(v1, &c1);
-  content_get(v2, &c2);
-  text_diff(&c1, &c2, pOut, 4, 1);
-  blob_reset(&c1);
-  blob_reset(&c2);
+  if( !sideBySide || isPatch ){
+    content_get(v1, &c1);
+    content_get(v2, &c2);
+    text_diff(&c1, &c2, pOut, 4, 1);
+    blob_reset(&c1);
+    blob_reset(&c2);
+  }
   if( !isPatch ){
     style_header("Diff");
     style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch",
                           g.zTop, P("v1"), P("v2"));
+    if( !sideBySide ){
+      style_submenu_element("Side-by-side Diff", "sbsdiff",
+                            "%s/fdiff?v1=%T&v2=%T&sbs=1",
+                            g.zTop, P("v1"), P("v2"));
+    }else{
+      style_submenu_element("Unified Diff", "udiff",
+                            "%s/fdiff?v1=%T&v2=%T&sbs=0",
+                            g.zTop, P("v1"), P("v2"));
+    }
+
     @ <h2>Differences From
     @ Artifact <a href="%s(g.zTop)/artifact/%S(zV1)">[%S(zV1)]</a>:</h2>
     object_description(v1, 0, 0);
     @ <h2>To Artifact <a href="%s(g.zTop)/artifact/%S(zV2)">[%S(zV2)]</a>:</h2>
     object_description(v2, 0, 0);
     @ <hr />
-    @ <blockquote><pre>
-    @ %h(blob_str(&diff))
-    @ </pre></blockquote>
+    if( sideBySide ){
+      generate_sbsdiff(zV1, zV2);
+    }else{
+      @ <blockquote><pre>
+      @ %h(blob_str(&diff))
+      @ </pre></blockquote>
+    }
     blob_reset(&diff);
     style_footer();
   }
 }
 

Index: src/skins.c
==================================================================
--- src/skins.c
+++ src/skins.c
@@ -152,10 +152,55 @@
 @ /* The label/value pairs on (for example) the vinfo page */
 @ table.label-value th {
 @   vertical-align: top;
 @   text-align: right;
 @   padding: 0.2ex 2ex;
+@ }
+@
+@ /* Side-by-side diff */
+@ table.sbsdiff {
+@   background-color: white;
+@   font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace;
+@   font-size: 10pt;
+@   border-collapse:collapse;
+@   white-space: pre;
+@   width: 98%;
+@   border: 1px #000 dashed;
+@ }
+@
+@ table.sbsdiff th.diffhdr {
+@   border-bottom: dotted;
+@   border-width: 1px;
+@ }
+@
+@ table.sbsdiff tr td {
+@   white-space: pre;
+@   padding-left: 3px;
+@   padding-right: 3px;
+@   margin: 0px;
+@ }
+@
+@ table.sbsdiff tr td.lineno {
+@   text-align: right;
+@ }
+@
+@ table.sbsdiff tr td.meta {
+@   color: white;
+@   background-color: rgb(20, 20, 20);
+@   text-align: center;
+@ }
+@
+@ table.sbsdiff tr td.added {
+@   background-color: rgb(230, 230, 230);
+@ }
+@
+@ table.sbsdiff tr td.removed {
+@   background-color: rgb(200, 200, 200);
+@ }
+@
+@ table.sbsdiff tr td.changed {
+@   background-color: rgb(220, 220, 220);
 @ }');
 @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html>
 @ <head>
 @ <title>$<project_name>: $<title></title>
 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@@ -356,11 +401,54 @@
 @ table.label-value th {
 @   vertical-align: top;
 @   text-align: right;
 @   padding: 0.2ex 2ex;
 @ }
-@ ');
+@
+@ /* Side-by-side diff */
+@ table.sbsdiff {
+@   background-color: #ffffc5;
+@   font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace;
+@   font-size: 10pt;
+@   border-collapse:collapse;
+@   white-space: pre;
+@   width: 98%;
+@   border: 1px #000 dashed;
+@ }
+@
+@ table.sbsdiff th.diffhdr {
+@   border-bottom: dotted;
+@   border-width: 1px;
+@ }
+@
+@ table.sbsdiff tr td {
+@   white-space: pre;
+@   padding-left: 3px;
+@   padding-right: 3px;
+@   margin: 0px;
+@ }
+@
+@ table.sbsdiff tr td.lineno {
+@   text-align: right;
+@ }
+@
+@ table.sbsdiff tr td.meta {
+@   background-color: #a09048;
+@   text-align: center;
+@ }
+@
+@ table.sbsdiff tr td.added {
+@   background-color: rgb(210, 210, 100);
+@ }
+@
+@ table.sbsdiff tr td.removed {
+@   background-color: rgb(190, 200, 110);
+@ }
+@
+@ table.sbsdiff tr td.changed {
+@   background-color: rgb(200, 210, 120);
+@ }');
 @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html>
 @ <head>
 @ <title>$<project_name>: $<title></title>
 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
 @       href="$home/timeline.rss">
@@ -590,10 +678,56 @@
 @ /* The label/value pairs on (for example) the ci page */
 @ table.label-value th {
 @   vertical-align: top;
 @   text-align: right;
 @   padding: 0.2ex 2ex;
+@ }
+@
+@ /* Side-by-side diff */
+@ table.sbsdiff {
+@   background-color: white;
+@   font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace;
+@   font-size: 10pt;
+@   border-collapse:collapse;
+@   white-space: pre;
+@   width: 98%;
+@   border: 1px #000 dashed;
+@ }
+@
+@ table.sbsdiff th.diffhdr {
+@   border-bottom: dotted;
+@   border-width: 1px;
+@ }
+@
+@ table.sbsdiff tr td {
+@   white-space: pre;
+@   padding-left: 3px;
+@   padding-right: 3px;
+@   margin: 0px;
+@ }
+@
+@ table.sbsdiff tr td.lineno {
+@   text-align: right;
+@ }
+@
+@ table.sbsdiff tr td.meta {
+@   color: white;
+@   background-color: black;
+@   text-align: center;
+@ }
+@
+@ table.sbsdiff tr td.added {
+@   background-color: white;
+@ }
+@
+@ table.sbsdiff tr td.removed {
+@   background-color: white;
+@   text-decoration: line-through;
+@ }
+@
+@ table.sbsdiff tr td.changed {
+@   background-color: white;
 @ }');
 @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html>
 @ <head>
 @ <title>$<project_name>: $<title></title>
 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@@ -885,10 +1019,77 @@
 @   padding: 3px 5px;
 @ }
 @ 
 @ textarea {
 @   font-size: 1em;
+@ }
+@
+@ /* Side-by-side diff */
+@ table.sbsdiff {
+@   background-color: white;
+@   font-family: Dejavu Sans Mono, Monaco, Lucida Console, monospace;
+@   font-size: 8pt;
+@   border-collapse:collapse;
+@   width: 98%;
+@   border: 1px #000 dashed;
+@   margin-left: auto;
+@   margin-right: auto;
+@ }
+@
+@ table.sbsdiff th.diffhdr {
+@   border-bottom: dotted;
+@   border-width: 1px;
+@ }
+@
+@ table.sbsdiff tr td {
+@   padding-left: 3px;
+@   padding-right: 3px;
+@   margin: 0px;
+@   vertical-align: top;
+@   white-space: pre-wrap;
+@ }
+@
+@ table.sbsdiff tr td.lineno {
+@   text-align: right;
+@   /* border-bottom: 1px solid rgb(220, 220, 220); */
+@ }
+@
+@ table.sbsdiff tr td.srcline {
+@   max-width: 400px;
+@   /* Note: May partially hide long lines without whitespaces */
+@   overflow: hidden;
+@   /* border-bottom: 1px solid rgb(220, 220, 220); */
+@ }
+@
+@ table.sbsdiff tr td.meta {
+@   background-color: rgb(170, 160, 255);
+@   padding-top: 0.25em;
+@   padding-bottom: 0.25em;
+@   text-align: center;
+@   -moz-border-radius: 5px;
+@   -moz-border-radius: 5px;
+@   -webkit-border-radius: 5px;
+@   -webkit-border-radius: 5px;
+@   -border-radius: 5px;
+@   -border-radius: 5px;
+@   border-radius: 5px;
+@   border-radius: 5px;
+@ }
+@
+@ table.sbsdiff tr td.added {
+@   background-color: rgb(180, 250, 180);
+@   /* border-bottom: 1px solid rgb(160, 230, 160); */
+@ }
+@
+@ table.sbsdiff tr td.removed {
+@   background-color: rgb(250, 130, 130);
+@   /* border-bottom: 1px solid rgb(230, 110, 110); */
+@ }
+@
+@ table.sbsdiff tr td.changed {
+@   background-color: rgb(210, 210, 200);
+@   /* border-bottom: 1px solid rgb(190, 190, 180); */
 @ }');
 @ REPLACE INTO config(name,mtime,value) VALUES('header',now(),'<html>
 @ <head>
 @ <title>$<project_name>: $<title></title>
 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"

Index: src/style.c
==================================================================
--- src/style.c
+++ src/style.c
@@ -396,10 +396,69 @@
 @ table.label-value th {
 @   vertical-align: top;
 @   text-align: right;
 @   padding: 0.2ex 2ex;
 @ }
+@
+@ /* Side-by-side diff */
+@ table.sbsdiff {
+@   background-color: white;
+@   font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace;
+@   font-size: 10pt;
+@   border-collapse:collapse;
+@   white-space: pre;
+@   width: 98%;
+@   border: 1px #000 dashed;
+@   margin-left: auto;
+@   margin-right: auto;
+@ }
+@
+@ table.sbsdiff th.diffhdr {
+@   border-bottom: dotted;
+@   border-width: 1px;
+@ }
+@
+@ table.sbsdiff tr td {
+@   white-space: pre;
+@   padding-left: 3px;
+@   padding-right: 3px;
+@   margin: 0px;
+@   vertical-align: top;
+@ }
+@
+@ table.sbsdiff tr td.lineno {
+@   text-align: right;
+@ }
+@
+@ table.sbsdiff tr td.srcline {
+@ }
+@
+@ table.sbsdiff tr td.meta {
+@   background-color: rgb(170, 160, 255);
+@   text-align: center;
+@ }
+@
+@ table.sbsdiff tr td.added {
+@   background-color: rgb(180, 250, 180);
+@ }
+@ table.sbsdiff tr td.addedvoid {
+@   background-color: rgb(190, 190, 180);
+@ }
+@
+@ table.sbsdiff tr td.removed {
+@   background-color: rgb(250, 130, 130);
+@ }
+@ table.sbsdiff tr td.removedvoid {
+@   background-color: rgb(190, 190, 180);
+@ }
+@
+@ table.sbsdiff tr td.changed {
+@   background-color: rgb(210, 210, 200);
+@ }
+@ table.sbsdiff tr td.changedvoid {
+@   background-color: rgb(190, 190, 180);
+@ }
 @
 ;
 
 
 /* The following table contains bits of default CSS that must