--- /dev/null
+;;; -*-emacs-lisp-*-
+
+(setq skel-alist
+ (append
+ '((author . "Mark Wooding")
+ (licence-text . "[[agpl-3]]")
+ (full-title . "Trivial Gallery"))
+ skel-alist))
--- /dev/null
+### -*-cperl-*-
+###
+### Main output for Trivial Gallery.
+###
+### (c) 2021 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of Trivial Gallery.
+###
+### Trivial Gallery is free software: you can redistribute it and/or modify
+### it under the terms of the GNU Affero General Public License as
+### published by the Free Software Foundation; either version 3 of the
+### License, or (at your option) any later version.
+###
+### Trivial Gallery is distributed in the hope that it will be useful, but
+### WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+### Affero General Public License for more details.
+###
+### You should have received a copy of the GNU Affero General Public
+### License along with Trivial Gallery. If not, see
+### <https://www.gnu.org/licenses/>.
+
+package TrivGal;
+
+use autodie qw{:all};
+
+use Errno;
+use Exporter qw{import};
+use File::Path qw{make_path};
+use File::stat;
+use Image::Imlib2;
+use User::pwent;
+use POSIX;
+
+our @EXPORT;
+sub export (@) { push @EXPORT, @_; }
+
+###--------------------------------------------------------------------------
+### Internal utilities.
+
+sub read_or_set ($\$@) {
+ my ($me, $ref, @args) = @_;
+ if (@args == 0) { return $$ref; }
+ elsif (@args == 1) { $$ref = $args[0]; return $me; }
+ elsif (@args > 1) { die "too many arguments"; }
+}
+
+###--------------------------------------------------------------------------
+### Random utilities.
+
+export qw{join_paths};
+sub join_paths (@) {
+ my @p = @_;
+ my $p = "";
+ ELT: for my $e (@p) {
+ $e =~ s:^/{2,}:/:;
+ $e =~ s,([^/])/+$,$1,;
+ if ($e eq "") { next ELT; }
+ elsif ($p eq "" || $e =~ m,^/,) { $p = $e; }
+ else { $p = "$p/$e"; }
+ }
+ return $p;
+}
+
+export qw{split_path};
+sub split_path ($) {
+ my ($path) = @_;
+
+ my ($dir, $base, $ext) = $path =~ m,^(?:(.*)/)?(?:([^/]*)\.)?([^./]*)$,;
+ if (defined $base) { $ext = ".$ext"; }
+ else { $base = $ext; $ext = ""; }
+ return ($dir, $base, $ext);
+}
+
+export qw{urlencode};
+sub urlencode ($) {
+ my ($u) = @_;
+ $u =~ s:([^0-9a-zA-Z_./~-]):sprintf "%%%02x", ord $1:eg;
+ return $u;
+}
+
+export qw{urldecode};
+sub urldecode ($) {
+ my ($u) = @_;
+ $u =~ s:\%([0-9a-fA-F]{2}):chr hex $1:eg;
+ return $u;
+}
+
+###--------------------------------------------------------------------------
+### Image types.
+
+our %TYPE;
+
+package TrivGal::ImageType {
+ sub new ($$) {
+ my ($cls, $ext) = @_;
+ return $TYPE{$ext} = bless { ext => $ext }, $cls;
+ }
+ sub ext ($) {
+ my ($me, @args) = @_;
+ return $me->{ext};
+ }
+ sub mimetype ($@) {
+ my ($me, @args) = @_;
+ return TrivGal::read_or_set $me, $me->{mimetype}, @args;
+ }
+ sub imlibfmt ($@) {
+ my ($me, @args) = @_;
+ return TrivGal::read_or_set $me, $me->{imlibfmt}, @args;
+ }
+};
+
+TrivGal::ImageType->new(".jpg")->mimetype("image/jpeg")->imlibfmt("jpeg");
+TrivGal::ImageType->new(".png")->mimetype("image/png")->imlibfmt("png");
+
+###--------------------------------------------------------------------------
+### Configuration.
+
+export qw{$SCOPE $SUFFIX};
+our $SCOPE //= $::SCOPE;
+our $SUFFIX //= $::SUFFIX;
+
+export qw{$IMGROOT $CACHE $TMP};
+our $IMGROOT //= "$ENV{HOME}/publish/$SCOPE-html$SUFFIX/tgal-images";
+our $CACHE //=
+ ($ENV{XDG_CACHE_HOME} // "$ENV{HOME}/.cache") .
+ "/tgal/$SCOPE$SUFFIX";
+our $TMP //= "$CACHE/tmp";
+
+export qw{$ROOTURL $IMGURL $CACHEURL $STATICURL $SCRIPTURL};
+my $user = getpwuid($>)->name;
+our $ROOTURL //= "/~$user";
+our $IMGURL //= "$ROOTURL/tgal-images";
+our $CACHEURL //= "$ROOTURL/tgal-cache";
+our $STATICURL //= "$ROOTURL/tgal-static";
+our $SCRIPTURL;
+
+export qw{%SIZE};
+our %SIZE = (thumb => 228, view => 1200);
+
+export qw{init};
+my $initp = 0;
+sub init () {
+ my $m = HTML::Mason::Request->instance;
+ my $r = $m->cgi_request;
+
+ $m->interp->set_escape(u => sub { my ($r) = @_; $$r = urlencode $$r; });
+
+ return unless !$initp;
+
+ $SCRIPTURL //= $r->subprocess_env("SCRIPT_NAME");
+ $initp = 1;
+}
+
+###--------------------------------------------------------------------------
+### Temporary files.
+
+export qw{clean_temp_files};
+sub clean_temp_files () {
+ my $d;
+
+ eval { opendir $d, $TMP; };
+ if ($@) {
+ if ($@->isa("autodie::exception") && $@->errno == ENOENT) { return; }
+ else { die $@; }
+ }
+ my $now = time;
+ FILE: while (my $name = readdir $d) {
+ next FILE unless $name =~ /^t(\d+)\-/;
+ my $pid = $1;
+ next FILE if kill 0, $pid;
+ my $f = "$TMP/$name";
+ my $st = stat $name;
+ next FILE if $now - $st->mtime() < 300;
+ unlink $f;
+ }
+ closedir $d;
+}
+
+###--------------------------------------------------------------------------
+### Scaled images.
+
+export qw{scaled};
+sub scaled ($$) {
+ my ($scale, $path) = @_;
+
+ my $sz = $SIZE{$scale} or die "unknown scale `$scale'";
+ my $imgpath = "$IMGROOT/$path";
+ my $ist = stat $imgpath or die "no image `$path'";
+ my $thumb = "$CACHE/scaled.$scale/$path";
+ my $thumburl = "$CACHEURL/scaled.$scale/$path";
+ my $tst = stat $thumb;
+ if (defined $tst && $tst->mtime > $ist->mtime) { return $thumburl; }
+ my ($dir, $base, $ext) = split_path $thumb;
+ my $ty = $TYPE{lc $ext} or die "unknown type `$ext'";
+
+ my $img = Image::Imlib2->load($imgpath);
+ my ($wd, $ht) = ($img->width, $img->height);
+ my $max = $wd > $ht ? $wd : $ht;
+ if ($max <= $sz) { return "$IMGURL/$path"; }
+ my $sc = $sz/$max;
+ my $scaled = $img->create_scaled_image($sc*$wd, $sc*$ht);
+
+ $scaled->image_set_format($ty->imlibfmt);
+ $scaled->set_quality(90);
+ my $new = "$TMP/t${$}$ext";
+ make_path $TMP;
+ $scaled->save($new);
+ make_path $dir;
+ rename $new, $thumb;
+ return $thumburl;
+}
+
+###--------------------------------------------------------------------------
+### Directory listings.
+
+package TrivGal::Item {
+ sub new ($$) {
+ my ($cls, $name) = @_;
+ return bless { name => $name }, $cls;
+ }
+ sub name ($@) {
+ my ($me, @args) = @_;
+ return TrivGal::read_or_set $me, $me->{name}, @args;
+ }
+ sub comment ($@) {
+ my ($me, @args) = @_;
+ return TrivGal::read_or_set $me, $me->{comment}, @args;
+ }
+};
+
+export qw{listdir};
+sub listdir ($) {
+ my ($path) = @_;
+ my (@d, @f);
+ my $ix = undef;
+
+ if (-f "$path/.tgal.index") {
+ open my $f, "<", "$path/.tgal.index";
+ my $item = undef;
+ my $comment = undef;
+ LINE: while (<$f>) {
+ chomp;
+ next LINE if /^\s*(\#|$)/;
+ if (s/^\s+//) {
+ die "no item" unless $item;
+ $comment = defined $comment ? $comment . "\n" . $_ : $_;
+ } else {
+ if ($item && $comment) { $item->comment($comment); }
+ my ($indexp, $name, $c) = /(!\s+)?(\S+)\s*(\S|\S.*\S)?\s*$/;
+ $name = urldecode $name;
+ my $list;
+ if ($name =~ m!/$!) {
+ $list = \@d;
+ die "can't index a folder" if $indexp;
+ } else {
+ $list = \@f;
+ my ($dir, $base, $ext) = TrivGal::split_path $name;
+ die "unknown image type" unless $TYPE{lc $ext};
+ if ($indexp) {
+ die "two index images" if defined $ix;
+ $ix = $item;
+ }
+ }
+ $item = TrivGal::Item->new($name);
+ $comment = $c;
+ push @$list, $item;
+ }
+ }
+ if ($item && $comment) { $item->comment($comment); }
+ close $f;
+ } else {
+ opendir $d, $path;
+ my @e = readdir $d;
+ closedir $d;
+
+ ENT: for my $e (sort @e) {
+ my ($dir, $base, $ext) = split_path $e;
+ my $dotp = $e =~ /^\./;
+ my $st = stat "$path/$e";
+ my $list = undef;
+ if ($dotp || !($st->mode&0004)) { }
+ elsif (-d $st) { $list = \@d; }
+ elsif ($TYPE{lc $ext} && -f $st) { $list = \@f; }
+ $list and push @$list, TrivGal::Item->new($e);
+ }
+ $ix = $f[0] if @f;
+ }
+
+ return (\@d, \@f, $ix);
+}
+
+export qw{contents};
+sub contents ($) {
+ my ($file) = @_;
+ my $contents = "";
+ my $f;
+ local $@;
+ eval { open $f, "<", "$file"; };
+ if ($@) {
+ if ($@->isa("autodie::exception") && $@->errno == ENOENT)
+ { return undef; }
+ die $@;
+ }
+ while (sysread $f, $buf, 16384) { $contents .= $buf; }
+ close $f;
+ return $contents;
+}
+
+export qw{find_covering_file};
+sub find_covering_file ($$$) {
+ my ($top, $path, $name) = @_;
+ for (;;) {
+ my $stuff = contents "$top/$path/$name"; return $stuff if defined $stuff;
+ if ($path eq "") { return undef; }
+ if ($path =~ m!^(.*)/[^/]+/?!) { $path = $1; }
+ else { $path = ""; }
+ }
+}
+
+###----- That's all, folks --------------------------------------------------
+
+clean_temp_files;
+
+1;
--- /dev/null
+%### -*-html-*-
+%###
+%### Main output for Trivial Gallery.
+%###
+%### (c) 2021 Mark Wooding
+%###
+%
+%###----- Licensing notice --------------------------------------------------
+%###
+%### This file is part of Trivial Gallery.
+%###
+%### Trivial Gallery is free software: you can redistribute it and/or modify
+%### it under the terms of the GNU Affero General Public License as
+%### published by the Free Software Foundation; either version 3 of the
+%### License, or (at your option) any later version.
+%###
+%### Trivial Gallery is distributed in the hope that it will be useful, but
+%### WITHOUT ANY WARRANTY; without even the implied warranty of
+%### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%### Affero General Public License for more details.
+%###
+%### You should have received a copy of the GNU Affero General Public
+%### License along with Trivial Gallery. If not, see
+%### <https://www.gnu.org/licenses/>.
+%
+%###-------------------------------------------------------------------------
+<%def .html>\
+% $r->content_type("text/html; charset=\"utf-8\"");
+<!DOCTYPE html>
+<!--
+Trivial Gallery, copyright © 2021 Mark Wooding.
+Free software: you can redistribute it and/or modify it under the terms
+of the GNU Affero General Public License.
+-->
+<html>
+<head>
+ <meta name=viewport content="width=device-width initial-scale=1.0">
+ <script type="text/javascript" src="<% "$STATICURL/tgal.js" |u %>" defer></script>
+ <link rel=stylesheet type=text/css href="<% "$STATICURL/tgal.css" |u %>">
+<% $head %>\
+ <title><% $title %></title>
+</head>
+<body>
+<% $m->content %>
+</body>
+</html>\
+%
+<%args>
+ $title
+ $head => ""
+</%args>
+</%def>
+%
+%###-------------------------------------------------------------------------
+<%def .not-found>\
+<&| .html, title => "Not found" &>
+<h1>Not found</h1>
+Failed to find ‘<% $path |h %>’.
+</&>
+% return 404;
+%
+<%args>
+ $path
+</%args>
+</%def>
+%
+%###-------------------------------------------------------------------------
+<%def .contact>\
+<%perl>
+ unless ($r->path_info =~ m!/$!) {
+ $m->redirect(join_paths($SCRIPTURL, $path) . "/");
+ }
+ my $real = join_paths $IMGROOT, $path;
+ my $url = join_paths $SCRIPTURL, $path;
+ my ($dd, $ff, $ii) = listdir $real;
+ my $links = "";
+ my $uplink;
+ if ($path eq "" || $path eq "/") { $uplink = undef; }
+ else {
+ ($uplink = $path) =~ s![^/]*/$!!;
+ $links .= sprintf " <link rel=up href=\"%s\">\n",
+ urlencode "$SCRIPTURL/$uplink";
+ }
+ (my $nosl = $path) =~ s!/$!!;
+</%perl>
+%
+<&| .html, title => "Folder " . $m->interp->apply_escapes($nosl || "[top]", "h"),
+ head => $links &>
+<& .breadcrumbs, what => "Folder", path => $path &>
+%
+% my $note = contents "$IMGROOT/$path/.tgal-note.html";
+% if (defined $note) {
+<div class=note>
+<% $note %>
+</div>
+% }
+%
+% if (@$dd) {
+<h2>Subfolders</h2>
+<div class=gallery>
+% for my $d (@$dd) {
+% my ($ddd, $fff, $iii) = listdir $real . "/" . $d->name;
+% my $tn;
+% if ($iii) { $tn = join_paths $path, $d->name . "/" . $iii->name; }
+% else { $tn = undef; }
+ <& .thumbnail, target => $d->name . "/", img => $tn,
+ caption => $m->interp->apply_escapes($d->name, "h"),
+ comment => $d->comment &>\
+% }
+</div>
+% }
+%
+% if (@$ff) {
+<h2>Images</h2>
+<div class=gallery>
+% for my $f (@$ff) {
+ <& .thumbnail, target => $f->name, img => $path . $f->name,
+ caption => $m->interp->apply_escapes($f->name, "h"),
+ comment => $f->comment &>\
+% }
+</div>
+% }
+%
+<div class=fill></div>
+<& .footer, path => $path &>
+</&>
+%
+<%args>
+ $path
+</%args>
+</%def>
+%
+%###-------------------------------------------------------------------------
+<%def .image>\
+<%perl>
+ my ($dir, $base, $ext) = split_path $path;
+ my $real = join_paths $IMGROOT, $path;
+ my $url = join_paths $IMGURL, $path;
+ my $realdir = join_paths $IMGROOT, $dir;
+ my $urldir = join_paths $SCRIPTURL, $dir;
+ my ($dd, $ff, $ii) = listdir $realdir;
+ my $vw = scaled "view", $path;
+
+ my $fi = undef;
+ FILE: for (my $i = 0; $i < @$ff; $i++)
+ { if ($ff->[$i]->name eq "$base$ext") { $fi = $i; last FILE; } }
+ defined $fi or die "image not found in its folder?";
+ my $this = $ff->[$fi];
+
+ my %link;
+ $link{up} = "";
+ if ($fi != 0) {
+ $link{first} = $ff->[0]->name;
+ $link{prev} = $ff->[$fi - 1]->name;
+ }
+ if ($fi != @$ff - 1) {
+ $link{last} = $ff->[-1]->name;
+ $link{next} = $ff->[$fi + 1]->name;
+ }
+
+ my $links = "";
+ my $pre =
+ urlencode join_paths $SCRIPTURL, $dir;
+ for my $rel (qw{up first prev next last}) {
+ exists $link{$rel} and
+ $links .= sprintf " <link rel=%s href=\"%s\">\n",
+ $rel, urlencode "$pre/$link{$rel}";
+ }
+</%perl>
+%
+<&| .html, title => "Image " . $m->interp->apply_escapes($path, "h"),
+ head => $links &>
+<& .breadcrumbs, what => "Image", path => $path &>
+% if ($this->comment) {
+ <div class=comment>
+ <p><% $this->comment %>
+ </div>
+% }
+%
+<div class=viewnav>
+% if ($link{prev}) {
+ <div class=prev><a class=prev href="<% "$pre/$link{prev}" |u %>">‹</a></div>
+% }
+ <a class=view href="<% $url |h %>">
+ <img src="<% $vw |h %>">
+ </a>
+% if ($link{next}) {
+ <div class=next><a class=next href="<% "$pre/$link{next}" |u %>">›</a></div>
+% }
+</div>
+%
+<div class=thumbstrip>
+% for my $f (@$ff) {
+ <& .thumbnail, target => $f->name, img => $dir . "/" . $f->name,
+ caption => $m->interp->apply_escapes($f->name, "h"),
+ focus => $f->name eq "$base$ext" &>\
+% }
+</div>
+<& .footer, path => $dir &>
+</&>
+%
+<%args>
+ $path
+</%args>
+</%def>
+%
+%###-------------------------------------------------------------------------
+<%def .breadcrumbs>\
+% $path =~ s!/$!!;
+% my @p = split m!/!, $path;
+% my $pp = "";
+% my $prev = undef;
+<h1><% $what %> \
+% if (!@p) {
+[top]
+% } else {
+<a href="<% $SCRIPTURL |u %>/">[top]</a> / \
+% STEP: for my $p (@p) {
+% if (defined $prev) {
+% $pp .= "$prev/";
+<a href="<% join_paths($SCRIPTURL, $pp) |u %>/">\
+<% $prev %></a> / \
+% }
+% $prev = $p;
+% }
+<% $prev %>\
+% }
+</h1>
+<%args>
+ $what
+ $path
+</%args>
+</%def>
+%
+%###-------------------------------------------------------------------------
+<%def .thumbnail>\
+% my $tn;
+% if (defined $img) { $tn = scaled "thumb", $img; }
+% else { $tn = "$STATICURL/folder.svg"; }
+% if ($focus) {
+ <div class=pic id=focusthumb>
+ <img class=thumb src="<% $tn |u %>">
+ <div class=caption><span class=name><% $caption %></span></div>
+% } else {
+ <div class=pic>
+ <a class=pic href="<% $target |u %>">
+ <img class=thumb src="<% $tn |u %>">
+ <div class=caption>
+ <span class=name><% $caption %></span>
+% if (defined $comment) {
+ <span class=comment><% $comment %></span>
+% }
+ </div>
+ </a>
+% }
+ </div>
+%
+<%args>
+ $target
+ $img
+ $caption
+ $comment => undef
+ $focus => 0
+</%args>
+</%def>
+%
+%###-------------------------------------------------------------------------
+<%def .footer>\
+<%perl>
+</%perl>
+<div class=footer>
+ <div class=footitem>
+ <a href="https://www.gnu.org/licenses/agpl-3.0.en.html"><img class=licence src="<% "$STATICURL/agpl.png" |u %>"></a>
+ Trivial Gallery, copyright © 2021 Mark Wooding.
+ Free software: you can modify it and/or redistribute it under the
+ terms of the
+ <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">GNU Affero
+ General Public License version 3</a>.
+ Browse or download
+ the <a href="https://git.distorted.org.uk/~mdw/tgal/">source code</a>.
+ </div>
+% my $user =
+% find_covering_file $IMGROOT, $path, ".tgal-footer.html";
+% if (defined $user) {
+ <div class=footitem>
+<% $user %>
+ </div>
+% }
+</div>
+<%args>
+ $path
+</%args>
+</%def>
+%
+%###-------------------------------------------------------------------------
+<%once>
+ use autodie;
+use Data::Dumper;
+ use File::stat;
+
+ use TrivGal;
+</%once>
+%
+<%init>
+ TrivGal->init;
+
+ my $path = $m->dhandler_arg;
+ my $st = stat "$IMGROOT/$path";
+ my $comp;
+ if (!$st) { $comp = ".not-found"; }
+ elsif (-d $st) { $comp = ".contact"; }
+ elsif (-f $st) { $comp = ".image"; }
+ else { $comp = ".not-found"; }
+ $m->comp($comp, path => $path);
+</%init>
+%
+%###----- That's all, folks -------------------------------------------------
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="240"
+ height="240"
+ id="svg97"
+ sodipodi:version="0.32"
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
+ sodipodi:docname="folder.svg"
+ inkscape:export-filename="/home/jimmac/Desktop/horlander-style3.png"
+ inkscape:export-xdpi="90.000000"
+ inkscape:export-ydpi="90.000000"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ version="1.1">
+ <defs
+ id="defs3">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ id="perspective68" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5060"
+ id="radialGradient6719"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+ cx="605.71429"
+ cy="486.64789"
+ fx="605.71429"
+ fy="486.64789"
+ r="117.14286" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5060">
+ <stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0"
+ id="stop5062" />
+ <stop
+ style="stop-color:black;stop-opacity:0;"
+ offset="1"
+ id="stop5064" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5060"
+ id="radialGradient6717"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+ cx="605.71429"
+ cy="486.64789"
+ fx="605.71429"
+ fy="486.64789"
+ r="117.14286" />
+ <linearGradient
+ id="linearGradient5048">
+ <stop
+ style="stop-color:black;stop-opacity:0;"
+ offset="0"
+ id="stop5050" />
+ <stop
+ id="stop5056"
+ offset="0.5"
+ style="stop-color:black;stop-opacity:1;" />
+ <stop
+ style="stop-color:black;stop-opacity:0;"
+ offset="1"
+ id="stop5052" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5048"
+ id="linearGradient6715"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+ x1="302.85715"
+ y1="366.64789"
+ x2="302.85715"
+ y2="609.50507" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient9806">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop9808" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop9810" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient9766">
+ <stop
+ style="stop-color:#6194cb;stop-opacity:1;"
+ offset="0"
+ id="stop9768" />
+ <stop
+ style="stop-color:#729fcf;stop-opacity:1;"
+ offset="1"
+ id="stop9770" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3096">
+ <stop
+ id="stop3098"
+ offset="0"
+ style="stop-color:#424242;stop-opacity:1;" />
+ <stop
+ id="stop3100"
+ offset="1.0000000"
+ style="stop-color:#777777;stop-opacity:1.0000000;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient319"
+ inkscape:collect="always">
+ <stop
+ id="stop320"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop321"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient1789">
+ <stop
+ style="stop-color:#202020;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop1790" />
+ <stop
+ style="stop-color:#b9b9b9;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop1791" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient1789"
+ id="radialGradient238"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.055022,-0.02734504,0.177703,1.190929,-3.572177,-7.125301)"
+ cx="20.706017"
+ cy="37.517986"
+ fx="20.706017"
+ fy="37.517986"
+ r="30.905205" />
+ <linearGradient
+ id="linearGradient3983">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.87628865;"
+ offset="0.0000000"
+ id="stop3984" />
+ <stop
+ style="stop-color:#fffffe;stop-opacity:0.0000000;"
+ offset="1.0000000"
+ id="stop3985" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3983"
+ id="linearGradient491"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.516844,0,0,0.708978,-0.879573,-1.318166)"
+ x1="6.2297964"
+ y1="13.773066"
+ x2="9.8980894"
+ y2="66.834053" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="46.689312"
+ x2="12.853771"
+ y1="32.567184"
+ x1="13.035696"
+ gradientTransform="matrix(1.317489,0,0,0.816256,-0.879573,-1.318166)"
+ id="linearGradient322"
+ xlink:href="#linearGradient319"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="6.1802502"
+ x2="15.514889"
+ y1="31.36775"
+ x1="18.112709"
+ id="linearGradient3104"
+ xlink:href="#linearGradient3096"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient9766"
+ id="linearGradient9772"
+ x1="22.175976"
+ y1="36.987999"
+ x2="22.065331"
+ y2="32.050499"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient9806"
+ id="radialGradient9812"
+ cx="24.35099"
+ cy="41.591846"
+ fx="24.35099"
+ fy="41.591846"
+ r="19.136078"
+ gradientTransform="matrix(1,0,0,0.242494,0,31.50606)"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <sodipodi:namedview
+ fill="#729fcf"
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.10196078"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="144.11879"
+ inkscape:cy="101.28997"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1026"
+ inkscape:window-height="818"
+ inkscape:window-x="169"
+ inkscape:window-y="30"
+ inkscape:showpageshadow="false"
+ stroke="#3465a4" />
+ <metadata
+ id="metadata4">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ <dc:date />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <cc:license
+ rdf:resource="http://creativecommons.org/publicdomain/zero/1.0/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>folder</rdf:li>
+ <rdf:li>directory</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/publicdomain/zero/1.0/">
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Folder"
+ inkscape:groupmode="layer"
+ transform="translate(0,192)">
+ <g
+ id="g95"
+ transform="matrix(5.1567691,0,0,5.1567691,0.04843989,-196.52972)">
+ <g
+ id="g6707"
+ transform="matrix(0.02262383,0,0,0.02086758,43.38343,36.36962)"
+ style="display:inline">
+ <rect
+ y="-150.69685"
+ x="-1559.2523"
+ height="478.35718"
+ width="1339.6335"
+ id="rect6709"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.40206185;fill:url(#linearGradient6715);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc"
+ id="path6711"
+ d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.40206185;fill:url(#radialGradient6717);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.40206185;fill:url(#radialGradient6719);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
+ id="path6713"
+ sodipodi:nodetypes="cccc" />
+ </g>
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccssssccc"
+ style="fill:url(#radialGradient238);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient3104);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path216"
+ d="m 4.5217805,38.687417 c 0.021796,0.416304 0.4599049,0.832609 0.8762095,0.832609 h 31.327021 c 0.416302,0 0.810812,-0.416305 0.789016,-0.832609 L 36.577584,11.460682 c -0.0218,-0.416303 -0.459897,-0.832616 -0.876201,-0.832616 H 22.43051 c -0.485057,0 -1.234473,-0.315589 -1.401644,-1.1066322 L 20.417475,6.6283628 C 20.262006,5.8926895 19.535261,5.5904766 19.118957,5.5904766 H 4.3400975 c -0.4163128,0 -0.8108208,0.4163041 -0.7890249,0.8326083 z" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 5.2265927,22.5625 H 35.492173"
+ id="path9788"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 5.0421736,18.5625 H 35.489104"
+ id="path9784"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc"
+ id="path9778"
+ d="M 4.9806965,12.5625 H 35.488057"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc"
+ id="path9798"
+ d="M 5.3861577,32.5625 H 35.494881"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 5.5091398,34.5625 H 35.496893"
+ id="path9800"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc"
+ id="path9782"
+ d="M 5.0421736,16.5625 H 35.489104"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 5.0114345,14.5625 H 35.48858"
+ id="path9780"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 4.9220969,10.5625 H 20.202912"
+ id="path9776"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc"
+ id="path9774"
+ d="M 4.8737534,8.5624999 H 19.657487"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.99999982;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc"
+ id="path9794"
+ d="M 5.3246666,28.5625 H 35.493876"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 5.2880638,26.5625 H 35.493184"
+ id="path9792"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc"
+ id="path9790"
+ d="M 5.2265927,24.5625 H 35.492173"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc"
+ id="path9786"
+ d="M 5.1958537,20.5625 H 35.491649"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="M 5.3246666,30.5625 H 35.493876"
+ id="path9796"
+ sodipodi:nodetypes="cc" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc"
+ id="path9802"
+ d="M 5.5091398,36.5625 H 35.496893"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.11363633;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccscccccc"
+ id="path219"
+ d="m 6.068343,38.864023 c 0.016343,0.312228 -0.1809113,0.520379 -0.4985848,0.416303 v 0 C 5.2520766,39.176251 5.033027,38.968099 5.0166756,38.65587 L 4.068956,6.5913839 C 4.0526131,6.2791558 4.2341418,6.0906134 4.5463699,6.0906134 L 18.96842,6.0429196 c 0.312228,0 0.931943,0.3004727 1.132936,1.3221818 l 0.573489,2.8155346 C 20.247791,9.715379 20.255652,9.7010175 20.037287,9.0239299 L 19.631192,7.7647478 C 19.412142,7.0371009 18.932991,6.9328477 18.620763,6.9328477 H 5.7329889 c -0.3122276,0 -0.5094814,0.2081522 -0.4931306,0.5203887 L 6.1778636,38.968099 Z"
+ style="color:#000000;display:block;overflow:visible;visibility:visible;opacity:0.45142858;fill:url(#linearGradient491);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.21380496;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <g
+ inkscape:export-ydpi="74.800003"
+ inkscape:export-xdpi="74.800003"
+ inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
+ transform="matrix(1.040764,0,0.05449252,1.040764,-8.670199,2.670594)"
+ id="g220"
+ style="fill:#ffffff;fill-opacity:0.75706213;fill-rule:nonzero;stroke:none;stroke-width:0.99946535;stroke-miterlimit:4">
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscscs"
+ id="path221"
+ d="m 42.417183,8.5151772 c 0.0051,-0.097113 -0.128161,-0.2469882 -0.235117,-0.2470056 l -13.031401,-0.00212 c 0,0 0.911714,0.5879545 2.201812,0.5962436 l 11.053497,0.07102 c 0.01109,-0.2117278 0.0027,-0.2560322 0.01121,-0.4181395 z"
+ style="fill:#ffffff;fill-opacity:0.50847461" />
+ </g>
+ <path
+ inkscape:connector-curvature="0"
+ inkscape:export-ydpi="74.800003"
+ inkscape:export-xdpi="74.800003"
+ inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
+ sodipodi:nodetypes="cscccscc"
+ id="path233"
+ d="m 39.783532,39.51062 c 1.143894,-0.04406 1.963076,-1.096299 2.047035,-2.321005 0.791787,-11.548687 1.65936,-21.231949 1.65936,-21.231949 0.07215,-0.247484 -0.167911,-0.494967 -0.48014,-0.494967 H 8.6386304 c 0,0 -1.8503191,21.866892 -1.8503191,21.866892 -0.1145551,0.982066 -0.4660075,1.804718 -1.5498358,2.183713 z"
+ style="color:#000000;display:block;visibility:visible;fill:url(#linearGradient9772);fill-opacity:1;fill-rule:nonzero;stroke:#3465a4;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" />
+ <path
+ inkscape:connector-curvature="0"
+ style="opacity:0.46590911;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient322);stroke-width:0.9999997px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 9.6202444,16.463921 32.7910986,0.06481 -1.574046,20.001979 c -0.08432,1.071511 -0.450678,1.428215 -1.872656,1.428215 -1.871502,0 -28.677968,-0.03241 -31.394742,-0.03241 0.2335983,-0.320811 0.3337557,-0.988623 0.3350963,-1.004612 z"
+ id="path304"
+ sodipodi:nodetypes="ccsscsc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#ffffff;fill-opacity:0.0892857;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 9.6202481,16.223182 8.4536014,31.866453 c 0,0 8.2961546,-4.148078 18.6663476,-4.148078 10.370193,0 15.55529,-11.495193 15.55529,-11.495193 z"
+ id="path323"
+ sodipodi:nodetypes="ccccc" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="pattern"
+ transform="translate(0,192)" />
+</svg>
--- /dev/null
+/* -*-css-*-
+ *
+ * Style sheet for Trivial Gallery.
+ *
+ * (c) 2021 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Trivial Gallery.
+ *
+ * Trivial Gallery is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Trivial Gallery is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with Trivial Gallery. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+html { height: 100%; }
+body {
+ height: calc(100% - 2ex);
+ display: flex; flex-direction: column;
+ margin-top: 0; margin-bottom: 0;
+ background-color: white;
+ color: black;
+ margin: 1ex 2em;
+}
+
+a { text-decoration: none; }
+a:link { color: blue; }
+a:link:active, a:visited { color: darkblue; }
+a:link:hover, a:visited:hover { background: #ccc; }
+
+h1 {
+ padding: 0.47ex 0;
+ border-bottom: thick black solid;
+ margin-top: 0.5ex; margin-bottom: 1.41ex;
+ font-weight: bold;
+ font-size: 200%;
+}
+h1 + h2, div.toc + h2, h1 + div > h2 {
+ border-top: none;
+ padding-top: 1ex;
+ margin-top: 0;
+}
+h2 { border-top: thin black solid; padding-top: 1ex; }
+h2, h3 { margin-top: 3ex; }
+h2 { font-size: x-large; }
+h3 { font-size: large; }
+h4, h5, h6 { display: run-in; }
+h1, h2, h3, h4, h5, h6 { font-family: sans-serif; font-weight bold; }
+
+hr { width: calc(100% - 4em); }
+div.fill { flex-grow: 1; }
+
+div.footer {
+ border-top: medium black solid;
+ margin-top: 3.43ex;
+ padding-top: 1ex;
+ font-size: small;
+ font-style: italic;
+ text-align: right;
+}
+div.footer img.licence { float: left; margin: 1ex; }
+div.footitem {
+ margin-top: 1ex; margin-bottom: 1ex;
+ clear: both;
+}
+
+div.gallery {
+ display: block;
+ text-align: center;
+}
+
+div.pic {
+ display: inline-block;
+ vertical-align: top;
+ width: 228px;
+ margin: 1em;
+}
+
+div.pic a:link { display: inline-block; }
+
+img.thumb {
+ width: 228px; height: 228px;
+ object-fit: contain;
+}
+
+div.comment {
+ border: thin black solid;
+ max-width: 40em;
+ align-self: center;
+ background-color: #ccc;
+ padding-left: 1em; padding-right: 1em;
+ margin-top: 2ex; margin-bottom: 2ex;
+}
+
+div.caption {
+ display: block;
+ width: 228px;
+ white-space: normal;
+}
+div.caption span.name { font-family: sans-serif; }
+div.caption span.comment { font-style: italic; margin-inline-start: 1em; }
+
+div.viewnav {
+ flex-grow: 1; flex-basis: 0;
+ display: flex; flex-direction: row;
+ position: relative;
+}
+div.prev, div.next {
+ position: absolute;
+ height: 100%;
+ display: flex; flex-direction: row; align-items: center;
+}
+div.prev { left: 0%; }
+div.next { right: 0%; }
+a.prev, a.next {
+ font-size: 400%;
+ font-weight: bold;
+ background-color: #0006;
+ min-width: 1em;
+ text-align: center;
+ min-height: 3ex;
+}
+a.view {
+ flex-grow: 1; flex-basis: 0;
+ display: flex; flex-direction: column;
+}
+a:link:hover.view { background: inherit; }
+
+a.view img {
+ min-width: 0; min-height: 0;
+ max-width: 100%; max-height: 100%;
+ flex-grow: 1; flex-basis: 0;
+ object-fit: contain;
+}
+
+div.thumbstrip {
+ width: 100%;
+ overflow-x: auto;
+ text-align: center;
+ white-space: nowrap;
+}
+
+/*----- That's all, folks -------------------------------------------------*/
--- /dev/null
+/* -*-javascript-*-
+ *
+ * Interactive features for Trivial Gallery.
+ *
+ * (c) 2021 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Trivial Gallery.
+ *
+ * Trivial Gallery is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Trivial Gallery is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with Trivial Gallery. If not, see
+ * <https://www.gnu.org/licenses/>.
+ */
+
+/* Handle keyboard interaction. */
+addEventListener("keydown", function (ev) {
+ var dir;
+ if (ev.key === " " || ev.key === "ArrowRight") dir = "next";
+ else if (ev.key === " " || ev.key === "ArrowRight") dir = "next";
+ else if (ev.key === "Backspace" || ev.key === "ArrowLeft") dir = "prev";
+ else if (ev.key === "^") dir = "up";
+ else if (ev.key === "<") dir = "first";
+ else if (ev.key === ">") dir = "last";
+ else return;
+ var elt = document.querySelector("link[rel=" + dir + "]");
+ if (!elt) return;
+ location = elt.getAttribute("href");
+ ev.stopPropagation();
+}, true);
+
+/* Scroll the thumbnail strip so that the current image is in the middle. */
+(function () {
+ var strip = document.querySelector("div.thumbstrip");
+ var focus = document.querySelector("#focusthumb");
+ if (strip && focus) {
+ var stripbox = strip.getBoundingClientRect();
+ var focusbox = focus.getBoundingClientRect();
+ strip.scrollLeft +=
+ (focusbox.x - stripbox.x) -
+ (stripbox.width - focusbox.width)/2;
+ }
+})();
+
+/*----- That's all, folks -------------------------------------------------*/