Initial Push (v0.0.3)

This commit is contained in:
Fred Boniface 2023-02-09 20:29:07 +00:00
parent 3307d02a7c
commit a3fdda62d4
76 changed files with 2692 additions and 0 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
.dockerignore
Dockerfile

34
404.html Normal file
View File

@ -0,0 +1,34 @@
<html>
<head>
<meta charset="UTF-8"/>
<meta name="description" content="OwlBoard - Live train departures for traincrew."/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="application-name" content="OwlBoard">
<meta name="author" content="Frederick Boniface">
<meta name="theme-color" content="#00b7b7">
<link rel="apple-touch-icon" href="/images/app-icons/any/apple-192.png">
<link rel="stylesheet" type="text/css" href="./styles/main.css"/>
<link rel="icon" type="image/svg+xml" href="./images/icon.svg"/>
<link rel="manifest" type="application/json" href="./manifest.json"/>
<!-- NO SCRIPTS LOADED - NOT REQUIRED AT PRESENT -->
<title>OwlBoard - Error</title>
</head>
<body>
<div id="top_button" class="hide_micro">
<picture aria-label="Back" class="sidebar_control" onclick="history.back()">
<source srcset="/images/nav/back.svg" type="image/svg+xml">
<img src="back-40.png" alt="Back">
</picture>
</div>
<picture>
<source srcset="/images/logo/wide_logo.svg" type="image/svg+xml">
<source media="(max-height: 739px)" srcset="/images/logo/logo-full-200.png" type="image/png">
<source srcset="/images/logo/logo-full-250.png" type="image/png">
<img class="titleimg" src="/images/logo/logo-full-250.png" alt="OwlBoard Logo">
</picture>
<h2>Oh no!</h2>
<p>That page cannot be found</p>
<p>Try going to the <a href="/">homepage</a></p>
<p>Error number: 404</p>
</body>
</html>

12
Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM fedora:latest as compressor
RUN dnf install brotli nodejs npm jq -y
RUN npm i uglifyjs-folder uglifycss html-minifier-terser -g
COPY . /data/in
RUN bash /data/in/conf/deploy.sh
FROM fholzer/nginx-brotli:latest
RUN rm /etc/nginx/nginx.conf
RUN apk update
RUN apk add --upgrade libxml2 libxslt
COPY ./conf/nginx.conf /etc/nginx/nginx.conf
COPY --from=compressor /data/out/ /site-static/

129
board.html Normal file
View File

@ -0,0 +1,129 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="description" content="OwlBoard - Live train departures for traincrew."/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="application-name" content="OwlBoard">
<meta name="author" content="Frederick Boniface">
<meta name="theme-color" content="#00b7b7">
<title>OwlBoard - Loading</title>
<link rel="apple-touch-icon" href="/images/app-icons/any/apple-192.png">
<link rel="stylesheet" type="text/css" href="./styles/main.css"/>
<link rel="stylesheet" type="text/css" href="./styles/boards.css"/>
<link rel="icon" type="image/svg+xml" href="./images/icon.svg"/>
<link rel="manifest" type="application/json" href="./manifest.json"/>
<script src="./js/lib.main.js" defer></script>
<script src="./js/lib.board.js" defer></script>
<script src="./js/simple-board.js" defer></script>
</head>
<body>
<div id="loading">
<div class="spinner">
</div>
<p id="loading_desc">\nLoading</p>
</div>
<div id="content">
<div id="header">
<div id="station_name">
<h1 id="stn_name" class="header-large"></h1>
</div>
<div id="header-right">
<p class="header-small">Data from:</p>
<p id="fetch_time" class="header-small">Loading...</p>
</div>
</div>
<div id="alerts" onclick="">
<div id="alerts_bar" onclick="inflateAlerts()">
<picture>
<source srcset="./images/nav/alert_icon.svg" type="image/svg+xml">
<img id="alert_icon" src="./images/nav/alert_icon.svg" alt="">
</picture>
<p id="alert_bar_note"></p>
<button id="alert_expand_arrow">&#8897;</button>
<div id="alerts_msg" onclick="NULL">
</div>
</div>
</div>
<div id="output">
<table>
<caption>Train Services</caption>
<tr>
<th class="name">Origin</th>
<th class="name">Dest.</th>
<th class="plat">Plat.</th>
<th class="time">Sch Arr.</th>
<th class="time">Exp Arr.</th>
<th class="time">Sch Dep.</th>
<th class="time">Exp Dep.</th>
</tr>
</table>
</div>
<div id="no_services" class="main-notice hidden-whille-loading">
<p>There are no scheduled train services from this station</p>
</div>
<div id="ferry" class="hide-when-loading secondary-table">
<table>
<caption>Ferry Services</caption>
<tr>
<th class="name">Origin</th>
<th class="name">Dest.</th>
<th class="plat"></th>
<th class="time">Sch Arr.</th>
<th class="time">Exp Arr.</th>
<th class="time">Sch Dep.</th>
<th class="time">Exp Dep.</th>
</tr>
</table>
</div>
<div id="bus" class="hide-when-loading secondary-table">
<table>
<caption>Bus Services</caption>
<tr>
<th class="name">Origin</th>
<th class="name">Dest.</th>
<th class="plat"></th>
<th class="time">Sch Arr.</th>
<th class="time">Exp Arr.</th>
<th class="time">Sch Dep.</th>
<th class="time">Exp Dep.</th>
</tr>
</table>
</div>
<div id="error_notice" class="main-notice hide-when-loading">
<h1 class="error">Oops</h1>
<p class="error">There was an error with your request</p>
<p id="err_not_found" class="notices-hidden">The station you are searching for cannot be found</p>
<p id="err_no_data" class="notices-hidden">The station has no data. It may not be in operation yet/anymore.</p>
<p id="err_conn" class="notices-hidden">Connection Error, check your data connection. Retrying.</p>
</div>
<div id="footer">
<a href="https://nationalrail.co.uk" target="_blank" rel="nofollow external noreferrer noopener">
<picture id="nre_logo">
<source srcset="./images/nre/nre-powered_400w.jxl" type="image/jxl">
<source srcset="./images/nre/nre-powered_400w.webp" type="image/webp">
<img src="./images/nre/nre-powered_400w.png" alt="Powered by National Rail Enquiries">
</picture>
</a>
<a href="/">
<picture id="owlboard_logo">
<source srcset="./images/logo/mono-logo.svg" type="image/svg+xml">
<img src="./images/logo/mono-logo-33.png" alt="OwlBoard Logo">
</picture>
<picture id="home_icon">
<source srcset="./images/nav/home_icon.svg" type="image/svg+xml">
<img src="./images/nav/home_icon-40.png" alt="Home">
</picture>
</a>
</div>
</div>
</body>
</html>

38
conf/deploy.sh Normal file
View File

@ -0,0 +1,38 @@
#!/bin/bash
ROOTIN="/data/in"
ROOTOUT="/data/out"
echo "Running UglifyJS on /data/in folder"
uglifyjs-folder "$ROOTIN" -x ".js" -eo "$ROOTOUT"
echo "Running UglifyCSS"
CSSIN="/data/in/styles/"
CSSOUT="/data/out/styles"
cd $CSSIN
echo "Changed directory"
pwd
for f in *
do
if [ -f "$f" ]; then
uglifycss "$f" --output "$f";
fi
done
echo "Moving 'styles' to 'out'"
cp -r /data/in/styles /data/out/styles
echo "Running html-minifier-terser on /folder"
HTMLIN="/data/in/"
HTMLOUT="/data/out"
html-minifier-terser --collapse-whitespace --remove-comments --file-ext html --input-dir /data/in/ --output-dir /data/out/
echo "Moving JSON Manifest file from root to output"
cat /data/in/manifest.json | jq -c > /data/out/manifest.json
echo "Moving images folder from in/ to out/"
cp -r /data/in/images /data/out/images
echo "Running GZIP & Brotli on all HTML, JS, CSS, JSON & SVG files"
find /data/out -type f -name \*.html -or -name \*.js -or -name \*.css -or -name \*.json -or -name \*.svg -or -name \*.ttf | while read file; do gzip -k -9 $file; brotli -k -q 11 $file; done

60
conf/nginx.conf Normal file
View File

@ -0,0 +1,60 @@
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
proxy_cache_path /var/cache/nginx keys_zone=owl_cache:20m inactive=24h;
server {
listen 80;
server_name localhost;
proxy_cache owl_cache;
add_header Content-Security-Policy "default-src 'self'";
location / {
root /site-static/;
index index.html;
gzip_static on;
brotli_static on;
error_page 404 /404.html;
expires 3600;
add_header Cache-Control "public, no-transform";
}
location /api/ {
proxy_pass http://localhost:8460;
proxy_cache_key $scheme://$host$uri$is_args$query_string;
proxy_ignore_headers Cache-Control;
proxy_cache_valid 200 2m; # Evaluate whether 2m or 1m is more appropriate
expires 2m;
add_header Cache-Control "private, no-transform";
}
location /api/v1/list/ {
proxy_pass http://localhost:8460;
proxy_cache_key $scheme://$host$uri$is_args$query_string;
proxy_ignore_headers Cache-Control;
proxy_cache_valid 200 10080m;
expires 3d;
add_header Cache-Control "public, no-transform";
}
}
}

36
conn-err.html Normal file
View File

@ -0,0 +1,36 @@
<html>
<head>
<meta charset="UTF-8"/>
<meta name="description" content="OwlBoard - Live train departures for traincrew."/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="application-name" content="OwlBoard">
<meta name="author" content="Frederick Boniface">
<meta name="theme-color" content="#00b7b7">
<link rel="apple-touch-icon" href="/images/app-icons/any/apple-192.png">
<link rel="stylesheet" type="text/css" href="./styles/main.css"/>
<link rel="icon" type="image/svg+xml" href="./images/icon.svg"/>
<link rel="manifest" type="application/json" href="./manifest.json"/>
<!-- NO SCRIPTS LOADED - NOT REQUIRED AT PRESENT -->
<title>OwlBoard - Error</title>
</head>
<body>
<div id="top_button" class="hide_micro">
<picture aria-label="Close Menu" class="sidebar_control" onclick="history.back()">
<source srcset="/images/nav/back.svg" type="image/svg+xml">
<img src="back-40.png" alt="Close menu">
</picture>
</div>
<picture>
<source srcset="/images/logo/wide_logo.svg" type="image/svg+xml">
<source media="(max-height: 739px)" srcset="/images/logo/logo-full-200.png" type="image/png">
<source srcset="/images/logo/logo-full-250.png" type="image/png">
<img class="titleimg" src="/images/logo/logo-full-250.png" alt="OwlBoard Logo">
</picture>
<h2>Oh no!</h2>
<p>OwlBoard has encountered a Connection Error</p>
<p>Check your data connection and try again</p>
<p>Go to the <a href="/">homepage</a></p>
<br>
<p>Error Code: CERR</p>
</body>
</html>

64
find-code.html Normal file
View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="description" content="OwlBoard - Live train departures for traincrew."/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="application-name" content="OwlBoard">
<meta name="author" content="Frederick Boniface">
<meta name="theme-color" content="#00b7b7">
<link rel="apple-touch-icon" href="/images/app-icons/any/apple-192.png">
<link rel="stylesheet" type="text/css" href="./styles/main.css"/>
<link rel="stylesheet" type="text/css" href="./styles/find-code.css"/>
<link rel="icon" type="image/svg+xml" href="./images/icon.svg"/>
<link rel="manifest" type="application/json" href="./manifest.json"/>
<title>OwlBoard - Code Lookup</title>
<script src="./js/lib.main.js" defer></script>
<script src="./js/find-code.js" defer></script>
</head>
<body>
<div id="top_button" class="hide_micro">
<a href="/">
<picture aria-label="Home" class="sidebar_control">
<source srcset="/images/nav/back.svg" type="image/svg+xml">
<img src="back-40.png" alt="Home">
</picture>
</a>
</div>
<picture>
<source srcset="/images/logo/wide_logo.svg" type="image/svg+xml">
<source media="(max-height: 739px)" srcset="/images/logo/logo-full-200.png" type="image/png">
<source srcset="/images/logo/logo-full-250.png" type="image/png">
<img class="titleimg" src="/images/logo/logo-full-250.png" alt="OwlBoard Logo">
</picture>
<h2>Code Lookup</h2>
<p>Enter one known code in the relevant box below and hit submit.
Where they exist, the other code types will be filled in.</p>
<p>You cannot yet lookup by location name as the values are not unique.</p>
<p>Location name search will be added in the future.</p>
<div id="loading">
<div class="spinner">
</div>
<p id="loading_desc">Searching</p>
</div>
<label for="name">Location name:</label><br>
<input type="text" class="small-lookup-box" id="name" name="name" readonly=""><br>
<label for="3alpha">CRS/3ALPHA:</label><br>
<input type="text" class="small-lookup-box" id="3alpha" name="3alpha" maxlength="3"><br>
<label for="nlc">NLC:</label><br>
<input type="number" class="small-lookup-box" id="nlc" name="nlc" min="100000" max="999999"><br>
<label for="tiploc">TIPLOC:</label><br>
<input type="text" class="small-lookup-box" id="tiploc" name="tiploc" maxlength="7"><br>
<label for="stanox">STANOX:</label><br>
<input type="number" class="small-lookup-box" id="stanox" name="stanox"><br>
<label for="stanme" hidden>STANME:</label><br>
<input type="test" class="small-lookup-box" id="stanme" name="stanme" readonly="" hidden><br>
<input type="submit" value="Find" class="lookup-button" onclick="fetchEntry()">
<input type="submit" value="Clear" class="lookup-button" onclick="clearForm()">
</body>
</html>

93
help.html Normal file
View File

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="description" content="OwlBoard - Live train departures for traincrew."/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="application-name" content="OwlBoard">
<meta name="author" content="Frederick Boniface">
<meta name="theme-color" content="#00b7b7">
<link rel="apple-touch-icon" href="/images/app-icons/any/apple-192.png">
<link rel="stylesheet" type="text/css" href="./styles/main.css"/>
<link rel="stylesheet" type="text/css" href="./styles/help.css"/>
<link rel="icon" type="image/svg+xml" href="./images/icon.svg"/>
<link rel="manifest" type="application/json" href="./manifest.json"/>
<!-- NO SCRIPTS LOADED - NOT REQUIRED AT PRESENT -->
<title>OwlBoard</title>
</head>
<body>
<div id="top_button" class="hide_micro">
<a href="/">
<picture aria-label="Home" class="sidebar_control">
<source srcset="/images/nav/back.svg" type="image/svg+xml">
<img src="back-40.png" alt="Home">
</picture>
</a>
</div>
<picture>
<source srcset="/images/logo/wide_logo.svg" type="image/svg+xml">
<source media="(max-height: 739px)" srcset="/images/logo/logo-full-200.png" type="image/png">
<source srcset="/images/logo/logo-full-250.png" type="image/png">
<img class="titleimg" src="/images/logo/logo-full-250.png" alt="OwlBoard Logo">
</picture>
<h2>Help</h2>
<p>OwlBoard gives you quick and easy access to departure boards for
all National Rail stations in the UK.</p>
<p>Just type a CRS, TIPLOC or STANOX into the textbox on the homepage and tap
enter on the screen or your keypad. You can also select a differnt board type,
more details on your choices below.</p>
<p>For example, Portway Park &
Ride's CRS is 'PRI', and its TIPLOC is 'PTWYPR'; Portsmouth Harbour's
CRS is 'PMH', and its TIPLOC is 'PHBR'.</p>
<p>A CRS is always three letters,
a TIPLOC can be between 4-7 letters.</p>
<br>
<h3>Don't know the CRS or TIPLOC?</h3>
<p>Sorry, you can't search by name but you can use our <a href="find-code.html">
Code Lookup</a> page to help.</p>
<h3>Board Types</h3>
<h4>Basic Board - Default</h4>
<p>The basic board shows the next 10 train arrival and departures, as well as
bus and ferry departures where available.</p>
<p>You can tap on a trains origin or destination to see service details.</p>
<br>
<h3>Glossary</h3>
<p>Some of the terms may be new to you or different from those commonly used.</p>
<table id="table">
<tr>
<th>Term</th>
<th>Definition</th>
</tr>
<tr>
<td>CAN</td>
<td>Cancelled</td>
</tr>
<tr>
<td>CRS</td>
<td>Computer Reservation System Code - correctly termed as '3ALPHA'</td>
</tr>
<tr>
<td>NLC</td>
<td>National Location Code - Used for finance & accounting</td>
</tr>
<tr>
<td>RT</td>
<td>Right rime (On time)</td>
</tr>
<tr>
<td>STANOX</td>
<td>Station Number</td>
</tr>
<tr>
<td>TIPLOC</td>
<td>Timing Point Location (Name)</td>
</tr>
</table>
<br>
<h3>Spotted an issue with the site?</h3>
<p>Let me know by <a href="./report.html">reporting an issue</a>.</p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 667.26 706.8" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><metadata><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><g transform="translate(-111.19 -90.003)"><rect x="213.88" y="260.42" width="81.997" height="48.755" fill="#fcfc09"/><rect x="167.35" y="187.29" width="181.72" height="68.7" fill="#f0ffff"/></g><g transform="matrix(1.3333 0 0 -1.3333 -111.19 790)" fill="#00b7b7"><g transform="translate(7.5,-7.5)"><path d="m112.25 369.32c12.988-10.101 30.989 0.076 39.875 15.722 9.969-34.512 37.501-53.367 57.648-37.596-6.503-20.085-34.373-34.554-61.522 13.215-15.947-14.598-28.559-14.416-36.001 8.659m14.302-81.369c15.405-6.791 28.15 7.974 30.66 25.885 21.351-31.973 53.097-43.81 65.802-22.905 1.237-21.743-18.954-43.315-60.644-2.464-9.305-18.663-20.837-21.694-35.818-0.516m33.549 175.93 24.381-9.342 24.382 9.342-24.382-24.381zm-14.899 47.955c1.574 0 3.075-0.31 4.448-0.869-1.973-1.1-3.309-3.206-3.309-5.626 0-3.554 2.883-6.436 6.437-6.436 1.608 0 3.079 0.59 4.208 1.566 5e-3 -0.143 8e-3 -0.284 8e-3 -0.428 0-6.512-5.28-11.791-11.792-11.791s-11.791 5.279-11.791 11.791c0 6.513 5.279 11.793 11.791 11.793m77.642 0c1.573 0 3.075-0.31 4.447-0.869-1.973-1.1-3.308-3.206-3.308-5.626 0-3.554 2.882-6.436 6.437-6.436 1.608 0 3.079 0.59 4.207 1.566 6e-3 -0.143 9e-3 -0.284 9e-3 -0.428 0-6.512-5.28-11.791-11.792-11.791-6.513 0-11.792 5.279-11.792 11.791 0 6.513 5.279 11.793 11.792 11.793m0 8.887c11.421 0 20.677-9.259 20.677-20.68 0-11.42-9.256-20.677-20.677-20.677-11.42 0-20.678 9.257-20.678 20.677 0 11.421 9.258 20.68 20.678 20.68m-77.642 0c11.42 0 20.679-9.259 20.679-20.68 0-11.42-9.259-20.677-20.679-20.677s-20.678 9.257-20.678 20.677c0 11.421 9.258 20.68 20.678 20.68m222.62-271.32c-5.257-16.303-14.169-16.431-25.436-6.118-19.182-33.751-38.872-23.527-43.468-9.336 14.236-11.143 33.688 2.178 40.73 26.562 6.28-11.055 18.998-18.245 28.174-11.108m-7.657 101.75c-5.26-16.304-14.169-16.433-25.436-6.118-19.182-33.751-38.873-23.529-43.469-9.338 14.236-11.142 33.688 2.179 40.731 26.564 6.279-11.055 18.997-18.247 28.174-11.108m3.828-50.877c-5.259-16.302-14.168-16.429-25.435-6.117-19.182-33.752-38.873-23.528-43.469-9.338 14.236-11.14 33.687 2.181 40.731 26.564 6.279-11.055 18.996-18.243 28.173-11.109m-185-126.56 8.456 14.687-2.481 14.064c8.24-6.441 16.897-12.257 25.895-17.419l-13.787-20.682c-5.163 5.163-11.523 8.215-18.083 9.35m214.44 47.276-16.013 24.214v93.753c0 40.019-32.441 72.459-72.458 72.459-37.742 0-68.739-28.855-72.144-65.707-0.563 6.626-0.974 13.336-1.228 20.135 0 36.412 26.858 66.546 61.843 71.684 15.118 42.436 3.44 91.058-31.815 121.88-0.332 0.288-0.668 0.569-1.002 0.854-14.934-8.987-28.921-18.756-41.766-29.467 14.159 17.506 30.102 32.253 47.212 45.198-47.368 32.008-116.69 32.008-164.06 0 17.11-12.946 33.054-27.694 47.212-45.201-11.522 9.609-23.965 18.462-37.188 26.67-40.329-37.119-47.937-98.2-17.545-144.16 4.83-7.304 7.155-15.981 6.624-24.722-3.808-62.683 19.436-123.99 63.84-168.4 3.505-3.505 7.116-6.877 10.824-10.115l-15.66-23.489c-18.988 18.987-54.154 9.494-56.053-19.369 4.94 7.294 12.965 10.334 21.93 10.306 4.624 3.683 10.595 5.668 18.277 5.498-14.197-2.669-23.71-11.684-25.256-26.007 9.826 11.065 21.246 13.643 34.785 11.262-7.045-4.94-12.081-12.841-12.767-23.274 4.941 7.293 12.965 10.335 21.932 10.303 1.457 1.162 3.047 2.155 4.783 2.963 0.557-0.597 1.095-1.202 1.614-1.819-7.504-4.493-12.316-11.823-13.378-21.652 5.924 6.673 12.428 10.259 19.627 11.533 1.471-3.662 2.152-7.45 1.823-11.24 4.057 2.964 6.698 7.077 8.006 11.651 2.475-0.187 5.026-0.574 7.654-1.132 12.8-8.149 22.377-20.41 21.31-32.736 11.849 8.659 11.621 27.116 1.367 38.28l8.659 15.04-2.26 12.808c15.693-7.825 32.28-13.746 49.402-17.626-7.313 17.831-12.59 36.793-15.633 56.995 19.737-52.989 51.201-99.462 92.224-140.63 3.548-6.147 10.784-9.143 17.64-7.305 6.856 1.837 11.623 8.048 11.623 15.147v65.793c33.793-9.913 62.771-33.463 79.074-66.13l119.85 29.509c-14.567 69.186-99.576 110.14-175.59 96.362z" fill="#00b7b7" fill-rule="evenodd"/></g><path transform="scale(1,-1)" d="m426.75-584.57h11.635v242.67h-11.635z" fill-rule="evenodd" stroke-width=".80733"/><path transform="scale(1,-1)" d="m533.35-584.09h11.635v242.67h-11.635z" fill-rule="evenodd" stroke-width=".80733"/><path transform="scale(1,-1)" d="m404.98-554.66h159.56v34.904h-159.56z" fill-rule="evenodd" stroke-width=".75"/><path transform="scale(1,-1)" d="m404.98-481.16h159.56v34.904h-159.56z" fill-rule="evenodd" stroke-width=".75"/><path transform="scale(1,-1)" d="m404.98-404.66h159.56v34.904h-159.56z" fill-rule="evenodd" stroke-width=".75"/></g></svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

2
images/icon.svg Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 667.26 706.8" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><metadata><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><g transform="translate(-101.19 -80.003)"><rect transform="matrix(1.3333 0 0 1.3333 0 880)" x="152.91" y="-472.18" width="61.498" height="36.566" fill="#fcfc09" stroke-width=".75"/><rect x="157.35" y="177.29" width="181.72" height="68.7" fill="#f0ffff"/></g><g transform="matrix(1.3333 0 0 -1.3333 -101.19 800)"><g fill="#00b7b7"><path d="m112.25 369.32c12.988-10.101 30.989 0.076 39.875 15.722 9.969-34.512 37.501-53.367 57.648-37.596-6.503-20.085-34.373-34.554-61.522 13.215-15.947-14.598-28.559-14.416-36.001 8.659m14.302-81.369c15.405-6.791 28.15 7.974 30.66 25.885 21.351-31.973 53.097-43.81 65.802-22.905 1.237-21.743-18.954-43.315-60.644-2.464-9.305-18.663-20.837-21.694-35.818-0.516m33.549 175.93 24.381-9.342 24.382 9.342-24.382-24.381zm-14.899 47.955c1.574 0 3.075-0.31 4.448-0.869-1.973-1.1-3.309-3.206-3.309-5.626 0-3.554 2.883-6.436 6.437-6.436 1.608 0 3.079 0.59 4.208 1.566 5e-3 -0.143 8e-3 -0.284 8e-3 -0.428 0-6.512-5.28-11.791-11.792-11.791s-11.791 5.279-11.791 11.791c0 6.513 5.279 11.793 11.791 11.793m77.642 0c1.573 0 3.075-0.31 4.447-0.869-1.973-1.1-3.308-3.206-3.308-5.626 0-3.554 2.882-6.436 6.437-6.436 1.608 0 3.079 0.59 4.207 1.566 6e-3 -0.143 9e-3 -0.284 9e-3 -0.428 0-6.512-5.28-11.791-11.792-11.791-6.513 0-11.792 5.279-11.792 11.791 0 6.513 5.279 11.793 11.792 11.793m0 8.887c11.421 0 20.677-9.259 20.677-20.68 0-11.42-9.256-20.677-20.677-20.677-11.42 0-20.678 9.257-20.678 20.677 0 11.421 9.258 20.68 20.678 20.68m-77.642 0c11.42 0 20.679-9.259 20.679-20.68 0-11.42-9.259-20.677-20.679-20.677s-20.678 9.257-20.678 20.677c0 11.421 9.258 20.68 20.678 20.68m222.62-271.32c-5.257-16.303-14.169-16.431-25.436-6.118-19.182-33.751-38.872-23.527-43.468-9.336 14.236-11.143 33.688 2.178 40.73 26.562 6.28-11.055 18.998-18.245 28.174-11.108m-7.657 101.75c-5.26-16.304-14.169-16.433-25.436-6.118-19.182-33.751-38.873-23.529-43.469-9.338 14.236-11.142 33.688 2.179 40.731 26.564 6.279-11.055 18.997-18.247 28.174-11.108m3.828-50.877c-5.259-16.302-14.168-16.429-25.435-6.117-19.182-33.752-38.873-23.528-43.469-9.338 14.236-11.14 33.687 2.181 40.731 26.564 6.279-11.055 18.996-18.243 28.173-11.109m-185-126.56 8.456 14.687-2.481 14.064c8.24-6.441 16.897-12.257 25.895-17.419l-13.787-20.682c-5.163 5.163-11.523 8.215-18.083 9.35m214.44 47.276-16.013 24.214v93.753c0 40.019-32.441 72.459-72.458 72.459-37.742 0-68.739-28.855-72.144-65.707-0.563 6.626-0.974 13.336-1.228 20.135 0 36.412 26.858 66.546 61.843 71.684 15.118 42.436 3.44 91.058-31.815 121.88-0.332 0.288-0.668 0.569-1.002 0.854-14.934-8.987-28.921-18.756-41.766-29.467 14.159 17.506 30.102 32.253 47.212 45.198-47.368 32.008-116.69 32.008-164.06 0 17.11-12.946 33.054-27.694 47.212-45.201-11.522 9.609-23.965 18.462-37.188 26.67-40.329-37.119-47.937-98.2-17.545-144.16 4.83-7.304 7.155-15.981 6.624-24.722-3.808-62.683 19.436-123.99 63.84-168.4 3.505-3.505 7.116-6.877 10.824-10.115l-15.66-23.489c-18.988 18.987-54.154 9.494-56.053-19.369 4.94 7.294 12.965 10.334 21.93 10.306 4.624 3.683 10.595 5.668 18.277 5.498-14.197-2.669-23.71-11.684-25.256-26.007 9.826 11.065 21.246 13.643 34.785 11.262-7.045-4.94-12.081-12.841-12.767-23.274 4.941 7.293 12.965 10.335 21.932 10.303 1.457 1.162 3.047 2.155 4.783 2.963 0.557-0.597 1.095-1.202 1.614-1.819-7.504-4.493-12.316-11.823-13.378-21.652 5.924 6.673 12.428 10.259 19.627 11.533 1.471-3.662 2.152-7.45 1.823-11.24 4.057 2.964 6.698 7.077 8.006 11.651 2.475-0.187 5.026-0.574 7.654-1.132 12.8-8.149 22.377-20.41 21.31-32.736 11.849 8.659 11.621 27.116 1.367 38.28l8.659 15.04-2.26 12.808c15.693-7.825 32.28-13.746 49.402-17.626-7.313 17.831-12.59 36.793-15.633 56.995 19.737-52.989 51.201-99.462 92.224-140.63 3.548-6.147 10.784-9.143 17.64-7.305 6.856 1.837 11.623 8.048 11.623 15.147v65.793c33.793-9.913 62.771-33.463 79.074-66.13l119.85 29.509c-14.567 69.186-99.576 110.14-175.59 96.362z" fill="#00b7b7" fill-rule="evenodd"/></g></g></svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 667.26 706.8" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><metadata><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><g transform="matrix(1.3333 0 0 -1.3333 -101.19 800)"><g fill="#fff"><path d="m112.25 369.32c12.988-10.101 30.989 0.076 39.875 15.722 9.969-34.512 37.501-53.367 57.648-37.596-6.503-20.085-34.373-34.554-61.522 13.215-15.947-14.598-28.559-14.416-36.001 8.659m14.302-81.369c15.405-6.791 28.15 7.974 30.66 25.885 21.351-31.973 53.097-43.81 65.802-22.905 1.237-21.743-18.954-43.315-60.644-2.464-9.305-18.663-20.837-21.694-35.818-0.516m33.549 175.93 24.381-9.342 24.382 9.342-24.382-24.381zm-14.899 47.955c1.574 0 3.075-0.31 4.448-0.869-1.973-1.1-3.309-3.206-3.309-5.626 0-3.554 2.883-6.436 6.437-6.436 1.608 0 3.079 0.59 4.208 1.566 5e-3 -0.143 8e-3 -0.284 8e-3 -0.428 0-6.512-5.28-11.791-11.792-11.791s-11.791 5.279-11.791 11.791c0 6.513 5.279 11.793 11.791 11.793m77.642 0c1.573 0 3.075-0.31 4.447-0.869-1.973-1.1-3.308-3.206-3.308-5.626 0-3.554 2.882-6.436 6.437-6.436 1.608 0 3.079 0.59 4.207 1.566 6e-3 -0.143 9e-3 -0.284 9e-3 -0.428 0-6.512-5.28-11.791-11.792-11.791-6.513 0-11.792 5.279-11.792 11.791 0 6.513 5.279 11.793 11.792 11.793m0 8.887c11.421 0 20.677-9.259 20.677-20.68 0-11.42-9.256-20.677-20.677-20.677-11.42 0-20.678 9.257-20.678 20.677 0 11.421 9.258 20.68 20.678 20.68m-77.642 0c11.42 0 20.679-9.259 20.679-20.68 0-11.42-9.259-20.677-20.679-20.677s-20.678 9.257-20.678 20.677c0 11.421 9.258 20.68 20.678 20.68m222.62-271.32c-5.257-16.303-14.169-16.431-25.436-6.118-19.182-33.751-38.872-23.527-43.468-9.336 14.236-11.143 33.688 2.178 40.73 26.562 6.28-11.055 18.998-18.245 28.174-11.108m-7.657 101.75c-5.26-16.304-14.169-16.433-25.436-6.118-19.182-33.751-38.873-23.529-43.469-9.338 14.236-11.142 33.688 2.179 40.731 26.564 6.279-11.055 18.997-18.247 28.174-11.108m3.828-50.877c-5.259-16.302-14.168-16.429-25.435-6.117-19.182-33.752-38.873-23.528-43.469-9.338 14.236-11.14 33.687 2.181 40.731 26.564 6.279-11.055 18.996-18.243 28.173-11.109m-185-126.56 8.456 14.687-2.481 14.064c8.24-6.441 16.897-12.257 25.895-17.419l-13.787-20.682c-5.163 5.163-11.523 8.215-18.083 9.35m214.44 47.276-16.013 24.214v93.753c0 40.019-32.441 72.459-72.458 72.459-37.742 0-68.739-28.855-72.144-65.707-0.563 6.626-0.974 13.336-1.228 20.135 0 36.412 26.858 66.546 61.843 71.684 15.118 42.436 3.44 91.058-31.815 121.88-0.332 0.288-0.668 0.569-1.002 0.854-14.934-8.987-28.921-18.756-41.766-29.467 14.159 17.506 30.102 32.253 47.212 45.198-47.368 32.008-116.69 32.008-164.06 0 17.11-12.946 33.054-27.694 47.212-45.201-11.522 9.609-23.965 18.462-37.188 26.67-40.329-37.119-47.937-98.2-17.545-144.16 4.83-7.304 7.155-15.981 6.624-24.722-3.808-62.683 19.436-123.99 63.84-168.4 3.505-3.505 7.116-6.877 10.824-10.115l-15.66-23.489c-18.988 18.987-54.154 9.494-56.053-19.369 4.94 7.294 12.965 10.334 21.93 10.306 4.624 3.683 10.595 5.668 18.277 5.498-14.197-2.669-23.71-11.684-25.256-26.007 9.826 11.065 21.246 13.643 34.785 11.262-7.045-4.94-12.081-12.841-12.767-23.274 4.941 7.293 12.965 10.335 21.932 10.303 1.457 1.162 3.047 2.155 4.783 2.963 0.557-0.597 1.095-1.202 1.614-1.819-7.504-4.493-12.316-11.823-13.378-21.652 5.924 6.673 12.428 10.259 19.627 11.533 1.471-3.662 2.152-7.45 1.823-11.24 4.057 2.964 6.698 7.077 8.006 11.651 2.475-0.187 5.026-0.574 7.654-1.132 12.8-8.149 22.377-20.41 21.31-32.736 11.849 8.659 11.621 27.116 1.367 38.28l8.659 15.04-2.26 12.808c15.693-7.825 32.28-13.746 49.402-17.626-7.313 17.831-12.59 36.793-15.633 56.995 19.737-52.989 51.201-99.462 92.224-140.63 3.548-6.147 10.784-9.143 17.64-7.305 6.856 1.837 11.623 8.048 11.623 15.147v65.793c33.793-9.913 62.771-33.463 79.074-66.13l119.85 29.509c-14.567 69.186-99.576 110.14-175.59 96.362z" fill="#fff" fill-rule="evenodd"/></g></g></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

10
images/nav/alert_icon.svg Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 448 433" version="1.1" viewBox="0 0 448 433" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<radialGradient id="a" cx="216.7" cy="393.79" r="296.7" gradientUnits="userSpaceOnUse">
<stop stop-color="#F4D708" offset="0"/>
<stop stop-color="#FCB400" offset="1"/>
</radialGradient>
<path d="m8.551 390.5 184.85-368.8s26.409-31.504 52.815 0c26.41 31.501 180.19 370.65 180.19 370.65s3.105 18.534-27.961 18.534-361.94 0-361.94 0-23.299 0-27.959-20.38z" fill="url(#a)"/>
<path d="m8.551 390.5 184.85-368.8s26.409-31.504 52.815 0c26.41 31.501 180.19 370.65 180.19 370.65s3.105 18.534-27.961 18.534-361.94 0-361.94 0-23.299 0-27.959-20.38z" fill="none" stroke="#E2A713" stroke-width="5"/>
<path d="m212.5 292.63c-13.168-79.969-19.75-123.12-19.75-129.45 0-7.703 2.551-13.926 7.66-18.676 5.105-4.746 10.871-7.121 17.293-7.121 6.949 0 12.82 2.535 17.609 7.598s7.188 11.023 7.188 17.883c0 6.543-6.668 49.801-20 129.77h-10zm27 38.17c0 6.098-2.156 11.301-6.469 15.613-4.313 4.309-9.461 6.465-15.453 6.465-6.098 0-11.301-2.156-15.613-6.465-4.313-4.313-6.465-9.516-6.465-15.613 0-5.992 2.152-11.141 6.465-15.453s9.516-6.469 15.613-6.469c5.992 0 11.141 2.156 15.453 6.469s6.48 9.45 6.48 15.44z"/>
<metadata><rdf:RDF><cc:Work><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/><dc:publisher><cc:Agent rdf:about="http://openclipart.org/"><dc:title>Openclipart</dc:title></cc:Agent></dc:publisher><dc:title>Warning Notification</dc:title><dc:date>2007-02-08T17:08:47</dc:date><dc:description>Beveled yellow caution sign</dc:description><dc:source>http://openclipart.org/detail/3130/warning-notification-by-eastshores</dc:source><dc:creator><cc:Agent><dc:title>eastshores</dc:title></cc:Agent></dc:creator><dc:subject><rdf:Bag><rdf:li>alert</rdf:li><rdf:li>caution</rdf:li><rdf:li>clip art</rdf:li><rdf:li>clipart</rdf:li><rdf:li>icon</rdf:li><rdf:li>image</rdf:li><rdf:li>media</rdf:li><rdf:li>public domain</rdf:li><rdf:li>svg</rdf:li><rdf:li>warning</rdf:li></rdf:Bag></dc:subject></cc:Work><cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/"><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></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
images/nav/back-40.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

6
images/nav/back.svg Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="4.191mm" height="4.191mm" version="1.1" viewBox="0 0 4.191 4.191" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-91.032 -156.47)">
<path d="m95.223 158.3v0.52916h-3.175c0.48507 0.48507 0.97014 0.97014 1.4552 1.4552-0.12524 0.12524-0.25047 0.25047-0.37571 0.37571l-2.0955-2.0955 2.0955-2.0955c0.12524 0.12524 0.25047 0.25047 0.37571 0.37571-0.48507 0.48507-0.97014 0.97014-1.4552 1.4552h3.175z" fill="#f9f9f9" stroke-width=".26458"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 510 B

BIN
images/nav/close-40.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

6
images/nav/close.svg Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="3.7042mm" height="3.7042mm" version="1.1" viewBox="0 0 3.7042 3.7042" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-20.8 -106.38)">
<path d="m24.505 106.75-0.37306-0.37306-1.479 1.479-1.479-1.479-0.37306 0.37306 1.479 1.479-1.479 1.479 0.37306 0.37306 1.479-1.479 1.479 1.479 0.37306-0.37306-1.479-1.479z" fill="#f9f9f9" stroke-width=".26458"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 422 B

6
images/nav/hamburger.svg Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="4.7625mm" height="3.175mm" version="1.1" viewBox="0 0 4.7625 3.175" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-54.25 -207.32)">
<path d="m54.25 207.32h4.7625v0.52917h-4.7625v-0.52917m0 1.3229h4.7625v0.52916h-4.7625v-0.52916m0 1.3229h4.7625v0.52917h-4.7625z" fill="#f9f9f9" stroke-width=".26458"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 378 B

BIN
images/nav/home_icon-25.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

15
images/nav/home_icon.svg Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 97.74 85.154" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<metadata>
<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/>
</cc:Work>
</rdf:RDF>
</metadata>
<g transform="translate(-51.081 -910.49)">
<path d="m100.72 910.49-24.82 23.287-24.82 23.285 16.678 0.084v38.498h23.467v-32.783h17.453v32.783h23.467v-38.174l16.676 0.084-48.1-47.064z" color="#000000" color-rendering="auto" fill="#fff" fill-rule="evenodd" image-rendering="auto" shape-rendering="auto" solid-color="#000000" style="isolation:auto;mix-blend-mode:normal"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 836 B

BIN
images/nav/save-59.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

6
images/nav/save.svg Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="4.7625mm" height="4.7625mm" version="1.1" viewBox="0 0 4.7625 4.7625" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-123.47 -217.85)">
<path d="m126.38 220.44c-0.1323-0.12965-0.3228-0.21167-0.52917-0.21167-0.43921 0-0.79375 0.35454-0.79375 0.79375 0 0.3466 0.22225 0.63765 0.52917 0.74612 0.0185-0.56885 0.33602-1.0636 0.79375-1.3282m-0.76994 1.6404h-1.6113v-3.7042h2.9554l0.74877 0.74877v1.1959c0.19844 0.0688 0.37571 0.17198 0.52917 0.31486v-1.7304l-1.0583-1.0583h-3.175c-0.29369 0-0.52917 0.23812-0.52917 0.52917v3.7042c0 0.29104 0.23548 0.52917 0.52917 0.52917h1.8018c-0.0926-0.1614-0.15875-0.33867-0.1905-0.52917m-1.3467-2.3812h2.3812v-1.0583h-2.3812v1.0583m2.5797 2.9104-0.72761-0.79375 0.30692-0.30692 0.42069 0.42069 0.94985-0.94985 0.30692 0.37306-1.2568 1.2568" fill="#f9f9f9" stroke-width=".26458"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 888 B

BIN
images/nre/nre-powered.xcf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

70
index.html Normal file
View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="description" content="OwlBoard - Live train departures for traincrew."/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="application-name" content="OwlBoard">
<meta name="author" content="Frederick Boniface">
<meta name="theme-color" content="#00b7b7">
<link rel="apple-touch-icon" href="/images/app-icons/any/apple-192.png">
<link rel="stylesheet" type="text/css" href="./styles/main.css"/>
<link rel="icon" type="image/svg+xml" href="./images/icon.svg"/>
<link rel="manifest" type="application/json" href="./manifest.json"/>
<script src="./js/lib.main.js" defer></script>
<script src="./js/index.js" defer></script>
<title>OwlBoard</title>
</head>
<body>
<!-- Loading Box -->
<div id="loading">
<div class="spinner">
</div>
<p id="loading_desc">Loading</p>
</div>
<!-- Popup Menu -->
<div id="top_button" class="hide_micro">
<picture aria-label="Menu" class="sidebar_control" id="sidebar_open_short" onclick="sidebarOpen()">
<source srcset="/images/nav/hamburger.svg" type="image/svg+xml">
<img src="hamburger_40.png" alt="Open menu">
</picture>
<picture aria-label="Close Menu" class="sidebar_control" id="sidebar_close_short" onclick="sidebarClose()">
<source srcset="/images/nav/close.svg" type="image/svg+xml">
<img src="close-40.png" alt="Close menu">
</picture>
</div>
<div id="sidebar">
<a href="/">Home</a>
<a href="/find-code.html">Code Search</a>
<a href="/settings.html">Settings</a>
<a href="/help.html">Help</a>
<a href="/issue.html">Report Issue</a>
</div>
<!-- Main Content Begins -->
<picture>
<source srcset="/images/logo/wide_logo.svg" type="image/svg+xml">
<source media="(max-height: 739px)" srcset="/images/logo/logo-full-200.png" type="image/png">
<source srcset="/images/logo/logo-full-250.png" type="image/png">
<img class="titleimg" src="/images/logo/logo-full-250.png" alt="OwlBoard Logo">
</picture>
<br>
<form action="board.html">
<input class="lookup-box" type="text" id="crs-lookup" name="stn" placeholder="Enter CRS/TIPLOC" autocomplete="off"/>
<br>
<input type="submit" value="Lookup Board" class="lookup-button" onclick="vibe('ok')">
</form>
<h2>Quick Links</h2>
<div id="quick_links">
</div>
<div class="text-description">
<p>Customise your quick links on the <a href="/settings.html">Settings</a> page.</p>
</div>
<!-- Footer -->
<footer>
<p>Created by <a href="https://fredboniface.co.uk" target="_blank" rel="noreferrer noopener">Fred Boniface</a> - 0.0.3</p>
</footer>
</body>
</html>

58
issue.html Normal file
View File

@ -0,0 +1,58 @@
<html>
<head>
<meta charset="UTF-8"/>
<meta name="description" content="OwlBoard - Live train departures for traincrew."/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="application-name" content="OwlBoard">
<meta name="author" content="Frederick Boniface">
<meta name="theme-color" content="#00b7b7">
<link rel="apple-touch-icon" href="/images/app-icons/any/apple-192.png">
<link rel="stylesheet" type="text/css" href="./styles/main.css"/>
<link rel="stylesheet" type="text/css" href="./styles/issue.css"/>
<link rel="icon" type="image/svg+xml" href="./images/icon.svg"/>
<link rel="manifest" type="application/json" href="./manifest.json"/>
<script src="./js/lib.main.js" defer></script>
<script src="./js/issue.js" defer></script>
<title>OwlBoard - Report</title>
</head>
<body>
<!-- Loading Box -->
<div id="loading">
<div class="spinner">
</div>
<p id="loading_desc">Loading</p>
</div>
<div id="top_button" class="hide_micro">
<a href="/">
<picture aria-label="Home" class="sidebar_control">
<source srcset="/images/nav/back.svg" type="image/svg+xml">
<img src="back-40.png" alt="Home">
</picture>
</a>
</div>
<picture>
<source srcset="/images/logo/wide_logo.svg" type="image/svg+xml">
<source media="(max-height: 739px)" srcset="/images/logo/logo-full-200.png" type="image/png">
<source srcset="/images/logo/logo-full-250.png" type="image/png">
<img class="titleimg" src="/images/logo/logo-full-250.png" alt="OwlBoard Logo">
</picture>
<h2>Report an Issue</h2>
<p>To help diagnosing an issue, data about your browser and device will be
collected alongside the data that you enter below.</p>
<p>The data will be available publically in the <a href="https://git.fjla.uk/fred.boniface/owlboard/issues">
OwlBoard Issue Tracker</a>. A preview will be shown before the data is sent.</p>
<label for="subject">Subject:</label><br>
<input type="text" name="subject" id="subject" class="text-entry"/><br>
<label for="content">Message:</label><br>
<textarea name="message" id="message" class="text-entry-long"></textarea><br>
<input type="submit" name="submit" id="submit" label="Preview" class="lookup-button" onclick="submit()">
<div id="preflight">
<h3>Check & Send</h3>
<h3>---</h3>
<h4 id="pre_subject"></h4>
<p id="pre_message"></p>
<button id="send" class="lookup-button" onclick="send()">Send</button>
<button id="cancel" class="lookup-button" onclick="cancel()">Cancel</button>
</div>
</body>
</html>

97
js/find-code.js Normal file
View File

@ -0,0 +1,97 @@
hideLoading();
async function fetchEntry(){ // This can be condensed
showLoading();
var name = document.getElementById("name")
var crs = document.getElementById("3alpha")
var nlc = document.getElementById("nlc")
var tiploc = document.getElementById("tiploc")
var stanox = document.getElementById("stanox")
var values = {
name: name.value,
crs: crs.value,
nlc: nlc.value,
tiploc: tiploc.value,
stanox: stanox.value
}
parseData(values)
}
async function parseData(values){
vibe()
if (values.crs != ""){
setLoadingDesc(`Searching\n${values.crs.toUpperCase()}`)
var data = await getData("crs", values.crs)
} else if (values.nlc != ""){
setLoadingDesc(`Searching\n${values.nlc.toUpperCase()}`)
var data = await getData("nlc", values.nlc)
} else if (values.tiploc != ""){
setLoadingDesc(`Searching\n${values.tiploc.toUpperCase()}`)
var data = await getData("tiploc", values.tiploc)
} else if (values.stanox != ""){
setLoadingDesc(`Searching\n${values.stanox.toUpperCase()}`)
var data = await getData("stanox", values.stanox)
} else if (values.name != ""){
setLoadingDesc(`Searching\n${values.name}`)
var data = await getData("name", values.name)
} else {
log("find-code.parseData: No data entered", "WARN")
await clearForm();
document.getElementById("name").value = "No data entered"
vibe("err");
hideLoading();
return;
}
displayData(data);
}
async function getData(type, value){
log(`find-code.getData: Looking for: ${type} '${value}'`, "INFO")
try {
var url = `${window.location.origin}/api/v1/find/${type}/${value}`;
var resp = await fetch(url);
return await resp.json()
} catch (err) {
log(`find-code.getData: Error getting data: ${err}`, "WARN")
vibe("err")
return "";
}
}
async function displayData(data){
hideLoading();
if (data.status === "failed" || data == ""){
log(`find-code.displayData: Unable to find data`, "WARN")
clearForm();
document.getElementById("name").value = "Not Found";
} else {
log(`find-code.displayData: Inserting data`, "INFO")
vibe("ok")
try {
document.getElementById("name").value = data['0']['NLCDESC']
} catch (err) {}
try {
document.getElementById("3alpha").value = data['0']['3ALPHA']
} catch (err) {}
try {
document.getElementById("nlc").value = data['0']['NLC']
} catch (err) {}
try {
document.getElementById("tiploc").value = data['0']['TIPLOC']
} catch (err) {}
try {
document.getElementById("stanox").value = data['0']['STANOX']
} catch (err) {}
}
}
async function clearForm(){
document.getElementById("name").value = ""
document.getElementById("3alpha").value = ""
document.getElementById("nlc").value = ""
document.getElementById("tiploc").value = ""
document.getElementById("stanox").value = ""
vibe("ok");
hideLoading();
}

26
js/index.js Normal file
View File

@ -0,0 +1,26 @@
// Init:
pageInit();
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js");
}
async function pageInit() {
await loadQuickLinks();
hideLoading(); // From lib.main
}
async function gotoBoard(station){
vibe("ok")
window.location.assign(`${window.location.origin}/board.html?stn=${station}`);
}
async function loadQuickLinks(){
var data = await getQuickLinks(); // From lib.main
var buttons = "";
for(var i = 0; i < data.length; i++) {
buttons += `
<button class="actionbutton" onclick="gotoBoard('${data[i]}')">${data[i].toUpperCase()}</button>`
}
document.getElementById("quick_links").insertAdjacentHTML("beforeend", buttons)
}

88
js/issue.js Normal file
View File

@ -0,0 +1,88 @@
init();
async function init() {
hideLoading()
}
async function submit() {
setLoadingDesc("Collecting\nData")
showLoading()
var browserData = await getBrowserData();
setLoadingDesc("Reading\nForm")
var formData = await getFormData();
preflight({browserData: browserData, formData: formData})
}
async function getFormData() {
let data = {}
data.subject = document.getElementById("subject").value
data.message = document.getElementById("message").value
return data
}
async function getBrowserData() {
let data = {}
data.userAgent = navigator.userAgent
data.userAgentData = JSON.stringify(navigator.userAgentData)
data.localStorage = JSON.stringify(await storageAvailable('localStorage'))
data.sessionStorage = JSON.stringify(await storageAvailable('sessionStorage'))
data.viewport = `${window.innerWidth} x ${window.innerHeight}`
return data
}
async function preflight(data) {
document.getElementById("pre_subject").textContent = data.formData.subject
pre_msg = `UserAgent: ${data.browserData.userAgent}
\nUserAgentData: ${data.browserData.userAgentData}
\nlocalStorage Avail: ${data.browserData.localStorage}
\nsessionStorage Avail: ${data.browserData.sessionStorage}
\nViewport size: ${data.browserData.viewport}
\nUser message:\n\n${data.formData.message}`
document.getElementById("pre_message").innerText = pre_msg
hideLoading()
document.getElementById("preflight").style = "display: block"
sessionStorage.setItem("preflight_subject", data.formData.subject)
sessionStorage.setItem("preflight_msg", pre_msg)
}
async function cancel() {
document.getElementById("preflight").style = "display: none"
}
async function send() {
setLoadingDesc("Sending\nData")
document.getElementById("preflight").style = "display: none"
showLoading()
var subject = sessionStorage.getItem("preflight_subject");
var msg = sessionStorage.getItem("preflight_msg")
if (typeof subject != "string") {
subject = document.getElementById("preflight_subject").innerText
}
if (typeof msg != "string") {
msg = document.getElementById("preflight_msg")
}
var payload = JSON.stringify({subject: subject, msg: msg})
console.log(payload);
let opt = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
redirect: 'follow',
body: payload
}
var res = await fetch(`${window.location.origin}/api/v1/issue`, opt)
if (res.status == 200) {
setLoadingDesc("Success")
vibe("ok")
await delay(2500)
window.location.replace("/")
} else {
setLoadingDesc("Error")
vibe("err")
await delay(2500)
hideLoading()
document.getElementById("preflight").style = "display: none;"
}
}

248
js/lib.board.js Normal file
View File

@ -0,0 +1,248 @@
/* Fetch Functions */
async function publicLdb(stn) {
var url = `${window.location.origin}/api/v1/ldb/${stn}`;
var resp = await fetch(url);
return await resp.json();
}
/* Set page heading */
async function setHeaders(title,time) {
var prefix = `OwlBoard - `
document.title = `${prefix}${title}`
document.getElementById("stn_name").textContent = title
document.getElementById("fetch_time").textContent = time.toLocaleTimeString()
sessionStorage.setItem("board_location", title);
}
/* Display No Trains Message */
async function displayNoTrains() {
log("No Trains", "WARN")
document.getElementById('no_services').style = "display: block;";
hideLoading();
}
/* Parse the value of `platform` to account for unknown platforms */
async function parsePlatform(svc){
if (svc.platform != undefined) {
var platform = svc.platform;
} else {
var platform = "-";
}
if (svc.platformChanged) { // Not present in public API, ready for staff version.
var changed = "changed";
} else {
var changed = "";
}
return {num: platform, change: changed}
}
/* Change value of time strings to fit well on small screens */
async function parseTime(string){
switch (string) {
case "Delayed":
var output = "LATE";
var change = "changed";
break;
case "Cancelled":
var output = "CANC";
var change = "cancelled";
break;
case "On time":
var output = "RT";
var change = "";
break;
case "":
var output = "-";
var change = "";
break;
case undefined:
var output = "-";
var change = "";
break;
case "No report":
var output = "-";
var change = "";
break;
case "undefined":
var output = false;
var change = "";
break;
default:
var output = string;
var change = "changed";
}
return {data: output, changed: change};
}
/* Convert multiple Origin/Destinations to single string */
async function parseName(location) {
if (Array.isArray(location)) {
var name = `${location[0]['locationName']} & ${location[1]['locationName']}`
return name;
}
else {
return location.locationName;
}
}
// Display Alert Messages
async function displayAlerts(array) {
var counter = 0
var messages = ""
for(var i = 0; i < array.length; i++) {
// Increment counter
counter += 1;
// Reset Vars
messages += `<p>${array[i]}</p>`;
}
if (counter > 0) {
document.getElementById("alerts_msg").insertAdjacentHTML("beforeend", messages)
document.getElementById("alerts").style = "display:block"
document.getElementById("alerts_bar").style = "display:block"
if (counter == 1) {
document.getElementById("alert_bar_note").textContent = `There is ${counter} active alert`
} else if (counter > 1) {
document.getElementById("alert_bar_note").textContent = `There are ${counter} active alerts`
}
return true;
}
return false;
}
/* Show/Hide alerts box */
async function inflateAlerts() {
document.getElementById("alerts_msg").style = "display:block;";
document.getElementById("alert_expand_arrow").style = "transform: rotate(180deg);";
document.getElementById("alerts_bar").setAttribute("onclick", "deflateAlerts()")
}
async function deflateAlerts() {
document.getElementById("alerts_msg").style = "display.none;";
document.getElementById("alert_expand_arrow").style = "transform: rotate(0deg);";
document.getElementById("alerts_bar").setAttribute("onclick", "inflateAlerts()")
}
/*//// SERVICE DETAIL LISTS ////*/
// Build calling list: -- This outputs calling point data to sessionStorage in the format: key{pre: [{PREVIOUS_Stops}], post: [{POST_STOPS}]}
async function buildCallLists(svc) {
var sSvcId = svc.serviceID;
var oSvcData = {
plat: svc.platform,
sta: svc.sta,
eta: svc.eta,
std: svc.std,
etd: svc.etd
};
try {
if (typeof svc.previousCallingPoints.callingPointList.callingPoint != 'undefined') {
let array = await makeArray(svc.previousCallingPoints.callingPointList.callingPoint);
oSvcData.pre = array;
}
} catch (err) { /* Do nothing if ERR */ }
try {
if (typeof svc.subsequentCallingPoints.callingPointList.callingPoint != 'undefined') {
let array = await makeArray(svc.subsequentCallingPoints.callingPointList.callingPoint);
oSvcData.post = array;
}
} catch (err) { /* Do nothing if ERR */ }
sessionStorage.setItem(sSvcId, JSON.stringify(oSvcData))
}
/* Display calling list: - Read data from sessionStorage and write to DOM. */
async function showCalls(id) {
log(`Showing details for service ${id}`, "INFO")
var svcDetail = await JSON.parse(sessionStorage.getItem(id));
var pre = "";
var post = "";
if (typeof svcDetail.pre != 'undefined') {
for(var preCall = 0; preCall < svcDetail.pre.length; preCall++) {
pre += await singleCall(svcDetail.pre[preCall]);
}
}
if (typeof svcDetail.post != 'undefined') {
for(var postCall = 0; postCall < svcDetail.post.length; postCall++) {
post += await singleCall(svcDetail.post[postCall]);
}
}
/* Run retreived data through parsers */
var thisStd = await parseTime(svcDetail.std);
var thisEtd = await parseTime(svcDetail.etd);
var thisSta = await parseTime(svcDetail.sta);
var thisEta = await parseTime(svcDetail.eta);
/* Prepare data for this station */
if (thisStd.data != "-") {
var sTime = `${thisStd.data}`
var eTime = `${thisEtd.data}`
var change = thisEtd.changed
} else {
var sTime = `${thisSta.data}`
var eTime = `${thisEta.data}`
var change = thisEta.changed
};
let here = `<tr>
<td class="detail-name detail-name-here detail-table-content">${sessionStorage.getItem("board_location")}</td>
<td class="detail-table-content">${sTime}</td>
<td class="detail-table-content ${change}">${eTime}</td>
</tr> `
/* Prepare then insert DOM Data */
let dom = ` <div id="${id}" class="call-data">
<p class="close-data" onclick="hideCalls('${id}')">X</p>
<table class="call-table">
<tr>
<th class="detail-name-head">Location</th>
<th class="time">Schedule</th>
<th class="time">Act/Est</th>
</tr>
${pre}
${here}
${post}
</table>
</div>`
document.body.insertAdjacentHTML("beforeend", dom);
document.getElementById(id).style = "display: block;";
return;
}
async function hideCalls(id) {
let element = document.getElementById(id)
element.style = "display: none;";
element.remove();
return;
}
/* Builds the train data information in to a table row */
async function singleCall(data) {
if (typeof data.et != "undefined") {
var time = await parseTime(data.et)
} else if (typeof data.at != "undefined") {
var time = await parseTime(data.at)
}
return `<tr>
<td class="detail-name detail-table-content">${data.locationName}</td>
<td class="detail-table-content">${data.st}</td>
<td class="detail-table-content ${time.changed}">${time.data}</td>
</tr>`
}
/* Error Handler */
async function errorHandler() {
if (sessionStorage.getItem("failcount")) {
var errCount = parseInt(sessionStorage.getItem("failcount"))
} else {
var errCount = 0;
}
errCount += 1;
sessionStorage.setItem("failcount", errCount.toString())
if (errCount < 10){
await delay(3000);
vibe("err")
location.reload()
} else {
sessionStorage.removeItem("failcount");
window.location.assign("conn-err.html")
}
}

132
js/lib.main.js Normal file
View File

@ -0,0 +1,132 @@
/* Feature Detectors */
/* Valid values for ${type}: localstorage, sessionstorage */
async function storageAvailable(type) { // Currently not used
try {
let storage = window[type];
let x = '__storage_test__';
storage.setItem(x, "test");
storage.getItem(x);
storage.removeItem(x);
log(`lib.main.storageAvailable: ${type} is available`, "INFO")
return true;
} catch (err) {
log(`lib.main.storageAvailable: ${type} is not available`, "ERR")
return false;
}
}
/* Array Converter
Converts a string to a single item array */
async function makeArray(data) {
if (!Array.isArray(data)) {
var array = [];
array.push(data);
return array;
}
return data;
}
/* Timeouts */
/* Usage: '' */
const delay = ms => new Promise(res => setTimeout(res, ms));
/* Log Helper */
/* Values for level: 1, 2, 3 */
/* Maintains backwards compatibility for previous
implementation of log helper */
async function log(msg, type) {
var time = new Date().toISOString();
switch (type) {
case "ERR":
console.error(`${time} - ${msg}`);
break;
case "WARN":
console.warn(`${time} - ${msg}`);
break;
case "INFO":
console.info(`${time} - ${msg}`);
break;
default:
console.log(`${time} - ${msg}`);
break;
};
};
/* Show/Hide - Menu Control */
async function sidebarOpen() {
document.getElementById("sidebar").style.width = "50%";
document.getElementById("sidebar_open_short").style.display = "none";
document.getElementById("sidebar_close_short").style.display = "block";
}
async function sidebarClose() {
document.getElementById("sidebar").style.width = "0%"
document.getElementById("sidebar_open_short").style.display = "block";
document.getElementById("sidebar_close_short").style.display = "none";
}
/* Loading Box Control */
async function hideLoading() {
document.getElementById("loading").style = "display: none;";
}
/* DEPRECIATED: Alias for hideLoading() - Marked for removal*/
async function clearLoading() {
log("Depreciated function called - clearLoading() - Alias to hideLoading()", "WARN")
await hideLoading();
}
async function showLoading() {
document.getElementById("loading").style = "display: block;";
}
async function setLoadingDesc(desc) {
document.getElementById("loading_desc").textContent = `${desc}`;
}
/* Fetch User Settings */
async function getQuickLinks() {
var defaults =
["bri","lwh","srd","mtp","rda","cfn",
"sml","shh","pri","avn","sar","svb"];
try {
if (localStorage.getItem("qlOpt")) {
var data = JSON.parse(localStorage.getItem("qlOpt"));
} else {
data = defaults;
}
} catch (err) {
data = defaults;
}
return data.sort();
}
/* Fetch a known query parameter from the pages URL */
async function getQuery(param) {
var params = new URLSearchParams(window.location.search)
var query = params.get(param)
if (query) {
return query
} else {
return 'false'
}
}
async function vibe(type) {
let canVibrate = "vibrate" in navigator || "mozVibrate" in navigator
if (canVibrate && !("vibrate" in navigator)){
navigator.vibrate = navigator.mozVibrate
}
switch (type) {
case "err":
navigator.vibrate([300])
break;
case "ok":
navigator.vibrate([50,50,50])
break;
default:
navigator.vibrate(30)
}
}

57
js/settings.js Normal file
View File

@ -0,0 +1,57 @@
// Init:
const ql = ["ql0","ql1","ql2","ql3","ql4","ql5","ql6","ql7","ql8","ql9","ql10","ql11"]
storageAvailable("localStorage");
getQl();
hideLoading();
async function getQl(){
var qlOpt = await getQuickLinks()
if (qlOpt){
var i = 0
while (i < 12) {
if (qlOpt[i] != 'undefined') {
document.getElementById(`ql${i}`).value = qlOpt[i]
i +=1
}
}
}
}
async function setQl(){
await showLoading();
var qlSet = []
for (i in ql) {
var opt = document.getElementById(`ql${i}`).value
if (opt != ""){
qlSet.push(opt)
}
qlSet.sort()
}
localStorage.setItem("qlOpt", JSON.stringify(qlSet))
log(`settings.setQl: User settings saved`, "INFO")
await hideLoading();
await showDone();
vibe("ok")
await delay(800);
hideDone();
}
async function clearQl(){
showLoading();
localStorage.removeItem("qlOpt")
log(`settings.setQl: User settings reset to default`, "INFO")
getQl()
await hideLoading();
await showDone();
vibe("ok");
await delay(800);
hideDone();
}
async function showDone() {
document.getElementById("done").style = "opacity: 1";
}
async function hideDone() {
document.getElementById("done").style = "opacity: 0";
}

235
js/simple-board.js Normal file
View File

@ -0,0 +1,235 @@
/* Page Init: */
init()
/* Init function */
async function init() {
console.time("Loading Time")
setLoadingDesc(`Loading\nservices`)
var stn = await getQuery("stn");
setLoadingDesc(`Loading\n${stn.toUpperCase()}`)
log(`init: Looking up: ${stn}`);
var sv = await getQuery("sv");
log(`init: Staff Version: ${sv}`);
if (sv === 'true') {
log("init: Staff Version not supported yet.")
log("init: Unable to proceed.")
} else {
try {
var data = await publicLdb(stn)
setLoadingDesc(`${stn.toUpperCase()}\nParsing Data`)
log("simple-board.init: Fetched LDB Data", "INFO")
} catch (err) {
var data = "err"
setLoadingDesc(`Waiting\nConnection`)
log(`simple-board.init: Error fetching data: ${err}`, "ERR")
}
parseLdb(data)
}
}
/* Check for any errors in data returned from the Fetch call
If no errors, if there are none, call buildPage(). */
async function parseLdb(data) {
if (data.ERROR == "NOT_FOUND") { // Station not found
hideLoading();
document.getElementById("error_notice").style = "display: block;";
document.getElementById("err_not_found").style = "display: block;";
setHeaders("Not Found",new Date())
} else if (data == false) { // No data for station
hideLoading();
document.getElementById("error_notice").style = "display: block;";
document.getElementById("err_no_data").style = "display:block;";
setHeaders("No Data",new Date())
} else if (data == "err") { // Connection Error
await delay(2000);
hideLoading();
document.getElementById("error_notice").style = "display: block;";
document.getElementById("err_conn").style = "display: block;";
setHeaders("Connection Error",new Date())
showLoading();
await delay(5000);
log(`parseLdb: Passing to error handler`, "ERR")
errorHandler();
} else {
buildPage(data);
}
}
// Build and Display Functions
async function buildPage(data) {
setLoadingDesc('Loading\nData')
var stationName = data.GetStationBoardResult.locationName;
log(`buildPage: Data ready for ${stationName}`);
var generateTime = new Date(await data.GetStationBoardResult.generatedAt);
log(`buildPage: Data prepared at ${generateTime.toLocaleString()}`)
setHeaders(stationName, generateTime);
// Check for notices and if true pass to function
if (data.GetStationBoardResult.nrccMessages) {
setLoadingDesc('Loading\nAlerts')
await displayAlerts(await makeArray(data.GetStationBoardResult.nrccMessages.message));
}
if (data.GetStationBoardResult.trainServices) {
setLoadingDesc('Loading\nTrains')
displayTrains(await makeArray(data.GetStationBoardResult.trainServices.service))
} else {
displayNoTrains()
}
if (data.GetStationBoardResult.ferryServices) {
setLoadingDesc('Loading\nFerries')
displayFerry(await makeArray(data.GetStationBoardResult.ferryServices.service))
}
if (data.GetStationBoardResult.busServices) {
setLoadingDesc('Loading\nBusses')
displayBus(await makeArray(data.GetStationBoardResult.busServices.service))
}
console.timeEnd("Loading Time")
}
async function displayTrains(data) {
log(`simple-board.displayTrains: Inserting data in DOM`)
for(var i = 0; i < data.length; i++) {
// Reset Vars
var svc = data[i];
displayService(svc);
buildCallLists(svc);
}
hideLoading();
document.getElementById("output").style = "display:block;";
log(`simple-board.displayTrains: Insertion complete`)
}
async function displayFerry(ferrySvc) {
for(var i = 0; i < ferrySvc.length; i++) {
displayFerryService(ferrySvc[i])
}
}
async function displayBus(busSvc) {
for(var i = 0; i < busSvc.length; i++) {
displayBusService(busSvc[i])
buildCallLists(busSvc[i])
}
}
async function displayService(svc) {
var table = document.getElementById("output");
// Determine Time Message
var sta = await parseTime(svc.sta);
var eta = await parseTime(svc.eta);
var std = await parseTime(svc.std);
var etd = await parseTime(svc.etd);
// Determine Platform Message
//if (svc.platform != undefined){var plt = svc.platform} else {var plt = "-"};
var plt = await parsePlatform(svc);
// Define Table Row
var row = `
<table>
<tr>
<td class="name name-item" onclick="showCalls('${svc.serviceID}')">${await parseName(svc.origin.location)}</td>
<td class="name name-item" onclick="showCalls('${svc.serviceID}')">${await parseName(svc.destination.location)}</td>
<td class="plat ${plt.changed}">${plt.num}</td>
<td class="time">${sta.data}</td>
<td class="time ${eta.changed}">${eta.data}</td>
<td class="time">${std.data}</td>
<td class="time ${etd.changed}">${etd.data}</td>
</tr>
</table>`
// Put Table Row
table.insertAdjacentHTML("beforeend", row)
// Display Operator
if (svc.operator) {
var opRow = `<p class="msg op">A ${svc.operator} service</p>`
table.insertAdjacentHTML("beforeend", opRow);
}
// Parse cancelReason & delayReason
if (svc.cancelReason) {
var cancelRow = `<p class="msg">${svc.cancelReason}</p>`
table.insertAdjacentHTML("beforeend", cancelRow);
}
if (svc.delayReason) {
var delayRow = `<p class="msg">${svc.delayReason}</p>`
table.insertAdjacentHTML("beforeend", delayRow);
}
}
async function displayFerryService(svc) {
var table = document.getElementById("ferry");
log(JSON.stringify(svc))
// Determine Time Message
var sta = await parseTime(svc.sta);
var eta = await parseTime(svc.eta);
var std = await parseTime(svc.std);
var etd = await parseTime(svc.etd);
// Determine Platform Message
var plt = "";
// Define Table Row
var row = `
<table>
<tr>
<td class="name name-item">${await parseName(svc.origin.location)}</td>
<td class="name name-item">${await parseName(svc.destination.location)}</td>
<td class="plat}">${plt}</td>
<td class="time">${sta.data}</td>
<td class="time ${eta.changed}">${eta.data}</td>
<td class="time">${std.data}</td>
<td class="time ${etd.changed}">${etd.data}</td>
</tr>
</table>`
// Put Table Row
table.insertAdjacentHTML("beforeend", row)
// Parse cancelReason & delayReason
if (svc.cancelReason) {
var cancelRow = `<p class="msg">${svc.cancelReason}</p>`
table.insertAdjacentHTML("beforeend", cancelRow);
}
if (svc.delayReason) {
var delayRow = `<p class="msg">${svc.delayReason}</p>`
table.insertAdjacentHTML("beforeend", delayRow);
}
document.getElementById("ferry").style = "display:block"
}
async function displayBusService(svc) {
var table = document.getElementById("bus");
log(JSON.stringify(svc))
// Determine Time Message
var sta = await parseTime(svc.sta);
var eta = await parseTime(svc.eta);
var std = await parseTime(svc.std);
var etd = await parseTime(svc.etd);
// Determine Platform Message
var plt = "";
// Define Table Row
var row = `
<table>
<tr>
<td class="name name-item" onclick="showCalls('${svc.serviceID}')">${svc.origin.location.locationName}</td>
<td class="name name-item" onclick="showCalls('${svc.serviceID}')">${svc.destination.location.locationName}</td>
<td class="plat}">${plt}</td>
<td class="time">${sta.data}</td>
<td class="time ${eta.changed}">${eta.data}</td>
<td class="time">${std.data}</td>
<td class="time ${etd.changed}">${etd.data}</td>
</tr>
</table>`
// Put Table Row
table.insertAdjacentHTML("beforeend", row)
// Display operator
if (svc.operator) {
var opRow = `<p class="msg op">A ${svc.operator} service</p>`
table.insertAdjacentHTML("beforeend", opRow);
}
// Parse cancelReason & delayReason
if (svc.cancelReason) {
var cancelRow = `<p class="msg">${svc.cancelReason}</p>`
table.insertAdjacentHTML("beforeend", cancelRow);
}
if (svc.delayReason) {
var delayRow = `<p class="msg">${svc.delayReason}</p>`
table.insertAdjacentHTML("beforeend", delayRow);
}
document.getElementById("bus").style = "display:block"
}

24
js/stat.js Normal file
View File

@ -0,0 +1,24 @@
init();
async function init() {
display(await get())
}
async function get() {
var url = `${window.location.origin}/api/v1/stats`;
var resp = await fetch(url);
return await resp.json();
}
async function display(data) {
document.getElementById('server_host').textContent = `HOST: ${data.host}`;
let dat = data.dat[0]
console.log(JSON.stringify(dat))
document.getElementById('time').textContent = dat.since;
document.getElementById('ldbws').textContent = dat.ldbws || "0";
document.getElementById('ldbsvws').textContent = dat.ldbsvws || "0";
document.getElementById('corpus').textContent = dat.corpus || "0";
document.getElementById('stations').textContent = dat.stations || "0";
document.getElementById('users').textContent = dat.user || "0";
document.getElementById('meta').textContent = dat.meta || "0";
}

33
manifest.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "OwlBoard",
"short_name": "OwlBoard",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#404c55",
"description": "Live station departures - aimed at train-crew.",
"categories": "travel,utilities",
"lang": "en",
"orientation": "portrait",
"theme_color": "#00b7b7",
"icons": [
{
"src": "/images/app-icons/maskable/mask-icon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "maskable"
},
{
"src": "/images/app-icons/any/plain-logo.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/images/app-icons/any/plain-logo-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
}
]
}

72
settings.html Normal file
View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="description" content="OwlBoard - Live train departures for traincrew."/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="application-name" content="OwlBoard">
<meta name="author" content="Frederick Boniface">
<meta name="theme-color" content="#00b7b7">
<link rel="apple-touch-icon" href="/images/app-icons/any/apple-192.png">
<link rel="stylesheet" type="text/css" href="./styles/main.css"/>
<link rel="stylesheet" type="text/css" href="./styles/settings.css"/>
<link rel="icon" type="image/svg+xml" href="./images/icon.svg"/>
<link rel="manifest" type="application/json" href="./manifest.json"/>
<title>OwlBoard - Settings</title>
<script src="./js/lib.main.js" defer></script>
<script src="./js/settings.js" defer></script>
</head>
<body>
<div id="loading">
<div class="spinner">
</div>
<p>Loading</p>
</div>
<div id="done">
<!-- Insert white tick SVG Here -->
<picture id="save-icon">
<source srcset="./images/nav/save.svg" type="image/svg+xml">
<img src="./images/nav/save-59.png" alt="">
</picture>
<p>Saved</p>
</div>
<div id="top_button" class="hide_micro">
<a href="/">
<picture aria-label="Home" class="sidebar_control">
<source srcset="/images/nav/back.svg" type="image/svg+xml">
<img src="back-40.png" alt="Home">
</picture>
</a>
</div>
<picture>
<source srcset="/images/logo/wide_logo.svg" type="image/svg+xml">
<source media="(max-height: 739px)" srcset="/images/logo/logo-full-200.png" type="image/png">
<source srcset="/images/logo/logo-full-250.png" type="image/png">
<img class="titleimg" src="/images/logo/logo-full-250.png" alt="OwlBoard Logo">
</picture>
<h2>Settings</h2>
<p>Any settings you apply will only apply to the device you are using now.</p>
<label>Personal Quick Links:</label><br>
<p>Enter one CRS/3ALPHA code per box</p>
<input type="text" maxlength="3" id="ql0" name="ql0" autocomplete="off" class="small-lookup-box">
<input type="text" maxlength="3" id="ql1" name="ql1" autocomplete="off" class="small-lookup-box"><br>
<input type="text" maxlength="3" id="ql2" name="ql2" autocomplete="off" class="small-lookup-box">
<input type="text" maxlength="3" id="ql3" name="ql3" autocomplete="off" class="small-lookup-box"><br>
<input type="text" maxlength="3" id="ql4" name="ql4" autocomplete="off" class="small-lookup-box">
<input type="text" maxlength="3" id="ql5" name="ql5" autocomplete="off" class="small-lookup-box"><br>
<input type="text" maxlength="3" id="ql6" name="ql6" autocomplete="off" class="small-lookup-box">
<input type="text" maxlength="3" id="ql7" name="ql7" autocomplete="off" class="small-lookup-box"><br>
<input type="text" maxlength="3" id="ql8" name="ql8" autocomplete="off" class="small-lookup-box">
<input type="text" maxlength="3" id="ql9" name="ql9" autocomplete="off" class="small-lookup-box"><br>
<input type="text" maxlength="3" id="ql10" name="ql10" autocomplete="off" class="small-lookup-box">
<input type="text" maxlength="3" id="ql11" name="ql11" autocomplete="off" class="small-lookup-box"><br>
<button onclick="setQl()" class="lookup-button">Apply</button>
<button onclick="clearQl()" class="lookup-button">Defaults</button>
</body>
</html>

45
stat.html Normal file
View File

@ -0,0 +1,45 @@
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OwlBoard - STATS</title>
<script src="./js/stat.js"></script>
</head>
<body style="text-align:center">
<h1>OwlBoard Server Stats</h1>
<h2 id="server_host"></h2>
<p>Counters Reset - <span id="time"></span></p>
<table style="margin:auto;text-align:center;">
<tr>
<th>Resource</th>
<th>Hit Count</th>
</tr>
<tr>
<td>LDBWS</td>
<td id="ldbws"></td>
</tr>
<tr>
<td>LDBSVWS</td>
<td id="ldbsvws"></td>
</tr>
<tr>
<td>DB-CORPUS</td>
<td id="corpus"></td>
</tr>
<tr>
<td>DB-Stations</td>
<td id="stations"></td>
</tr>
<tr>
<td>DB-Users</td>
<td id="users"></td>
</tr>
<tr>
<td>DB-Meta</td>
<td id="meta"></td>
</tr>
</table>
<br><br>
<p>The statistics represent hits & queries on all servers attached to the database.
Multiple servers are served by each database server.</p>
</body>
</html>

351
styles/boards.css Normal file
View File

@ -0,0 +1,351 @@
/* Hide when loading */
.hide-when-loading {
display: none;
}
/* Main Notices: */
.main-notice {
display: none;
margin-top: 150px;
}
.notices-hidden {
display: none;
}
#no_services {
width: 75%;
margin: auto;
margin-top: 110px;
margin-bottom: 30px;
font-size: 20px;
font-weight: 900;
}
/* Fixed Content: */
#header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 50px;
background-color: var(--overlay-color);
color: var(--second-text-color);
}
#station_name {
position: absolute;
max-width: 50%;
left: 7px;
top: 3px;
}
.header-large{
left: 0;
text-align: left;
font-size: 13pt;
margin-top: -2px;
overflow-wrap: anywhere;
text-transform: capitalize;
}
@media (min-width: 380px){
.header-large{
font-size: 13pt;
margin-top: 9px;
white-space: nowrap;
}
}
@media (min-width: 580px){
.header-large{
font-size: 19pt;
margin-top: 5px;
white-space: nowrap;
}
}
.header-small {
text-align: right;
padding-right: 5px;
margin: 3px;
}
/* NRCC Notices */
#alerts{
display: none;
position: fixed;
width: 100%;
left: 0;
top: 0;
}
#alerts_bar{
display: none;
position: absolute;
margin-top:50px;
left: 0;
width: 100%;
height: 40px;
background-color: var(--main-alert-color);
color: var(--second-text-color);
cursor: pointer;
}
#alert_icon{
position: absolute;
left: 10px;
margin-top: 5px;
width: 30px;
height: 30px;
}
#alert_bar_note {
position: relative;
text-align: center;
margin: auto;
margin-top: 8px;
font-weight: 900;
}
#alert_expand_arrow {
position: absolute;
right: 0;
top: 0;
padding: 10px;
padding-right: 15px;
padding-left: 15px;
background: none;
border: none;
font-weight: 900;
color: var(--second-text-color);
transition: transform 0.25s linear;
cursor: pointer;
}
#alerts_msg{
display: none;
position: absolute;
left: 0;
top: 40px;
width: 100%;
background-color: var(--main-alert-color);
background-image: radial-gradient(var(--second-alert-color) 10%,var(--main-alert-color) 70%); /* Undecided whether this actually looks better than plain orange? */
}
#alerts_msg p {
width: 90%;
margin-left: auto;
margin-right: auto;
font-weight: 900;
}
/* Content */
#output {
display: none;
width: 100%;
margin-top: 65px;
}
table {
color: white;
width: 100%;
margin-top: 3px;
font-size: 10.5px;
}
caption{
padding-top: 5px;
padding-bottom: 10px;
font-size: larger;
font-weight: 900;
}
.secondary-table{
margin-top: 25px;
}
.name{
width: 25%;
text-align: left;
}
.detail-name-head {
text-align: left;
}
.detail-name{
text-align: left;
}
.name-item, .name, .plat, .time {
font-size: 12px;
}
.name-item {
color: var(--board-name-color);
cursor: pointer;
}
.plat{
width: 4%;
text-align: center;
}
.time{
width: 11.5%;
text-align: center;
}
.msg{
width: 95%;
font-size: 10px;
margin: 0;
margin-left: 3px;
text-align: left;
color: var(--note-text-color);
}
.close-data {
position: absolute;
right: 19px;
top: -8px;
font-weight: 900;
cursor: pointer;
}
@media (min-width: 800px) {
.detail-name-head, .name-item, .name, .plat, .time, .close-data {
font-size: 16px;
}
.msg {
font-size: 13px
}
}
@media (min-width: 1000px) {
.detail-name-head, .name-item, .name, .plat, .time, .close-data {
font-size: 18px;
}
.msg {
font-size: 14px
}
}
@media (min-width: 1600px) {
.detail-name-head, .name-item, .name, .plat, .time, .close-data {
font-size: 20px;
}
.msg {
font-size: 15px
}
}
.call-data {
display: none;
border-radius: 20px;
width: 93%;
max-height: 75%;
position: fixed;
z-index: 10;
top: 50px;
left: 0;
margin: 2%;
padding-top: 30px;
padding-left: 5px;
padding-right: 5px;
padding-bottom: 10px;
margin-bottom: 25px;
background-color: var(--overlay-color);
overflow: auto;
}
.detail-name {
overflow: hidden;
}
.detail-name:after {
content: "";
display: inline-block;
height: 0.5em;
vertical-align: bottom;
width: 100%;
margin-right: -100%;
margin-left: 30px;
border-top: 1px solid;
}
.call-table {
margin: auto;
width: 90%;
}
.detail-name-here {
color: var(--board-name-color);
}
.detail-table-content {
font-size: 13px;
}
@media (min-width: 800px) {
.detail-table-content {
font-size: 20px;
}
}
@media (min-width: 1000px) {
.detail-table-content {
font-size: 21px;
}
}
@media (min-width: 1600px) {
.detail-table-content {
font-size: 22px;
}
}
.changed{
animation: pulse-change 1.5s linear infinite;
}
.cancelled {
animation: pulse-cancel 1.5s linear infinite;
}
/* Footer: */
#footer {
position: fixed;
bottom: -1px;
left: 0;
width: 100%;
height: 40px;
background-image: linear-gradient(to left, var(--accent-color), azure 190px);
}
#footer img {
height: 25px;
}
#nre_logo {
position: absolute;
left: 15px;
top: 6px;
}
#owlboard_logo {
position: absolute;
right: 60px;
top: 8px;
}
#home_icon {
position: absolute;
width: 10px;
right: 40px;
top: 8px;
}
/* Animations */
@keyframes pulse-change {
50% {
color: var(--main-warning-color);
}
}
@keyframes pulse-cancel {
50% {
color: var(--main-alert-color);
}
}

13
styles/find-code.css Normal file
View File

@ -0,0 +1,13 @@
/*Overrides*/
.titleimg{
padding-bottom: 0px;
}
.small-lookup-box{
width: 25%;
min-width: 75px;
max-width: 125px;
}
#name{
width: 75%;
max-width: 275px;
}

View File

@ -0,0 +1,95 @@
** This license applies only to fonts within the same folder *
Copyright (c) 2012-2013, The Mozilla Corporation and Telefonica S.A.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,55 @@
https://www.tug.org/fonts/lppl-urw.txt
https://lists.dante.de/pipermail/ctan-ann/2009-June/003741.html
From: Jerzy B. Ludwichowski
Subject: URW++ making original 35 fonts available under LPPL
I am forwarding the message below on behalf of Peter Rosenfeld, Managing
Director of URW++, who has kindly agreed to make the basic 35 PostScript
fonts also available under the LPPL. At his request, I'll be
disseminating the information throughout the TeX community.
Many thanks to Dr. Rosenfeld!
Many thanks are also due to Karl Berry, TUG President, for his unswerving
support and advice and Bogus\{}aw Jackowski, lead TeX Gyre developer,
for pushing the issue.
Best,
Jerzy
...............................................................
From: Peter Rosenfeld
Date: Mon, 22 Jun 2009 11:33:29 +0200
Subject: URW++ original 35 fonts available under LPPL
To whom it may concern,
Many years ago, URW++ Design and Development Inc. released their
Type 1 implementations of the basic 35 PostScript fonts under the
GNU General Public License and the Aladdin Ghostscript Free Public
License.
We now additionally release them under the LaTeX Project Public License
(http://www.latex-project.org/lppl), either version 1 or (at your
option) any later version.
Of course, this additional licensing applies to the original URW++
material, not any subsequent changes and additions made by other
parties.
The original font files are widely available, for instance as part of
the Ghostscript 4.00 release, and therefore we are not releasing any new
font files. Those interested can replace the license terms in those
original files accordingly. Responsibility for ensuring that no
material is incorrectly licensed remains with the distributor, as
always.
We hope this additional licensing will make our fonts even more widely
available and usable in the free software community, such as the TeX
Gyre Project.
Sincerely,
Peter Rosenfeld (Managing Director, URW++)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

13
styles/help.css Normal file
View File

@ -0,0 +1,13 @@
/* Glossary */
table, th, td {
border: 1px solid;
border-color: lightgrey;
border-collapse: collapse;
}
#table {
color: lightgrey;
width: 80%;
max-width: 700px;
margin: auto;
}

20
styles/issue.css Normal file
View File

@ -0,0 +1,20 @@
#preflight {
display: none;
border-radius: 20px;
width: 93%;
max-height: 80%;
position: fixed;
z-index: 10;
top: 50px;
left: 0;
margin: 2%;
padding-top: 30px;
padding-left: 5px;
padding-right: 5px;
padding-bottom: 10px;
margin-bottom: 25px;
background-color: var(--overlay-color);
color: var(--second-text-color);
overflow: auto;
}

318
styles/main.css Normal file
View File

@ -0,0 +1,318 @@
/* FONTS */
@font-face {
font-family: 'firamono';
src: url('/styles/fonts/firamono/firamono-regular.woff2') format('woff2'),
url('/styles/fonts/firamono/firamono-regular.woff') format('woff'),
url('/styles/fonts/firamono/firamono-regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'firamono';
src: url('/styles/fonts/firamono/firamono-500.woff2') format('woff2'),
url('/styles/fonts/firamono/firamono-500.woff') format('woff'),
url('/styles/fonts/firamono/firamono-500.ttf') format('truetype');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'urwgothic';
src: url('/styles/fonts/urwgothic/urwgothic.woff2') format('woff2'),
url('/styles/fonts/urwgothic/urwgothic.woff') format('woff'),
url('/styles/fonts/urwgothic/urwgothic.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'urwgothic';
src: url('/styles/fonts/urwgothic/urwgothicDemi.woff2') format('woff2'),
url('/styles/fonts/urwgothic/urwgothicDemi.woff') format('woff'),
url('/styles/fonts/urwgothic/urwgothicDemi.ttf') format('truetype');
font-weight: 900;
font-style: normal;
}
/* COLOR VARS */
:root {
--main-bg-color: #404c55;
--second-bg-color: #2b343c; /* Use as first arg in radial gradient */
--accent-color: #007979;
--overlay-color: #3c6f79de;
--main-text-color: #00b7b7;
--second-text-color: azure;
--note-text-color: #9de7ff;
--link-color: azure;
--box-border-color: ;
--link-visited-color: azure;
--main-alert-color: #ed6d00;
--second-alert-color: #e77f00; /* Use as second arg in radial gradient */
--main-warning-color: orange;
--board-name-color: #fcfc09;
}
/* Loading Box: */
@keyframes spinner {
0% {
transform: translate3d(-50%, -50%, 0) rotate(0deg);
}
100% {
transform: translate3d(-50%, -50%, 0) rotate(360deg);
}
}
.spinner::before {
animation: 1.5s linear infinite spinner;
animation-play-state: inherit;
border: solid 5px var(--overlay-color);
border-bottom-color: var(--second-text-color);
border-radius: 50%;
content: "";
height: 40px;
width: 40px;
position: absolute;
top: 30%;
margin: auto;
transform: translate3d(-50%, -50%, 0);
will-change: transform;
}
#loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin: auto;
background-color: var(--overlay-color);
border-radius: 45px;
padding: 20px;
padding-bottom: 1px;
min-width: 90px;
max-width: 90px;
}
#loading p {
padding-top: 50px;
font-weight: bolder;
overflow-wrap: normal;
}
/* MAIN */
html{
width: 100%;
height: 100%;
}
body {
background-color: var(--main-bg-color);
background-image: radial-gradient(var(--second-bg-color), var(--main-bg-color));
color: var(--main-text-color);
font-family: urwgothic, sans-serif;
text-align: center;
padding-bottom: 60px; /*Footer height*/
}
body a {color:var(--link-color)}
body a:visited {color:var(--link-visited-color)}
.titleimg {
width: 45%;
padding-top: 20px;
padding-bottom: 20px;
width: 200px;
height: 131px;
transition: 0.2s;
}
@media only screen and (min-height: 740px) {.titleimg{width: 250px;height: 164px;}}
.lookup-box {
text-align: center;
border: black;
border-radius: 40px;
padding: 10px;
margin-bottom: 5px;
font-size: 18px;
text-transform: uppercase;
font-family: urwgothic, sans-serif;
transition: 0.2s;
}
.text-entry {
text-align: center;
border: black;
width: 75%;
max-width: 250px;
border-radius: 40px;
padding: 10px;
margin-bottom: 5px;
font-size: 12px;
font-family: urwgothic, sans-serif;
transition: 0.2s;
}
.text-entry-long{
text-align: left;
border: black;
width: 75%;
max-width: 250px;
height: 30%;
max-height: 350px;
border-radius: 20px;
padding: 10px;
margin-bottom: 5px;
font-size: 12px;
font-family: urwgothic, sans-serif;
transition: 0.2s;
}
label {
font-weight: 900;
}
.small-lookup-box {
text-align: center;
border: black;
border-radius: 40px;
padding: 10px;
margin-bottom: 10px;
text-transform: uppercase;
font-family: urwgothic, sans-serif;
transition: 0.2s;
}
.form-text-small {
text-align: center;
border: black;
width: 80%;
border-radius: 5px;
padding: 10px;
font-size: 18px;
transition: 0.2s;
}
@media only screen and (min-width: 600px) {.form-text-small{width: 50%}}
.form-text-large {
text-align: left;
border: black;
width: 80%;
height: 90px;
border-radius: 5px;
padding: 5px;
font-size: 16px;
transition: 0.2s;
}
@media only screen and (min-width: 600px) {.form-text-large{width: 50%}}
.form-info {
color: var(--main-text-color);
font-size: 17px;
font-weight: bolder;
margin-bottom: 4px;
}
.text-description {
display: inline-block;
width: 80%;
font-family: sans-serif;
color: var(--main-text-color);
padding-top: 5px;
padding-bottom: 5px;
margin-left: auto;
margin-right: auto;
transition: 0.2s;
}
@media only screen and (min-width: 600px) {.text-description{width: 50%}}
.lookup-button {
background-color: var(--accent-color);
color: var(--link-color);
border: none;
border-radius: 18px;
font-size: 16px;
font-weight: normal;
font-family: urwgothic, sans-serif;
padding: 5px;
padding-left: 15px;
padding-right: 15px;
margin-bottom: 10px;
cursor: pointer;
}
#quick_links{
width: 75%;
max-width: 300px;
margin: auto;
}
.actionbutton {
display: inline-block;
text-decoration: none;
font-family: firamono, monospace;
font-weight: 400;
cursor: pointer;
background-color: var(--accent-color);
border: none;
border-radius: 10px;
color: var(--link-color);
padding: 3px;
padding-left: 8px;
padding-right: 8px;
margin-left: 5px;
margin-right: 5px;
margin-bottom: 10px;
font-size: 18px;
}
.inlinelink {
text-decoration: underline;
color: var(--link-color);
cursor: pointer;
}
/* START MENU STYLE */
#top_button {
position: absolute;
top: 2px;
right: 5px;
padding: 5px;
}
.sidebar_control {
background-color: transparent;
color: var(--link-color);
border: none;
font-family: sans-serif;
font-size: larger;
cursor: pointer;
}
#sidebar_open_short {display: block;}
#sidebar_close_short {
display: none;
font-size: x-large;
}
#sidebar {
position: fixed;
top: 40px;
right: 0;
margin: auto;
display: block;
max-width: 250px;
width: 0;
border-top-left-radius: 45px;
border-bottom-left-radius: 45px;
background-color: var(--overlay-color);
transition: 0.4s;
}
#sidebar a {
padding: 8px 8px 8px 8px;
margin-top: 10px;
margin-bottom: 10px;
font-family: urwgothic, sans-serif;
font-weight: 300;
text-decoration: none;
text-align: center;
font-size: 25px;
color: var(--link-color);
display: block;
white-space: nowrap;
transition: 0.5s;
}
/* Footer Styles */
footer {
background-color: var(--accent-color);
font-family: firamono, monospace;
font-size: smaller;
color: var(--second-text-color);
width: 100%;
position: fixed;
bottom: -1px;
left: 0;
}
footer a {
text-decoration: underline;
color: var(--link-color);
}
footer a:visited {
color: var(--link-visited-color);
}
footer a:hover {
color: beige;
}

31
styles/settings.css Normal file
View File

@ -0,0 +1,31 @@
.small-lookup-box{
max-width: 100px;
height: 20px;
}
#done {
opacity: 0;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin: auto;
background-color: var(--overlay-color);
border-radius: 45px;
padding: 20px;
padding-bottom: 1px;
min-width: 90px;
max-width: 90px;
transition: opacity 0.25s;
}
#done img {
width: 80px;
height: 80px;
}
#done p {
padding-top: 0px;
font-weight: bolder;
overflow-wrap: normal;
}

82
sw.js Normal file
View File

@ -0,0 +1,82 @@
/* Service Worker */
const appVersion = "0.0.3"
const cacheName = `owlboard-${appVersion}`
const cacheIDs = [cacheName]
const cacheFiles = [
"/404.html",
"/conn-err.html",
"/help.html",
"/",
"/issue.html",
"/find-code.html",
"/settings.html",
"/manifest.json",
"/styles/fonts/firamono/firamono-500.woff2",
"/styles/fonts/firamono/firamono-regular.woff2",
"/styles/fonts/urwgothic/urwgothic.woff2",
"/styles/fonts/urwgothic/urwgothicDemi.woff2",
"/styles/boards.css",
"/styles/find-code.css",
"/styles/help.css",
"/styles/issue.css",
"/styles/main.css",
"/styles/settings.css",
"/js/find-code.js",
"/js/index.js",
"/js/issue.js",
"/js/lib.board.js",
"/js/lib.main.js",
"/js/settings.js",
"/js/simple-board.js",
"/images/icon.svg",
"/images/logo/wide_logo.svg",
"/images/logo/mono-logo.svg",
"images/app-icons/any/plain-logo.svg",
"images/app-icons/any/plain-logo-512.png",
"/images/nav/alert_icon.svg",
"/images/nav/save.svg",
"/images/nav/home_icon.svg",
"/images/nav/back.svg",
"/images/nav/hamburger.svg",
"/images/nav/close.svg",
"/images/nre/nre-powered_400w.webp",
"/images/nre/nre-powered_400w.jxl"
]
self.addEventListener("install", (e) => {
console.log("[Service Worker] Install");
e.waitUntil(
(async () => {
const cache = await caches.open(cacheName);
console.log("[Service Worker] Caching app data");
await cache.addAll(cacheFiles);
})()
);
});
self.addEventListener("fetch", (e) => {
e.respondWith(
(async () => {
const r = await caches.match(e.request);
if (r) {
return r;
}
const response = await fetch(e.request);
console.log(`[Service Worker] Not cached - fetching from server: ${e.request.url}`);
return response;
})()
);
});
self.addEventListener('activate', function (event) {
event.waitUntil(caches.keys().then(function (keys) {
return Promise.all(keys.filter(function (key) {
return !cacheIDs.includes(key);
}).map(function (key) {
return caches.delete(key);
}));
}).then(function () {
return self.clients.claim();
}));
});