]> SVR.JS Git server - svrjs-shortener.git/commitdiff
authorDorian Niemiec <dorian.niemiec@svrjs.org>
Tue, 16 Apr 2024 19:20:08 +0000 (21:20 +0200)
committerDorian Niemiec <dorian.niemiec@svrjs.org>
Tue, 16 Apr 2024 19:20:08 +0000 (21:20 +0200)
27 files changed:
README.txt [new file with mode: 0644]
backend/serverSideScript.js [new file with mode: 0644]
backend/shortener-config.json [new file with mode: 0644]
backend/templates/index.html.template [new file with mode: 0644]
frontend/css/main-ie7.css [new file with mode: 0644]
frontend/css/main.css [new file with mode: 0644]
frontend/favicon.ico [new file with mode: 0644]
frontend/fonts/DejaVuSansMono-Bold.ttf [new file with mode: 0644]
frontend/fonts/DejaVuSansMono-Bold.woff [new file with mode: 0644]
frontend/fonts/DejaVuSansMono-BoldOblique.ttf [new file with mode: 0644]
frontend/fonts/DejaVuSansMono-BoldOblique.woff [new file with mode: 0644]
frontend/fonts/DejaVuSansMono-Oblique.ttf [new file with mode: 0644]
frontend/fonts/DejaVuSansMono-Oblique.woff [new file with mode: 0644]
frontend/fonts/DejaVuSansMono.ttf [new file with mode: 0644]
frontend/fonts/DejaVuSansMono.woff [new file with mode: 0644]
frontend/fonts/FreeSans.ttf [new file with mode: 0644]
frontend/fonts/FreeSans.woff [new file with mode: 0644]
frontend/fonts/FreeSansBold.ttf [new file with mode: 0644]
frontend/fonts/FreeSansBold.woff [new file with mode: 0644]
frontend/fonts/FreeSansBoldOblique.ttf [new file with mode: 0644]
frontend/fonts/FreeSansBoldOblique.woff [new file with mode: 0644]
frontend/fonts/FreeSansOblique.ttf [new file with mode: 0644]
frontend/fonts/FreeSansOblique.woff [new file with mode: 0644]
frontend/fonts/main.css [new file with mode: 0644]
frontend/img/navbar-logo.png [new file with mode: 0644]
frontend/js/html5shiv.js [new file with mode: 0644]
frontend/robots.txt [new file with mode: 0644]

diff --git a/README.txt b/README.txt
new file mode 100644 (file)
index 0000000..583b068
--- /dev/null
@@ -0,0 +1,5 @@
+The "backend" folder contains files, that would be copied to /usr/lib/svrjs directory.
+The "frontend" folder contains files, that would be copied to /var/www/svrjs directory.
+
+You may need to set "useWebRootServerSideScript" in SVR.JS config.json to "false" and set up HTTP authentication at "/admin" and "/admin/" URLs.
+
diff --git a/backend/serverSideScript.js b/backend/serverSideScript.js
new file mode 100644 (file)
index 0000000..77f96e8
--- /dev/null
@@ -0,0 +1,186 @@
+disableEndElseCallbackExecute = true;
+
+var querystring = require("querystring");
+
+var urlregex = /^(?:https?:\/\/)?(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:])*@)?(?:\[(?:(?:(?:[0-9a-f]{1,4}:){6}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|::(?:[0-9a-f]{1,4}:){5}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3})|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|v[0-9a-f]+\.[-a-z0-9\._~!\$&'\(\)\*\+,;=:]+)\]|(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}|(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=])*)(?::[0-9]*)?(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@]))*)*|\/(?:(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@]))+)(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@]))*)*)?|(?:(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@]))+)(?:\/(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@]))*)*|(?!(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@])))(?:\?(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@])|[\uE000-\uF8FF\/\?])*)?(?:\#(?:(?:%[0-9a-f][0-9a-f]|[-a-z0-9\._~\xA0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!\$&'\(\)\*\+,;=:@])|[\/\?])*)?$/i;
+
+var MongoClient = require('mongodb').MongoClient;
+if(!customvar1 && !customvar2) {
+  try {
+    customvar1 = JSON.parse(fs.readFileSync(__dirname + "/../shortener-config.json"));
+  } catch (err) {
+    customvar2 = err;
+  }
+}
+
+if(customvar2) {
+  // customvar2 is a instance of Error
+  callServerError(500, customvar2);
+}
+
+// customvar1 is a shortener configuration
+
+var mongoURL = customvar1.mongoURL;
+var dbname = customvar1.dbname;
+var defaultURL = customvar1.defaultURL;
+
+function antiXSS(string) {
+    return string.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;");
+}
+
+function generateShortenedURLID() {
+  const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+  let result = "";
+  for (let i = 0; i < 6; i++) {
+    result += chars.charAt(Math.floor(Math.random() * chars.length));
+  }
+  return result;
+}
+
+function generateUniqueShortenedURLID(db, callback) {
+  var id = generateShortenedURLID();
+  db.collection("urls").find({id: id}).toArray(function(err, result) {
+    if (err) {
+      db.close();
+      callServerError(500, err);
+      return;
+    }
+    if(result.length > 0) {
+      generateUniqueShortenedURLID(db);
+    } else {
+      callback(id);
+    }
+  });
+}
+
+function escapeRegExp(string) {
+    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+}
+
+function connectMongo(callback) {
+  MongoClient.connect(mongoURL, function(err, db) {
+    if (err) {
+      callServerError(500, err);
+      return;
+    }
+    var dbo = db.db(dbname);
+    dbo.createCollection("urls", function(err, dbres) {
+      callback(dbo);
+    });
+  });
+}
+
+function formatTemplate(templateName, templateData, callback) {
+    fs.readFile(__dirname + "/../templates/" + templateName + ".template", function(err, data) {
+      if (err) {
+        callServerError(500, err);
+        return;
+      }
+      var tD = data.toString();
+      Object.keys(templateData).forEach(function (key) {
+        tD = tD.replace(new RegExp("\\{\\{" + escapeRegExp(key) + "\\}\\}", "g"), templateData[key]);
+      });
+      callback(tD);
+    });
+}
+if (href.match(/^\/admin\/?$/)) {
+  if(req.method != "POST") {
+    formatTemplate("index.html", {
+      "url": "",
+      "shorturl": ""
+    }, function(data) {
+      res.writeHead(200, {"Content-Type": "text/html; charset=utf-8"});
+      res.end(data);
+    });
+     return;
+   }
+   var postdata = "";
+   req.on("data", function(data) {postdata += data.toString();});
+   req.on("end", function() {
+     postdata = querystring.parse(postdata);
+     if(!postdata.url) {
+       formatTemplate("index.html", {
+         "url": "",
+         "shorturl": "<b>URL must not be empty</b>"
+       }, function(data) {
+         res.writeHead(400, {"Content-Type": "text/html; charset=utf-8"});
+         res.end(data);
+       });
+       return;
+     } else if(!postdata.url.match(urlregex)) {
+       formatTemplate("index.html", {
+         "url": "",
+         "shorturl": "<b>Invalid URL</b>"
+       }, function(data) {
+         res.writeHead(400, {"Content-Type": "text/html; charset=utf-8"});
+         res.end(data);
+       });
+       return;
+     } else {
+       function finalizeResponse(uri, id) {
+         var shorturl = (req.socket.encrypted ? "https" : "http") + "://" + (req.headers.host ? req.headers.host : req.socket.localAddress) + "/" + id;
+         formatTemplate("index.html", {
+           "url": antiXSS(uri),
+           "shorturl": "<p>Shortened URL: <b><a href=\"" + antiXSS(shorturl) + "\" target=\"_blank\">" + antiXSS(shorturl) + "</a></b>"
+         }, function(data) {
+           res.writeHead(200, {"Content-Type": "text/html; charset=utf-8"});
+           res.end(data);
+         });
+       }
+       var purl = postdata.url;
+       if(!purl.match(/^https?:\/\//i)) purl = "http://" + purl;
+       connectMongo(function(db) {
+         db.collection("urls").find({url: purl}).toArray(function(err, result) {
+           if (err) {
+             db.close();
+             callServerError(500, err);
+             return;
+           }
+           if(result.length > 0) {
+             finalizeResponse(purl, result[0].id);
+           } else {
+             generateUniqueShortenedURLID(db, function(id) {
+               db.collection("urls").insertOne({id: id, url: purl}, function(err, dbres) {
+                 if (err) {
+                   db.close();
+                   callServerError(500, err);
+                   return;
+                 }
+                 finalizeResponse(purl, id);
+               });
+             });
+           }
+         });
+       });
+     }
+   });
+} else {
+  function shortenRedirect() {
+    var id = href.substr(1);
+    connectMongo(function(db) {
+      db.collection("urls").find({id: id}).toArray(function(err, result) {
+        if (err) {
+          db.close();
+          callServerError(500, err);
+          return;
+        }
+        if(result.length > 0) {
+          redirect(result[0].url);
+        } else {
+          redirect(defaultURL, href == "/" ? false : true);
+        }
+      });
+    });
+  }
+  if (href == "/") {
+    shortenRedirect();
+  } else {
+    fs.stat("." + decodeURIComponent(href), function(err, stats) {
+      if (!err && (stats.isDirectory() || stats.isFile())) {
+        elseCallback();
+      } else {
+        shortenRedirect();
+      }
+    });
+  }
+}
diff --git a/backend/shortener-config.json b/backend/shortener-config.json
new file mode 100644 (file)
index 0000000..23f9b3c
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  "mongoURL": "mongodb://localhost/shortenerdb",
+  "database": "shortenerdb",
+  "defaultURL": "https://svrjs.org"
+}
diff --git a/backend/templates/index.html.template b/backend/templates/index.html.template
new file mode 100644 (file)
index 0000000..0e4513f
--- /dev/null
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html lang="en-US">
+    <head>
+    
+    <title>Shorten URL - SVR.JS</title>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    
+    <link rel="shortcut icon" href="/favicon.ico">
+    
+    <!--[if lt IE 9]>
+<script src="/js/html5shiv.js"></script>
+<![endif]-->
+    
+    
+<link rel="stylesheet" href="/css/main.css">
+
+    <!--[if lte IE 7]>
+<link rel="stylesheet" href="/css/main-ie7.css">
+<![endif]-->
+    
+    
+    
+    
+
+  </head>
+
+  <body>
+    
+    
+
+
+    <header class="header">
+      <div class="header-contents">
+        <div class="mainlink">
+          <a href="https://svrjs.org/" translate="no">
+            <span class="headlogo-container">
+              <img src="/img/navbar-logo.png" class="headlogo" alt="SVR.JS logo"><span class="headname">SVR.JS</span>
+            </span>
+          </a>
+        </div>
+        <nav class="navigation">
+          <ul>
+            <li><a href="https://forum.svrjs.org">Forum</a></li>
+            <li><a href="https://git.svrjs.org">Git</a></li>
+            <li><a href="https://blog.svrjs.org">Blog</a></li>
+            <li><a href="https://svrjs.org/docs">Docs</a></li>
+          </ul>
+        </nav>
+      </div>
+    </header>
+
+
+    
+
+<div class="content">
+
+  <main class="real-content">
+
+    <h1>Shorten URL</h1>
+<form method="post">
+      <label for="url">URL to shorten:</label>
+      <input type="url" name="url" placeholder="https://example.com" value="{{url}}">
+      <input type="submit" value="Shorten!">
+    </form>
+    {{shorturl}}
+
+  </main>
+</div>
+
+
+    
+    <footer class="footer">
+      <div class="footer-contents">
+        <div class="footer-layout">
+          <div class="footer-column">
+            <span class="footer-headline">Resources and development</span>
+            <ul class="footer-list">
+              <li><a href="https://blog.svrjs.org">Blog</a> (<a href="https://blog.svrjs.org/atom.xml">Feed</a>)</li>
+              <li><a href="https://git.svrjs.org">Git server</a></li>
+              <li><a href="https://svrjs.org/contribute">Contribute</a></li>
+              <li><a href="https://svrjs.org/changelog">Change log</a></li>
+              <li><a href="https://downloads.svrjs.org">Downloads</a></li>
+              <li><a href="https://svrjs.org/mods">Official SVR.JS mods</a></li>
+              <li><a href="https://svrjs.org/branding">Branding</a></li>
+            </ul>
+          </div>
+          <div class="footer-column">
+            <span class="footer-headline">Support</span>
+            <ul class="footer-list">
+              <li><a href="https://svrjs.org/docs">Documentation</a></li>
+              <li><a href="https://forum.svrjs.org">Forum</a> (<a href="https://forum.svrjs.org/feed">Feed</a>)</li>
+              <li><a href="https://svrjs.org/contact">Contact us</a></li>
+              <li><a href="https://status.svrjs.org">Server uptime</a></li>
+            </ul>
+          </div>
+          <div class="footer-column">
+            <span class="footer-headline">Social</span>
+            <ul class="footer-list">
+              <li><a rel="me" href="https://mastodon.social/@svrjs">Mastodon</a></li>
+              <li><a rel="me" href="https://twitter.com/SVR_JS">X (Twitter)</a></li>
+              <li><a rel="me" href="https://bsky.app/profile/svrjs.org">Bluesky</a></li>
+            </ul>
+            <span class="footer-headline">Policies and Security</span>
+            <ul class="footer-list">
+              <li><a href="https://svrjs.org/vulnerabilities">Security</a></li>
+              <li><a href="https://svrjs.org/toc">Terms and Conditions</a></li>
+              <li><a href="https://svrjs.org/privacy">Privacy Policy</a></li>
+            </ul>
+          </div>
+        </div>
+        <hr/>
+        <p class="footer-copyright-text">Copyright &copy; 2023-2024 SVR.JS. All rights reserved.</p>
+      </div>
+    </footer>
+
+
+    
+  </body>
+</html>
diff --git a/frontend/css/main-ie7.css b/frontend/css/main-ie7.css
new file mode 100644 (file)
index 0000000..e897366
--- /dev/null
@@ -0,0 +1,18 @@
+.mainlink {
+  float: left;
+}
+.headname {
+  line-height: normal;
+  display: inline;
+}
+.navigation {
+  float: right;
+  padding: 10px 0;
+}
+.footer-layout {
+  display: block;
+  text-align: center;
+}
+.footer-column {
+  display: block;
+}
diff --git a/frontend/css/main.css b/frontend/css/main.css
new file mode 100644 (file)
index 0000000..4995135
--- /dev/null
@@ -0,0 +1,556 @@
+/*
+* Prefixed by https://autoprefixer.github.io
+* PostCSS: v8.4.14,
+* Autoprefixer: v10.4.7
+* Browsers: last 400 version
+*/
+
+body {
+  margin: 0;
+  padding: 0;
+  font-family: FreeSans, Helvetica, Tahoma, Verdana, Arial, sans-serif;
+  background-color: #dfffdf;
+  color: #000000;
+}
+
+.translated-rtl {
+  direction: rtl;
+}
+
+.header {
+  position: relative;
+  position: -webkit-sticky;
+  position: sticky;
+  top: 0;
+  z-index: 10;
+  padding: 0.65em 0.45em;
+  background-color: #ffffff;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  display: block;
+}
+
+.header:after {
+  content: '';
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  -webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
+  -moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
+  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
+  z-index: -1;
+}
+
+.header-contents {
+  display: table;
+  width: 100%;
+  table-layout: fixed;
+}
+
+.headlogo {
+  display: inline;
+  height: 2.2em;
+  padding-left: 0.2em;
+  padding-right: 0.35em;
+  padding-bottom: 0.03em;
+  vertical-align: middle;
+}
+
+.translated-rtl .headlogo {
+  padding-left: 0.35em;
+  padding-right: 0.2em;
+}
+
+.headname {
+  font-size: 2.3em;
+  font-weight: bold;
+  vertical-align: middle;
+  line-height: 0.9em;
+  display: inline-block;
+}
+
+.navigation {
+  display: table-cell;
+  text-align: right;
+  position: relative;
+  vertical-align: middle;
+  width: 57.5%;
+}
+
+.navigation ul {
+  overflow-x: auto;
+  overflow-y: hidden;
+  white-space: nowrap;
+  margin: 0;
+  padding: 0;
+  list-style-type: none;
+  display: block;
+}
+
+.mainlink {
+  display: table-cell;
+  vertical-align: middle;
+  position: relative;
+  white-space: nowrap;
+  width: 42.5%;
+}
+
+.mainlink a {
+  text-decoration: none;
+  color: #000000;
+}
+
+.translated-rtl .navigation {
+  text-align: left;
+}
+
+.headlogo-container {
+  display: inline-block;
+}
+
+.navigation ul li {
+  display: inline;
+}
+
+.navigation ul li a {
+  text-decoration: none;
+  font-size: 1.45em;
+  padding: 0 0.25em;
+  color: #000000;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+
+@-moz-document url-prefix() { 
+  .navigation ul li a {
+    display: inline-block;
+    margin-bottom: 0.05em;
+  }
+}
+
+.navigation ul li a:hover {
+  color: #007f00;
+}
+
+.content {
+  padding: 0.5em;
+}
+
+.real-content {
+  width: 100%;
+  max-width: 1280px;
+  margin: 0 auto;
+  display: block;
+}
+
+.real-content img {
+  max-width: 100%;
+  margin: auto;
+  display: block;
+}
+
+.styled-button {
+  display: inline-block;
+  text-decoration: none;
+  color: #ffffff;
+  background-color: #00b000;
+  font-size: 1.225em;
+  padding: 0.725em;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  -webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.25);
+  -moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.25);
+  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.25);
+}
+
+.styled-button:hover {
+  background-color: #007000;
+}
+
+.styled-button.styled-button-disabled, .styled-button.styled-button-disabled:hover {
+  background-color: #808080;
+}
+
+.footer {
+  background-color: #007000;
+  color: #ffffff;
+  -webkit-box-shadow: 0 -4px 8px 0 rgba(0, 0, 0, 0.2);
+  -moz-box-shadow: 0 -4px 8px 0 rgba(0, 0, 0, 0.2);
+  box-shadow: 0 -4px 8px 0 rgba(0, 0, 0, 0.2);
+  padding: 1.75em 1em;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  display: block;
+}
+
+.footer hr {
+  border-color: white;    
+}
+
+.footer a {
+  color: #ffffff;
+}
+
+.footer a:hover {
+  color: #00ff00;
+}
+
+.footer-contents {
+  max-width: 1440px;
+  width: 100%;
+  margin: auto;
+}
+
+.footer-copyright-text {
+  font-size: 1.25em;
+  margin: 0;
+  margin-top: 0.5em;
+  padding-bottom: 0.5em;
+  display: block;
+  text-align: center;
+}
+
+.footer-layout {
+  display: table;
+  table-layout: fixed;
+  width: 100%;
+  font-size: 1.075em;
+}
+
+.footer-column {
+  display: table-cell;
+  padding: 0.3em;
+}
+
+.footer-headline {
+  display: block;
+  font-weight: bold;
+  font-size: 1.525em;
+}
+
+.footer-list {
+  margin: 0;
+  padding: 0;
+  margin-bottom: 1em;
+  list-style-type: none;
+}
+
+img {
+  border-style: none;
+}
+
+table {
+  border-collapse: collapse;
+  margin: 0;
+  overflow-wrap: break-word;
+  word-wrap: break-word;
+  word-break: break-all;
+  word-break: break-word;
+  position: relative;
+}
+
+table tbody {
+  background-color: #ffffff;
+  color: #000000;
+}
+
+table tbody:after {
+  -webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.175);
+  -moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.175);
+  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.175);
+  content: ' ';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: -1;
+}
+
+table img {
+  margin: 0;
+  display: inline;
+}
+
+th,
+tr {
+  padding: 0.15em;
+  text-align: center;
+}
+
+th {
+  background-color: #007000;
+  color: #ffffff;
+}
+
+th a {
+  color: #ffffff;
+}
+
+.highlight, .gist {
+  margin: 0 -0.5em;
+}
+
+pre {
+  margin: 0 -0.588235em;
+}
+
+.header-banner {
+  background-color: #007000;
+  color: #ffffff;
+  font-size: 1.1em;
+  padding: 0.325em;
+  text-align: center;
+  -webkit-box-shadow: 0 -3px 6px 0 inset rgba(0, 0, 0, 0.2);
+  -moz-box-shadow: 0 -3px 6px 0 inset rgba(0, 0, 0, 0.2);
+  box-shadow: 0 -3px 6px 0 inset rgba(0, 0, 0, 0.2);
+}
+
+.header-banner a {
+  color: #ffffff;
+}
+
+.header-banner a:hover {
+  color: #00ff00;
+}
+
+input {
+  max-width: 60%;
+  font-family: FreeSans, Helvetica, Arial, sans-serif;
+  background-color: #fafafa;
+  border: 1px solid #bbbbbb;
+}
+
+select {
+  background-color: #fafafa;
+  border: 1px solid #bbbbbb;
+}
+
+input:hover, submit:hover,
+input:focus, submit:focus {
+  border-color: #00b000;
+}
+
+input[type=submit] {
+  font-weight: bold;
+  color: #ffffff;
+  border: none;
+  background-color: #00b000;
+  padding: 3px 5px;
+}
+
+input[type=submit]:hover {
+  background-color: #007000;
+  cursor: pointer;
+}
+
+@media screen and (-ms-high-contrast:active), (-ms-high-contrast:none) {
+  .headname {
+    margin-top: 0.06em;
+  }
+}
+
+@media screen and (prefers-color-scheme: dark) {
+  .dummy {}
+
+  body {
+    background-color: #002000;
+    color: #ffffff;
+  }
+
+  a {
+    color: #ffffff;
+  }
+
+  a:hover {
+    color: #00ff00;
+  }
+
+  .header {
+    background-color: #004f00;
+  }
+
+  .header:after {
+    -webkit-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.2);
+    -moz-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.2);
+    box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.2);
+  }
+
+  .mainlink a {
+    color: #ffffff;
+  }
+
+  .mainlink:hover {
+    color: #ffffff;
+  }
+
+  .navigation ul li a {
+    color: #ffffff;
+  }
+
+  .navigation ul li a:hover {
+    color: #00ff00;
+  }
+
+  .footer {
+    -webkit-box-shadow: 0 -4px 8px 0 rgba(127, 127, 127, 0.2);
+    -moz-box-shadow: 0 -4px 8px 0 rgba(127, 127, 127, 0.2);
+    box-shadow: 0 -4px 8px 0 rgba(127, 127, 127, 0.2);
+  }
+
+  .styled-button {
+    background-color: #00b000;
+    -webkit-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.25);
+    -moz-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.25);
+    box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.25);
+  }
+
+  .styled-button:hover {
+    background-color: #00d000;
+    color: #ffffff;
+  }
+
+  table tbody {
+    background-color: #000f00;
+    color: #ffffff;
+  }
+
+  table tbody:after {
+    -webkit-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.175);
+    -moz-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.175);
+    box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.175);
+  }
+
+  .header-banner {
+    -webkit-box-shadow: 0 -3px 6px 0 inset rgba(127, 127, 127, 0.2);
+    -moz-box-shadow: 0 -3px 6px 0 inset rgba(127, 127, 127, 0.2);
+    box-shadow: 0 -3px 6px 0 inset rgba(127, 127, 127, 0.2);
+  }
+
+  input, select {
+    background-color: #000000;
+    color: #ffffff;
+  }
+
+  input[type=submit] {
+    color: #ffffff;
+    background-color: #00b000;
+  }
+
+  input[type=submit]:hover {
+    background-color: #00d000;
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .footer-layout {
+    display: block;
+  }
+  
+  .footer-column {
+    display: block;
+    text-align: center;
+    padding: 0;
+  }
+}
+
+@media screen and (max-width: 480px) {
+  .headlogo {
+    height: 1.8em;
+    padding-left: 0;
+    padding-right: 0.25em;
+    padding-bottom: 0;
+  }
+
+  .translated-rtl .headlogo {
+    padding-left: 0.25em;
+    padding-right: 0;
+  }
+
+  .headname {
+    font-size: 1.85em;
+  }
+
+  .navigation ul li a {
+    font-size: 1.15em;
+    padding: 0 0.225em;
+  }
+}
+
+
+@media screen and (max-width: 372px) {
+  .navigation ul li a {
+    font-size: 1.075em;
+    padding: 0 0.2em;
+  }
+}
+
+@media screen and (max-width: 350px) {
+  .navigation {
+    display: block;
+    text-align: left;
+    width: 100%;
+  }
+
+  .translated-rtl .navigation {
+    text-align: right
+  }
+
+  .mainlink {
+    display: block;
+    width: 100%;
+  }
+
+  .header-contents {
+    display: block;
+  }
+}
+
+@media print {
+  .dummy {}
+  .header, .footer, .header-banner {
+    display: none;
+  }
+  .real-content {
+    max-width: none !important;
+  }
+}
+
+@font-face {
+  font-family: "FreeSans";
+  font-display: swap;
+  src: url("/fonts/FreeSans.woff") format("woff"), url("/fonts/FreeSans.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: "FreeSans";
+  font-weight: bold;
+  font-display: swap;
+  src: url("/fonts/FreeSansBold.woff") format("woff"), url("/fonts/FreeSansBold.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: "FreeSans";
+  font-style: italic;
+  font-display: swap;
+  src: url("/fonts/FreeSansOblique.woff") format("woff"), url("/fonts/FreeSansOblique.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: "FreeSans";
+  font-style: italic;
+  font-weight: bold;
+  font-display: swap;
+  src: url("/fonts/FreeSansBoldOblique.woff") format("woff"), url("/fonts/FreeSansBoldOblique.ttf") format("truetype");
+}
+
diff --git a/frontend/favicon.ico b/frontend/favicon.ico
new file mode 100644 (file)
index 0000000..c5d08a7
Binary files /dev/null and b/frontend/favicon.ico differ
diff --git a/frontend/fonts/DejaVuSansMono-Bold.ttf b/frontend/fonts/DejaVuSansMono-Bold.ttf
new file mode 100644 (file)
index 0000000..8184ced
Binary files /dev/null and b/frontend/fonts/DejaVuSansMono-Bold.ttf differ
diff --git a/frontend/fonts/DejaVuSansMono-Bold.woff b/frontend/fonts/DejaVuSansMono-Bold.woff
new file mode 100644 (file)
index 0000000..abb18d0
Binary files /dev/null and b/frontend/fonts/DejaVuSansMono-Bold.woff differ
diff --git a/frontend/fonts/DejaVuSansMono-BoldOblique.ttf b/frontend/fonts/DejaVuSansMono-BoldOblique.ttf
new file mode 100644 (file)
index 0000000..754dca7
Binary files /dev/null and b/frontend/fonts/DejaVuSansMono-BoldOblique.ttf differ
diff --git a/frontend/fonts/DejaVuSansMono-BoldOblique.woff b/frontend/fonts/DejaVuSansMono-BoldOblique.woff
new file mode 100644 (file)
index 0000000..abb1c2b
Binary files /dev/null and b/frontend/fonts/DejaVuSansMono-BoldOblique.woff differ
diff --git a/frontend/fonts/DejaVuSansMono-Oblique.ttf b/frontend/fonts/DejaVuSansMono-Oblique.ttf
new file mode 100644 (file)
index 0000000..4c858d4
Binary files /dev/null and b/frontend/fonts/DejaVuSansMono-Oblique.ttf differ
diff --git a/frontend/fonts/DejaVuSansMono-Oblique.woff b/frontend/fonts/DejaVuSansMono-Oblique.woff
new file mode 100644 (file)
index 0000000..6493cb6
Binary files /dev/null and b/frontend/fonts/DejaVuSansMono-Oblique.woff differ
diff --git a/frontend/fonts/DejaVuSansMono.ttf b/frontend/fonts/DejaVuSansMono.ttf
new file mode 100644 (file)
index 0000000..f578602
Binary files /dev/null and b/frontend/fonts/DejaVuSansMono.ttf differ
diff --git a/frontend/fonts/DejaVuSansMono.woff b/frontend/fonts/DejaVuSansMono.woff
new file mode 100644 (file)
index 0000000..629c352
Binary files /dev/null and b/frontend/fonts/DejaVuSansMono.woff differ
diff --git a/frontend/fonts/FreeSans.ttf b/frontend/fonts/FreeSans.ttf
new file mode 100644 (file)
index 0000000..9db9585
Binary files /dev/null and b/frontend/fonts/FreeSans.ttf differ
diff --git a/frontend/fonts/FreeSans.woff b/frontend/fonts/FreeSans.woff
new file mode 100644 (file)
index 0000000..2b60745
Binary files /dev/null and b/frontend/fonts/FreeSans.woff differ
diff --git a/frontend/fonts/FreeSansBold.ttf b/frontend/fonts/FreeSansBold.ttf
new file mode 100644 (file)
index 0000000..63644e7
Binary files /dev/null and b/frontend/fonts/FreeSansBold.ttf differ
diff --git a/frontend/fonts/FreeSansBold.woff b/frontend/fonts/FreeSansBold.woff
new file mode 100644 (file)
index 0000000..9c698ef
Binary files /dev/null and b/frontend/fonts/FreeSansBold.woff differ
diff --git a/frontend/fonts/FreeSansBoldOblique.ttf b/frontend/fonts/FreeSansBoldOblique.ttf
new file mode 100644 (file)
index 0000000..dde7f32
Binary files /dev/null and b/frontend/fonts/FreeSansBoldOblique.ttf differ
diff --git a/frontend/fonts/FreeSansBoldOblique.woff b/frontend/fonts/FreeSansBoldOblique.woff
new file mode 100644 (file)
index 0000000..e374d95
Binary files /dev/null and b/frontend/fonts/FreeSansBoldOblique.woff differ
diff --git a/frontend/fonts/FreeSansOblique.ttf b/frontend/fonts/FreeSansOblique.ttf
new file mode 100644 (file)
index 0000000..7452885
Binary files /dev/null and b/frontend/fonts/FreeSansOblique.ttf differ
diff --git a/frontend/fonts/FreeSansOblique.woff b/frontend/fonts/FreeSansOblique.woff
new file mode 100644 (file)
index 0000000..9953932
Binary files /dev/null and b/frontend/fonts/FreeSansOblique.woff differ
diff --git a/frontend/fonts/main.css b/frontend/fonts/main.css
new file mode 100644 (file)
index 0000000..4995135
--- /dev/null
@@ -0,0 +1,556 @@
+/*
+* Prefixed by https://autoprefixer.github.io
+* PostCSS: v8.4.14,
+* Autoprefixer: v10.4.7
+* Browsers: last 400 version
+*/
+
+body {
+  margin: 0;
+  padding: 0;
+  font-family: FreeSans, Helvetica, Tahoma, Verdana, Arial, sans-serif;
+  background-color: #dfffdf;
+  color: #000000;
+}
+
+.translated-rtl {
+  direction: rtl;
+}
+
+.header {
+  position: relative;
+  position: -webkit-sticky;
+  position: sticky;
+  top: 0;
+  z-index: 10;
+  padding: 0.65em 0.45em;
+  background-color: #ffffff;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  display: block;
+}
+
+.header:after {
+  content: '';
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  -webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
+  -moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
+  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
+  z-index: -1;
+}
+
+.header-contents {
+  display: table;
+  width: 100%;
+  table-layout: fixed;
+}
+
+.headlogo {
+  display: inline;
+  height: 2.2em;
+  padding-left: 0.2em;
+  padding-right: 0.35em;
+  padding-bottom: 0.03em;
+  vertical-align: middle;
+}
+
+.translated-rtl .headlogo {
+  padding-left: 0.35em;
+  padding-right: 0.2em;
+}
+
+.headname {
+  font-size: 2.3em;
+  font-weight: bold;
+  vertical-align: middle;
+  line-height: 0.9em;
+  display: inline-block;
+}
+
+.navigation {
+  display: table-cell;
+  text-align: right;
+  position: relative;
+  vertical-align: middle;
+  width: 57.5%;
+}
+
+.navigation ul {
+  overflow-x: auto;
+  overflow-y: hidden;
+  white-space: nowrap;
+  margin: 0;
+  padding: 0;
+  list-style-type: none;
+  display: block;
+}
+
+.mainlink {
+  display: table-cell;
+  vertical-align: middle;
+  position: relative;
+  white-space: nowrap;
+  width: 42.5%;
+}
+
+.mainlink a {
+  text-decoration: none;
+  color: #000000;
+}
+
+.translated-rtl .navigation {
+  text-align: left;
+}
+
+.headlogo-container {
+  display: inline-block;
+}
+
+.navigation ul li {
+  display: inline;
+}
+
+.navigation ul li a {
+  text-decoration: none;
+  font-size: 1.45em;
+  padding: 0 0.25em;
+  color: #000000;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+
+@-moz-document url-prefix() { 
+  .navigation ul li a {
+    display: inline-block;
+    margin-bottom: 0.05em;
+  }
+}
+
+.navigation ul li a:hover {
+  color: #007f00;
+}
+
+.content {
+  padding: 0.5em;
+}
+
+.real-content {
+  width: 100%;
+  max-width: 1280px;
+  margin: 0 auto;
+  display: block;
+}
+
+.real-content img {
+  max-width: 100%;
+  margin: auto;
+  display: block;
+}
+
+.styled-button {
+  display: inline-block;
+  text-decoration: none;
+  color: #ffffff;
+  background-color: #00b000;
+  font-size: 1.225em;
+  padding: 0.725em;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  -webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.25);
+  -moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.25);
+  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.25);
+}
+
+.styled-button:hover {
+  background-color: #007000;
+}
+
+.styled-button.styled-button-disabled, .styled-button.styled-button-disabled:hover {
+  background-color: #808080;
+}
+
+.footer {
+  background-color: #007000;
+  color: #ffffff;
+  -webkit-box-shadow: 0 -4px 8px 0 rgba(0, 0, 0, 0.2);
+  -moz-box-shadow: 0 -4px 8px 0 rgba(0, 0, 0, 0.2);
+  box-shadow: 0 -4px 8px 0 rgba(0, 0, 0, 0.2);
+  padding: 1.75em 1em;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  display: block;
+}
+
+.footer hr {
+  border-color: white;    
+}
+
+.footer a {
+  color: #ffffff;
+}
+
+.footer a:hover {
+  color: #00ff00;
+}
+
+.footer-contents {
+  max-width: 1440px;
+  width: 100%;
+  margin: auto;
+}
+
+.footer-copyright-text {
+  font-size: 1.25em;
+  margin: 0;
+  margin-top: 0.5em;
+  padding-bottom: 0.5em;
+  display: block;
+  text-align: center;
+}
+
+.footer-layout {
+  display: table;
+  table-layout: fixed;
+  width: 100%;
+  font-size: 1.075em;
+}
+
+.footer-column {
+  display: table-cell;
+  padding: 0.3em;
+}
+
+.footer-headline {
+  display: block;
+  font-weight: bold;
+  font-size: 1.525em;
+}
+
+.footer-list {
+  margin: 0;
+  padding: 0;
+  margin-bottom: 1em;
+  list-style-type: none;
+}
+
+img {
+  border-style: none;
+}
+
+table {
+  border-collapse: collapse;
+  margin: 0;
+  overflow-wrap: break-word;
+  word-wrap: break-word;
+  word-break: break-all;
+  word-break: break-word;
+  position: relative;
+}
+
+table tbody {
+  background-color: #ffffff;
+  color: #000000;
+}
+
+table tbody:after {
+  -webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.175);
+  -moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.175);
+  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.175);
+  content: ' ';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: -1;
+}
+
+table img {
+  margin: 0;
+  display: inline;
+}
+
+th,
+tr {
+  padding: 0.15em;
+  text-align: center;
+}
+
+th {
+  background-color: #007000;
+  color: #ffffff;
+}
+
+th a {
+  color: #ffffff;
+}
+
+.highlight, .gist {
+  margin: 0 -0.5em;
+}
+
+pre {
+  margin: 0 -0.588235em;
+}
+
+.header-banner {
+  background-color: #007000;
+  color: #ffffff;
+  font-size: 1.1em;
+  padding: 0.325em;
+  text-align: center;
+  -webkit-box-shadow: 0 -3px 6px 0 inset rgba(0, 0, 0, 0.2);
+  -moz-box-shadow: 0 -3px 6px 0 inset rgba(0, 0, 0, 0.2);
+  box-shadow: 0 -3px 6px 0 inset rgba(0, 0, 0, 0.2);
+}
+
+.header-banner a {
+  color: #ffffff;
+}
+
+.header-banner a:hover {
+  color: #00ff00;
+}
+
+input {
+  max-width: 60%;
+  font-family: FreeSans, Helvetica, Arial, sans-serif;
+  background-color: #fafafa;
+  border: 1px solid #bbbbbb;
+}
+
+select {
+  background-color: #fafafa;
+  border: 1px solid #bbbbbb;
+}
+
+input:hover, submit:hover,
+input:focus, submit:focus {
+  border-color: #00b000;
+}
+
+input[type=submit] {
+  font-weight: bold;
+  color: #ffffff;
+  border: none;
+  background-color: #00b000;
+  padding: 3px 5px;
+}
+
+input[type=submit]:hover {
+  background-color: #007000;
+  cursor: pointer;
+}
+
+@media screen and (-ms-high-contrast:active), (-ms-high-contrast:none) {
+  .headname {
+    margin-top: 0.06em;
+  }
+}
+
+@media screen and (prefers-color-scheme: dark) {
+  .dummy {}
+
+  body {
+    background-color: #002000;
+    color: #ffffff;
+  }
+
+  a {
+    color: #ffffff;
+  }
+
+  a:hover {
+    color: #00ff00;
+  }
+
+  .header {
+    background-color: #004f00;
+  }
+
+  .header:after {
+    -webkit-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.2);
+    -moz-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.2);
+    box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.2);
+  }
+
+  .mainlink a {
+    color: #ffffff;
+  }
+
+  .mainlink:hover {
+    color: #ffffff;
+  }
+
+  .navigation ul li a {
+    color: #ffffff;
+  }
+
+  .navigation ul li a:hover {
+    color: #00ff00;
+  }
+
+  .footer {
+    -webkit-box-shadow: 0 -4px 8px 0 rgba(127, 127, 127, 0.2);
+    -moz-box-shadow: 0 -4px 8px 0 rgba(127, 127, 127, 0.2);
+    box-shadow: 0 -4px 8px 0 rgba(127, 127, 127, 0.2);
+  }
+
+  .styled-button {
+    background-color: #00b000;
+    -webkit-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.25);
+    -moz-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.25);
+    box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.25);
+  }
+
+  .styled-button:hover {
+    background-color: #00d000;
+    color: #ffffff;
+  }
+
+  table tbody {
+    background-color: #000f00;
+    color: #ffffff;
+  }
+
+  table tbody:after {
+    -webkit-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.175);
+    -moz-box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.175);
+    box-shadow: 0 4px 8px 0 rgba(127, 127, 127, 0.175);
+  }
+
+  .header-banner {
+    -webkit-box-shadow: 0 -3px 6px 0 inset rgba(127, 127, 127, 0.2);
+    -moz-box-shadow: 0 -3px 6px 0 inset rgba(127, 127, 127, 0.2);
+    box-shadow: 0 -3px 6px 0 inset rgba(127, 127, 127, 0.2);
+  }
+
+  input, select {
+    background-color: #000000;
+    color: #ffffff;
+  }
+
+  input[type=submit] {
+    color: #ffffff;
+    background-color: #00b000;
+  }
+
+  input[type=submit]:hover {
+    background-color: #00d000;
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .footer-layout {
+    display: block;
+  }
+  
+  .footer-column {
+    display: block;
+    text-align: center;
+    padding: 0;
+  }
+}
+
+@media screen and (max-width: 480px) {
+  .headlogo {
+    height: 1.8em;
+    padding-left: 0;
+    padding-right: 0.25em;
+    padding-bottom: 0;
+  }
+
+  .translated-rtl .headlogo {
+    padding-left: 0.25em;
+    padding-right: 0;
+  }
+
+  .headname {
+    font-size: 1.85em;
+  }
+
+  .navigation ul li a {
+    font-size: 1.15em;
+    padding: 0 0.225em;
+  }
+}
+
+
+@media screen and (max-width: 372px) {
+  .navigation ul li a {
+    font-size: 1.075em;
+    padding: 0 0.2em;
+  }
+}
+
+@media screen and (max-width: 350px) {
+  .navigation {
+    display: block;
+    text-align: left;
+    width: 100%;
+  }
+
+  .translated-rtl .navigation {
+    text-align: right
+  }
+
+  .mainlink {
+    display: block;
+    width: 100%;
+  }
+
+  .header-contents {
+    display: block;
+  }
+}
+
+@media print {
+  .dummy {}
+  .header, .footer, .header-banner {
+    display: none;
+  }
+  .real-content {
+    max-width: none !important;
+  }
+}
+
+@font-face {
+  font-family: "FreeSans";
+  font-display: swap;
+  src: url("/fonts/FreeSans.woff") format("woff"), url("/fonts/FreeSans.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: "FreeSans";
+  font-weight: bold;
+  font-display: swap;
+  src: url("/fonts/FreeSansBold.woff") format("woff"), url("/fonts/FreeSansBold.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: "FreeSans";
+  font-style: italic;
+  font-display: swap;
+  src: url("/fonts/FreeSansOblique.woff") format("woff"), url("/fonts/FreeSansOblique.ttf") format("truetype");
+}
+
+@font-face {
+  font-family: "FreeSans";
+  font-style: italic;
+  font-weight: bold;
+  font-display: swap;
+  src: url("/fonts/FreeSansBoldOblique.woff") format("woff"), url("/fonts/FreeSansBoldOblique.ttf") format("truetype");
+}
+
diff --git a/frontend/img/navbar-logo.png b/frontend/img/navbar-logo.png
new file mode 100644 (file)
index 0000000..5df709b
Binary files /dev/null and b/frontend/img/navbar-logo.png differ
diff --git a/frontend/js/html5shiv.js b/frontend/js/html5shiv.js
new file mode 100644 (file)
index 0000000..45ea723
--- /dev/null
@@ -0,0 +1,326 @@
+/**
+* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
+*/
+;(function(window, document) {
+/*jshint evil:true */
+  /** version */
+  var version = '3.7.3';
+
+  /** Preset options */
+  var options = window.html5 || {};
+
+  /** Used to skip problem elements */
+  var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
+
+  /** Not all elements can be cloned in IE **/
+  var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
+
+  /** Detect whether the browser supports default html5 styles */
+  var supportsHtml5Styles;
+
+  /** Name of the expando, to work with multiple documents or to re-shiv one document */
+  var expando = '_html5shiv';
+
+  /** The id for the the documents expando */
+  var expanID = 0;
+
+  /** Cached data for each document */
+  var expandoData = {};
+
+  /** Detect whether the browser supports unknown elements */
+  var supportsUnknownElements;
+
+  (function() {
+    try {
+        var a = document.createElement('a');
+        a.innerHTML = '<xyz></xyz>';
+        //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
+        supportsHtml5Styles = ('hidden' in a);
+
+        supportsUnknownElements = a.childNodes.length == 1 || (function() {
+          // assign a false positive if unable to shiv
+          (document.createElement)('a');
+          var frag = document.createDocumentFragment();
+          return (
+            typeof frag.cloneNode == 'undefined' ||
+            typeof frag.createDocumentFragment == 'undefined' ||
+            typeof frag.createElement == 'undefined'
+          );
+        }());
+    } catch(e) {
+      // assign a false positive if detection fails => unable to shiv
+      supportsHtml5Styles = true;
+      supportsUnknownElements = true;
+    }
+
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a style sheet with the given CSS text and adds it to the document.
+   * @private
+   * @param {Document} ownerDocument The document.
+   * @param {String} cssText The CSS text.
+   * @returns {StyleSheet} The style element.
+   */
+  function addStyleSheet(ownerDocument, cssText) {
+    var p = ownerDocument.createElement('p'),
+        parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
+
+    p.innerHTML = 'x<style>' + cssText + '</style>';
+    return parent.insertBefore(p.lastChild, parent.firstChild);
+  }
+
+  /**
+   * Returns the value of `html5.elements` as an array.
+   * @private
+   * @returns {Array} An array of shived element node names.
+   */
+  function getElements() {
+    var elements = html5.elements;
+    return typeof elements == 'string' ? elements.split(' ') : elements;
+  }
+
+  /**
+   * Extends the built-in list of html5 elements
+   * @memberOf html5
+   * @param {String|Array} newElements whitespace separated list or array of new element names to shiv
+   * @param {Document} ownerDocument The context document.
+   */
+  function addElements(newElements, ownerDocument) {
+    var elements = html5.elements;
+    if(typeof elements != 'string'){
+      elements = elements.join(' ');
+    }
+    if(typeof newElements != 'string'){
+      newElements = newElements.join(' ');
+    }
+    html5.elements = elements +' '+ newElements;
+    shivDocument(ownerDocument);
+  }
+
+   /**
+   * Returns the data associated to the given document
+   * @private
+   * @param {Document} ownerDocument The document.
+   * @returns {Object} An object of data.
+   */
+  function getExpandoData(ownerDocument) {
+    var data = expandoData[ownerDocument[expando]];
+    if (!data) {
+        data = {};
+        expanID++;
+        ownerDocument[expando] = expanID;
+        expandoData[expanID] = data;
+    }
+    return data;
+  }
+
+  /**
+   * returns a shived element for the given nodeName and document
+   * @memberOf html5
+   * @param {String} nodeName name of the element
+   * @param {Document|DocumentFragment} ownerDocument The context document.
+   * @returns {Object} The shived element.
+   */
+  function createElement(nodeName, ownerDocument, data){
+    if (!ownerDocument) {
+        ownerDocument = document;
+    }
+    if(supportsUnknownElements){
+        return ownerDocument.createElement(nodeName);
+    }
+    if (!data) {
+        data = getExpandoData(ownerDocument);
+    }
+    var node;
+
+    if (data.cache[nodeName]) {
+        node = data.cache[nodeName].cloneNode();
+    } else if (saveClones.test(nodeName)) {
+        node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
+    } else {
+        node = data.createElem(nodeName);
+    }
+
+    // Avoid adding some elements to fragments in IE < 9 because
+    // * Attributes like `name` or `type` cannot be set/changed once an element
+    //   is inserted into a document/fragment
+    // * Link elements with `src` attributes that are inaccessible, as with
+    //   a 403 response, will cause the tab/window to crash
+    // * Script elements appended to fragments will execute when their `src`
+    //   or `text` property is set
+    return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
+  }
+
+  /**
+   * returns a shived DocumentFragment for the given document
+   * @memberOf html5
+   * @param {Document} ownerDocument The context document.
+   * @returns {Object} The shived DocumentFragment.
+   */
+  function createDocumentFragment(ownerDocument, data){
+    if (!ownerDocument) {
+        ownerDocument = document;
+    }
+    if(supportsUnknownElements){
+        return ownerDocument.createDocumentFragment();
+    }
+    data = data || getExpandoData(ownerDocument);
+    var clone = data.frag.cloneNode(),
+        i = 0,
+        elems = getElements(),
+        l = elems.length;
+    for(;i<l;i++){
+        clone.createElement(elems[i]);
+    }
+    return clone;
+  }
+
+  /**
+   * Shivs the `createElement` and `createDocumentFragment` methods of the document.
+   * @private
+   * @param {Document|DocumentFragment} ownerDocument The document.
+   * @param {Object} data of the document.
+   */
+  function shivMethods(ownerDocument, data) {
+    if (!data.cache) {
+        data.cache = {};
+        data.createElem = ownerDocument.createElement;
+        data.createFrag = ownerDocument.createDocumentFragment;
+        data.frag = data.createFrag();
+    }
+
+
+    ownerDocument.createElement = function(nodeName) {
+      //abort shiv
+      if (!html5.shivMethods) {
+          return data.createElem(nodeName);
+      }
+      return createElement(nodeName, ownerDocument, data);
+    };
+
+    ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
+      'var n=f.cloneNode(),c=n.createElement;' +
+      'h.shivMethods&&(' +
+        // unroll the `createElement` calls
+        getElements().join().replace(/[\w\-:]+/g, function(nodeName) {
+          data.createElem(nodeName);
+          data.frag.createElement(nodeName);
+          return 'c("' + nodeName + '")';
+        }) +
+      ');return n}'
+    )(html5, data.frag);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Shivs the given document.
+   * @memberOf html5
+   * @param {Document} ownerDocument The document to shiv.
+   * @returns {Document} The shived document.
+   */
+  function shivDocument(ownerDocument) {
+    if (!ownerDocument) {
+        ownerDocument = document;
+    }
+    var data = getExpandoData(ownerDocument);
+
+    if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
+      data.hasCSS = !!addStyleSheet(ownerDocument,
+        // corrects block display not defined in IE6/7/8/9
+        'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' +
+        // adds styling not present in IE6/7/8/9
+        'mark{background:#FF0;color:#000}' +
+        // hides non-rendered elements
+        'template{display:none}'
+      );
+    }
+    if (!supportsUnknownElements) {
+      shivMethods(ownerDocument, data);
+    }
+    return ownerDocument;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The `html5` object is exposed so that more elements can be shived and
+   * existing shiving can be detected on iframes.
+   * @type Object
+   * @example
+   *
+   * // options can be changed before the script is included
+   * html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false };
+   */
+  var html5 = {
+
+    /**
+     * An array or space separated string of node names of the elements to shiv.
+     * @memberOf html5
+     * @type Array|String
+     */
+    'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video',
+
+    /**
+     * current version of html5shiv
+     */
+    'version': version,
+
+    /**
+     * A flag to indicate that the HTML5 style sheet should be inserted.
+     * @memberOf html5
+     * @type Boolean
+     */
+    'shivCSS': (options.shivCSS !== false),
+
+    /**
+     * Is equal to true if a browser supports creating unknown/HTML5 elements
+     * @memberOf html5
+     * @type boolean
+     */
+    'supportsUnknownElements': supportsUnknownElements,
+
+    /**
+     * A flag to indicate that the document's `createElement` and `createDocumentFragment`
+     * methods should be overwritten.
+     * @memberOf html5
+     * @type Boolean
+     */
+    'shivMethods': (options.shivMethods !== false),
+
+    /**
+     * A string to describe the type of `html5` object ("default" or "default print").
+     * @memberOf html5
+     * @type String
+     */
+    'type': 'default',
+
+    // shivs the document according to the specified `html5` object options
+    'shivDocument': shivDocument,
+
+    //creates a shived element
+    createElement: createElement,
+
+    //creates a shived documentFragment
+    createDocumentFragment: createDocumentFragment,
+
+    //extends list of elements
+    addElements: addElements
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose html5
+  window.html5 = html5;
+
+  // shiv the document
+  shivDocument(document);
+
+  if(typeof module == 'object' && module.exports){
+    module.exports = html5;
+  }
+
+}(typeof window !== "undefined" ? window : this, document));
diff --git a/frontend/robots.txt b/frontend/robots.txt
new file mode 100644 (file)
index 0000000..1f53798
--- /dev/null
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /