]> mj.ucw.cz Git - gallery.git/commitdiff
The big rename
authorMartin Mares <mj@ucw.cz>
Sun, 8 Feb 2015 20:16:51 +0000 (21:16 +0100)
committerMartin Mares <mj@ucw.cz>
Sun, 8 Feb 2015 20:16:51 +0000 (21:16 +0100)
Everything moved to the top-level directory, except for the Perl
modules, which now reside in lib/...

160 files changed:
FORMAT [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
bin/gal-cache [new file with mode: 0755]
bin/gal-date [new file with mode: 0755]
bin/gal-dump-config [new file with mode: 0755]
bin/gal-dump-meta [new file with mode: 0755]
bin/gal-from-gqview [new file with mode: 0755]
bin/gal-gen [new file with mode: 0755]
bin/gal-mj-digikam [new file with mode: 0755]
bin/gal-mj-init [new file with mode: 0755]
bin/gal-mj-map [new file with mode: 0755]
bin/gal-mj-migrate-check [new file with mode: 0755]
bin/gal-mj-upgrade [new file with mode: 0755]
bin/gal-mj-upload [new file with mode: 0755]
bin/gal-scan [new file with mode: 0755]
gal [new file with mode: 0755]
gal/FORMAT [deleted file]
gal/Makefile [deleted file]
gal/README [deleted file]
gal/UCW/Gallery.pm [deleted file]
gal/UCW/Gallery/Archive.pm [deleted file]
gal/UCW/Gallery/Hashes.pm [deleted file]
gal/UCW/Gallery/Web.pm [deleted file]
gal/UCW/Gallery/Web/HighSlide.pm [deleted file]
gal/UCW/Gallery/Web/NrtBlue.pm [deleted file]
gal/UCW/Gallery/Web/Plain.pm [deleted file]
gal/bin/gal-cache [deleted file]
gal/bin/gal-date [deleted file]
gal/bin/gal-dump-config [deleted file]
gal/bin/gal-dump-meta [deleted file]
gal/bin/gal-from-gqview [deleted file]
gal/bin/gal-gen [deleted file]
gal/bin/gal-mj-digikam [deleted file]
gal/bin/gal-mj-init [deleted file]
gal/bin/gal-mj-map [deleted file]
gal/bin/gal-mj-migrate-check [deleted file]
gal/bin/gal-mj-upgrade [deleted file]
gal/bin/gal-mj-upload [deleted file]
gal/bin/gal-scan [deleted file]
gal/gal [deleted file]
gal/highslide/custom.css [deleted file]
gal/highslide/custom.js [deleted file]
gal/highslide/graphics/close.png [deleted file]
gal/highslide/graphics/closeX.png [deleted file]
gal/highslide/graphics/controlbar-black-border.gif [deleted file]
gal/highslide/graphics/controlbar-text-buttons.png [deleted file]
gal/highslide/graphics/controlbar-white-small.gif [deleted file]
gal/highslide/graphics/controlbar-white.gif [deleted file]
gal/highslide/graphics/controlbar2.gif [deleted file]
gal/highslide/graphics/controlbar3.gif [deleted file]
gal/highslide/graphics/controlbar4-hover.gif [deleted file]
gal/highslide/graphics/controlbar4.gif [deleted file]
gal/highslide/graphics/fullexpand.gif [deleted file]
gal/highslide/graphics/geckodimmer.png [deleted file]
gal/highslide/graphics/icon.gif [deleted file]
gal/highslide/graphics/loader.big.black.gif [deleted file]
gal/highslide/graphics/loader.big.white.gif [deleted file]
gal/highslide/graphics/loader.black.gif [deleted file]
gal/highslide/graphics/loader.white.gif [deleted file]
gal/highslide/graphics/outlines/beveled.png [deleted file]
gal/highslide/graphics/outlines/custom.png [deleted file]
gal/highslide/graphics/outlines/drop-shadow.png [deleted file]
gal/highslide/graphics/outlines/glossy-dark.png [deleted file]
gal/highslide/graphics/outlines/outer-glow.png [deleted file]
gal/highslide/graphics/outlines/rounded-black.png [deleted file]
gal/highslide/graphics/outlines/rounded-white.png [deleted file]
gal/highslide/graphics/resize.gif [deleted file]
gal/highslide/graphics/scrollarrows.png [deleted file]
gal/highslide/graphics/zoom.png [deleted file]
gal/highslide/graphics/zoomin.cur [deleted file]
gal/highslide/graphics/zoomout.cur [deleted file]
gal/highslide/highslide-ie6.css [deleted file]
gal/highslide/highslide-with-gallery.js [deleted file]
gal/highslide/highslide.css [deleted file]
gal/highslide/nav/back.png [deleted file]
gal/highslide/nav/next.png [deleted file]
gal/highslide/nav/prev.png [deleted file]
gal/nrt-blue/back.png [deleted file]
gal/nrt-blue/back.xcf [deleted file]
gal/nrt-blue/bot.png [deleted file]
gal/nrt-blue/bot.xcf [deleted file]
gal/nrt-blue/left.png [deleted file]
gal/nrt-blue/left.xcf [deleted file]
gal/nrt-blue/next.png [deleted file]
gal/nrt-blue/next.xcf [deleted file]
gal/nrt-blue/prev.png [deleted file]
gal/nrt-blue/prev.xcf [deleted file]
gal/nrt-blue/right.png [deleted file]
gal/nrt-blue/right.xcf [deleted file]
gal/nrt-blue/style.css [deleted file]
gal/nrt-blue/top.png [deleted file]
gal/nrt-blue/top.xcf [deleted file]
gal/plain/back.png [deleted file]
gal/plain/next.png [deleted file]
gal/plain/prev.png [deleted file]
gal/plain/style.css [deleted file]
highslide/custom.css [new file with mode: 0644]
highslide/custom.js [new file with mode: 0644]
highslide/graphics/close.png [new file with mode: 0644]
highslide/graphics/closeX.png [new file with mode: 0644]
highslide/graphics/controlbar-black-border.gif [new file with mode: 0644]
highslide/graphics/controlbar-text-buttons.png [new file with mode: 0644]
highslide/graphics/controlbar-white-small.gif [new file with mode: 0644]
highslide/graphics/controlbar-white.gif [new file with mode: 0644]
highslide/graphics/controlbar2.gif [new file with mode: 0644]
highslide/graphics/controlbar3.gif [new file with mode: 0644]
highslide/graphics/controlbar4-hover.gif [new file with mode: 0644]
highslide/graphics/controlbar4.gif [new file with mode: 0644]
highslide/graphics/fullexpand.gif [new file with mode: 0644]
highslide/graphics/geckodimmer.png [new file with mode: 0644]
highslide/graphics/icon.gif [new file with mode: 0644]
highslide/graphics/loader.big.black.gif [new file with mode: 0644]
highslide/graphics/loader.big.white.gif [new file with mode: 0644]
highslide/graphics/loader.black.gif [new file with mode: 0644]
highslide/graphics/loader.white.gif [new file with mode: 0644]
highslide/graphics/outlines/beveled.png [new file with mode: 0644]
highslide/graphics/outlines/custom.png [new file with mode: 0644]
highslide/graphics/outlines/drop-shadow.png [new file with mode: 0644]
highslide/graphics/outlines/glossy-dark.png [new file with mode: 0644]
highslide/graphics/outlines/outer-glow.png [new file with mode: 0644]
highslide/graphics/outlines/rounded-black.png [new file with mode: 0644]
highslide/graphics/outlines/rounded-white.png [new file with mode: 0644]
highslide/graphics/resize.gif [new file with mode: 0644]
highslide/graphics/scrollarrows.png [new file with mode: 0644]
highslide/graphics/zoom.png [new file with mode: 0644]
highslide/graphics/zoomin.cur [new file with mode: 0644]
highslide/graphics/zoomout.cur [new file with mode: 0644]
highslide/highslide-ie6.css [new file with mode: 0644]
highslide/highslide-with-gallery.js [new file with mode: 0644]
highslide/highslide.css [new file with mode: 0644]
highslide/nav/back.png [new file with mode: 0644]
highslide/nav/next.png [new file with mode: 0644]
highslide/nav/prev.png [new file with mode: 0644]
lib/UCW/Gallery.pm [new file with mode: 0644]
lib/UCW/Gallery/Archive.pm [new file with mode: 0644]
lib/UCW/Gallery/Hashes.pm [new file with mode: 0644]
lib/UCW/Gallery/Web.pm [new file with mode: 0644]
lib/UCW/Gallery/Web/HighSlide.pm [new file with mode: 0644]
lib/UCW/Gallery/Web/NrtBlue.pm [new file with mode: 0644]
lib/UCW/Gallery/Web/Plain.pm [new file with mode: 0644]
nrt-blue/back.png [new file with mode: 0644]
nrt-blue/back.xcf [new file with mode: 0644]
nrt-blue/bot.png [new file with mode: 0644]
nrt-blue/bot.xcf [new file with mode: 0644]
nrt-blue/left.png [new file with mode: 0644]
nrt-blue/left.xcf [new file with mode: 0644]
nrt-blue/next.png [new file with mode: 0644]
nrt-blue/next.xcf [new file with mode: 0644]
nrt-blue/prev.png [new file with mode: 0644]
nrt-blue/prev.xcf [new file with mode: 0644]
nrt-blue/right.png [new file with mode: 0644]
nrt-blue/right.xcf [new file with mode: 0644]
nrt-blue/style.css [new file with mode: 0644]
nrt-blue/top.png [new file with mode: 0644]
nrt-blue/top.xcf [new file with mode: 0644]
plain/back.png [new file with mode: 0644]
plain/next.png [new file with mode: 0644]
plain/prev.png [new file with mode: 0644]
plain/style.css [new file with mode: 0644]

diff --git a/FORMAT b/FORMAT
new file mode 100644 (file)
index 0000000..059f2af
--- /dev/null
+++ b/FORMAT
@@ -0,0 +1,58 @@
+List of photos (gallery.list)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+One photo per line, tab-separated columns:
+
+       File name (relative to OrigDir in config)
+       Identifier (16 hex digits)
+       Orientation: one of "l", "r", "d", "."
+       Transformation -- sequence of:
+               n       normalize contrast
+               s       sharpen
+               h       equalize histogram
+       Title (UTF-8 string)
+
+Lines starting with "#" are ignored.
+
+Lines starting with a tab add further attributes to the previous photo.
+The following attributes are recognized:
+
+       lat             geographic latitude in degrees north of equator
+       lon             geographic longitude in degrees east of Greenwich
+       alt             geographic altitude in meters
+       t               time when the photo was taken (2014-01-25 09:40:12)
+
+
+Importing meta-data from other sources
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Occasionally, you want to add photos from a source which already provides
+some meta-data. In this case, you can construct a gallery.list with a subset
+of fields, or feed the stdin of "gal scan" with such a list.
+
+For convenience, orientation, transformation and title can be also given
+as named attributes. They are called "orientation", "xf", and "title".
+
+
+Photo meta-data (PhotoDir/gallery.meta or CacheDir/cache.meta)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Perl Storable containing a single hash.
+
+$meta->{photo}->{$identifier} is a hash of:
+       o               orientation
+       xf              transformation applied
+       w               width after scaling
+       h               height after scaling
+       w0, h0          width and height of original image
+       title           photo title
+       fmt             photo format (png/jpg; defaults to jpg)
+       lat             geographic latitude in degrees north of equator
+       lon             geographic longitude in degrees east of Greenwich
+       alt             geographic altitude in meters
+       t               time when the photo was taken (2014-01-25 09:40:12)
+
+The rest is present in cache.meta only:
+
+$meta->{sequence} is an array of photo IDs as they appear in the gallery.
+
+$meta->{thumb}->{$format}->{$identifier} is a hash of:
+       w               thumbnail width
+       h               thumbnail height
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..f7d01c2
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+$(eval $(dir-setup))
+
+$(call lib-copy, UCW/Gallery.pm)
+$(call lib-copy, $(addprefix UCW/Gallery/, Web.pm Hashes.pm Archive.pm))
+$(call lib-copy, $(addprefix UCW/Gallery/Web/, Plain.pm NrtBlue.pm HighSlide.pm))
+
+$(call copy, $(addprefix nrt-blue/,back.png bot.png left.png next.png prev.png right.png top.png style.css))
+$(call copy, $(addprefix plain/,back.png next.png prev.png style.css))
+$(call copy, $(addprefix highslide/nav/,back.png next.png prev.png))
+
+$(call copy, $(addprefix highslide/, highslide.css highslide-ie6.css highslide-with-gallery.js custom.css custom.js))
+$(call copy, $(addprefix highslide/graphics/, \
+       close.png closeX.png controlbar-black-border.gif controlbar-text-buttons.png controlbar-white-small.gif \
+       controlbar-white.gif controlbar2.gif controlbar3.gif controlbar4-hover.gif controlbar4.gif fullexpand.gif \
+       geckodimmer.png icon.gif loader.big.black.gif loader.big.white.gif loader.black.gif loader.white.gif \
+       resize.gif scrollarrows.png zoom.png zoomin.cur zoomout.cur))
+$(call copy, $(addprefix highslide/graphics/outlines/, \
+       beveled.png custom.png drop-shadow.png glossy-dark.png outer-glow.png rounded-black.png rounded-white.png))
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..0a5d7c7
--- /dev/null
+++ b/README
@@ -0,0 +1,88 @@
+================================================================================
+
+                              UCW::Gallery v2.0
+
+                   (c) 2004--2015 Martin Mares <mj@ucw.cz>
+
+================================================================================
+
+This is a simple photo gallery for my web pages, or rather a set of bricks
+from which you can create one. It can be freely used and distributed according
+to the terms of the GNU GPL v2 or newer.
+
+Requirements
+~~~~~~~~~~~~
+Perl with the following non-core modules:
+
+   o  UCW::CGI (see http://www.ucw.cz/libucw/)
+   o  Image::Magick
+   o  Image::Exif
+   o  Archive::Zip
+   o  Digest::SHA
+
+Files and directories
+~~~~~~~~~~~~~~~~~~~~~
+
+   o  gal -- a front-end script for maintenance of galleries. All other programs
+      are run as sub-commands of this script. Try "gal --help".
+
+   o  gallery.cf -- all programs expect that the current directory contains
+      a configuration file. In fact, the config file is a perl script, whose
+      sole purpose is to set up paths, construct a gallery object and set its
+      options.
+
+      A simple example looks as follows:
+
+               use UCW::Gallery;
+
+               my $gal = UCW::Gallery->new;
+               $gal->set(Title => 'A Gallery', SubTitle => '(an example)');
+               return $gal;
+
+   o  gallery.list -- a list of original photos together with per-photo parameters.
+      Usually created by "gal scan", then tweaked manually. See `FORMATS' for
+      description of file syntax.
+
+   o  gallery.cache -- used internally by "gal scan" to store cached image hashes.
+
+   o  Originals directory -- original images, from which the gallery is generated.
+
+   o  Photo directory -- keeps processed (usually down-scaled) versions of the original
+      images. These are the images shown to the user.
+
+   o  Cache directory -- keeps various cached data, like thumbnails of all photos.
+
+   o  gallery.cgi -- interfaces the gallery to your web server. E.g.:
+
+               #!/usr/bin/perl
+
+               use lib "../path/to/gallery/modules";
+               use UCW::Gallery;
+               use UCW::Gallery::Web::Plain;
+
+               my $gal = UCW::Gallery->load_config();
+               UCW::Gallery::Web::Plain->run($gal);
+
+Workflow
+~~~~~~~~
+
+   o  vi gallery.cf
+
+   o  gal scan /path/to/originals -- this creates gallery.list, populates it with
+      descriptions of all photos and reads rotation from EXIF tags.
+
+   o  vi gallery.list -- edit image descriptions and other options.
+
+   o  gal gen -- generate photos from the originals (after this, the originals are no
+      longer needed).
+
+   o  gal cache -- generate cached data.
+
+   o  Later, the gallery can be updated:
+
+       - When you edit image descriptions, re-run "gal cache".
+       - When you edit image options (rotation etc.), re-run "gal gen".
+       - When you want to add new images, re-run "gal scan". Give it the new list
+         of images and it will try to re-use as much information from the previous
+         gallery.list as possible.
+       - When you modify existing images, run "gal scan --update".
diff --git a/bin/gal-cache b/bin/gal-cache
new file mode 100755 (executable)
index 0000000..73c97db
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/bin/perl
+# UCW Gallery: Prepare cache
+# (c) 2004--2012 Martin Mares <mj@ucw.cz>
+
+use strict;
+use warnings;
+
+use UCW::Gallery;
+use Image::Magick;
+use IO::Handle;
+use File::Spec;
+use File::Path;
+
+STDOUT->autoflush(1);
+
+my $gal = UCW::Gallery->load_config;
+
+print "Reading gallery.list\n";
+my $orig_list = $gal->read_list('gallery.list') or die "Cannot read gallery.list: $!\n";
+
+my $photo_meta = $gal->photo_meta_name;
+print "Reading meta-data from $photo_meta\n";
+-f $photo_meta or die "Cannot load $photo_meta\n";
+my $meta = $gal->read_meta($photo_meta);
+
+my $cache_dir = $gal->get('CacheDir');
+if (-d $cache_dir) {
+       print "Deleting old cache directory: $cache_dir\n";
+       File::Path::remove_tree($cache_dir);
+}
+print "Creating cache directory: $cache_dir\n";
+File::Path::mkpath($cache_dir) or die "Unable to create $cache_dir: $!\n";
+
+# Construct sequence and store photo titles
+$meta->{sequence} = [];
+for my $f (@$orig_list) {
+       push @{$meta->{sequence}}, $f->{id};
+       my $m = $meta->{photo}->{$f->{id}} or die;
+       $m->{title} = $f->{title};
+}
+
+for my $thumb_fmt (@{$gal->get('ThumbFormats')}) {
+       print "Generating $thumb_fmt thumbnails\n";
+       my ($tw, $th) = $gal->thumb_fmt_to_size($thumb_fmt);
+       my $thumb_meta = {};
+       $meta->{thumb}->{$thumb_fmt} = $thumb_meta;
+       my $thumb_dir = File::Spec->catfile($cache_dir, $thumb_fmt);
+       -d $thumb_dir or File::Path::mkpath($thumb_dir) or die "Unable to create $thumb_dir: $!\n";
+
+       for my $id (@{$meta->{sequence}}) {
+               my $m = $meta->{photo}->{$id} or die;
+               print "\t$id: ";
+
+               my $p = new Image::Magick;
+               my $photo = $gal->photo_name($m, $id);
+               my $e;
+               $e = $p->Read($photo) and die "Error reading $photo: $e";
+
+               my $w = $m->{w};
+               my $h = $m->{h};
+               if ($w > $tw) {
+                       my $s = $tw / $w;
+                       $w = $w * $s;
+                       $h = $h * $s;
+               }
+               if ($h > $th) {
+                       my $s = $th / $h;
+                       $w = $w * $s;
+                       $h = $h * $s;
+               }
+               $w = int($w + .5);
+               $h = int($h + .5);
+               print "${w}x${h} ";
+               $p->Resize(width=>$w, height=>$h);
+
+               my $out = File::Spec->catfile($thumb_dir, "$id.jpg");
+               my $tmp = "$photo.new";
+               $e = $p->Write($tmp) and die "Unable to write $tmp: $e";
+               rename $tmp, $out or die "Unable to rename $tmp to $out: $!\n";
+
+               $thumb_meta->{$id} = {
+                       'w' => $w,
+                       'h' => $h,
+               };
+
+               print "... OK\n";
+       }
+}
+
+my $cache_meta = $gal->cache_meta_name;
+print "Writing meta-data to $cache_meta\n";
+$gal->write_meta($cache_meta, $meta);
diff --git a/bin/gal-date b/bin/gal-date
new file mode 100755 (executable)
index 0000000..451201c
--- /dev/null
@@ -0,0 +1,98 @@
+#!/usr/bin/perl
+# UCW Gallery: Symlink photos according to their EXIF timestamps
+# (c) 2013 Martin Mares <mj@ucw.cz>
+
+use strict;
+use warnings;
+
+use Image::EXIF;
+use Getopt::Long;
+
+if (@ARGV && $ARGV[0] eq '--help') {
+       die <<AMEN ;
+Usage: cat list | gal date [<options>]
+   or: gal date [<options>] <files>
+
+Options:
+-d, --destdir=<dir>    Create symlinks in specified directory (default: current dir)
+-n, --dry-run          Perform a trial run with no changes made
+-o, --offset=<offset>  Adjust timestamps by a given offset (<h>[:<m>[:<s>]])
+    --suffix=<s>       Add suffix to all names
+-s, --symbolic         Create symbolic links (default: hardlinks)
+AMEN
+}
+
+my $destdir = '.';
+my $dry_run = 0;
+my $offset = 0;
+my $suffix = "";
+my $symbolic = 0;
+Getopt::Long::Configure('bundling');
+GetOptions(
+       'destdir|d=s' => \$destdir,
+       'dry-run|n!' => \$dry_run,
+       'offset|o=s' => \$offset,
+       'suffix=s' => \$suffix,
+       'symbolic|s!' => \$symbolic,
+) or die "Try gal date --help\n";
+
+my @src = @ARGV;
+if (!@src) {
+       while (<STDIN>) {
+               chomp;
+               push @src, $_;
+       }
+}
+
+if ($offset =~ m{^([+-])?(\d)+(:(\d+)(:(\d+))?)?$}) {
+       $offset = $2 * 3600 + ($4 // 0) * 60 + ($6 // 0);
+       if ($1 eq '-') { $offset = -$offset; }
+} else {
+       die "Invalid offset: $offset\n";
+}
+
+foreach my $f (@src) {
+       my $e = new Image::EXIF($f);
+       my $i = $e->get_all_info();
+       if ($e->error) {
+               print STDERR "EXIF error on $f: ", $e->error, "\n";
+               next;
+       }
+       # print STDERR Dumper($i), "\n";
+
+       my $d = $i->{'image'}->{'Image Created'};
+       if (!defined $d) {
+               print STDERR "No date for $f\n";
+               next;
+       }
+       my ($ty, $tm, $td, $tH, $tM, $tS) = ($d =~ m{^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$}) or die "EXIF date parse error: $d\n";
+       my $t = 3600*$tH + 60*$tM + $tS;
+       $t = int($t + $offset);
+       $tH = int($t / 3600);
+       $tM = int(($t % 3600) / 60);
+       $tS = $t % 60;
+
+       my $retry = 0;
+       my $dest = "";
+       do {
+               $dest = sprintf("%s/%04d-%02d-%02d-%02d:%02d:%02d%s%s.jpg",
+                       $destdir,
+                       $ty, $tm, $td,
+                       $tH, $tM, $tS,
+                       $suffix,
+                       $retry ? sprintf("-%d", $retry) : "");
+               $retry++;
+       } while (-f $dest);
+
+       print "$f $dest\n";
+
+       unless ($dry_run) {
+               if ($symbolic) {
+                       symlink $f, $dest or die "Cannot symlink $f to $dest: $!\n";
+               } else {
+                       link $f, $dest or die "Cannot hardlink $f to $dest: $!\n";
+               }
+       }
+}
+
+STDOUT->autoflush(1);
diff --git a/bin/gal-dump-config b/bin/gal-dump-config
new file mode 100755 (executable)
index 0000000..600a9d5
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+# UCW Gallery: Show configuration variables
+# (c) 2004--2012 Martin Mares <mj@ucw.cz>
+
+use strict;
+use warnings;
+
+use UCW::Gallery;
+use Data::Dumper;
+
+my $gal = UCW::Gallery->load_config();
+
+for my $k (sort $gal->get_config_keys) {
+       my $d = Data::Dumper->new([ $gal->get($k) ]);
+       $d->Terse(1);
+       print "$k=", $d->Dump;
+}
diff --git a/bin/gal-dump-meta b/bin/gal-dump-meta
new file mode 100755 (executable)
index 0000000..1734183
--- /dev/null
@@ -0,0 +1,15 @@
+#!/usr/bin/perl
+# UCW Gallery: Dump meta-data
+# (c) 2004--2012 Martin Mares <mj@ucw.cz>
+
+use strict;
+use warnings;
+
+use UCW::Gallery;
+use Data::Dumper;
+
+@ARGV == 1 or die "Usage: $0 <meta-file>\n";
+
+my $gal = UCW::Gallery->load_config;
+my $meta = $gal->read_meta($ARGV[0]);
+print Dumper($meta);
diff --git a/bin/gal-from-gqview b/bin/gal-from-gqview
new file mode 100755 (executable)
index 0000000..138af5a
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+# UCW Gallery: Extract image list from GQview collection
+# (c) 2004--2012 Martin Mares <mj@ucw.cz>
+
+use strict;
+use warnings;
+
+while (<>) {
+       chomp;
+       /^#/ && next;
+       /^$/ && next;
+       if (/^"(.*)"$/) {
+               print "$1\n";
+       } else {
+               die "Error parsing collection: $_";
+       }
+}
diff --git a/bin/gal-gen b/bin/gal-gen
new file mode 100755 (executable)
index 0000000..1ee10b0
--- /dev/null
@@ -0,0 +1,212 @@
+#!/usr/bin/perl
+# UCW Gallery: Generate published photos
+# (c) 2004--2015 Martin Mares <mj@ucw.cz>
+
+use strict;
+use warnings;
+
+use UCW::Gallery;
+use Image::EXIF;
+use Image::Magick;
+use IO::Handle;
+use File::Spec;
+use File::Path;
+
+STDOUT->autoflush(1);
+
+my $gal = UCW::Gallery->load_config;
+
+my $orig_list = $gal->read_list('gallery.list') or die "Cannot read gallery.list: $!\n";
+
+my $photo_dir = $gal->get('PhotoDir');
+if (-d $photo_dir) {
+       print "Using existing output directory: $photo_dir\n";
+} else {
+       print "Creating output directory: $photo_dir\n";
+       File::Path::mkpath($photo_dir) or die "Unable to create $photo_dir: $!\n";
+}
+
+my $photo_meta = $gal->photo_meta_name;
+my $old_meta = {};
+if (-f $photo_meta) {
+       print "Reading old meta-data\n";
+       $old_meta = $gal->read_meta($photo_meta);
+       # use Data::Dumper; print "Read old meta: ", Dumper($old_meta), "\n";
+}
+my $meta = { 'photo' => {} };
+
+sub get_meta_basic($$$) {
+       my ($f, $m, $p) = @_;
+       my $rotate = $f->{orientation};
+
+       my ($orig_w, $orig_h, $orig_size, $orig_format) = $p->PingImage($f->{orig}) or die "Error reading " . $f->{orig} . "\n";
+       print "${orig_w}x${orig_h} ";
+
+       my ($w0, $h0) = ($rotate eq "l" || $rotate eq "r") ? ($orig_h, $orig_w) : ($orig_w, $orig_h);
+       my ($w, $h) = ($w0, $h0);
+       if ($w > $gal->get('PhotoMaxWidth')) {
+               my $s = $gal->get('PhotoMaxWidth') / $w;
+               $w = $w * $s;
+               $h = $h * $s;
+       }
+       if ($h > $gal->get('PhotoMaxHeight')) {
+               my $s = $gal->get('PhotoMaxHeight') / $h;
+               $w = $w * $s;
+               $h = $h * $s;
+       }
+       $w = int($w + .5);
+       $h = int($h + .5);
+       
+       $m->{o} = $rotate;
+       for my $k (keys %UCW::Gallery::list_attrs) {
+               next if $UCW::Gallery::list_attrs{$k} < 2;
+               $m->{$k} = $f->{$k} if defined $f->{$k};
+       }
+       $m->{w0} = $w0;
+       $m->{h0} = $h0;
+       $m->{w} = $w;
+       $m->{h} = $h;
+}
+
+sub parse_geo($) {
+       my ($g) = @_;
+       defined $g or return;
+       if ($g =~ m{^([NEWS]) (\d+)\xb0 ([0-9.]+)'$}) {
+               $g = $2 + $3/60;
+               $g = -$g if $1 eq 'W' || $1 eq 'S';
+       } elsif ($g =~ m{^([NEWS]) (\d+)\xb0 (\d+)' ([0-9.]+)$}) {
+               $g = $2 + $3/60 + $4/3600;
+               $g = -$g if $1 eq 'W' || $1 eq 'S';
+       } else {
+               print "[EXIF: unable to parse coordinate $g] ";
+               return;
+       }
+       return sprintf "%.6f", $g;
+}
+
+sub get_meta_exif($$) {
+       my ($f, $m) = @_;
+       $gal->get('CacheExif') or return;
+
+       my $e = new Image::EXIF($f->{orig});
+       my $i = $e->get_all_info();
+       if ($e->error) {
+               print "[EXIF error: ", $e->error, "] ";
+               return;
+       }
+       # use Data::Dumper; print Dumper($i);
+
+       my $lat = parse_geo($i->{image}->{'Latitude'});
+       my $lon = parse_geo($i->{image}->{'Longitude'});
+
+       my $alt = $i->{image}->{'Altitude'};
+       if ($alt) {
+               if ($alt =~ m{^([0-9.]+) m$}) {
+                       $alt = $1;
+               } else {
+                       print "[EXIF: unable to parse altitude $alt] ";
+                       $alt = undef;
+               }
+       }
+
+       # printf "[GEO: lat=%s lon=%s alt=%s] ", $lat // '?', $lon // '?', $alt // '?';
+       if ($lat && $lon) {
+               $m->{lat} //= $lat;
+               $m->{lon} //= $lon;
+       }
+       $m->{alt} //= $alt if $alt;
+
+       my $time = $i->{image}->{'Image Created'};
+       if ($time) {
+               if ($time =~ m{^(\d{4}):(\d{2}):(\d{2}) (\d{2}:\d{2}:\d{2})$}) {
+                       $m->{t} //= "$1-$2-$3 $4";
+                       # print "[TIME: ", $m->{t}, "] ";
+               } else {
+                       print "[EXIF: unable to parse time $time] ";
+               }
+       }
+}
+
+sub generate_photo($$$) {
+       my ($f, $m, $p) = @_;
+
+       my $e;
+       $e = $p->Read($f->{orig}) and die "Error reading " . $f->{orig} . ": $e";
+       $p->Strip;
+       $p->SetAttribute(quality=>90);
+
+       my $xfrm = $m->{xf};
+       if ($xfrm =~ /s/) {
+               print "-> sharpen ";
+               $p->Sharpen(1);
+       }
+       if ($xfrm =~ /h/) {
+               print "-> equalize ";
+               $p->Equalize();
+       }
+       if ($xfrm =~ /n/) {
+               print "-> normalize ";
+               $p->Normalize();
+       }
+
+       my $rotate = $m->{o};
+       my $rot = 0;
+       if ($rotate eq "l") { $rot = 270; }
+       elsif ($rotate eq "r") { $rot = 90; }
+       elsif ($rotate eq "u") { $rot = 180; }
+       if ($rot) {
+               print "-> rotate $rot ";
+               $p->Rotate(degrees=>$rot);
+       }
+
+       my ($w, $h) = ($m->{w}, $m->{h});
+       if ($w != $m->{w0} || $h != $m->{h0}) {
+               print "-> ${w}x${h} ";
+               $p->Resize(width=>$w, height=>$h);
+       }
+
+       my $photo = $gal->photo_name($m, $f->{id});
+       my $tmp = "$photo.new";
+       $e = $p->Write($tmp) and die "Unable to write $tmp: $e";
+       rename $tmp, $photo or die "Cannot rename $tmp to $photo: $!\n";
+}
+
+for my $f (@$orig_list) {
+       my $id = $f->{id};
+       print "$id: ";
+
+       my $m = { };
+       $meta->{photo}->{$id} = $m;
+       $f->{orig} = File::Spec->rel2abs($f->{file}, $gal->get('OrigDir'));
+
+       my $p = new Image::Magick;
+       get_meta_basic($f, $m, $p);
+       get_meta_exif($f, $m);
+
+       my $om = $old_meta->{photo}->{$id};
+       if ($om &&
+           $om->{o} eq $m->{o} &&
+           $om->{xf} eq $m->{xf} &&
+           $om->{w} eq $m->{w} &&
+           $om->{h} eq $m->{h}) {
+               print "... uptodate\n";
+               next;
+       }
+
+       generate_photo($f, $m, $p);
+       print "... OK\n";
+}
+
+print "Cleaning up stale files\n";
+for my $f (<$photo_dir/*.jpg>) {
+       my ($vv, $dd, $id) = File::Spec->splitpath($f);
+       $id =~ s{\..*$}{};
+       unless (defined $meta->{photo}->{$id}) {
+               print "$id: removing\n";
+               unlink $f;
+       }
+}
+
+print "Writing meta-data\n";
+$gal->write_meta($photo_meta, $meta);
+exit 0;
diff --git a/bin/gal-mj-digikam b/bin/gal-mj-digikam
new file mode 100755 (executable)
index 0000000..8576f89
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Cwd;
+use DBI;
+use Getopt::Long;
+
+if (@ARGV && $ARGV[0] eq '--help') {
+       die <<AMEN ;
+Usage: gal mj-digikam [<album>]
+AMEN
+}
+
+my $photos_root = $ENV{HOME} . '/photos';
+
+my $album = $ARGV[0];
+if (!defined $album) {
+       my $cwd = getcwd;
+       $cwd =~ m{/photos/(.*)} or die "Cannot identify album from current directory, need to specify maunally.\n";
+       $album = $1;
+}
+
+if (! -f "gallery.cf") {
+       system 'gal', 'mj-init'; die if $?;
+}
+
+my $dbfile = "$photos_root/digikam4.db";
+my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", "", "") or die "Cannot access $dbfile\n";
+
+my %alba = ();
+for my $r (@{$dbh->selectall_arrayref(
+       'SELECT a.id AS id, r.label AS label, a.relativePath AS rpath FROM Albums a JOIN AlbumRoots r ON (r.id = a.albumRoot)',
+       { Slice => {} })}) {
+       my $name = $r->{label} . $r->{rpath};
+       # print "$name\n";
+       $alba{$name} = $r->{id};
+}
+
+my $album_id = $alba{$album} // die "Unknown album $album\n";
+print "## Album $album: id=$album_id\n";
+
+my ($tag_id) = $dbh->selectrow_array('SELECT id FROM Tags WHERE pid=0 AND name="web"');
+$tag_id // die "Cannot find web tag\n";
+print "## Tag ID: $tag_id\n";
+
+my $res = $dbh->selectall_arrayref( <<AMEN, { Slice => {} },
+       SELECT
+               i.name AS name,
+               p.latitudeNumber AS lat,
+               p.longitudeNumber AS lon,
+               p.altitude AS alt
+       FROM Images i
+       JOIN ImageTags t ON (i.id = t.imageid)
+       LEFT JOIN ImagePositions p ON (i.id = p.imageid)
+       WHERE i.album=? AND t.tagid=?
+       ORDER BY i.modificationDate
+AMEN
+       $album_id,
+       $tag_id,
+       );
+
+open OUT, '|-', $ENV{GALLERY_ROOT} . '/bin/gal-scan' or die "Cannot feed gal scan\n";
+for my $r (@$res) {
+       print OUT "$photos_root/$album/" . $r->{name}, "\n";
+       for my $k (qw(lat lon alt)) {
+               print OUT "\t$k=", $r->{$k}, "\n" if defined $r->{$k};
+       }
+}
+close OUT;
diff --git a/bin/gal-mj-init b/bin/gal-mj-init
new file mode 100755 (executable)
index 0000000..1105254
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+set -e
+
+if [ -f gallery.cf ] ; then
+       echo >&2 'gallery.cf already present, giving up!'
+       exit 1
+fi
+
+cat >gallery.cf <<'AMEN'
+use strict;
+use warnings;
+use utf8;
+
+my $gal = require '../../default.cf';
+$gal->set(
+       Title => 'Unnamed',
+);
+
+return $gal;
+AMEN
diff --git a/bin/gal-mj-map b/bin/gal-mj-map
new file mode 100755 (executable)
index 0000000..b9f48d9
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Digest::SHA;
+use File::Find;
+
+STDOUT->autoflush(1);
+
+find({
+       wanted => sub {
+               my $name = $File::Find::name;
+               $name =~ s{^\./}{};
+               $name =~ m{^\d} or return;
+               $name =~ m{\.jpe?g$}i or return;
+               -f $name or return;
+               print "$name\t";
+
+               my $sha = Digest::SHA->new(1);
+               $sha->addfile($name) or die "Cannot hash $name\n";
+               my $id = substr($sha->hexdigest, 0, 16);
+               print "$id\n";
+       },
+       no_chdir => 1,
+}, ".");
diff --git a/bin/gal-mj-migrate-check b/bin/gal-mj-migrate-check
new file mode 100755 (executable)
index 0000000..5936f32
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/perl
+# Check that thumbnail aspect ratios match pre-migration data
+
+use strict;
+use warnings;
+
+use UCW::Gallery;
+
+my $gal = UCW::Gallery->load_config;
+my $meta = $gal->read_meta($gal->cache_meta_name);
+
+open T, "gallery.thumb" or die "Cannot read gallery.thumb\n";
+my @thumbs = ();
+while (<T>) {
+       chomp;
+       my ($name, $tw, $th) = split /\s+/;
+       push @thumbs, [ $tw, $th ];
+}
+close T;
+
+for my $id (@{$meta->{sequence}}) {
+       my ($tw, $th) = @{shift @thumbs} or die;
+       my $ta = $tw / $th;
+       my $m = $meta->{photo}->{$id} or die;
+       my $pw = $m->{w};
+       my $ph = $m->{h};
+       my $pa = $pw / $ph;
+       if (abs($ta - $pa) > 0.05) {
+               if (abs($ta - 1/$pa) > 0.05) {
+                       print STDERR "$id: Mismatched aspect ratio: orig $ta (${tw}x${th}) new $pa (${pw}x${ph})\n";
+               } else {
+                       print STDERR "$id: Mismatched rotation\n";
+               }
+       }
+}
diff --git a/bin/gal-mj-upgrade b/bin/gal-mj-upgrade
new file mode 100755 (executable)
index 0000000..66c260f
--- /dev/null
@@ -0,0 +1,154 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use UCW::Gallery;
+use File::Spec;
+use Image::Magick;
+
+my $photos_root = $ENV{HOME} . '/photos';
+
+my $album = $ARGV[0] // die "Usage: gal mj-upgrade <album>\n";
+print "### $album ###\n";
+
+### Scan photos.map ###
+
+my $year = $album;
+$year =~ s{/.*}{};
+if ($album eq '2009/Fireworks') { $year = 2008; }
+print "Loading map for $year\n";
+open M, "$photos_root/photos.map" or die "No map file found\n";
+my %map = ();
+while (<M>) {
+       chomp;
+       m{^$year} or next;
+       my ($path, $hash) = split /\t/;
+       my $name = $path;
+       $name =~ s{.*/}{};
+       if (defined $map{$name}) {
+               my $prev = $map{$name};
+               if ($prev->{hash} ne $hash) {
+                       print STDERR "Collision for $name: ", $prev->{path}, " vs. ", $path, "\n";
+               } else {
+                       # print STDERR "Harmless collision for $name: ", $prev->{path}, " vs. ", $path, "\n";
+               }
+       } else {
+               $map{$name} = { path => $path, hash => $hash };
+       }
+}
+close M;
+
+### Scan static list (if any) ###
+
+my @src = ();
+if (open S, "../static/photos/$album/x") {
+       print "Found static list\n";
+       while (<S>) {
+               chomp;
+               my @fields = split /\t/;
+               if (@fields == 4) {
+                       push @src, { name => $fields[0], rotate => $fields[2], xform => $fields[3] };
+               } elsif (@fields == 2) {
+                       push @src, { name => $fields[0], rotate => $fields[1], xform => '.' };
+               } else {
+                       die "Error parsing gallery list: $_\n";
+               }
+       }
+       close S;
+}
+
+### Parse index.cgi and produce gallery.new ###
+
+open I, "$album/index.cgi" or die "Cannot find $album/index.cgi\n";
+open W, ">$album/gallery.new" or die "Cannot create $album/gallery.new\n";
+open T, ">$album/gallery.thumb" or die "Cannot create $album/gallery.thumb\n";
+my %opt = ();
+my %found_dirs = ();
+my @items = ();
+while (<I>) {
+       chomp;
+       if (/^\s+"(\w+)" => "(.*)",?$/) {
+               $opt{$1} = $2;
+               print "Option: $1 = $2\n";
+       } elsif (/^img\("([^"]+)(\.jpe?g)", "([^"]*)"\);\s*# (\S+)/) {
+               my $nr = $1;
+               my $ext = $2;
+               my $title = $3;
+               my $file = $4;
+               $file =~ s{^.*/}{}g;
+
+               my $map = $map{$file};
+               if (!$map) {
+                       print STDERR "$album: No match for $file\n";
+                       next;
+               }
+               my $path = $map->{path};
+               my ($vv, $dd, $ff) = File::Spec->splitpath($path);
+               $found_dirs{$dd} = 1;
+
+               print "Image: $nr $path [$title]\n";
+               my $src;
+               if (@src) {
+                       if ($nr =~ m{^\d+$} && $nr <= @src) {
+                               $src = $src[$nr-1];
+                       } else {
+                               print STDERR "$album: Crooked refs ($nr)\n";
+                       }
+               }
+
+               print W join("\t",
+                       "$photos_root/$path",
+                       $map->{hash},
+                       ($src ? $src->{rotate} : '-'),
+                       ($src ? $src->{xform} : '.'),
+                       ($title ne "" ? $title : '-'),
+                       ), "\n";
+
+
+               my $thumb = "../static/photos/$album/$nr-thumb.jpg";
+               if (!-f $thumb) {
+                       print STDERR "$album: Cannot find thumbnail for photo $nr ($thumb)\n";
+                       print T "1 1\n";
+               } else {
+                       my $im = new Image::Magick;
+                       my ($thumb_w, $thumb_h, $thumb_size, $thumb_format) = $im->PingImage($thumb) or die "Error reading $thumb\n";
+                       print T "$thumb $thumb_w $thumb_h\n";
+               }
+       } elsif (/^($|#|require|SetOptions|\)|Start|Finish)/) {
+               # Nothing important
+       } else {
+               print STDERR "$album/index.cgi: Parse error at line $.: $_\n";
+       }
+}
+close T;
+close W;
+close I;
+
+if (scalar keys %found_dirs != 1) {
+       print STDERR "$album: Photos in multiple directories\n";
+}
+
+### Create gallery.cf ###
+
+open CF, ">$album/gallery.cf" or die "Cannot create $album/gallery.cf";
+print CF <<'AMEN' ;
+use strict;
+use warnings;
+use utf8;
+
+my $gal = require '../../default.cf';
+$gal->set(
+AMEN
+
+for my $cf (reverse sort keys %opt) {
+       print CF "\t$cf => \"", $opt{$cf}, "\",\n";
+}
+
+print CF <<'AMEN' ;
+);
+
+return $gal;
+AMEN
+
+close CF;
diff --git a/bin/gal-mj-upload b/bin/gal-mj-upload
new file mode 100755 (executable)
index 0000000..6fc7cf1
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+print "## Updating Git\n";
+system "git", "pull";
+die if $?;
+
+print "## Ensuring that photos are generated\n";
+system "gal", "gen";
+die if $?;
+
+use Cwd;
+my $cwd = getcwd;
+my ($root, $album) = $cwd =~ m{(.*)/photos/(.*)} or die "Cannot identify album from current directory, giving up.\n";
+my $static = "$root/static/gallery/photo/$album";
+-d $static or die "Cannot find generated photos in $static, giving up.\n";
+
+print "## Uploading album $album\n";
+system "rs", "$static/", "jw:www/static/gallery/photo/$album/";
+die if $?;
+
+print "## Calling editor on index files\n";
+system 'vim', 'gallery.cf', "$root/photos/Makefile", "$root/photos/index.thtml";
+die if $?;
+
+print "## Committing to repository\n";
+system 'git', 'add', 'gallery.cf', 'gallery.list'; die if #?;
+system 'git', 'add', "$root/photos/Makefile", "$root/photos/index.thtml"; die if $?;
+system 'git', 'commit'; die if $?;
+system 'git', 'push'; die if $?;
+
+print "## Pulling at the server\n";
+system "ssh", "jw", "cd web && git pull && make";
+die if $?;
+
+print "## Regenerating cache at the server\n";
+system "ssh", "jw", "cd web/photos/$album && ~/web/gal/gal cache";
+die if $?;
+
+print "Done.\n";
diff --git a/bin/gal-scan b/bin/gal-scan
new file mode 100755 (executable)
index 0000000..a976d5d
--- /dev/null
@@ -0,0 +1,150 @@
+#!/usr/bin/perl
+# UCW Gallery: Scan images and generate image list
+# (c) 2004--2015 Martin Mares <mj@ucw.cz>
+
+use strict;
+use warnings;
+
+use UCW::Gallery;
+use UCW::Gallery::Hashes;
+use File::Spec;
+use Image::EXIF;
+use Getopt::Long;
+
+if (@ARGV && $ARGV[0] eq '--help') {
+       die <<AMEN ;
+Usage: cat list | gal scan <options>
+   or: gal scan <options> <files and directories>
+   or: gal scan <options> --update
+
+Options:
+--add          Keep existing images and add new ones
+AMEN
+}
+
+my $add;
+my $update;
+GetOptions(
+       'add!' => \$add,
+       'update!' => \$update,
+) or die "Try gal scan --help\n";
+
+STDOUT->autoflush(1);
+my $gal = UCW::Gallery->load_config;
+my $orig_prefix = $gal->get('OrigDir');
+$orig_prefix =~ m{/$} or $orig_prefix .= '/';
+
+my @source = ();
+if ($update) {
+       print "Loading previous gallery.list\n";
+       my $pg = $gal->read_list('gallery.list') or die "Unable to load gallery.list\n";
+       @source = @{$pg};
+} elsif (@ARGV) {
+       for my $in (@ARGV) {
+               if (-f $in) {
+                       push @source, { file => $in };
+               } elsif (-d $in) {
+                       opendir D, $in or die "Cannot scan directory $in: $!\n";
+                       my @p = ();
+                       while (my $e = readdir D) {
+                               my $f = File::Spec->canonpath(File::Spec->catfile($in, $e));
+                               if ($f =~ m{\.(jpe?g|png)$}i) {
+                                       push @p, $f;
+                               }
+                       }
+                       closedir D;
+                       push @source, map { { file => $_ } } sort @p;
+               } else {
+                       die "$in is neither file nor directory\n";
+               }
+       }
+} else {
+       binmode STDIN, ':utf8';
+       @source = @{$gal->read_list_fh(\*STDIN)};
+}
+
+my $hashes = UCW::Gallery::Hashes->new($gal);
+
+print "Scanning photos\n";
+my @images = ();
+foreach my $src (@source) {
+       my $name = $src->{file};
+       if ($name =~ m{^/}) {
+               # Try to relativize to OrigDir
+               if (substr($name, 0, length $orig_prefix) eq $orig_prefix) {
+                       $src->{file} = $name = substr($name, length $orig_prefix);
+               }
+       }
+       print "\t$name:";
+
+       my $path = File::Spec->rel2abs($name, $gal->get('OrigDir'));
+       -f $path or die "Cannot find $path\n";
+
+       $src->{id} = $hashes->hash_image($path);
+       print " id=", $src->{id};
+
+       if (!defined $src->{orientation} || $src->{orientation} eq '-') {
+               my $e = new Image::EXIF($path);
+               my $i = $e->get_all_info();
+               if ($e->error) {
+                       print "EXIF error: ", $e->error, "\n";
+                       $src->{orientation} = '.';
+               } else {
+                       # print STDERR Dumper($i), "\n";
+                       my $o = $i->{'image'}->{'Image Orientation'} || "Top, Left-Hand";
+                       if ($o eq "Top, Left-Hand") { $o = "."; }
+                       elsif ($o eq "Right-Hand, Top") { $o = "r"; }
+                       elsif ($o eq "Left-Hand, Bottom") { $o = "l"; }
+                       elsif ($o eq "Bottom, Right-Hand") { $o = "u"; }
+                       else {
+                               print "Unrecognized orientation: $o\n";
+                               $o = ".";
+                       }
+                       $src->{orientation} = $o;
+               }
+       }
+       print " ori=", $src->{orientation};
+
+       defined $src->{xf} or $src->{xf} = $gal->get('ScanDefaultTransform');
+       print " xfrm=", $src->{xf};
+
+       $src->{title} //= '';
+       push @images, $src;
+       print "\n";
+}
+
+if (!$update) {
+       my $old = $gal->read_list('gallery.list');
+       if ($old) {
+               print "Updating gallery.list\n";
+               my %new_by_id = map { $_->{id} => $_ } @images;
+               my @result = ();
+               for my $o (@$old) {
+                       my $id = $o->{id};
+                       my $i = $new_by_id{$id};
+                       if (!$i) {
+                               if ($add) {
+                                       print "\t$id: kept\n";
+                                       push @result, $o;
+                               } else {
+                                       print "\t$id: removed\n";
+                               }
+                       } else {
+                               print "\t$id: kept\n";
+                               push @result, $o;
+                               delete $new_by_id{$id};
+                       }
+               }
+               for my $i (@images) {
+                       my $id = $i->{id};
+                       $new_by_id{$id} or next;
+                       print "\t$id: new\n";
+                       push @result, $i;
+               }
+               @images = @result;
+       }
+}
+
+$gal->write_list('gallery.list', \@images);
+print "Written gallery.list (", (scalar @images), " items)\n";
+$hashes->write;
diff --git a/gal b/gal
new file mode 100755 (executable)
index 0000000..f16a5f7
--- /dev/null
+++ b/gal
@@ -0,0 +1,114 @@
+#!/usr/bin/perl
+# UCW Gallery -- Master Program
+# (c) 2012 Martin Mares <mj@ucw.cz>
+
+use strict;
+use warnings;
+use Getopt::Long;
+
+use FindBin;
+my $gallery_root = $FindBin::Bin;
+
+my $all;
+my $parallel;
+
+sub show_help() {
+       print <<AMEN ;
+Usage: gal <general-options> <command> <command-options> <args>
+
+General options:
+    --all              Run on all galleries in subdirectories
+-p, --parallel=<n>     Run in parallel and use <n> processes
+
+Common commands:
+scan                   Scan directory and obtain originals
+gen                    Generate web photos from originals
+cache                  Rebuild thumbnails and other cached info
+
+Other commands:
+dump-config            Show configuration settings
+dump-meta              Show contents of metadata files
+from-gqview            Parse gqview/geeqie collections
+AMEN
+       exit 0;
+}
+
+Getopt::Long::Configure('require_order', 'bundling');
+GetOptions(
+       "help" => \&show_help,
+       "version" => sub {
+                       print "UCW Gallery 2.0 (c) 2004-2012 Martin Mares <mj\@ucw.cz>\n";
+               },
+       "all!" => \$all,
+       "p|parallel=i" => \$parallel,
+) or die "Try `gal help' for more information.\n";
+Getopt::Long::Configure('default');
+
+@ARGV or die "Missing subcommand.\n";
+my $sub = shift @ARGV;
+$sub =~ /^[0-9a-zA-Z-]+$/ or die "Invalid subcommand $sub\n";
+
+if ($sub eq 'help') { show_help(); }
+
+my $sub_path = "$gallery_root/bin/gal-$sub";
+-x $sub_path or die "Unknown subcommand $sub\n";
+
+$ENV{"GALLERY_ROOT"} = $gallery_root;
+$ENV{"PERL5LIB"} = join(":", $gallery_root, $ENV{"PERL5LIB"} // ());
+
+if (!$all) {
+       exec $sub_path, @ARGV;
+       die "Cannot execute $sub_path: $!\n";
+}
+
+### Parallel execution ###
+
+my @dirs = sort map { chomp; s{^\./}{}; s{\/gallery.cf}{}; $_; } `find . -mindepth 2 -name gallery.cf`;
+my $done = 0;
+my $need = @dirs;
+my $logging = $parallel ? 1 : 0;
+my $threads = $parallel // 1;
+
+my $running = 0;
+my $errors = 0;
+my %pid_to_dir = ();
+
+while ($running || @dirs) {
+       if ($running == $threads || !@dirs) {
+               # Wait for children
+               my $pid = wait; die if $pid < 0;
+               my $dir = $pid_to_dir{$pid} or die;
+               if ($?) {
+                       print "!! $dir FAILED";
+                       print " [see $dir/gallery.log]" if $logging;
+                       $errors++;
+               } else {
+                       print "++ $dir";
+                       unlink "$dir/gallery.log" if $logging;
+               }
+               delete $pid_to_dir{$pid};
+               $running--;
+               $done++;
+               print " (done $done/$need)\n";
+       } else {
+               my $dir = shift @dirs;
+               print "<< $dir\n";
+               my $pid = fork;
+               if (!$pid) {
+                       if ($logging) {
+                               close STDOUT;
+                               open STDOUT, '>', "$dir/gallery.log" or die;
+                               close STDERR;
+                               open STDERR, '>&STDOUT';
+                       }
+                       chdir $dir or die "Cannot chdir to $dir: $!\n";
+                       exec $sub_path, @ARGV;
+                       die "Cannot execute $sub_path: $!\n";
+               }
+               $pid_to_dir{$pid} = $dir;
+               $running++;
+       }
+}
+
+print "$done jobs, $errors errors.\n";
+exit ($errors > 0);
diff --git a/gal/FORMAT b/gal/FORMAT
deleted file mode 100644 (file)
index 059f2af..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-List of photos (gallery.list)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-One photo per line, tab-separated columns:
-
-       File name (relative to OrigDir in config)
-       Identifier (16 hex digits)
-       Orientation: one of "l", "r", "d", "."
-       Transformation -- sequence of:
-               n       normalize contrast
-               s       sharpen
-               h       equalize histogram
-       Title (UTF-8 string)
-
-Lines starting with "#" are ignored.
-
-Lines starting with a tab add further attributes to the previous photo.
-The following attributes are recognized:
-
-       lat             geographic latitude in degrees north of equator
-       lon             geographic longitude in degrees east of Greenwich
-       alt             geographic altitude in meters
-       t               time when the photo was taken (2014-01-25 09:40:12)
-
-
-Importing meta-data from other sources
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Occasionally, you want to add photos from a source which already provides
-some meta-data. In this case, you can construct a gallery.list with a subset
-of fields, or feed the stdin of "gal scan" with such a list.
-
-For convenience, orientation, transformation and title can be also given
-as named attributes. They are called "orientation", "xf", and "title".
-
-
-Photo meta-data (PhotoDir/gallery.meta or CacheDir/cache.meta)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Perl Storable containing a single hash.
-
-$meta->{photo}->{$identifier} is a hash of:
-       o               orientation
-       xf              transformation applied
-       w               width after scaling
-       h               height after scaling
-       w0, h0          width and height of original image
-       title           photo title
-       fmt             photo format (png/jpg; defaults to jpg)
-       lat             geographic latitude in degrees north of equator
-       lon             geographic longitude in degrees east of Greenwich
-       alt             geographic altitude in meters
-       t               time when the photo was taken (2014-01-25 09:40:12)
-
-The rest is present in cache.meta only:
-
-$meta->{sequence} is an array of photo IDs as they appear in the gallery.
-
-$meta->{thumb}->{$format}->{$identifier} is a hash of:
-       w               thumbnail width
-       h               thumbnail height
diff --git a/gal/Makefile b/gal/Makefile
deleted file mode 100644 (file)
index f7d01c2..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-$(eval $(dir-setup))
-
-$(call lib-copy, UCW/Gallery.pm)
-$(call lib-copy, $(addprefix UCW/Gallery/, Web.pm Hashes.pm Archive.pm))
-$(call lib-copy, $(addprefix UCW/Gallery/Web/, Plain.pm NrtBlue.pm HighSlide.pm))
-
-$(call copy, $(addprefix nrt-blue/,back.png bot.png left.png next.png prev.png right.png top.png style.css))
-$(call copy, $(addprefix plain/,back.png next.png prev.png style.css))
-$(call copy, $(addprefix highslide/nav/,back.png next.png prev.png))
-
-$(call copy, $(addprefix highslide/, highslide.css highslide-ie6.css highslide-with-gallery.js custom.css custom.js))
-$(call copy, $(addprefix highslide/graphics/, \
-       close.png closeX.png controlbar-black-border.gif controlbar-text-buttons.png controlbar-white-small.gif \
-       controlbar-white.gif controlbar2.gif controlbar3.gif controlbar4-hover.gif controlbar4.gif fullexpand.gif \
-       geckodimmer.png icon.gif loader.big.black.gif loader.big.white.gif loader.black.gif loader.white.gif \
-       resize.gif scrollarrows.png zoom.png zoomin.cur zoomout.cur))
-$(call copy, $(addprefix highslide/graphics/outlines/, \
-       beveled.png custom.png drop-shadow.png glossy-dark.png outer-glow.png rounded-black.png rounded-white.png))
diff --git a/gal/README b/gal/README
deleted file mode 100644 (file)
index 0a5d7c7..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-================================================================================
-
-                              UCW::Gallery v2.0
-
-                   (c) 2004--2015 Martin Mares <mj@ucw.cz>
-
-================================================================================
-
-This is a simple photo gallery for my web pages, or rather a set of bricks
-from which you can create one. It can be freely used and distributed according
-to the terms of the GNU GPL v2 or newer.
-
-Requirements
-~~~~~~~~~~~~
-Perl with the following non-core modules:
-
-   o  UCW::CGI (see http://www.ucw.cz/libucw/)
-   o  Image::Magick
-   o  Image::Exif
-   o  Archive::Zip
-   o  Digest::SHA
-
-Files and directories
-~~~~~~~~~~~~~~~~~~~~~
-
-   o  gal -- a front-end script for maintenance of galleries. All other programs
-      are run as sub-commands of this script. Try "gal --help".
-
-   o  gallery.cf -- all programs expect that the current directory contains
-      a configuration file. In fact, the config file is a perl script, whose
-      sole purpose is to set up paths, construct a gallery object and set its
-      options.
-
-      A simple example looks as follows:
-
-               use UCW::Gallery;
-
-               my $gal = UCW::Gallery->new;
-               $gal->set(Title => 'A Gallery', SubTitle => '(an example)');
-               return $gal;
-
-   o  gallery.list -- a list of original photos together with per-photo parameters.
-      Usually created by "gal scan", then tweaked manually. See `FORMATS' for
-      description of file syntax.
-
-   o  gallery.cache -- used internally by "gal scan" to store cached image hashes.
-
-   o  Originals directory -- original images, from which the gallery is generated.
-
-   o  Photo directory -- keeps processed (usually down-scaled) versions of the original
-      images. These are the images shown to the user.
-
-   o  Cache directory -- keeps various cached data, like thumbnails of all photos.
-
-   o  gallery.cgi -- interfaces the gallery to your web server. E.g.:
-
-               #!/usr/bin/perl
-
-               use lib "../path/to/gallery/modules";
-               use UCW::Gallery;
-               use UCW::Gallery::Web::Plain;
-
-               my $gal = UCW::Gallery->load_config();
-               UCW::Gallery::Web::Plain->run($gal);
-
-Workflow
-~~~~~~~~
-
-   o  vi gallery.cf
-
-   o  gal scan /path/to/originals -- this creates gallery.list, populates it with
-      descriptions of all photos and reads rotation from EXIF tags.
-
-   o  vi gallery.list -- edit image descriptions and other options.
-
-   o  gal gen -- generate photos from the originals (after this, the originals are no
-      longer needed).
-
-   o  gal cache -- generate cached data.
-
-   o  Later, the gallery can be updated:
-
-       - When you edit image descriptions, re-run "gal cache".
-       - When you edit image options (rotation etc.), re-run "gal gen".
-       - When you want to add new images, re-run "gal scan". Give it the new list
-         of images and it will try to re-use as much information from the previous
-         gallery.list as possible.
-       - When you modify existing images, run "gal scan --update".
diff --git a/gal/UCW/Gallery.pm b/gal/UCW/Gallery.pm
deleted file mode 100644 (file)
index 748fe3c..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-# Simple Photo Gallery
-# (c) 2003--2015 Martin Mares <mj@ucw.cz>
-
-package UCW::Gallery;
-
-use strict;
-use warnings;
-
-use File::Spec;
-use Storable;
-
-### Class methods ###
-
-sub new($) {
-       my ($class) = @_;
-       my $self = { };
-       $self->{cfg} = {
-               # Directories
-               OrigDir => '.',                 # Original images
-               PhotoDir => 'photo',            # Scaled-down photos for web
-               CacheDir => 'cache',            # Cache with meta-data and thumbnails
-
-               # URL prefixes
-               PhotoUrlPrefix => 'photo/',
-               ThumbUrlPrefix => 'thumb/',
-               ThemeUrlPrefix => 'gal/',
-
-               # Processing machinery settings
-               ScanDefaultTransform => 's',
-               PhotoMaxWidth => 1024,
-               PhotoMaxHeight => 1024,
-               ThumbFormats => [ "114x94" ],   # Built-in themes use the first size,
-                                               # but more can be generated
-               CacheExif => 0,                 # Cache selected EXIF meta-data
-               CacheHashes => 1,               # Let gal-scan cache file hashes
-
-               # Titles and navigation
-               Title => 'An Unnamed Gallery',
-               SubTitle => "",
-               ParentURL => '../',
-               BackURL => "",
-               FwdURL => "",
-
-               # Hacks
-               GeoHack => 0,
-       };
-       return bless $self, $class;
-}
-
-sub load_config($) {
-       my $cfg = "./gallery.cf";
-       my $self = do $cfg;
-       unless (defined $self) {
-               if ($@) {
-                       die "Error parsing $cfg: $@";
-               } elsif ($!) {
-                       die "Cannot load $cfg: $!\n";
-               } else {
-                       die "Cannot load $cfg, check that it returns true\n";
-               }
-       }
-       return $self;
-}
-
-### Object methods ###
-
-sub get($$) {
-       my ($self, $key) = @_;
-       if (exists $self->{cfg}->{$key}) {
-               my $val = $self->{cfg}->{$key};
-               defined $val or warn "Gallery: Config item $key is not set\n";
-               return $val;
-       } else {
-               warn "Gallery: Config item $key does not exist\n";
-               return;
-       }
-}
-
-sub try_get($$) {
-       my ($self, $key) = @_;
-       return $self->{cfg}->{$key};
-}
-
-sub def($@) {
-       my $self = shift;
-       while (my $key = shift @_) {
-               my $val = shift @_;
-               $self->{cfg}->{$key} //= $val;
-       }
-}
-
-sub set($@) {
-       my $self = shift;
-       while (my $key = shift @_) {
-               $self->{cfg}->{$key} = shift @_;
-       }
-}
-
-sub get_config_keys($) {
-       my ($self) = @_;
-       return keys %{$self->{cfg}};
-}
-
-our %list_attrs = (
-       'file' => 0,            # 0 = not permitted as extended attribute
-       'id' => 0,
-       'orientation' => 1,     # 1 = aliases for normal attributes
-       'title' => 1,
-       'xf' => 2,              # 2 = ... and propagated to gal-gen
-       'lat' => 3,             # 3 = normal extended attributes, propagated to gal-gen
-       'lon' => 3,
-       'alt' => 3,
-       't' => 3,
-);
-
-sub write_list($$$) {
-       my ($self, $file, $images) = @_;
-       open my $fh, '>:utf8', "$file.new" or die "Cannot create $file.new: $!\n";
-       for my $i (@$images) {
-               print $fh join("\t",
-                       $i->{file},
-                       $i->{id},
-                       $i->{orientation},
-                       $i->{xf},
-                       ($i->{title} eq '' ? '-' : $i->{title}),
-               ), "\n";
-               for my $k (keys %$i) {
-                       print $fh "\t$k=", $i->{$k}, "\n" if $list_attrs{$k} >= 3;
-               }
-       }
-       close $fh;
-       rename "$file.new", $file or die "Cannot rename $file.new to $file: $!\n";
-}
-
-sub read_list_fh($$) {
-       my ($self, $fh) = @_;
-       my @images = ();
-       while (<$fh>) {
-               chomp;
-               /^$/ and next;
-               /^#/ and next;
-               if (/^\t/) {
-                       @images or die "Misplaced continuation line before first image\n";
-                       if (my ($k, $v) = /^\t+(.*?)=(.*)/) {
-                               # Continutation of previous line
-                               my $i = $images[-1];
-                               if ($list_attrs{$k}) {
-                                       $i->{$k} = $v;
-                               } else {
-                                       print STDERR "Ignoring unknown attribute $k for ", $i->{file}, "\n";
-                               }
-                       } else {
-                               die "Invalid continuation line. Expecting 'key=value'.\n";
-                       }
-               } else {
-                       my $i = {};
-                       ($i->{file}, $i->{id}, $i->{orientation}, $i->{xf}, $i->{title}) = split /\t/;
-                       if (!defined $i->{title} || $i->{title} eq '-') { $i->{title} = ""; }
-                       push @images, $i;
-               }
-       }
-       return \@images;
-}
-
-sub read_list($$) {
-       my ($self, $file) = @_;
-       open my $fh, '<:utf8', $file or return;
-       my $list = $self->read_list_fh($fh);
-       close $fh;
-       return $list;
-}
-
-sub write_meta($$) {
-       my ($self, $file, $meta) = @_;
-       open my $fh, '>', "$file.new" or die "Cannot create $file.new: $!\n";
-       Storable::nstore_fd($meta, $fh);
-       close $fh;
-       rename "$file.new", $file or die "Cannot rename $file.new to $file: $!\n";
-}
-
-sub read_meta($) {
-       my ($self, $file) = @_;
-       open my $fh, '<', $file or die "Cannot read $file: $!\n";
-       my $meta = Storable::fd_retrieve($fh) or die "Cannot parse $file\n";
-       close $fh;
-       return $meta;
-}
-
-sub photo_meta_name($) {
-       my ($self) = @_;
-       return File::Spec->catfile($self->get('PhotoDir'), 'gallery.meta');
-}
-
-sub cache_meta_name($) {
-       my ($self) = @_;
-       return File::Spec->catfile($self->get('CacheDir'), 'cache.meta');
-}
-
-sub thumb_fmt_to_size($$) {
-       my ($self, $fmt) = @_;
-       my ($tw, $th) = ($fmt =~ m{^(\d+)x(\d+)$}) or die "Cannot parse thumbnail format $fmt\n";
-       return ($tw, $th);
-}
-
-sub photo_file_name($$$) {
-       my ($self, $photo_meta, $id) = @_;
-       return $id . '.' . ($photo_meta->{fmt} // 'jpg');
-}
-
-sub photo_name($$$) {
-       my ($self, $photo_meta, $id) = @_;
-       return File::Spec->catfile($self->get('PhotoDir'), $self->photo_file_name($photo_meta, $id));
-}
-
-
-1;
diff --git a/gal/UCW/Gallery/Archive.pm b/gal/UCW/Gallery/Archive.pm
deleted file mode 100644 (file)
index fb7c40b..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-# Simple Photo Gallery: Archiving
-# (c) 2003--2012 Martin Mares <mj@ucw.cz>
-
-package UCW::Gallery::Archive;
-
-use strict;
-use warnings;
-
-use Archive::Zip;
-use File::Spec;
-use UCW::CGI;
-
-sub send_archive($$) {
-       my ($gal, $meta) = @_;
-
-       if (!$gal->get('WebAllowArchives')) {
-               UCW::CGI::http_error('403 Archiving forbidden by server configuration');
-               return;
-       }
-
-       my $zip = Archive::Zip->new;
-       my $cnt = 0;
-       for my $id (@{$meta->{sequence}}) {
-               $zip->addFile(File::Spec->catfile($gal->get('PhotoDir'), "$id.jpg"), sprintf("%03d.jpg", $cnt)) or die;
-               $cnt++;
-       }
-
-       print "Content-type: application/zip\n";
-       print "Content-Disposition: attachment; filename=gallery.zip\n";
-       print "\n";
-       $zip->writeToFileHandle(\*STDOUT, 0);
-}
-
-42;
diff --git a/gal/UCW/Gallery/Hashes.pm b/gal/UCW/Gallery/Hashes.pm
deleted file mode 100644 (file)
index d0414e9..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-# Simple Photo Gallery: Image Hashes
-# (c) 2015 Martin Mares <mj@ucw.cz>
-
-package UCW::Gallery::Hashes;
-
-use strict;
-use warnings;
-
-use File::stat ();
-use Digest::SHA;
-
-sub new {
-       my ($class, $gal) = @_;
-       my $self = { gal => $gal };
-
-       if ($gal->get('CacheHashes') && -f 'gallery.cache') {
-               print "Loading gallery.cache\n";
-               $self->{cache} = $gal->read_meta('gallery.cache');
-       } else {
-               $self->{cache} = {};
-       }
-
-       return bless $self, $class;
-}
-
-sub write {
-       my ($self) = @_;
-       my $gal = $self->{gal};
-       if ($gal->get('CacheHashes')) {
-               print "Writing gallery.cache\n";
-               $gal->write_meta('gallery.cache', $self->{cache});
-       }
-}
-
-sub hash_image {
-       my ($self, $path) = @_;
-       my $cache = $self->{cache};
-
-       my $st = File::stat::stat($path) or die "Cannot access $path: $!\n";
-       my $key_text = join(":", $path, $st->dev, $st->ino, $st->mtime);
-       my $key = Digest::SHA->sha1_base64($key_text);
-
-       if (!exists $cache->{$key}) {
-               my $sha = Digest::SHA->new(1);
-               $sha->addfile($path) or die "Cannot hash $path\n";
-               $cache->{$key} = substr($sha->hexdigest, 0, 16);
-       }
-       return $cache->{$key};
-}
-
-42;
diff --git a/gal/UCW/Gallery/Web.pm b/gal/UCW/Gallery/Web.pm
deleted file mode 100644 (file)
index ccca477..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-# Simple Photo Gallery: Web Interface
-# (c) 2003--2012 Martin Mares <mj@ucw.cz>
-
-package UCW::Gallery::Web;
-
-use strict;
-use warnings;
-
-use UCW::Gallery;
-use UCW::CGI;
-use File::Spec;
-
-my $show_img;
-my $want_archive;
-
-my %args = (
-       'i'     => { 'var' => \$show_img, 'check' => '\d+' },
-       'a'     => { 'var' => \$want_archive },
-);
-
-sub error($) {
-       print "<p style='color:red'>Bad luck, the script is broken. Sorry.\n<p>$_[0]\n";
-       print "</body></html>\n";
-}
-
-sub get($$) {
-       my ($self, $key) = @_;
-       return $self->{gal}->get($key);
-}
-
-sub extras($$) {
-       my ($self, $key) = @_;
-       my $val = $self->get($key);
-       if (ref $val eq 'CODE') {
-               return &$val($self);
-       } else {
-               return $val;
-       }
-}
-
-# For use by extras hooks
-sub gallery($) {
-       my ($self) = @_;
-       return $self->{gal};
-}
-
-# For use by extras hooks: return true if we are showing an image page, false for index page
-sub showing_image($) {
-       my ($self) = @_;
-       return $show_img ne "";
-}
-
-sub html_top($) {
-       my ($self) = @_;
-       my $title = UCW::CGI::html_escape($self->get('Title'));
-       my $hextras = $self->extras('WebHeadExtras');
-       my $theme_hextras = $self->theme_head_extras;
-       print <<EOF ;
-Content-Type: text/html
-
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<html><head>
-$hextras$theme_hextras<title>$title</title>
-</head><body>
-EOF
-
-       $UCW::CGI::ErrorHandler::error_hook = \&error;
-
-       # WebTopExtras are evaluated separately, since they can override the error hook
-       print $self->extras('WebTopExtras');
-}
-
-sub html_bot($) {
-       my ($self) = @_;
-       print $self->extras('WebBotExtras'), "</body></html>\n";
-}
-
-sub show_img($) {
-       my ($self) = @_;
-
-       if ($show_img < 1 || $show_img > $self->{num_photos}) {
-               UCW::CGI::http_error('404 No such photo');
-               return;
-       }
-
-       my $meta = $self->{meta};
-       my $id = $meta->{sequence}->[$show_img-1];
-       my $m = $meta->{photo}->{$id} or die;
-       $self->html_top;
-
-       $self->show_links(($show_img > 1 ? ("?i=".($show_img-1)) : ""),
-                         ".",
-                         ($show_img < $self->{num_photos} ? ("?i=".($show_img+1)) : ""));
-
-       my $t = UCW::CGI::html_escape($m->{title});
-       my $w = $m->{w};
-       my $h = $m->{h};
-       print "<h1>$t</h1>\n" if $t ne "";
-       my $img = $self->get('PhotoUrlPrefix') . $self->{gal}->photo_file_name($m, $id);
-       print "<p class=large><img src='$img' width=$w height=$h alt='$t'>\n";
-
-       $self->html_bot;
-}
-
-sub show_pre_thumbs($) {
-       my ($self) = @_;
-}
-
-sub show_post_thumbs($) {
-       my ($self) = @_;
-}
-
-sub show_list($) {
-       my ($self) = @_;
-       $self->html_top;
-
-       $self->show_links($self->get('BackURL'), $self->get('ParentURL'), $self->get('FwdURL'));
-       print "<h1>", $self->get('Title'), "</h1>\n";
-       my $subtitle = $self->get('SubTitle');
-       print "<h2>$subtitle</h2>\n" if $subtitle ne "";
-       $self->show_pre_thumbs;
-
-       my $meta = $self->{meta};
-       for my $idx (1..$self->{num_photos}) {
-               my $id = $meta->{sequence}->[$idx-1];
-               my $m = $meta->{photo}->{$id};
-               my $click_url;
-               if ($self->get('WebImageSubpages')) {
-                       $click_url = "?i=$idx";
-               } else {
-                       $click_url = $self->get('PhotoUrlPrefix') . $self->{gal}->photo_file_name($m, $id);
-               }
-               $self->show_thumb($meta, $id, $click_url);
-       }
-
-       $self->show_post_thumbs;
-       $self->html_bot();
-}
-
-sub dispatch($) {
-       my ($self) = @_;
-       binmode STDOUT, ':utf8';
-       UCW::CGI::parse_args(\%args);
-       $self->{meta} = $self->{gal}->read_meta(File::Spec->catfile($self->get('CacheDir'), 'cache.meta'));
-       $self->{num_photos} = scalar @{$self->{meta}->{sequence}};
-
-       if ($want_archive) {
-               require UCW::Gallery::Archive;
-               UCW::Gallery::Archive::send_archive($self->{gal}, $self->{meta});
-       } elsif ($show_img ne "") {
-               $self->show_img;
-       } else {
-               $self->show_list;
-       }
-}
-
-sub attach($$) {
-       my ($class, $gal) = @_;
-       my $self = { gal => $gal };
-       $gal->def(
-               WebFE => $self,
-
-               # Extras are either strings or functions called with the current gallery object as parameter
-               WebHeadExtras => "",
-               WebTopExtras => "",
-               WebBotExtras => "",
-
-               # Used by the theming logic
-               WebThemeCSS => undef,
-
-               # 1 if thumbnail link to sub-pages with images, 0 if they link directly to image files
-               WebImageSubpages => 1,
-
-               # If enabled, calling the CGI with a=zip produces a ZIP archive with all photos.
-               WebAllowArchives => 1,
-       );
-       bless $self, $class;
-       return $self;
-}
-
-42;
diff --git a/gal/UCW/Gallery/Web/HighSlide.pm b/gal/UCW/Gallery/Web/HighSlide.pm
deleted file mode 100644 (file)
index b1e503b..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-# Highslide JS Theme for MJ's Photo Gallery
-# (c) 2012 Martin Mares <mj@ucw.cz>; GPL'ed
-
-package UCW::Gallery::Web::HighSlide;
-
-use strict;
-use warnings;
-use utf8;
-
-use UCW::Gallery;
-use UCW::Gallery::Web;
-
-our @ISA = qw(UCW::Gallery::Web);
-
-sub theme_head_extras($) {
-       my ($self) = @_;
-       my $hsdir = $self->get('ThemeUrlPrefix') . "highslide";
-       return $self->showing_image ? <<AMEN_MINI : <<AMEN_FULL ;
-<link rel="stylesheet" type="text/css" href="$hsdir/custom.css">
-AMEN_MINI
-<script type="text/javascript" src="$hsdir/highslide-with-gallery.js"></script>
-<script type="text/javascript" src="$hsdir/custom.js" charset="utf-8"></script>
-<script type="text/javascript">
-hs.graphicsDir = '$hsdir/graphics/';
-</script>
-<link rel="stylesheet" type="text/css" href="$hsdir/highslide.css">
-<link rel="stylesheet" type="text/css" href="$hsdir/custom.css">
-<!--[if lt IE 7]>
-<link rel="stylesheet" type="text/css" href="$hsdir/highslide-ie6.css">
-<![endif]-->
-AMEN_FULL
-}
-
-sub show_links($$$$) {
-       my ($self, $prev, $up, $next) = @_;
-       my $nav = $self->get('ThemeUrlPrefix') . "highslide/nav";
-       print "<p class=parent>";
-       print "<a href='$prev'><img class=back prev src='$nav/prev.png'></a>" if $prev ne "";
-       printf "<a href='$next'><img class=fwd src='$nav/next.png'></a>" if $next ne "";
-       printf "<a href='$up'><img class=up src='$nav/back.png'></a>" if $up ne "";
-}
-
-sub show_pre_thumbs($) {
-       my ($self) = @_;
-       print "\n<div class='highslide-gallery'><ul>\n";
-       $self->{hs_thumb_counter} = 0;
-}
-
-sub show_post_thumbs($) {
-       my ($self) = @_;
-       print "</ul></div>\n\n";
-}
-
-sub show_thumb($) {
-       my ($self, $meta, $photo_id, $click_url) = @_;
-       my $m = $meta->{photo}->{$photo_id};
-       my $annot = UCW::CGI::html_escape($m->{title});
-       my $tf = $self->get('ThumbFormats')->[0];
-       my $tm = $meta->{thumb}->{$tf}->{$photo_id} or die "No thumbnails for format $tf found!\n";
-       my $tw = $tm->{w};
-       my $th = $tm->{h};
-       my $thumb = $self->get('ThumbUrlPrefix') . "$tf/$photo_id.jpg";
-       # Highslide requires title either for all images, or for none
-       my $tit = " title=\"$annot\"";
-       my $aid = 'i'.(++$self->{hs_thumb_counter});
-       my $photo_url = $self->get('PhotoUrlPrefix') . $self->{gal}->photo_file_name($m, $photo_id);
-       print "<li><a id='$aid' href='$click_url' class=highslide onclick='return hs.expand(this, { src: \"$photo_url\" })'>";
-       print "<img src='$thumb' width=$tw height=$th alt='Photo'$tit></a>\n";
-       if ($self->get('GeoHack')) {
-               my ($lat, $lon) = ($m->{lat}, $m->{lon});
-               if (defined $lat && defined $lon) {
-                       my $local = "<a href='map.cgi?i=" . $self->{hs_thumb_counter} . "'>trasa</a>";
-                       my $osm = "<a href='http://www.openstreetmap.org/?mlat=$lat&amp;mlon=$lon#map=16/$lat/$lon'>OSM</a>";
-                       print "<div class='highslide-caption'>Ukázat na mapě: $local, $osm</div>\n";
-               }
-       }
-}
-
-sub run($$) {
-       my ($class, $gal) = @_;
-       my $self = $class->SUPER::attach($gal);
-       $self->dispatch();
-       return $self;
-}
-
-1;
diff --git a/gal/UCW/Gallery/Web/NrtBlue.pm b/gal/UCW/Gallery/Web/NrtBlue.pm
deleted file mode 100644 (file)
index 7fc5637..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-# NRT Theme for MJ's Photo Gallery
-# (c) 2003--2012 Martin Mares <mj@ucw.cz>; GPL'ed
-# Theme images taken from the cthumb package (c) Carlos Puchol
-
-package UCW::Gallery::Web::NrtBlue;
-
-use strict;
-use warnings;
-
-use UCW::Gallery;
-use UCW::Gallery::Web;
-
-our @ISA = qw(UCW::Gallery::Web);
-
-my $theme_name = "nrt-blue";
-my $navw = 48;
-my $navh = 48;
-my $interior_margin = 4;
-my $left_w = 14;
-my $right_w = 18;
-my $top_h = 14;
-my $bot_h = 18;
-
-sub theme_dir($) {
-       my ($self) = @_;
-       return $self->get('ThemeUrlPrefix') . $theme_name;
-}
-
-sub theme_head_extras($) {
-       my ($self) = @_;
-       my $stylesheet = $self->theme_dir . "/style.css";
-       return "<link rel=stylesheet href='$stylesheet' type='text/css' media=all>\n";
-}
-
-sub show_links($$$$) {
-       my ($self, $prev, $up, $next) = @_;
-       my $theme = $self->theme_dir;
-       print "<p class=parent>";
-       print "<span class=back style='width: ${navw}px; height: ${navh}px'>";
-       print "<a href='$prev'><img src='$theme/prev.png' width=${navw} height=${navh} alt='Back'></a>" if $prev ne "";
-       print "</span>\n";
-       printf "<span class=fwd style='width: ${navw}px; height: ${navh}px'>";
-       printf "<a href='$next'><img src='$theme/next.png' width=${navw} height=${navh} alt='Forward'></a>" if $next ne "";
-       print "</span>\n";
-       printf "<a href='$up'><img src='$theme/back.png' width=${navw} height=${navh} alt='Up'></a>" if $up ne "";
-}
-
-sub show_thumb($) {
-       my ($self, $meta, $photo_id, $click_url) = @_;
-       my $theme = $self->theme_dir;
-       my $m = $meta->{photo}->{$photo_id};
-       my $annot = UCW::CGI::html_escape($m->{title});
-       my $tf = $self->get('ThumbFormats')->[0];
-       my ($thumb_w, $thumb_h) = $self->{gal}->thumb_fmt_to_size($tf);
-       my $tm = $meta->{thumb}->{$tf}->{$photo_id} or die "No thumbnails for format $tf found!\n";
-       my $tw = $tm->{w};
-       my $th = $tm->{h};
-       my $thumb = $self->get('ThumbUrlPrefix') . "$tf/$photo_id.jpg";
-       my $side_w = $thumb_w + 2*$interior_margin;
-       my $side_h = $thumb_h + 2*$interior_margin;
-       my $box_w = $left_w + $side_w + $right_w;
-       my $box_h = $top_h + $side_h + $bot_h;
-       print "<div class=thf><div class=thumb>\n";
-       print "<img src='$theme/top.png' width=$box_w height=$top_h alt='' class=tt>\n";
-       print "<img src='$theme/left.png' width=$left_w height=$side_h alt='' class=tl>\n";
-       my $ol = $left_w + $interior_margin + int(($thumb_w - $tw)/2);
-       my $ot = $top_h + $interior_margin + int(($thumb_h - $th)/2);
-       my $tit = ($annot ne "") ? " title=\"$annot\"" : "";
-       print "<a href='$click_url'><img src='$thumb' width=$tw height=$th alt='Photo'$tit class=ti style='left: ${ol}px; top: ${ot}px'></a>\n";
-       print "<img src='$theme/right.png' width=$right_w height=$side_h alt='' class=tr>\n";
-       print "<img src='$theme/bot.png' width=$box_w height=$bot_h alt='' class=tb>\n";
-       print "</div></div>\n\n";
-}
-
-sub run($$) {
-       my ($class, $gal) = @_;
-       my $self = $class->SUPER::attach($gal);
-       $self->dispatch();
-       return $self;
-}
-
-1;
diff --git a/gal/UCW/Gallery/Web/Plain.pm b/gal/UCW/Gallery/Web/Plain.pm
deleted file mode 100644 (file)
index f7d213c..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-# Plain Theme for MJ's Photo Gallery
-# (c) 2012 Martin Mares <mj@ucw.cz>; GPL'ed
-
-package UCW::Gallery::Web::Plain;
-
-use strict;
-use warnings;
-
-use UCW::Gallery;
-use UCW::Gallery::Web;
-
-our @ISA = qw(UCW::Gallery::Web);
-
-my $theme_name = "plain";
-my $navw = 48;
-my $navh = 48;
-my $box_w = 130;
-my $box_h = 110;
-
-sub theme_dir($) {
-       my ($self) = @_;
-       return $self->get('ThemeUrlPrefix') . $theme_name;
-}
-
-sub theme_head_extras($) {
-       my ($self) = @_;
-       my $stylesheet = $self->theme_dir . "/style.css";
-       return "<link rel=stylesheet href='$stylesheet' type='text/css' media=all>\n";
-}
-
-sub show_links($$$$) {
-       my ($self, $prev, $up, $next) = @_;
-       my $theme = $self->theme_dir;
-       print "<p class=parent>";
-       print "<span class=back style='width: ${navw}px; height: ${navh}px'>";
-       print "<a href='$prev'><img src='$theme/prev.png' width=${navw} height=${navh} alt='Back'></a>" if $prev ne "";
-       print "</span>\n";
-       printf "<span class=fwd style='width: ${navw}px; height: ${navh}px'>";
-       printf "<a href='$next'><img src='$theme/next.png' width=${navw} height=${navh} alt='Forward'></a>" if $next ne "";
-       print "</span>\n";
-       printf "<a href='$up'><img src='$theme/back.png' width=${navw} height=${navh} alt='Up'></a>" if $up ne "";
-}
-
-sub show_thumb($) {
-       my ($self, $meta, $photo_id, $click_url) = @_;
-       my $theme = $self->theme_dir;
-       my $m = $meta->{photo}->{$photo_id};
-       my $annot = UCW::CGI::html_escape($m->{title});
-       my $tf = $self->get('ThumbFormats')->[0];
-       my ($thumb_w, $thumb_h) = $self->{gal}->thumb_fmt_to_size($tf);
-       my $tm = $meta->{thumb}->{$tf}->{$photo_id} or die "No thumbnails for format $tf found!\n";
-       my $tw = $tm->{w};
-       my $th = $tm->{h};
-       my $thumb = $self->get('ThumbUrlPrefix') . "$tf/$photo_id.jpg";
-       print "<div class=thf><div class=thumb>\n";
-       my $ol = int(($box_w - $tw)/2);
-       my $ot = int(($box_h - $th)/2);
-       my $tit = ($annot ne "") ? " title=\"$annot\"" : "";
-       print "<a href='$click_url'><img src='$thumb' width=$tw height=$th alt='Photo'$tit class=ti style='left: ${ol}px; top: ${ot}px'></a>\n";
-       print "</div></div>\n\n";
-}
-
-sub run($$) {
-       my ($class, $gal) = @_;
-       my $self = $class->SUPER::attach($gal);
-       $self->dispatch();
-       return $self;
-}
-
-1;
diff --git a/gal/bin/gal-cache b/gal/bin/gal-cache
deleted file mode 100755 (executable)
index 73c97db..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/perl
-# UCW Gallery: Prepare cache
-# (c) 2004--2012 Martin Mares <mj@ucw.cz>
-
-use strict;
-use warnings;
-
-use UCW::Gallery;
-use Image::Magick;
-use IO::Handle;
-use File::Spec;
-use File::Path;
-
-STDOUT->autoflush(1);
-
-my $gal = UCW::Gallery->load_config;
-
-print "Reading gallery.list\n";
-my $orig_list = $gal->read_list('gallery.list') or die "Cannot read gallery.list: $!\n";
-
-my $photo_meta = $gal->photo_meta_name;
-print "Reading meta-data from $photo_meta\n";
--f $photo_meta or die "Cannot load $photo_meta\n";
-my $meta = $gal->read_meta($photo_meta);
-
-my $cache_dir = $gal->get('CacheDir');
-if (-d $cache_dir) {
-       print "Deleting old cache directory: $cache_dir\n";
-       File::Path::remove_tree($cache_dir);
-}
-print "Creating cache directory: $cache_dir\n";
-File::Path::mkpath($cache_dir) or die "Unable to create $cache_dir: $!\n";
-
-# Construct sequence and store photo titles
-$meta->{sequence} = [];
-for my $f (@$orig_list) {
-       push @{$meta->{sequence}}, $f->{id};
-       my $m = $meta->{photo}->{$f->{id}} or die;
-       $m->{title} = $f->{title};
-}
-
-for my $thumb_fmt (@{$gal->get('ThumbFormats')}) {
-       print "Generating $thumb_fmt thumbnails\n";
-       my ($tw, $th) = $gal->thumb_fmt_to_size($thumb_fmt);
-       my $thumb_meta = {};
-       $meta->{thumb}->{$thumb_fmt} = $thumb_meta;
-       my $thumb_dir = File::Spec->catfile($cache_dir, $thumb_fmt);
-       -d $thumb_dir or File::Path::mkpath($thumb_dir) or die "Unable to create $thumb_dir: $!\n";
-
-       for my $id (@{$meta->{sequence}}) {
-               my $m = $meta->{photo}->{$id} or die;
-               print "\t$id: ";
-
-               my $p = new Image::Magick;
-               my $photo = $gal->photo_name($m, $id);
-               my $e;
-               $e = $p->Read($photo) and die "Error reading $photo: $e";
-
-               my $w = $m->{w};
-               my $h = $m->{h};
-               if ($w > $tw) {
-                       my $s = $tw / $w;
-                       $w = $w * $s;
-                       $h = $h * $s;
-               }
-               if ($h > $th) {
-                       my $s = $th / $h;
-                       $w = $w * $s;
-                       $h = $h * $s;
-               }
-               $w = int($w + .5);
-               $h = int($h + .5);
-               print "${w}x${h} ";
-               $p->Resize(width=>$w, height=>$h);
-
-               my $out = File::Spec->catfile($thumb_dir, "$id.jpg");
-               my $tmp = "$photo.new";
-               $e = $p->Write($tmp) and die "Unable to write $tmp: $e";
-               rename $tmp, $out or die "Unable to rename $tmp to $out: $!\n";
-
-               $thumb_meta->{$id} = {
-                       'w' => $w,
-                       'h' => $h,
-               };
-
-               print "... OK\n";
-       }
-}
-
-my $cache_meta = $gal->cache_meta_name;
-print "Writing meta-data to $cache_meta\n";
-$gal->write_meta($cache_meta, $meta);
diff --git a/gal/bin/gal-date b/gal/bin/gal-date
deleted file mode 100755 (executable)
index 451201c..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-#!/usr/bin/perl
-# UCW Gallery: Symlink photos according to their EXIF timestamps
-# (c) 2013 Martin Mares <mj@ucw.cz>
-
-use strict;
-use warnings;
-
-use Image::EXIF;
-use Getopt::Long;
-
-if (@ARGV && $ARGV[0] eq '--help') {
-       die <<AMEN ;
-Usage: cat list | gal date [<options>]
-   or: gal date [<options>] <files>
-
-Options:
--d, --destdir=<dir>    Create symlinks in specified directory (default: current dir)
--n, --dry-run          Perform a trial run with no changes made
--o, --offset=<offset>  Adjust timestamps by a given offset (<h>[:<m>[:<s>]])
-    --suffix=<s>       Add suffix to all names
--s, --symbolic         Create symbolic links (default: hardlinks)
-AMEN
-}
-
-my $destdir = '.';
-my $dry_run = 0;
-my $offset = 0;
-my $suffix = "";
-my $symbolic = 0;
-Getopt::Long::Configure('bundling');
-GetOptions(
-       'destdir|d=s' => \$destdir,
-       'dry-run|n!' => \$dry_run,
-       'offset|o=s' => \$offset,
-       'suffix=s' => \$suffix,
-       'symbolic|s!' => \$symbolic,
-) or die "Try gal date --help\n";
-
-my @src = @ARGV;
-if (!@src) {
-       while (<STDIN>) {
-               chomp;
-               push @src, $_;
-       }
-}
-
-if ($offset =~ m{^([+-])?(\d)+(:(\d+)(:(\d+))?)?$}) {
-       $offset = $2 * 3600 + ($4 // 0) * 60 + ($6 // 0);
-       if ($1 eq '-') { $offset = -$offset; }
-} else {
-       die "Invalid offset: $offset\n";
-}
-
-foreach my $f (@src) {
-       my $e = new Image::EXIF($f);
-       my $i = $e->get_all_info();
-       if ($e->error) {
-               print STDERR "EXIF error on $f: ", $e->error, "\n";
-               next;
-       }
-       # print STDERR Dumper($i), "\n";
-
-       my $d = $i->{'image'}->{'Image Created'};
-       if (!defined $d) {
-               print STDERR "No date for $f\n";
-               next;
-       }
-       my ($ty, $tm, $td, $tH, $tM, $tS) = ($d =~ m{^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$}) or die "EXIF date parse error: $d\n";
-       my $t = 3600*$tH + 60*$tM + $tS;
-       $t = int($t + $offset);
-       $tH = int($t / 3600);
-       $tM = int(($t % 3600) / 60);
-       $tS = $t % 60;
-
-       my $retry = 0;
-       my $dest = "";
-       do {
-               $dest = sprintf("%s/%04d-%02d-%02d-%02d:%02d:%02d%s%s.jpg",
-                       $destdir,
-                       $ty, $tm, $td,
-                       $tH, $tM, $tS,
-                       $suffix,
-                       $retry ? sprintf("-%d", $retry) : "");
-               $retry++;
-       } while (-f $dest);
-
-       print "$f $dest\n";
-
-       unless ($dry_run) {
-               if ($symbolic) {
-                       symlink $f, $dest or die "Cannot symlink $f to $dest: $!\n";
-               } else {
-                       link $f, $dest or die "Cannot hardlink $f to $dest: $!\n";
-               }
-       }
-}
-
-STDOUT->autoflush(1);
diff --git a/gal/bin/gal-dump-config b/gal/bin/gal-dump-config
deleted file mode 100755 (executable)
index 600a9d5..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/perl
-# UCW Gallery: Show configuration variables
-# (c) 2004--2012 Martin Mares <mj@ucw.cz>
-
-use strict;
-use warnings;
-
-use UCW::Gallery;
-use Data::Dumper;
-
-my $gal = UCW::Gallery->load_config();
-
-for my $k (sort $gal->get_config_keys) {
-       my $d = Data::Dumper->new([ $gal->get($k) ]);
-       $d->Terse(1);
-       print "$k=", $d->Dump;
-}
diff --git a/gal/bin/gal-dump-meta b/gal/bin/gal-dump-meta
deleted file mode 100755 (executable)
index 1734183..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/perl
-# UCW Gallery: Dump meta-data
-# (c) 2004--2012 Martin Mares <mj@ucw.cz>
-
-use strict;
-use warnings;
-
-use UCW::Gallery;
-use Data::Dumper;
-
-@ARGV == 1 or die "Usage: $0 <meta-file>\n";
-
-my $gal = UCW::Gallery->load_config;
-my $meta = $gal->read_meta($ARGV[0]);
-print Dumper($meta);
diff --git a/gal/bin/gal-from-gqview b/gal/bin/gal-from-gqview
deleted file mode 100755 (executable)
index 138af5a..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/perl
-# UCW Gallery: Extract image list from GQview collection
-# (c) 2004--2012 Martin Mares <mj@ucw.cz>
-
-use strict;
-use warnings;
-
-while (<>) {
-       chomp;
-       /^#/ && next;
-       /^$/ && next;
-       if (/^"(.*)"$/) {
-               print "$1\n";
-       } else {
-               die "Error parsing collection: $_";
-       }
-}
diff --git a/gal/bin/gal-gen b/gal/bin/gal-gen
deleted file mode 100755 (executable)
index 1ee10b0..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-#!/usr/bin/perl
-# UCW Gallery: Generate published photos
-# (c) 2004--2015 Martin Mares <mj@ucw.cz>
-
-use strict;
-use warnings;
-
-use UCW::Gallery;
-use Image::EXIF;
-use Image::Magick;
-use IO::Handle;
-use File::Spec;
-use File::Path;
-
-STDOUT->autoflush(1);
-
-my $gal = UCW::Gallery->load_config;
-
-my $orig_list = $gal->read_list('gallery.list') or die "Cannot read gallery.list: $!\n";
-
-my $photo_dir = $gal->get('PhotoDir');
-if (-d $photo_dir) {
-       print "Using existing output directory: $photo_dir\n";
-} else {
-       print "Creating output directory: $photo_dir\n";
-       File::Path::mkpath($photo_dir) or die "Unable to create $photo_dir: $!\n";
-}
-
-my $photo_meta = $gal->photo_meta_name;
-my $old_meta = {};
-if (-f $photo_meta) {
-       print "Reading old meta-data\n";
-       $old_meta = $gal->read_meta($photo_meta);
-       # use Data::Dumper; print "Read old meta: ", Dumper($old_meta), "\n";
-}
-my $meta = { 'photo' => {} };
-
-sub get_meta_basic($$$) {
-       my ($f, $m, $p) = @_;
-       my $rotate = $f->{orientation};
-
-       my ($orig_w, $orig_h, $orig_size, $orig_format) = $p->PingImage($f->{orig}) or die "Error reading " . $f->{orig} . "\n";
-       print "${orig_w}x${orig_h} ";
-
-       my ($w0, $h0) = ($rotate eq "l" || $rotate eq "r") ? ($orig_h, $orig_w) : ($orig_w, $orig_h);
-       my ($w, $h) = ($w0, $h0);
-       if ($w > $gal->get('PhotoMaxWidth')) {
-               my $s = $gal->get('PhotoMaxWidth') / $w;
-               $w = $w * $s;
-               $h = $h * $s;
-       }
-       if ($h > $gal->get('PhotoMaxHeight')) {
-               my $s = $gal->get('PhotoMaxHeight') / $h;
-               $w = $w * $s;
-               $h = $h * $s;
-       }
-       $w = int($w + .5);
-       $h = int($h + .5);
-       
-       $m->{o} = $rotate;
-       for my $k (keys %UCW::Gallery::list_attrs) {
-               next if $UCW::Gallery::list_attrs{$k} < 2;
-               $m->{$k} = $f->{$k} if defined $f->{$k};
-       }
-       $m->{w0} = $w0;
-       $m->{h0} = $h0;
-       $m->{w} = $w;
-       $m->{h} = $h;
-}
-
-sub parse_geo($) {
-       my ($g) = @_;
-       defined $g or return;
-       if ($g =~ m{^([NEWS]) (\d+)\xb0 ([0-9.]+)'$}) {
-               $g = $2 + $3/60;
-               $g = -$g if $1 eq 'W' || $1 eq 'S';
-       } elsif ($g =~ m{^([NEWS]) (\d+)\xb0 (\d+)' ([0-9.]+)$}) {
-               $g = $2 + $3/60 + $4/3600;
-               $g = -$g if $1 eq 'W' || $1 eq 'S';
-       } else {
-               print "[EXIF: unable to parse coordinate $g] ";
-               return;
-       }
-       return sprintf "%.6f", $g;
-}
-
-sub get_meta_exif($$) {
-       my ($f, $m) = @_;
-       $gal->get('CacheExif') or return;
-
-       my $e = new Image::EXIF($f->{orig});
-       my $i = $e->get_all_info();
-       if ($e->error) {
-               print "[EXIF error: ", $e->error, "] ";
-               return;
-       }
-       # use Data::Dumper; print Dumper($i);
-
-       my $lat = parse_geo($i->{image}->{'Latitude'});
-       my $lon = parse_geo($i->{image}->{'Longitude'});
-
-       my $alt = $i->{image}->{'Altitude'};
-       if ($alt) {
-               if ($alt =~ m{^([0-9.]+) m$}) {
-                       $alt = $1;
-               } else {
-                       print "[EXIF: unable to parse altitude $alt] ";
-                       $alt = undef;
-               }
-       }
-
-       # printf "[GEO: lat=%s lon=%s alt=%s] ", $lat // '?', $lon // '?', $alt // '?';
-       if ($lat && $lon) {
-               $m->{lat} //= $lat;
-               $m->{lon} //= $lon;
-       }
-       $m->{alt} //= $alt if $alt;
-
-       my $time = $i->{image}->{'Image Created'};
-       if ($time) {
-               if ($time =~ m{^(\d{4}):(\d{2}):(\d{2}) (\d{2}:\d{2}:\d{2})$}) {
-                       $m->{t} //= "$1-$2-$3 $4";
-                       # print "[TIME: ", $m->{t}, "] ";
-               } else {
-                       print "[EXIF: unable to parse time $time] ";
-               }
-       }
-}
-
-sub generate_photo($$$) {
-       my ($f, $m, $p) = @_;
-
-       my $e;
-       $e = $p->Read($f->{orig}) and die "Error reading " . $f->{orig} . ": $e";
-       $p->Strip;
-       $p->SetAttribute(quality=>90);
-
-       my $xfrm = $m->{xf};
-       if ($xfrm =~ /s/) {
-               print "-> sharpen ";
-               $p->Sharpen(1);
-       }
-       if ($xfrm =~ /h/) {
-               print "-> equalize ";
-               $p->Equalize();
-       }
-       if ($xfrm =~ /n/) {
-               print "-> normalize ";
-               $p->Normalize();
-       }
-
-       my $rotate = $m->{o};
-       my $rot = 0;
-       if ($rotate eq "l") { $rot = 270; }
-       elsif ($rotate eq "r") { $rot = 90; }
-       elsif ($rotate eq "u") { $rot = 180; }
-       if ($rot) {
-               print "-> rotate $rot ";
-               $p->Rotate(degrees=>$rot);
-       }
-
-       my ($w, $h) = ($m->{w}, $m->{h});
-       if ($w != $m->{w0} || $h != $m->{h0}) {
-               print "-> ${w}x${h} ";
-               $p->Resize(width=>$w, height=>$h);
-       }
-
-       my $photo = $gal->photo_name($m, $f->{id});
-       my $tmp = "$photo.new";
-       $e = $p->Write($tmp) and die "Unable to write $tmp: $e";
-       rename $tmp, $photo or die "Cannot rename $tmp to $photo: $!\n";
-}
-
-for my $f (@$orig_list) {
-       my $id = $f->{id};
-       print "$id: ";
-
-       my $m = { };
-       $meta->{photo}->{$id} = $m;
-       $f->{orig} = File::Spec->rel2abs($f->{file}, $gal->get('OrigDir'));
-
-       my $p = new Image::Magick;
-       get_meta_basic($f, $m, $p);
-       get_meta_exif($f, $m);
-
-       my $om = $old_meta->{photo}->{$id};
-       if ($om &&
-           $om->{o} eq $m->{o} &&
-           $om->{xf} eq $m->{xf} &&
-           $om->{w} eq $m->{w} &&
-           $om->{h} eq $m->{h}) {
-               print "... uptodate\n";
-               next;
-       }
-
-       generate_photo($f, $m, $p);
-       print "... OK\n";
-}
-
-print "Cleaning up stale files\n";
-for my $f (<$photo_dir/*.jpg>) {
-       my ($vv, $dd, $id) = File::Spec->splitpath($f);
-       $id =~ s{\..*$}{};
-       unless (defined $meta->{photo}->{$id}) {
-               print "$id: removing\n";
-               unlink $f;
-       }
-}
-
-print "Writing meta-data\n";
-$gal->write_meta($photo_meta, $meta);
-exit 0;
diff --git a/gal/bin/gal-mj-digikam b/gal/bin/gal-mj-digikam
deleted file mode 100755 (executable)
index 8576f89..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use Cwd;
-use DBI;
-use Getopt::Long;
-
-if (@ARGV && $ARGV[0] eq '--help') {
-       die <<AMEN ;
-Usage: gal mj-digikam [<album>]
-AMEN
-}
-
-my $photos_root = $ENV{HOME} . '/photos';
-
-my $album = $ARGV[0];
-if (!defined $album) {
-       my $cwd = getcwd;
-       $cwd =~ m{/photos/(.*)} or die "Cannot identify album from current directory, need to specify maunally.\n";
-       $album = $1;
-}
-
-if (! -f "gallery.cf") {
-       system 'gal', 'mj-init'; die if $?;
-}
-
-my $dbfile = "$photos_root/digikam4.db";
-my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", "", "") or die "Cannot access $dbfile\n";
-
-my %alba = ();
-for my $r (@{$dbh->selectall_arrayref(
-       'SELECT a.id AS id, r.label AS label, a.relativePath AS rpath FROM Albums a JOIN AlbumRoots r ON (r.id = a.albumRoot)',
-       { Slice => {} })}) {
-       my $name = $r->{label} . $r->{rpath};
-       # print "$name\n";
-       $alba{$name} = $r->{id};
-}
-
-my $album_id = $alba{$album} // die "Unknown album $album\n";
-print "## Album $album: id=$album_id\n";
-
-my ($tag_id) = $dbh->selectrow_array('SELECT id FROM Tags WHERE pid=0 AND name="web"');
-$tag_id // die "Cannot find web tag\n";
-print "## Tag ID: $tag_id\n";
-
-my $res = $dbh->selectall_arrayref( <<AMEN, { Slice => {} },
-       SELECT
-               i.name AS name,
-               p.latitudeNumber AS lat,
-               p.longitudeNumber AS lon,
-               p.altitude AS alt
-       FROM Images i
-       JOIN ImageTags t ON (i.id = t.imageid)
-       LEFT JOIN ImagePositions p ON (i.id = p.imageid)
-       WHERE i.album=? AND t.tagid=?
-       ORDER BY i.modificationDate
-AMEN
-       $album_id,
-       $tag_id,
-       );
-
-open OUT, '|-', $ENV{GALLERY_ROOT} . '/bin/gal-scan' or die "Cannot feed gal scan\n";
-for my $r (@$res) {
-       print OUT "$photos_root/$album/" . $r->{name}, "\n";
-       for my $k (qw(lat lon alt)) {
-               print OUT "\t$k=", $r->{$k}, "\n" if defined $r->{$k};
-       }
-}
-close OUT;
diff --git a/gal/bin/gal-mj-init b/gal/bin/gal-mj-init
deleted file mode 100755 (executable)
index 1105254..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-set -e
-
-if [ -f gallery.cf ] ; then
-       echo >&2 'gallery.cf already present, giving up!'
-       exit 1
-fi
-
-cat >gallery.cf <<'AMEN'
-use strict;
-use warnings;
-use utf8;
-
-my $gal = require '../../default.cf';
-$gal->set(
-       Title => 'Unnamed',
-);
-
-return $gal;
-AMEN
diff --git a/gal/bin/gal-mj-map b/gal/bin/gal-mj-map
deleted file mode 100755 (executable)
index b9f48d9..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use Digest::SHA;
-use File::Find;
-
-STDOUT->autoflush(1);
-
-find({
-       wanted => sub {
-               my $name = $File::Find::name;
-               $name =~ s{^\./}{};
-               $name =~ m{^\d} or return;
-               $name =~ m{\.jpe?g$}i or return;
-               -f $name or return;
-               print "$name\t";
-
-               my $sha = Digest::SHA->new(1);
-               $sha->addfile($name) or die "Cannot hash $name\n";
-               my $id = substr($sha->hexdigest, 0, 16);
-               print "$id\n";
-       },
-       no_chdir => 1,
-}, ".");
diff --git a/gal/bin/gal-mj-migrate-check b/gal/bin/gal-mj-migrate-check
deleted file mode 100755 (executable)
index 5936f32..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/perl
-# Check that thumbnail aspect ratios match pre-migration data
-
-use strict;
-use warnings;
-
-use UCW::Gallery;
-
-my $gal = UCW::Gallery->load_config;
-my $meta = $gal->read_meta($gal->cache_meta_name);
-
-open T, "gallery.thumb" or die "Cannot read gallery.thumb\n";
-my @thumbs = ();
-while (<T>) {
-       chomp;
-       my ($name, $tw, $th) = split /\s+/;
-       push @thumbs, [ $tw, $th ];
-}
-close T;
-
-for my $id (@{$meta->{sequence}}) {
-       my ($tw, $th) = @{shift @thumbs} or die;
-       my $ta = $tw / $th;
-       my $m = $meta->{photo}->{$id} or die;
-       my $pw = $m->{w};
-       my $ph = $m->{h};
-       my $pa = $pw / $ph;
-       if (abs($ta - $pa) > 0.05) {
-               if (abs($ta - 1/$pa) > 0.05) {
-                       print STDERR "$id: Mismatched aspect ratio: orig $ta (${tw}x${th}) new $pa (${pw}x${ph})\n";
-               } else {
-                       print STDERR "$id: Mismatched rotation\n";
-               }
-       }
-}
diff --git a/gal/bin/gal-mj-upgrade b/gal/bin/gal-mj-upgrade
deleted file mode 100755 (executable)
index 66c260f..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use UCW::Gallery;
-use File::Spec;
-use Image::Magick;
-
-my $photos_root = $ENV{HOME} . '/photos';
-
-my $album = $ARGV[0] // die "Usage: gal mj-upgrade <album>\n";
-print "### $album ###\n";
-
-### Scan photos.map ###
-
-my $year = $album;
-$year =~ s{/.*}{};
-if ($album eq '2009/Fireworks') { $year = 2008; }
-print "Loading map for $year\n";
-open M, "$photos_root/photos.map" or die "No map file found\n";
-my %map = ();
-while (<M>) {
-       chomp;
-       m{^$year} or next;
-       my ($path, $hash) = split /\t/;
-       my $name = $path;
-       $name =~ s{.*/}{};
-       if (defined $map{$name}) {
-               my $prev = $map{$name};
-               if ($prev->{hash} ne $hash) {
-                       print STDERR "Collision for $name: ", $prev->{path}, " vs. ", $path, "\n";
-               } else {
-                       # print STDERR "Harmless collision for $name: ", $prev->{path}, " vs. ", $path, "\n";
-               }
-       } else {
-               $map{$name} = { path => $path, hash => $hash };
-       }
-}
-close M;
-
-### Scan static list (if any) ###
-
-my @src = ();
-if (open S, "../static/photos/$album/x") {
-       print "Found static list\n";
-       while (<S>) {
-               chomp;
-               my @fields = split /\t/;
-               if (@fields == 4) {
-                       push @src, { name => $fields[0], rotate => $fields[2], xform => $fields[3] };
-               } elsif (@fields == 2) {
-                       push @src, { name => $fields[0], rotate => $fields[1], xform => '.' };
-               } else {
-                       die "Error parsing gallery list: $_\n";
-               }
-       }
-       close S;
-}
-
-### Parse index.cgi and produce gallery.new ###
-
-open I, "$album/index.cgi" or die "Cannot find $album/index.cgi\n";
-open W, ">$album/gallery.new" or die "Cannot create $album/gallery.new\n";
-open T, ">$album/gallery.thumb" or die "Cannot create $album/gallery.thumb\n";
-my %opt = ();
-my %found_dirs = ();
-my @items = ();
-while (<I>) {
-       chomp;
-       if (/^\s+"(\w+)" => "(.*)",?$/) {
-               $opt{$1} = $2;
-               print "Option: $1 = $2\n";
-       } elsif (/^img\("([^"]+)(\.jpe?g)", "([^"]*)"\);\s*# (\S+)/) {
-               my $nr = $1;
-               my $ext = $2;
-               my $title = $3;
-               my $file = $4;
-               $file =~ s{^.*/}{}g;
-
-               my $map = $map{$file};
-               if (!$map) {
-                       print STDERR "$album: No match for $file\n";
-                       next;
-               }
-               my $path = $map->{path};
-               my ($vv, $dd, $ff) = File::Spec->splitpath($path);
-               $found_dirs{$dd} = 1;
-
-               print "Image: $nr $path [$title]\n";
-               my $src;
-               if (@src) {
-                       if ($nr =~ m{^\d+$} && $nr <= @src) {
-                               $src = $src[$nr-1];
-                       } else {
-                               print STDERR "$album: Crooked refs ($nr)\n";
-                       }
-               }
-
-               print W join("\t",
-                       "$photos_root/$path",
-                       $map->{hash},
-                       ($src ? $src->{rotate} : '-'),
-                       ($src ? $src->{xform} : '.'),
-                       ($title ne "" ? $title : '-'),
-                       ), "\n";
-
-
-               my $thumb = "../static/photos/$album/$nr-thumb.jpg";
-               if (!-f $thumb) {
-                       print STDERR "$album: Cannot find thumbnail for photo $nr ($thumb)\n";
-                       print T "1 1\n";
-               } else {
-                       my $im = new Image::Magick;
-                       my ($thumb_w, $thumb_h, $thumb_size, $thumb_format) = $im->PingImage($thumb) or die "Error reading $thumb\n";
-                       print T "$thumb $thumb_w $thumb_h\n";
-               }
-       } elsif (/^($|#|require|SetOptions|\)|Start|Finish)/) {
-               # Nothing important
-       } else {
-               print STDERR "$album/index.cgi: Parse error at line $.: $_\n";
-       }
-}
-close T;
-close W;
-close I;
-
-if (scalar keys %found_dirs != 1) {
-       print STDERR "$album: Photos in multiple directories\n";
-}
-
-### Create gallery.cf ###
-
-open CF, ">$album/gallery.cf" or die "Cannot create $album/gallery.cf";
-print CF <<'AMEN' ;
-use strict;
-use warnings;
-use utf8;
-
-my $gal = require '../../default.cf';
-$gal->set(
-AMEN
-
-for my $cf (reverse sort keys %opt) {
-       print CF "\t$cf => \"", $opt{$cf}, "\",\n";
-}
-
-print CF <<'AMEN' ;
-);
-
-return $gal;
-AMEN
-
-close CF;
diff --git a/gal/bin/gal-mj-upload b/gal/bin/gal-mj-upload
deleted file mode 100755 (executable)
index 6fc7cf1..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-print "## Updating Git\n";
-system "git", "pull";
-die if $?;
-
-print "## Ensuring that photos are generated\n";
-system "gal", "gen";
-die if $?;
-
-use Cwd;
-my $cwd = getcwd;
-my ($root, $album) = $cwd =~ m{(.*)/photos/(.*)} or die "Cannot identify album from current directory, giving up.\n";
-my $static = "$root/static/gallery/photo/$album";
--d $static or die "Cannot find generated photos in $static, giving up.\n";
-
-print "## Uploading album $album\n";
-system "rs", "$static/", "jw:www/static/gallery/photo/$album/";
-die if $?;
-
-print "## Calling editor on index files\n";
-system 'vim', 'gallery.cf', "$root/photos/Makefile", "$root/photos/index.thtml";
-die if $?;
-
-print "## Committing to repository\n";
-system 'git', 'add', 'gallery.cf', 'gallery.list'; die if #?;
-system 'git', 'add', "$root/photos/Makefile", "$root/photos/index.thtml"; die if $?;
-system 'git', 'commit'; die if $?;
-system 'git', 'push'; die if $?;
-
-print "## Pulling at the server\n";
-system "ssh", "jw", "cd web && git pull && make";
-die if $?;
-
-print "## Regenerating cache at the server\n";
-system "ssh", "jw", "cd web/photos/$album && ~/web/gal/gal cache";
-die if $?;
-
-print "Done.\n";
diff --git a/gal/bin/gal-scan b/gal/bin/gal-scan
deleted file mode 100755 (executable)
index a976d5d..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-#!/usr/bin/perl
-# UCW Gallery: Scan images and generate image list
-# (c) 2004--2015 Martin Mares <mj@ucw.cz>
-
-use strict;
-use warnings;
-
-use UCW::Gallery;
-use UCW::Gallery::Hashes;
-use File::Spec;
-use Image::EXIF;
-use Getopt::Long;
-
-if (@ARGV && $ARGV[0] eq '--help') {
-       die <<AMEN ;
-Usage: cat list | gal scan <options>
-   or: gal scan <options> <files and directories>
-   or: gal scan <options> --update
-
-Options:
---add          Keep existing images and add new ones
-AMEN
-}
-
-my $add;
-my $update;
-GetOptions(
-       'add!' => \$add,
-       'update!' => \$update,
-) or die "Try gal scan --help\n";
-
-STDOUT->autoflush(1);
-my $gal = UCW::Gallery->load_config;
-my $orig_prefix = $gal->get('OrigDir');
-$orig_prefix =~ m{/$} or $orig_prefix .= '/';
-
-my @source = ();
-if ($update) {
-       print "Loading previous gallery.list\n";
-       my $pg = $gal->read_list('gallery.list') or die "Unable to load gallery.list\n";
-       @source = @{$pg};
-} elsif (@ARGV) {
-       for my $in (@ARGV) {
-               if (-f $in) {
-                       push @source, { file => $in };
-               } elsif (-d $in) {
-                       opendir D, $in or die "Cannot scan directory $in: $!\n";
-                       my @p = ();
-                       while (my $e = readdir D) {
-                               my $f = File::Spec->canonpath(File::Spec->catfile($in, $e));
-                               if ($f =~ m{\.(jpe?g|png)$}i) {
-                                       push @p, $f;
-                               }
-                       }
-                       closedir D;
-                       push @source, map { { file => $_ } } sort @p;
-               } else {
-                       die "$in is neither file nor directory\n";
-               }
-       }
-} else {
-       binmode STDIN, ':utf8';
-       @source = @{$gal->read_list_fh(\*STDIN)};
-}
-
-my $hashes = UCW::Gallery::Hashes->new($gal);
-
-print "Scanning photos\n";
-my @images = ();
-foreach my $src (@source) {
-       my $name = $src->{file};
-       if ($name =~ m{^/}) {
-               # Try to relativize to OrigDir
-               if (substr($name, 0, length $orig_prefix) eq $orig_prefix) {
-                       $src->{file} = $name = substr($name, length $orig_prefix);
-               }
-       }
-       print "\t$name:";
-
-       my $path = File::Spec->rel2abs($name, $gal->get('OrigDir'));
-       -f $path or die "Cannot find $path\n";
-
-       $src->{id} = $hashes->hash_image($path);
-       print " id=", $src->{id};
-
-       if (!defined $src->{orientation} || $src->{orientation} eq '-') {
-               my $e = new Image::EXIF($path);
-               my $i = $e->get_all_info();
-               if ($e->error) {
-                       print "EXIF error: ", $e->error, "\n";
-                       $src->{orientation} = '.';
-               } else {
-                       # print STDERR Dumper($i), "\n";
-                       my $o = $i->{'image'}->{'Image Orientation'} || "Top, Left-Hand";
-                       if ($o eq "Top, Left-Hand") { $o = "."; }
-                       elsif ($o eq "Right-Hand, Top") { $o = "r"; }
-                       elsif ($o eq "Left-Hand, Bottom") { $o = "l"; }
-                       elsif ($o eq "Bottom, Right-Hand") { $o = "u"; }
-                       else {
-                               print "Unrecognized orientation: $o\n";
-                               $o = ".";
-                       }
-                       $src->{orientation} = $o;
-               }
-       }
-       print " ori=", $src->{orientation};
-
-       defined $src->{xf} or $src->{xf} = $gal->get('ScanDefaultTransform');
-       print " xfrm=", $src->{xf};
-
-       $src->{title} //= '';
-       push @images, $src;
-       print "\n";
-}
-
-if (!$update) {
-       my $old = $gal->read_list('gallery.list');
-       if ($old) {
-               print "Updating gallery.list\n";
-               my %new_by_id = map { $_->{id} => $_ } @images;
-               my @result = ();
-               for my $o (@$old) {
-                       my $id = $o->{id};
-                       my $i = $new_by_id{$id};
-                       if (!$i) {
-                               if ($add) {
-                                       print "\t$id: kept\n";
-                                       push @result, $o;
-                               } else {
-                                       print "\t$id: removed\n";
-                               }
-                       } else {
-                               print "\t$id: kept\n";
-                               push @result, $o;
-                               delete $new_by_id{$id};
-                       }
-               }
-               for my $i (@images) {
-                       my $id = $i->{id};
-                       $new_by_id{$id} or next;
-                       print "\t$id: new\n";
-                       push @result, $i;
-               }
-               @images = @result;
-       }
-}
-
-$gal->write_list('gallery.list', \@images);
-print "Written gallery.list (", (scalar @images), " items)\n";
-$hashes->write;
diff --git a/gal/gal b/gal/gal
deleted file mode 100755 (executable)
index f16a5f7..0000000
--- a/gal/gal
+++ /dev/null
@@ -1,114 +0,0 @@
-#!/usr/bin/perl
-# UCW Gallery -- Master Program
-# (c) 2012 Martin Mares <mj@ucw.cz>
-
-use strict;
-use warnings;
-use Getopt::Long;
-
-use FindBin;
-my $gallery_root = $FindBin::Bin;
-
-my $all;
-my $parallel;
-
-sub show_help() {
-       print <<AMEN ;
-Usage: gal <general-options> <command> <command-options> <args>
-
-General options:
-    --all              Run on all galleries in subdirectories
--p, --parallel=<n>     Run in parallel and use <n> processes
-
-Common commands:
-scan                   Scan directory and obtain originals
-gen                    Generate web photos from originals
-cache                  Rebuild thumbnails and other cached info
-
-Other commands:
-dump-config            Show configuration settings
-dump-meta              Show contents of metadata files
-from-gqview            Parse gqview/geeqie collections
-AMEN
-       exit 0;
-}
-
-Getopt::Long::Configure('require_order', 'bundling');
-GetOptions(
-       "help" => \&show_help,
-       "version" => sub {
-                       print "UCW Gallery 2.0 (c) 2004-2012 Martin Mares <mj\@ucw.cz>\n";
-               },
-       "all!" => \$all,
-       "p|parallel=i" => \$parallel,
-) or die "Try `gal help' for more information.\n";
-Getopt::Long::Configure('default');
-
-@ARGV or die "Missing subcommand.\n";
-my $sub = shift @ARGV;
-$sub =~ /^[0-9a-zA-Z-]+$/ or die "Invalid subcommand $sub\n";
-
-if ($sub eq 'help') { show_help(); }
-
-my $sub_path = "$gallery_root/bin/gal-$sub";
--x $sub_path or die "Unknown subcommand $sub\n";
-
-$ENV{"GALLERY_ROOT"} = $gallery_root;
-$ENV{"PERL5LIB"} = join(":", $gallery_root, $ENV{"PERL5LIB"} // ());
-
-if (!$all) {
-       exec $sub_path, @ARGV;
-       die "Cannot execute $sub_path: $!\n";
-}
-
-### Parallel execution ###
-
-my @dirs = sort map { chomp; s{^\./}{}; s{\/gallery.cf}{}; $_; } `find . -mindepth 2 -name gallery.cf`;
-my $done = 0;
-my $need = @dirs;
-my $logging = $parallel ? 1 : 0;
-my $threads = $parallel // 1;
-
-my $running = 0;
-my $errors = 0;
-my %pid_to_dir = ();
-
-while ($running || @dirs) {
-       if ($running == $threads || !@dirs) {
-               # Wait for children
-               my $pid = wait; die if $pid < 0;
-               my $dir = $pid_to_dir{$pid} or die;
-               if ($?) {
-                       print "!! $dir FAILED";
-                       print " [see $dir/gallery.log]" if $logging;
-                       $errors++;
-               } else {
-                       print "++ $dir";
-                       unlink "$dir/gallery.log" if $logging;
-               }
-               delete $pid_to_dir{$pid};
-               $running--;
-               $done++;
-               print " (done $done/$need)\n";
-       } else {
-               my $dir = shift @dirs;
-               print "<< $dir\n";
-               my $pid = fork;
-               if (!$pid) {
-                       if ($logging) {
-                               close STDOUT;
-                               open STDOUT, '>', "$dir/gallery.log" or die;
-                               close STDERR;
-                               open STDERR, '>&STDOUT';
-                       }
-                       chdir $dir or die "Cannot chdir to $dir: $!\n";
-                       exec $sub_path, @ARGV;
-                       die "Cannot execute $sub_path: $!\n";
-               }
-               $pid_to_dir{$pid} = $dir;
-               $running++;
-       }
-}
-
-print "$done jobs, $errors errors.\n";
-exit ($errors > 0);
diff --git a/gal/highslide/custom.css b/gal/highslide/custom.css
deleted file mode 100644 (file)
index 6ef8df9..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- *     Site-specific stylesheet for Highslide JS gallery theme
- */
-
-H1      { text-align: center; }
-H2      { text-align: center; margin-bottom: 3ex; }
-.parent        { text-align: center; }
-.large { text-align: center; }
-
-.fwd {
-       float: right;
-       width: 48px;
-       height: 48px;
-}
-.back {
-       float: left;
-       width: 48px;
-       height: 48px;
-}
-.up { 
-       width: 48px;
-       height: 48px;
-}
-
-A[href]:hover { background-color: inherit; }
-
-.highslide-wrapper, .highslide-outline {
-       background: #111111;
-}
-.highslide img {
-       border: 1px solid #D0D0D0;
-}
-.highslide:hover img {
-       border-color: #A0A0A0;
-}
-.highslide-active-anchor img {
-       visibility: visible;
-       border-color: #808080 !important;
-}
-.highslide-image {
-       border: 2px solid #111111;
-}
-.highslide-caption {
-       color: #CCCCCC;
-       padding: 2px;
-}
-.highslide-loading {
-       color: black;
-       border: 1px solid black;
-       background-color: white;
-       background-image: url(graphics/loader.white.gif);
-}
-.highslide-controls {
-       position: static !important;
-       margin: 0;
-       width: 120px !important;
-}
-.highslide-gallery ul li {
-       width: 130px;
-       height: 110px;
-       border: 1px solid #505;
-       margin: 2px;
-}
-.highslide-dimming {
-       background: black;
-}
diff --git a/gal/highslide/custom.js b/gal/highslide/custom.js
deleted file mode 100644 (file)
index ae76993..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- *     Site-specific configuration settings for Highslide JS
- */
-
-hs.showCredits = true;
-hs.creditsPosition = 'top right';
-hs.outlineType = 'rounded-black';
-hs.fadeInOut = false;
-hs.align = 'center';
-hs.useBox = true;
-hs.width = 1080;
-hs.height = 840;
-// hs.allowMultipleInstances = false;
-hs.captionEval = 'this.thumb.title';
-hs.captionOverlay = { position: "top" };
-hs.expandDuration = 100;
-hs.restoreDuration = 100;
-hs.dimmingDuration = 100;
-hs.dimmingOpacity = 0.8;
-hs.transitionDuration = 100;
-hs.thumbnailId = 'thumb1';
-hs.numberPosition = 'caption';
-hs.transitions = ['expand', 'crossfade'];
-
-hs.addSlideshow({
-       interval: 5000,
-       repeat: false,
-       useControls: true,
-       fixedControls: 'fit',
-       overlayOptions: {
-               className: 'controls-in-heading',
-               opacity: 0.6,
-               position: 'top center',
-               hideOnMouseOut: true
-       },
-       thumbstrip: {
-               mode: 'horizontal',
-               position: 'below',
-               relativeTo: 'expander'
-       }
-});
-
-
-////////////////////////////////////////////////
-// Dynamic change of the hash part of the URL //
-////////////////////////////////////////////////
-var hashTag = '';
-// Use hashDelimiter to "hide" name of the anchor (and to not scroll the page)
-var hashDelimiter = '_';
-
-hs.extend (hs.Expander.prototype, {
-       onAfterExpand: function(sender) {
-               hashTag = this.a.id;
-               window.location.hash = hashDelimiter + hashTag;
-       },
-
-       onBeforeClose: function(sender) {
-               window.location.hash = '';
-       }
-});
-
-function showDefaultImage() {
-       var hashParts = window.location.hash.split(hashDelimiter);
-       var myThumb = document.getElementById(hashParts[1])
-       if (hashParts[1] && myThumb) myThumb.click();
-       else window.location.hash = '';
-}
-// If the new hash is not equal to the hash we store internally,
-// then it must be the user hitting the "back/forward" button
-function checkHashChange() {
-       var hashParts = window.location.hash.split(hashDelimiter);
-       if (hashParts[1] != hashTag) {
-               hs.close();
-               window.location.hash = '';
-       }
-}
-
-// Add onLoad and onHashChange functions
-if(window.onload) {
-       var current = window.onload;
-       var newAction = function() {
-               current();
-               showDefaultImage();
-       };
-       window.onload = newAction;
-} else window.onload = showDefaultImage;
-
-if(window.onhashchange) {
-       var current = window.onHashChange;
-       var newAction = function() {
-               current();
-               checkHashChange();
-       };
-       window.onhashchange = newAction;
-} else window.onhashchange = checkHashChange;
diff --git a/gal/highslide/graphics/close.png b/gal/highslide/graphics/close.png
deleted file mode 100644 (file)
index 4de4396..0000000
Binary files a/gal/highslide/graphics/close.png and /dev/null differ
diff --git a/gal/highslide/graphics/closeX.png b/gal/highslide/graphics/closeX.png
deleted file mode 100644 (file)
index cf5d018..0000000
Binary files a/gal/highslide/graphics/closeX.png and /dev/null differ
diff --git a/gal/highslide/graphics/controlbar-black-border.gif b/gal/highslide/graphics/controlbar-black-border.gif
deleted file mode 100644 (file)
index e2403fe..0000000
Binary files a/gal/highslide/graphics/controlbar-black-border.gif and /dev/null differ
diff --git a/gal/highslide/graphics/controlbar-text-buttons.png b/gal/highslide/graphics/controlbar-text-buttons.png
deleted file mode 100644 (file)
index d2f72e0..0000000
Binary files a/gal/highslide/graphics/controlbar-text-buttons.png and /dev/null differ
diff --git a/gal/highslide/graphics/controlbar-white-small.gif b/gal/highslide/graphics/controlbar-white-small.gif
deleted file mode 100644 (file)
index 462fce7..0000000
Binary files a/gal/highslide/graphics/controlbar-white-small.gif and /dev/null differ
diff --git a/gal/highslide/graphics/controlbar-white.gif b/gal/highslide/graphics/controlbar-white.gif
deleted file mode 100644 (file)
index 1f143f5..0000000
Binary files a/gal/highslide/graphics/controlbar-white.gif and /dev/null differ
diff --git a/gal/highslide/graphics/controlbar2.gif b/gal/highslide/graphics/controlbar2.gif
deleted file mode 100644 (file)
index 39ad652..0000000
Binary files a/gal/highslide/graphics/controlbar2.gif and /dev/null differ
diff --git a/gal/highslide/graphics/controlbar3.gif b/gal/highslide/graphics/controlbar3.gif
deleted file mode 100644 (file)
index 3eebb81..0000000
Binary files a/gal/highslide/graphics/controlbar3.gif and /dev/null differ
diff --git a/gal/highslide/graphics/controlbar4-hover.gif b/gal/highslide/graphics/controlbar4-hover.gif
deleted file mode 100644 (file)
index ca08b59..0000000
Binary files a/gal/highslide/graphics/controlbar4-hover.gif and /dev/null differ
diff --git a/gal/highslide/graphics/controlbar4.gif b/gal/highslide/graphics/controlbar4.gif
deleted file mode 100644 (file)
index 7a3ad34..0000000
Binary files a/gal/highslide/graphics/controlbar4.gif and /dev/null differ
diff --git a/gal/highslide/graphics/fullexpand.gif b/gal/highslide/graphics/fullexpand.gif
deleted file mode 100644 (file)
index 26d9ed0..0000000
Binary files a/gal/highslide/graphics/fullexpand.gif and /dev/null differ
diff --git a/gal/highslide/graphics/geckodimmer.png b/gal/highslide/graphics/geckodimmer.png
deleted file mode 100644 (file)
index 309bb27..0000000
Binary files a/gal/highslide/graphics/geckodimmer.png and /dev/null differ
diff --git a/gal/highslide/graphics/icon.gif b/gal/highslide/graphics/icon.gif
deleted file mode 100644 (file)
index b74a073..0000000
Binary files a/gal/highslide/graphics/icon.gif and /dev/null differ
diff --git a/gal/highslide/graphics/loader.big.black.gif b/gal/highslide/graphics/loader.big.black.gif
deleted file mode 100644 (file)
index c95d05a..0000000
Binary files a/gal/highslide/graphics/loader.big.black.gif and /dev/null differ
diff --git a/gal/highslide/graphics/loader.big.white.gif b/gal/highslide/graphics/loader.big.white.gif
deleted file mode 100644 (file)
index 3288d10..0000000
Binary files a/gal/highslide/graphics/loader.big.white.gif and /dev/null differ
diff --git a/gal/highslide/graphics/loader.black.gif b/gal/highslide/graphics/loader.black.gif
deleted file mode 100644 (file)
index 0b31f6f..0000000
Binary files a/gal/highslide/graphics/loader.black.gif and /dev/null differ
diff --git a/gal/highslide/graphics/loader.white.gif b/gal/highslide/graphics/loader.white.gif
deleted file mode 100644 (file)
index f2a1bc0..0000000
Binary files a/gal/highslide/graphics/loader.white.gif and /dev/null differ
diff --git a/gal/highslide/graphics/outlines/beveled.png b/gal/highslide/graphics/outlines/beveled.png
deleted file mode 100644 (file)
index fc428f4..0000000
Binary files a/gal/highslide/graphics/outlines/beveled.png and /dev/null differ
diff --git a/gal/highslide/graphics/outlines/custom.png b/gal/highslide/graphics/outlines/custom.png
deleted file mode 100644 (file)
index 2f20f70..0000000
Binary files a/gal/highslide/graphics/outlines/custom.png and /dev/null differ
diff --git a/gal/highslide/graphics/outlines/drop-shadow.png b/gal/highslide/graphics/outlines/drop-shadow.png
deleted file mode 100644 (file)
index 0186c2e..0000000
Binary files a/gal/highslide/graphics/outlines/drop-shadow.png and /dev/null differ
diff --git a/gal/highslide/graphics/outlines/glossy-dark.png b/gal/highslide/graphics/outlines/glossy-dark.png
deleted file mode 100644 (file)
index 3c64c0d..0000000
Binary files a/gal/highslide/graphics/outlines/glossy-dark.png and /dev/null differ
diff --git a/gal/highslide/graphics/outlines/outer-glow.png b/gal/highslide/graphics/outlines/outer-glow.png
deleted file mode 100644 (file)
index 288d43f..0000000
Binary files a/gal/highslide/graphics/outlines/outer-glow.png and /dev/null differ
diff --git a/gal/highslide/graphics/outlines/rounded-black.png b/gal/highslide/graphics/outlines/rounded-black.png
deleted file mode 100644 (file)
index a77e65d..0000000
Binary files a/gal/highslide/graphics/outlines/rounded-black.png and /dev/null differ
diff --git a/gal/highslide/graphics/outlines/rounded-white.png b/gal/highslide/graphics/outlines/rounded-white.png
deleted file mode 100644 (file)
index 0d4b817..0000000
Binary files a/gal/highslide/graphics/outlines/rounded-white.png and /dev/null differ
diff --git a/gal/highslide/graphics/resize.gif b/gal/highslide/graphics/resize.gif
deleted file mode 100644 (file)
index 9100de7..0000000
Binary files a/gal/highslide/graphics/resize.gif and /dev/null differ
diff --git a/gal/highslide/graphics/scrollarrows.png b/gal/highslide/graphics/scrollarrows.png
deleted file mode 100644 (file)
index b3d5575..0000000
Binary files a/gal/highslide/graphics/scrollarrows.png and /dev/null differ
diff --git a/gal/highslide/graphics/zoom.png b/gal/highslide/graphics/zoom.png
deleted file mode 100644 (file)
index 908612e..0000000
Binary files a/gal/highslide/graphics/zoom.png and /dev/null differ
diff --git a/gal/highslide/graphics/zoomin.cur b/gal/highslide/graphics/zoomin.cur
deleted file mode 100644 (file)
index cb79124..0000000
Binary files a/gal/highslide/graphics/zoomin.cur and /dev/null differ
diff --git a/gal/highslide/graphics/zoomout.cur b/gal/highslide/graphics/zoomout.cur
deleted file mode 100644 (file)
index acf6199..0000000
Binary files a/gal/highslide/graphics/zoomout.cur and /dev/null differ
diff --git a/gal/highslide/highslide-ie6.css b/gal/highslide/highslide-ie6.css
deleted file mode 100644 (file)
index b4d5484..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-.closebutton {
-    /* NOTE! This URL is relative to the HTML page, not the CSS */
-       filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(
-               src='../highslide/graphics/close.png', sizingMethod='scale');
-
-       background: none;
-       cursor: hand;
-}
-
-/* Viewport fixed hack */
-.highslide-viewport {
-       position: absolute;
-    left: expression( ( ( ignoreMe1 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' );
-       top: expression( ( ignoreMe2 = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ) + 'px' );
-       width: expression( ( ( ignoreMe3 = document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth ) ) + 'px' );
-       height: expression( ( ( ignoreMe4 = document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight ) ) + 'px' );
-}
-
-/* Thumbstrip PNG fix */
-.highslide-scroll-down, .highslide-scroll-up {
-       position: relative;
-       overflow: hidden;
-}
-.highslide-scroll-down div, .highslide-scroll-up div {
-       /* NOTE! This URL is relative to the HTML page, not the CSS */
-       filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(
-               src='../highslide/graphics/scrollarrows.png', sizingMethod='scale');
-       background: none !important;
-       position: absolute;
-       cursor: hand;
-       width: 75px;
-       height: 75px !important;
-}
-.highslide-thumbstrip-horizontal .highslide-scroll-down div {
-       left: -50px;
-       top: -15px;
-}
-.highslide-thumbstrip-horizontal .highslide-scroll-up div {
-       top: -15px;
-}
-.highslide-thumbstrip-vertical .highslide-scroll-down div {
-       top: -50px;
-}
-
-/* Thumbstrip marker arrow trasparent background fix */
-.highslide-thumbstrip .highslide-marker {
-       border-color: white; /* match the background */
-}
-.dark .highslide-thumbstrip-horizontal .highslide-marker {
-       border-color: #111;
-}
-.highslide-viewport .highslide-marker {
-       border-color: #333;
-}
-.highslide-thumbstrip {
-       float: left;
-}
-
-/* Positioning fixes for the control bar */
-.text-controls .highslide-controls {
-       width: 480px;
-}
-.text-controls a span {
-       width: 4em;
-}
-.text-controls .highslide-full-expand a span {
-       width: 0;
-}
-.text-controls .highslide-close a span {
-       width: 0;
-}
-
-/* Special */
-.in-page .highslide-thumbstrip-horizontal .highslide-marker {
-    border-bottom: gray;
-}
diff --git a/gal/highslide/highslide-with-gallery.js b/gal/highslide/highslide-with-gallery.js
deleted file mode 100644 (file)
index 5576e5a..0000000
+++ /dev/null
@@ -1,2687 +0,0 @@
-/**
- * Name:    Highslide JS
- * Version: 4.1.13 (2011-10-06)
- * Config:  default +events +slideshow +positioning +transitions +viewport +thumbstrip
- * Author:  Torstein Hønsi
- * Support: www.highslide.com/support
- * License: www.highslide.com/#license
- */
-if (!hs) { var hs = {
-// Language strings
-lang : {
-       cssDirection: 'ltr',
-       loadingText : 'Loading...',
-       loadingTitle : 'Click to cancel',
-       focusTitle : 'Click to bring to front',
-       fullExpandTitle : 'Expand to actual size (f)',
-       creditsText : 'Powered by <i>Highslide JS</i>',
-       creditsTitle : 'Go to the Highslide JS homepage',
-       previousText : 'Previous',
-       nextText : 'Next',
-       moveText : 'Move',
-       closeText : 'Close',
-       closeTitle : 'Close (esc)',
-       resizeTitle : 'Resize',
-       playText : 'Play',
-       playTitle : 'Play slideshow (spacebar)',
-       pauseText : 'Pause',
-       pauseTitle : 'Pause slideshow (spacebar)',
-       previousTitle : 'Previous (arrow left)',
-       nextTitle : 'Next (arrow right)',
-       moveTitle : 'Move',
-       fullExpandText : '1:1',
-       number: 'Image %1 of %2',
-       restoreTitle : 'Click to close image, click and drag to move. Use arrow keys for next and previous.'
-},
-// See http://highslide.com/ref for examples of settings
-graphicsDir : 'highslide/graphics/',
-expandCursor : 'zoomin.cur', // null disables
-restoreCursor : 'zoomout.cur', // null disables
-expandDuration : 250, // milliseconds
-restoreDuration : 250,
-marginLeft : 15,
-marginRight : 15,
-marginTop : 15,
-marginBottom : 15,
-zIndexCounter : 1001, // adjust to other absolutely positioned elements
-loadingOpacity : 0.75,
-allowMultipleInstances: true,
-numberOfImagesToPreload : 5,
-outlineWhileAnimating : 2, // 0 = never, 1 = always, 2 = HTML only
-outlineStartOffset : 3, // ends at 10
-padToMinWidth : false, // pad the popup width to make room for wide caption
-fullExpandPosition : 'bottom right',
-fullExpandOpacity : 1,
-showCredits : true, // you can set this to false if you want
-creditsHref : 'http://highslide.com/',
-creditsTarget : '_self',
-enableKeyListener : true,
-openerTagNames : ['a'], // Add more to allow slideshow indexing
-transitions : [],
-transitionDuration: 250,
-dimmingOpacity: 0, // Lightbox style dimming background
-dimmingDuration: 50, // 0 for instant dimming
-
-anchor : 'auto', // where the image expands from
-align : 'auto', // position in the client (overrides anchor)
-targetX: null, // the id of a target element
-targetY: null,
-dragByHeading: true,
-minWidth: 200,
-minHeight: 200,
-allowSizeReduction: true, // allow the image to reduce to fit client size. If false, this overrides minWidth and minHeight
-outlineType : 'drop-shadow', // set null to disable outlines
-skin : {
-       controls:
-               '<div class="highslide-controls"><ul>'+
-                       '<li class="highslide-previous">'+
-                               '<a href="#" title="{hs.lang.previousTitle}">'+
-                               '<span>{hs.lang.previousText}</span></a>'+
-                       '</li>'+
-                       '<li class="highslide-play">'+
-                               '<a href="#" title="{hs.lang.playTitle}">'+
-                               '<span>{hs.lang.playText}</span></a>'+
-                       '</li>'+
-                       '<li class="highslide-pause">'+
-                               '<a href="#" title="{hs.lang.pauseTitle}">'+
-                               '<span>{hs.lang.pauseText}</span></a>'+
-                       '</li>'+
-                       '<li class="highslide-next">'+
-                               '<a href="#" title="{hs.lang.nextTitle}">'+
-                               '<span>{hs.lang.nextText}</span></a>'+
-                       '</li>'+
-                       '<li class="highslide-move">'+
-                               '<a href="#" title="{hs.lang.moveTitle}">'+
-                               '<span>{hs.lang.moveText}</span></a>'+
-                       '</li>'+
-                       '<li class="highslide-full-expand">'+
-                               '<a href="#" title="{hs.lang.fullExpandTitle}">'+
-                               '<span>{hs.lang.fullExpandText}</span></a>'+
-                       '</li>'+
-                       '<li class="highslide-close">'+
-                               '<a href="#" title="{hs.lang.closeTitle}" >'+
-                               '<span>{hs.lang.closeText}</span></a>'+
-                       '</li>'+
-               '</ul></div>'
-},
-// END OF YOUR SETTINGS
-
-
-// declare internal properties
-preloadTheseImages : [],
-continuePreloading: true,
-expanders : [],
-overrides : [
-       'allowSizeReduction',
-       'useBox',
-       'anchor',
-       'align',
-       'targetX',
-       'targetY',
-       'outlineType',
-       'outlineWhileAnimating',
-       'captionId',
-       'captionText',
-       'captionEval',
-       'captionOverlay',
-       'headingId',
-       'headingText',
-       'headingEval',
-       'headingOverlay',
-       'creditsPosition',
-       'dragByHeading',
-       'autoplay',
-       'numberPosition',
-       'transitions',
-       'dimmingOpacity',
-
-       'width',
-       'height',
-
-       'wrapperClassName',
-       'minWidth',
-       'minHeight',
-       'maxWidth',
-       'maxHeight',
-       'pageOrigin',
-       'slideshowGroup',
-       'easing',
-       'easingClose',
-       'fadeInOut',
-       'src'
-],
-overlays : [],
-idCounter : 0,
-oPos : {
-       x: ['leftpanel', 'left', 'center', 'right', 'rightpanel'],
-       y: ['above', 'top', 'middle', 'bottom', 'below']
-},
-mouse: {},
-headingOverlay: {},
-captionOverlay: {},
-timers : [],
-
-slideshows : [],
-
-pendingOutlines : {},
-clones : {},
-onReady: [],
-uaVersion: /Trident\/4\.0/.test(navigator.userAgent) ? 8 :
-       parseFloat((navigator.userAgent.toLowerCase().match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1]),
-ie : (document.all && !window.opera),
-//ie : navigator && /MSIE [678]/.test(navigator.userAgent), // ie9 compliant?
-safari : /Safari/.test(navigator.userAgent),
-geckoMac : /Macintosh.+rv:1\.[0-8].+Gecko/.test(navigator.userAgent),
-
-$ : function (id) {
-       if (id) return document.getElementById(id);
-},
-
-push : function (arr, val) {
-       arr[arr.length] = val;
-},
-
-createElement : function (tag, attribs, styles, parent, nopad) {
-       var el = document.createElement(tag);
-       if (attribs) hs.extend(el, attribs);
-       if (nopad) hs.setStyles(el, {padding: 0, border: 'none', margin: 0});
-       if (styles) hs.setStyles(el, styles);
-       if (parent) parent.appendChild(el);
-       return el;
-},
-
-extend : function (el, attribs) {
-       for (var x in attribs) el[x] = attribs[x];
-       return el;
-},
-
-setStyles : function (el, styles) {
-       for (var x in styles) {
-               if (hs.ieLt9 && x == 'opacity') {
-                       if (styles[x] > 0.99) el.style.removeAttribute('filter');
-                       else el.style.filter = 'alpha(opacity='+ (styles[x] * 100) +')';
-               }
-               else el.style[x] = styles[x];
-       }
-},
-animate: function(el, prop, opt) {
-       var start,
-               end,
-               unit;
-       if (typeof opt != 'object' || opt === null) {
-               var args = arguments;
-               opt = {
-                       duration: args[2],
-                       easing: args[3],
-                       complete: args[4]
-               };
-       }
-       if (typeof opt.duration != 'number') opt.duration = 250;
-       opt.easing = Math[opt.easing] || Math.easeInQuad;
-       opt.curAnim = hs.extend({}, prop);
-       for (var name in prop) {
-               var e = new hs.fx(el, opt , name );
-
-               start = parseFloat(hs.css(el, name)) || 0;
-               end = parseFloat(prop[name]);
-               unit = name != 'opacity' ? 'px' : '';
-
-               e.custom( start, end, unit );
-       }
-},
-css: function(el, prop) {
-       if (el.style[prop]) {
-               return el.style[prop];
-       } else if (document.defaultView) {
-               return document.defaultView.getComputedStyle(el, null).getPropertyValue(prop);
-
-       } else {
-               if (prop == 'opacity') prop = 'filter';
-               var val = el.currentStyle[prop.replace(/\-(\w)/g, function (a, b){ return b.toUpperCase(); })];
-               if (prop == 'filter')
-                       val = val.replace(/alpha\(opacity=([0-9]+)\)/,
-                               function (a, b) { return b / 100 });
-               return val === '' ? 1 : val;
-       }
-},
-
-getPageSize : function () {
-       var d = document, w = window, iebody = d.compatMode && d.compatMode != 'BackCompat'
-               ? d.documentElement : d.body,
-               ieLt9 = hs.ie && (hs.uaVersion < 9 || typeof pageXOffset == 'undefined');
-
-       var width = ieLt9 ? iebody.clientWidth :
-                       (d.documentElement.clientWidth || self.innerWidth),
-               height = ieLt9 ? iebody.clientHeight : self.innerHeight;
-       hs.page = {
-               width: width,
-               height: height,
-               scrollLeft: ieLt9 ? iebody.scrollLeft : pageXOffset,
-               scrollTop: ieLt9 ? iebody.scrollTop : pageYOffset
-       };
-       return hs.page;
-},
-
-getPosition : function(el)     {
-       var p = { x: el.offsetLeft, y: el.offsetTop };
-       while (el.offsetParent) {
-               el = el.offsetParent;
-               p.x += el.offsetLeft;
-               p.y += el.offsetTop;
-               if (el != document.body && el != document.documentElement) {
-                       p.x -= el.scrollLeft;
-                       p.y -= el.scrollTop;
-               }
-       }
-       return p;
-},
-
-expand : function(a, params, custom, type) {
-       if (!a) a = hs.createElement('a', null, { display: 'none' }, hs.container);
-       if (typeof a.getParams == 'function') return params;
-       try {
-               new hs.Expander(a, params, custom);
-               return false;
-       } catch (e) { return true; }
-},
-getElementByClass : function (el, tagName, className) {
-       var els = el.getElementsByTagName(tagName);
-       for (var i = 0; i < els.length; i++) {
-               if ((new RegExp(className)).test(els[i].className)) {
-                       return els[i];
-               }
-       }
-       return null;
-},
-replaceLang : function(s) {
-       s = s.replace(/\s/g, ' ');
-       var re = /{hs\.lang\.([^}]+)\}/g,
-               matches = s.match(re),
-               lang;
-       if (matches) for (var i = 0; i < matches.length; i++) {
-               lang = matches[i].replace(re, "$1");
-               if (typeof hs.lang[lang] != 'undefined') s = s.replace(matches[i], hs.lang[lang]);
-       }
-       return s;
-},
-
-
-focusTopmost : function() {
-       var topZ = 0,
-               topmostKey = -1,
-               expanders = hs.expanders,
-               exp,
-               zIndex;
-       for (var i = 0; i < expanders.length; i++) {
-               exp = expanders[i];
-               if (exp) {
-                       zIndex = exp.wrapper.style.zIndex;
-                       if (zIndex && zIndex > topZ) {
-                               topZ = zIndex;
-                               topmostKey = i;
-                       }
-               }
-       }
-       if (topmostKey == -1) hs.focusKey = -1;
-       else expanders[topmostKey].focus();
-},
-
-getParam : function (a, param) {
-       a.getParams = a.onclick;
-       var p = a.getParams ? a.getParams() : null;
-       a.getParams = null;
-
-       return (p && typeof p[param] != 'undefined') ? p[param] :
-               (typeof hs[param] != 'undefined' ? hs[param] : null);
-},
-
-getSrc : function (a) {
-       var src = hs.getParam(a, 'src');
-       if (src) return src;
-       return a.href;
-},
-
-getNode : function (id) {
-       var node = hs.$(id), clone = hs.clones[id], a = {};
-       if (!node && !clone) return null;
-       if (!clone) {
-               clone = node.cloneNode(true);
-               clone.id = '';
-               hs.clones[id] = clone;
-               return node;
-       } else {
-               return clone.cloneNode(true);
-       }
-},
-
-discardElement : function(d) {
-       if (d) hs.garbageBin.appendChild(d);
-       hs.garbageBin.innerHTML = '';
-},
-dim : function(exp) {
-       if (!hs.dimmer) {
-               isNew = true;
-               hs.dimmer = hs.createElement ('div', {
-                               className: 'highslide-dimming highslide-viewport-size',
-                               owner: '',
-                               onclick: function() {
-                                       if (hs.fireEvent(hs, 'onDimmerClick'))
-
-                                               hs.close();
-                               }
-                       }, {
-                               visibility: 'visible',
-                               opacity: 0
-                       }, hs.container, true);
-
-               if (/(Android|iPad|iPhone|iPod)/.test(navigator.userAgent)) {
-                       var body = document.body;
-                       function pixDimmerSize() {
-                               hs.setStyles(hs.dimmer, {
-                                       width: body.scrollWidth +'px',
-                                       height: body.scrollHeight +'px'
-                               });
-                       }
-                       pixDimmerSize();
-                       hs.addEventListener(window, 'resize', pixDimmerSize);
-               }
-       }
-       hs.dimmer.style.display = '';
-
-       var isNew = hs.dimmer.owner == '';
-       hs.dimmer.owner += '|'+ exp.key;
-
-       if (isNew) {
-               if (hs.geckoMac && hs.dimmingGeckoFix)
-                       hs.setStyles(hs.dimmer, {
-                               background: 'url('+ hs.graphicsDir + 'geckodimmer.png)',
-                               opacity: 1
-                       });
-               else
-                       hs.animate(hs.dimmer, { opacity: exp.dimmingOpacity }, hs.dimmingDuration);
-       }
-},
-undim : function(key) {
-       if (!hs.dimmer) return;
-       if (typeof key != 'undefined') hs.dimmer.owner = hs.dimmer.owner.replace('|'+ key, '');
-
-       if (
-               (typeof key != 'undefined' && hs.dimmer.owner != '')
-               || (hs.upcoming && hs.getParam(hs.upcoming, 'dimmingOpacity'))
-       ) return;
-
-       if (hs.geckoMac && hs.dimmingGeckoFix) hs.dimmer.style.display = 'none';
-       else hs.animate(hs.dimmer, { opacity: 0 }, hs.dimmingDuration, null, function() {
-               hs.dimmer.style.display = 'none';
-       });
-},
-transit : function (adj, exp) {
-       var last = exp || hs.getExpander();
-       exp = last;
-       if (hs.upcoming) return false;
-       else hs.last = last;
-       hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
-       try {
-               hs.upcoming = adj;
-               adj.onclick();
-       } catch (e){
-               hs.last = hs.upcoming = null;
-       }
-       try {
-               if (!adj || exp.transitions[1] != 'crossfade')
-               exp.close();
-       } catch (e) {}
-       return false;
-},
-
-previousOrNext : function (el, op) {
-       var exp = hs.getExpander(el);
-       if (exp) return hs.transit(exp.getAdjacentAnchor(op), exp);
-       else return false;
-},
-
-previous : function (el) {
-       return hs.previousOrNext(el, -1);
-},
-
-next : function (el) {
-       return hs.previousOrNext(el, 1);
-},
-
-keyHandler : function(e) {
-       if (!e) e = window.event;
-       if (!e.target) e.target = e.srcElement; // ie
-       if (typeof e.target.form != 'undefined') return true; // form element has focus
-       if (!hs.fireEvent(hs, 'onKeyDown', e)) return true;
-       var exp = hs.getExpander();
-
-       var op = null;
-       switch (e.keyCode) {
-               case 70: // f
-                       if (exp) exp.doFullExpand();
-                       return true;
-               case 32: // Space
-                       op = 2;
-                       break;
-               case 34: // Page Down
-               case 39: // Arrow right
-               case 40: // Arrow down
-                       op = 1;
-                       break;
-               case 8:  // Backspace
-               case 33: // Page Up
-               case 37: // Arrow left
-               case 38: // Arrow up
-                       op = -1;
-                       break;
-               case 27: // Escape
-               case 13: // Enter
-                       op = 0;
-       }
-       if (op !== null) {if (op != 2)hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
-               if (!hs.enableKeyListener) return true;
-
-               if (e.preventDefault) e.preventDefault();
-               else e.returnValue = false;
-               if (exp) {
-                       if (op == 0) {
-                               exp.close();
-                       } else if (op == 2) {
-                               if (exp.slideshow) exp.slideshow.hitSpace();
-                       } else {
-                               if (exp.slideshow) exp.slideshow.pause();
-                               hs.previousOrNext(exp.key, op);
-                       }
-                       return false;
-               }
-       }
-       return true;
-},
-
-
-registerOverlay : function (overlay) {
-       hs.push(hs.overlays, hs.extend(overlay, { hsId: 'hsId'+ hs.idCounter++ } ));
-},
-
-
-addSlideshow : function (options) {
-       var sg = options.slideshowGroup;
-       if (typeof sg == 'object') {
-               for (var i = 0; i < sg.length; i++) {
-                       var o = {};
-                       for (var x in options) o[x] = options[x];
-                       o.slideshowGroup = sg[i];
-                       hs.push(hs.slideshows, o);
-               }
-       } else {
-               hs.push(hs.slideshows, options);
-       }
-},
-
-getWrapperKey : function (element, expOnly) {
-       var el, re = /^highslide-wrapper-([0-9]+)$/;
-       // 1. look in open expanders
-       el = element;
-       while (el.parentNode)   {
-               if (el.hsKey !== undefined) return el.hsKey;
-               if (el.id && re.test(el.id)) return el.id.replace(re, "$1");
-               el = el.parentNode;
-       }
-       // 2. look in thumbnail
-       if (!expOnly) {
-               el = element;
-               while (el.parentNode)   {
-                       if (el.tagName && hs.isHsAnchor(el)) {
-                               for (var key = 0; key < hs.expanders.length; key++) {
-                                       var exp = hs.expanders[key];
-                                       if (exp && exp.a == el) return key;
-                               }
-                       }
-                       el = el.parentNode;
-               }
-       }
-       return null;
-},
-
-getExpander : function (el, expOnly) {
-       if (typeof el == 'undefined') return hs.expanders[hs.focusKey] || null;
-       if (typeof el == 'number') return hs.expanders[el] || null;
-       if (typeof el == 'string') el = hs.$(el);
-       return hs.expanders[hs.getWrapperKey(el, expOnly)] || null;
-},
-
-isHsAnchor : function (a) {
-       return (a.onclick && a.onclick.toString().replace(/\s/g, ' ').match(/hs.(htmlE|e)xpand/));
-},
-
-reOrder : function () {
-       for (var i = 0; i < hs.expanders.length; i++)
-               if (hs.expanders[i] && hs.expanders[i].isExpanded) hs.focusTopmost();
-},
-fireEvent : function (obj, evt, args) {
-       return obj && obj[evt] ? (obj[evt](obj, args) !== false) : true;
-},
-
-mouseClickHandler : function(e)
-{
-       if (!e) e = window.event;
-       if (e.button > 1) return true;
-       if (!e.target) e.target = e.srcElement;
-
-       var el = e.target;
-       while (el.parentNode
-               && !(/highslide-(image|move|html|resize)/.test(el.className)))
-       {
-               el = el.parentNode;
-       }
-       var exp = hs.getExpander(el);
-       if (exp && (exp.isClosing || !exp.isExpanded)) return true;
-
-       if (exp && e.type == 'mousedown') {
-               if (e.target.form) return true;
-               var match = el.className.match(/highslide-(image|move|resize)/);
-               if (match) {
-                       hs.dragArgs = {
-                               exp: exp ,
-                               type: match[1],
-                               left: exp.x.pos,
-                               width: exp.x.size,
-                               top: exp.y.pos,
-                               height: exp.y.size,
-                               clickX: e.clientX,
-                               clickY: e.clientY
-                       };
-
-
-                       hs.addEventListener(document, 'mousemove', hs.dragHandler);
-                       if (e.preventDefault) e.preventDefault(); // FF
-
-                       if (/highslide-(image|html)-blur/.test(exp.content.className)) {
-                               exp.focus();
-                               hs.hasFocused = true;
-                       }
-                       return false;
-               }
-       } else if (e.type == 'mouseup') {
-
-               hs.removeEventListener(document, 'mousemove', hs.dragHandler);
-
-               if (hs.dragArgs) {
-                       if (hs.styleRestoreCursor && hs.dragArgs.type == 'image')
-                               hs.dragArgs.exp.content.style.cursor = hs.styleRestoreCursor;
-                       var hasDragged = hs.dragArgs.hasDragged;
-
-                       if (!hasDragged &&!hs.hasFocused && !/(move|resize)/.test(hs.dragArgs.type)) {
-                               if (hs.fireEvent(exp, 'onImageClick'))
-                               exp.close();
-                       }
-                       else if (hasDragged || (!hasDragged && hs.hasHtmlExpanders)) {
-                               hs.dragArgs.exp.doShowHide('hidden');
-                       }
-
-                       if (hasDragged) hs.fireEvent(hs.dragArgs.exp, 'onDrop', hs.dragArgs);
-                       hs.hasFocused = false;
-                       hs.dragArgs = null;
-
-               } else if (/highslide-image-blur/.test(el.className)) {
-                       el.style.cursor = hs.styleRestoreCursor;
-               }
-       }
-       return false;
-},
-
-dragHandler : function(e)
-{
-       if (!hs.dragArgs) return true;
-       if (!e) e = window.event;
-       var a = hs.dragArgs, exp = a.exp;
-
-       a.dX = e.clientX - a.clickX;
-       a.dY = e.clientY - a.clickY;
-
-       var distance = Math.sqrt(Math.pow(a.dX, 2) + Math.pow(a.dY, 2));
-       if (!a.hasDragged) a.hasDragged = (a.type != 'image' && distance > 0)
-               || (distance > (hs.dragSensitivity || 5));
-
-       if (a.hasDragged && e.clientX > 5 && e.clientY > 5) {
-               if (!hs.fireEvent(exp, 'onDrag', a)) return false;
-
-               if (a.type == 'resize') exp.resize(a);
-               else {
-                       exp.moveTo(a.left + a.dX, a.top + a.dY);
-                       if (a.type == 'image') exp.content.style.cursor = 'move';
-               }
-       }
-       return false;
-},
-
-wrapperMouseHandler : function (e) {
-       try {
-               if (!e) e = window.event;
-               var over = /mouseover/i.test(e.type);
-               if (!e.target) e.target = e.srcElement; // ie
-               if (!e.relatedTarget) e.relatedTarget =
-                       over ? e.fromElement : e.toElement; // ie
-               var exp = hs.getExpander(e.target);
-               if (!exp.isExpanded) return;
-               if (!exp || !e.relatedTarget || hs.getExpander(e.relatedTarget, true) == exp
-                       || hs.dragArgs) return;
-               hs.fireEvent(exp, over ? 'onMouseOver' : 'onMouseOut', e);
-               for (var i = 0; i < exp.overlays.length; i++) (function() {
-                       var o = hs.$('hsId'+ exp.overlays[i]);
-                       if (o && o.hideOnMouseOut) {
-                               if (over) hs.setStyles(o, { visibility: 'visible', display: '' });
-                               hs.animate(o, { opacity: over ? o.opacity : 0 }, o.dur);
-                       }
-               })();
-       } catch (e) {}
-},
-addEventListener : function (el, event, func) {
-       if (el == document && event == 'ready') {
-               hs.push(hs.onReady, func);
-       }
-       try {
-               el.addEventListener(event, func, false);
-       } catch (e) {
-               try {
-                       el.detachEvent('on'+ event, func);
-                       el.attachEvent('on'+ event, func);
-               } catch (e) {
-                       el['on'+ event] = func;
-               }
-       }
-},
-
-removeEventListener : function (el, event, func) {
-       try {
-               el.removeEventListener(event, func, false);
-       } catch (e) {
-               try {
-                       el.detachEvent('on'+ event, func);
-               } catch (e) {
-                       el['on'+ event] = null;
-               }
-       }
-},
-
-preloadFullImage : function (i) {
-       if (hs.continuePreloading && hs.preloadTheseImages[i] && hs.preloadTheseImages[i] != 'undefined') {
-               var img = document.createElement('img');
-               img.onload = function() {
-                       img = null;
-                       hs.preloadFullImage(i + 1);
-               };
-               img.src = hs.preloadTheseImages[i];
-       }
-},
-preloadImages : function (number) {
-       if (number && typeof number != 'object') hs.numberOfImagesToPreload = number;
-
-       var arr = hs.getAnchors();
-       for (var i = 0; i < arr.images.length && i < hs.numberOfImagesToPreload; i++) {
-               hs.push(hs.preloadTheseImages, hs.getSrc(arr.images[i]));
-       }
-
-       // preload outlines
-       if (hs.outlineType)     new hs.Outline(hs.outlineType, function () { hs.preloadFullImage(0)} );
-       else
-
-       hs.preloadFullImage(0);
-
-       // preload cursor
-       if (hs.restoreCursor) var cur = hs.createElement('img', { src: hs.graphicsDir + hs.restoreCursor });
-},
-
-
-init : function () {
-       if (!hs.container) {
-
-               hs.ieLt7 = hs.ie && hs.uaVersion < 7;
-               hs.ieLt9 = hs.ie && hs.uaVersion < 9;
-
-               hs.getPageSize();
-               for (var x in hs.langDefaults) {
-                       if (typeof hs[x] != 'undefined') hs.lang[x] = hs[x];
-                       else if (typeof hs.lang[x] == 'undefined' && typeof hs.langDefaults[x] != 'undefined')
-                               hs.lang[x] = hs.langDefaults[x];
-               }
-
-               hs.container = hs.createElement('div', {
-                               className: 'highslide-container'
-                       }, {
-                               position: 'absolute',
-                               left: 0,
-                               top: 0,
-                               width: '100%',
-                               zIndex: hs.zIndexCounter,
-                               direction: 'ltr'
-                       },
-                       document.body,
-                       true
-               );
-               hs.loading = hs.createElement('a', {
-                               className: 'highslide-loading',
-                               title: hs.lang.loadingTitle,
-                               innerHTML: hs.lang.loadingText,
-                               href: 'javascript:;'
-                       }, {
-                               position: 'absolute',
-                               top: '-9999px',
-                               opacity: hs.loadingOpacity,
-                               zIndex: 1
-                       }, hs.container
-               );
-               hs.garbageBin = hs.createElement('div', null, { display: 'none' }, hs.container);
-               hs.viewport = hs.createElement('div', {
-                               className: 'highslide-viewport highslide-viewport-size'
-                       }, {
-                               visibility: (hs.safari && hs.uaVersion < 525) ? 'visible' : 'hidden'
-                       }, hs.container, 1
-               );
-
-               // http://www.robertpenner.com/easing/
-               Math.linearTween = function (t, b, c, d) {
-                       return c*t/d + b;
-               };
-               Math.easeInQuad = function (t, b, c, d) {
-                       return c*(t/=d)*t + b;
-               };
-               Math.easeOutQuad = function (t, b, c, d) {
-                       return -c *(t/=d)*(t-2) + b;
-               };
-
-               hs.hideSelects = hs.ieLt7;
-               hs.hideIframes = ((window.opera && hs.uaVersion < 9) || navigator.vendor == 'KDE'
-                       || (hs.ieLt7 && hs.uaVersion < 5.5));
-               hs.fireEvent(this, 'onActivate');
-       }
-},
-ready : function() {
-       if (hs.isReady) return;
-       hs.isReady = true;
-       for (var i = 0; i < hs.onReady.length; i++) hs.onReady[i]();
-},
-
-updateAnchors : function() {
-       var el, els, all = [], images = [],groups = {}, re;
-
-       for (var i = 0; i < hs.openerTagNames.length; i++) {
-               els = document.getElementsByTagName(hs.openerTagNames[i]);
-               for (var j = 0; j < els.length; j++) {
-                       el = els[j];
-                       re = hs.isHsAnchor(el);
-                       if (re) {
-                               hs.push(all, el);
-                               if (re[0] == 'hs.expand') hs.push(images, el);
-                               var g = hs.getParam(el, 'slideshowGroup') || 'none';
-                               if (!groups[g]) groups[g] = [];
-                               hs.push(groups[g], el);
-                       }
-               }
-       }
-       hs.anchors = { all: all, groups: groups, images: images };
-       return hs.anchors;
-
-},
-
-getAnchors : function() {
-       return hs.anchors || hs.updateAnchors();
-},
-
-
-close : function(el) {
-       var exp = hs.getExpander(el);
-       if (exp) exp.close();
-       return false;
-}
-}; // end hs object
-hs.fx = function( elem, options, prop ){
-       this.options = options;
-       this.elem = elem;
-       this.prop = prop;
-
-       if (!options.orig) options.orig = {};
-};
-hs.fx.prototype = {
-       update: function(){
-               (hs.fx.step[this.prop] || hs.fx.step._default)(this);
-
-               if (this.options.step)
-                       this.options.step.call(this.elem, this.now, this);
-
-       },
-       custom: function(from, to, unit){
-               this.startTime = (new Date()).getTime();
-               this.start = from;
-               this.end = to;
-               this.unit = unit;// || this.unit || "px";
-               this.now = this.start;
-               this.pos = this.state = 0;
-
-               var self = this;
-               function t(gotoEnd){
-                       return self.step(gotoEnd);
-               }
-
-               t.elem = this.elem;
-
-               if ( t() && hs.timers.push(t) == 1 ) {
-                       hs.timerId = setInterval(function(){
-                               var timers = hs.timers;
-
-                               for ( var i = 0; i < timers.length; i++ )
-                                       if ( !timers[i]() )
-                                               timers.splice(i--, 1);
-
-                               if ( !timers.length ) {
-                                       clearInterval(hs.timerId);
-                               }
-                       }, 13);
-               }
-       },
-       step: function(gotoEnd){
-               var t = (new Date()).getTime();
-               if ( gotoEnd || t >= this.options.duration + this.startTime ) {
-                       this.now = this.end;
-                       this.pos = this.state = 1;
-                       this.update();
-
-                       this.options.curAnim[ this.prop ] = true;
-
-                       var done = true;
-                       for ( var i in this.options.curAnim )
-                               if ( this.options.curAnim[i] !== true )
-                                       done = false;
-
-                       if ( done ) {
-                               if (this.options.complete) this.options.complete.call(this.elem);
-                       }
-                       return false;
-               } else {
-                       var n = t - this.startTime;
-                       this.state = n / this.options.duration;
-                       this.pos = this.options.easing(n, 0, 1, this.options.duration);
-                       this.now = this.start + ((this.end - this.start) * this.pos);
-                       this.update();
-               }
-               return true;
-       }
-
-};
-
-hs.extend( hs.fx, {
-       step: {
-
-               opacity: function(fx){
-                       hs.setStyles(fx.elem, { opacity: fx.now });
-               },
-
-               _default: function(fx){
-                       try {
-                               if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
-                                       fx.elem.style[ fx.prop ] = fx.now + fx.unit;
-                               else
-                                       fx.elem[ fx.prop ] = fx.now;
-                       } catch (e) {}
-               }
-       }
-});
-
-hs.Outline =  function (outlineType, onLoad) {
-       this.onLoad = onLoad;
-       this.outlineType = outlineType;
-       var v = hs.uaVersion, tr;
-
-       this.hasAlphaImageLoader = hs.ie && hs.uaVersion < 7;
-       if (!outlineType) {
-               if (onLoad) onLoad();
-               return;
-       }
-
-       hs.init();
-       this.table = hs.createElement(
-               'table', {
-                       cellSpacing: 0
-               }, {
-                       visibility: 'hidden',
-                       position: 'absolute',
-                       borderCollapse: 'collapse',
-                       width: 0
-               },
-               hs.container,
-               true
-       );
-       var tbody = hs.createElement('tbody', null, null, this.table, 1);
-
-       this.td = [];
-       for (var i = 0; i <= 8; i++) {
-               if (i % 3 == 0) tr = hs.createElement('tr', null, { height: 'auto' }, tbody, true);
-               this.td[i] = hs.createElement('td', null, null, tr, true);
-               var style = i != 4 ? { lineHeight: 0, fontSize: 0} : { position : 'relative' };
-               hs.setStyles(this.td[i], style);
-       }
-       this.td[4].className = outlineType +' highslide-outline';
-
-       this.preloadGraphic();
-};
-
-hs.Outline.prototype = {
-preloadGraphic : function () {
-       var src = hs.graphicsDir + (hs.outlinesDir || "outlines/")+ this.outlineType +".png";
-
-       var appendTo = hs.safari && hs.uaVersion < 525 ? hs.container : null;
-       this.graphic = hs.createElement('img', null, { position: 'absolute',
-               top: '-9999px' }, appendTo, true); // for onload trigger
-
-       var pThis = this;
-       this.graphic.onload = function() { pThis.onGraphicLoad(); };
-
-       this.graphic.src = src;
-},
-
-onGraphicLoad : function () {
-       var o = this.offset = this.graphic.width / 4,
-               pos = [[0,0],[0,-4],[-2,0],[0,-8],0,[-2,-8],[0,-2],[0,-6],[-2,-2]],
-               dim = { height: (2*o) +'px', width: (2*o) +'px' };
-       for (var i = 0; i <= 8; i++) {
-               if (pos[i]) {
-                       if (this.hasAlphaImageLoader) {
-                               var w = (i == 1 || i == 7) ? '100%' : this.graphic.width +'px';
-                               var div = hs.createElement('div', null, { width: '100%', height: '100%', position: 'relative', overflow: 'hidden'}, this.td[i], true);
-                               hs.createElement ('div', null, {
-                                               filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale, src='"+ this.graphic.src + "')",
-                                               position: 'absolute',
-                                               width: w,
-                                               height: this.graphic.height +'px',
-                                               left: (pos[i][0]*o)+'px',
-                                               top: (pos[i][1]*o)+'px'
-                                       },
-                               div,
-                               true);
-                       } else {
-                               hs.setStyles(this.td[i], { background: 'url('+ this.graphic.src +') '+ (pos[i][0]*o)+'px '+(pos[i][1]*o)+'px'});
-                       }
-
-                       if (window.opera && (i == 3 || i ==5))
-                               hs.createElement('div', null, dim, this.td[i], true);
-
-                       hs.setStyles (this.td[i], dim);
-               }
-       }
-       this.graphic = null;
-       if (hs.pendingOutlines[this.outlineType]) hs.pendingOutlines[this.outlineType].destroy();
-       hs.pendingOutlines[this.outlineType] = this;
-       if (this.onLoad) this.onLoad();
-},
-
-setPosition : function (pos, offset, vis, dur, easing) {
-       var exp = this.exp,
-               stl = exp.wrapper.style,
-               offset = offset || 0,
-               pos = pos || {
-                       x: exp.x.pos + offset,
-                       y: exp.y.pos + offset,
-                       w: exp.x.get('wsize') - 2 * offset,
-                       h: exp.y.get('wsize') - 2 * offset
-               };
-       if (vis) this.table.style.visibility = (pos.h >= 4 * this.offset)
-               ? 'visible' : 'hidden';
-       hs.setStyles(this.table, {
-               left: (pos.x - this.offset) +'px',
-               top: (pos.y - this.offset) +'px',
-               width: (pos.w + 2 * this.offset) +'px'
-       });
-
-       pos.w -= 2 * this.offset;
-       pos.h -= 2 * this.offset;
-       hs.setStyles (this.td[4], {
-               width: pos.w >= 0 ? pos.w +'px' : 0,
-               height: pos.h >= 0 ? pos.h +'px' : 0
-       });
-       if (this.hasAlphaImageLoader) this.td[3].style.height
-               = this.td[5].style.height = this.td[4].style.height;
-
-},
-
-destroy : function(hide) {
-       if (hide) this.table.style.visibility = 'hidden';
-       else hs.discardElement(this.table);
-}
-};
-
-hs.Dimension = function(exp, dim) {
-       this.exp = exp;
-       this.dim = dim;
-       this.ucwh = dim == 'x' ? 'Width' : 'Height';
-       this.wh = this.ucwh.toLowerCase();
-       this.uclt = dim == 'x' ? 'Left' : 'Top';
-       this.lt = this.uclt.toLowerCase();
-       this.ucrb = dim == 'x' ? 'Right' : 'Bottom';
-       this.rb = this.ucrb.toLowerCase();
-       this.p1 = this.p2 = 0;
-};
-hs.Dimension.prototype = {
-get : function(key) {
-       switch (key) {
-               case 'loadingPos':
-                       return this.tpos + this.tb + (this.t - hs.loading['offset'+ this.ucwh]) / 2;
-               case 'loadingPosXfade':
-                       return this.pos + this.cb+ this.p1 + (this.size - hs.loading['offset'+ this.ucwh]) / 2;
-               case 'wsize':
-                       return this.size + 2 * this.cb + this.p1 + this.p2;
-               case 'fitsize':
-                       return this.clientSize - this.marginMin - this.marginMax;
-               case 'maxsize':
-                       return this.get('fitsize') - 2 * this.cb - this.p1 - this.p2 ;
-               case 'opos':
-                       return this.pos - (this.exp.outline ? this.exp.outline.offset : 0);
-               case 'osize':
-                       return this.get('wsize') + (this.exp.outline ? 2*this.exp.outline.offset : 0);
-               case 'imgPad':
-                       return this.imgSize ? Math.round((this.size - this.imgSize) / 2) : 0;
-
-       }
-},
-calcBorders: function() {
-       // correct for borders
-       this.cb = (this.exp.content['offset'+ this.ucwh] - this.t) / 2;
-
-       this.marginMax = hs['margin'+ this.ucrb];
-},
-calcThumb: function() {
-       this.t = this.exp.el[this.wh] ? parseInt(this.exp.el[this.wh]) :
-               this.exp.el['offset'+ this.ucwh];
-       this.tpos = this.exp.tpos[this.dim];
-       this.tb = (this.exp.el['offset'+ this.ucwh] - this.t) / 2;
-       if (this.tpos == 0 || this.tpos == -1) {
-               this.tpos = (hs.page[this.wh] / 2) + hs.page['scroll'+ this.uclt];
-       };
-},
-calcExpanded: function() {
-       var exp = this.exp;
-       this.justify = 'auto';
-
-       // get alignment
-       if (exp.align == 'center') this.justify = 'center';
-       else if (new RegExp(this.lt).test(exp.anchor)) this.justify = null;
-       else if (new RegExp(this.rb).test(exp.anchor)) this.justify = 'max';
-
-
-       // size and position
-       this.pos = this.tpos - this.cb + this.tb;
-
-       if (this.maxHeight && this.dim == 'x')
-               exp.maxWidth = Math.min(exp.maxWidth || this.full, exp.maxHeight * this.full / exp.y.full);
-
-       this.size = Math.min(this.full, exp['max'+ this.ucwh] || this.full);
-       this.minSize = exp.allowSizeReduction ?
-               Math.min(exp['min'+ this.ucwh], this.full) :this.full;
-       if (exp.isImage && exp.useBox)  {
-               this.size = exp[this.wh];
-               this.imgSize = this.full;
-       }
-       if (this.dim == 'x' && hs.padToMinWidth) this.minSize = exp.minWidth;
-       this.target = exp['target'+ this.dim.toUpperCase()];
-       this.marginMin = hs['margin'+ this.uclt];
-       this.scroll = hs.page['scroll'+ this.uclt];
-       this.clientSize = hs.page[this.wh];
-},
-setSize: function(i) {
-       var exp = this.exp;
-       if (exp.isImage && (exp.useBox || hs.padToMinWidth)) {
-               this.imgSize = i;
-               this.size = Math.max(this.size, this.imgSize);
-               exp.content.style[this.lt] = this.get('imgPad')+'px';
-       } else
-       this.size = i;
-
-       exp.content.style[this.wh] = i +'px';
-       exp.wrapper.style[this.wh] = this.get('wsize') +'px';
-       if (exp.outline) exp.outline.setPosition();
-       if (this.dim == 'x' && exp.overlayBox) exp.sizeOverlayBox(true);
-       if (this.dim == 'x' && exp.slideshow && exp.isImage) {
-               if (i == this.full) exp.slideshow.disable('full-expand');
-               else exp.slideshow.enable('full-expand');
-       }
-},
-setPos: function(i) {
-       this.pos = i;
-       this.exp.wrapper.style[this.lt] = i +'px';
-
-       if (this.exp.outline) this.exp.outline.setPosition();
-
-}
-};
-
-hs.Expander = function(a, params, custom, contentType) {
-       if (document.readyState && hs.ie && !hs.isReady) {
-               hs.addEventListener(document, 'ready', function() {
-                       new hs.Expander(a, params, custom, contentType);
-               });
-               return;
-       }
-       this.a = a;
-       this.custom = custom;
-       this.contentType = contentType || 'image';
-       this.isImage = !this.isHtml;
-
-       hs.continuePreloading = false;
-       this.overlays = [];
-       this.last = hs.last;
-       hs.last = null;
-       hs.init();
-       var key = this.key = hs.expanders.length;
-       // override inline parameters
-       for (var i = 0; i < hs.overrides.length; i++) {
-               var name = hs.overrides[i];
-               this[name] = params && typeof params[name] != 'undefined' ?
-                       params[name] : hs[name];
-       }
-       if (!this.src) this.src = a.href;
-
-       // get thumb
-       var el = (params && params.thumbnailId) ? hs.$(params.thumbnailId) : a;
-       el = this.thumb = el.getElementsByTagName('img')[0] || el;
-       this.thumbsUserSetId = el.id || a.id;
-       if (!hs.fireEvent(this, 'onInit')) return true;
-
-       // check if already open
-       for (var i = 0; i < hs.expanders.length; i++) {
-               if (hs.expanders[i] && hs.expanders[i].a == a
-                       && !(this.last && this.transitions[1] == 'crossfade')) {
-                       hs.expanders[i].focus();
-                       return false;
-               }
-       }
-
-       // cancel other
-       if (!hs.allowSimultaneousLoading) for (var i = 0; i < hs.expanders.length; i++) {
-               if (hs.expanders[i] && hs.expanders[i].thumb != el && !hs.expanders[i].onLoadStarted) {
-                       hs.expanders[i].cancelLoading();
-               }
-       }
-       hs.expanders[key] = this;
-       if (!hs.allowMultipleInstances && !hs.upcoming) {
-               if (hs.expanders[key-1]) hs.expanders[key-1].close();
-               if (typeof hs.focusKey != 'undefined' && hs.expanders[hs.focusKey])
-                       hs.expanders[hs.focusKey].close();
-       }
-
-       // initiate metrics
-       this.el = el;
-       this.tpos = this.pageOrigin || hs.getPosition(el);
-       hs.getPageSize();
-       var x = this.x = new hs.Dimension(this, 'x');
-       x.calcThumb();
-       var y = this.y = new hs.Dimension(this, 'y');
-       y.calcThumb();
-       this.wrapper = hs.createElement(
-               'div', {
-                       id: 'highslide-wrapper-'+ this.key,
-                       className: 'highslide-wrapper '+ this.wrapperClassName
-               }, {
-                       visibility: 'hidden',
-                       position: 'absolute',
-                       zIndex: hs.zIndexCounter += 2
-               }, null, true );
-
-       this.wrapper.onmouseover = this.wrapper.onmouseout = hs.wrapperMouseHandler;
-       if (this.contentType == 'image' && this.outlineWhileAnimating == 2)
-               this.outlineWhileAnimating = 0;
-
-       // get the outline
-       if (!this.outlineType
-               || (this.last && this.isImage && this.transitions[1] == 'crossfade')) {
-               this[this.contentType +'Create']();
-
-       } else if (hs.pendingOutlines[this.outlineType]) {
-               this.connectOutline();
-               this[this.contentType +'Create']();
-
-       } else {
-               this.showLoading();
-               var exp = this;
-               new hs.Outline(this.outlineType,
-                       function () {
-                               exp.connectOutline();
-                               exp[exp.contentType +'Create']();
-                       }
-               );
-       }
-       return true;
-};
-
-hs.Expander.prototype = {
-error : function(e) {
-       if (hs.debug) alert ('Line '+ e.lineNumber +': '+ e.message);
-       else window.location.href = this.src;
-},
-
-connectOutline : function() {
-       var outline = this.outline = hs.pendingOutlines[this.outlineType];
-       outline.exp = this;
-       outline.table.style.zIndex = this.wrapper.style.zIndex - 1;
-       hs.pendingOutlines[this.outlineType] = null;
-},
-
-showLoading : function() {
-       if (this.onLoadStarted || this.loading) return;
-
-       this.loading = hs.loading;
-       var exp = this;
-       this.loading.onclick = function() {
-               exp.cancelLoading();
-       };
-
-
-       if (!hs.fireEvent(this, 'onShowLoading')) return;
-       var exp = this,
-               l = this.x.get('loadingPos') +'px',
-               t = this.y.get('loadingPos') +'px';
-       if (!tgt && this.last && this.transitions[1] == 'crossfade')
-               var tgt = this.last;
-       if (tgt) {
-               l = tgt.x.get('loadingPosXfade') +'px';
-               t = tgt.y.get('loadingPosXfade') +'px';
-               this.loading.style.zIndex = hs.zIndexCounter++;
-       }
-       setTimeout(function () {
-               if (exp.loading) hs.setStyles(exp.loading, { left: l, top: t, zIndex: hs.zIndexCounter++ })}
-       , 100);
-},
-
-imageCreate : function() {
-       var exp = this;
-
-       var img = document.createElement('img');
-       this.content = img;
-       img.onload = function () {
-               if (hs.expanders[exp.key]) exp.contentLoaded();
-       };
-       if (hs.blockRightClick) img.oncontextmenu = function() { return false; };
-       img.className = 'highslide-image';
-       hs.setStyles(img, {
-               visibility: 'hidden',
-               display: 'block',
-               position: 'absolute',
-               maxWidth: '9999px',
-               zIndex: 3
-       });
-       img.title = hs.lang.restoreTitle;
-       if (hs.safari && hs.uaVersion < 525) hs.container.appendChild(img);
-       if (hs.ie && hs.flushImgSize) img.src = null;
-       img.src = this.src;
-
-       this.showLoading();
-},
-
-contentLoaded : function() {
-       try {
-               if (!this.content) return;
-               this.content.onload = null;
-               if (this.onLoadStarted) return;
-               else this.onLoadStarted = true;
-
-               var x = this.x, y = this.y;
-
-               if (this.loading) {
-                       hs.setStyles(this.loading, { top: '-9999px' });
-                       this.loading = null;
-                       hs.fireEvent(this, 'onHideLoading');
-               }
-                       x.full = this.content.width;
-                       y.full = this.content.height;
-
-                       hs.setStyles(this.content, {
-                               width: x.t +'px',
-                               height: y.t +'px'
-                       });
-                       this.wrapper.appendChild(this.content);
-                       hs.container.appendChild(this.wrapper);
-
-               x.calcBorders();
-               y.calcBorders();
-
-               hs.setStyles (this.wrapper, {
-                       left: (x.tpos + x.tb - x.cb) +'px',
-                       top: (y.tpos + x.tb - y.cb) +'px'
-               });
-
-
-               this.initSlideshow();
-               this.getOverlays();
-
-               var ratio = x.full / y.full;
-               x.calcExpanded();
-               this.justify(x);
-
-               y.calcExpanded();
-               this.justify(y);
-               if (this.overlayBox) this.sizeOverlayBox(0, 1);
-
-
-               if (this.allowSizeReduction) {
-                               this.correctRatio(ratio);
-                       var ss = this.slideshow;
-                       if (ss && this.last && ss.controls && ss.fixedControls) {
-                               var pos = ss.overlayOptions.position || '', p;
-                               for (var dim in hs.oPos) for (var i = 0; i < 5; i++) {
-                                       p = this[dim];
-                                       if (pos.match(hs.oPos[dim][i])) {
-                                               p.pos = this.last[dim].pos
-                                                       + (this.last[dim].p1 - p.p1)
-                                                       + (this.last[dim].size - p.size) * [0, 0, .5, 1, 1][i];
-                                               if (ss.fixedControls == 'fit') {
-                                                       if (p.pos + p.size + p.p1 + p.p2 > p.scroll + p.clientSize - p.marginMax)
-                                                               p.pos = p.scroll + p.clientSize - p.size - p.marginMin - p.marginMax - p.p1 - p.p2;
-                                                       if (p.pos < p.scroll + p.marginMin) p.pos = p.scroll + p.marginMin;
-                                               }
-                                       }
-                               }
-                       }
-                       if (this.isImage && this.x.full > (this.x.imgSize || this.x.size)) {
-                               this.createFullExpand();
-                               if (this.overlays.length == 1) this.sizeOverlayBox();
-                       }
-               }
-               this.show();
-
-       } catch (e) {
-               this.error(e);
-       }
-},
-
-justify : function (p, moveOnly) {
-       var tgtArr, tgt = p.target, dim = p == this.x ? 'x' : 'y';
-
-       if (tgt && tgt.match(/ /)) {
-               tgtArr = tgt.split(' ');
-               tgt = tgtArr[0];
-       }
-       if (tgt && hs.$(tgt)) {
-               p.pos = hs.getPosition(hs.$(tgt))[dim];
-               if (tgtArr && tgtArr[1] && tgtArr[1].match(/^[-]?[0-9]+px$/))
-                       p.pos += parseInt(tgtArr[1]);
-               if (p.size < p.minSize) p.size = p.minSize;
-
-       } else if (p.justify == 'auto' || p.justify == 'center') {
-
-               var hasMovedMin = false;
-
-               var allowReduce = p.exp.allowSizeReduction;
-               if (p.justify == 'center')
-                       p.pos = Math.round(p.scroll + (p.clientSize + p.marginMin - p.marginMax - p.get('wsize')) / 2);
-               else
-                       p.pos = Math.round(p.pos - ((p.get('wsize') - p.t) / 2));
-               if (p.pos < p.scroll + p.marginMin) {
-                       p.pos = p.scroll + p.marginMin;
-                       hasMovedMin = true;
-               }
-               if (!moveOnly && p.size < p.minSize) {
-                       p.size = p.minSize;
-                       allowReduce = false;
-               }
-               if (p.pos + p.get('wsize') > p.scroll + p.clientSize - p.marginMax) {
-                       if (!moveOnly && hasMovedMin && allowReduce) {
-                               p.size = Math.min(p.size, p.get(dim == 'y' ? 'fitsize' : 'maxsize'));
-                       } else if (p.get('wsize') < p.get('fitsize')) {
-                               p.pos = p.scroll + p.clientSize - p.marginMax - p.get('wsize');
-                       } else { // image larger than viewport
-                               p.pos = p.scroll + p.marginMin;
-                               if (!moveOnly && allowReduce) p.size = p.get(dim == 'y' ? 'fitsize' : 'maxsize');
-                       }
-               }
-
-               if (!moveOnly && p.size < p.minSize) {
-                       p.size = p.minSize;
-                       allowReduce = false;
-               }
-
-
-       } else if (p.justify == 'max') {
-               p.pos = Math.floor(p.pos - p.size + p.t);
-       }
-
-
-       if (p.pos < p.marginMin) {
-               var tmpMin = p.pos;
-               p.pos = p.marginMin;
-
-               if (allowReduce && !moveOnly) p.size = p.size - (p.pos - tmpMin);
-
-       }
-},
-
-correctRatio : function(ratio) {
-       var x = this.x,
-               y = this.y,
-               changed = false,
-               xSize = Math.min(x.full, x.size),
-               ySize = Math.min(y.full, y.size),
-               useBox = (this.useBox || hs.padToMinWidth);
-
-       if (xSize / ySize > ratio) { // width greater
-               xSize = ySize * ratio;
-               if (xSize < x.minSize) { // below minWidth
-                       xSize = x.minSize;
-                       ySize = xSize / ratio;
-               }
-               changed = true;
-
-       } else if (xSize / ySize < ratio) { // height greater
-               ySize = xSize / ratio;
-               changed = true;
-       }
-
-       if (hs.padToMinWidth && x.full < x.minSize) {
-               x.imgSize = x.full;
-               y.size = y.imgSize = y.full;
-       } else if (this.useBox) {
-               x.imgSize = xSize;
-               y.imgSize = ySize;
-       } else {
-               x.size = xSize;
-               y.size = ySize;
-       }
-       changed = this.fitOverlayBox(this.useBox ? null : ratio, changed);
-       if (useBox && y.size < y.imgSize) {
-               y.imgSize = y.size;
-               x.imgSize = y.size * ratio;
-       }
-       if (changed || useBox) {
-               x.pos = x.tpos - x.cb + x.tb;
-               x.minSize = x.size;
-               this.justify(x, true);
-
-               y.pos = y.tpos - y.cb + y.tb;
-               y.minSize = y.size;
-               this.justify(y, true);
-               if (this.overlayBox) this.sizeOverlayBox();
-       }
-
-
-},
-fitOverlayBox : function(ratio, changed) {
-       var x = this.x, y = this.y;
-       if (this.overlayBox) {
-               while (y.size > this.minHeight && x.size > this.minWidth
-                               &&  y.get('wsize') > y.get('fitsize')) {
-                       y.size -= 10;
-                       if (ratio) x.size = y.size * ratio;
-                       this.sizeOverlayBox(0, 1);
-                       changed = true;
-               }
-       }
-       return changed;
-},
-
-show : function () {
-       var x = this.x, y = this.y;
-       this.doShowHide('hidden');
-       hs.fireEvent(this, 'onBeforeExpand');
-       if (this.slideshow && this.slideshow.thumbstrip) this.slideshow.thumbstrip.selectThumb();
-
-       // Apply size change
-       this.changeSize(
-               1, {
-                       wrapper: {
-                               width : x.get('wsize'),
-                               height : y.get('wsize'),
-                               left: x.pos,
-                               top: y.pos
-                       },
-                       content: {
-                               left: x.p1 + x.get('imgPad'),
-                               top: y.p1 + y.get('imgPad'),
-                               width:x.imgSize ||x.size,
-                               height:y.imgSize ||y.size
-                       }
-               },
-               hs.expandDuration
-       );
-},
-
-changeSize : function(up, to, dur) {
-       // transition
-       var trans = this.transitions,
-       other = up ? (this.last ? this.last.a : null) : hs.upcoming,
-       t = (trans[1] && other
-                       && hs.getParam(other, 'transitions')[1] == trans[1]) ?
-               trans[1] : trans[0];
-
-       if (this[t] && t != 'expand') {
-               this[t](up, to);
-               return;
-       }
-
-       if (this.outline && !this.outlineWhileAnimating) {
-               if (up) this.outline.setPosition();
-               else this.outline.destroy();
-       }
-
-
-       if (!up) this.destroyOverlays();
-
-       var exp = this,
-               x = exp.x,
-               y = exp.y,
-               easing = this.easing;
-       if (!up) easing = this.easingClose || easing;
-       var after = up ?
-               function() {
-
-                       if (exp.outline) exp.outline.table.style.visibility = "visible";
-                       setTimeout(function() {
-                               exp.afterExpand();
-                       }, 50);
-               } :
-               function() {
-                       exp.afterClose();
-               };
-       if (up) hs.setStyles( this.wrapper, {
-               width: x.t +'px',
-               height: y.t +'px'
-       });
-       if (this.fadeInOut) {
-               hs.setStyles(this.wrapper, { opacity: up ? 0 : 1 });
-               hs.extend(to.wrapper, { opacity: up });
-       }
-       hs.animate( this.wrapper, to.wrapper, {
-               duration: dur,
-               easing: easing,
-               step: function(val, args) {
-                       if (exp.outline && exp.outlineWhileAnimating && args.prop == 'top') {
-                               var fac = up ? args.pos : 1 - args.pos;
-                               var pos = {
-                                       w: x.t + (x.get('wsize') - x.t) * fac,
-                                       h: y.t + (y.get('wsize') - y.t) * fac,
-                                       x: x.tpos + (x.pos - x.tpos) * fac,
-                                       y: y.tpos + (y.pos - y.tpos) * fac
-                               };
-                               exp.outline.setPosition(pos, 0, 1);
-                       }
-               }
-       });
-       hs.animate( this.content, to.content, dur, easing, after);
-       if (up) {
-               this.wrapper.style.visibility = 'visible';
-               this.content.style.visibility = 'visible';
-               this.a.className += ' highslide-active-anchor';
-       }
-},
-
-
-
-fade : function(up, to) {
-       this.outlineWhileAnimating = false;
-       var exp = this, t = up ? hs.expandDuration : 0;
-
-       if (up) {
-               hs.animate(this.wrapper, to.wrapper, 0);
-               hs.setStyles(this.wrapper, { opacity: 0, visibility: 'visible' });
-               hs.animate(this.content, to.content, 0);
-               this.content.style.visibility = 'visible';
-
-               hs.animate(this.wrapper, { opacity: 1 }, t, null,
-                       function() { exp.afterExpand(); });
-       }
-
-       if (this.outline) {
-               this.outline.table.style.zIndex = this.wrapper.style.zIndex;
-               var dir = up || -1,
-                       offset = this.outline.offset,
-                       startOff = up ? 3 : offset,
-                       endOff = up? offset : 3;
-               for (var i = startOff; dir * i <= dir * endOff; i += dir, t += 25) {
-                       (function() {
-                               var o = up ? endOff - i : startOff - i;
-                               setTimeout(function() {
-                                       exp.outline.setPosition(0, o, 1);
-                               }, t);
-                       })();
-               }
-       }
-
-
-       if (up) {}//setTimeout(function() { exp.afterExpand(); }, t+50);
-       else {
-               setTimeout( function() {
-                       if (exp.outline) exp.outline.destroy(exp.preserveContent);
-
-                       exp.destroyOverlays();
-
-                       hs.animate( exp.wrapper, { opacity: 0 }, hs.restoreDuration, null, function(){
-                               exp.afterClose();
-                       });
-               }, t);
-       }
-},
-crossfade : function (up, to, from) {
-       if (!up) return;
-       var exp = this,
-               last = this.last,
-               x = this.x,
-               y = this.y,
-               lastX = last.x,
-               lastY = last.y,
-               wrapper = this.wrapper,
-               content = this.content,
-               overlayBox = this.overlayBox;
-       hs.removeEventListener(document, 'mousemove', hs.dragHandler);
-
-       hs.setStyles(content, {
-               width: (x.imgSize || x.size) +'px',
-               height: (y.imgSize || y.size) +'px'
-       });
-       if (overlayBox) overlayBox.style.overflow = 'visible';
-       this.outline = last.outline;
-       if (this.outline) this.outline.exp = exp;
-       last.outline = null;
-       var fadeBox = hs.createElement('div', {
-                       className: 'highslide-'+ this.contentType
-               }, {
-                       position: 'absolute',
-                       zIndex: 4,
-                       overflow: 'hidden',
-                       display: 'none'
-               }
-       );
-       var names = { oldImg: last, newImg: this };
-       for (var n in names) {
-               this[n] = names[n].content.cloneNode(1);
-               hs.setStyles(this[n], {
-                       position: 'absolute',
-                       border: 0,
-                       visibility: 'visible'
-               });
-               fadeBox.appendChild(this[n]);
-       }
-       wrapper.appendChild(fadeBox);
-       if (overlayBox) {
-               overlayBox.className = '';
-               wrapper.appendChild(overlayBox);
-       }
-       fadeBox.style.display = '';
-       last.content.style.display = 'none';
-
-
-       if (hs.safari && hs.uaVersion < 525) {
-               this.wrapper.style.visibility = 'visible';
-       }
-       hs.animate(wrapper, {
-               width: x.size
-       }, {
-               duration: hs.transitionDuration,
-               step: function(val, args) {
-                       var pos = args.pos,
-                               invPos = 1 - pos;
-                       var prop,
-                               size = {},
-                               props = ['pos', 'size', 'p1', 'p2'];
-                       for (var n in props) {
-                               prop = props[n];
-                               size['x'+ prop] = Math.round(invPos * lastX[prop] + pos * x[prop]);
-                               size['y'+ prop] = Math.round(invPos * lastY[prop] + pos * y[prop]);
-                               size.ximgSize = Math.round(
-                                       invPos * (lastX.imgSize || lastX.size) + pos * (x.imgSize || x.size));
-                               size.ximgPad = Math.round(invPos * lastX.get('imgPad') + pos * x.get('imgPad'));
-                               size.yimgSize = Math.round(
-                                       invPos * (lastY.imgSize || lastY.size) + pos * (y.imgSize || y.size));
-                               size.yimgPad = Math.round(invPos * lastY.get('imgPad') + pos * y.get('imgPad'));
-                       }
-                       if (exp.outline) exp.outline.setPosition({
-                               x: size.xpos,
-                               y: size.ypos,
-                               w: size.xsize + size.xp1 + size.xp2 + 2 * x.cb,
-                               h: size.ysize + size.yp1 + size.yp2 + 2 * y.cb
-                       });
-                       last.wrapper.style.clip = 'rect('
-                               + (size.ypos - lastY.pos)+'px, '
-                               + (size.xsize + size.xp1 + size.xp2 + size.xpos + 2 * lastX.cb - lastX.pos) +'px, '
-                               + (size.ysize + size.yp1 + size.yp2 + size.ypos + 2 * lastY.cb - lastY.pos) +'px, '
-                               + (size.xpos - lastX.pos)+'px)';
-
-                       hs.setStyles(content, {
-                               top: (size.yp1 + y.get('imgPad')) +'px',
-                               left: (size.xp1 + x.get('imgPad')) +'px',
-                               marginTop: (y.pos - size.ypos) +'px',
-                               marginLeft: (x.pos - size.xpos) +'px'
-                       });
-                       hs.setStyles(wrapper, {
-                               top: size.ypos +'px',
-                               left: size.xpos +'px',
-                               width: (size.xp1 + size.xp2 + size.xsize + 2 * x.cb)+ 'px',
-                               height: (size.yp1 + size.yp2 + size.ysize + 2 * y.cb) + 'px'
-                       });
-                       hs.setStyles(fadeBox, {
-                               width: (size.ximgSize || size.xsize) + 'px',
-                               height: (size.yimgSize || size.ysize) +'px',
-                               left: (size.xp1 + size.ximgPad)  +'px',
-                               top: (size.yp1 + size.yimgPad) +'px',
-                               visibility: 'visible'
-                       });
-
-                       hs.setStyles(exp.oldImg, {
-                               top: (lastY.pos - size.ypos + lastY.p1 - size.yp1 + lastY.get('imgPad') - size.yimgPad)+'px',
-                               left: (lastX.pos - size.xpos + lastX.p1 - size.xp1 + lastX.get('imgPad') - size.ximgPad)+'px'
-                       });
-
-                       hs.setStyles(exp.newImg, {
-                               opacity: pos,
-                               top: (y.pos - size.ypos + y.p1 - size.yp1 + y.get('imgPad') - size.yimgPad) +'px',
-                               left: (x.pos - size.xpos + x.p1 - size.xp1 + x.get('imgPad') - size.ximgPad) +'px'
-                       });
-                       if (overlayBox) hs.setStyles(overlayBox, {
-                               width: size.xsize + 'px',
-                               height: size.ysize +'px',
-                               left: (size.xp1 + x.cb)  +'px',
-                               top: (size.yp1 + y.cb) +'px'
-                       });
-               },
-               complete: function () {
-                       wrapper.style.visibility = content.style.visibility = 'visible';
-                       content.style.display = 'block';
-                       hs.discardElement(fadeBox);
-                       exp.afterExpand();
-                       last.afterClose();
-                       exp.last = null;
-               }
-
-       });
-},
-reuseOverlay : function(o, el) {
-       if (!this.last) return false;
-       for (var i = 0; i < this.last.overlays.length; i++) {
-               var oDiv = hs.$('hsId'+ this.last.overlays[i]);
-               if (oDiv && oDiv.hsId == o.hsId) {
-                       this.genOverlayBox();
-                       oDiv.reuse = this.key;
-                       hs.push(this.overlays, this.last.overlays[i]);
-                       return true;
-               }
-       }
-       return false;
-},
-
-
-afterExpand : function() {
-       this.isExpanded = true;
-       this.focus();
-       if (this.dimmingOpacity) hs.dim(this);
-       if (hs.upcoming && hs.upcoming == this.a) hs.upcoming = null;
-       this.prepareNextOutline();
-       var p = hs.page, mX = hs.mouse.x + p.scrollLeft, mY = hs.mouse.y + p.scrollTop;
-       this.mouseIsOver = this.x.pos < mX && mX < this.x.pos + this.x.get('wsize')
-               && this.y.pos < mY && mY < this.y.pos + this.y.get('wsize');
-       if (this.overlayBox) this.showOverlays();
-       hs.fireEvent(this, 'onAfterExpand');
-
-},
-
-
-prepareNextOutline : function() {
-       var key = this.key;
-       var outlineType = this.outlineType;
-       new hs.Outline(outlineType,
-               function () { try { hs.expanders[key].preloadNext(); } catch (e) {} });
-},
-
-
-preloadNext : function() {
-       var next = this.getAdjacentAnchor(1);
-       if (next && next.onclick.toString().match(/hs\.expand/))
-               var img = hs.createElement('img', { src: hs.getSrc(next) });
-},
-
-
-getAdjacentAnchor : function(op) {
-       var current = this.getAnchorIndex(), as = hs.anchors.groups[this.slideshowGroup || 'none'];
-       if (as && !as[current + op] && this.slideshow && this.slideshow.repeat) {
-               if (op == 1) return as[0];
-               else if (op == -1) return as[as.length-1];
-       }
-       return (as && as[current + op]) || null;
-},
-
-getAnchorIndex : function() {
-       var arr = hs.getAnchors().groups[this.slideshowGroup || 'none'];
-       if (arr) for (var i = 0; i < arr.length; i++) {
-               if (arr[i] == this.a) return i;
-       }
-       return null;
-},
-
-
-getNumber : function() {
-       if (this[this.numberPosition]) {
-               var arr = hs.anchors.groups[this.slideshowGroup || 'none'];
-               if (arr) {
-                       var s = hs.lang.number.replace('%1', this.getAnchorIndex() + 1).replace('%2', arr.length);
-                       this[this.numberPosition].innerHTML =
-                               '<div class="highslide-number">'+ s +'</div>'+ this[this.numberPosition].innerHTML;
-               }
-       }
-},
-initSlideshow : function() {
-       if (!this.last) {
-               for (var i = 0; i < hs.slideshows.length; i++) {
-                       var ss = hs.slideshows[i], sg = ss.slideshowGroup;
-                       if (typeof sg == 'undefined' || sg === null || sg === this.slideshowGroup)
-                               this.slideshow = new hs.Slideshow(this.key, ss);
-               }
-       } else {
-               this.slideshow = this.last.slideshow;
-       }
-       var ss = this.slideshow;
-       if (!ss) return;
-       var key = ss.expKey = this.key;
-
-       ss.checkFirstAndLast();
-       ss.disable('full-expand');
-       if (ss.controls) {
-               this.createOverlay(hs.extend(ss.overlayOptions || {}, {
-                       overlayId: ss.controls,
-                       hsId: 'controls',
-                       zIndex: 5
-               }));
-       }
-       if (ss.thumbstrip) ss.thumbstrip.add(this);
-       if (!this.last && this.autoplay) ss.play(true);
-       if (ss.autoplay) {
-               ss.autoplay = setTimeout(function() {
-                       hs.next(key);
-               }, (ss.interval || 500));
-       }
-},
-
-cancelLoading : function() {
-       hs.discardElement (this.wrapper);
-       hs.expanders[this.key] = null;
-       if (hs.upcoming == this.a) hs.upcoming = null;
-       hs.undim(this.key);
-       if (this.loading) hs.loading.style.left = '-9999px';
-       hs.fireEvent(this, 'onHideLoading');
-},
-
-writeCredits : function () {
-       if (this.credits) return;
-       this.credits = hs.createElement('a', {
-               href: hs.creditsHref,
-               target: hs.creditsTarget,
-               className: 'highslide-credits',
-               innerHTML: hs.lang.creditsText,
-               title: hs.lang.creditsTitle
-       });
-       this.createOverlay({
-               overlayId: this.credits,
-               position: this.creditsPosition || 'top left',
-               hsId: 'credits'
-       });
-},
-
-getInline : function(types, addOverlay) {
-       for (var i = 0; i < types.length; i++) {
-               var type = types[i], s = null;
-               if (type == 'caption' && !hs.fireEvent(this, 'onBeforeGetCaption')) return;
-               else if (type == 'heading' && !hs.fireEvent(this, 'onBeforeGetHeading')) return;
-               if (!this[type +'Id'] && this.thumbsUserSetId)
-                       this[type +'Id'] = type +'-for-'+ this.thumbsUserSetId;
-               if (this[type +'Id']) this[type] = hs.getNode(this[type +'Id']);
-               if (!this[type] && !this[type +'Text'] && this[type +'Eval']) try {
-                       s = eval(this[type +'Eval']);
-               } catch (e) {}
-               if (!this[type] && this[type +'Text']) {
-                       s = this[type +'Text'];
-               }
-               if (!this[type] && !s) {
-                       this[type] = hs.getNode(this.a['_'+ type + 'Id']);
-                       if (!this[type]) {
-                               var next = this.a.nextSibling;
-                               while (next && !hs.isHsAnchor(next)) {
-                                       if ((new RegExp('highslide-'+ type)).test(next.className || null)) {
-                                               if (!next.id) this.a['_'+ type + 'Id'] = next.id = 'hsId'+ hs.idCounter++;
-                                               this[type] = hs.getNode(next.id);
-                                               break;
-                                       }
-                                       next = next.nextSibling;
-                               }
-                       }
-               }
-               if (!this[type] && !s && this.numberPosition == type) s = '\n';
-
-               if (!this[type] && s) this[type] = hs.createElement('div',
-                               { className: 'highslide-'+ type, innerHTML: s } );
-
-               if (addOverlay && this[type]) {
-                       var o = { position: (type == 'heading') ? 'above' : 'below' };
-                       for (var x in this[type+'Overlay']) o[x] = this[type+'Overlay'][x];
-                       o.overlayId = this[type];
-                       this.createOverlay(o);
-               }
-       }
-},
-
-
-// on end move and resize
-doShowHide : function(visibility) {
-       if (hs.hideSelects) this.showHideElements('SELECT', visibility);
-       if (hs.hideIframes) this.showHideElements('IFRAME', visibility);
-       if (hs.geckoMac) this.showHideElements('*', visibility);
-},
-showHideElements : function (tagName, visibility) {
-       var els = document.getElementsByTagName(tagName);
-       var prop = tagName == '*' ? 'overflow' : 'visibility';
-       for (var i = 0; i < els.length; i++) {
-               if (prop == 'visibility' || (document.defaultView.getComputedStyle(
-                               els[i], "").getPropertyValue('overflow') == 'auto'
-                               || els[i].getAttribute('hidden-by') != null)) {
-                       var hiddenBy = els[i].getAttribute('hidden-by');
-                       if (visibility == 'visible' && hiddenBy) {
-                               hiddenBy = hiddenBy.replace('['+ this.key +']', '');
-                               els[i].setAttribute('hidden-by', hiddenBy);
-                               if (!hiddenBy) els[i].style[prop] = els[i].origProp;
-                       } else if (visibility == 'hidden') { // hide if behind
-                               var elPos = hs.getPosition(els[i]);
-                               elPos.w = els[i].offsetWidth;
-                               elPos.h = els[i].offsetHeight;
-                               if (!this.dimmingOpacity) { // hide all if dimming
-
-                                       var clearsX = (elPos.x + elPos.w < this.x.get('opos')
-                                               || elPos.x > this.x.get('opos') + this.x.get('osize'));
-                                       var clearsY = (elPos.y + elPos.h < this.y.get('opos')
-                                               || elPos.y > this.y.get('opos') + this.y.get('osize'));
-                               }
-                               var wrapperKey = hs.getWrapperKey(els[i]);
-                               if (!clearsX && !clearsY && wrapperKey != this.key) { // element falls behind image
-                                       if (!hiddenBy) {
-                                               els[i].setAttribute('hidden-by', '['+ this.key +']');
-                                               els[i].origProp = els[i].style[prop];
-                                               els[i].style[prop] = 'hidden';
-
-                                       } else if (hiddenBy.indexOf('['+ this.key +']') == -1) {
-                                               els[i].setAttribute('hidden-by', hiddenBy + '['+ this.key +']');
-                                       }
-                               } else if ((hiddenBy == '['+ this.key +']' || hs.focusKey == wrapperKey)
-                                               && wrapperKey != this.key) { // on move
-                                       els[i].setAttribute('hidden-by', '');
-                                       els[i].style[prop] = els[i].origProp || '';
-                               } else if (hiddenBy && hiddenBy.indexOf('['+ this.key +']') > -1) {
-                                       els[i].setAttribute('hidden-by', hiddenBy.replace('['+ this.key +']', ''));
-                               }
-
-                       }
-               }
-       }
-},
-
-focus : function() {
-       this.wrapper.style.zIndex = hs.zIndexCounter += 2;
-       // blur others
-       for (var i = 0; i < hs.expanders.length; i++) {
-               if (hs.expanders[i] && i == hs.focusKey) {
-                       var blurExp = hs.expanders[i];
-                       blurExp.content.className += ' highslide-'+ blurExp.contentType +'-blur';
-                               blurExp.content.style.cursor = hs.ieLt7 ? 'hand' : 'pointer';
-                               blurExp.content.title = hs.lang.focusTitle;
-                       hs.fireEvent(blurExp, 'onBlur');
-               }
-       }
-
-       // focus this
-       if (this.outline) this.outline.table.style.zIndex
-               = this.wrapper.style.zIndex - 1;
-       this.content.className = 'highslide-'+ this.contentType;
-               this.content.title = hs.lang.restoreTitle;
-
-               if (hs.restoreCursor) {
-                       hs.styleRestoreCursor = window.opera ? 'pointer' : 'url('+ hs.graphicsDir + hs.restoreCursor +'), pointer';
-                       if (hs.ieLt7 && hs.uaVersion < 6) hs.styleRestoreCursor = 'hand';
-                       this.content.style.cursor = hs.styleRestoreCursor;
-               }
-
-       hs.focusKey = this.key;
-       hs.addEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
-       hs.fireEvent(this, 'onFocus');
-},
-moveTo: function(x, y) {
-       this.x.setPos(x);
-       this.y.setPos(y);
-},
-resize : function (e) {
-       var w, h, r = e.width / e.height;
-       w = Math.max(e.width + e.dX, Math.min(this.minWidth, this.x.full));
-       if (this.isImage && Math.abs(w - this.x.full) < 12) w = this.x.full;
-       h = w / r;
-       if (h < Math.min(this.minHeight, this.y.full)) {
-               h = Math.min(this.minHeight, this.y.full);
-               if (this.isImage) w = h * r;
-       }
-       this.resizeTo(w, h);
-},
-resizeTo: function(w, h) {
-       this.y.setSize(h);
-       this.x.setSize(w);
-       this.wrapper.style.height = this.y.get('wsize') +'px';
-},
-
-close : function() {
-       if (this.isClosing || !this.isExpanded) return;
-       if (this.transitions[1] == 'crossfade' && hs.upcoming) {
-               hs.getExpander(hs.upcoming).cancelLoading();
-               hs.upcoming = null;
-       }
-       if (!hs.fireEvent(this, 'onBeforeClose')) return;
-       this.isClosing = true;
-       if (this.slideshow && !hs.upcoming) this.slideshow.pause();
-
-       hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
-
-       try {
-               this.content.style.cursor = 'default';
-               this.changeSize(
-                       0, {
-                               wrapper: {
-                                       width : this.x.t,
-                                       height : this.y.t,
-                                       left: this.x.tpos - this.x.cb + this.x.tb,
-                                       top: this.y.tpos - this.y.cb + this.y.tb
-                               },
-                               content: {
-                                       left: 0,
-                                       top: 0,
-                                       width: this.x.t,
-                                       height: this.y.t
-                               }
-                       }, hs.restoreDuration
-               );
-       } catch (e) { this.afterClose(); }
-},
-
-createOverlay : function (o) {
-       var el = o.overlayId,
-               relToVP = (o.relativeTo == 'viewport' && !/panel$/.test(o.position));
-       if (typeof el == 'string') el = hs.getNode(el);
-       if (o.html) el = hs.createElement('div', { innerHTML: o.html });
-       if (!el || typeof el == 'string') return;
-       if (!hs.fireEvent(this, 'onCreateOverlay', { overlay: el })) return;
-       el.style.display = 'block';
-       o.hsId = o.hsId || o.overlayId;
-       if (this.transitions[1] == 'crossfade' && this.reuseOverlay(o, el)) return;
-       this.genOverlayBox();
-       var width = o.width && /^[0-9]+(px|%)$/.test(o.width) ? o.width : 'auto';
-       if (/^(left|right)panel$/.test(o.position) && !/^[0-9]+px$/.test(o.width)) width = '200px';
-       var overlay = hs.createElement(
-               'div', {
-                       id: 'hsId'+ hs.idCounter++,
-                       hsId: o.hsId
-               }, {
-                       position: 'absolute',
-                       visibility: 'hidden',
-                       width: width,
-                       direction: hs.lang.cssDirection || '',
-                       opacity: 0
-               },
-               relToVP ? hs.viewport :this.overlayBox,
-               true
-       );
-       if (relToVP) overlay.hsKey = this.key;
-
-       overlay.appendChild(el);
-       hs.extend(overlay, {
-               opacity: 1,
-               offsetX: 0,
-               offsetY: 0,
-               dur: (o.fade === 0 || o.fade === false || (o.fade == 2 && hs.ie)) ? 0 : 250
-       });
-       hs.extend(overlay, o);
-
-
-       if (this.gotOverlays) {
-               this.positionOverlay(overlay);
-               if (!overlay.hideOnMouseOut || this.mouseIsOver)
-                       hs.animate(overlay, { opacity: overlay.opacity }, overlay.dur);
-       }
-       hs.push(this.overlays, hs.idCounter - 1);
-},
-positionOverlay : function(overlay) {
-       var p = overlay.position || 'middle center',
-               relToVP = (overlay.relativeTo == 'viewport'),
-               offX = overlay.offsetX,
-               offY = overlay.offsetY;
-       if (relToVP) {
-               hs.viewport.style.display = 'block';
-               overlay.hsKey = this.key;
-               if (overlay.offsetWidth > overlay.parentNode.offsetWidth)
-                       overlay.style.width = '100%';
-       } else
-       if (overlay.parentNode != this.overlayBox) this.overlayBox.appendChild(overlay);
-       if (/left$/.test(p)) overlay.style.left = offX +'px';
-
-       if (/center$/.test(p))  hs.setStyles (overlay, {
-               left: '50%',
-               marginLeft: (offX - Math.round(overlay.offsetWidth / 2)) +'px'
-       });
-
-       if (/right$/.test(p)) overlay.style.right = - offX +'px';
-
-       if (/^leftpanel$/.test(p)) {
-               hs.setStyles(overlay, {
-                       right: '100%',
-                       marginRight: this.x.cb +'px',
-                       top: - this.y.cb +'px',
-                       bottom: - this.y.cb +'px',
-                       overflow: 'auto'
-               });
-               this.x.p1 = overlay.offsetWidth;
-
-       } else if (/^rightpanel$/.test(p)) {
-               hs.setStyles(overlay, {
-                       left: '100%',
-                       marginLeft: this.x.cb +'px',
-                       top: - this.y.cb +'px',
-                       bottom: - this.y.cb +'px',
-                       overflow: 'auto'
-               });
-               this.x.p2 = overlay.offsetWidth;
-       }
-       var parOff = overlay.parentNode.offsetHeight;
-       overlay.style.height = 'auto';
-       if (relToVP && overlay.offsetHeight > parOff)
-               overlay.style.height = hs.ieLt7 ? parOff +'px' : '100%';
-
-       if (/^top/.test(p)) overlay.style.top = offY +'px';
-       if (/^middle/.test(p))  hs.setStyles (overlay, {
-               top: '50%',
-               marginTop: (offY - Math.round(overlay.offsetHeight / 2)) +'px'
-       });
-       if (/^bottom/.test(p)) overlay.style.bottom = - offY +'px';
-       if (/^above$/.test(p)) {
-               hs.setStyles(overlay, {
-                       left: (- this.x.p1 - this.x.cb) +'px',
-                       right: (- this.x.p2 - this.x.cb) +'px',
-                       bottom: '100%',
-                       marginBottom: this.y.cb +'px',
-                       width: 'auto'
-               });
-               this.y.p1 = overlay.offsetHeight;
-
-       } else if (/^below$/.test(p)) {
-               hs.setStyles(overlay, {
-                       position: 'relative',
-                       left: (- this.x.p1 - this.x.cb) +'px',
-                       right: (- this.x.p2 - this.x.cb) +'px',
-                       top: '100%',
-                       marginTop: this.y.cb +'px',
-                       width: 'auto'
-               });
-               this.y.p2 = overlay.offsetHeight;
-               overlay.style.position = 'absolute';
-       }
-},
-
-getOverlays : function() {
-       this.getInline(['heading', 'caption'], true);
-       this.getNumber();
-       if (this.caption) hs.fireEvent(this, 'onAfterGetCaption');
-       if (this.heading) hs.fireEvent(this, 'onAfterGetHeading');
-       if (this.heading && this.dragByHeading) this.heading.className += ' highslide-move';
-       if (hs.showCredits) this.writeCredits();
-       for (var i = 0; i < hs.overlays.length; i++) {
-               var o = hs.overlays[i], tId = o.thumbnailId, sg = o.slideshowGroup;
-               if ((!tId && !sg) || (tId && tId == this.thumbsUserSetId)
-                               || (sg && sg === this.slideshowGroup)) {
-                       this.createOverlay(o);
-               }
-       }
-       var os = [];
-       for (var i = 0; i < this.overlays.length; i++) {
-               var o = hs.$('hsId'+ this.overlays[i]);
-               if (/panel$/.test(o.position)) this.positionOverlay(o);
-               else hs.push(os, o);
-       }
-       for (var i = 0; i < os.length; i++) this.positionOverlay(os[i]);
-       this.gotOverlays = true;
-},
-genOverlayBox : function() {
-       if (!this.overlayBox) this.overlayBox = hs.createElement (
-               'div', {
-                       className: this.wrapperClassName
-               }, {
-                       position : 'absolute',
-                       width: (this.x.size || (this.useBox ? this.width : null)
-                               || this.x.full) +'px',
-                       height: (this.y.size || this.y.full) +'px',
-                       visibility : 'hidden',
-                       overflow : 'hidden',
-                       zIndex : hs.ie ? 4 : 'auto'
-               },
-               hs.container,
-               true
-       );
-},
-sizeOverlayBox : function(doWrapper, doPanels) {
-       var overlayBox = this.overlayBox,
-               x = this.x,
-               y = this.y;
-       hs.setStyles( overlayBox, {
-               width: x.size +'px',
-               height: y.size +'px'
-       });
-       if (doWrapper || doPanels) {
-               for (var i = 0; i < this.overlays.length; i++) {
-                       var o = hs.$('hsId'+ this.overlays[i]);
-                       var ie6 = (hs.ieLt7 || document.compatMode == 'BackCompat');
-                       if (o && /^(above|below)$/.test(o.position)) {
-                               if (ie6) {
-                                       o.style.width = (overlayBox.offsetWidth + 2 * x.cb
-                                               + x.p1 + x.p2) +'px';
-                               }
-                               y[o.position == 'above' ? 'p1' : 'p2'] = o.offsetHeight;
-                       }
-                       if (o && ie6 && /^(left|right)panel$/.test(o.position)) {
-                               o.style.height = (overlayBox.offsetHeight + 2* y.cb) +'px';
-                       }
-               }
-       }
-       if (doWrapper) {
-               hs.setStyles(this.content, {
-                       top: y.p1 +'px'
-               });
-               hs.setStyles(overlayBox, {
-                       top: (y.p1 + y.cb) +'px'
-               });
-       }
-},
-
-showOverlays : function() {
-       var b = this.overlayBox;
-       b.className = '';
-       hs.setStyles(b, {
-               top: (this.y.p1 + this.y.cb) +'px',
-               left: (this.x.p1 + this.x.cb) +'px',
-               overflow : 'visible'
-       });
-       if (hs.safari) b.style.visibility = 'visible';
-       this.wrapper.appendChild (b);
-       for (var i = 0; i < this.overlays.length; i++) {
-               var o = hs.$('hsId'+ this.overlays[i]);
-               o.style.zIndex = o.zIndex || 4;
-               if (!o.hideOnMouseOut || this.mouseIsOver) {
-                       o.style.visibility = 'visible';
-                       hs.setStyles(o, { visibility: 'visible', display: '' });
-                       hs.animate(o, { opacity: o.opacity }, o.dur);
-               }
-       }
-},
-
-destroyOverlays : function() {
-       if (!this.overlays.length) return;
-       if (this.slideshow) {
-               var c = this.slideshow.controls;
-               if (c && hs.getExpander(c) == this) c.parentNode.removeChild(c);
-       }
-       for (var i = 0; i < this.overlays.length; i++) {
-               var o = hs.$('hsId'+ this.overlays[i]);
-               if (o && o.parentNode == hs.viewport && hs.getExpander(o) == this) hs.discardElement(o);
-       }
-       hs.discardElement(this.overlayBox);
-},
-
-
-
-createFullExpand : function () {
-       if (this.slideshow && this.slideshow.controls) {
-               this.slideshow.enable('full-expand');
-               return;
-       }
-       this.fullExpandLabel = hs.createElement(
-               'a', {
-                       href: 'javascript:hs.expanders['+ this.key +'].doFullExpand();',
-                       title: hs.lang.fullExpandTitle,
-                       className: 'highslide-full-expand'
-               }
-       );
-       if (!hs.fireEvent(this, 'onCreateFullExpand')) return;
-
-       this.createOverlay({
-               overlayId: this.fullExpandLabel,
-               position: hs.fullExpandPosition,
-               hideOnMouseOut: true,
-               opacity: hs.fullExpandOpacity
-       });
-},
-
-doFullExpand : function () {
-       try {
-               if (!hs.fireEvent(this, 'onDoFullExpand')) return;
-               if (this.fullExpandLabel) hs.discardElement(this.fullExpandLabel);
-
-               this.focus();
-               var xSize = this.x.size,
-                       ySize = this.y.size;
-               this.resizeTo(this.x.full, this.y.full);
-
-               var xpos = this.x.pos - (this.x.size - xSize) / 2;
-               if (xpos < hs.marginLeft) xpos = hs.marginLeft;
-
-               var ypos = this.y.pos - (this.y.size - ySize) / 2;
-               if (ypos < hs.marginTop) ypos = hs.marginTop;
-
-               this.moveTo(xpos, ypos);
-               this.doShowHide('hidden');
-
-       } catch (e) {
-               this.error(e);
-       }
-},
-
-
-afterClose : function () {
-       this.a.className = this.a.className.replace('highslide-active-anchor', '');
-
-       this.doShowHide('visible');
-               if (this.outline && this.outlineWhileAnimating) this.outline.destroy();
-
-               hs.discardElement(this.wrapper);
-       this.destroyOverlays();
-       if (!hs.viewport.childNodes.length) hs.viewport.style.display = 'none';
-
-       if (this.dimmingOpacity) hs.undim(this.key);
-       hs.fireEvent(this, 'onAfterClose');
-       hs.expanders[this.key] = null;
-       hs.reOrder();
-}
-
-};
-
-
-hs.Slideshow = function (expKey, options) {
-       if (hs.dynamicallyUpdateAnchors !== false) hs.updateAnchors();
-       this.expKey = expKey;
-       for (var x in options) this[x] = options[x];
-       if (this.useControls) this.getControls();
-       if (this.thumbstrip) this.thumbstrip = hs.Thumbstrip(this);
-};
-hs.Slideshow.prototype = {
-getControls: function() {
-       this.controls = hs.createElement('div', { innerHTML: hs.replaceLang(hs.skin.controls) },
-               null, hs.container);
-
-       var buttons = ['play', 'pause', 'previous', 'next', 'move', 'full-expand', 'close'];
-       this.btn = {};
-       var pThis = this;
-       for (var i = 0; i < buttons.length; i++) {
-               this.btn[buttons[i]] = hs.getElementByClass(this.controls, 'li', 'highslide-'+ buttons[i]);
-               this.enable(buttons[i]);
-       }
-       this.btn.pause.style.display = 'none';
-       //this.disable('full-expand');
-},
-checkFirstAndLast: function() {
-       if (this.repeat || !this.controls) return;
-       var exp = hs.expanders[this.expKey],
-               cur = exp.getAnchorIndex(),
-               re = /disabled$/;
-       if (cur == 0)
-               this.disable('previous');
-       else if (re.test(this.btn.previous.getElementsByTagName('a')[0].className))
-               this.enable('previous');
-       if (cur + 1 == hs.anchors.groups[exp.slideshowGroup || 'none'].length) {
-               this.disable('next');
-               this.disable('play');
-       } else if (re.test(this.btn.next.getElementsByTagName('a')[0].className)) {
-               this.enable('next');
-               this.enable('play');
-       }
-},
-enable: function(btn) {
-       if (!this.btn) return;
-       var sls = this, a = this.btn[btn].getElementsByTagName('a')[0], re = /disabled$/;
-       a.onclick = function() {
-               sls[btn]();
-               return false;
-       };
-       if (re.test(a.className)) a.className = a.className.replace(re, '');
-},
-disable: function(btn) {
-       if (!this.btn) return;
-       var a = this.btn[btn].getElementsByTagName('a')[0];
-       a.onclick = function() { return false; };
-       if (!/disabled$/.test(a.className)) a.className += ' disabled';
-},
-hitSpace: function() {
-       if (this.autoplay) this.pause();
-       else this.play();
-},
-play: function(wait) {
-       if (this.btn) {
-               this.btn.play.style.display = 'none';
-               this.btn.pause.style.display = '';
-       }
-
-       this.autoplay = true;
-       if (!wait) hs.next(this.expKey);
-},
-pause: function() {
-       if (this.btn) {
-               this.btn.pause.style.display = 'none';
-               this.btn.play.style.display = '';
-       }
-
-       clearTimeout(this.autoplay);
-       this.autoplay = null;
-},
-previous: function() {
-       this.pause();
-       hs.previous(this.btn.previous);
-},
-next: function() {
-       this.pause();
-       hs.next(this.btn.next);
-},
-move: function() {},
-'full-expand': function() {
-       hs.getExpander().doFullExpand();
-},
-close: function() {
-       hs.close(this.btn.close);
-}
-};
-hs.Thumbstrip = function(slideshow) {
-       function add (exp) {
-               hs.extend(options || {}, {
-                       overlayId: dom,
-                       hsId: 'thumbstrip',
-                       className: 'highslide-thumbstrip-'+ mode +'-overlay ' + (options.className || '')
-               });
-               if (hs.ieLt7) options.fade = 0;
-               exp.createOverlay(options);
-               hs.setStyles(dom.parentNode, { overflow: 'hidden' });
-       };
-
-       function scroll (delta) {
-               selectThumb(undefined, Math.round(delta * dom[isX ? 'offsetWidth' : 'offsetHeight'] * 0.7));
-       };
-
-       function selectThumb (i, scrollBy) {
-               if (i === undefined) for (var j = 0; j < group.length; j++) {
-                       if (group[j] == hs.expanders[slideshow.expKey].a) {
-                               i = j;
-                               break;
-                       }
-               }
-               if (i === undefined) return;
-               var as = dom.getElementsByTagName('a'),
-                       active = as[i],
-                       cell = active.parentNode,
-                       left = isX ? 'Left' : 'Top',
-                       right = isX ? 'Right' : 'Bottom',
-                       width = isX ? 'Width' : 'Height',
-                       offsetLeft = 'offset' + left,
-                       offsetWidth = 'offset' + width,
-                       overlayWidth = div.parentNode.parentNode[offsetWidth],
-                       minTblPos = overlayWidth - table[offsetWidth],
-                       curTblPos = parseInt(table.style[isX ? 'left' : 'top']) || 0,
-                       tblPos = curTblPos,
-                       mgnRight = 20;
-               if (scrollBy !== undefined) {
-                       tblPos = curTblPos - scrollBy;
-
-                       if (minTblPos > 0) minTblPos = 0;
-                       if (tblPos > 0) tblPos = 0;
-                       if (tblPos < minTblPos) tblPos = minTblPos;
-
-
-               } else {
-                       for (var j = 0; j < as.length; j++) as[j].className = '';
-                       active.className = 'highslide-active-anchor';
-                       var activeLeft = i > 0 ? as[i - 1].parentNode[offsetLeft] : cell[offsetLeft],
-                               activeRight = cell[offsetLeft] + cell[offsetWidth] +
-                                       (as[i + 1] ? as[i + 1].parentNode[offsetWidth] : 0);
-                       if (activeRight > overlayWidth - curTblPos) tblPos = overlayWidth - activeRight;
-                       else if (activeLeft < -curTblPos) tblPos = -activeLeft;
-               }
-               var markerPos = cell[offsetLeft] + (cell[offsetWidth] - marker[offsetWidth]) / 2 + tblPos;
-               hs.animate(table, isX ? { left: tblPos } : { top: tblPos }, null, 'easeOutQuad');
-               hs.animate(marker, isX ? { left: markerPos } : { top: markerPos }, null, 'easeOutQuad');
-               scrollUp.style.display = tblPos < 0 ? 'block' : 'none';
-               scrollDown.style.display = (tblPos > minTblPos)  ? 'block' : 'none';
-
-       };
-
-
-       // initialize
-       var group = hs.anchors.groups[hs.expanders[slideshow.expKey].slideshowGroup || 'none'],
-               options = slideshow.thumbstrip,
-               mode = options.mode || 'horizontal',
-               floatMode = (mode == 'float'),
-               tree = floatMode ? ['div', 'ul', 'li', 'span'] : ['table', 'tbody', 'tr', 'td'],
-               isX = (mode == 'horizontal'),
-               dom = hs.createElement('div', {
-                               className: 'highslide-thumbstrip highslide-thumbstrip-'+ mode,
-                               innerHTML:
-                                       '<div class="highslide-thumbstrip-inner">'+
-                                       '<'+ tree[0] +'><'+ tree[1] +'></'+ tree[1] +'></'+ tree[0] +'></div>'+
-                                       '<div class="highslide-scroll-up"><div></div></div>'+
-                                       '<div class="highslide-scroll-down"><div></div></div>'+
-                                       '<div class="highslide-marker"><div></div></div>'
-                       }, {
-                               display: 'none'
-                       }, hs.container),
-               domCh = dom.childNodes,
-               div = domCh[0],
-               scrollUp = domCh[1],
-               scrollDown = domCh[2],
-               marker = domCh[3],
-               table = div.firstChild,
-               tbody = dom.getElementsByTagName(tree[1])[0],
-               tr;
-       for (var i = 0; i < group.length; i++) {
-               if (i == 0 || !isX) tr = hs.createElement(tree[2], null, null, tbody);
-               (function(){
-                       var a = group[i],
-                               cell = hs.createElement(tree[3], null, null, tr),
-                               pI = i;
-                       hs.createElement('a', {
-                               href: a.href,
-                               title: a.title,
-                               onclick: function() {
-                                       if (/highslide-active-anchor/.test(this.className)) return false;
-                                       hs.getExpander(this).focus();
-                                       return hs.transit(a);
-                               },
-                               innerHTML: hs.stripItemFormatter ? hs.stripItemFormatter(a) : a.innerHTML
-                       }, null, cell);
-               })();
-       }
-       if (!floatMode) {
-               scrollUp.onclick = function () { scroll(-1); };
-               scrollDown.onclick = function() { scroll(1); };
-               hs.addEventListener(tbody, document.onmousewheel !== undefined ?
-                               'mousewheel' : 'DOMMouseScroll', function(e) {
-                       var delta = 0;
-                       e = e || window.event;
-                       if (e.wheelDelta) {
-                               delta = e.wheelDelta/120;
-                               if (hs.opera) delta = -delta;
-                       } else if (e.detail) {
-                               delta = -e.detail/3;
-                       }
-                       if (delta) scroll(-delta * 0.2);
-                       if (e.preventDefault) e.preventDefault();
-                       e.returnValue = false;
-               });
-       }
-
-       return {
-               add: add,
-               selectThumb: selectThumb
-       }
-};
-hs.langDefaults = hs.lang;
-// history
-var HsExpander = hs.Expander;
-if (hs.ie && window == window.top) {
-       (function () {
-               try {
-                       document.documentElement.doScroll('left');
-               } catch (e) {
-                       setTimeout(arguments.callee, 50);
-                       return;
-               }
-               hs.ready();
-       })();
-}
-hs.addEventListener(document, 'DOMContentLoaded', hs.ready);
-hs.addEventListener(window, 'load', hs.ready);
-
-// set handlers
-hs.addEventListener(document, 'ready', function() {
-       if (hs.expandCursor || hs.dimmingOpacity) {
-               var style = hs.createElement('style', { type: 'text/css' }, null,
-                       document.getElementsByTagName('HEAD')[0]),
-                       backCompat = document.compatMode == 'BackCompat';
-
-
-               function addRule(sel, dec) {
-                       if (hs.ie && (hs.uaVersion < 9 || backCompat)) {
-                               var last = document.styleSheets[document.styleSheets.length - 1];
-                               if (typeof(last.addRule) == "object") last.addRule(sel, dec);
-                       } else {
-                               style.appendChild(document.createTextNode(sel + " {" + dec + "}"));
-                       }
-               }
-               function fix(prop) {
-                       return 'expression( ( ( ignoreMe = document.documentElement.'+ prop +
-                               ' ? document.documentElement.'+ prop +' : document.body.'+ prop +' ) ) + \'px\' );';
-               }
-               if (hs.expandCursor) addRule ('.highslide img',
-                       'cursor: url('+ hs.graphicsDir + hs.expandCursor +'), pointer !important;');
-               addRule ('.highslide-viewport-size',
-                       hs.ie && (hs.uaVersion < 7 || backCompat) ?
-                               'position: absolute; '+
-                               'left:'+ fix('scrollLeft') +
-                               'top:'+ fix('scrollTop') +
-                               'width:'+ fix('clientWidth') +
-                               'height:'+ fix('clientHeight') :
-                               'position: fixed; width: 100%; height: 100%; left: 0; top: 0');
-       }
-});
-hs.addEventListener(window, 'resize', function() {
-       hs.getPageSize();
-       if (hs.viewport) for (var i = 0; i < hs.viewport.childNodes.length; i++) {
-               var node = hs.viewport.childNodes[i],
-                       exp = hs.getExpander(node);
-               exp.positionOverlay(node);
-               if (node.hsId == 'thumbstrip') exp.slideshow.thumbstrip.selectThumb();
-       }
-});
-hs.addEventListener(document, 'mousemove', function(e) {
-       hs.mouse = { x: e.clientX, y: e.clientY };
-});
-hs.addEventListener(document, 'mousedown', hs.mouseClickHandler);
-hs.addEventListener(document, 'mouseup', hs.mouseClickHandler);
-
-hs.addEventListener(document, 'ready', hs.getAnchors);
-hs.addEventListener(window, 'load', hs.preloadImages);
-}
diff --git a/gal/highslide/highslide.css b/gal/highslide/highslide.css
deleted file mode 100644 (file)
index 452b862..0000000
+++ /dev/null
@@ -1,888 +0,0 @@
-/**
-* @file: highslide.css 
-* @version: 4.1.13
-*/
-.highslide-container div {
-       font-family: Verdana, Helvetica;
-       font-size: 10pt;
-}
-.highslide-container table {
-       background: none;
-       table-layout: auto;
-}
-.highslide {
-       outline: none;
-       text-decoration: none;
-}
-.highslide img {
-       border: 2px solid silver;
-}
-.highslide:hover img {
-       border-color: gray;
-}
-.highslide-active-anchor img {
-       visibility: hidden;
-}
-.highslide-gallery .highslide-active-anchor img {
-       border-color: black;
-       visibility: visible;
-       cursor: default;
-}
-.highslide-image {
-       border-width: 2px;
-       border-style: solid;
-       border-color: white;
-}
-.highslide-wrapper, .highslide-outline {
-       background: white;
-}
-.glossy-dark {
-       background: #111;
-}
-
-.highslide-image-blur {
-}
-.highslide-number {
-       font-weight: bold;
-       color: gray;
-       font-size: .9em;
-}
-.highslide-caption {
-       display: none;
-       font-size: 1em;
-       padding: 5px;
-       /*background: white;*/
-}
-.highslide-heading {
-       display: none;
-       font-weight: bold;
-       margin: 0.4em;
-}
-.highslide-dimming {
-       /*position: absolute;*/
-       background: black;
-}
-a.highslide-full-expand {
-   background: url(graphics/fullexpand.gif) no-repeat;
-   display: block;
-   margin: 0 10px 10px 0;
-   width: 34px;
-   height: 34px;
-}
-.highslide-loading {
-       display: block;
-       color: black;
-       font-size: 9px;
-       font-weight: bold;
-       text-transform: uppercase;
-       text-decoration: none;
-       padding: 3px;
-       border: 1px solid white;
-       background-color: white;
-       padding-left: 22px;
-       background-image: url(graphics/loader.white.gif);
-       background-repeat: no-repeat;
-       background-position: 3px 1px;
-}
-a.highslide-credits,
-a.highslide-credits i {
-       padding: 2px;
-       color: silver;
-       text-decoration: none;
-       font-size: 10px;
-}
-a.highslide-credits:hover,
-a.highslide-credits:hover i {
-       color: white;
-       background-color: gray;
-}
-.highslide-move, .highslide-move * {
-       cursor: move;
-}
-
-.highslide-viewport {
-       display: none;
-       position: fixed;
-       width: 100%;
-       height: 100%;
-       z-index: 1;
-       background: none;
-       left: 0;
-       top: 0;
-}
-.highslide-overlay {
-       display: none;
-}
-.hidden-container {
-       display: none;
-}
-/* Example of a semitransparent, offset closebutton */
-.closebutton {
-       position: relative;
-       top: -15px;
-       left: 15px;
-       width: 30px;
-       height: 30px;
-       cursor: pointer;
-       background: url(graphics/close.png);
-       /* NOTE! For IE6, you also need to update the highslide-ie6.css file. */
-}
-
-/*****************************************************************************/
-/* Thumbnail boxes for the galleries.                                        */
-/* Remove these if you are not using a gallery.                              */
-/*****************************************************************************/
-.highslide-gallery ul {
-       list-style-type: none;
-       margin: 0;
-       padding: 0;
-}
-.highslide-gallery ul li {
-       display: block;
-       position: relative;
-       float: left;
-       width: 106px;
-       height: 106px;
-       margin: 2px;
-       padding: 0;
-       line-height: 0;
-       overflow: hidden;
-}
-.highslide-gallery ul a {
-       position: absolute;
-       top: 50%;
-       left: 50%;
-}
-.highslide-gallery ul img {
-       position: relative;
-       top: -50%;
-       left: -50%;
-}
-html>/**/body .highslide-gallery ul li {
-       display: table;
-       text-align: center;
-}
-html>/**/body .highslide-gallery ul li {
-       text-align: center;
-}
-html>/**/body .highslide-gallery ul a {
-       position: static;
-       display: table-cell;
-       vertical-align: middle;
-}
-html>/**/body .highslide-gallery ul img {
-       position: static;
-}
-
-/*****************************************************************************/
-/* Controls for the galleries.                                                                                      */
-/* Remove these if you are not using a gallery                                                      */
-/*****************************************************************************/
-.highslide-controls {
-       width: 195px;
-       height: 40px;
-       background: url(graphics/controlbar-white.gif) 0 -90px no-repeat;
-       margin: 20px 15px 10px 0;
-}
-.highslide-controls ul {
-       position: relative;
-       left: 15px;
-       height: 40px;
-       list-style: none;
-       margin: 0;
-       padding: 0;
-       background: url(graphics/controlbar-white.gif) right -90px no-repeat;
-
-}
-.highslide-controls li {
-       float: left;
-       padding: 5px 0;
-       margin:0;
-       list-style: none;
-}
-.highslide-controls a {
-       background-image: url(graphics/controlbar-white.gif);
-       display: block;
-       float: left;
-       height: 30px;
-       width: 30px;
-       outline: none;
-}
-.highslide-controls a.disabled {
-       cursor: default;
-}
-.highslide-controls a.disabled span {
-       cursor: default;
-}
-.highslide-controls a span {
-       /* hide the text for these graphic buttons */
-       display: none;
-       cursor: pointer;
-}
-
-
-/* The CSS sprites for the controlbar - see http://www.google.com/search?q=css+sprites */
-.highslide-controls .highslide-previous a {
-       background-position: 0 0;
-}
-.highslide-controls .highslide-previous a:hover {
-       background-position: 0 -30px;
-}
-.highslide-controls .highslide-previous a.disabled {
-       background-position: 0 -60px !important;
-}
-.highslide-controls .highslide-play a {
-       background-position: -30px 0;
-}
-.highslide-controls .highslide-play a:hover {
-       background-position: -30px -30px;
-}
-.highslide-controls .highslide-play a.disabled {
-       background-position: -30px -60px !important;
-}
-.highslide-controls .highslide-pause a {
-       background-position: -60px 0;
-}
-.highslide-controls .highslide-pause a:hover {
-       background-position: -60px -30px;
-}
-.highslide-controls .highslide-next a {
-       background-position: -90px 0;
-}
-.highslide-controls .highslide-next a:hover {
-       background-position: -90px -30px;
-}
-.highslide-controls .highslide-next a.disabled {
-       background-position: -90px -60px !important;
-}
-.highslide-controls .highslide-move a {
-       background-position: -120px 0;
-}
-.highslide-controls .highslide-move a:hover {
-       background-position: -120px -30px;
-}
-.highslide-controls .highslide-full-expand a {
-       background-position: -150px 0;
-}
-.highslide-controls .highslide-full-expand a:hover {
-       background-position: -150px -30px;
-}
-.highslide-controls .highslide-full-expand a.disabled {
-       background-position: -150px -60px !important;
-}
-.highslide-controls .highslide-close a {
-       background-position: -180px 0;
-}
-.highslide-controls .highslide-close a:hover {
-       background-position: -180px -30px;
-}
-
-/*****************************************************************************/
-/* Styles for the HTML popups                                                                                       */
-/* Remove these if you are not using Highslide HTML                                                 */
-/*****************************************************************************/
-.highslide-maincontent {
-       display: none;
-}
-.highslide-html {
-       background-color: white;
-}
-.mobile .highslide-html {
-       border: 1px solid silver;
-}
-.highslide-html-content {
-       display: none;
-       width: 400px;
-       padding: 0 5px 5px 5px;
-}
-.highslide-header {
-       padding-bottom: 5px;
-}
-.highslide-header ul {
-       margin: 0;
-       padding: 0;
-       text-align: right;
-}
-.highslide-header ul li {
-       display: inline;
-       padding-left: 1em;
-}
-.highslide-header ul li.highslide-previous, .highslide-header ul li.highslide-next {
-       display: none;
-}
-.highslide-header a {
-       font-weight: bold;
-       color: gray;
-       text-transform: uppercase;
-       text-decoration: none;
-}
-.highslide-header a:hover {
-       color: black;
-}
-.highslide-header .highslide-move a {
-       cursor: move;
-}
-.highslide-footer {
-       height: 16px;
-}
-.highslide-footer .highslide-resize {
-       display: block;
-       float: right;
-       margin-top: 5px;
-       height: 11px;
-       width: 11px;
-       background: url(graphics/resize.gif) no-repeat;
-}
-.highslide-footer .highslide-resize span {
-       display: none;
-}
-.highslide-body {
-}
-.highslide-resize {
-       cursor: nw-resize;
-}
-
-/*****************************************************************************/
-/* Styles for the Individual wrapper class names.                                                       */
-/* See www.highslide.com/ref/hs.wrapperClassName                                                        */
-/* You can safely remove the class name themes you don't use                            */
-/*****************************************************************************/
-
-/* hs.wrapperClassName = 'draggable-header' */
-.draggable-header .highslide-header {
-       height: 18px;
-       border-bottom: 1px solid #dddddd;
-}
-.draggable-header .highslide-heading {
-       position: absolute;
-       margin: 2px 0.4em;
-}
-
-.draggable-header .highslide-header .highslide-move {
-       cursor: move;
-       display: block;
-       height: 16px;
-       position: absolute;
-       right: 24px;
-       top: 0;
-       width: 100%;
-       z-index: 1;
-}
-.draggable-header .highslide-header .highslide-move * {
-       display: none;
-}
-.draggable-header .highslide-header .highslide-close {
-       position: absolute;
-       right: 2px;
-       top: 2px;
-       z-index: 5;
-       padding: 0;
-}
-.draggable-header .highslide-header .highslide-close a {
-       display: block;
-       height: 16px;
-       width: 16px;
-       background-image: url(graphics/closeX.png);
-}
-.draggable-header .highslide-header .highslide-close a:hover {
-       background-position: 0 16px;
-}
-.draggable-header .highslide-header .highslide-close span {
-       display: none;
-}
-.draggable-header .highslide-maincontent {
-       padding-top: 1em;
-}
-
-/* hs.wrapperClassName = 'titlebar' */
-.titlebar .highslide-header {
-       height: 18px;
-       border-bottom: 1px solid #dddddd;
-}
-.titlebar .highslide-heading {
-       position: absolute;
-       width: 90%;
-       margin: 1px 0 1px 5px;
-       color: #666666;
-}
-
-.titlebar .highslide-header .highslide-move {
-       cursor: move;
-       display: block;
-       height: 16px;
-       position: absolute;
-       right: 24px;
-       top: 0;
-       width: 100%;
-       z-index: 1;
-}
-.titlebar .highslide-header .highslide-move * {
-       display: none;
-}
-.titlebar .highslide-header li {
-       position: relative;
-       top: 3px;
-       z-index: 2;
-       padding: 0 0 0 1em;
-}
-.titlebar .highslide-maincontent {
-       padding-top: 1em;
-}
-
-/* hs.wrapperClassName = 'no-footer' */
-.no-footer .highslide-footer {
-       display: none;
-}
-
-/* hs.wrapperClassName = 'wide-border' */
-.wide-border {
-       background: white;
-}
-.wide-border .highslide-image {
-       border-width: 10px;
-}
-.wide-border .highslide-caption {
-       padding: 0 10px 10px 10px;
-}
-
-/* hs.wrapperClassName = 'borderless' */
-.borderless .highslide-image {
-       border: none;
-}
-.borderless .highslide-caption {
-       border-bottom: 1px solid white;
-       border-top: 1px solid white;
-       background: silver;
-}
-
-/* hs.wrapperClassName = 'outer-glow' */
-.outer-glow {
-       background: #444;
-}
-.outer-glow .highslide-image {
-       border: 5px solid #444444;
-}
-.outer-glow .highslide-caption {
-       border: 5px solid #444444;
-       border-top: none;
-       padding: 5px;
-       background-color: gray;
-}
-
-/* hs.wrapperClassName = 'colored-border' */
-.colored-border {
-       background: white;
-}
-.colored-border .highslide-image {
-       border: 2px solid green;
-}
-.colored-border .highslide-caption {
-       border: 2px solid green;
-       border-top: none;
-}
-
-/* hs.wrapperClassName = 'dark' */
-.dark {
-       background: #111;
-}
-.dark .highslide-image {
-       border-color: black black #202020 black;
-       background: gray;
-}
-.dark .highslide-caption {
-       color: white;
-       background: #111;
-}
-.dark .highslide-controls,
-.dark .highslide-controls ul,
-.dark .highslide-controls a {
-       background-image: url(graphics/controlbar-black-border.gif);
-}
-
-/* hs.wrapperClassName = 'floating-caption' */
-.floating-caption .highslide-caption {
-       position: absolute;
-       padding: 1em 0 0 0;
-       background: none;
-       color: white;
-       border: none;
-       font-weight: bold;
-}
-
-/* hs.wrapperClassName = 'controls-in-heading' */
-.controls-in-heading .highslide-heading {
-       color: gray;
-       font-weight: bold;
-       height: 20px;
-       overflow: hidden;
-       cursor: default;
-       padding: 0 0 0 22px;
-       margin: 0;
-       background: url(graphics/icon.gif) no-repeat 0 1px;
-}
-.controls-in-heading .highslide-controls {
-       width: 105px;
-       height: 20px;
-       position: relative;
-       margin: 0;
-       top: -23px;
-       left: 7px;
-       background: none;
-}
-.controls-in-heading .highslide-controls ul {
-       position: static;
-       height: 20px;
-       background: none;
-}
-.controls-in-heading .highslide-controls li {
-       padding: 0;
-}
-.controls-in-heading .highslide-controls a {
-       background-image: url(graphics/controlbar-white-small.gif);
-       height: 20px;
-       width: 20px;
-}
-
-.controls-in-heading .highslide-controls .highslide-move {
-       display: none;
-}
-
-.controls-in-heading .highslide-controls .highslide-previous a {
-       background-position: 0 0;
-}
-.controls-in-heading .highslide-controls .highslide-previous a:hover {
-       background-position: 0 -20px;
-}
-.controls-in-heading .highslide-controls .highslide-previous a.disabled {
-       background-position: 0 -40px !important;
-}
-.controls-in-heading .highslide-controls .highslide-play a {
-       background-position: -20px 0;
-}
-.controls-in-heading .highslide-controls .highslide-play a:hover {
-       background-position: -20px -20px;
-}
-.controls-in-heading .highslide-controls .highslide-play a.disabled {
-       background-position: -20px -40px !important;
-}
-.controls-in-heading .highslide-controls .highslide-pause a {
-       background-position: -40px 0;
-}
-.controls-in-heading .highslide-controls .highslide-pause a:hover {
-       background-position: -40px -20px;
-}
-.controls-in-heading .highslide-controls .highslide-next a {
-       background-position: -60px 0;
-}
-.controls-in-heading .highslide-controls .highslide-next a:hover {
-       background-position: -60px -20px;
-}
-.controls-in-heading .highslide-controls .highslide-next a.disabled {
-       background-position: -60px -40px !important;
-}
-.controls-in-heading .highslide-controls .highslide-full-expand a {
-       background-position: -100px 0;
-}
-.controls-in-heading .highslide-controls .highslide-full-expand a:hover {
-       background-position: -100px -20px;
-}
-.controls-in-heading .highslide-controls .highslide-full-expand a.disabled {
-       background-position: -100px -40px !important;
-}
-.controls-in-heading .highslide-controls .highslide-close a {
-       background-position: -120px 0;
-}
-.controls-in-heading .highslide-controls .highslide-close a:hover {
-       background-position: -120px -20px;
-}
-
-/*****************************************************************************/
-/* Styles for text based controls.                                                                  */
-/* You can safely remove this if you don't use text based controls                      */
-/*****************************************************************************/
-
-.text-controls .highslide-controls {
-       width: auto;
-       height: auto;
-       margin: 0;
-       text-align: center;
-       background: none;
-}
-.text-controls ul {
-       position: static;
-       background: none;
-       height: auto;
-       left: 0;
-}
-.text-controls .highslide-move {
-       display: none;
-}
-.text-controls li {
-    background-image: url(graphics/controlbar-text-buttons.png);
-       background-position: right top !important;
-       padding: 0;
-       margin-left: 15px;
-       display: block;
-       width: auto;
-}
-.text-controls a {
-    background: url(graphics/controlbar-text-buttons.png) no-repeat;
-    background-position: left top !important;
-    position: relative;
-    left: -10px;
-       display: block;
-       width: auto;
-       height: auto;
-       text-decoration: none !important;
-}
-.text-controls a span {
-       background: url(graphics/controlbar-text-buttons.png) no-repeat;
-    margin: 1px 2px 1px 10px;
-       display: block;
-    min-width: 4em;
-    height: 18px;
-    line-height: 18px;
-       padding: 1px 0 1px 18px;
-    color: #333;
-       font-family: "Trebuchet MS", Arial, sans-serif;
-       font-size: 12px;
-       font-weight: bold;
-       white-space: nowrap;
-}
-.text-controls .highslide-next {
-       margin-right: 1em;
-}
-.text-controls .highslide-full-expand a span {
-       min-width: 0;
-       margin: 1px 0;
-       padding: 1px 0 1px 10px;
-}
-.text-controls .highslide-close a span {
-       min-width: 0;
-}
-.text-controls a:hover span {
-       color: black;
-}
-.text-controls a.disabled span {
-       color: #999;
-}
-
-.text-controls .highslide-previous span {
-       background-position: 0 -40px;
-}
-.text-controls .highslide-previous a.disabled {
-       background-position: left top !important;
-}
-.text-controls .highslide-previous a.disabled span {
-       background-position: 0 -140px;
-}
-.text-controls .highslide-play span {
-       background-position: 0 -60px;
-}
-.text-controls .highslide-play a.disabled {
-       background-position: left top !important;
-}
-.text-controls .highslide-play a.disabled span {
-       background-position: 0 -160px;
-}
-.text-controls .highslide-pause span {
-       background-position: 0 -80px;
-}
-.text-controls .highslide-next span {
-       background-position: 0 -100px;
-}
-.text-controls .highslide-next a.disabled {
-       background-position: left top !important;
-}
-.text-controls .highslide-next a.disabled span {
-       background-position: 0 -200px;
-}
-.text-controls .highslide-full-expand span {
-       background: none;
-}
-.text-controls .highslide-full-expand a.disabled {
-       background-position: left top !important;
-}
-.text-controls .highslide-close span {
-       background-position: 0 -120px;
-}
-
-
-/*****************************************************************************/
-/* Styles for the thumbstrip.                                                                       */
-/* See www.highslide.com/ref/hs.addSlideshow                                                            */
-/* You can safely remove this if you don't use a thumbstrip                             */
-/*****************************************************************************/
-
-.highslide-thumbstrip {
-       height: 100%;
-       direction: ltr;
-}
-.highslide-thumbstrip div {
-       overflow: hidden;
-}
-.highslide-thumbstrip table {
-       position: relative;
-       padding: 0;
-       border-collapse: collapse;
-}
-.highslide-thumbstrip td {
-       padding: 1px;
-       /*text-align: center;*/
-}
-.highslide-thumbstrip a {
-       outline: none;
-}
-.highslide-thumbstrip img {
-       display: block;
-       border: 1px solid gray;
-       margin: 0 auto;
-}
-.highslide-thumbstrip .highslide-active-anchor img {
-       visibility: visible;
-}
-.highslide-thumbstrip .highslide-marker {
-       position: absolute;
-       width: 0;
-       height: 0;
-       border-width: 0;
-       border-style: solid;
-       border-color: transparent; /* change this to actual background color in highslide-ie6.css */
-}
-.highslide-thumbstrip-horizontal div {
-       width: auto;
-       /* width: 100% breaks in small strips in IE */
-}
-.highslide-thumbstrip-horizontal .highslide-scroll-up {
-       display: none;
-       position: absolute;
-       top: 3px;
-       left: 3px;
-       width: 25px;
-       height: 42px;
-}
-.highslide-thumbstrip-horizontal .highslide-scroll-up div {
-       margin-bottom: 10px;
-       cursor: pointer;
-       background: url(graphics/scrollarrows.png) left center no-repeat;
-       height: 42px;
-}
-.highslide-thumbstrip-horizontal .highslide-scroll-down {
-       display: none;
-       position: absolute;
-       top: 3px;
-       right: 3px;
-       width: 25px;
-       height: 42px;
-}
-.highslide-thumbstrip-horizontal .highslide-scroll-down div {
-       margin-bottom: 10px;
-       cursor: pointer;
-       background: url(graphics/scrollarrows.png) center right no-repeat;
-       height: 42px;
-}
-.highslide-thumbstrip-horizontal table {
-       margin: 2px 0 10px 0;
-}
-.highslide-viewport .highslide-thumbstrip-horizontal table {
-       margin-left: 10px;
-}
-.highslide-thumbstrip-horizontal img {
-       width: auto;
-       height: 40px;
-}
-.highslide-thumbstrip-horizontal .highslide-marker {
-       top: 47px;
-       border-left-width: 6px;
-       border-right-width: 6px;
-       border-bottom: 6px solid gray;
-}
-.highslide-viewport .highslide-thumbstrip-horizontal .highslide-marker {
-       margin-left: 10px;
-}
-.dark .highslide-thumbstrip-horizontal .highslide-marker, .highslide-viewport .highslide-thumbstrip-horizontal .highslide-marker {
-       border-bottom-color: white !important;
-}
-
-.highslide-thumbstrip-vertical-overlay {
-       overflow: hidden !important;
-}
-.highslide-thumbstrip-vertical div {
-       height: 100%;
-}
-.highslide-thumbstrip-vertical a {
-       display: block;
-}
-.highslide-thumbstrip-vertical .highslide-scroll-up {
-       display: none;
-       position: absolute;
-       top: 0;
-       left: 0;
-       width: 100%;
-       height: 25px;
-}
-.highslide-thumbstrip-vertical .highslide-scroll-up div {
-       margin-left: 10px;
-       cursor: pointer;
-       background: url(graphics/scrollarrows.png) top center no-repeat;
-       height: 25px;
-}
-.highslide-thumbstrip-vertical .highslide-scroll-down {
-       display: none;
-       position: absolute;
-       bottom: 0;
-       left: 0;
-       width: 100%;
-       height: 25px;
-}
-.highslide-thumbstrip-vertical .highslide-scroll-down div {
-       margin-left: 10px;
-       cursor: pointer;
-       background: url(graphics/scrollarrows.png) bottom center no-repeat;
-       height: 25px;
-}
-.highslide-thumbstrip-vertical table {
-       margin: 10px 0 0 10px;
-}
-.highslide-thumbstrip-vertical img {
-       width: 60px; /* t=5481 */
-}
-.highslide-thumbstrip-vertical .highslide-marker {
-       left: 0;
-       margin-top: 8px;
-       border-top-width: 6px;
-       border-bottom-width: 6px;
-       border-left: 6px solid gray;
-}
-.dark .highslide-thumbstrip-vertical .highslide-marker, .highslide-viewport .highslide-thumbstrip-vertical .highslide-marker {
-       border-left-color: white;
-}
-
-.highslide-viewport .highslide-thumbstrip-float {
-       overflow: auto;
-}
-.highslide-thumbstrip-float ul {
-       margin: 2px 0;
-       padding: 0;
-}
-.highslide-thumbstrip-float li {
-       display: block;
-       height: 60px;
-       margin: 0 2px;
-       list-style: none;
-       float: left;
-}
-.highslide-thumbstrip-float img {
-       display: inline;
-       border-color: silver;
-       max-height: 56px;
-}
-.highslide-thumbstrip-float .highslide-active-anchor img {
-       border-color: black;
-}
-.highslide-thumbstrip-float .highslide-scroll-up div, .highslide-thumbstrip-float .highslide-scroll-down div {
-       display: none;
-}
-.highslide-thumbstrip-float .highslide-marker {
-       display: none;
-}
diff --git a/gal/highslide/nav/back.png b/gal/highslide/nav/back.png
deleted file mode 100644 (file)
index 501909c..0000000
Binary files a/gal/highslide/nav/back.png and /dev/null differ
diff --git a/gal/highslide/nav/next.png b/gal/highslide/nav/next.png
deleted file mode 100644 (file)
index bdd1011..0000000
Binary files a/gal/highslide/nav/next.png and /dev/null differ
diff --git a/gal/highslide/nav/prev.png b/gal/highslide/nav/prev.png
deleted file mode 100644 (file)
index 68b2514..0000000
Binary files a/gal/highslide/nav/prev.png and /dev/null differ
diff --git a/gal/nrt-blue/back.png b/gal/nrt-blue/back.png
deleted file mode 100644 (file)
index 501909c..0000000
Binary files a/gal/nrt-blue/back.png and /dev/null differ
diff --git a/gal/nrt-blue/back.xcf b/gal/nrt-blue/back.xcf
deleted file mode 100644 (file)
index 5d8848d..0000000
Binary files a/gal/nrt-blue/back.xcf and /dev/null differ
diff --git a/gal/nrt-blue/bot.png b/gal/nrt-blue/bot.png
deleted file mode 100644 (file)
index 86bb5f2..0000000
Binary files a/gal/nrt-blue/bot.png and /dev/null differ
diff --git a/gal/nrt-blue/bot.xcf b/gal/nrt-blue/bot.xcf
deleted file mode 100644 (file)
index a3dbedc..0000000
Binary files a/gal/nrt-blue/bot.xcf and /dev/null differ
diff --git a/gal/nrt-blue/left.png b/gal/nrt-blue/left.png
deleted file mode 100644 (file)
index a9852c7..0000000
Binary files a/gal/nrt-blue/left.png and /dev/null differ
diff --git a/gal/nrt-blue/left.xcf b/gal/nrt-blue/left.xcf
deleted file mode 100644 (file)
index 76912ed..0000000
Binary files a/gal/nrt-blue/left.xcf and /dev/null differ
diff --git a/gal/nrt-blue/next.png b/gal/nrt-blue/next.png
deleted file mode 100644 (file)
index bdd1011..0000000
Binary files a/gal/nrt-blue/next.png and /dev/null differ
diff --git a/gal/nrt-blue/next.xcf b/gal/nrt-blue/next.xcf
deleted file mode 100644 (file)
index ad06333..0000000
Binary files a/gal/nrt-blue/next.xcf and /dev/null differ
diff --git a/gal/nrt-blue/prev.png b/gal/nrt-blue/prev.png
deleted file mode 100644 (file)
index 68b2514..0000000
Binary files a/gal/nrt-blue/prev.png and /dev/null differ
diff --git a/gal/nrt-blue/prev.xcf b/gal/nrt-blue/prev.xcf
deleted file mode 100644 (file)
index dbf35d5..0000000
Binary files a/gal/nrt-blue/prev.xcf and /dev/null differ
diff --git a/gal/nrt-blue/right.png b/gal/nrt-blue/right.png
deleted file mode 100644 (file)
index 7af66ba..0000000
Binary files a/gal/nrt-blue/right.png and /dev/null differ
diff --git a/gal/nrt-blue/right.xcf b/gal/nrt-blue/right.xcf
deleted file mode 100644 (file)
index 0668d05..0000000
Binary files a/gal/nrt-blue/right.xcf and /dev/null differ
diff --git a/gal/nrt-blue/style.css b/gal/nrt-blue/style.css
deleted file mode 100644 (file)
index de50720..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-.thf    { margin: 0 0 0 0; padding: 0 0 0 0; float: left; border: none; }
-.thumb  { position: relative; width: 154px; height: 134px; background-color: black; }
-.tt     { position: absolute; top: 0; left: 0; }
-.tl     { position: absolute; top: 14px; left: 0; }
-.ti     { position: absolute; }
-.tr     { position: absolute; top: 14px; right: 0; }
-.tb     { position: absolute; bottom: 0; right: 0; }
-IMG     { border: none; }
-H1      { text-align: center; }
-H2      { text-align: center; margin-bottom: 3ex; }
-P      { clear: both; }
-H2     { clear: both; }
-.parent        { text-align: center; }
-.large { text-align: center; }
-.back  { float: left; }
-.fwd   { float: right; }
-A[href]:hover { background-color: #11001d; }
diff --git a/gal/nrt-blue/top.png b/gal/nrt-blue/top.png
deleted file mode 100644 (file)
index db5a677..0000000
Binary files a/gal/nrt-blue/top.png and /dev/null differ
diff --git a/gal/nrt-blue/top.xcf b/gal/nrt-blue/top.xcf
deleted file mode 100644 (file)
index 8298369..0000000
Binary files a/gal/nrt-blue/top.xcf and /dev/null differ
diff --git a/gal/plain/back.png b/gal/plain/back.png
deleted file mode 100644 (file)
index 501909c..0000000
Binary files a/gal/plain/back.png and /dev/null differ
diff --git a/gal/plain/next.png b/gal/plain/next.png
deleted file mode 100644 (file)
index bdd1011..0000000
Binary files a/gal/plain/next.png and /dev/null differ
diff --git a/gal/plain/prev.png b/gal/plain/prev.png
deleted file mode 100644 (file)
index 68b2514..0000000
Binary files a/gal/plain/prev.png and /dev/null differ
diff --git a/gal/plain/style.css b/gal/plain/style.css
deleted file mode 100644 (file)
index 4e86e31..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-.thf    { margin: 0 0 0 0; padding: 0 0 0 0; float: left; border: none; }
-.thumb  { position: relative; width: 130px; height: 110px; }
-.ti     { position: absolute; border: 1px solid #505; }
-IMG     { border: none; }
-H1      { text-align: center; }
-H2      { text-align: center; margin-bottom: 3ex; }
-P      { clear: both; }
-H2     { clear: both; }
-.parent        { text-align: center; }
-.large { text-align: center; }
-.back  { float: left; }
-.fwd   { float: right; }
-A[href]:hover { background-color: #11001d; }
diff --git a/highslide/custom.css b/highslide/custom.css
new file mode 100644 (file)
index 0000000..6ef8df9
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ *     Site-specific stylesheet for Highslide JS gallery theme
+ */
+
+H1      { text-align: center; }
+H2      { text-align: center; margin-bottom: 3ex; }
+.parent        { text-align: center; }
+.large { text-align: center; }
+
+.fwd {
+       float: right;
+       width: 48px;
+       height: 48px;
+}
+.back {
+       float: left;
+       width: 48px;
+       height: 48px;
+}
+.up { 
+       width: 48px;
+       height: 48px;
+}
+
+A[href]:hover { background-color: inherit; }
+
+.highslide-wrapper, .highslide-outline {
+       background: #111111;
+}
+.highslide img {
+       border: 1px solid #D0D0D0;
+}
+.highslide:hover img {
+       border-color: #A0A0A0;
+}
+.highslide-active-anchor img {
+       visibility: visible;
+       border-color: #808080 !important;
+}
+.highslide-image {
+       border: 2px solid #111111;
+}
+.highslide-caption {
+       color: #CCCCCC;
+       padding: 2px;
+}
+.highslide-loading {
+       color: black;
+       border: 1px solid black;
+       background-color: white;
+       background-image: url(graphics/loader.white.gif);
+}
+.highslide-controls {
+       position: static !important;
+       margin: 0;
+       width: 120px !important;
+}
+.highslide-gallery ul li {
+       width: 130px;
+       height: 110px;
+       border: 1px solid #505;
+       margin: 2px;
+}
+.highslide-dimming {
+       background: black;
+}
diff --git a/highslide/custom.js b/highslide/custom.js
new file mode 100644 (file)
index 0000000..ae76993
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ *     Site-specific configuration settings for Highslide JS
+ */
+
+hs.showCredits = true;
+hs.creditsPosition = 'top right';
+hs.outlineType = 'rounded-black';
+hs.fadeInOut = false;
+hs.align = 'center';
+hs.useBox = true;
+hs.width = 1080;
+hs.height = 840;
+// hs.allowMultipleInstances = false;
+hs.captionEval = 'this.thumb.title';
+hs.captionOverlay = { position: "top" };
+hs.expandDuration = 100;
+hs.restoreDuration = 100;
+hs.dimmingDuration = 100;
+hs.dimmingOpacity = 0.8;
+hs.transitionDuration = 100;
+hs.thumbnailId = 'thumb1';
+hs.numberPosition = 'caption';
+hs.transitions = ['expand', 'crossfade'];
+
+hs.addSlideshow({
+       interval: 5000,
+       repeat: false,
+       useControls: true,
+       fixedControls: 'fit',
+       overlayOptions: {
+               className: 'controls-in-heading',
+               opacity: 0.6,
+               position: 'top center',
+               hideOnMouseOut: true
+       },
+       thumbstrip: {
+               mode: 'horizontal',
+               position: 'below',
+               relativeTo: 'expander'
+       }
+});
+
+
+////////////////////////////////////////////////
+// Dynamic change of the hash part of the URL //
+////////////////////////////////////////////////
+var hashTag = '';
+// Use hashDelimiter to "hide" name of the anchor (and to not scroll the page)
+var hashDelimiter = '_';
+
+hs.extend (hs.Expander.prototype, {
+       onAfterExpand: function(sender) {
+               hashTag = this.a.id;
+               window.location.hash = hashDelimiter + hashTag;
+       },
+
+       onBeforeClose: function(sender) {
+               window.location.hash = '';
+       }
+});
+
+function showDefaultImage() {
+       var hashParts = window.location.hash.split(hashDelimiter);
+       var myThumb = document.getElementById(hashParts[1])
+       if (hashParts[1] && myThumb) myThumb.click();
+       else window.location.hash = '';
+}
+// If the new hash is not equal to the hash we store internally,
+// then it must be the user hitting the "back/forward" button
+function checkHashChange() {
+       var hashParts = window.location.hash.split(hashDelimiter);
+       if (hashParts[1] != hashTag) {
+               hs.close();
+               window.location.hash = '';
+       }
+}
+
+// Add onLoad and onHashChange functions
+if(window.onload) {
+       var current = window.onload;
+       var newAction = function() {
+               current();
+               showDefaultImage();
+       };
+       window.onload = newAction;
+} else window.onload = showDefaultImage;
+
+if(window.onhashchange) {
+       var current = window.onHashChange;
+       var newAction = function() {
+               current();
+               checkHashChange();
+       };
+       window.onhashchange = newAction;
+} else window.onhashchange = checkHashChange;
diff --git a/highslide/graphics/close.png b/highslide/graphics/close.png
new file mode 100644 (file)
index 0000000..4de4396
Binary files /dev/null and b/highslide/graphics/close.png differ
diff --git a/highslide/graphics/closeX.png b/highslide/graphics/closeX.png
new file mode 100644 (file)
index 0000000..cf5d018
Binary files /dev/null and b/highslide/graphics/closeX.png differ
diff --git a/highslide/graphics/controlbar-black-border.gif b/highslide/graphics/controlbar-black-border.gif
new file mode 100644 (file)
index 0000000..e2403fe
Binary files /dev/null and b/highslide/graphics/controlbar-black-border.gif differ
diff --git a/highslide/graphics/controlbar-text-buttons.png b/highslide/graphics/controlbar-text-buttons.png
new file mode 100644 (file)
index 0000000..d2f72e0
Binary files /dev/null and b/highslide/graphics/controlbar-text-buttons.png differ
diff --git a/highslide/graphics/controlbar-white-small.gif b/highslide/graphics/controlbar-white-small.gif
new file mode 100644 (file)
index 0000000..462fce7
Binary files /dev/null and b/highslide/graphics/controlbar-white-small.gif differ
diff --git a/highslide/graphics/controlbar-white.gif b/highslide/graphics/controlbar-white.gif
new file mode 100644 (file)
index 0000000..1f143f5
Binary files /dev/null and b/highslide/graphics/controlbar-white.gif differ
diff --git a/highslide/graphics/controlbar2.gif b/highslide/graphics/controlbar2.gif
new file mode 100644 (file)
index 0000000..39ad652
Binary files /dev/null and b/highslide/graphics/controlbar2.gif differ
diff --git a/highslide/graphics/controlbar3.gif b/highslide/graphics/controlbar3.gif
new file mode 100644 (file)
index 0000000..3eebb81
Binary files /dev/null and b/highslide/graphics/controlbar3.gif differ
diff --git a/highslide/graphics/controlbar4-hover.gif b/highslide/graphics/controlbar4-hover.gif
new file mode 100644 (file)
index 0000000..ca08b59
Binary files /dev/null and b/highslide/graphics/controlbar4-hover.gif differ
diff --git a/highslide/graphics/controlbar4.gif b/highslide/graphics/controlbar4.gif
new file mode 100644 (file)
index 0000000..7a3ad34
Binary files /dev/null and b/highslide/graphics/controlbar4.gif differ
diff --git a/highslide/graphics/fullexpand.gif b/highslide/graphics/fullexpand.gif
new file mode 100644 (file)
index 0000000..26d9ed0
Binary files /dev/null and b/highslide/graphics/fullexpand.gif differ
diff --git a/highslide/graphics/geckodimmer.png b/highslide/graphics/geckodimmer.png
new file mode 100644 (file)
index 0000000..309bb27
Binary files /dev/null and b/highslide/graphics/geckodimmer.png differ
diff --git a/highslide/graphics/icon.gif b/highslide/graphics/icon.gif
new file mode 100644 (file)
index 0000000..b74a073
Binary files /dev/null and b/highslide/graphics/icon.gif differ
diff --git a/highslide/graphics/loader.big.black.gif b/highslide/graphics/loader.big.black.gif
new file mode 100644 (file)
index 0000000..c95d05a
Binary files /dev/null and b/highslide/graphics/loader.big.black.gif differ
diff --git a/highslide/graphics/loader.big.white.gif b/highslide/graphics/loader.big.white.gif
new file mode 100644 (file)
index 0000000..3288d10
Binary files /dev/null and b/highslide/graphics/loader.big.white.gif differ
diff --git a/highslide/graphics/loader.black.gif b/highslide/graphics/loader.black.gif
new file mode 100644 (file)
index 0000000..0b31f6f
Binary files /dev/null and b/highslide/graphics/loader.black.gif differ
diff --git a/highslide/graphics/loader.white.gif b/highslide/graphics/loader.white.gif
new file mode 100644 (file)
index 0000000..f2a1bc0
Binary files /dev/null and b/highslide/graphics/loader.white.gif differ
diff --git a/highslide/graphics/outlines/beveled.png b/highslide/graphics/outlines/beveled.png
new file mode 100644 (file)
index 0000000..fc428f4
Binary files /dev/null and b/highslide/graphics/outlines/beveled.png differ
diff --git a/highslide/graphics/outlines/custom.png b/highslide/graphics/outlines/custom.png
new file mode 100644 (file)
index 0000000..2f20f70
Binary files /dev/null and b/highslide/graphics/outlines/custom.png differ
diff --git a/highslide/graphics/outlines/drop-shadow.png b/highslide/graphics/outlines/drop-shadow.png
new file mode 100644 (file)
index 0000000..0186c2e
Binary files /dev/null and b/highslide/graphics/outlines/drop-shadow.png differ
diff --git a/highslide/graphics/outlines/glossy-dark.png b/highslide/graphics/outlines/glossy-dark.png
new file mode 100644 (file)
index 0000000..3c64c0d
Binary files /dev/null and b/highslide/graphics/outlines/glossy-dark.png differ
diff --git a/highslide/graphics/outlines/outer-glow.png b/highslide/graphics/outlines/outer-glow.png
new file mode 100644 (file)
index 0000000..288d43f
Binary files /dev/null and b/highslide/graphics/outlines/outer-glow.png differ
diff --git a/highslide/graphics/outlines/rounded-black.png b/highslide/graphics/outlines/rounded-black.png
new file mode 100644 (file)
index 0000000..a77e65d
Binary files /dev/null and b/highslide/graphics/outlines/rounded-black.png differ
diff --git a/highslide/graphics/outlines/rounded-white.png b/highslide/graphics/outlines/rounded-white.png
new file mode 100644 (file)
index 0000000..0d4b817
Binary files /dev/null and b/highslide/graphics/outlines/rounded-white.png differ
diff --git a/highslide/graphics/resize.gif b/highslide/graphics/resize.gif
new file mode 100644 (file)
index 0000000..9100de7
Binary files /dev/null and b/highslide/graphics/resize.gif differ
diff --git a/highslide/graphics/scrollarrows.png b/highslide/graphics/scrollarrows.png
new file mode 100644 (file)
index 0000000..b3d5575
Binary files /dev/null and b/highslide/graphics/scrollarrows.png differ
diff --git a/highslide/graphics/zoom.png b/highslide/graphics/zoom.png
new file mode 100644 (file)
index 0000000..908612e
Binary files /dev/null and b/highslide/graphics/zoom.png differ
diff --git a/highslide/graphics/zoomin.cur b/highslide/graphics/zoomin.cur
new file mode 100644 (file)
index 0000000..cb79124
Binary files /dev/null and b/highslide/graphics/zoomin.cur differ
diff --git a/highslide/graphics/zoomout.cur b/highslide/graphics/zoomout.cur
new file mode 100644 (file)
index 0000000..acf6199
Binary files /dev/null and b/highslide/graphics/zoomout.cur differ
diff --git a/highslide/highslide-ie6.css b/highslide/highslide-ie6.css
new file mode 100644 (file)
index 0000000..b4d5484
--- /dev/null
@@ -0,0 +1,76 @@
+.closebutton {
+    /* NOTE! This URL is relative to the HTML page, not the CSS */
+       filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(
+               src='../highslide/graphics/close.png', sizingMethod='scale');
+
+       background: none;
+       cursor: hand;
+}
+
+/* Viewport fixed hack */
+.highslide-viewport {
+       position: absolute;
+    left: expression( ( ( ignoreMe1 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' );
+       top: expression( ( ignoreMe2 = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ) + 'px' );
+       width: expression( ( ( ignoreMe3 = document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth ) ) + 'px' );
+       height: expression( ( ( ignoreMe4 = document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight ) ) + 'px' );
+}
+
+/* Thumbstrip PNG fix */
+.highslide-scroll-down, .highslide-scroll-up {
+       position: relative;
+       overflow: hidden;
+}
+.highslide-scroll-down div, .highslide-scroll-up div {
+       /* NOTE! This URL is relative to the HTML page, not the CSS */
+       filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(
+               src='../highslide/graphics/scrollarrows.png', sizingMethod='scale');
+       background: none !important;
+       position: absolute;
+       cursor: hand;
+       width: 75px;
+       height: 75px !important;
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-down div {
+       left: -50px;
+       top: -15px;
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-up div {
+       top: -15px;
+}
+.highslide-thumbstrip-vertical .highslide-scroll-down div {
+       top: -50px;
+}
+
+/* Thumbstrip marker arrow trasparent background fix */
+.highslide-thumbstrip .highslide-marker {
+       border-color: white; /* match the background */
+}
+.dark .highslide-thumbstrip-horizontal .highslide-marker {
+       border-color: #111;
+}
+.highslide-viewport .highslide-marker {
+       border-color: #333;
+}
+.highslide-thumbstrip {
+       float: left;
+}
+
+/* Positioning fixes for the control bar */
+.text-controls .highslide-controls {
+       width: 480px;
+}
+.text-controls a span {
+       width: 4em;
+}
+.text-controls .highslide-full-expand a span {
+       width: 0;
+}
+.text-controls .highslide-close a span {
+       width: 0;
+}
+
+/* Special */
+.in-page .highslide-thumbstrip-horizontal .highslide-marker {
+    border-bottom: gray;
+}
diff --git a/highslide/highslide-with-gallery.js b/highslide/highslide-with-gallery.js
new file mode 100644 (file)
index 0000000..5576e5a
--- /dev/null
@@ -0,0 +1,2687 @@
+/**
+ * Name:    Highslide JS
+ * Version: 4.1.13 (2011-10-06)
+ * Config:  default +events +slideshow +positioning +transitions +viewport +thumbstrip
+ * Author:  Torstein Hønsi
+ * Support: www.highslide.com/support
+ * License: www.highslide.com/#license
+ */
+if (!hs) { var hs = {
+// Language strings
+lang : {
+       cssDirection: 'ltr',
+       loadingText : 'Loading...',
+       loadingTitle : 'Click to cancel',
+       focusTitle : 'Click to bring to front',
+       fullExpandTitle : 'Expand to actual size (f)',
+       creditsText : 'Powered by <i>Highslide JS</i>',
+       creditsTitle : 'Go to the Highslide JS homepage',
+       previousText : 'Previous',
+       nextText : 'Next',
+       moveText : 'Move',
+       closeText : 'Close',
+       closeTitle : 'Close (esc)',
+       resizeTitle : 'Resize',
+       playText : 'Play',
+       playTitle : 'Play slideshow (spacebar)',
+       pauseText : 'Pause',
+       pauseTitle : 'Pause slideshow (spacebar)',
+       previousTitle : 'Previous (arrow left)',
+       nextTitle : 'Next (arrow right)',
+       moveTitle : 'Move',
+       fullExpandText : '1:1',
+       number: 'Image %1 of %2',
+       restoreTitle : 'Click to close image, click and drag to move. Use arrow keys for next and previous.'
+},
+// See http://highslide.com/ref for examples of settings
+graphicsDir : 'highslide/graphics/',
+expandCursor : 'zoomin.cur', // null disables
+restoreCursor : 'zoomout.cur', // null disables
+expandDuration : 250, // milliseconds
+restoreDuration : 250,
+marginLeft : 15,
+marginRight : 15,
+marginTop : 15,
+marginBottom : 15,
+zIndexCounter : 1001, // adjust to other absolutely positioned elements
+loadingOpacity : 0.75,
+allowMultipleInstances: true,
+numberOfImagesToPreload : 5,
+outlineWhileAnimating : 2, // 0 = never, 1 = always, 2 = HTML only
+outlineStartOffset : 3, // ends at 10
+padToMinWidth : false, // pad the popup width to make room for wide caption
+fullExpandPosition : 'bottom right',
+fullExpandOpacity : 1,
+showCredits : true, // you can set this to false if you want
+creditsHref : 'http://highslide.com/',
+creditsTarget : '_self',
+enableKeyListener : true,
+openerTagNames : ['a'], // Add more to allow slideshow indexing
+transitions : [],
+transitionDuration: 250,
+dimmingOpacity: 0, // Lightbox style dimming background
+dimmingDuration: 50, // 0 for instant dimming
+
+anchor : 'auto', // where the image expands from
+align : 'auto', // position in the client (overrides anchor)
+targetX: null, // the id of a target element
+targetY: null,
+dragByHeading: true,
+minWidth: 200,
+minHeight: 200,
+allowSizeReduction: true, // allow the image to reduce to fit client size. If false, this overrides minWidth and minHeight
+outlineType : 'drop-shadow', // set null to disable outlines
+skin : {
+       controls:
+               '<div class="highslide-controls"><ul>'+
+                       '<li class="highslide-previous">'+
+                               '<a href="#" title="{hs.lang.previousTitle}">'+
+                               '<span>{hs.lang.previousText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-play">'+
+                               '<a href="#" title="{hs.lang.playTitle}">'+
+                               '<span>{hs.lang.playText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-pause">'+
+                               '<a href="#" title="{hs.lang.pauseTitle}">'+
+                               '<span>{hs.lang.pauseText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-next">'+
+                               '<a href="#" title="{hs.lang.nextTitle}">'+
+                               '<span>{hs.lang.nextText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-move">'+
+                               '<a href="#" title="{hs.lang.moveTitle}">'+
+                               '<span>{hs.lang.moveText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-full-expand">'+
+                               '<a href="#" title="{hs.lang.fullExpandTitle}">'+
+                               '<span>{hs.lang.fullExpandText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-close">'+
+                               '<a href="#" title="{hs.lang.closeTitle}" >'+
+                               '<span>{hs.lang.closeText}</span></a>'+
+                       '</li>'+
+               '</ul></div>'
+},
+// END OF YOUR SETTINGS
+
+
+// declare internal properties
+preloadTheseImages : [],
+continuePreloading: true,
+expanders : [],
+overrides : [
+       'allowSizeReduction',
+       'useBox',
+       'anchor',
+       'align',
+       'targetX',
+       'targetY',
+       'outlineType',
+       'outlineWhileAnimating',
+       'captionId',
+       'captionText',
+       'captionEval',
+       'captionOverlay',
+       'headingId',
+       'headingText',
+       'headingEval',
+       'headingOverlay',
+       'creditsPosition',
+       'dragByHeading',
+       'autoplay',
+       'numberPosition',
+       'transitions',
+       'dimmingOpacity',
+
+       'width',
+       'height',
+
+       'wrapperClassName',
+       'minWidth',
+       'minHeight',
+       'maxWidth',
+       'maxHeight',
+       'pageOrigin',
+       'slideshowGroup',
+       'easing',
+       'easingClose',
+       'fadeInOut',
+       'src'
+],
+overlays : [],
+idCounter : 0,
+oPos : {
+       x: ['leftpanel', 'left', 'center', 'right', 'rightpanel'],
+       y: ['above', 'top', 'middle', 'bottom', 'below']
+},
+mouse: {},
+headingOverlay: {},
+captionOverlay: {},
+timers : [],
+
+slideshows : [],
+
+pendingOutlines : {},
+clones : {},
+onReady: [],
+uaVersion: /Trident\/4\.0/.test(navigator.userAgent) ? 8 :
+       parseFloat((navigator.userAgent.toLowerCase().match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1]),
+ie : (document.all && !window.opera),
+//ie : navigator && /MSIE [678]/.test(navigator.userAgent), // ie9 compliant?
+safari : /Safari/.test(navigator.userAgent),
+geckoMac : /Macintosh.+rv:1\.[0-8].+Gecko/.test(navigator.userAgent),
+
+$ : function (id) {
+       if (id) return document.getElementById(id);
+},
+
+push : function (arr, val) {
+       arr[arr.length] = val;
+},
+
+createElement : function (tag, attribs, styles, parent, nopad) {
+       var el = document.createElement(tag);
+       if (attribs) hs.extend(el, attribs);
+       if (nopad) hs.setStyles(el, {padding: 0, border: 'none', margin: 0});
+       if (styles) hs.setStyles(el, styles);
+       if (parent) parent.appendChild(el);
+       return el;
+},
+
+extend : function (el, attribs) {
+       for (var x in attribs) el[x] = attribs[x];
+       return el;
+},
+
+setStyles : function (el, styles) {
+       for (var x in styles) {
+               if (hs.ieLt9 && x == 'opacity') {
+                       if (styles[x] > 0.99) el.style.removeAttribute('filter');
+                       else el.style.filter = 'alpha(opacity='+ (styles[x] * 100) +')';
+               }
+               else el.style[x] = styles[x];
+       }
+},
+animate: function(el, prop, opt) {
+       var start,
+               end,
+               unit;
+       if (typeof opt != 'object' || opt === null) {
+               var args = arguments;
+               opt = {
+                       duration: args[2],
+                       easing: args[3],
+                       complete: args[4]
+               };
+       }
+       if (typeof opt.duration != 'number') opt.duration = 250;
+       opt.easing = Math[opt.easing] || Math.easeInQuad;
+       opt.curAnim = hs.extend({}, prop);
+       for (var name in prop) {
+               var e = new hs.fx(el, opt , name );
+
+               start = parseFloat(hs.css(el, name)) || 0;
+               end = parseFloat(prop[name]);
+               unit = name != 'opacity' ? 'px' : '';
+
+               e.custom( start, end, unit );
+       }
+},
+css: function(el, prop) {
+       if (el.style[prop]) {
+               return el.style[prop];
+       } else if (document.defaultView) {
+               return document.defaultView.getComputedStyle(el, null).getPropertyValue(prop);
+
+       } else {
+               if (prop == 'opacity') prop = 'filter';
+               var val = el.currentStyle[prop.replace(/\-(\w)/g, function (a, b){ return b.toUpperCase(); })];
+               if (prop == 'filter')
+                       val = val.replace(/alpha\(opacity=([0-9]+)\)/,
+                               function (a, b) { return b / 100 });
+               return val === '' ? 1 : val;
+       }
+},
+
+getPageSize : function () {
+       var d = document, w = window, iebody = d.compatMode && d.compatMode != 'BackCompat'
+               ? d.documentElement : d.body,
+               ieLt9 = hs.ie && (hs.uaVersion < 9 || typeof pageXOffset == 'undefined');
+
+       var width = ieLt9 ? iebody.clientWidth :
+                       (d.documentElement.clientWidth || self.innerWidth),
+               height = ieLt9 ? iebody.clientHeight : self.innerHeight;
+       hs.page = {
+               width: width,
+               height: height,
+               scrollLeft: ieLt9 ? iebody.scrollLeft : pageXOffset,
+               scrollTop: ieLt9 ? iebody.scrollTop : pageYOffset
+       };
+       return hs.page;
+},
+
+getPosition : function(el)     {
+       var p = { x: el.offsetLeft, y: el.offsetTop };
+       while (el.offsetParent) {
+               el = el.offsetParent;
+               p.x += el.offsetLeft;
+               p.y += el.offsetTop;
+               if (el != document.body && el != document.documentElement) {
+                       p.x -= el.scrollLeft;
+                       p.y -= el.scrollTop;
+               }
+       }
+       return p;
+},
+
+expand : function(a, params, custom, type) {
+       if (!a) a = hs.createElement('a', null, { display: 'none' }, hs.container);
+       if (typeof a.getParams == 'function') return params;
+       try {
+               new hs.Expander(a, params, custom);
+               return false;
+       } catch (e) { return true; }
+},
+getElementByClass : function (el, tagName, className) {
+       var els = el.getElementsByTagName(tagName);
+       for (var i = 0; i < els.length; i++) {
+               if ((new RegExp(className)).test(els[i].className)) {
+                       return els[i];
+               }
+       }
+       return null;
+},
+replaceLang : function(s) {
+       s = s.replace(/\s/g, ' ');
+       var re = /{hs\.lang\.([^}]+)\}/g,
+               matches = s.match(re),
+               lang;
+       if (matches) for (var i = 0; i < matches.length; i++) {
+               lang = matches[i].replace(re, "$1");
+               if (typeof hs.lang[lang] != 'undefined') s = s.replace(matches[i], hs.lang[lang]);
+       }
+       return s;
+},
+
+
+focusTopmost : function() {
+       var topZ = 0,
+               topmostKey = -1,
+               expanders = hs.expanders,
+               exp,
+               zIndex;
+       for (var i = 0; i < expanders.length; i++) {
+               exp = expanders[i];
+               if (exp) {
+                       zIndex = exp.wrapper.style.zIndex;
+                       if (zIndex && zIndex > topZ) {
+                               topZ = zIndex;
+                               topmostKey = i;
+                       }
+               }
+       }
+       if (topmostKey == -1) hs.focusKey = -1;
+       else expanders[topmostKey].focus();
+},
+
+getParam : function (a, param) {
+       a.getParams = a.onclick;
+       var p = a.getParams ? a.getParams() : null;
+       a.getParams = null;
+
+       return (p && typeof p[param] != 'undefined') ? p[param] :
+               (typeof hs[param] != 'undefined' ? hs[param] : null);
+},
+
+getSrc : function (a) {
+       var src = hs.getParam(a, 'src');
+       if (src) return src;
+       return a.href;
+},
+
+getNode : function (id) {
+       var node = hs.$(id), clone = hs.clones[id], a = {};
+       if (!node && !clone) return null;
+       if (!clone) {
+               clone = node.cloneNode(true);
+               clone.id = '';
+               hs.clones[id] = clone;
+               return node;
+       } else {
+               return clone.cloneNode(true);
+       }
+},
+
+discardElement : function(d) {
+       if (d) hs.garbageBin.appendChild(d);
+       hs.garbageBin.innerHTML = '';
+},
+dim : function(exp) {
+       if (!hs.dimmer) {
+               isNew = true;
+               hs.dimmer = hs.createElement ('div', {
+                               className: 'highslide-dimming highslide-viewport-size',
+                               owner: '',
+                               onclick: function() {
+                                       if (hs.fireEvent(hs, 'onDimmerClick'))
+
+                                               hs.close();
+                               }
+                       }, {
+                               visibility: 'visible',
+                               opacity: 0
+                       }, hs.container, true);
+
+               if (/(Android|iPad|iPhone|iPod)/.test(navigator.userAgent)) {
+                       var body = document.body;
+                       function pixDimmerSize() {
+                               hs.setStyles(hs.dimmer, {
+                                       width: body.scrollWidth +'px',
+                                       height: body.scrollHeight +'px'
+                               });
+                       }
+                       pixDimmerSize();
+                       hs.addEventListener(window, 'resize', pixDimmerSize);
+               }
+       }
+       hs.dimmer.style.display = '';
+
+       var isNew = hs.dimmer.owner == '';
+       hs.dimmer.owner += '|'+ exp.key;
+
+       if (isNew) {
+               if (hs.geckoMac && hs.dimmingGeckoFix)
+                       hs.setStyles(hs.dimmer, {
+                               background: 'url('+ hs.graphicsDir + 'geckodimmer.png)',
+                               opacity: 1
+                       });
+               else
+                       hs.animate(hs.dimmer, { opacity: exp.dimmingOpacity }, hs.dimmingDuration);
+       }
+},
+undim : function(key) {
+       if (!hs.dimmer) return;
+       if (typeof key != 'undefined') hs.dimmer.owner = hs.dimmer.owner.replace('|'+ key, '');
+
+       if (
+               (typeof key != 'undefined' && hs.dimmer.owner != '')
+               || (hs.upcoming && hs.getParam(hs.upcoming, 'dimmingOpacity'))
+       ) return;
+
+       if (hs.geckoMac && hs.dimmingGeckoFix) hs.dimmer.style.display = 'none';
+       else hs.animate(hs.dimmer, { opacity: 0 }, hs.dimmingDuration, null, function() {
+               hs.dimmer.style.display = 'none';
+       });
+},
+transit : function (adj, exp) {
+       var last = exp || hs.getExpander();
+       exp = last;
+       if (hs.upcoming) return false;
+       else hs.last = last;
+       hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
+       try {
+               hs.upcoming = adj;
+               adj.onclick();
+       } catch (e){
+               hs.last = hs.upcoming = null;
+       }
+       try {
+               if (!adj || exp.transitions[1] != 'crossfade')
+               exp.close();
+       } catch (e) {}
+       return false;
+},
+
+previousOrNext : function (el, op) {
+       var exp = hs.getExpander(el);
+       if (exp) return hs.transit(exp.getAdjacentAnchor(op), exp);
+       else return false;
+},
+
+previous : function (el) {
+       return hs.previousOrNext(el, -1);
+},
+
+next : function (el) {
+       return hs.previousOrNext(el, 1);
+},
+
+keyHandler : function(e) {
+       if (!e) e = window.event;
+       if (!e.target) e.target = e.srcElement; // ie
+       if (typeof e.target.form != 'undefined') return true; // form element has focus
+       if (!hs.fireEvent(hs, 'onKeyDown', e)) return true;
+       var exp = hs.getExpander();
+
+       var op = null;
+       switch (e.keyCode) {
+               case 70: // f
+                       if (exp) exp.doFullExpand();
+                       return true;
+               case 32: // Space
+                       op = 2;
+                       break;
+               case 34: // Page Down
+               case 39: // Arrow right
+               case 40: // Arrow down
+                       op = 1;
+                       break;
+               case 8:  // Backspace
+               case 33: // Page Up
+               case 37: // Arrow left
+               case 38: // Arrow up
+                       op = -1;
+                       break;
+               case 27: // Escape
+               case 13: // Enter
+                       op = 0;
+       }
+       if (op !== null) {if (op != 2)hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
+               if (!hs.enableKeyListener) return true;
+
+               if (e.preventDefault) e.preventDefault();
+               else e.returnValue = false;
+               if (exp) {
+                       if (op == 0) {
+                               exp.close();
+                       } else if (op == 2) {
+                               if (exp.slideshow) exp.slideshow.hitSpace();
+                       } else {
+                               if (exp.slideshow) exp.slideshow.pause();
+                               hs.previousOrNext(exp.key, op);
+                       }
+                       return false;
+               }
+       }
+       return true;
+},
+
+
+registerOverlay : function (overlay) {
+       hs.push(hs.overlays, hs.extend(overlay, { hsId: 'hsId'+ hs.idCounter++ } ));
+},
+
+
+addSlideshow : function (options) {
+       var sg = options.slideshowGroup;
+       if (typeof sg == 'object') {
+               for (var i = 0; i < sg.length; i++) {
+                       var o = {};
+                       for (var x in options) o[x] = options[x];
+                       o.slideshowGroup = sg[i];
+                       hs.push(hs.slideshows, o);
+               }
+       } else {
+               hs.push(hs.slideshows, options);
+       }
+},
+
+getWrapperKey : function (element, expOnly) {
+       var el, re = /^highslide-wrapper-([0-9]+)$/;
+       // 1. look in open expanders
+       el = element;
+       while (el.parentNode)   {
+               if (el.hsKey !== undefined) return el.hsKey;
+               if (el.id && re.test(el.id)) return el.id.replace(re, "$1");
+               el = el.parentNode;
+       }
+       // 2. look in thumbnail
+       if (!expOnly) {
+               el = element;
+               while (el.parentNode)   {
+                       if (el.tagName && hs.isHsAnchor(el)) {
+                               for (var key = 0; key < hs.expanders.length; key++) {
+                                       var exp = hs.expanders[key];
+                                       if (exp && exp.a == el) return key;
+                               }
+                       }
+                       el = el.parentNode;
+               }
+       }
+       return null;
+},
+
+getExpander : function (el, expOnly) {
+       if (typeof el == 'undefined') return hs.expanders[hs.focusKey] || null;
+       if (typeof el == 'number') return hs.expanders[el] || null;
+       if (typeof el == 'string') el = hs.$(el);
+       return hs.expanders[hs.getWrapperKey(el, expOnly)] || null;
+},
+
+isHsAnchor : function (a) {
+       return (a.onclick && a.onclick.toString().replace(/\s/g, ' ').match(/hs.(htmlE|e)xpand/));
+},
+
+reOrder : function () {
+       for (var i = 0; i < hs.expanders.length; i++)
+               if (hs.expanders[i] && hs.expanders[i].isExpanded) hs.focusTopmost();
+},
+fireEvent : function (obj, evt, args) {
+       return obj && obj[evt] ? (obj[evt](obj, args) !== false) : true;
+},
+
+mouseClickHandler : function(e)
+{
+       if (!e) e = window.event;
+       if (e.button > 1) return true;
+       if (!e.target) e.target = e.srcElement;
+
+       var el = e.target;
+       while (el.parentNode
+               && !(/highslide-(image|move|html|resize)/.test(el.className)))
+       {
+               el = el.parentNode;
+       }
+       var exp = hs.getExpander(el);
+       if (exp && (exp.isClosing || !exp.isExpanded)) return true;
+
+       if (exp && e.type == 'mousedown') {
+               if (e.target.form) return true;
+               var match = el.className.match(/highslide-(image|move|resize)/);
+               if (match) {
+                       hs.dragArgs = {
+                               exp: exp ,
+                               type: match[1],
+                               left: exp.x.pos,
+                               width: exp.x.size,
+                               top: exp.y.pos,
+                               height: exp.y.size,
+                               clickX: e.clientX,
+                               clickY: e.clientY
+                       };
+
+
+                       hs.addEventListener(document, 'mousemove', hs.dragHandler);
+                       if (e.preventDefault) e.preventDefault(); // FF
+
+                       if (/highslide-(image|html)-blur/.test(exp.content.className)) {
+                               exp.focus();
+                               hs.hasFocused = true;
+                       }
+                       return false;
+               }
+       } else if (e.type == 'mouseup') {
+
+               hs.removeEventListener(document, 'mousemove', hs.dragHandler);
+
+               if (hs.dragArgs) {
+                       if (hs.styleRestoreCursor && hs.dragArgs.type == 'image')
+                               hs.dragArgs.exp.content.style.cursor = hs.styleRestoreCursor;
+                       var hasDragged = hs.dragArgs.hasDragged;
+
+                       if (!hasDragged &&!hs.hasFocused && !/(move|resize)/.test(hs.dragArgs.type)) {
+                               if (hs.fireEvent(exp, 'onImageClick'))
+                               exp.close();
+                       }
+                       else if (hasDragged || (!hasDragged && hs.hasHtmlExpanders)) {
+                               hs.dragArgs.exp.doShowHide('hidden');
+                       }
+
+                       if (hasDragged) hs.fireEvent(hs.dragArgs.exp, 'onDrop', hs.dragArgs);
+                       hs.hasFocused = false;
+                       hs.dragArgs = null;
+
+               } else if (/highslide-image-blur/.test(el.className)) {
+                       el.style.cursor = hs.styleRestoreCursor;
+               }
+       }
+       return false;
+},
+
+dragHandler : function(e)
+{
+       if (!hs.dragArgs) return true;
+       if (!e) e = window.event;
+       var a = hs.dragArgs, exp = a.exp;
+
+       a.dX = e.clientX - a.clickX;
+       a.dY = e.clientY - a.clickY;
+
+       var distance = Math.sqrt(Math.pow(a.dX, 2) + Math.pow(a.dY, 2));
+       if (!a.hasDragged) a.hasDragged = (a.type != 'image' && distance > 0)
+               || (distance > (hs.dragSensitivity || 5));
+
+       if (a.hasDragged && e.clientX > 5 && e.clientY > 5) {
+               if (!hs.fireEvent(exp, 'onDrag', a)) return false;
+
+               if (a.type == 'resize') exp.resize(a);
+               else {
+                       exp.moveTo(a.left + a.dX, a.top + a.dY);
+                       if (a.type == 'image') exp.content.style.cursor = 'move';
+               }
+       }
+       return false;
+},
+
+wrapperMouseHandler : function (e) {
+       try {
+               if (!e) e = window.event;
+               var over = /mouseover/i.test(e.type);
+               if (!e.target) e.target = e.srcElement; // ie
+               if (!e.relatedTarget) e.relatedTarget =
+                       over ? e.fromElement : e.toElement; // ie
+               var exp = hs.getExpander(e.target);
+               if (!exp.isExpanded) return;
+               if (!exp || !e.relatedTarget || hs.getExpander(e.relatedTarget, true) == exp
+                       || hs.dragArgs) return;
+               hs.fireEvent(exp, over ? 'onMouseOver' : 'onMouseOut', e);
+               for (var i = 0; i < exp.overlays.length; i++) (function() {
+                       var o = hs.$('hsId'+ exp.overlays[i]);
+                       if (o && o.hideOnMouseOut) {
+                               if (over) hs.setStyles(o, { visibility: 'visible', display: '' });
+                               hs.animate(o, { opacity: over ? o.opacity : 0 }, o.dur);
+                       }
+               })();
+       } catch (e) {}
+},
+addEventListener : function (el, event, func) {
+       if (el == document && event == 'ready') {
+               hs.push(hs.onReady, func);
+       }
+       try {
+               el.addEventListener(event, func, false);
+       } catch (e) {
+               try {
+                       el.detachEvent('on'+ event, func);
+                       el.attachEvent('on'+ event, func);
+               } catch (e) {
+                       el['on'+ event] = func;
+               }
+       }
+},
+
+removeEventListener : function (el, event, func) {
+       try {
+               el.removeEventListener(event, func, false);
+       } catch (e) {
+               try {
+                       el.detachEvent('on'+ event, func);
+               } catch (e) {
+                       el['on'+ event] = null;
+               }
+       }
+},
+
+preloadFullImage : function (i) {
+       if (hs.continuePreloading && hs.preloadTheseImages[i] && hs.preloadTheseImages[i] != 'undefined') {
+               var img = document.createElement('img');
+               img.onload = function() {
+                       img = null;
+                       hs.preloadFullImage(i + 1);
+               };
+               img.src = hs.preloadTheseImages[i];
+       }
+},
+preloadImages : function (number) {
+       if (number && typeof number != 'object') hs.numberOfImagesToPreload = number;
+
+       var arr = hs.getAnchors();
+       for (var i = 0; i < arr.images.length && i < hs.numberOfImagesToPreload; i++) {
+               hs.push(hs.preloadTheseImages, hs.getSrc(arr.images[i]));
+       }
+
+       // preload outlines
+       if (hs.outlineType)     new hs.Outline(hs.outlineType, function () { hs.preloadFullImage(0)} );
+       else
+
+       hs.preloadFullImage(0);
+
+       // preload cursor
+       if (hs.restoreCursor) var cur = hs.createElement('img', { src: hs.graphicsDir + hs.restoreCursor });
+},
+
+
+init : function () {
+       if (!hs.container) {
+
+               hs.ieLt7 = hs.ie && hs.uaVersion < 7;
+               hs.ieLt9 = hs.ie && hs.uaVersion < 9;
+
+               hs.getPageSize();
+               for (var x in hs.langDefaults) {
+                       if (typeof hs[x] != 'undefined') hs.lang[x] = hs[x];
+                       else if (typeof hs.lang[x] == 'undefined' && typeof hs.langDefaults[x] != 'undefined')
+                               hs.lang[x] = hs.langDefaults[x];
+               }
+
+               hs.container = hs.createElement('div', {
+                               className: 'highslide-container'
+                       }, {
+                               position: 'absolute',
+                               left: 0,
+                               top: 0,
+                               width: '100%',
+                               zIndex: hs.zIndexCounter,
+                               direction: 'ltr'
+                       },
+                       document.body,
+                       true
+               );
+               hs.loading = hs.createElement('a', {
+                               className: 'highslide-loading',
+                               title: hs.lang.loadingTitle,
+                               innerHTML: hs.lang.loadingText,
+                               href: 'javascript:;'
+                       }, {
+                               position: 'absolute',
+                               top: '-9999px',
+                               opacity: hs.loadingOpacity,
+                               zIndex: 1
+                       }, hs.container
+               );
+               hs.garbageBin = hs.createElement('div', null, { display: 'none' }, hs.container);
+               hs.viewport = hs.createElement('div', {
+                               className: 'highslide-viewport highslide-viewport-size'
+                       }, {
+                               visibility: (hs.safari && hs.uaVersion < 525) ? 'visible' : 'hidden'
+                       }, hs.container, 1
+               );
+
+               // http://www.robertpenner.com/easing/
+               Math.linearTween = function (t, b, c, d) {
+                       return c*t/d + b;
+               };
+               Math.easeInQuad = function (t, b, c, d) {
+                       return c*(t/=d)*t + b;
+               };
+               Math.easeOutQuad = function (t, b, c, d) {
+                       return -c *(t/=d)*(t-2) + b;
+               };
+
+               hs.hideSelects = hs.ieLt7;
+               hs.hideIframes = ((window.opera && hs.uaVersion < 9) || navigator.vendor == 'KDE'
+                       || (hs.ieLt7 && hs.uaVersion < 5.5));
+               hs.fireEvent(this, 'onActivate');
+       }
+},
+ready : function() {
+       if (hs.isReady) return;
+       hs.isReady = true;
+       for (var i = 0; i < hs.onReady.length; i++) hs.onReady[i]();
+},
+
+updateAnchors : function() {
+       var el, els, all = [], images = [],groups = {}, re;
+
+       for (var i = 0; i < hs.openerTagNames.length; i++) {
+               els = document.getElementsByTagName(hs.openerTagNames[i]);
+               for (var j = 0; j < els.length; j++) {
+                       el = els[j];
+                       re = hs.isHsAnchor(el);
+                       if (re) {
+                               hs.push(all, el);
+                               if (re[0] == 'hs.expand') hs.push(images, el);
+                               var g = hs.getParam(el, 'slideshowGroup') || 'none';
+                               if (!groups[g]) groups[g] = [];
+                               hs.push(groups[g], el);
+                       }
+               }
+       }
+       hs.anchors = { all: all, groups: groups, images: images };
+       return hs.anchors;
+
+},
+
+getAnchors : function() {
+       return hs.anchors || hs.updateAnchors();
+},
+
+
+close : function(el) {
+       var exp = hs.getExpander(el);
+       if (exp) exp.close();
+       return false;
+}
+}; // end hs object
+hs.fx = function( elem, options, prop ){
+       this.options = options;
+       this.elem = elem;
+       this.prop = prop;
+
+       if (!options.orig) options.orig = {};
+};
+hs.fx.prototype = {
+       update: function(){
+               (hs.fx.step[this.prop] || hs.fx.step._default)(this);
+
+               if (this.options.step)
+                       this.options.step.call(this.elem, this.now, this);
+
+       },
+       custom: function(from, to, unit){
+               this.startTime = (new Date()).getTime();
+               this.start = from;
+               this.end = to;
+               this.unit = unit;// || this.unit || "px";
+               this.now = this.start;
+               this.pos = this.state = 0;
+
+               var self = this;
+               function t(gotoEnd){
+                       return self.step(gotoEnd);
+               }
+
+               t.elem = this.elem;
+
+               if ( t() && hs.timers.push(t) == 1 ) {
+                       hs.timerId = setInterval(function(){
+                               var timers = hs.timers;
+
+                               for ( var i = 0; i < timers.length; i++ )
+                                       if ( !timers[i]() )
+                                               timers.splice(i--, 1);
+
+                               if ( !timers.length ) {
+                                       clearInterval(hs.timerId);
+                               }
+                       }, 13);
+               }
+       },
+       step: function(gotoEnd){
+               var t = (new Date()).getTime();
+               if ( gotoEnd || t >= this.options.duration + this.startTime ) {
+                       this.now = this.end;
+                       this.pos = this.state = 1;
+                       this.update();
+
+                       this.options.curAnim[ this.prop ] = true;
+
+                       var done = true;
+                       for ( var i in this.options.curAnim )
+                               if ( this.options.curAnim[i] !== true )
+                                       done = false;
+
+                       if ( done ) {
+                               if (this.options.complete) this.options.complete.call(this.elem);
+                       }
+                       return false;
+               } else {
+                       var n = t - this.startTime;
+                       this.state = n / this.options.duration;
+                       this.pos = this.options.easing(n, 0, 1, this.options.duration);
+                       this.now = this.start + ((this.end - this.start) * this.pos);
+                       this.update();
+               }
+               return true;
+       }
+
+};
+
+hs.extend( hs.fx, {
+       step: {
+
+               opacity: function(fx){
+                       hs.setStyles(fx.elem, { opacity: fx.now });
+               },
+
+               _default: function(fx){
+                       try {
+                               if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
+                                       fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+                               else
+                                       fx.elem[ fx.prop ] = fx.now;
+                       } catch (e) {}
+               }
+       }
+});
+
+hs.Outline =  function (outlineType, onLoad) {
+       this.onLoad = onLoad;
+       this.outlineType = outlineType;
+       var v = hs.uaVersion, tr;
+
+       this.hasAlphaImageLoader = hs.ie && hs.uaVersion < 7;
+       if (!outlineType) {
+               if (onLoad) onLoad();
+               return;
+       }
+
+       hs.init();
+       this.table = hs.createElement(
+               'table', {
+                       cellSpacing: 0
+               }, {
+                       visibility: 'hidden',
+                       position: 'absolute',
+                       borderCollapse: 'collapse',
+                       width: 0
+               },
+               hs.container,
+               true
+       );
+       var tbody = hs.createElement('tbody', null, null, this.table, 1);
+
+       this.td = [];
+       for (var i = 0; i <= 8; i++) {
+               if (i % 3 == 0) tr = hs.createElement('tr', null, { height: 'auto' }, tbody, true);
+               this.td[i] = hs.createElement('td', null, null, tr, true);
+               var style = i != 4 ? { lineHeight: 0, fontSize: 0} : { position : 'relative' };
+               hs.setStyles(this.td[i], style);
+       }
+       this.td[4].className = outlineType +' highslide-outline';
+
+       this.preloadGraphic();
+};
+
+hs.Outline.prototype = {
+preloadGraphic : function () {
+       var src = hs.graphicsDir + (hs.outlinesDir || "outlines/")+ this.outlineType +".png";
+
+       var appendTo = hs.safari && hs.uaVersion < 525 ? hs.container : null;
+       this.graphic = hs.createElement('img', null, { position: 'absolute',
+               top: '-9999px' }, appendTo, true); // for onload trigger
+
+       var pThis = this;
+       this.graphic.onload = function() { pThis.onGraphicLoad(); };
+
+       this.graphic.src = src;
+},
+
+onGraphicLoad : function () {
+       var o = this.offset = this.graphic.width / 4,
+               pos = [[0,0],[0,-4],[-2,0],[0,-8],0,[-2,-8],[0,-2],[0,-6],[-2,-2]],
+               dim = { height: (2*o) +'px', width: (2*o) +'px' };
+       for (var i = 0; i <= 8; i++) {
+               if (pos[i]) {
+                       if (this.hasAlphaImageLoader) {
+                               var w = (i == 1 || i == 7) ? '100%' : this.graphic.width +'px';
+                               var div = hs.createElement('div', null, { width: '100%', height: '100%', position: 'relative', overflow: 'hidden'}, this.td[i], true);
+                               hs.createElement ('div', null, {
+                                               filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale, src='"+ this.graphic.src + "')",
+                                               position: 'absolute',
+                                               width: w,
+                                               height: this.graphic.height +'px',
+                                               left: (pos[i][0]*o)+'px',
+                                               top: (pos[i][1]*o)+'px'
+                                       },
+                               div,
+                               true);
+                       } else {
+                               hs.setStyles(this.td[i], { background: 'url('+ this.graphic.src +') '+ (pos[i][0]*o)+'px '+(pos[i][1]*o)+'px'});
+                       }
+
+                       if (window.opera && (i == 3 || i ==5))
+                               hs.createElement('div', null, dim, this.td[i], true);
+
+                       hs.setStyles (this.td[i], dim);
+               }
+       }
+       this.graphic = null;
+       if (hs.pendingOutlines[this.outlineType]) hs.pendingOutlines[this.outlineType].destroy();
+       hs.pendingOutlines[this.outlineType] = this;
+       if (this.onLoad) this.onLoad();
+},
+
+setPosition : function (pos, offset, vis, dur, easing) {
+       var exp = this.exp,
+               stl = exp.wrapper.style,
+               offset = offset || 0,
+               pos = pos || {
+                       x: exp.x.pos + offset,
+                       y: exp.y.pos + offset,
+                       w: exp.x.get('wsize') - 2 * offset,
+                       h: exp.y.get('wsize') - 2 * offset
+               };
+       if (vis) this.table.style.visibility = (pos.h >= 4 * this.offset)
+               ? 'visible' : 'hidden';
+       hs.setStyles(this.table, {
+               left: (pos.x - this.offset) +'px',
+               top: (pos.y - this.offset) +'px',
+               width: (pos.w + 2 * this.offset) +'px'
+       });
+
+       pos.w -= 2 * this.offset;
+       pos.h -= 2 * this.offset;
+       hs.setStyles (this.td[4], {
+               width: pos.w >= 0 ? pos.w +'px' : 0,
+               height: pos.h >= 0 ? pos.h +'px' : 0
+       });
+       if (this.hasAlphaImageLoader) this.td[3].style.height
+               = this.td[5].style.height = this.td[4].style.height;
+
+},
+
+destroy : function(hide) {
+       if (hide) this.table.style.visibility = 'hidden';
+       else hs.discardElement(this.table);
+}
+};
+
+hs.Dimension = function(exp, dim) {
+       this.exp = exp;
+       this.dim = dim;
+       this.ucwh = dim == 'x' ? 'Width' : 'Height';
+       this.wh = this.ucwh.toLowerCase();
+       this.uclt = dim == 'x' ? 'Left' : 'Top';
+       this.lt = this.uclt.toLowerCase();
+       this.ucrb = dim == 'x' ? 'Right' : 'Bottom';
+       this.rb = this.ucrb.toLowerCase();
+       this.p1 = this.p2 = 0;
+};
+hs.Dimension.prototype = {
+get : function(key) {
+       switch (key) {
+               case 'loadingPos':
+                       return this.tpos + this.tb + (this.t - hs.loading['offset'+ this.ucwh]) / 2;
+               case 'loadingPosXfade':
+                       return this.pos + this.cb+ this.p1 + (this.size - hs.loading['offset'+ this.ucwh]) / 2;
+               case 'wsize':
+                       return this.size + 2 * this.cb + this.p1 + this.p2;
+               case 'fitsize':
+                       return this.clientSize - this.marginMin - this.marginMax;
+               case 'maxsize':
+                       return this.get('fitsize') - 2 * this.cb - this.p1 - this.p2 ;
+               case 'opos':
+                       return this.pos - (this.exp.outline ? this.exp.outline.offset : 0);
+               case 'osize':
+                       return this.get('wsize') + (this.exp.outline ? 2*this.exp.outline.offset : 0);
+               case 'imgPad':
+                       return this.imgSize ? Math.round((this.size - this.imgSize) / 2) : 0;
+
+       }
+},
+calcBorders: function() {
+       // correct for borders
+       this.cb = (this.exp.content['offset'+ this.ucwh] - this.t) / 2;
+
+       this.marginMax = hs['margin'+ this.ucrb];
+},
+calcThumb: function() {
+       this.t = this.exp.el[this.wh] ? parseInt(this.exp.el[this.wh]) :
+               this.exp.el['offset'+ this.ucwh];
+       this.tpos = this.exp.tpos[this.dim];
+       this.tb = (this.exp.el['offset'+ this.ucwh] - this.t) / 2;
+       if (this.tpos == 0 || this.tpos == -1) {
+               this.tpos = (hs.page[this.wh] / 2) + hs.page['scroll'+ this.uclt];
+       };
+},
+calcExpanded: function() {
+       var exp = this.exp;
+       this.justify = 'auto';
+
+       // get alignment
+       if (exp.align == 'center') this.justify = 'center';
+       else if (new RegExp(this.lt).test(exp.anchor)) this.justify = null;
+       else if (new RegExp(this.rb).test(exp.anchor)) this.justify = 'max';
+
+
+       // size and position
+       this.pos = this.tpos - this.cb + this.tb;
+
+       if (this.maxHeight && this.dim == 'x')
+               exp.maxWidth = Math.min(exp.maxWidth || this.full, exp.maxHeight * this.full / exp.y.full);
+
+       this.size = Math.min(this.full, exp['max'+ this.ucwh] || this.full);
+       this.minSize = exp.allowSizeReduction ?
+               Math.min(exp['min'+ this.ucwh], this.full) :this.full;
+       if (exp.isImage && exp.useBox)  {
+               this.size = exp[this.wh];
+               this.imgSize = this.full;
+       }
+       if (this.dim == 'x' && hs.padToMinWidth) this.minSize = exp.minWidth;
+       this.target = exp['target'+ this.dim.toUpperCase()];
+       this.marginMin = hs['margin'+ this.uclt];
+       this.scroll = hs.page['scroll'+ this.uclt];
+       this.clientSize = hs.page[this.wh];
+},
+setSize: function(i) {
+       var exp = this.exp;
+       if (exp.isImage && (exp.useBox || hs.padToMinWidth)) {
+               this.imgSize = i;
+               this.size = Math.max(this.size, this.imgSize);
+               exp.content.style[this.lt] = this.get('imgPad')+'px';
+       } else
+       this.size = i;
+
+       exp.content.style[this.wh] = i +'px';
+       exp.wrapper.style[this.wh] = this.get('wsize') +'px';
+       if (exp.outline) exp.outline.setPosition();
+       if (this.dim == 'x' && exp.overlayBox) exp.sizeOverlayBox(true);
+       if (this.dim == 'x' && exp.slideshow && exp.isImage) {
+               if (i == this.full) exp.slideshow.disable('full-expand');
+               else exp.slideshow.enable('full-expand');
+       }
+},
+setPos: function(i) {
+       this.pos = i;
+       this.exp.wrapper.style[this.lt] = i +'px';
+
+       if (this.exp.outline) this.exp.outline.setPosition();
+
+}
+};
+
+hs.Expander = function(a, params, custom, contentType) {
+       if (document.readyState && hs.ie && !hs.isReady) {
+               hs.addEventListener(document, 'ready', function() {
+                       new hs.Expander(a, params, custom, contentType);
+               });
+               return;
+       }
+       this.a = a;
+       this.custom = custom;
+       this.contentType = contentType || 'image';
+       this.isImage = !this.isHtml;
+
+       hs.continuePreloading = false;
+       this.overlays = [];
+       this.last = hs.last;
+       hs.last = null;
+       hs.init();
+       var key = this.key = hs.expanders.length;
+       // override inline parameters
+       for (var i = 0; i < hs.overrides.length; i++) {
+               var name = hs.overrides[i];
+               this[name] = params && typeof params[name] != 'undefined' ?
+                       params[name] : hs[name];
+       }
+       if (!this.src) this.src = a.href;
+
+       // get thumb
+       var el = (params && params.thumbnailId) ? hs.$(params.thumbnailId) : a;
+       el = this.thumb = el.getElementsByTagName('img')[0] || el;
+       this.thumbsUserSetId = el.id || a.id;
+       if (!hs.fireEvent(this, 'onInit')) return true;
+
+       // check if already open
+       for (var i = 0; i < hs.expanders.length; i++) {
+               if (hs.expanders[i] && hs.expanders[i].a == a
+                       && !(this.last && this.transitions[1] == 'crossfade')) {
+                       hs.expanders[i].focus();
+                       return false;
+               }
+       }
+
+       // cancel other
+       if (!hs.allowSimultaneousLoading) for (var i = 0; i < hs.expanders.length; i++) {
+               if (hs.expanders[i] && hs.expanders[i].thumb != el && !hs.expanders[i].onLoadStarted) {
+                       hs.expanders[i].cancelLoading();
+               }
+       }
+       hs.expanders[key] = this;
+       if (!hs.allowMultipleInstances && !hs.upcoming) {
+               if (hs.expanders[key-1]) hs.expanders[key-1].close();
+               if (typeof hs.focusKey != 'undefined' && hs.expanders[hs.focusKey])
+                       hs.expanders[hs.focusKey].close();
+       }
+
+       // initiate metrics
+       this.el = el;
+       this.tpos = this.pageOrigin || hs.getPosition(el);
+       hs.getPageSize();
+       var x = this.x = new hs.Dimension(this, 'x');
+       x.calcThumb();
+       var y = this.y = new hs.Dimension(this, 'y');
+       y.calcThumb();
+       this.wrapper = hs.createElement(
+               'div', {
+                       id: 'highslide-wrapper-'+ this.key,
+                       className: 'highslide-wrapper '+ this.wrapperClassName
+               }, {
+                       visibility: 'hidden',
+                       position: 'absolute',
+                       zIndex: hs.zIndexCounter += 2
+               }, null, true );
+
+       this.wrapper.onmouseover = this.wrapper.onmouseout = hs.wrapperMouseHandler;
+       if (this.contentType == 'image' && this.outlineWhileAnimating == 2)
+               this.outlineWhileAnimating = 0;
+
+       // get the outline
+       if (!this.outlineType
+               || (this.last && this.isImage && this.transitions[1] == 'crossfade')) {
+               this[this.contentType +'Create']();
+
+       } else if (hs.pendingOutlines[this.outlineType]) {
+               this.connectOutline();
+               this[this.contentType +'Create']();
+
+       } else {
+               this.showLoading();
+               var exp = this;
+               new hs.Outline(this.outlineType,
+                       function () {
+                               exp.connectOutline();
+                               exp[exp.contentType +'Create']();
+                       }
+               );
+       }
+       return true;
+};
+
+hs.Expander.prototype = {
+error : function(e) {
+       if (hs.debug) alert ('Line '+ e.lineNumber +': '+ e.message);
+       else window.location.href = this.src;
+},
+
+connectOutline : function() {
+       var outline = this.outline = hs.pendingOutlines[this.outlineType];
+       outline.exp = this;
+       outline.table.style.zIndex = this.wrapper.style.zIndex - 1;
+       hs.pendingOutlines[this.outlineType] = null;
+},
+
+showLoading : function() {
+       if (this.onLoadStarted || this.loading) return;
+
+       this.loading = hs.loading;
+       var exp = this;
+       this.loading.onclick = function() {
+               exp.cancelLoading();
+       };
+
+
+       if (!hs.fireEvent(this, 'onShowLoading')) return;
+       var exp = this,
+               l = this.x.get('loadingPos') +'px',
+               t = this.y.get('loadingPos') +'px';
+       if (!tgt && this.last && this.transitions[1] == 'crossfade')
+               var tgt = this.last;
+       if (tgt) {
+               l = tgt.x.get('loadingPosXfade') +'px';
+               t = tgt.y.get('loadingPosXfade') +'px';
+               this.loading.style.zIndex = hs.zIndexCounter++;
+       }
+       setTimeout(function () {
+               if (exp.loading) hs.setStyles(exp.loading, { left: l, top: t, zIndex: hs.zIndexCounter++ })}
+       , 100);
+},
+
+imageCreate : function() {
+       var exp = this;
+
+       var img = document.createElement('img');
+       this.content = img;
+       img.onload = function () {
+               if (hs.expanders[exp.key]) exp.contentLoaded();
+       };
+       if (hs.blockRightClick) img.oncontextmenu = function() { return false; };
+       img.className = 'highslide-image';
+       hs.setStyles(img, {
+               visibility: 'hidden',
+               display: 'block',
+               position: 'absolute',
+               maxWidth: '9999px',
+               zIndex: 3
+       });
+       img.title = hs.lang.restoreTitle;
+       if (hs.safari && hs.uaVersion < 525) hs.container.appendChild(img);
+       if (hs.ie && hs.flushImgSize) img.src = null;
+       img.src = this.src;
+
+       this.showLoading();
+},
+
+contentLoaded : function() {
+       try {
+               if (!this.content) return;
+               this.content.onload = null;
+               if (this.onLoadStarted) return;
+               else this.onLoadStarted = true;
+
+               var x = this.x, y = this.y;
+
+               if (this.loading) {
+                       hs.setStyles(this.loading, { top: '-9999px' });
+                       this.loading = null;
+                       hs.fireEvent(this, 'onHideLoading');
+               }
+                       x.full = this.content.width;
+                       y.full = this.content.height;
+
+                       hs.setStyles(this.content, {
+                               width: x.t +'px',
+                               height: y.t +'px'
+                       });
+                       this.wrapper.appendChild(this.content);
+                       hs.container.appendChild(this.wrapper);
+
+               x.calcBorders();
+               y.calcBorders();
+
+               hs.setStyles (this.wrapper, {
+                       left: (x.tpos + x.tb - x.cb) +'px',
+                       top: (y.tpos + x.tb - y.cb) +'px'
+               });
+
+
+               this.initSlideshow();
+               this.getOverlays();
+
+               var ratio = x.full / y.full;
+               x.calcExpanded();
+               this.justify(x);
+
+               y.calcExpanded();
+               this.justify(y);
+               if (this.overlayBox) this.sizeOverlayBox(0, 1);
+
+
+               if (this.allowSizeReduction) {
+                               this.correctRatio(ratio);
+                       var ss = this.slideshow;
+                       if (ss && this.last && ss.controls && ss.fixedControls) {
+                               var pos = ss.overlayOptions.position || '', p;
+                               for (var dim in hs.oPos) for (var i = 0; i < 5; i++) {
+                                       p = this[dim];
+                                       if (pos.match(hs.oPos[dim][i])) {
+                                               p.pos = this.last[dim].pos
+                                                       + (this.last[dim].p1 - p.p1)
+                                                       + (this.last[dim].size - p.size) * [0, 0, .5, 1, 1][i];
+                                               if (ss.fixedControls == 'fit') {
+                                                       if (p.pos + p.size + p.p1 + p.p2 > p.scroll + p.clientSize - p.marginMax)
+                                                               p.pos = p.scroll + p.clientSize - p.size - p.marginMin - p.marginMax - p.p1 - p.p2;
+                                                       if (p.pos < p.scroll + p.marginMin) p.pos = p.scroll + p.marginMin;
+                                               }
+                                       }
+                               }
+                       }
+                       if (this.isImage && this.x.full > (this.x.imgSize || this.x.size)) {
+                               this.createFullExpand();
+                               if (this.overlays.length == 1) this.sizeOverlayBox();
+                       }
+               }
+               this.show();
+
+       } catch (e) {
+               this.error(e);
+       }
+},
+
+justify : function (p, moveOnly) {
+       var tgtArr, tgt = p.target, dim = p == this.x ? 'x' : 'y';
+
+       if (tgt && tgt.match(/ /)) {
+               tgtArr = tgt.split(' ');
+               tgt = tgtArr[0];
+       }
+       if (tgt && hs.$(tgt)) {
+               p.pos = hs.getPosition(hs.$(tgt))[dim];
+               if (tgtArr && tgtArr[1] && tgtArr[1].match(/^[-]?[0-9]+px$/))
+                       p.pos += parseInt(tgtArr[1]);
+               if (p.size < p.minSize) p.size = p.minSize;
+
+       } else if (p.justify == 'auto' || p.justify == 'center') {
+
+               var hasMovedMin = false;
+
+               var allowReduce = p.exp.allowSizeReduction;
+               if (p.justify == 'center')
+                       p.pos = Math.round(p.scroll + (p.clientSize + p.marginMin - p.marginMax - p.get('wsize')) / 2);
+               else
+                       p.pos = Math.round(p.pos - ((p.get('wsize') - p.t) / 2));
+               if (p.pos < p.scroll + p.marginMin) {
+                       p.pos = p.scroll + p.marginMin;
+                       hasMovedMin = true;
+               }
+               if (!moveOnly && p.size < p.minSize) {
+                       p.size = p.minSize;
+                       allowReduce = false;
+               }
+               if (p.pos + p.get('wsize') > p.scroll + p.clientSize - p.marginMax) {
+                       if (!moveOnly && hasMovedMin && allowReduce) {
+                               p.size = Math.min(p.size, p.get(dim == 'y' ? 'fitsize' : 'maxsize'));
+                       } else if (p.get('wsize') < p.get('fitsize')) {
+                               p.pos = p.scroll + p.clientSize - p.marginMax - p.get('wsize');
+                       } else { // image larger than viewport
+                               p.pos = p.scroll + p.marginMin;
+                               if (!moveOnly && allowReduce) p.size = p.get(dim == 'y' ? 'fitsize' : 'maxsize');
+                       }
+               }
+
+               if (!moveOnly && p.size < p.minSize) {
+                       p.size = p.minSize;
+                       allowReduce = false;
+               }
+
+
+       } else if (p.justify == 'max') {
+               p.pos = Math.floor(p.pos - p.size + p.t);
+       }
+
+
+       if (p.pos < p.marginMin) {
+               var tmpMin = p.pos;
+               p.pos = p.marginMin;
+
+               if (allowReduce && !moveOnly) p.size = p.size - (p.pos - tmpMin);
+
+       }
+},
+
+correctRatio : function(ratio) {
+       var x = this.x,
+               y = this.y,
+               changed = false,
+               xSize = Math.min(x.full, x.size),
+               ySize = Math.min(y.full, y.size),
+               useBox = (this.useBox || hs.padToMinWidth);
+
+       if (xSize / ySize > ratio) { // width greater
+               xSize = ySize * ratio;
+               if (xSize < x.minSize) { // below minWidth
+                       xSize = x.minSize;
+                       ySize = xSize / ratio;
+               }
+               changed = true;
+
+       } else if (xSize / ySize < ratio) { // height greater
+               ySize = xSize / ratio;
+               changed = true;
+       }
+
+       if (hs.padToMinWidth && x.full < x.minSize) {
+               x.imgSize = x.full;
+               y.size = y.imgSize = y.full;
+       } else if (this.useBox) {
+               x.imgSize = xSize;
+               y.imgSize = ySize;
+       } else {
+               x.size = xSize;
+               y.size = ySize;
+       }
+       changed = this.fitOverlayBox(this.useBox ? null : ratio, changed);
+       if (useBox && y.size < y.imgSize) {
+               y.imgSize = y.size;
+               x.imgSize = y.size * ratio;
+       }
+       if (changed || useBox) {
+               x.pos = x.tpos - x.cb + x.tb;
+               x.minSize = x.size;
+               this.justify(x, true);
+
+               y.pos = y.tpos - y.cb + y.tb;
+               y.minSize = y.size;
+               this.justify(y, true);
+               if (this.overlayBox) this.sizeOverlayBox();
+       }
+
+
+},
+fitOverlayBox : function(ratio, changed) {
+       var x = this.x, y = this.y;
+       if (this.overlayBox) {
+               while (y.size > this.minHeight && x.size > this.minWidth
+                               &&  y.get('wsize') > y.get('fitsize')) {
+                       y.size -= 10;
+                       if (ratio) x.size = y.size * ratio;
+                       this.sizeOverlayBox(0, 1);
+                       changed = true;
+               }
+       }
+       return changed;
+},
+
+show : function () {
+       var x = this.x, y = this.y;
+       this.doShowHide('hidden');
+       hs.fireEvent(this, 'onBeforeExpand');
+       if (this.slideshow && this.slideshow.thumbstrip) this.slideshow.thumbstrip.selectThumb();
+
+       // Apply size change
+       this.changeSize(
+               1, {
+                       wrapper: {
+                               width : x.get('wsize'),
+                               height : y.get('wsize'),
+                               left: x.pos,
+                               top: y.pos
+                       },
+                       content: {
+                               left: x.p1 + x.get('imgPad'),
+                               top: y.p1 + y.get('imgPad'),
+                               width:x.imgSize ||x.size,
+                               height:y.imgSize ||y.size
+                       }
+               },
+               hs.expandDuration
+       );
+},
+
+changeSize : function(up, to, dur) {
+       // transition
+       var trans = this.transitions,
+       other = up ? (this.last ? this.last.a : null) : hs.upcoming,
+       t = (trans[1] && other
+                       && hs.getParam(other, 'transitions')[1] == trans[1]) ?
+               trans[1] : trans[0];
+
+       if (this[t] && t != 'expand') {
+               this[t](up, to);
+               return;
+       }
+
+       if (this.outline && !this.outlineWhileAnimating) {
+               if (up) this.outline.setPosition();
+               else this.outline.destroy();
+       }
+
+
+       if (!up) this.destroyOverlays();
+
+       var exp = this,
+               x = exp.x,
+               y = exp.y,
+               easing = this.easing;
+       if (!up) easing = this.easingClose || easing;
+       var after = up ?
+               function() {
+
+                       if (exp.outline) exp.outline.table.style.visibility = "visible";
+                       setTimeout(function() {
+                               exp.afterExpand();
+                       }, 50);
+               } :
+               function() {
+                       exp.afterClose();
+               };
+       if (up) hs.setStyles( this.wrapper, {
+               width: x.t +'px',
+               height: y.t +'px'
+       });
+       if (this.fadeInOut) {
+               hs.setStyles(this.wrapper, { opacity: up ? 0 : 1 });
+               hs.extend(to.wrapper, { opacity: up });
+       }
+       hs.animate( this.wrapper, to.wrapper, {
+               duration: dur,
+               easing: easing,
+               step: function(val, args) {
+                       if (exp.outline && exp.outlineWhileAnimating && args.prop == 'top') {
+                               var fac = up ? args.pos : 1 - args.pos;
+                               var pos = {
+                                       w: x.t + (x.get('wsize') - x.t) * fac,
+                                       h: y.t + (y.get('wsize') - y.t) * fac,
+                                       x: x.tpos + (x.pos - x.tpos) * fac,
+                                       y: y.tpos + (y.pos - y.tpos) * fac
+                               };
+                               exp.outline.setPosition(pos, 0, 1);
+                       }
+               }
+       });
+       hs.animate( this.content, to.content, dur, easing, after);
+       if (up) {
+               this.wrapper.style.visibility = 'visible';
+               this.content.style.visibility = 'visible';
+               this.a.className += ' highslide-active-anchor';
+       }
+},
+
+
+
+fade : function(up, to) {
+       this.outlineWhileAnimating = false;
+       var exp = this, t = up ? hs.expandDuration : 0;
+
+       if (up) {
+               hs.animate(this.wrapper, to.wrapper, 0);
+               hs.setStyles(this.wrapper, { opacity: 0, visibility: 'visible' });
+               hs.animate(this.content, to.content, 0);
+               this.content.style.visibility = 'visible';
+
+               hs.animate(this.wrapper, { opacity: 1 }, t, null,
+                       function() { exp.afterExpand(); });
+       }
+
+       if (this.outline) {
+               this.outline.table.style.zIndex = this.wrapper.style.zIndex;
+               var dir = up || -1,
+                       offset = this.outline.offset,
+                       startOff = up ? 3 : offset,
+                       endOff = up? offset : 3;
+               for (var i = startOff; dir * i <= dir * endOff; i += dir, t += 25) {
+                       (function() {
+                               var o = up ? endOff - i : startOff - i;
+                               setTimeout(function() {
+                                       exp.outline.setPosition(0, o, 1);
+                               }, t);
+                       })();
+               }
+       }
+
+
+       if (up) {}//setTimeout(function() { exp.afterExpand(); }, t+50);
+       else {
+               setTimeout( function() {
+                       if (exp.outline) exp.outline.destroy(exp.preserveContent);
+
+                       exp.destroyOverlays();
+
+                       hs.animate( exp.wrapper, { opacity: 0 }, hs.restoreDuration, null, function(){
+                               exp.afterClose();
+                       });
+               }, t);
+       }
+},
+crossfade : function (up, to, from) {
+       if (!up) return;
+       var exp = this,
+               last = this.last,
+               x = this.x,
+               y = this.y,
+               lastX = last.x,
+               lastY = last.y,
+               wrapper = this.wrapper,
+               content = this.content,
+               overlayBox = this.overlayBox;
+       hs.removeEventListener(document, 'mousemove', hs.dragHandler);
+
+       hs.setStyles(content, {
+               width: (x.imgSize || x.size) +'px',
+               height: (y.imgSize || y.size) +'px'
+       });
+       if (overlayBox) overlayBox.style.overflow = 'visible';
+       this.outline = last.outline;
+       if (this.outline) this.outline.exp = exp;
+       last.outline = null;
+       var fadeBox = hs.createElement('div', {
+                       className: 'highslide-'+ this.contentType
+               }, {
+                       position: 'absolute',
+                       zIndex: 4,
+                       overflow: 'hidden',
+                       display: 'none'
+               }
+       );
+       var names = { oldImg: last, newImg: this };
+       for (var n in names) {
+               this[n] = names[n].content.cloneNode(1);
+               hs.setStyles(this[n], {
+                       position: 'absolute',
+                       border: 0,
+                       visibility: 'visible'
+               });
+               fadeBox.appendChild(this[n]);
+       }
+       wrapper.appendChild(fadeBox);
+       if (overlayBox) {
+               overlayBox.className = '';
+               wrapper.appendChild(overlayBox);
+       }
+       fadeBox.style.display = '';
+       last.content.style.display = 'none';
+
+
+       if (hs.safari && hs.uaVersion < 525) {
+               this.wrapper.style.visibility = 'visible';
+       }
+       hs.animate(wrapper, {
+               width: x.size
+       }, {
+               duration: hs.transitionDuration,
+               step: function(val, args) {
+                       var pos = args.pos,
+                               invPos = 1 - pos;
+                       var prop,
+                               size = {},
+                               props = ['pos', 'size', 'p1', 'p2'];
+                       for (var n in props) {
+                               prop = props[n];
+                               size['x'+ prop] = Math.round(invPos * lastX[prop] + pos * x[prop]);
+                               size['y'+ prop] = Math.round(invPos * lastY[prop] + pos * y[prop]);
+                               size.ximgSize = Math.round(
+                                       invPos * (lastX.imgSize || lastX.size) + pos * (x.imgSize || x.size));
+                               size.ximgPad = Math.round(invPos * lastX.get('imgPad') + pos * x.get('imgPad'));
+                               size.yimgSize = Math.round(
+                                       invPos * (lastY.imgSize || lastY.size) + pos * (y.imgSize || y.size));
+                               size.yimgPad = Math.round(invPos * lastY.get('imgPad') + pos * y.get('imgPad'));
+                       }
+                       if (exp.outline) exp.outline.setPosition({
+                               x: size.xpos,
+                               y: size.ypos,
+                               w: size.xsize + size.xp1 + size.xp2 + 2 * x.cb,
+                               h: size.ysize + size.yp1 + size.yp2 + 2 * y.cb
+                       });
+                       last.wrapper.style.clip = 'rect('
+                               + (size.ypos - lastY.pos)+'px, '
+                               + (size.xsize + size.xp1 + size.xp2 + size.xpos + 2 * lastX.cb - lastX.pos) +'px, '
+                               + (size.ysize + size.yp1 + size.yp2 + size.ypos + 2 * lastY.cb - lastY.pos) +'px, '
+                               + (size.xpos - lastX.pos)+'px)';
+
+                       hs.setStyles(content, {
+                               top: (size.yp1 + y.get('imgPad')) +'px',
+                               left: (size.xp1 + x.get('imgPad')) +'px',
+                               marginTop: (y.pos - size.ypos) +'px',
+                               marginLeft: (x.pos - size.xpos) +'px'
+                       });
+                       hs.setStyles(wrapper, {
+                               top: size.ypos +'px',
+                               left: size.xpos +'px',
+                               width: (size.xp1 + size.xp2 + size.xsize + 2 * x.cb)+ 'px',
+                               height: (size.yp1 + size.yp2 + size.ysize + 2 * y.cb) + 'px'
+                       });
+                       hs.setStyles(fadeBox, {
+                               width: (size.ximgSize || size.xsize) + 'px',
+                               height: (size.yimgSize || size.ysize) +'px',
+                               left: (size.xp1 + size.ximgPad)  +'px',
+                               top: (size.yp1 + size.yimgPad) +'px',
+                               visibility: 'visible'
+                       });
+
+                       hs.setStyles(exp.oldImg, {
+                               top: (lastY.pos - size.ypos + lastY.p1 - size.yp1 + lastY.get('imgPad') - size.yimgPad)+'px',
+                               left: (lastX.pos - size.xpos + lastX.p1 - size.xp1 + lastX.get('imgPad') - size.ximgPad)+'px'
+                       });
+
+                       hs.setStyles(exp.newImg, {
+                               opacity: pos,
+                               top: (y.pos - size.ypos + y.p1 - size.yp1 + y.get('imgPad') - size.yimgPad) +'px',
+                               left: (x.pos - size.xpos + x.p1 - size.xp1 + x.get('imgPad') - size.ximgPad) +'px'
+                       });
+                       if (overlayBox) hs.setStyles(overlayBox, {
+                               width: size.xsize + 'px',
+                               height: size.ysize +'px',
+                               left: (size.xp1 + x.cb)  +'px',
+                               top: (size.yp1 + y.cb) +'px'
+                       });
+               },
+               complete: function () {
+                       wrapper.style.visibility = content.style.visibility = 'visible';
+                       content.style.display = 'block';
+                       hs.discardElement(fadeBox);
+                       exp.afterExpand();
+                       last.afterClose();
+                       exp.last = null;
+               }
+
+       });
+},
+reuseOverlay : function(o, el) {
+       if (!this.last) return false;
+       for (var i = 0; i < this.last.overlays.length; i++) {
+               var oDiv = hs.$('hsId'+ this.last.overlays[i]);
+               if (oDiv && oDiv.hsId == o.hsId) {
+                       this.genOverlayBox();
+                       oDiv.reuse = this.key;
+                       hs.push(this.overlays, this.last.overlays[i]);
+                       return true;
+               }
+       }
+       return false;
+},
+
+
+afterExpand : function() {
+       this.isExpanded = true;
+       this.focus();
+       if (this.dimmingOpacity) hs.dim(this);
+       if (hs.upcoming && hs.upcoming == this.a) hs.upcoming = null;
+       this.prepareNextOutline();
+       var p = hs.page, mX = hs.mouse.x + p.scrollLeft, mY = hs.mouse.y + p.scrollTop;
+       this.mouseIsOver = this.x.pos < mX && mX < this.x.pos + this.x.get('wsize')
+               && this.y.pos < mY && mY < this.y.pos + this.y.get('wsize');
+       if (this.overlayBox) this.showOverlays();
+       hs.fireEvent(this, 'onAfterExpand');
+
+},
+
+
+prepareNextOutline : function() {
+       var key = this.key;
+       var outlineType = this.outlineType;
+       new hs.Outline(outlineType,
+               function () { try { hs.expanders[key].preloadNext(); } catch (e) {} });
+},
+
+
+preloadNext : function() {
+       var next = this.getAdjacentAnchor(1);
+       if (next && next.onclick.toString().match(/hs\.expand/))
+               var img = hs.createElement('img', { src: hs.getSrc(next) });
+},
+
+
+getAdjacentAnchor : function(op) {
+       var current = this.getAnchorIndex(), as = hs.anchors.groups[this.slideshowGroup || 'none'];
+       if (as && !as[current + op] && this.slideshow && this.slideshow.repeat) {
+               if (op == 1) return as[0];
+               else if (op == -1) return as[as.length-1];
+       }
+       return (as && as[current + op]) || null;
+},
+
+getAnchorIndex : function() {
+       var arr = hs.getAnchors().groups[this.slideshowGroup || 'none'];
+       if (arr) for (var i = 0; i < arr.length; i++) {
+               if (arr[i] == this.a) return i;
+       }
+       return null;
+},
+
+
+getNumber : function() {
+       if (this[this.numberPosition]) {
+               var arr = hs.anchors.groups[this.slideshowGroup || 'none'];
+               if (arr) {
+                       var s = hs.lang.number.replace('%1', this.getAnchorIndex() + 1).replace('%2', arr.length);
+                       this[this.numberPosition].innerHTML =
+                               '<div class="highslide-number">'+ s +'</div>'+ this[this.numberPosition].innerHTML;
+               }
+       }
+},
+initSlideshow : function() {
+       if (!this.last) {
+               for (var i = 0; i < hs.slideshows.length; i++) {
+                       var ss = hs.slideshows[i], sg = ss.slideshowGroup;
+                       if (typeof sg == 'undefined' || sg === null || sg === this.slideshowGroup)
+                               this.slideshow = new hs.Slideshow(this.key, ss);
+               }
+       } else {
+               this.slideshow = this.last.slideshow;
+       }
+       var ss = this.slideshow;
+       if (!ss) return;
+       var key = ss.expKey = this.key;
+
+       ss.checkFirstAndLast();
+       ss.disable('full-expand');
+       if (ss.controls) {
+               this.createOverlay(hs.extend(ss.overlayOptions || {}, {
+                       overlayId: ss.controls,
+                       hsId: 'controls',
+                       zIndex: 5
+               }));
+       }
+       if (ss.thumbstrip) ss.thumbstrip.add(this);
+       if (!this.last && this.autoplay) ss.play(true);
+       if (ss.autoplay) {
+               ss.autoplay = setTimeout(function() {
+                       hs.next(key);
+               }, (ss.interval || 500));
+       }
+},
+
+cancelLoading : function() {
+       hs.discardElement (this.wrapper);
+       hs.expanders[this.key] = null;
+       if (hs.upcoming == this.a) hs.upcoming = null;
+       hs.undim(this.key);
+       if (this.loading) hs.loading.style.left = '-9999px';
+       hs.fireEvent(this, 'onHideLoading');
+},
+
+writeCredits : function () {
+       if (this.credits) return;
+       this.credits = hs.createElement('a', {
+               href: hs.creditsHref,
+               target: hs.creditsTarget,
+               className: 'highslide-credits',
+               innerHTML: hs.lang.creditsText,
+               title: hs.lang.creditsTitle
+       });
+       this.createOverlay({
+               overlayId: this.credits,
+               position: this.creditsPosition || 'top left',
+               hsId: 'credits'
+       });
+},
+
+getInline : function(types, addOverlay) {
+       for (var i = 0; i < types.length; i++) {
+               var type = types[i], s = null;
+               if (type == 'caption' && !hs.fireEvent(this, 'onBeforeGetCaption')) return;
+               else if (type == 'heading' && !hs.fireEvent(this, 'onBeforeGetHeading')) return;
+               if (!this[type +'Id'] && this.thumbsUserSetId)
+                       this[type +'Id'] = type +'-for-'+ this.thumbsUserSetId;
+               if (this[type +'Id']) this[type] = hs.getNode(this[type +'Id']);
+               if (!this[type] && !this[type +'Text'] && this[type +'Eval']) try {
+                       s = eval(this[type +'Eval']);
+               } catch (e) {}
+               if (!this[type] && this[type +'Text']) {
+                       s = this[type +'Text'];
+               }
+               if (!this[type] && !s) {
+                       this[type] = hs.getNode(this.a['_'+ type + 'Id']);
+                       if (!this[type]) {
+                               var next = this.a.nextSibling;
+                               while (next && !hs.isHsAnchor(next)) {
+                                       if ((new RegExp('highslide-'+ type)).test(next.className || null)) {
+                                               if (!next.id) this.a['_'+ type + 'Id'] = next.id = 'hsId'+ hs.idCounter++;
+                                               this[type] = hs.getNode(next.id);
+                                               break;
+                                       }
+                                       next = next.nextSibling;
+                               }
+                       }
+               }
+               if (!this[type] && !s && this.numberPosition == type) s = '\n';
+
+               if (!this[type] && s) this[type] = hs.createElement('div',
+                               { className: 'highslide-'+ type, innerHTML: s } );
+
+               if (addOverlay && this[type]) {
+                       var o = { position: (type == 'heading') ? 'above' : 'below' };
+                       for (var x in this[type+'Overlay']) o[x] = this[type+'Overlay'][x];
+                       o.overlayId = this[type];
+                       this.createOverlay(o);
+               }
+       }
+},
+
+
+// on end move and resize
+doShowHide : function(visibility) {
+       if (hs.hideSelects) this.showHideElements('SELECT', visibility);
+       if (hs.hideIframes) this.showHideElements('IFRAME', visibility);
+       if (hs.geckoMac) this.showHideElements('*', visibility);
+},
+showHideElements : function (tagName, visibility) {
+       var els = document.getElementsByTagName(tagName);
+       var prop = tagName == '*' ? 'overflow' : 'visibility';
+       for (var i = 0; i < els.length; i++) {
+               if (prop == 'visibility' || (document.defaultView.getComputedStyle(
+                               els[i], "").getPropertyValue('overflow') == 'auto'
+                               || els[i].getAttribute('hidden-by') != null)) {
+                       var hiddenBy = els[i].getAttribute('hidden-by');
+                       if (visibility == 'visible' && hiddenBy) {
+                               hiddenBy = hiddenBy.replace('['+ this.key +']', '');
+                               els[i].setAttribute('hidden-by', hiddenBy);
+                               if (!hiddenBy) els[i].style[prop] = els[i].origProp;
+                       } else if (visibility == 'hidden') { // hide if behind
+                               var elPos = hs.getPosition(els[i]);
+                               elPos.w = els[i].offsetWidth;
+                               elPos.h = els[i].offsetHeight;
+                               if (!this.dimmingOpacity) { // hide all if dimming
+
+                                       var clearsX = (elPos.x + elPos.w < this.x.get('opos')
+                                               || elPos.x > this.x.get('opos') + this.x.get('osize'));
+                                       var clearsY = (elPos.y + elPos.h < this.y.get('opos')
+                                               || elPos.y > this.y.get('opos') + this.y.get('osize'));
+                               }
+                               var wrapperKey = hs.getWrapperKey(els[i]);
+                               if (!clearsX && !clearsY && wrapperKey != this.key) { // element falls behind image
+                                       if (!hiddenBy) {
+                                               els[i].setAttribute('hidden-by', '['+ this.key +']');
+                                               els[i].origProp = els[i].style[prop];
+                                               els[i].style[prop] = 'hidden';
+
+                                       } else if (hiddenBy.indexOf('['+ this.key +']') == -1) {
+                                               els[i].setAttribute('hidden-by', hiddenBy + '['+ this.key +']');
+                                       }
+                               } else if ((hiddenBy == '['+ this.key +']' || hs.focusKey == wrapperKey)
+                                               && wrapperKey != this.key) { // on move
+                                       els[i].setAttribute('hidden-by', '');
+                                       els[i].style[prop] = els[i].origProp || '';
+                               } else if (hiddenBy && hiddenBy.indexOf('['+ this.key +']') > -1) {
+                                       els[i].setAttribute('hidden-by', hiddenBy.replace('['+ this.key +']', ''));
+                               }
+
+                       }
+               }
+       }
+},
+
+focus : function() {
+       this.wrapper.style.zIndex = hs.zIndexCounter += 2;
+       // blur others
+       for (var i = 0; i < hs.expanders.length; i++) {
+               if (hs.expanders[i] && i == hs.focusKey) {
+                       var blurExp = hs.expanders[i];
+                       blurExp.content.className += ' highslide-'+ blurExp.contentType +'-blur';
+                               blurExp.content.style.cursor = hs.ieLt7 ? 'hand' : 'pointer';
+                               blurExp.content.title = hs.lang.focusTitle;
+                       hs.fireEvent(blurExp, 'onBlur');
+               }
+       }
+
+       // focus this
+       if (this.outline) this.outline.table.style.zIndex
+               = this.wrapper.style.zIndex - 1;
+       this.content.className = 'highslide-'+ this.contentType;
+               this.content.title = hs.lang.restoreTitle;
+
+               if (hs.restoreCursor) {
+                       hs.styleRestoreCursor = window.opera ? 'pointer' : 'url('+ hs.graphicsDir + hs.restoreCursor +'), pointer';
+                       if (hs.ieLt7 && hs.uaVersion < 6) hs.styleRestoreCursor = 'hand';
+                       this.content.style.cursor = hs.styleRestoreCursor;
+               }
+
+       hs.focusKey = this.key;
+       hs.addEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
+       hs.fireEvent(this, 'onFocus');
+},
+moveTo: function(x, y) {
+       this.x.setPos(x);
+       this.y.setPos(y);
+},
+resize : function (e) {
+       var w, h, r = e.width / e.height;
+       w = Math.max(e.width + e.dX, Math.min(this.minWidth, this.x.full));
+       if (this.isImage && Math.abs(w - this.x.full) < 12) w = this.x.full;
+       h = w / r;
+       if (h < Math.min(this.minHeight, this.y.full)) {
+               h = Math.min(this.minHeight, this.y.full);
+               if (this.isImage) w = h * r;
+       }
+       this.resizeTo(w, h);
+},
+resizeTo: function(w, h) {
+       this.y.setSize(h);
+       this.x.setSize(w);
+       this.wrapper.style.height = this.y.get('wsize') +'px';
+},
+
+close : function() {
+       if (this.isClosing || !this.isExpanded) return;
+       if (this.transitions[1] == 'crossfade' && hs.upcoming) {
+               hs.getExpander(hs.upcoming).cancelLoading();
+               hs.upcoming = null;
+       }
+       if (!hs.fireEvent(this, 'onBeforeClose')) return;
+       this.isClosing = true;
+       if (this.slideshow && !hs.upcoming) this.slideshow.pause();
+
+       hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
+
+       try {
+               this.content.style.cursor = 'default';
+               this.changeSize(
+                       0, {
+                               wrapper: {
+                                       width : this.x.t,
+                                       height : this.y.t,
+                                       left: this.x.tpos - this.x.cb + this.x.tb,
+                                       top: this.y.tpos - this.y.cb + this.y.tb
+                               },
+                               content: {
+                                       left: 0,
+                                       top: 0,
+                                       width: this.x.t,
+                                       height: this.y.t
+                               }
+                       }, hs.restoreDuration
+               );
+       } catch (e) { this.afterClose(); }
+},
+
+createOverlay : function (o) {
+       var el = o.overlayId,
+               relToVP = (o.relativeTo == 'viewport' && !/panel$/.test(o.position));
+       if (typeof el == 'string') el = hs.getNode(el);
+       if (o.html) el = hs.createElement('div', { innerHTML: o.html });
+       if (!el || typeof el == 'string') return;
+       if (!hs.fireEvent(this, 'onCreateOverlay', { overlay: el })) return;
+       el.style.display = 'block';
+       o.hsId = o.hsId || o.overlayId;
+       if (this.transitions[1] == 'crossfade' && this.reuseOverlay(o, el)) return;
+       this.genOverlayBox();
+       var width = o.width && /^[0-9]+(px|%)$/.test(o.width) ? o.width : 'auto';
+       if (/^(left|right)panel$/.test(o.position) && !/^[0-9]+px$/.test(o.width)) width = '200px';
+       var overlay = hs.createElement(
+               'div', {
+                       id: 'hsId'+ hs.idCounter++,
+                       hsId: o.hsId
+               }, {
+                       position: 'absolute',
+                       visibility: 'hidden',
+                       width: width,
+                       direction: hs.lang.cssDirection || '',
+                       opacity: 0
+               },
+               relToVP ? hs.viewport :this.overlayBox,
+               true
+       );
+       if (relToVP) overlay.hsKey = this.key;
+
+       overlay.appendChild(el);
+       hs.extend(overlay, {
+               opacity: 1,
+               offsetX: 0,
+               offsetY: 0,
+               dur: (o.fade === 0 || o.fade === false || (o.fade == 2 && hs.ie)) ? 0 : 250
+       });
+       hs.extend(overlay, o);
+
+
+       if (this.gotOverlays) {
+               this.positionOverlay(overlay);
+               if (!overlay.hideOnMouseOut || this.mouseIsOver)
+                       hs.animate(overlay, { opacity: overlay.opacity }, overlay.dur);
+       }
+       hs.push(this.overlays, hs.idCounter - 1);
+},
+positionOverlay : function(overlay) {
+       var p = overlay.position || 'middle center',
+               relToVP = (overlay.relativeTo == 'viewport'),
+               offX = overlay.offsetX,
+               offY = overlay.offsetY;
+       if (relToVP) {
+               hs.viewport.style.display = 'block';
+               overlay.hsKey = this.key;
+               if (overlay.offsetWidth > overlay.parentNode.offsetWidth)
+                       overlay.style.width = '100%';
+       } else
+       if (overlay.parentNode != this.overlayBox) this.overlayBox.appendChild(overlay);
+       if (/left$/.test(p)) overlay.style.left = offX +'px';
+
+       if (/center$/.test(p))  hs.setStyles (overlay, {
+               left: '50%',
+               marginLeft: (offX - Math.round(overlay.offsetWidth / 2)) +'px'
+       });
+
+       if (/right$/.test(p)) overlay.style.right = - offX +'px';
+
+       if (/^leftpanel$/.test(p)) {
+               hs.setStyles(overlay, {
+                       right: '100%',
+                       marginRight: this.x.cb +'px',
+                       top: - this.y.cb +'px',
+                       bottom: - this.y.cb +'px',
+                       overflow: 'auto'
+               });
+               this.x.p1 = overlay.offsetWidth;
+
+       } else if (/^rightpanel$/.test(p)) {
+               hs.setStyles(overlay, {
+                       left: '100%',
+                       marginLeft: this.x.cb +'px',
+                       top: - this.y.cb +'px',
+                       bottom: - this.y.cb +'px',
+                       overflow: 'auto'
+               });
+               this.x.p2 = overlay.offsetWidth;
+       }
+       var parOff = overlay.parentNode.offsetHeight;
+       overlay.style.height = 'auto';
+       if (relToVP && overlay.offsetHeight > parOff)
+               overlay.style.height = hs.ieLt7 ? parOff +'px' : '100%';
+
+       if (/^top/.test(p)) overlay.style.top = offY +'px';
+       if (/^middle/.test(p))  hs.setStyles (overlay, {
+               top: '50%',
+               marginTop: (offY - Math.round(overlay.offsetHeight / 2)) +'px'
+       });
+       if (/^bottom/.test(p)) overlay.style.bottom = - offY +'px';
+       if (/^above$/.test(p)) {
+               hs.setStyles(overlay, {
+                       left: (- this.x.p1 - this.x.cb) +'px',
+                       right: (- this.x.p2 - this.x.cb) +'px',
+                       bottom: '100%',
+                       marginBottom: this.y.cb +'px',
+                       width: 'auto'
+               });
+               this.y.p1 = overlay.offsetHeight;
+
+       } else if (/^below$/.test(p)) {
+               hs.setStyles(overlay, {
+                       position: 'relative',
+                       left: (- this.x.p1 - this.x.cb) +'px',
+                       right: (- this.x.p2 - this.x.cb) +'px',
+                       top: '100%',
+                       marginTop: this.y.cb +'px',
+                       width: 'auto'
+               });
+               this.y.p2 = overlay.offsetHeight;
+               overlay.style.position = 'absolute';
+       }
+},
+
+getOverlays : function() {
+       this.getInline(['heading', 'caption'], true);
+       this.getNumber();
+       if (this.caption) hs.fireEvent(this, 'onAfterGetCaption');
+       if (this.heading) hs.fireEvent(this, 'onAfterGetHeading');
+       if (this.heading && this.dragByHeading) this.heading.className += ' highslide-move';
+       if (hs.showCredits) this.writeCredits();
+       for (var i = 0; i < hs.overlays.length; i++) {
+               var o = hs.overlays[i], tId = o.thumbnailId, sg = o.slideshowGroup;
+               if ((!tId && !sg) || (tId && tId == this.thumbsUserSetId)
+                               || (sg && sg === this.slideshowGroup)) {
+                       this.createOverlay(o);
+               }
+       }
+       var os = [];
+       for (var i = 0; i < this.overlays.length; i++) {
+               var o = hs.$('hsId'+ this.overlays[i]);
+               if (/panel$/.test(o.position)) this.positionOverlay(o);
+               else hs.push(os, o);
+       }
+       for (var i = 0; i < os.length; i++) this.positionOverlay(os[i]);
+       this.gotOverlays = true;
+},
+genOverlayBox : function() {
+       if (!this.overlayBox) this.overlayBox = hs.createElement (
+               'div', {
+                       className: this.wrapperClassName
+               }, {
+                       position : 'absolute',
+                       width: (this.x.size || (this.useBox ? this.width : null)
+                               || this.x.full) +'px',
+                       height: (this.y.size || this.y.full) +'px',
+                       visibility : 'hidden',
+                       overflow : 'hidden',
+                       zIndex : hs.ie ? 4 : 'auto'
+               },
+               hs.container,
+               true
+       );
+},
+sizeOverlayBox : function(doWrapper, doPanels) {
+       var overlayBox = this.overlayBox,
+               x = this.x,
+               y = this.y;
+       hs.setStyles( overlayBox, {
+               width: x.size +'px',
+               height: y.size +'px'
+       });
+       if (doWrapper || doPanels) {
+               for (var i = 0; i < this.overlays.length; i++) {
+                       var o = hs.$('hsId'+ this.overlays[i]);
+                       var ie6 = (hs.ieLt7 || document.compatMode == 'BackCompat');
+                       if (o && /^(above|below)$/.test(o.position)) {
+                               if (ie6) {
+                                       o.style.width = (overlayBox.offsetWidth + 2 * x.cb
+                                               + x.p1 + x.p2) +'px';
+                               }
+                               y[o.position == 'above' ? 'p1' : 'p2'] = o.offsetHeight;
+                       }
+                       if (o && ie6 && /^(left|right)panel$/.test(o.position)) {
+                               o.style.height = (overlayBox.offsetHeight + 2* y.cb) +'px';
+                       }
+               }
+       }
+       if (doWrapper) {
+               hs.setStyles(this.content, {
+                       top: y.p1 +'px'
+               });
+               hs.setStyles(overlayBox, {
+                       top: (y.p1 + y.cb) +'px'
+               });
+       }
+},
+
+showOverlays : function() {
+       var b = this.overlayBox;
+       b.className = '';
+       hs.setStyles(b, {
+               top: (this.y.p1 + this.y.cb) +'px',
+               left: (this.x.p1 + this.x.cb) +'px',
+               overflow : 'visible'
+       });
+       if (hs.safari) b.style.visibility = 'visible';
+       this.wrapper.appendChild (b);
+       for (var i = 0; i < this.overlays.length; i++) {
+               var o = hs.$('hsId'+ this.overlays[i]);
+               o.style.zIndex = o.zIndex || 4;
+               if (!o.hideOnMouseOut || this.mouseIsOver) {
+                       o.style.visibility = 'visible';
+                       hs.setStyles(o, { visibility: 'visible', display: '' });
+                       hs.animate(o, { opacity: o.opacity }, o.dur);
+               }
+       }
+},
+
+destroyOverlays : function() {
+       if (!this.overlays.length) return;
+       if (this.slideshow) {
+               var c = this.slideshow.controls;
+               if (c && hs.getExpander(c) == this) c.parentNode.removeChild(c);
+       }
+       for (var i = 0; i < this.overlays.length; i++) {
+               var o = hs.$('hsId'+ this.overlays[i]);
+               if (o && o.parentNode == hs.viewport && hs.getExpander(o) == this) hs.discardElement(o);
+       }
+       hs.discardElement(this.overlayBox);
+},
+
+
+
+createFullExpand : function () {
+       if (this.slideshow && this.slideshow.controls) {
+               this.slideshow.enable('full-expand');
+               return;
+       }
+       this.fullExpandLabel = hs.createElement(
+               'a', {
+                       href: 'javascript:hs.expanders['+ this.key +'].doFullExpand();',
+                       title: hs.lang.fullExpandTitle,
+                       className: 'highslide-full-expand'
+               }
+       );
+       if (!hs.fireEvent(this, 'onCreateFullExpand')) return;
+
+       this.createOverlay({
+               overlayId: this.fullExpandLabel,
+               position: hs.fullExpandPosition,
+               hideOnMouseOut: true,
+               opacity: hs.fullExpandOpacity
+       });
+},
+
+doFullExpand : function () {
+       try {
+               if (!hs.fireEvent(this, 'onDoFullExpand')) return;
+               if (this.fullExpandLabel) hs.discardElement(this.fullExpandLabel);
+
+               this.focus();
+               var xSize = this.x.size,
+                       ySize = this.y.size;
+               this.resizeTo(this.x.full, this.y.full);
+
+               var xpos = this.x.pos - (this.x.size - xSize) / 2;
+               if (xpos < hs.marginLeft) xpos = hs.marginLeft;
+
+               var ypos = this.y.pos - (this.y.size - ySize) / 2;
+               if (ypos < hs.marginTop) ypos = hs.marginTop;
+
+               this.moveTo(xpos, ypos);
+               this.doShowHide('hidden');
+
+       } catch (e) {
+               this.error(e);
+       }
+},
+
+
+afterClose : function () {
+       this.a.className = this.a.className.replace('highslide-active-anchor', '');
+
+       this.doShowHide('visible');
+               if (this.outline && this.outlineWhileAnimating) this.outline.destroy();
+
+               hs.discardElement(this.wrapper);
+       this.destroyOverlays();
+       if (!hs.viewport.childNodes.length) hs.viewport.style.display = 'none';
+
+       if (this.dimmingOpacity) hs.undim(this.key);
+       hs.fireEvent(this, 'onAfterClose');
+       hs.expanders[this.key] = null;
+       hs.reOrder();
+}
+
+};
+
+
+hs.Slideshow = function (expKey, options) {
+       if (hs.dynamicallyUpdateAnchors !== false) hs.updateAnchors();
+       this.expKey = expKey;
+       for (var x in options) this[x] = options[x];
+       if (this.useControls) this.getControls();
+       if (this.thumbstrip) this.thumbstrip = hs.Thumbstrip(this);
+};
+hs.Slideshow.prototype = {
+getControls: function() {
+       this.controls = hs.createElement('div', { innerHTML: hs.replaceLang(hs.skin.controls) },
+               null, hs.container);
+
+       var buttons = ['play', 'pause', 'previous', 'next', 'move', 'full-expand', 'close'];
+       this.btn = {};
+       var pThis = this;
+       for (var i = 0; i < buttons.length; i++) {
+               this.btn[buttons[i]] = hs.getElementByClass(this.controls, 'li', 'highslide-'+ buttons[i]);
+               this.enable(buttons[i]);
+       }
+       this.btn.pause.style.display = 'none';
+       //this.disable('full-expand');
+},
+checkFirstAndLast: function() {
+       if (this.repeat || !this.controls) return;
+       var exp = hs.expanders[this.expKey],
+               cur = exp.getAnchorIndex(),
+               re = /disabled$/;
+       if (cur == 0)
+               this.disable('previous');
+       else if (re.test(this.btn.previous.getElementsByTagName('a')[0].className))
+               this.enable('previous');
+       if (cur + 1 == hs.anchors.groups[exp.slideshowGroup || 'none'].length) {
+               this.disable('next');
+               this.disable('play');
+       } else if (re.test(this.btn.next.getElementsByTagName('a')[0].className)) {
+               this.enable('next');
+               this.enable('play');
+       }
+},
+enable: function(btn) {
+       if (!this.btn) return;
+       var sls = this, a = this.btn[btn].getElementsByTagName('a')[0], re = /disabled$/;
+       a.onclick = function() {
+               sls[btn]();
+               return false;
+       };
+       if (re.test(a.className)) a.className = a.className.replace(re, '');
+},
+disable: function(btn) {
+       if (!this.btn) return;
+       var a = this.btn[btn].getElementsByTagName('a')[0];
+       a.onclick = function() { return false; };
+       if (!/disabled$/.test(a.className)) a.className += ' disabled';
+},
+hitSpace: function() {
+       if (this.autoplay) this.pause();
+       else this.play();
+},
+play: function(wait) {
+       if (this.btn) {
+               this.btn.play.style.display = 'none';
+               this.btn.pause.style.display = '';
+       }
+
+       this.autoplay = true;
+       if (!wait) hs.next(this.expKey);
+},
+pause: function() {
+       if (this.btn) {
+               this.btn.pause.style.display = 'none';
+               this.btn.play.style.display = '';
+       }
+
+       clearTimeout(this.autoplay);
+       this.autoplay = null;
+},
+previous: function() {
+       this.pause();
+       hs.previous(this.btn.previous);
+},
+next: function() {
+       this.pause();
+       hs.next(this.btn.next);
+},
+move: function() {},
+'full-expand': function() {
+       hs.getExpander().doFullExpand();
+},
+close: function() {
+       hs.close(this.btn.close);
+}
+};
+hs.Thumbstrip = function(slideshow) {
+       function add (exp) {
+               hs.extend(options || {}, {
+                       overlayId: dom,
+                       hsId: 'thumbstrip',
+                       className: 'highslide-thumbstrip-'+ mode +'-overlay ' + (options.className || '')
+               });
+               if (hs.ieLt7) options.fade = 0;
+               exp.createOverlay(options);
+               hs.setStyles(dom.parentNode, { overflow: 'hidden' });
+       };
+
+       function scroll (delta) {
+               selectThumb(undefined, Math.round(delta * dom[isX ? 'offsetWidth' : 'offsetHeight'] * 0.7));
+       };
+
+       function selectThumb (i, scrollBy) {
+               if (i === undefined) for (var j = 0; j < group.length; j++) {
+                       if (group[j] == hs.expanders[slideshow.expKey].a) {
+                               i = j;
+                               break;
+                       }
+               }
+               if (i === undefined) return;
+               var as = dom.getElementsByTagName('a'),
+                       active = as[i],
+                       cell = active.parentNode,
+                       left = isX ? 'Left' : 'Top',
+                       right = isX ? 'Right' : 'Bottom',
+                       width = isX ? 'Width' : 'Height',
+                       offsetLeft = 'offset' + left,
+                       offsetWidth = 'offset' + width,
+                       overlayWidth = div.parentNode.parentNode[offsetWidth],
+                       minTblPos = overlayWidth - table[offsetWidth],
+                       curTblPos = parseInt(table.style[isX ? 'left' : 'top']) || 0,
+                       tblPos = curTblPos,
+                       mgnRight = 20;
+               if (scrollBy !== undefined) {
+                       tblPos = curTblPos - scrollBy;
+
+                       if (minTblPos > 0) minTblPos = 0;
+                       if (tblPos > 0) tblPos = 0;
+                       if (tblPos < minTblPos) tblPos = minTblPos;
+
+
+               } else {
+                       for (var j = 0; j < as.length; j++) as[j].className = '';
+                       active.className = 'highslide-active-anchor';
+                       var activeLeft = i > 0 ? as[i - 1].parentNode[offsetLeft] : cell[offsetLeft],
+                               activeRight = cell[offsetLeft] + cell[offsetWidth] +
+                                       (as[i + 1] ? as[i + 1].parentNode[offsetWidth] : 0);
+                       if (activeRight > overlayWidth - curTblPos) tblPos = overlayWidth - activeRight;
+                       else if (activeLeft < -curTblPos) tblPos = -activeLeft;
+               }
+               var markerPos = cell[offsetLeft] + (cell[offsetWidth] - marker[offsetWidth]) / 2 + tblPos;
+               hs.animate(table, isX ? { left: tblPos } : { top: tblPos }, null, 'easeOutQuad');
+               hs.animate(marker, isX ? { left: markerPos } : { top: markerPos }, null, 'easeOutQuad');
+               scrollUp.style.display = tblPos < 0 ? 'block' : 'none';
+               scrollDown.style.display = (tblPos > minTblPos)  ? 'block' : 'none';
+
+       };
+
+
+       // initialize
+       var group = hs.anchors.groups[hs.expanders[slideshow.expKey].slideshowGroup || 'none'],
+               options = slideshow.thumbstrip,
+               mode = options.mode || 'horizontal',
+               floatMode = (mode == 'float'),
+               tree = floatMode ? ['div', 'ul', 'li', 'span'] : ['table', 'tbody', 'tr', 'td'],
+               isX = (mode == 'horizontal'),
+               dom = hs.createElement('div', {
+                               className: 'highslide-thumbstrip highslide-thumbstrip-'+ mode,
+                               innerHTML:
+                                       '<div class="highslide-thumbstrip-inner">'+
+                                       '<'+ tree[0] +'><'+ tree[1] +'></'+ tree[1] +'></'+ tree[0] +'></div>'+
+                                       '<div class="highslide-scroll-up"><div></div></div>'+
+                                       '<div class="highslide-scroll-down"><div></div></div>'+
+                                       '<div class="highslide-marker"><div></div></div>'
+                       }, {
+                               display: 'none'
+                       }, hs.container),
+               domCh = dom.childNodes,
+               div = domCh[0],
+               scrollUp = domCh[1],
+               scrollDown = domCh[2],
+               marker = domCh[3],
+               table = div.firstChild,
+               tbody = dom.getElementsByTagName(tree[1])[0],
+               tr;
+       for (var i = 0; i < group.length; i++) {
+               if (i == 0 || !isX) tr = hs.createElement(tree[2], null, null, tbody);
+               (function(){
+                       var a = group[i],
+                               cell = hs.createElement(tree[3], null, null, tr),
+                               pI = i;
+                       hs.createElement('a', {
+                               href: a.href,
+                               title: a.title,
+                               onclick: function() {
+                                       if (/highslide-active-anchor/.test(this.className)) return false;
+                                       hs.getExpander(this).focus();
+                                       return hs.transit(a);
+                               },
+                               innerHTML: hs.stripItemFormatter ? hs.stripItemFormatter(a) : a.innerHTML
+                       }, null, cell);
+               })();
+       }
+       if (!floatMode) {
+               scrollUp.onclick = function () { scroll(-1); };
+               scrollDown.onclick = function() { scroll(1); };
+               hs.addEventListener(tbody, document.onmousewheel !== undefined ?
+                               'mousewheel' : 'DOMMouseScroll', function(e) {
+                       var delta = 0;
+                       e = e || window.event;
+                       if (e.wheelDelta) {
+                               delta = e.wheelDelta/120;
+                               if (hs.opera) delta = -delta;
+                       } else if (e.detail) {
+                               delta = -e.detail/3;
+                       }
+                       if (delta) scroll(-delta * 0.2);
+                       if (e.preventDefault) e.preventDefault();
+                       e.returnValue = false;
+               });
+       }
+
+       return {
+               add: add,
+               selectThumb: selectThumb
+       }
+};
+hs.langDefaults = hs.lang;
+// history
+var HsExpander = hs.Expander;
+if (hs.ie && window == window.top) {
+       (function () {
+               try {
+                       document.documentElement.doScroll('left');
+               } catch (e) {
+                       setTimeout(arguments.callee, 50);
+                       return;
+               }
+               hs.ready();
+       })();
+}
+hs.addEventListener(document, 'DOMContentLoaded', hs.ready);
+hs.addEventListener(window, 'load', hs.ready);
+
+// set handlers
+hs.addEventListener(document, 'ready', function() {
+       if (hs.expandCursor || hs.dimmingOpacity) {
+               var style = hs.createElement('style', { type: 'text/css' }, null,
+                       document.getElementsByTagName('HEAD')[0]),
+                       backCompat = document.compatMode == 'BackCompat';
+
+
+               function addRule(sel, dec) {
+                       if (hs.ie && (hs.uaVersion < 9 || backCompat)) {
+                               var last = document.styleSheets[document.styleSheets.length - 1];
+                               if (typeof(last.addRule) == "object") last.addRule(sel, dec);
+                       } else {
+                               style.appendChild(document.createTextNode(sel + " {" + dec + "}"));
+                       }
+               }
+               function fix(prop) {
+                       return 'expression( ( ( ignoreMe = document.documentElement.'+ prop +
+                               ' ? document.documentElement.'+ prop +' : document.body.'+ prop +' ) ) + \'px\' );';
+               }
+               if (hs.expandCursor) addRule ('.highslide img',
+                       'cursor: url('+ hs.graphicsDir + hs.expandCursor +'), pointer !important;');
+               addRule ('.highslide-viewport-size',
+                       hs.ie && (hs.uaVersion < 7 || backCompat) ?
+                               'position: absolute; '+
+                               'left:'+ fix('scrollLeft') +
+                               'top:'+ fix('scrollTop') +
+                               'width:'+ fix('clientWidth') +
+                               'height:'+ fix('clientHeight') :
+                               'position: fixed; width: 100%; height: 100%; left: 0; top: 0');
+       }
+});
+hs.addEventListener(window, 'resize', function() {
+       hs.getPageSize();
+       if (hs.viewport) for (var i = 0; i < hs.viewport.childNodes.length; i++) {
+               var node = hs.viewport.childNodes[i],
+                       exp = hs.getExpander(node);
+               exp.positionOverlay(node);
+               if (node.hsId == 'thumbstrip') exp.slideshow.thumbstrip.selectThumb();
+       }
+});
+hs.addEventListener(document, 'mousemove', function(e) {
+       hs.mouse = { x: e.clientX, y: e.clientY };
+});
+hs.addEventListener(document, 'mousedown', hs.mouseClickHandler);
+hs.addEventListener(document, 'mouseup', hs.mouseClickHandler);
+
+hs.addEventListener(document, 'ready', hs.getAnchors);
+hs.addEventListener(window, 'load', hs.preloadImages);
+}
diff --git a/highslide/highslide.css b/highslide/highslide.css
new file mode 100644 (file)
index 0000000..452b862
--- /dev/null
@@ -0,0 +1,888 @@
+/**
+* @file: highslide.css 
+* @version: 4.1.13
+*/
+.highslide-container div {
+       font-family: Verdana, Helvetica;
+       font-size: 10pt;
+}
+.highslide-container table {
+       background: none;
+       table-layout: auto;
+}
+.highslide {
+       outline: none;
+       text-decoration: none;
+}
+.highslide img {
+       border: 2px solid silver;
+}
+.highslide:hover img {
+       border-color: gray;
+}
+.highslide-active-anchor img {
+       visibility: hidden;
+}
+.highslide-gallery .highslide-active-anchor img {
+       border-color: black;
+       visibility: visible;
+       cursor: default;
+}
+.highslide-image {
+       border-width: 2px;
+       border-style: solid;
+       border-color: white;
+}
+.highslide-wrapper, .highslide-outline {
+       background: white;
+}
+.glossy-dark {
+       background: #111;
+}
+
+.highslide-image-blur {
+}
+.highslide-number {
+       font-weight: bold;
+       color: gray;
+       font-size: .9em;
+}
+.highslide-caption {
+       display: none;
+       font-size: 1em;
+       padding: 5px;
+       /*background: white;*/
+}
+.highslide-heading {
+       display: none;
+       font-weight: bold;
+       margin: 0.4em;
+}
+.highslide-dimming {
+       /*position: absolute;*/
+       background: black;
+}
+a.highslide-full-expand {
+   background: url(graphics/fullexpand.gif) no-repeat;
+   display: block;
+   margin: 0 10px 10px 0;
+   width: 34px;
+   height: 34px;
+}
+.highslide-loading {
+       display: block;
+       color: black;
+       font-size: 9px;
+       font-weight: bold;
+       text-transform: uppercase;
+       text-decoration: none;
+       padding: 3px;
+       border: 1px solid white;
+       background-color: white;
+       padding-left: 22px;
+       background-image: url(graphics/loader.white.gif);
+       background-repeat: no-repeat;
+       background-position: 3px 1px;
+}
+a.highslide-credits,
+a.highslide-credits i {
+       padding: 2px;
+       color: silver;
+       text-decoration: none;
+       font-size: 10px;
+}
+a.highslide-credits:hover,
+a.highslide-credits:hover i {
+       color: white;
+       background-color: gray;
+}
+.highslide-move, .highslide-move * {
+       cursor: move;
+}
+
+.highslide-viewport {
+       display: none;
+       position: fixed;
+       width: 100%;
+       height: 100%;
+       z-index: 1;
+       background: none;
+       left: 0;
+       top: 0;
+}
+.highslide-overlay {
+       display: none;
+}
+.hidden-container {
+       display: none;
+}
+/* Example of a semitransparent, offset closebutton */
+.closebutton {
+       position: relative;
+       top: -15px;
+       left: 15px;
+       width: 30px;
+       height: 30px;
+       cursor: pointer;
+       background: url(graphics/close.png);
+       /* NOTE! For IE6, you also need to update the highslide-ie6.css file. */
+}
+
+/*****************************************************************************/
+/* Thumbnail boxes for the galleries.                                        */
+/* Remove these if you are not using a gallery.                              */
+/*****************************************************************************/
+.highslide-gallery ul {
+       list-style-type: none;
+       margin: 0;
+       padding: 0;
+}
+.highslide-gallery ul li {
+       display: block;
+       position: relative;
+       float: left;
+       width: 106px;
+       height: 106px;
+       margin: 2px;
+       padding: 0;
+       line-height: 0;
+       overflow: hidden;
+}
+.highslide-gallery ul a {
+       position: absolute;
+       top: 50%;
+       left: 50%;
+}
+.highslide-gallery ul img {
+       position: relative;
+       top: -50%;
+       left: -50%;
+}
+html>/**/body .highslide-gallery ul li {
+       display: table;
+       text-align: center;
+}
+html>/**/body .highslide-gallery ul li {
+       text-align: center;
+}
+html>/**/body .highslide-gallery ul a {
+       position: static;
+       display: table-cell;
+       vertical-align: middle;
+}
+html>/**/body .highslide-gallery ul img {
+       position: static;
+}
+
+/*****************************************************************************/
+/* Controls for the galleries.                                                                                      */
+/* Remove these if you are not using a gallery                                                      */
+/*****************************************************************************/
+.highslide-controls {
+       width: 195px;
+       height: 40px;
+       background: url(graphics/controlbar-white.gif) 0 -90px no-repeat;
+       margin: 20px 15px 10px 0;
+}
+.highslide-controls ul {
+       position: relative;
+       left: 15px;
+       height: 40px;
+       list-style: none;
+       margin: 0;
+       padding: 0;
+       background: url(graphics/controlbar-white.gif) right -90px no-repeat;
+
+}
+.highslide-controls li {
+       float: left;
+       padding: 5px 0;
+       margin:0;
+       list-style: none;
+}
+.highslide-controls a {
+       background-image: url(graphics/controlbar-white.gif);
+       display: block;
+       float: left;
+       height: 30px;
+       width: 30px;
+       outline: none;
+}
+.highslide-controls a.disabled {
+       cursor: default;
+}
+.highslide-controls a.disabled span {
+       cursor: default;
+}
+.highslide-controls a span {
+       /* hide the text for these graphic buttons */
+       display: none;
+       cursor: pointer;
+}
+
+
+/* The CSS sprites for the controlbar - see http://www.google.com/search?q=css+sprites */
+.highslide-controls .highslide-previous a {
+       background-position: 0 0;
+}
+.highslide-controls .highslide-previous a:hover {
+       background-position: 0 -30px;
+}
+.highslide-controls .highslide-previous a.disabled {
+       background-position: 0 -60px !important;
+}
+.highslide-controls .highslide-play a {
+       background-position: -30px 0;
+}
+.highslide-controls .highslide-play a:hover {
+       background-position: -30px -30px;
+}
+.highslide-controls .highslide-play a.disabled {
+       background-position: -30px -60px !important;
+}
+.highslide-controls .highslide-pause a {
+       background-position: -60px 0;
+}
+.highslide-controls .highslide-pause a:hover {
+       background-position: -60px -30px;
+}
+.highslide-controls .highslide-next a {
+       background-position: -90px 0;
+}
+.highslide-controls .highslide-next a:hover {
+       background-position: -90px -30px;
+}
+.highslide-controls .highslide-next a.disabled {
+       background-position: -90px -60px !important;
+}
+.highslide-controls .highslide-move a {
+       background-position: -120px 0;
+}
+.highslide-controls .highslide-move a:hover {
+       background-position: -120px -30px;
+}
+.highslide-controls .highslide-full-expand a {
+       background-position: -150px 0;
+}
+.highslide-controls .highslide-full-expand a:hover {
+       background-position: -150px -30px;
+}
+.highslide-controls .highslide-full-expand a.disabled {
+       background-position: -150px -60px !important;
+}
+.highslide-controls .highslide-close a {
+       background-position: -180px 0;
+}
+.highslide-controls .highslide-close a:hover {
+       background-position: -180px -30px;
+}
+
+/*****************************************************************************/
+/* Styles for the HTML popups                                                                                       */
+/* Remove these if you are not using Highslide HTML                                                 */
+/*****************************************************************************/
+.highslide-maincontent {
+       display: none;
+}
+.highslide-html {
+       background-color: white;
+}
+.mobile .highslide-html {
+       border: 1px solid silver;
+}
+.highslide-html-content {
+       display: none;
+       width: 400px;
+       padding: 0 5px 5px 5px;
+}
+.highslide-header {
+       padding-bottom: 5px;
+}
+.highslide-header ul {
+       margin: 0;
+       padding: 0;
+       text-align: right;
+}
+.highslide-header ul li {
+       display: inline;
+       padding-left: 1em;
+}
+.highslide-header ul li.highslide-previous, .highslide-header ul li.highslide-next {
+       display: none;
+}
+.highslide-header a {
+       font-weight: bold;
+       color: gray;
+       text-transform: uppercase;
+       text-decoration: none;
+}
+.highslide-header a:hover {
+       color: black;
+}
+.highslide-header .highslide-move a {
+       cursor: move;
+}
+.highslide-footer {
+       height: 16px;
+}
+.highslide-footer .highslide-resize {
+       display: block;
+       float: right;
+       margin-top: 5px;
+       height: 11px;
+       width: 11px;
+       background: url(graphics/resize.gif) no-repeat;
+}
+.highslide-footer .highslide-resize span {
+       display: none;
+}
+.highslide-body {
+}
+.highslide-resize {
+       cursor: nw-resize;
+}
+
+/*****************************************************************************/
+/* Styles for the Individual wrapper class names.                                                       */
+/* See www.highslide.com/ref/hs.wrapperClassName                                                        */
+/* You can safely remove the class name themes you don't use                            */
+/*****************************************************************************/
+
+/* hs.wrapperClassName = 'draggable-header' */
+.draggable-header .highslide-header {
+       height: 18px;
+       border-bottom: 1px solid #dddddd;
+}
+.draggable-header .highslide-heading {
+       position: absolute;
+       margin: 2px 0.4em;
+}
+
+.draggable-header .highslide-header .highslide-move {
+       cursor: move;
+       display: block;
+       height: 16px;
+       position: absolute;
+       right: 24px;
+       top: 0;
+       width: 100%;
+       z-index: 1;
+}
+.draggable-header .highslide-header .highslide-move * {
+       display: none;
+}
+.draggable-header .highslide-header .highslide-close {
+       position: absolute;
+       right: 2px;
+       top: 2px;
+       z-index: 5;
+       padding: 0;
+}
+.draggable-header .highslide-header .highslide-close a {
+       display: block;
+       height: 16px;
+       width: 16px;
+       background-image: url(graphics/closeX.png);
+}
+.draggable-header .highslide-header .highslide-close a:hover {
+       background-position: 0 16px;
+}
+.draggable-header .highslide-header .highslide-close span {
+       display: none;
+}
+.draggable-header .highslide-maincontent {
+       padding-top: 1em;
+}
+
+/* hs.wrapperClassName = 'titlebar' */
+.titlebar .highslide-header {
+       height: 18px;
+       border-bottom: 1px solid #dddddd;
+}
+.titlebar .highslide-heading {
+       position: absolute;
+       width: 90%;
+       margin: 1px 0 1px 5px;
+       color: #666666;
+}
+
+.titlebar .highslide-header .highslide-move {
+       cursor: move;
+       display: block;
+       height: 16px;
+       position: absolute;
+       right: 24px;
+       top: 0;
+       width: 100%;
+       z-index: 1;
+}
+.titlebar .highslide-header .highslide-move * {
+       display: none;
+}
+.titlebar .highslide-header li {
+       position: relative;
+       top: 3px;
+       z-index: 2;
+       padding: 0 0 0 1em;
+}
+.titlebar .highslide-maincontent {
+       padding-top: 1em;
+}
+
+/* hs.wrapperClassName = 'no-footer' */
+.no-footer .highslide-footer {
+       display: none;
+}
+
+/* hs.wrapperClassName = 'wide-border' */
+.wide-border {
+       background: white;
+}
+.wide-border .highslide-image {
+       border-width: 10px;
+}
+.wide-border .highslide-caption {
+       padding: 0 10px 10px 10px;
+}
+
+/* hs.wrapperClassName = 'borderless' */
+.borderless .highslide-image {
+       border: none;
+}
+.borderless .highslide-caption {
+       border-bottom: 1px solid white;
+       border-top: 1px solid white;
+       background: silver;
+}
+
+/* hs.wrapperClassName = 'outer-glow' */
+.outer-glow {
+       background: #444;
+}
+.outer-glow .highslide-image {
+       border: 5px solid #444444;
+}
+.outer-glow .highslide-caption {
+       border: 5px solid #444444;
+       border-top: none;
+       padding: 5px;
+       background-color: gray;
+}
+
+/* hs.wrapperClassName = 'colored-border' */
+.colored-border {
+       background: white;
+}
+.colored-border .highslide-image {
+       border: 2px solid green;
+}
+.colored-border .highslide-caption {
+       border: 2px solid green;
+       border-top: none;
+}
+
+/* hs.wrapperClassName = 'dark' */
+.dark {
+       background: #111;
+}
+.dark .highslide-image {
+       border-color: black black #202020 black;
+       background: gray;
+}
+.dark .highslide-caption {
+       color: white;
+       background: #111;
+}
+.dark .highslide-controls,
+.dark .highslide-controls ul,
+.dark .highslide-controls a {
+       background-image: url(graphics/controlbar-black-border.gif);
+}
+
+/* hs.wrapperClassName = 'floating-caption' */
+.floating-caption .highslide-caption {
+       position: absolute;
+       padding: 1em 0 0 0;
+       background: none;
+       color: white;
+       border: none;
+       font-weight: bold;
+}
+
+/* hs.wrapperClassName = 'controls-in-heading' */
+.controls-in-heading .highslide-heading {
+       color: gray;
+       font-weight: bold;
+       height: 20px;
+       overflow: hidden;
+       cursor: default;
+       padding: 0 0 0 22px;
+       margin: 0;
+       background: url(graphics/icon.gif) no-repeat 0 1px;
+}
+.controls-in-heading .highslide-controls {
+       width: 105px;
+       height: 20px;
+       position: relative;
+       margin: 0;
+       top: -23px;
+       left: 7px;
+       background: none;
+}
+.controls-in-heading .highslide-controls ul {
+       position: static;
+       height: 20px;
+       background: none;
+}
+.controls-in-heading .highslide-controls li {
+       padding: 0;
+}
+.controls-in-heading .highslide-controls a {
+       background-image: url(graphics/controlbar-white-small.gif);
+       height: 20px;
+       width: 20px;
+}
+
+.controls-in-heading .highslide-controls .highslide-move {
+       display: none;
+}
+
+.controls-in-heading .highslide-controls .highslide-previous a {
+       background-position: 0 0;
+}
+.controls-in-heading .highslide-controls .highslide-previous a:hover {
+       background-position: 0 -20px;
+}
+.controls-in-heading .highslide-controls .highslide-previous a.disabled {
+       background-position: 0 -40px !important;
+}
+.controls-in-heading .highslide-controls .highslide-play a {
+       background-position: -20px 0;
+}
+.controls-in-heading .highslide-controls .highslide-play a:hover {
+       background-position: -20px -20px;
+}
+.controls-in-heading .highslide-controls .highslide-play a.disabled {
+       background-position: -20px -40px !important;
+}
+.controls-in-heading .highslide-controls .highslide-pause a {
+       background-position: -40px 0;
+}
+.controls-in-heading .highslide-controls .highslide-pause a:hover {
+       background-position: -40px -20px;
+}
+.controls-in-heading .highslide-controls .highslide-next a {
+       background-position: -60px 0;
+}
+.controls-in-heading .highslide-controls .highslide-next a:hover {
+       background-position: -60px -20px;
+}
+.controls-in-heading .highslide-controls .highslide-next a.disabled {
+       background-position: -60px -40px !important;
+}
+.controls-in-heading .highslide-controls .highslide-full-expand a {
+       background-position: -100px 0;
+}
+.controls-in-heading .highslide-controls .highslide-full-expand a:hover {
+       background-position: -100px -20px;
+}
+.controls-in-heading .highslide-controls .highslide-full-expand a.disabled {
+       background-position: -100px -40px !important;
+}
+.controls-in-heading .highslide-controls .highslide-close a {
+       background-position: -120px 0;
+}
+.controls-in-heading .highslide-controls .highslide-close a:hover {
+       background-position: -120px -20px;
+}
+
+/*****************************************************************************/
+/* Styles for text based controls.                                                                  */
+/* You can safely remove this if you don't use text based controls                      */
+/*****************************************************************************/
+
+.text-controls .highslide-controls {
+       width: auto;
+       height: auto;
+       margin: 0;
+       text-align: center;
+       background: none;
+}
+.text-controls ul {
+       position: static;
+       background: none;
+       height: auto;
+       left: 0;
+}
+.text-controls .highslide-move {
+       display: none;
+}
+.text-controls li {
+    background-image: url(graphics/controlbar-text-buttons.png);
+       background-position: right top !important;
+       padding: 0;
+       margin-left: 15px;
+       display: block;
+       width: auto;
+}
+.text-controls a {
+    background: url(graphics/controlbar-text-buttons.png) no-repeat;
+    background-position: left top !important;
+    position: relative;
+    left: -10px;
+       display: block;
+       width: auto;
+       height: auto;
+       text-decoration: none !important;
+}
+.text-controls a span {
+       background: url(graphics/controlbar-text-buttons.png) no-repeat;
+    margin: 1px 2px 1px 10px;
+       display: block;
+    min-width: 4em;
+    height: 18px;
+    line-height: 18px;
+       padding: 1px 0 1px 18px;
+    color: #333;
+       font-family: "Trebuchet MS", Arial, sans-serif;
+       font-size: 12px;
+       font-weight: bold;
+       white-space: nowrap;
+}
+.text-controls .highslide-next {
+       margin-right: 1em;
+}
+.text-controls .highslide-full-expand a span {
+       min-width: 0;
+       margin: 1px 0;
+       padding: 1px 0 1px 10px;
+}
+.text-controls .highslide-close a span {
+       min-width: 0;
+}
+.text-controls a:hover span {
+       color: black;
+}
+.text-controls a.disabled span {
+       color: #999;
+}
+
+.text-controls .highslide-previous span {
+       background-position: 0 -40px;
+}
+.text-controls .highslide-previous a.disabled {
+       background-position: left top !important;
+}
+.text-controls .highslide-previous a.disabled span {
+       background-position: 0 -140px;
+}
+.text-controls .highslide-play span {
+       background-position: 0 -60px;
+}
+.text-controls .highslide-play a.disabled {
+       background-position: left top !important;
+}
+.text-controls .highslide-play a.disabled span {
+       background-position: 0 -160px;
+}
+.text-controls .highslide-pause span {
+       background-position: 0 -80px;
+}
+.text-controls .highslide-next span {
+       background-position: 0 -100px;
+}
+.text-controls .highslide-next a.disabled {
+       background-position: left top !important;
+}
+.text-controls .highslide-next a.disabled span {
+       background-position: 0 -200px;
+}
+.text-controls .highslide-full-expand span {
+       background: none;
+}
+.text-controls .highslide-full-expand a.disabled {
+       background-position: left top !important;
+}
+.text-controls .highslide-close span {
+       background-position: 0 -120px;
+}
+
+
+/*****************************************************************************/
+/* Styles for the thumbstrip.                                                                       */
+/* See www.highslide.com/ref/hs.addSlideshow                                                            */
+/* You can safely remove this if you don't use a thumbstrip                             */
+/*****************************************************************************/
+
+.highslide-thumbstrip {
+       height: 100%;
+       direction: ltr;
+}
+.highslide-thumbstrip div {
+       overflow: hidden;
+}
+.highslide-thumbstrip table {
+       position: relative;
+       padding: 0;
+       border-collapse: collapse;
+}
+.highslide-thumbstrip td {
+       padding: 1px;
+       /*text-align: center;*/
+}
+.highslide-thumbstrip a {
+       outline: none;
+}
+.highslide-thumbstrip img {
+       display: block;
+       border: 1px solid gray;
+       margin: 0 auto;
+}
+.highslide-thumbstrip .highslide-active-anchor img {
+       visibility: visible;
+}
+.highslide-thumbstrip .highslide-marker {
+       position: absolute;
+       width: 0;
+       height: 0;
+       border-width: 0;
+       border-style: solid;
+       border-color: transparent; /* change this to actual background color in highslide-ie6.css */
+}
+.highslide-thumbstrip-horizontal div {
+       width: auto;
+       /* width: 100% breaks in small strips in IE */
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-up {
+       display: none;
+       position: absolute;
+       top: 3px;
+       left: 3px;
+       width: 25px;
+       height: 42px;
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-up div {
+       margin-bottom: 10px;
+       cursor: pointer;
+       background: url(graphics/scrollarrows.png) left center no-repeat;
+       height: 42px;
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-down {
+       display: none;
+       position: absolute;
+       top: 3px;
+       right: 3px;
+       width: 25px;
+       height: 42px;
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-down div {
+       margin-bottom: 10px;
+       cursor: pointer;
+       background: url(graphics/scrollarrows.png) center right no-repeat;
+       height: 42px;
+}
+.highslide-thumbstrip-horizontal table {
+       margin: 2px 0 10px 0;
+}
+.highslide-viewport .highslide-thumbstrip-horizontal table {
+       margin-left: 10px;
+}
+.highslide-thumbstrip-horizontal img {
+       width: auto;
+       height: 40px;
+}
+.highslide-thumbstrip-horizontal .highslide-marker {
+       top: 47px;
+       border-left-width: 6px;
+       border-right-width: 6px;
+       border-bottom: 6px solid gray;
+}
+.highslide-viewport .highslide-thumbstrip-horizontal .highslide-marker {
+       margin-left: 10px;
+}
+.dark .highslide-thumbstrip-horizontal .highslide-marker, .highslide-viewport .highslide-thumbstrip-horizontal .highslide-marker {
+       border-bottom-color: white !important;
+}
+
+.highslide-thumbstrip-vertical-overlay {
+       overflow: hidden !important;
+}
+.highslide-thumbstrip-vertical div {
+       height: 100%;
+}
+.highslide-thumbstrip-vertical a {
+       display: block;
+}
+.highslide-thumbstrip-vertical .highslide-scroll-up {
+       display: none;
+       position: absolute;
+       top: 0;
+       left: 0;
+       width: 100%;
+       height: 25px;
+}
+.highslide-thumbstrip-vertical .highslide-scroll-up div {
+       margin-left: 10px;
+       cursor: pointer;
+       background: url(graphics/scrollarrows.png) top center no-repeat;
+       height: 25px;
+}
+.highslide-thumbstrip-vertical .highslide-scroll-down {
+       display: none;
+       position: absolute;
+       bottom: 0;
+       left: 0;
+       width: 100%;
+       height: 25px;
+}
+.highslide-thumbstrip-vertical .highslide-scroll-down div {
+       margin-left: 10px;
+       cursor: pointer;
+       background: url(graphics/scrollarrows.png) bottom center no-repeat;
+       height: 25px;
+}
+.highslide-thumbstrip-vertical table {
+       margin: 10px 0 0 10px;
+}
+.highslide-thumbstrip-vertical img {
+       width: 60px; /* t=5481 */
+}
+.highslide-thumbstrip-vertical .highslide-marker {
+       left: 0;
+       margin-top: 8px;
+       border-top-width: 6px;
+       border-bottom-width: 6px;
+       border-left: 6px solid gray;
+}
+.dark .highslide-thumbstrip-vertical .highslide-marker, .highslide-viewport .highslide-thumbstrip-vertical .highslide-marker {
+       border-left-color: white;
+}
+
+.highslide-viewport .highslide-thumbstrip-float {
+       overflow: auto;
+}
+.highslide-thumbstrip-float ul {
+       margin: 2px 0;
+       padding: 0;
+}
+.highslide-thumbstrip-float li {
+       display: block;
+       height: 60px;
+       margin: 0 2px;
+       list-style: none;
+       float: left;
+}
+.highslide-thumbstrip-float img {
+       display: inline;
+       border-color: silver;
+       max-height: 56px;
+}
+.highslide-thumbstrip-float .highslide-active-anchor img {
+       border-color: black;
+}
+.highslide-thumbstrip-float .highslide-scroll-up div, .highslide-thumbstrip-float .highslide-scroll-down div {
+       display: none;
+}
+.highslide-thumbstrip-float .highslide-marker {
+       display: none;
+}
diff --git a/highslide/nav/back.png b/highslide/nav/back.png
new file mode 100644 (file)
index 0000000..501909c
Binary files /dev/null and b/highslide/nav/back.png differ
diff --git a/highslide/nav/next.png b/highslide/nav/next.png
new file mode 100644 (file)
index 0000000..bdd1011
Binary files /dev/null and b/highslide/nav/next.png differ
diff --git a/highslide/nav/prev.png b/highslide/nav/prev.png
new file mode 100644 (file)
index 0000000..68b2514
Binary files /dev/null and b/highslide/nav/prev.png differ
diff --git a/lib/UCW/Gallery.pm b/lib/UCW/Gallery.pm
new file mode 100644 (file)
index 0000000..748fe3c
--- /dev/null
@@ -0,0 +1,216 @@
+# Simple Photo Gallery
+# (c) 2003--2015 Martin Mares <mj@ucw.cz>
+
+package UCW::Gallery;
+
+use strict;
+use warnings;
+
+use File::Spec;
+use Storable;
+
+### Class methods ###
+
+sub new($) {
+       my ($class) = @_;
+       my $self = { };
+       $self->{cfg} = {
+               # Directories
+               OrigDir => '.',                 # Original images
+               PhotoDir => 'photo',            # Scaled-down photos for web
+               CacheDir => 'cache',            # Cache with meta-data and thumbnails
+
+               # URL prefixes
+               PhotoUrlPrefix => 'photo/',
+               ThumbUrlPrefix => 'thumb/',
+               ThemeUrlPrefix => 'gal/',
+
+               # Processing machinery settings
+               ScanDefaultTransform => 's',
+               PhotoMaxWidth => 1024,
+               PhotoMaxHeight => 1024,
+               ThumbFormats => [ "114x94" ],   # Built-in themes use the first size,
+                                               # but more can be generated
+               CacheExif => 0,                 # Cache selected EXIF meta-data
+               CacheHashes => 1,               # Let gal-scan cache file hashes
+
+               # Titles and navigation
+               Title => 'An Unnamed Gallery',
+               SubTitle => "",
+               ParentURL => '../',
+               BackURL => "",
+               FwdURL => "",
+
+               # Hacks
+               GeoHack => 0,
+       };
+       return bless $self, $class;
+}
+
+sub load_config($) {
+       my $cfg = "./gallery.cf";
+       my $self = do $cfg;
+       unless (defined $self) {
+               if ($@) {
+                       die "Error parsing $cfg: $@";
+               } elsif ($!) {
+                       die "Cannot load $cfg: $!\n";
+               } else {
+                       die "Cannot load $cfg, check that it returns true\n";
+               }
+       }
+       return $self;
+}
+
+### Object methods ###
+
+sub get($$) {
+       my ($self, $key) = @_;
+       if (exists $self->{cfg}->{$key}) {
+               my $val = $self->{cfg}->{$key};
+               defined $val or warn "Gallery: Config item $key is not set\n";
+               return $val;
+       } else {
+               warn "Gallery: Config item $key does not exist\n";
+               return;
+       }
+}
+
+sub try_get($$) {
+       my ($self, $key) = @_;
+       return $self->{cfg}->{$key};
+}
+
+sub def($@) {
+       my $self = shift;
+       while (my $key = shift @_) {
+               my $val = shift @_;
+               $self->{cfg}->{$key} //= $val;
+       }
+}
+
+sub set($@) {
+       my $self = shift;
+       while (my $key = shift @_) {
+               $self->{cfg}->{$key} = shift @_;
+       }
+}
+
+sub get_config_keys($) {
+       my ($self) = @_;
+       return keys %{$self->{cfg}};
+}
+
+our %list_attrs = (
+       'file' => 0,            # 0 = not permitted as extended attribute
+       'id' => 0,
+       'orientation' => 1,     # 1 = aliases for normal attributes
+       'title' => 1,
+       'xf' => 2,              # 2 = ... and propagated to gal-gen
+       'lat' => 3,             # 3 = normal extended attributes, propagated to gal-gen
+       'lon' => 3,
+       'alt' => 3,
+       't' => 3,
+);
+
+sub write_list($$$) {
+       my ($self, $file, $images) = @_;
+       open my $fh, '>:utf8', "$file.new" or die "Cannot create $file.new: $!\n";
+       for my $i (@$images) {
+               print $fh join("\t",
+                       $i->{file},
+                       $i->{id},
+                       $i->{orientation},
+                       $i->{xf},
+                       ($i->{title} eq '' ? '-' : $i->{title}),
+               ), "\n";
+               for my $k (keys %$i) {
+                       print $fh "\t$k=", $i->{$k}, "\n" if $list_attrs{$k} >= 3;
+               }
+       }
+       close $fh;
+       rename "$file.new", $file or die "Cannot rename $file.new to $file: $!\n";
+}
+
+sub read_list_fh($$) {
+       my ($self, $fh) = @_;
+       my @images = ();
+       while (<$fh>) {
+               chomp;
+               /^$/ and next;
+               /^#/ and next;
+               if (/^\t/) {
+                       @images or die "Misplaced continuation line before first image\n";
+                       if (my ($k, $v) = /^\t+(.*?)=(.*)/) {
+                               # Continutation of previous line
+                               my $i = $images[-1];
+                               if ($list_attrs{$k}) {
+                                       $i->{$k} = $v;
+                               } else {
+                                       print STDERR "Ignoring unknown attribute $k for ", $i->{file}, "\n";
+                               }
+                       } else {
+                               die "Invalid continuation line. Expecting 'key=value'.\n";
+                       }
+               } else {
+                       my $i = {};
+                       ($i->{file}, $i->{id}, $i->{orientation}, $i->{xf}, $i->{title}) = split /\t/;
+                       if (!defined $i->{title} || $i->{title} eq '-') { $i->{title} = ""; }
+                       push @images, $i;
+               }
+       }
+       return \@images;
+}
+
+sub read_list($$) {
+       my ($self, $file) = @_;
+       open my $fh, '<:utf8', $file or return;
+       my $list = $self->read_list_fh($fh);
+       close $fh;
+       return $list;
+}
+
+sub write_meta($$) {
+       my ($self, $file, $meta) = @_;
+       open my $fh, '>', "$file.new" or die "Cannot create $file.new: $!\n";
+       Storable::nstore_fd($meta, $fh);
+       close $fh;
+       rename "$file.new", $file or die "Cannot rename $file.new to $file: $!\n";
+}
+
+sub read_meta($) {
+       my ($self, $file) = @_;
+       open my $fh, '<', $file or die "Cannot read $file: $!\n";
+       my $meta = Storable::fd_retrieve($fh) or die "Cannot parse $file\n";
+       close $fh;
+       return $meta;
+}
+
+sub photo_meta_name($) {
+       my ($self) = @_;
+       return File::Spec->catfile($self->get('PhotoDir'), 'gallery.meta');
+}
+
+sub cache_meta_name($) {
+       my ($self) = @_;
+       return File::Spec->catfile($self->get('CacheDir'), 'cache.meta');
+}
+
+sub thumb_fmt_to_size($$) {
+       my ($self, $fmt) = @_;
+       my ($tw, $th) = ($fmt =~ m{^(\d+)x(\d+)$}) or die "Cannot parse thumbnail format $fmt\n";
+       return ($tw, $th);
+}
+
+sub photo_file_name($$$) {
+       my ($self, $photo_meta, $id) = @_;
+       return $id . '.' . ($photo_meta->{fmt} // 'jpg');
+}
+
+sub photo_name($$$) {
+       my ($self, $photo_meta, $id) = @_;
+       return File::Spec->catfile($self->get('PhotoDir'), $self->photo_file_name($photo_meta, $id));
+}
+
+
+1;
diff --git a/lib/UCW/Gallery/Archive.pm b/lib/UCW/Gallery/Archive.pm
new file mode 100644 (file)
index 0000000..fb7c40b
--- /dev/null
@@ -0,0 +1,34 @@
+# Simple Photo Gallery: Archiving
+# (c) 2003--2012 Martin Mares <mj@ucw.cz>
+
+package UCW::Gallery::Archive;
+
+use strict;
+use warnings;
+
+use Archive::Zip;
+use File::Spec;
+use UCW::CGI;
+
+sub send_archive($$) {
+       my ($gal, $meta) = @_;
+
+       if (!$gal->get('WebAllowArchives')) {
+               UCW::CGI::http_error('403 Archiving forbidden by server configuration');
+               return;
+       }
+
+       my $zip = Archive::Zip->new;
+       my $cnt = 0;
+       for my $id (@{$meta->{sequence}}) {
+               $zip->addFile(File::Spec->catfile($gal->get('PhotoDir'), "$id.jpg"), sprintf("%03d.jpg", $cnt)) or die;
+               $cnt++;
+       }
+
+       print "Content-type: application/zip\n";
+       print "Content-Disposition: attachment; filename=gallery.zip\n";
+       print "\n";
+       $zip->writeToFileHandle(\*STDOUT, 0);
+}
+
+42;
diff --git a/lib/UCW/Gallery/Hashes.pm b/lib/UCW/Gallery/Hashes.pm
new file mode 100644 (file)
index 0000000..d0414e9
--- /dev/null
@@ -0,0 +1,51 @@
+# Simple Photo Gallery: Image Hashes
+# (c) 2015 Martin Mares <mj@ucw.cz>
+
+package UCW::Gallery::Hashes;
+
+use strict;
+use warnings;
+
+use File::stat ();
+use Digest::SHA;
+
+sub new {
+       my ($class, $gal) = @_;
+       my $self = { gal => $gal };
+
+       if ($gal->get('CacheHashes') && -f 'gallery.cache') {
+               print "Loading gallery.cache\n";
+               $self->{cache} = $gal->read_meta('gallery.cache');
+       } else {
+               $self->{cache} = {};
+       }
+
+       return bless $self, $class;
+}
+
+sub write {
+       my ($self) = @_;
+       my $gal = $self->{gal};
+       if ($gal->get('CacheHashes')) {
+               print "Writing gallery.cache\n";
+               $gal->write_meta('gallery.cache', $self->{cache});
+       }
+}
+
+sub hash_image {
+       my ($self, $path) = @_;
+       my $cache = $self->{cache};
+
+       my $st = File::stat::stat($path) or die "Cannot access $path: $!\n";
+       my $key_text = join(":", $path, $st->dev, $st->ino, $st->mtime);
+       my $key = Digest::SHA->sha1_base64($key_text);
+
+       if (!exists $cache->{$key}) {
+               my $sha = Digest::SHA->new(1);
+               $sha->addfile($path) or die "Cannot hash $path\n";
+               $cache->{$key} = substr($sha->hexdigest, 0, 16);
+       }
+       return $cache->{$key};
+}
+
+42;
diff --git a/lib/UCW/Gallery/Web.pm b/lib/UCW/Gallery/Web.pm
new file mode 100644 (file)
index 0000000..ccca477
--- /dev/null
@@ -0,0 +1,181 @@
+# Simple Photo Gallery: Web Interface
+# (c) 2003--2012 Martin Mares <mj@ucw.cz>
+
+package UCW::Gallery::Web;
+
+use strict;
+use warnings;
+
+use UCW::Gallery;
+use UCW::CGI;
+use File::Spec;
+
+my $show_img;
+my $want_archive;
+
+my %args = (
+       'i'     => { 'var' => \$show_img, 'check' => '\d+' },
+       'a'     => { 'var' => \$want_archive },
+);
+
+sub error($) {
+       print "<p style='color:red'>Bad luck, the script is broken. Sorry.\n<p>$_[0]\n";
+       print "</body></html>\n";
+}
+
+sub get($$) {
+       my ($self, $key) = @_;
+       return $self->{gal}->get($key);
+}
+
+sub extras($$) {
+       my ($self, $key) = @_;
+       my $val = $self->get($key);
+       if (ref $val eq 'CODE') {
+               return &$val($self);
+       } else {
+               return $val;
+       }
+}
+
+# For use by extras hooks
+sub gallery($) {
+       my ($self) = @_;
+       return $self->{gal};
+}
+
+# For use by extras hooks: return true if we are showing an image page, false for index page
+sub showing_image($) {
+       my ($self) = @_;
+       return $show_img ne "";
+}
+
+sub html_top($) {
+       my ($self) = @_;
+       my $title = UCW::CGI::html_escape($self->get('Title'));
+       my $hextras = $self->extras('WebHeadExtras');
+       my $theme_hextras = $self->theme_head_extras;
+       print <<EOF ;
+Content-Type: text/html
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head>
+$hextras$theme_hextras<title>$title</title>
+</head><body>
+EOF
+
+       $UCW::CGI::ErrorHandler::error_hook = \&error;
+
+       # WebTopExtras are evaluated separately, since they can override the error hook
+       print $self->extras('WebTopExtras');
+}
+
+sub html_bot($) {
+       my ($self) = @_;
+       print $self->extras('WebBotExtras'), "</body></html>\n";
+}
+
+sub show_img($) {
+       my ($self) = @_;
+
+       if ($show_img < 1 || $show_img > $self->{num_photos}) {
+               UCW::CGI::http_error('404 No such photo');
+               return;
+       }
+
+       my $meta = $self->{meta};
+       my $id = $meta->{sequence}->[$show_img-1];
+       my $m = $meta->{photo}->{$id} or die;
+       $self->html_top;
+
+       $self->show_links(($show_img > 1 ? ("?i=".($show_img-1)) : ""),
+                         ".",
+                         ($show_img < $self->{num_photos} ? ("?i=".($show_img+1)) : ""));
+
+       my $t = UCW::CGI::html_escape($m->{title});
+       my $w = $m->{w};
+       my $h = $m->{h};
+       print "<h1>$t</h1>\n" if $t ne "";
+       my $img = $self->get('PhotoUrlPrefix') . $self->{gal}->photo_file_name($m, $id);
+       print "<p class=large><img src='$img' width=$w height=$h alt='$t'>\n";
+
+       $self->html_bot;
+}
+
+sub show_pre_thumbs($) {
+       my ($self) = @_;
+}
+
+sub show_post_thumbs($) {
+       my ($self) = @_;
+}
+
+sub show_list($) {
+       my ($self) = @_;
+       $self->html_top;
+
+       $self->show_links($self->get('BackURL'), $self->get('ParentURL'), $self->get('FwdURL'));
+       print "<h1>", $self->get('Title'), "</h1>\n";
+       my $subtitle = $self->get('SubTitle');
+       print "<h2>$subtitle</h2>\n" if $subtitle ne "";
+       $self->show_pre_thumbs;
+
+       my $meta = $self->{meta};
+       for my $idx (1..$self->{num_photos}) {
+               my $id = $meta->{sequence}->[$idx-1];
+               my $m = $meta->{photo}->{$id};
+               my $click_url;
+               if ($self->get('WebImageSubpages')) {
+                       $click_url = "?i=$idx";
+               } else {
+                       $click_url = $self->get('PhotoUrlPrefix') . $self->{gal}->photo_file_name($m, $id);
+               }
+               $self->show_thumb($meta, $id, $click_url);
+       }
+
+       $self->show_post_thumbs;
+       $self->html_bot();
+}
+
+sub dispatch($) {
+       my ($self) = @_;
+       binmode STDOUT, ':utf8';
+       UCW::CGI::parse_args(\%args);
+       $self->{meta} = $self->{gal}->read_meta(File::Spec->catfile($self->get('CacheDir'), 'cache.meta'));
+       $self->{num_photos} = scalar @{$self->{meta}->{sequence}};
+
+       if ($want_archive) {
+               require UCW::Gallery::Archive;
+               UCW::Gallery::Archive::send_archive($self->{gal}, $self->{meta});
+       } elsif ($show_img ne "") {
+               $self->show_img;
+       } else {
+               $self->show_list;
+       }
+}
+
+sub attach($$) {
+       my ($class, $gal) = @_;
+       my $self = { gal => $gal };
+       $gal->def(
+               WebFE => $self,
+
+               # Extras are either strings or functions called with the current gallery object as parameter
+               WebHeadExtras => "",
+               WebTopExtras => "",
+               WebBotExtras => "",
+
+               # Used by the theming logic
+               WebThemeCSS => undef,
+
+               # 1 if thumbnail link to sub-pages with images, 0 if they link directly to image files
+               WebImageSubpages => 1,
+
+               # If enabled, calling the CGI with a=zip produces a ZIP archive with all photos.
+               WebAllowArchives => 1,
+       );
+       bless $self, $class;
+       return $self;
+}
+
+42;
diff --git a/lib/UCW/Gallery/Web/HighSlide.pm b/lib/UCW/Gallery/Web/HighSlide.pm
new file mode 100644 (file)
index 0000000..b1e503b
--- /dev/null
@@ -0,0 +1,86 @@
+# Highslide JS Theme for MJ's Photo Gallery
+# (c) 2012 Martin Mares <mj@ucw.cz>; GPL'ed
+
+package UCW::Gallery::Web::HighSlide;
+
+use strict;
+use warnings;
+use utf8;
+
+use UCW::Gallery;
+use UCW::Gallery::Web;
+
+our @ISA = qw(UCW::Gallery::Web);
+
+sub theme_head_extras($) {
+       my ($self) = @_;
+       my $hsdir = $self->get('ThemeUrlPrefix') . "highslide";
+       return $self->showing_image ? <<AMEN_MINI : <<AMEN_FULL ;
+<link rel="stylesheet" type="text/css" href="$hsdir/custom.css">
+AMEN_MINI
+<script type="text/javascript" src="$hsdir/highslide-with-gallery.js"></script>
+<script type="text/javascript" src="$hsdir/custom.js" charset="utf-8"></script>
+<script type="text/javascript">
+hs.graphicsDir = '$hsdir/graphics/';
+</script>
+<link rel="stylesheet" type="text/css" href="$hsdir/highslide.css">
+<link rel="stylesheet" type="text/css" href="$hsdir/custom.css">
+<!--[if lt IE 7]>
+<link rel="stylesheet" type="text/css" href="$hsdir/highslide-ie6.css">
+<![endif]-->
+AMEN_FULL
+}
+
+sub show_links($$$$) {
+       my ($self, $prev, $up, $next) = @_;
+       my $nav = $self->get('ThemeUrlPrefix') . "highslide/nav";
+       print "<p class=parent>";
+       print "<a href='$prev'><img class=back prev src='$nav/prev.png'></a>" if $prev ne "";
+       printf "<a href='$next'><img class=fwd src='$nav/next.png'></a>" if $next ne "";
+       printf "<a href='$up'><img class=up src='$nav/back.png'></a>" if $up ne "";
+}
+
+sub show_pre_thumbs($) {
+       my ($self) = @_;
+       print "\n<div class='highslide-gallery'><ul>\n";
+       $self->{hs_thumb_counter} = 0;
+}
+
+sub show_post_thumbs($) {
+       my ($self) = @_;
+       print "</ul></div>\n\n";
+}
+
+sub show_thumb($) {
+       my ($self, $meta, $photo_id, $click_url) = @_;
+       my $m = $meta->{photo}->{$photo_id};
+       my $annot = UCW::CGI::html_escape($m->{title});
+       my $tf = $self->get('ThumbFormats')->[0];
+       my $tm = $meta->{thumb}->{$tf}->{$photo_id} or die "No thumbnails for format $tf found!\n";
+       my $tw = $tm->{w};
+       my $th = $tm->{h};
+       my $thumb = $self->get('ThumbUrlPrefix') . "$tf/$photo_id.jpg";
+       # Highslide requires title either for all images, or for none
+       my $tit = " title=\"$annot\"";
+       my $aid = 'i'.(++$self->{hs_thumb_counter});
+       my $photo_url = $self->get('PhotoUrlPrefix') . $self->{gal}->photo_file_name($m, $photo_id);
+       print "<li><a id='$aid' href='$click_url' class=highslide onclick='return hs.expand(this, { src: \"$photo_url\" })'>";
+       print "<img src='$thumb' width=$tw height=$th alt='Photo'$tit></a>\n";
+       if ($self->get('GeoHack')) {
+               my ($lat, $lon) = ($m->{lat}, $m->{lon});
+               if (defined $lat && defined $lon) {
+                       my $local = "<a href='map.cgi?i=" . $self->{hs_thumb_counter} . "'>trasa</a>";
+                       my $osm = "<a href='http://www.openstreetmap.org/?mlat=$lat&amp;mlon=$lon#map=16/$lat/$lon'>OSM</a>";
+                       print "<div class='highslide-caption'>Ukázat na mapě: $local, $osm</div>\n";
+               }
+       }
+}
+
+sub run($$) {
+       my ($class, $gal) = @_;
+       my $self = $class->SUPER::attach($gal);
+       $self->dispatch();
+       return $self;
+}
+
+1;
diff --git a/lib/UCW/Gallery/Web/NrtBlue.pm b/lib/UCW/Gallery/Web/NrtBlue.pm
new file mode 100644 (file)
index 0000000..7fc5637
--- /dev/null
@@ -0,0 +1,82 @@
+# NRT Theme for MJ's Photo Gallery
+# (c) 2003--2012 Martin Mares <mj@ucw.cz>; GPL'ed
+# Theme images taken from the cthumb package (c) Carlos Puchol
+
+package UCW::Gallery::Web::NrtBlue;
+
+use strict;
+use warnings;
+
+use UCW::Gallery;
+use UCW::Gallery::Web;
+
+our @ISA = qw(UCW::Gallery::Web);
+
+my $theme_name = "nrt-blue";
+my $navw = 48;
+my $navh = 48;
+my $interior_margin = 4;
+my $left_w = 14;
+my $right_w = 18;
+my $top_h = 14;
+my $bot_h = 18;
+
+sub theme_dir($) {
+       my ($self) = @_;
+       return $self->get('ThemeUrlPrefix') . $theme_name;
+}
+
+sub theme_head_extras($) {
+       my ($self) = @_;
+       my $stylesheet = $self->theme_dir . "/style.css";
+       return "<link rel=stylesheet href='$stylesheet' type='text/css' media=all>\n";
+}
+
+sub show_links($$$$) {
+       my ($self, $prev, $up, $next) = @_;
+       my $theme = $self->theme_dir;
+       print "<p class=parent>";
+       print "<span class=back style='width: ${navw}px; height: ${navh}px'>";
+       print "<a href='$prev'><img src='$theme/prev.png' width=${navw} height=${navh} alt='Back'></a>" if $prev ne "";
+       print "</span>\n";
+       printf "<span class=fwd style='width: ${navw}px; height: ${navh}px'>";
+       printf "<a href='$next'><img src='$theme/next.png' width=${navw} height=${navh} alt='Forward'></a>" if $next ne "";
+       print "</span>\n";
+       printf "<a href='$up'><img src='$theme/back.png' width=${navw} height=${navh} alt='Up'></a>" if $up ne "";
+}
+
+sub show_thumb($) {
+       my ($self, $meta, $photo_id, $click_url) = @_;
+       my $theme = $self->theme_dir;
+       my $m = $meta->{photo}->{$photo_id};
+       my $annot = UCW::CGI::html_escape($m->{title});
+       my $tf = $self->get('ThumbFormats')->[0];
+       my ($thumb_w, $thumb_h) = $self->{gal}->thumb_fmt_to_size($tf);
+       my $tm = $meta->{thumb}->{$tf}->{$photo_id} or die "No thumbnails for format $tf found!\n";
+       my $tw = $tm->{w};
+       my $th = $tm->{h};
+       my $thumb = $self->get('ThumbUrlPrefix') . "$tf/$photo_id.jpg";
+       my $side_w = $thumb_w + 2*$interior_margin;
+       my $side_h = $thumb_h + 2*$interior_margin;
+       my $box_w = $left_w + $side_w + $right_w;
+       my $box_h = $top_h + $side_h + $bot_h;
+       print "<div class=thf><div class=thumb>\n";
+       print "<img src='$theme/top.png' width=$box_w height=$top_h alt='' class=tt>\n";
+       print "<img src='$theme/left.png' width=$left_w height=$side_h alt='' class=tl>\n";
+       my $ol = $left_w + $interior_margin + int(($thumb_w - $tw)/2);
+       my $ot = $top_h + $interior_margin + int(($thumb_h - $th)/2);
+       my $tit = ($annot ne "") ? " title=\"$annot\"" : "";
+       print "<a href='$click_url'><img src='$thumb' width=$tw height=$th alt='Photo'$tit class=ti style='left: ${ol}px; top: ${ot}px'></a>\n";
+       print "<img src='$theme/right.png' width=$right_w height=$side_h alt='' class=tr>\n";
+       print "<img src='$theme/bot.png' width=$box_w height=$bot_h alt='' class=tb>\n";
+       print "</div></div>\n\n";
+}
+
+sub run($$) {
+       my ($class, $gal) = @_;
+       my $self = $class->SUPER::attach($gal);
+       $self->dispatch();
+       return $self;
+}
+
+1;
diff --git a/lib/UCW/Gallery/Web/Plain.pm b/lib/UCW/Gallery/Web/Plain.pm
new file mode 100644 (file)
index 0000000..f7d213c
--- /dev/null
@@ -0,0 +1,70 @@
+# Plain Theme for MJ's Photo Gallery
+# (c) 2012 Martin Mares <mj@ucw.cz>; GPL'ed
+
+package UCW::Gallery::Web::Plain;
+
+use strict;
+use warnings;
+
+use UCW::Gallery;
+use UCW::Gallery::Web;
+
+our @ISA = qw(UCW::Gallery::Web);
+
+my $theme_name = "plain";
+my $navw = 48;
+my $navh = 48;
+my $box_w = 130;
+my $box_h = 110;
+
+sub theme_dir($) {
+       my ($self) = @_;
+       return $self->get('ThemeUrlPrefix') . $theme_name;
+}
+
+sub theme_head_extras($) {
+       my ($self) = @_;
+       my $stylesheet = $self->theme_dir . "/style.css";
+       return "<link rel=stylesheet href='$stylesheet' type='text/css' media=all>\n";
+}
+
+sub show_links($$$$) {
+       my ($self, $prev, $up, $next) = @_;
+       my $theme = $self->theme_dir;
+       print "<p class=parent>";
+       print "<span class=back style='width: ${navw}px; height: ${navh}px'>";
+       print "<a href='$prev'><img src='$theme/prev.png' width=${navw} height=${navh} alt='Back'></a>" if $prev ne "";
+       print "</span>\n";
+       printf "<span class=fwd style='width: ${navw}px; height: ${navh}px'>";
+       printf "<a href='$next'><img src='$theme/next.png' width=${navw} height=${navh} alt='Forward'></a>" if $next ne "";
+       print "</span>\n";
+       printf "<a href='$up'><img src='$theme/back.png' width=${navw} height=${navh} alt='Up'></a>" if $up ne "";
+}
+
+sub show_thumb($) {
+       my ($self, $meta, $photo_id, $click_url) = @_;
+       my $theme = $self->theme_dir;
+       my $m = $meta->{photo}->{$photo_id};
+       my $annot = UCW::CGI::html_escape($m->{title});
+       my $tf = $self->get('ThumbFormats')->[0];
+       my ($thumb_w, $thumb_h) = $self->{gal}->thumb_fmt_to_size($tf);
+       my $tm = $meta->{thumb}->{$tf}->{$photo_id} or die "No thumbnails for format $tf found!\n";
+       my $tw = $tm->{w};
+       my $th = $tm->{h};
+       my $thumb = $self->get('ThumbUrlPrefix') . "$tf/$photo_id.jpg";
+       print "<div class=thf><div class=thumb>\n";
+       my $ol = int(($box_w - $tw)/2);
+       my $ot = int(($box_h - $th)/2);
+       my $tit = ($annot ne "") ? " title=\"$annot\"" : "";
+       print "<a href='$click_url'><img src='$thumb' width=$tw height=$th alt='Photo'$tit class=ti style='left: ${ol}px; top: ${ot}px'></a>\n";
+       print "</div></div>\n\n";
+}
+
+sub run($$) {
+       my ($class, $gal) = @_;
+       my $self = $class->SUPER::attach($gal);
+       $self->dispatch();
+       return $self;
+}
+
+1;
diff --git a/nrt-blue/back.png b/nrt-blue/back.png
new file mode 100644 (file)
index 0000000..501909c
Binary files /dev/null and b/nrt-blue/back.png differ
diff --git a/nrt-blue/back.xcf b/nrt-blue/back.xcf
new file mode 100644 (file)
index 0000000..5d8848d
Binary files /dev/null and b/nrt-blue/back.xcf differ
diff --git a/nrt-blue/bot.png b/nrt-blue/bot.png
new file mode 100644 (file)
index 0000000..86bb5f2
Binary files /dev/null and b/nrt-blue/bot.png differ
diff --git a/nrt-blue/bot.xcf b/nrt-blue/bot.xcf
new file mode 100644 (file)
index 0000000..a3dbedc
Binary files /dev/null and b/nrt-blue/bot.xcf differ
diff --git a/nrt-blue/left.png b/nrt-blue/left.png
new file mode 100644 (file)
index 0000000..a9852c7
Binary files /dev/null and b/nrt-blue/left.png differ
diff --git a/nrt-blue/left.xcf b/nrt-blue/left.xcf
new file mode 100644 (file)
index 0000000..76912ed
Binary files /dev/null and b/nrt-blue/left.xcf differ
diff --git a/nrt-blue/next.png b/nrt-blue/next.png
new file mode 100644 (file)
index 0000000..bdd1011
Binary files /dev/null and b/nrt-blue/next.png differ
diff --git a/nrt-blue/next.xcf b/nrt-blue/next.xcf
new file mode 100644 (file)
index 0000000..ad06333
Binary files /dev/null and b/nrt-blue/next.xcf differ
diff --git a/nrt-blue/prev.png b/nrt-blue/prev.png
new file mode 100644 (file)
index 0000000..68b2514
Binary files /dev/null and b/nrt-blue/prev.png differ
diff --git a/nrt-blue/prev.xcf b/nrt-blue/prev.xcf
new file mode 100644 (file)
index 0000000..dbf35d5
Binary files /dev/null and b/nrt-blue/prev.xcf differ
diff --git a/nrt-blue/right.png b/nrt-blue/right.png
new file mode 100644 (file)
index 0000000..7af66ba
Binary files /dev/null and b/nrt-blue/right.png differ
diff --git a/nrt-blue/right.xcf b/nrt-blue/right.xcf
new file mode 100644 (file)
index 0000000..0668d05
Binary files /dev/null and b/nrt-blue/right.xcf differ
diff --git a/nrt-blue/style.css b/nrt-blue/style.css
new file mode 100644 (file)
index 0000000..de50720
--- /dev/null
@@ -0,0 +1,17 @@
+.thf    { margin: 0 0 0 0; padding: 0 0 0 0; float: left; border: none; }
+.thumb  { position: relative; width: 154px; height: 134px; background-color: black; }
+.tt     { position: absolute; top: 0; left: 0; }
+.tl     { position: absolute; top: 14px; left: 0; }
+.ti     { position: absolute; }
+.tr     { position: absolute; top: 14px; right: 0; }
+.tb     { position: absolute; bottom: 0; right: 0; }
+IMG     { border: none; }
+H1      { text-align: center; }
+H2      { text-align: center; margin-bottom: 3ex; }
+P      { clear: both; }
+H2     { clear: both; }
+.parent        { text-align: center; }
+.large { text-align: center; }
+.back  { float: left; }
+.fwd   { float: right; }
+A[href]:hover { background-color: #11001d; }
diff --git a/nrt-blue/top.png b/nrt-blue/top.png
new file mode 100644 (file)
index 0000000..db5a677
Binary files /dev/null and b/nrt-blue/top.png differ
diff --git a/nrt-blue/top.xcf b/nrt-blue/top.xcf
new file mode 100644 (file)
index 0000000..8298369
Binary files /dev/null and b/nrt-blue/top.xcf differ
diff --git a/plain/back.png b/plain/back.png
new file mode 100644 (file)
index 0000000..501909c
Binary files /dev/null and b/plain/back.png differ
diff --git a/plain/next.png b/plain/next.png
new file mode 100644 (file)
index 0000000..bdd1011
Binary files /dev/null and b/plain/next.png differ
diff --git a/plain/prev.png b/plain/prev.png
new file mode 100644 (file)
index 0000000..68b2514
Binary files /dev/null and b/plain/prev.png differ
diff --git a/plain/style.css b/plain/style.css
new file mode 100644 (file)
index 0000000..4e86e31
--- /dev/null
@@ -0,0 +1,13 @@
+.thf    { margin: 0 0 0 0; padding: 0 0 0 0; float: left; border: none; }
+.thumb  { position: relative; width: 130px; height: 110px; }
+.ti     { position: absolute; border: 1px solid #505; }
+IMG     { border: none; }
+H1      { text-align: center; }
+H2      { text-align: center; margin-bottom: 3ex; }
+P      { clear: both; }
+H2     { clear: both; }
+.parent        { text-align: center; }
+.large { text-align: center; }
+.back  { float: left; }
+.fwd   { float: right; }
+A[href]:hover { background-color: #11001d; }