+++ /dev/null
-<?php
-/*
-
-XMDV TELESHOPPING GFX INTERFACE
-clock pannel
-
-*/
-#error_reporting(E_ALL);
-#ini_set('display_errors', 'On');
-
-# read the file from the server
-$file = fopen("../clock.json", "r") or die("unable to open file!");
-$data = fread($file, filesize("../clock.json"));
-fclose($file);
-$clock = json_decode($data, true);
-
-# setup the increment for the clock positions
-$clockIncrement = 18; # deg
-
-# if getting a POST request to update the data
-if (!empty($_POST['update'])) {
- # replace required data with the POST data
- $clock['movementSpeed'] = (int)$_POST['movementSpeed'];
- $clock['currentPosition'] = (int)$_POST["doomsday"];
-
- # write the data to the JSON file
- $json = json_encode($clock);
- $file = fopen("../clock.json", "w");
- fwrite($file, $json);
- fclose($file);
-}
-?>
-
-<html>
-<head>
- <title>XMDV</title>
- <style>
-.split {
- display: inline-block;
- width: calc(50% - 20px);
- margin: 10px;
- vertical-align: top;
-}
-/*
-clock styling
- - makes div square
- - positions center pannel in the middle of the clock
- - positions radio buttons around the clock (PHP)
-*/
-/*
-media query to make sure the clock is always displayed well
-*/
-@media (orientation: portrait) {
- :root {
- font-size: 2em;
- }
- input {
- font-size: 1em;
- }
- input[type="submit"] {
- width: 100%;
- aspect-ratio: 4
- }
- .clock {
- position: relative;
- width: 100%;
- aspect-ratio: 1;
- }
- .clockButton {
- position: absolute;
- width: 2em;
- height: 2em;
- margin: 0;
- }
- <?php
- for ($i = 0; $i < 360; $i += $clockIncrement) {
- $radius = 45;
- $x = $radius * cos(deg2rad($i - 90)) + 50;
- $y = $radius * sin(deg2rad($i - 90)) + 50;
- echo ".angle$i {top: calc($y% - 1em); left: calc($x% - 1em);}";
- }
- ?>
-}
-@media (orientation: landscape) {
- .clock {
- position: relative;
- height: 70vh;
- aspect-ratio: 1;
- }
- .clockButton {
- position: absolute;
- width: 1.5em;
- height: 1.5em;
- margin: 0;
- }
- <?php
- for ($i = 0; $i < 360; $i += $clockIncrement) {
- $radius = 45;
- $x = $radius * cos(deg2rad($i - 90)) + 50;
- $y = $radius * sin(deg2rad($i - 90)) + 50;
- echo ".angle$i {top: calc($y% - 0.75em); left: calc($x% - 0.75em);}";
- }
- ?>
-}
-
-
- </style>
-</head>
-<body>
-<form action="./clock.php" method="POST">
-<fieldset>
- <legend>Doomsday clock <em>(Incr. <?php echo $clockIncrement;?> deg)</em></legend>
- <div class=clock>
- <svg class='clockFace' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'><circle r='45' cx='50' cy='50' fill='none' stroke='#a0a0a0' stroke-width='0.15px'/></svg>
-
-<?php
-for ($i = 0; $i < 360; $i += $clockIncrement) {
- $checked = "";
- if ($clock['currentPosition'] == $i) {
- $checked = " checked='checked' style='outline: 2px solid red;'";
- }
- echo "<input type='radio' class='clockButton angle$i' value='$i' name='doomsday'$checked>";
-}
-?>
-
- </div>
- </fieldset>
- <fieldset>
- <legend>Movement</legend>
- <input type='number' name='movementSpeed' id='movementSpeed' value='<?php echo $clock['movementSpeed']; ?>'>
- <label for='movementSpeed'> Movement speed (ms/tick)</label><br>
- <div class="split">
- <input type='radio' value='ease' name='function'>
- <label>Ease function</label>
- </div><div class="split">
- <input type='radio' value='linear' name='function' checked='checked'>
- <label>Linear function</label>
- </div>
- </fieldset>
-<input type="submit" name="update" value="Update">
-</form>
-</body>
-</html>
+++ /dev/null
-#!.venv/bin/python
-
-from pylatex import Command, Document, Section, Tabular, NewPage, NewLine
-from pylatex.utils import NoEscape, bold
-import json
-import os
-
-# clean up working-directory
-for filename in os.listdir("../docs/epub"):
- if filename[:4] == "item": os.remove("../docs/epub/" + filename)
-
-# generate item pages
-with open("../items.json", "r") as f:
- items = json.loads(f.read())
-
-for (i, item) in enumerate(items):
- with open(f"../docs/epub/item{i}.html", "w") as f:
- f.write(f"""
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
- "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>{item['code']}</title>
- </head>
- <body>
- <h2>{item['code']}</h2>
- <p><em>{item['subtext']}</em></p>
- <h4>Description</h4>
- <p>{item['description']}</p>
- <table>
- <tr>
- <th>Rating</th>
- <td>{item['rating']}</td>
- </tr>
- <tr>
- <th>Starting price</th>
- <td>{item['origionalPrice']}</td>
- </tr>
- <tr>
- <th>In stock</th>
- <td>{item['stockCount']}</td>
- </tr>
- </table>
- <h4>Anchor notes:</h4>
- <p>{item['notes']}</p>
- <h4>Crew notes:</h4>
- <p>{item['crew_notes']}</p>
- </body>
- </html>
- """)
-
-# update manifest
-manifest_items = " ".join([
- f"<item id='item{i}' href='item{i}.html' media-type='application/xhtml+xml' />"
- for i in range(len(items))
- ])
-toc_items = " ".join([
- f"<itemref idref='item{i}' />"
- for i in range(len(items))
- ])
-
-with open("../docs/epub/content.opf", "w") as f:
- f.write(f"""
-<?xml version="1.0"?>
-
-<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="dcidid"
- version="2.0">
-
-<metadata xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:dcterms="http://purl.org/dc/terms/"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:opf="http://www.idpf.org/2007/opf">
-
- <dc:title>XMDV Teleshopping item manifest</dc:title>
- <dc:language xsi:type="dcterms:RFC3066">en</dc:language>
- <dc:identifier id="dcidid" opf:scheme="URI">
- http://data.ozva.co.uk/shopping/docs/item_manifest.epub
- </dc:identifier>
- <dc:subject>Teleshopping, Manifest</dc:subject>
- <dc:description>Describing all items to be sold during the XMDV Teleshopping performance</dc:description>
- <dc:creator>William Greenwood</dc:creator>
- <dc:publisher>Goodnight Publishing</dc:publisher>
- <dc:date xsi:type="dcterms:W3CDTF">2024-12-28</dc:date>
- <dc:rights>Creative Commons BY-SA 3.0 License.</dc:rights>
-</metadata>
-
-<manifest>
- <item id="ncx" href="toc.ncx"
- media-type="application/x-dtbncx+xml" />
- <item id="css" href="stylesheet.css"
- media-type="text/css" />
- <item id="title" href="title_page.html"
- media-type="application/xhtml+xml" />
- <item id="intro" href="info_page.html"
- media-type="application/xhtml+xml" />
- {manifest_items}
-</manifest>
-
-<spine toc="ncx">
- <itemref idref="title" />
- <itemref idref="intro" />
- {toc_items}
-</spine>
-
-</package>
-""")
-
-toc_items = " ".join([
- f"""
-<navPoint id="navPoint-{i+3}" playOrder="{i+3}">
- <navLabel>
- <text>{i+1}: {item["code"]}</text>
- </navLabel>
- <content src="item{i}.html"/>
-</navPoint>
- """
- for (i, item) in enumerate(items)
- ])
-
-with open("../docs/epub/toc.ncx", "w") as f:
- f.write(f"""
-<?xml version="1.0"?>
-<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN"
- "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
-
-<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
-
-<head>
- <meta name="dtb:uid" content="http://data.ozva.co.uk/shopping/docs/item_manifest.epub"/>
- <meta name="dtb:depth" content="2"/>
- <meta name="dtb:totalPageCount" content="0"/>
- <meta name="dtb:maxPageNumber" content="0"/>
-</head>
-
-<docTitle>
- <text>XMDV Teleshopping item manifest</text>
-</docTitle>
-
-<navMap>
- <navPoint id="navPoint-1" playOrder="1">
- <navLabel>
- <text>Title Page</text>
- </navLabel>
- <content src="title_page.html"/>
- </navPoint>
- <navPoint id="navPoint-2" playOrder="2">
- <navLabel>
- <text>Information</text>
- </navLabel>
- <content src="info_page.html"/>
- </navPoint>
- {toc_items}
-</navMap>
-
-</ncx>
-""")
-
-os.system("""
- zip -X0 ../docs/item_manifest.epub ../docs/epub/mimetype &&
- zip -Xur9D ../docs/item_manifest.epub ../docs/epub/* &&
- ebook-convert ../docs/item_manifest.epub ../docs/item_manifest.mobi
- """)
-
-os.system(f"pandoc -f html ../docs/epub/info_page.html -t latex -o ../docs/pdf/info.tex")
-for i in range(len(items)):
- os.system(f"pandoc -f html ../docs/epub/item{i}.html -t latex -o ../docs/pdf/item{i}.tex")
-
-geometry_options = {
- "tmargin": "3cm",
- "bmargin": "2cm",
- "lmargin": "1.5cm",
- "rmargin": "1.5cm"
- }
-doc = Document(
- "XMDV Teleshopping item manifest", geometry_options=geometry_options,
- textcomp = True
- )
-doc.preamble.append(NoEscape(r"\usepackage{longtable}"))
-doc.preamble.append(NoEscape(r"\usepackage{booktabs}"))
-doc.preamble.append(NoEscape(r"\usepackage{hyperref}"))
-doc.preamble.append(NoEscape(r"\usepackage{xcolor}"))
-
-doc.preamble.append(NoEscape(r"\def\tightlist{}"))
-
-doc.preamble.append(Command("title", "XMDV Teleshopping item manifest"))
-doc.preamble.append(Command("author", "William Greenwood"))
-doc.preamble.append(Command("date", NoEscape(r"\today")))
-
-doc.append(NoEscape(r"""
-\maketitle
-\begin{center}
-\textcolor{red}{\large{}Contains HIDDEN INFORMATION. NOT to be shared with anchor before shoot.}
-\end{center}
-\tableofcontents
-\newpage
- """))
-
-with open(f"../docs/pdf/info.tex", "r") as f:
- doc.append(NoEscape(f.read().encode('latin-1', 'ignore').decode('utf-8')))
-
-doc.append(NoEscape(r"""
- \vspace*{\fill}
- \section{Items}
- \newpage
- """))
-
-for i in range(len(items)):
- with open(f"../docs/pdf/item{i}.tex", "r") as f:
- doc.append(NoEscape(f.read().encode('latin-1', 'ignore').decode('utf-8')))
-
- doc.append(NoEscape(r"\newpage"))
-
-doc.generate_tex("../docs/pdf/item_manifest")
-doc.generate_pdf("../docs/item_manifest", silent=False)
+++ /dev/null
-<?php
-error_reporting(E_ALL);
-ini_set('display_errors', 'On');
-
-$file = fopen("../items.json", "r") or die("unable to open file!");
-$items = fread($file, filesize("../items.json"));
-fclose($file);
-$items = json_decode($items, true);
-
-$file = fopen("../text.json", "r") or die("unable to open file!");
-$text = fread($file, filesize("../text.json"));
-fclose($file);
-$text = json_decode($text, true);
-
-$file = fopen("../data.json", "r") or die("unable to open file!");
-$data = fread($file, filesize("../data.json"));
-fclose($file);
-$data = json_decode($data, true);
-
-$file = fopen("../note", "r") or die("unable to open file!");
-$note = fread($file, filesize("../note"));
-fclose($file);
-
-$clockIncrement = 18;
-
-if (!empty($_POST['generate_docs'])) {
- exec("./generate_docs.py");
-}
-
-if (!empty($_POST['update'])) {
-
- $data['showingMain'] = var_export(!empty($_POST['main']), true);
- $data['showingAll'] = var_export(!empty($_POST['all']), true);
- $data['showingExtra'] = var_export(!empty($_POST['extra']), true);
- $data['showingTimer1'] = var_export(!empty($_POST['timer1']), true);
- $data['showingTimer2'] = var_export(!empty($_POST['timer2']), true);
- $data['showingTimer3'] = var_export(!empty($_POST['timer3']), true);
- $data['timer3Main'] = var_export(!empty($_POST['timer3Main']), true);
- $data['showingBanner'] = var_export(!empty($_POST['banner']), true);
- $data['showingSigil'] = [
- var_export(!empty($_POST['sigilNE']), true),
- var_export(!empty($_POST['sigilSE']), true),
- var_export(!empty($_POST['sigilSW']), true),
- var_export(!empty($_POST['sigilNW']), true)
- ];
- $data['round'] = var_export(!empty($_POST['round']), true);
-
- if (!empty($_POST['allChange'])) {
- if ($_POST['allChange'] == "on") {
- $data['showingSigil'] = ["true", "true", "true", "true"];
- }
- else if ($_POST['allChange'] == "off") {
- $data['showingSigil'] = ["false", "false", "false", "false"];
- }
- }
-
- $data['timerOffset'] = (int)$_POST['timerOffset'];
- $data['priceChange'] = (int)$_POST['priceChange'];
- $data['discount'] = (int)$_POST['discount'];
- $data['percentLeft'] = (int)$_POST['percentLeft'];
- $data['itemId'] = (int)$_POST['item'];
- $data['prefix'] = $_POST['prefix'];
-
- $data['currency']['prefix'] = $_POST['prefixString'];
- $data['currency']['postfix'] = $_POST['postfixString'];
-
- $getTopText = function ($x) use ($text) {return $text['topText'][(int)$x];};
- $getBottomText = function ($x) use ($text) {return $text['bottomText'][(int)$x];};
-
- if (!empty($_POST['topText'])) {
- $data['topText'] = array_map($getTopText, $_POST['topText']);
- } else {
- $data['topText'] = [];
- }
- if (!empty($_POST['bottomText'])) {
- $data['bottomText'] = array_map($getBottomText, $_POST['bottomText']);
-
- } else {
- $data['bottomText'] = [];
- }
-
- $data['bannerText'] = $text['bannerText'][(int)$_POST['bannerText']];
-
- if (!empty($_POST['addTime1'])) {
- $minutes = (int)substr($_POST['addTime1'], 0, 2);
- $seconds = (int)substr($_POST['addTime1'], 2, 4) + ($minutes * 60);
- $data['timer1End'] = time() + $seconds;
- }
- if (!empty($_POST['addTime2'])) {
- $minutes = (int)substr($_POST['addTime2'], 0, 2);
- $seconds = (int)substr($_POST['addTime2'], 2, 4) + ($minutes * 60);
- $data['timer2End'] = time() + $seconds;
- }
- if (!empty($_POST['addTime3'])) {
- $minutes = (int)substr($_POST['addTime3'], 0, 2);
- $seconds = (int)substr($_POST['addTime3'], 2, 4) + ($minutes * 60);
- $data['timer3End'] = time() + $seconds;
- }
-
- if (!empty($_POST['notes'])) {
- $note = $_POST['notes'];
- } else {
- $note = "";
- }
-
- $file = fopen("../note", "w");
- fwrite($file, $note);
- fclose($file);
-
- $json = json_encode($data);
- $file = fopen("../data.json", "w");
- fwrite($file, $json);
- fclose($file);
-}
-?>
-<html>
- <head>
- <style>
-h1 {
- margin: 0;
-}
-.split {
- display: inline-block;
- width: calc(50% - 20px);
- margin: 10px;
- vertical-align: top;
-}
-.third {
- display: inline-block;
- width: calc((100% / 3) - 20px);
- margin: 10px;
- vertical-align: top;
-}
-.entry {
- width: 50%;
- margin: 0;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-fieldtext > .split {
- width: 50%;
- margin: 0;
-}
-.clock {
- position: relative;
- width: 100%;
- aspect-ratio: 1;
-}
-.clock > div {
- position: absolute;
- top: 25%;
- left: 25%;
- width: 50%;
- height: 50%;
- overflow: scroll;
-}
-.clockButton {
- position: absolute;
- margin: 0;
-}
-<?php
-for ($i = 0; $i < 360; $i += $clockIncrement) {
- $radius = 45;
- $x = $radius * cos(deg2rad($i - 90)) + 50;
- $y = $radius * sin(deg2rad($i - 90)) + 50;
- echo ".angle$i {top: calc($y% - 7px); left: calc($x% - 7px);}";
-}
-?>
-@media screen and (max-width: 1130px) {
- .split {
- width: calc(100% - 20px);
- }
-}
- </style>
- </head>
- <body>
- <form action="./index.php" method="POST">
- <div class="third">
- <div class="split entry">
- <h1>XMDV Interface</h1>
- </div><div class="split entry">
- <p style="margin: 0; text-align: right;">
- XMDV Shopping is created by William Greenwood<br>
- <a href="../">GFX Overlay</a> - <a href="../autocue/">Autocue</a> - <a href="https://git.ozva.co.uk/?p=shopping-channel">Source repository</a>
- </p>
- </div>
- <fieldset>
- <legend>Products</legend>
- <div class='split'>
-<?php
-for ($i = 0; $i < count($items); $i++) {
- $subtext = $items[$i]["subtext"];
- $checked = "";
-
- if ($data['itemId'] == $i) {
- $checked = " checked='checked' ";
- }
-
- echo "<input type='radio' id='$i' value='$i' name='item'$checked>";
- echo "<label for='$i'> $subtext</label><br>";
-}
-$checkedExtra = "";
-if ($data['showingExtra'] == "true") {
- $checkedExtra = " checked='checked' ";
-}
-$checkedMain = "";
-if ($data['showingMain'] == "true") {
- $checkedMain = " checked='checked' ";
-}
-$checkedAll = "";
-if ($data['showingAll'] == "true") {
- $checkedAll = " checked='checked' ";
-}
-?>
- </div><div class='split'>
- <input type='checkbox' id='all' name='all' value='true'<?php echo $checkedAll; ?>>
- <label for='all'> Showing all components</label><br>
- <input type='checkbox' id='main' name='main' value='true'<?php echo $checkedMain; ?>>
- <label for='main'> Showing product information</label><br>
- <input type='checkbox' id='extra' name='extra' value='true'<?php echo $checkedExtra; ?>>
- <label for='extra'> Showing extra information</label>
- </div>
- </fieldset>
-
- <fieldset>
- <legend>Pricing</legend>
- <div class='split'>
-
-<?php
-$preChecked = "";
-$postChecked = "";
-if ($data['prefix'] == "true") {
- $preChecked = " checked='checked' ";
-} else {
- $postChecked = " checked='checked' ";
-}
-$prefix = $data['currency']['prefix'];
-$postfix = $data['currency']['postfix'];
-
-$checked = "";
-if ($data['round'] == "true") {
- $checked = " checked='checked' ";
-}
-?>
-
- <input type='radio' id='prefix' name='prefix' value='true'<?php echo $preChecked; ?>>
- <input type='text' id='prefix' name='prefixString' value='<?php echo $prefix; ?>'>
- <label for='prefix'> Prefix</label><br>
-
- <input type='radio' id='postfix' name='prefix' value='false'<?php echo $postChecked; ?>>
- <input type='text' id='postfix' name='postfixString' value='<?php echo $postfix; ?>'>
- <label for='postfix'> Postfix</label><br><br>
-
- <input type='number' name='percentLeft' id='percentLeft' value='<?php echo $data['percentLeft']; ?>'>
- <label for='percentLeft'> Unsold (%)</label>
-
- </div><div class='split'>
-
- <input type='number' name='discount' id='discount' value='<?php echo $data['discount']; ?>'>
- <label for='discount'> Price of origional (%)</label><br>
-
- <input type='number' name='priceChange' id='priceChange' value='<?php echo $data['priceChange']; ?>'>
- <label for='priceChange'> Discount change (%/s)</label><br>
-
- <input type='checkbox' id='round' name='round' value='true'<?php echo $checked; ?>>
- <label for='round'> Round prices</label>
- </div>
- </fieldset>
-
- <fieldset>
- <legend>Top text</legend>
-<?php
-for ($i = 0; $i < count($text['topText']); $i++) {
- $line = $text['topText'][$i];
- $checked = "";
- if (in_array($line, $data['topText'])) {
- $checked = " checked='checked' ";
- }
- echo "<div class='split entry' style='margin: 0;'><input type='checkbox' id='topText' value='$i' name='topText[]'$checked>";
- echo "<label for='topText'> $line</label></div>";
-}
-echo "</fieldset>";
-echo "<fieldset><legend>Bottom text</legend>";
-for ($i = 0; $i < count($text['bottomText']); $i++) {
- $line = $text['bottomText'][$i];
- $checked = "";
- if (in_array($line, $data['bottomText'])) {
- $checked = " checked='checked' ";
- }
- echo "<div class='split entry'><input type='checkbox' id='bottomText' value='$i' name='bottomText[]'$checked>";
- echo "<label for='bottomText'> $line</label></div>";
-}
-echo "</fieldset>";
-
-echo "<fieldset><legend>Notes</legend>";
-echo "<textarea name='notes' rows='10' style='width: 100%;'>";
-echo $note;
-echo "</textarea>";
-echo "</fieldset>";
-
-echo "</div><div class='third'>";
-echo "<fieldset><legend>Banner text</legend>";
-for ($i = 0; $i < count($text['bannerText']); $i++) {
- $line = $text['bannerText'][$i];
- $checked = "";
- if ($data['bannerText'] == $line) {
- $checked = " checked='checked' ";
- }
- echo "<div class='split entry' style='margin: 0;'><input type='radio' id='$i' value='$i' name='bannerText'$checked>";
- echo "<label for='$i'> $line</label></div>";
-}
-$checked = "";
-if ($data['showingBanner'] == "true") {
- $checked = " checked='checked' ";
-}
-?>
- <input type='checkbox' id='banner' name='banner' value='true'<?php echo $checked; ?>>
- <label for='banner'>Show banner</label>
- </fieldset>
-
- <fieldset>
- <legend>Sigil display</legend>
- <div class='split'>
-<?php
-$checked = "";
-if ($data['showingSigil'][3] == "true") {
- $checked = " checked='checked' ";
-}
-echo "<input type='checkbox' id='sigilNW' name='sigilNW' value='true'$checked>";
-echo "<label for='sigilNW'>Top left (NW)</label><br>";
-$checked = "";
-if ($data['showingSigil'][2] == "true") {
- $checked = " checked='checked' ";
-}
-echo "<input type='checkbox' id='sigilSW' name='sigilSW' value='true'$checked>";
-echo "<label for='sigilSW'>Bottom left (SW)</label><br>";
-
-echo "<input type='radio' id='allOn' value='on' name='allChange'>";
-echo "<label for='allOn'>All on</label><br>";
-
-echo "</div><div class='split'>";
-$checked = "";
-if ($data['showingSigil'][0] == "true") {
- $checked = " checked='checked' ";
-}
-echo "<input type='checkbox' id='sigilNE' name='sigilNE' value='true'$checked>";
-echo "<label for='sigilNE'>Top right (NE)</label><br>";
-$checked = "";
-if ($data['showingSigil'][1] == "true") {
- $checked = " checked='checked' ";
-}
-echo "<input type='checkbox' id='sigilSE' name='sigilSE' value='true'$checked>";
-echo "<label for='sigilSW'>Bottom right (SE)</label><br>";
-
-echo "<input type='radio' id='allOff' value='off' name='allChange'>";
-echo "<label for='allOff'>All off</label><br>";
-?>
- </div>
- </fieldset>
- <fieldset>
- <legend>Timer</legend>
-<?php
-$timer1Checked = "";
-if ($data['showingTimer1'] == "true") {
- $timer1Checked = " checked='checked' ";
-}
-$timer2Checked = "";
-if ($data['showingTimer2'] == "true") {
- $timer2Checked = " checked='checked' ";
-}
-$timer3Checked = "";
-if ($data['showingTimer3'] == "true") {
- $timer3Checked = " checked='checked' ";
-}
-$timer3MainChecked = "";
-if ($data['timer3Main'] == "true") {
- $timer3MainChecked = " checked='checked' ";
-}
-$timerOffset = $data["timerOffset"];
-?>
- <div class="third">
- <input type='radio' id='0000' value='0000' name='addTime1'>
- <label for='0000'> Reset</label><br>
- <input type='radio' id='0030' value='0030' name='addTime1'>
- <label for='0030'> +00:30</label><br>
- <input type='radio' id='0100' value='0100' name='addTime1'>
- <label for='0100'> +01:00</label><br>
- <input type='radio' id='0200' value='0200' name='addTime1'>
- <label for='0200'> +02:00</label><br>
- <input type='radio' id='0500' value='0500' name='addTime1'>
- <label for='0500'> +05:00</label><br>
- <input type='radio' id='1000' value='1000' name='addTime1'>
- <label for='1000'> +10:00</label><br>
- <input type='checkbox' id='timer1' name='timer1' value='true'<?php echo $timer1Checked; ?>>
- <label for='timer1'> Showing timer</label>
- </div><div class="third">
- <input type='radio' id='0000' value='0000' name='addTime2'>
- <label for='0000'> Reset</label><br>
- <input type='radio' id='0030' value='0030' name='addTime2'>
- <label for='0030'> +00:30</label><br>
- <input type='radio' id='0100' value='0100' name='addTime2'>
- <label for='0100'> +01:00</label><br>
- <input type='radio' id='0200' value='0200' name='addTime2'>
- <label for='0200'> +02:00</label><br>
- <input type='radio' id='0500' value='0500' name='addTime2'>
- <label for='0500'> +05:00</label><br>
- <input type='radio' id='1000' value='1000' name='addTime2'>
- <label for='1000'> +10:00</label><br>
- <input type='checkbox' id='timer2' name='timer2' value='true'<?php echo $timer2Checked; ?>>
- <label for='timer2'> Showing timer</label>
- </div><div class="third">
- <input type='radio' id='0000' value='0000' name='addTime3'>
- <label for='0000'> Reset</label><br>
- <input type='radio' id='0030' value='0030' name='addTime3'>
- <label for='0030'> +00:30</label><br>
- <input type='radio' id='0100' value='0100' name='addTime3'>
- <label for='0100'> +01:00</label><br>
- <input type='radio' id='0200' value='0200' name='addTime3'>
- <label for='0200'> +02:00</label><br>
- <input type='radio' id='0500' value='0500' name='addTime3'>
- <label for='0500'> +05:00</label><br>
- <input type='radio' id='1000' value='1000' name='addTime3'>
- <label for='1000'> +10:00</label><br>
- <input type='checkbox' id='timer3' name='timer3' value='true'<?php echo $timer3Checked; ?>>
- <label for='timer3'> Showing timer</label>
- </div>
- <div class="split">
- <input type='checkbox' id='timer3Main' name='timer3Main' value='true'<?php echo $timer3MainChecked; ?>>
- <label for='timer3Main'> Timer 3 focus</label>
- </div><div class="split">
- <input type='number' id='timerOffset' name='timerOffset' min='-5000' max='5000' step='1' value='<?php echo $timerOffset; ?>'>
- <label for='timerOffset'> Offset (ms)</label>
- </div>
- </fieldset>
- <fieldset>
- <legend>Lighting</legend>
- <div class="split">
-<?php
-for ($i = 0; $i < 5; $i ++) {
- $checked = "";
- if ($clock['lightingCues'][$i] == "true") {
- $checked = " checked='checked'";
- }
- echo "<input type='radio' value='$i' name='sceneCue'$checked>";
- echo "<label>Scene $i</label><br>";
-}
-?>
- </div><div class="split">
-<?php
-for ($i = 0; $i < 5; $i ++) {
- $checked = "";
- if ($clock['lightingCues'][$i + 5] == "true") {
- $checked = " checked='checked'";
- }
- echo "<input type='checkbox' value='true' name='triggerCue$i'$checked>";
- echo "<label>Trigger $i</label><br>";
-}
-?>
- </div>
- </fieldset>
- </div><div class="third">
-<?php
-echo "<fieldset><legend>Doomsday clock <em>(Incr. $clockIncrement deg)</em></legend>";
-echo "<div class=clock>";
-echo "<svg class='clockFace' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'><circle r='45' cx='50' cy='50' fill='none' stroke='#a0a0a0' stroke-width='0.15px'/></svg>";
-for ($i = 0; $i < 360; $i += $clockIncrement) {
- $checked = "";
- if ($clock['currentPosition'] == $i) {
- $checked = " checked='checked' style='outline: 2px solid red;'";
- }
- echo "<input type='radio' class='clockButton angle$i' value='$i' name='doomsday'$checked>";
-}
-?>
- <div>
- <fieldset>
- <legend>Movement</legend>
- <input type='number' name='movementSpeed' id='movementSpeed' value='<?php echo $clock['movementSpeed']; ?>'>
- <label for='movementSpeed'> Movement speed (ms/tick)</label><br>
- <div class="split">
- <input type='radio' value='ease' name='function'>
- <label>Ease function</label>
- </div><div class="split">
- <input type='radio' value='linear' name='function' checked='checked'>
- <label>Linear function</label>
- </div>
- </fieldset>
- </div>
- </div>
- </fieldset>
- <br>
- <input style="vertical-align: top;" type="submit" name="update" value="Update">
- <input style="vertical-align: top;" type="submit" name="generate_docs" value="Generate docs">
- <br>
- <details style="display: inline-block;">
- <summary>Current data</summary>
-<?php echo "<pre>" . var_export($data, true) . "</pre>" ?>
-<?php echo "<pre>" . var_export($clock, true) . "</pre>" ?>
- </details>
- </div>
- </form>
- </body>
-</html>
+++ /dev/null
-<hmtl>
- <head>
- <style>
-body {
- font-family: sans-serif;
- color: white;
- background-color: black;
-}
-#slider {
- position: fixed;
- top: 10px;
- left: 10px;
-}
-#notes, #message {
- margin: 20px;
-}
-#timer {
- position: absolute;
- bottom: 0;
- left: 0;
- margin: 20px;
-}
-#clock {
- position: absolute;
- bottom: 0;
- right: 0;
- width: 250px;
- height: 250px;
- margin: 10px;
-}
-#arrow {
- transition: transform 1.5s;
-}
- </style>
- <script>
-var itemId = 0;
-var unsold = 1;
-
-function makeTime(t, o, s) {
- let current = Math.round((Date.now() + o) / 1000);
- var time = t - current;
- if (Math.sign(time) == -1) {time = 0;}
- var minutes = Math.floor(time / 60);
- var seconds = (time - (minutes * 60));
-
- var minutesString = minutes.toString();
- var secondsString = seconds.toString();
- minutesString = minutesString.padStart(2, "0");
- secondsString = secondsString.padStart(2, "0");
- if (s == "true") {
- return `${minutesString}:${secondsString}`;
- } else {
- return `<s>${minutesString}:${secondsString}</s>`;
- }
-}
-
-function frame() {
- var slider = document.getElementById("slider");
- var notes = document.getElementById("notes");
- var message = document.getElementById("message");
- var timers = document.getElementById("timer");
-
- notes.style.fontSize = `${slider.value}px`;
- message.style.fontSize = `${slider.value}px`;
- timers.style.fontSize = `${slider.value}px`;
-}
-
-function update() {
- fetch("../data.json", {cache: "no-store"})
- .then(data => data.json())
- .then(data => {
- itemId = data.itemId;
- var timers = document.getElementsByClassName("time");
- timers[0].innerHTML = makeTime(data.timer1End, data.timerOffset, data.showingTimer1);
- timers[1].innerHTML = makeTime(data.timer2End, data.timerOffset, data.showingTimer2);
- timers[2].innerHTML = makeTime(data.timer3End, data.timerOffset, data.showingTimer3);
- unsold = data.percentLeft / 100;
- })
- fetch("../items.json", {cache: "no-store"})
- .then(data => data.json())
- .then(data => {
- const noteContainer = document.getElementById("notes");
- noteContainer.innerHTML = data[itemId].notes;
-
- const left = document.getElementById("left");
- const stock = document.getElementById("stock");
- left.innerHTML = Math.round(data[itemId].stockCount * unsold);
- stock.innerHTML = data[itemId].stockCount;
-
- })
- fetch("../clock.json", {cache: "no-store"})
- .then(data => data.json())
- .then(data => {
- document.getElementById("arrow").style.transform = `rotate(${data.currentPosition}deg)`;
-
- })
- fetch("../note", {cache: "no-store"})
- .then(data => data.text())
- .then(data => {
- const messageContainer = document.getElementById("message");
- messageContainer.innerHTML = data.replaceAll("\n", "<br>");
- })
-}
-
-setInterval(frame, 50);
-setInterval(update, 1000);
- </script>
- </head>
- <body onload="frame(); update();">
- <input type="range" min="30" max="100" value="45" id="slider">
- <div style="font-size: 50px;" id="notes">some test text!</div>
- <div style="font-size: 50px; color: yellow;" id="message">Producer Notes!</div>
- <div id="timer">
- T1: <span style="background-color: red;" class="time"></span> -
- T2: <span style="background-color: green;" class="time"></span> -
- T3: <span style="background-color: blue; margin-right: 40px;" class="time"></span>
- Left: <span id="left"></span>/<span id="stock"></span>
- </div>
- <div id="clock">
- <img style="width: 100%; margin-right: -100%;" src="../assets/clock.svg"><img id="arrow" style="width: 100%;" src="../assets/arrow.svg">
- </div>
- </body>
-</html>
--- /dev/null
+#!.venv/bin/python
+
+from pylatex import Command, Document, Section, Tabular, NewPage, NewLine
+from pylatex.utils import NoEscape, bold
+import json
+import os
+
+# clean up working-directory
+for filename in os.listdir("../docs/epub"):
+ if filename[:4] == "item": os.remove("../docs/epub/" + filename)
+
+# generate item pages
+with open("../items.json", "r") as f:
+ items = json.loads(f.read())
+
+for (i, item) in enumerate(items):
+ with open(f"../docs/epub/item{i}.html", "w") as f:
+ f.write(f"""
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>{item['code']}</title>
+ </head>
+ <body>
+ <h2>{item['code']}</h2>
+ <p><em>{item['subtext']}</em></p>
+ <h4>Description</h4>
+ <p>{item['description']}</p>
+ <table>
+ <tr>
+ <th>Rating</th>
+ <td>{item['rating']}</td>
+ </tr>
+ <tr>
+ <th>Starting price</th>
+ <td>{item['origionalPrice']}</td>
+ </tr>
+ <tr>
+ <th>In stock</th>
+ <td>{item['stockCount']}</td>
+ </tr>
+ </table>
+ <h4>Anchor notes:</h4>
+ <p>{item['notes']}</p>
+ <h4>Crew notes:</h4>
+ <p>{item['crew_notes']}</p>
+ </body>
+ </html>
+ """)
+
+# update manifest
+manifest_items = " ".join([
+ f"<item id='item{i}' href='item{i}.html' media-type='application/xhtml+xml' />"
+ for i in range(len(items))
+ ])
+toc_items = " ".join([
+ f"<itemref idref='item{i}' />"
+ for i in range(len(items))
+ ])
+
+with open("../docs/epub/content.opf", "w") as f:
+ f.write(f"""
+<?xml version="1.0"?>
+
+<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="dcidid"
+ version="2.0">
+
+<metadata xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:dcterms="http://purl.org/dc/terms/"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:opf="http://www.idpf.org/2007/opf">
+
+ <dc:title>XMDV Teleshopping item manifest</dc:title>
+ <dc:language xsi:type="dcterms:RFC3066">en</dc:language>
+ <dc:identifier id="dcidid" opf:scheme="URI">
+ http://data.ozva.co.uk/shopping/docs/item_manifest.epub
+ </dc:identifier>
+ <dc:subject>Teleshopping, Manifest</dc:subject>
+ <dc:description>Describing all items to be sold during the XMDV Teleshopping performance</dc:description>
+ <dc:creator>William Greenwood</dc:creator>
+ <dc:publisher>Goodnight Publishing</dc:publisher>
+ <dc:date xsi:type="dcterms:W3CDTF">2024-12-28</dc:date>
+ <dc:rights>Creative Commons BY-SA 3.0 License.</dc:rights>
+</metadata>
+
+<manifest>
+ <item id="ncx" href="toc.ncx"
+ media-type="application/x-dtbncx+xml" />
+ <item id="css" href="stylesheet.css"
+ media-type="text/css" />
+ <item id="title" href="title_page.html"
+ media-type="application/xhtml+xml" />
+ <item id="intro" href="info_page.html"
+ media-type="application/xhtml+xml" />
+ {manifest_items}
+</manifest>
+
+<spine toc="ncx">
+ <itemref idref="title" />
+ <itemref idref="intro" />
+ {toc_items}
+</spine>
+
+</package>
+""")
+
+toc_items = " ".join([
+ f"""
+<navPoint id="navPoint-{i+3}" playOrder="{i+3}">
+ <navLabel>
+ <text>{i+1}: {item["code"]}</text>
+ </navLabel>
+ <content src="item{i}.html"/>
+</navPoint>
+ """
+ for (i, item) in enumerate(items)
+ ])
+
+with open("../docs/epub/toc.ncx", "w") as f:
+ f.write(f"""
+<?xml version="1.0"?>
+<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN"
+ "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
+
+<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
+
+<head>
+ <meta name="dtb:uid" content="http://data.ozva.co.uk/shopping/docs/item_manifest.epub"/>
+ <meta name="dtb:depth" content="2"/>
+ <meta name="dtb:totalPageCount" content="0"/>
+ <meta name="dtb:maxPageNumber" content="0"/>
+</head>
+
+<docTitle>
+ <text>XMDV Teleshopping item manifest</text>
+</docTitle>
+
+<navMap>
+ <navPoint id="navPoint-1" playOrder="1">
+ <navLabel>
+ <text>Title Page</text>
+ </navLabel>
+ <content src="title_page.html"/>
+ </navPoint>
+ <navPoint id="navPoint-2" playOrder="2">
+ <navLabel>
+ <text>Information</text>
+ </navLabel>
+ <content src="info_page.html"/>
+ </navPoint>
+ {toc_items}
+</navMap>
+
+</ncx>
+""")
+
+os.system("""
+ zip -X0 ../docs/item_manifest.epub ../docs/epub/mimetype &&
+ zip -Xur9D ../docs/item_manifest.epub ../docs/epub/* &&
+ ebook-convert ../docs/item_manifest.epub ../docs/item_manifest.mobi
+ """)
--- /dev/null
+flask_httpauth
+flask
+werkzeug
CREATE TABLE state (
id INTEGER PRIMARY KEY,
item_id INTEGER,
- discount REAL,
+ discount_1 REAL,
+ discount_2 REAL,
+ discount_3 REAL,
+ discount_4 REAL,
discount_change INTEGER,
percent_remaining INTEGER,
currency_symbol TEXT,
movement_function TEXT,
note TEXT
);
+
INSERT INTO state (
id,
item_id,
- discount,
+ discount_1,
+ discount_2,
+ discount_3,
+ discount_4,
discount_change,
percent_remaining,
currency_symbol,
current_position,
movement_speed,
movement_function,
- note) VALUES (1, 0, 0.0, 50, 100, '£', 0, '[0,1]', 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 40, 'linear', 'test note');
+ note) VALUES (1, 0, 0.0, 0.0, 0.0, 0.0, 50, 100, '£', 0, '[0,1]', 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 40, 'linear', 'test note');
#!.venv/bin/python
+from werkzeug.security import generate_password_hash, check_password_hash
from flask import Flask, Response, request, render_template, jsonify
+from flask_httpauth import HTTPBasicAuth
+from datetime import datetime, timezone
+from math import radians, cos, sin
+from ast import literal_eval
from os import path
import sqlite3
import json
-from ast import literal_eval
+
+INCREMENT = 18
app = Flask(__name__)
+auth = HTTPBasicAuth()
+
+# get the secrets ready
+with open(path.join(app.root_path, "..", "secrets.json"), "r") as f:
+ users = json.loads(f.read())
+users = {k: generate_password_hash(v) for (k, v) in users.items()}
+
+# get the static data ready
+with open(path.join(app.root_path, "static", "static.json"), "r") as f:
+ static_data = json.loads(f.read())
+
+
+
+# make the password verifier function
+@auth.verify_password
+def verify_password(username, password):
+ if username in users and \
+ check_password_hash(users.get(username), password):
+ return username
+
+# database helper function
def database(field: str | list):
query = f"SELECT {','.join(field) if type(field) == list else field} FROM state WHERE id=1;"
return dict(zip(column_names, result)) # combine column names with data into dict
+
+
+
@app.route("/")
def gfx_main():
return Response(render_template("gfx.html"), mimetype="text/html")
@app.route("/<string:page>")
def gfx_page(page):
- if page in ["autocue", "docs"]:
+ if page in ["autocue"]:
return Response(render_template(f"{page}.html"), mimetype="text/html")
else:
return "", 404
+
+
+
@app.route("/admin")
+@auth.login_required
def admin_main():
- ...
+ return Response(render_template("admin.html", mimetype="text/html"))
-@app.route("/admin/<string:page>")
+@app.route("/admin/<string:page>", methods=['GET', 'POST'])
+@auth.login_required
def admin_page(page):
- ...
+ if page in ["text", "clock", "timer", "price"]:
+ if request.method == "POST": # if posting with data update the database then return the new admin page
+
+ post_data = request.form.to_dict()
+ aggrigate_data = {}
+
+ # perform all data clean up for special cases
+ aggrigate_data = {}
+ for key in post_data:
+ # if a key begins with the word "aggrigate": aggrigate it with all other keys with the same body into a list
+ if key[:9] == "aggrigate":
+ new_key = key[10:-5]
+ if new_key in aggrigate_data:
+ aggrigate_data[new_key].append(int(post_data[key]))
+ else:
+ aggrigate_data.update({new_key: [int(post_data[key])]})
+
+ # if the key is a timer, make the time fixed to the epoch instead of relative (& account for offset)
+ if key[:3] == "end":
+ aggrigate_data.update(
+ {key: int(datetime.now(timezone.utc).timestamp()) + int(post_data[key])}
+ )
+
+ else: aggrigate_data.update({key: post_data[key]})
+
+ with sqlite3.connect(path.join(app.root_path, "data.db")) as connection:
+ cursor = connection.cursor()
+ result = cursor.execute("SELECT * FROM pragma_table_info('state');").fetchall()[1:]
+
+ types = {"TEXT":str, "INTEGER":int, "REAL":float}
+ type_note = {c[1]:types[c[2]] for c in result}
+
+ valid_data = {k: type_note[k].__call__(aggrigate_data[k]) for k in aggrigate_data if k in type_note}
+
+ query = f"""
+ UPDATE state SET
+ {', '.join([f'{k} = \'{v}\'' if type(v) == str else f'{k} = {v}' for (k,v) in valid_data.items()])}
+ WHERE id = 1;
+ """
+
+ cursor.execute(query)
+ connection.commit()
+
+ request.method = "internal" # set method to internal so that the api call returns only the python object
+ if page == "clock":
+ coords = [{
+ "i":i,
+ "x":45*cos(radians(i-90))+50,
+ "y":45*sin(radians(i-90))+50
+ } for i in list(range(0, 360, INCREMENT))]
+ return Response(render_template("clock.html", data=api(), positions=coords), mimetype="text/html")
+
+ if page == "text":
+ return Response(render_template("text.html", data=api(), text=static_data['text']), mimetype="text/html")
+
+ if page == "timer":
+ return Response(render_template("timer.html", data=api()), mimetype="text/html")
+
+ if page == "price":
+ return Response(render_template("price.html", data=api(), items=static_data['items']), mimetype="text/html")
+ else:
+ return "", 404
+
+
+
@app.route("/api")
def api():
- if request.method == "GET":
+ if request.method in ["GET", "internal"]:
data = database("*")
focus_extra = {}
focus_extra[f"bool_{key[6:]}"] = bool(value) # if 1,2 then True, if 0 then False
data |= focus_extra
+ if request.method == "internal": return data
return jsonify(data)
-
- elif request.method == "POST":
- ...
else:
return "", 404
@app.route("/api/items")
def api_items():
if request.method == "GET":
- return app.send_static_file("static.json")
+ return jsonify(static_data)
else:
return "", 404
else:
return "", 404
+
+
+
if __name__ == "__main__":
- app.run(host='127.0.0.1', port=5000, debug=True)
+ # sanity check on the db
+ with open(path.join(app.root_path, "schema"), "r") as f:
+ schema, load = f.read().split("\n\n")
+ with sqlite3.connect(path.join(app.root_path, "data.db")) as connection:
+ cursor = connection.cursor()
+ try:
+ cursor.execute(load)
+ except sqlite3.IntegrityError:
+ print("Database is setup correctly")
+ pass
+ except sqlite3.OperationalError:
+ print("Table missing or corrupt...")
+ cursor.execute("DROP TABLE state;")
+ cursor.execute(schema)
+ cursor.execute(load)
+ connection.commit()
+
+ app.run(host='192.168.8.143', port=5080, debug=True)
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>XMDV</title>
+ </head>
+ <body>
+ <h1>Hello!</h1>
+ <h2>Get ready!</h2>
+ <fieldset>
+ <legend>Control pannels</legend>
+ <ul>
+ <li><a href="/admin/clock">Doomsday control</a></li>
+ <li><a href="/admin/price">Pricing and item panel</a></li>
+ <li><a href="/admin/text">Text panel</a></li>
+ <li><a href="/admin/timer">Timer panel</a></li>
+ </ul>
+ </fieldset>
+ </body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>XMDV</title>
+ <style>
+.split {
+ display: inline-block;
+ width: calc(50% - 20px);
+ margin: 10px;
+ vertical-align: top;
+}
+/*
+clock styling
+ - makes div square
+ - positions center pannel in the middle of the clock
+ - positions radio buttons around the clock (PHP)
+*/
+/*
+media query to make sure the clock is always displayed well
+*/
+@media (orientation: portrait) {
+ :root {
+ font-size: 2em;
+ }
+ input {
+ font-size: 1em;
+ }
+ input[type="submit"] {
+ width: 100%;
+ aspect-ratio: 4
+ }
+ .clock {
+ position: relative;
+ width: 100%;
+ aspect-ratio: 1;
+ }
+ .clockButton {
+ position: absolute;
+ width: 2em;
+ height: 2em;
+ margin: 0;
+ }
+ {% for p in positions %}
+ .angle{{p.i}} {top: calc({{p.y}}% - 1em); left: calc({{p.x}}% - 1em);}
+ {% endfor %}
+}
+@media (orientation: landscape) {
+ .clock {
+ position: relative;
+ height: 70vh;
+ aspect-ratio: 1;
+ }
+ .clockButton {
+ position: absolute;
+ width: 1.5em;
+ height: 1.5em;
+ margin: 0;
+ }
+ {% for p in positions %}
+ .angle{{p.i}} {top: calc({{p.y}}% - 0.75em); left: calc({{p.x}}% - 0.75em);}
+ {% endfor %}
+}
+
+ </style>
+ </head>
+ <body>
+ <form action="/admin/clock" method="POST">
+ <fieldset>
+ <legend>Doomsday clock <em>(Incr. {{positions[1].i}} deg)</em></legend>
+ <div class=clock>
+ <svg class='clockFace' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'><circle r='45' cx='50' cy='50' fill='none' stroke='#a0a0a0' stroke-width='0.15px'/></svg>
+
+ {% for p in positions %}
+ {% if p.i == data.current_position %}
+ <input type='radio' class='clockButton angle{{p.i}}' value='{{p.i}}' name='current_position' checked='checked' style='outline: 2px solid red;'>
+ {% else %}
+ <input type='radio' class='clockButton angle{{p.i}}' value='{{p.i}}' name='current_position'>
+ {% endif %}
+ {% endfor %}
+
+ </div>
+ </fieldset>
+ <fieldset>
+ <legend>Movement</legend>
+ <input type='number' name='movement_speed' id='movementSpeed' value='{{data.movement_speed}}'>
+ <label for='movementSpeed'> Movement speed (ms/tick)</label><br>
+ <div class="split">
+ <input type='radio' value='ease' name='movement_function'>
+ <label>Ease function</label>
+ </div><div class="split">
+ <input type='radio' value='linear' name='movement_function' checked='checked'>
+ <label>Linear function</label>
+ </div>
+ </fieldset>
+ <input type="submit" name="update" value="Update">
+ </form>
+ </body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="">
+ <head>
+ <meta charset="utf-8">
+ <title></title>
+ </head>
+ <body>
+ <header></header>
+ <main></main>
+ <footer></footer>
+ </body>
+</html>
<!DOCTYPE html>
-<html>
+<html lang="en">
<head>
+ <meta charset="utf-8">
+ <title>XMDV</title>
<style>
+
body {
- --star-light: #FFE0B3;
- --star-dark: #FFC266;
- --dark: #DEDEDE;
- --black: #3b3b45;
- --feature-dark: #AAAAEE;
- --feature-light: #BBBBEE;
- --feature-white: #EAEAFA;
- --background-light: #FFFFFF;
- --background-dark: #EEEEEE;
- --feature-gradient: linear-gradient(var(--feature-dark), var(--feature-light));
- --background: linear-gradient(var(--background-light), var(--background-dark));
- --clock: conic-gradient(var(--background-light) 0deg, var(--background-light) 0deg, rgb(0,0,0,0) 0.1deg), rgb(0,0,0,0) 360deg);
-
- font-family: "Archivo", sans-serif;
- font-size: 1.6vh;
- color: var(--black);
-
- opacity: 0;
- transition: opacity 1.5s;
+ --star-light: #FFE0B3;
+ --star-dark: #FFC266;
+ --dark: #DEDEDE;
+ --black: #3b3b45;
+ --feature-dark: #AAAAEE;
+ --feature-light: #BBBBEE;
+ --feature-white: #EAEAFA;
+ --background-light: #FFFFFF;
+ --background-dark: #EEEEEE;
+ --feature-gradient: linear-gradient(var(--feature-dark), var(--feature-light));
+ --background: linear-gradient(var(--background-light), var(--background-dark));
+ --clock: conic-gradient(var(--background-light) 0deg, var(--background-light) 0deg, rgb(0,0,0,0) 0.1deg), rgb(0,0,0,0) 360deg);
+
+ font-family: "Archivo", sans-serif;
+ font-size: 1.6vh;
+ color: var(--black);
+
+ opacity: 0;
+ transition: opacity 1.5s;
}
@keyframes spin {
- 0% {transform: rotate(0turn) scale(1.2);}
- 50% {transform: rotate(0.25turn) scale(1);}
- 100% {transform: rotate(0.5turn) scale(1.2);}
+ 0% {transform: rotate(0turn) scale(1.2);}
+ 50% {transform: rotate(0.25turn) scale(1);}
+ 100% {transform: rotate(0.5turn) scale(1.2);}
}
@keyframes spinText {
- 0% {transform: rotate(-0.01turn);}
- 50% {transform: rotate(0.01turn);}
- 100% {transform: rotate(-0.01turn);}
+ 0% {transform: rotate(-0.01turn);}
+ 50% {transform: rotate(0.01turn);}
+ 100% {transform: rotate(-0.01turn);}
}
.container {
- position: absolute;
- border-radius: 1vh;
+ position: absolute;
+ border-radius: 1vh;
}
.box {
- border-radius: 1vh;
- box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px;
- background-image: var(--background);
+ border-radius: 1vh;
+ box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px;
+ background-image: var(--background);
}
#side {
- left: 6vh;
- top: 6vh;
- width: 40vh;
- opacity: 0;
- transition: opacity 1.5s;
+left: 6vh;
+top: 6vh;
+width: 40vh;
+opacity: 0;
+transition: opacity 1.5s;
}
.soldBox {
- margin: 5px;
- padding: 5px;
- border-radius: 5px;
- background-image: var(--background);
- box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.19) 0px 1px 2px;
+ margin: 5px;
+ padding: 5px;
+ border-radius: 5px;
+ background-image: var(--background);
+ box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.19) 0px 1px 2px;
}
.soldBox > * {
- margin: 0;
- font-style: italic;
+ margin: 0;
+ font-style: italic;
}
.soldBox > * > span {
- font-weight: bold;
- font-style: normal;
+ font-weight: bold;
+ font-style: normal;
}
#raiting {
- margin: 5px;
- margin-top: 0;
- border-radius: 5px;
- font-size: 2em;
- line-height: 1em;
- color: var(--star-dark);
- text-shadow: -1px -1px 0 var(--star-light), 1px -1px 0 var(--star-light), -1px 1px 0 var(--star-light), 1px 1px 0 var(--star-light), 2px 2px 0 var(--dark);
- background-image: linear-gradient(var(--background-dark), var(--dark));
- box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.19) 0px 1px 2px;
+margin: 5px;
+margin-top: 0;
+border-radius: 5px;
+font-size: 2em;
+line-height: 1em;
+color: var(--star-dark);
+text-shadow: -1px -1px 0 var(--star-light), 1px -1px 0 var(--star-light), -1px 1px 0 var(--star-light), 1px 1px 0 var(--star-light), 2px 2px 0 var(--dark);
+background-image: linear-gradient(var(--background-dark), var(--dark));
+box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.19) 0px 1px 2px;
}
.main {
- z-index: 0;
+ z-index: 0;
}
#banner {
- left: 48vh;
- top: 6vh;
- height: 2em;
- width: 0vh;
- padding: 0.5vh;
- overflow: hidden;
- opacity: 0;
- transition: opacity 1.5s, width 1.5s, left 1.5s;
- background: linear-gradient(var(--feature-white), rgb(0,0,0,0), var(--feature-white)), var(--background);
- border: 1px solid var(--feature-dark);
+left: 48vh;
+top: 6vh;
+height: 2em;
+width: 0vh;
+padding: 0.5vh;
+overflow: hidden;
+opacity: 0;
+transition: opacity 1.5s, width 1.5s, left 1.5s;
+background: linear-gradient(var(--feature-white), rgb(0,0,0,0), var(--feature-white)), var(--background);
+border: 1px solid var(--feature-dark);
}
#banner.moved, #timer2.moved {
- left: 6vh;
+left: 6vh;
}
#timer2.movedUp {
- top: 6vh;
+top: 6vh;
}
#timer3.focus {
- bottom: calc(50% - 15vh);
- right: calc(50% - 15vh);
- width: 30vh;
- height: 30vh;
- border-radius: 15vh;
- font-size: 6em;
- line-height: 30vh;
+bottom: calc(50% - 15vh);
+right: calc(50% - 15vh);
+width: 30vh;
+height: 30vh;
+border-radius: 15vh;
+font-size: 6em;
+line-height: 30vh;
}
.extra {
- position: relative;
- top: -2vh;
- z-index: -1;
- transform: translateY(-100%);
- padding-top: 2vh;
- padding-bottom: 1px;
- background-image: var(--feature-gradient);
- transition: transform 1.5s;
+ position: relative;
+ top: -2vh;
+ z-index: -1;
+ transform: translateY(-100%);
+ padding-top: 2vh;
+ padding-bottom: 1px;
+ background-image: var(--feature-gradient);
+ transition: transform 1.5s;
}
.showExtra > .extra {
- transform: translateY(0%);
+ transform: translateY(0%);
}
.main > *, .extra > *:not(.soldBox), .bottom > *, .bottom > div > *, #marquee > * {
- margin: 0;
- padding: 0.5vh 1vh;
+ margin: 0;
+ padding: 0.5vh 1vh;
}
#banner > h1 {
- width: 49vh;
- margin: 0;
- font-size: 2em;
- line-height: 1;
- font-style: italic;
- font-weight: normal;
- text-align: center;
+width: 49vh;
+margin: 0;
+font-size: 2em;
+line-height: 1;
+font-style: italic;
+font-weight: normal;
+text-align: center;
}
.sigilBox {
- opacity: 0;
- transition: opacity 10s;
+ opacity: 0;
+ transition: opacity 10s;
}
.sigil {
- width: 20vh;
- height: 20vh;
+ width: 20vh;
+ height: 20vh;
}
#sigil1 {
- right: 4vh;
- top: 4vh;
+right: 4vh;
+top: 4vh;
}
#sigil2 {
- right: 4vh;
- bottom: 4vh;
+right: 4vh;
+bottom: 4vh;
}
#sigil3 {
- left: 4vh;
- bottom: 4vh;
+left: 4vh;
+bottom: 4vh;
}
#sigil4 {
- left: 4vh;
- top: 4vh;
+left: 4vh;
+top: 4vh;
}
#badgeContainer {
- position: relative;
- padding: 0;
+position: relative;
+padding: 0;
}
.badge {
- position: absolute;
- top: -2vh;
- left: 32vh;
- width: 10vh;
- height: 10vh;
- padding: 0;
- animation: spin 10s linear 0s infinite;
+ position: absolute;
+ top: -2vh;
+ left: 32vh;
+ width: 10vh;
+ height: 10vh;
+ padding: 0;
+ animation: spin 10s linear 0s infinite;
}
h2.badge {
- top: -1vh;
- left: 26.5vh;
- color: white;
- width: 20vh;
- animation: spinText 10s linear 0s infinite;
- rotate: 0.025turn;
- text-align: center;
- text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
+ top: -1vh;
+ left: 26.5vh;
+ color: white;
+ width: 20vh;
+ animation: spinText 10s linear 0s infinite;
+ rotate: 0.025turn;
+ text-align: center;
+ text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
}
#badgeContainer {
- transform: translateX(12vh) scale(0%);
- transition: transform 1.5s;
+transform: translateX(12vh) scale(0%);
+transition: transform 1.5s;
}
.main > hr, .extra > hr {
- margin: 0 1vh;
- padding: 0;
- border-color: var(--dark);
+ margin: 0 1vh;
+ padding: 0;
+ border-color: var(--dark);
}
.feature {
- border-top: 2px solid var(--dark);
- border-bottom: 2px solid var(--dark);
- background-image: var(--feature-gradient);
+ border-top: 2px solid var(--dark);
+ border-bottom: 2px solid var(--dark);
+ background-image: var(--feature-gradient);
}
.bottom {
- left: 6vh;
- bottom: 6vh;
- width: 100vh;
+ left: 6vh;
+ bottom: 6vh;
+ width: 100vh;
}
.bottom > *:not(#timer1) {
- display: inline-block;
- vertical-align: top;
+ display: inline-block;
+ vertical-align: top;
}
#topTextBox {
- width: calc(100% - 20px);
- margin: 5px;
- margin-bottom: 0;
- padding: 5px;
- border-radius: 5px;
- background-image: linear-gradient(0.25turn, var(--feature-dark), rgba(0,0,0,0) 40%), var(--background);
- box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.19) 0px 1px 2px;
+width: calc(100% - 20px);
+margin: 5px;
+margin-bottom: 0;
+padding: 5px;
+border-radius: 5px;
+background-image: linear-gradient(0.25turn, var(--feature-dark), rgba(0,0,0,0) 40%), var(--background);
+box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.19) 0px 1px 2px;
}
#topText {
- display: inline-block;
- width: 50%;
- padding: 0;
+display: inline-block;
+width: 50%;
+padding: 0;
}
#idText {
- display: inline-block;
- width: 50%;
- padding: 0;
- text-align: right;
- font-size: 1.5em;
- font-weight: bold;
- font-style: italic;
- text-shadow: 0 0 3px rgba(0,0,0,0.25);
- color: var(--feature-dark);
+display: inline-block;
+width: 50%;
+padding: 0;
+text-align: right;
+font-size: 1.5em;
+font-weight: bold;
+font-style: italic;
+text-shadow: 0 0 3px rgba(0,0,0,0.25);
+color: var(--feature-dark);
}
#marquee {
- padding: 0;
- width: 100%;
- mask-image: linear-gradient(0.25turn, transparent, black 5%, black 95%, transparent);
- overflow: hidden;
+padding: 0;
+width: 100%;
+mask-image: linear-gradient(0.25turn, transparent, black 5%, black 95%, transparent);
+overflow: hidden;
}
#marqueeText {
- white-space: nowrap;
- overflow: hidden;
+white-space: nowrap;
+overflow: hidden;
}
#timer1 {
- position: absolute;
- left: 101vh;
- padding: 0.5vh;
- opacity: 0;
- text-align: center;
- border: 2px solid var(--dark);
- border-radius: calc(1vh - 2px);
- transition: opacity 1.5s;
+position: absolute;
+left: 101vh;
+padding: 0.5vh;
+opacity: 0;
+text-align: center;
+border: 2px solid var(--dark);
+border-radius: calc(1vh - 2px);
+transition: opacity 1.5s;
}
#timer2 {
- position: absolute;
- left: calc(48vh + 2px);
- top: calc(11.5vh + 2px);
- height: 1.2em;
- width: 3em;
- margin: 0;
- text-align: center;
- font-size: 2em;
- font-weight: bold;
- line-height: 1.2em;
- border: 2px solid var(--dark);
- outline: 2px solid var(--feature-dark);
- opacity: 0;
- transition: opacity 1.5s, left 1.5s, top 1.5s;
+position: absolute;
+left: calc(48vh + 2px);
+top: calc(11.5vh + 2px);
+height: 1.2em;
+width: 3em;
+margin: 0;
+text-align: center;
+font-size: 2em;
+font-weight: bold;
+line-height: 1.2em;
+border: 2px solid var(--dark);
+outline: 2px solid var(--feature-dark);
+opacity: 0;
+transition: opacity 1.5s, left 1.5s, top 1.5s;
}
#timer3 {
- position: absolute;
- bottom: 4vh;
- right: 4vh;
- opacity: 0;
- width: 12vh;
- height: 12vh;
- border-radius: 6vh;
- margin: 0;
- text-align: center;
- vertical-align: center;
- font-size: 2em;
- font-weight: bold;
- line-height: 12vh;
- background-image: var(--background);
- border: 2px solid var(--feature-dark);
- outline: 2px solid var(--dark);
- opacity: 0;
- transition: opacity 1.5s, bottom 1.5s, right 1.5s, width 1.5s, height 1.5s, border-radius 1.5s, font-size 1.5s, line-height 1.5s, transform 1.5s;
- animation: spinText 10s linear 0s infinite;
+position: absolute;
+bottom: 4vh;
+right: 4vh;
+opacity: 0;
+width: 12vh;
+height: 12vh;
+border-radius: 6vh;
+margin: 0;
+text-align: center;
+vertical-align: center;
+font-size: 2em;
+font-weight: bold;
+line-height: 12vh;
+background-image: var(--background);
+border: 2px solid var(--feature-dark);
+outline: 2px solid var(--dark);
+opacity: 0;
+transition: opacity 1.5s, bottom 1.5s, right 1.5s, width 1.5s, height 1.5s, border-radius 1.5s, font-size 1.5s, line-height 1.5s, transform 1.5s;
+animation: spinText 10s linear 0s infinite;
}
.show {
- opacity: 1 !important;
+ opacity: 1 !important;
}
#badgeContainer.show {
- transform: translateX(0vh) scale(100%);
+transform: translateX(0vh) scale(100%);
}
#banner.show {
- width: 50vh;
+width: 50vh;
}
.sigilBox.show {
- opacity: 0.9;
+ opacity: 0.9;
}
@keyframes spinBox {
- 0% {transform: translateY(100%);}
- 50% {}
- 100% {}
+ 0% {transform: translateY(100%);}
+ 50% {}
+ 100% {}
}
line, polyline, circle {
- fill: none;
- stroke: var(--feature-dark);
- stroke-width: 3;
+ fill: none;
+ stroke: var(--feature-dark);
+ stroke-width: 3;
}
circle {
- fill: #F2F2F2;
+ fill: #F2F2F2;
}
.sigilGrid {
- stroke: var(--dark);
- stroke-width: 1;
+ stroke: var(--dark);
+ stroke-width: 1;
}
+
</style>
- <link rel="stylesheet" href="static/fonts.css">
+ <link rel="stylesheet" href="./static/fonts.css">
</head>
<body id="all" class="show" onload="frame();update();">
<circle class="sigilEnd" r="3">
</svg>
</div>
-
- <script>
+ <script>
/*
-structure
- *: function
- -: section
+ structure
+
+ *: function
+ -: section
-- setup of all const html elements
-- setup of all variables shared between the main call function and the other funtions
-* main call function (update) happens 2x per second
+ - setup of all const html elements
+ - setup of all variables shared between the main call function and the other funtions
+ * main call function (update) happens 2x per second
-- setup of all variables remebered between frame calls
-* frame function (frame) 200x per second
+ - setup of all variables remebered between frame calls
+ * frame function (frame) 200x per second
-* sigil function (sigil) 2X per second
+ * sigil function (sigil) 2X per second
-*/
+ */
const all = document.getElementById("all");
const timer5 = document.getElementById("timer5");
const timer6 = document.getElementById("timer6");
const timers = [
- timer1,
- timer2,
- timer3,
- timer4,
- timer5,
- timer6
- ];
+ timer1,
+timer2,
+timer3,
+timer4,
+timer5,
+timer6
+];
const banner = document.getElementById("banner");
const side = document.getElementById("side");
const badgeContainer = document.getElementById("badgeContainer");
// 1:NE 2:SE 3:SW 4:NW
const sigils = [
- document.getElementById("sigil1"),
- document.getElementById("sigil2"),
- document.getElementById("sigil3"),
- document.getElementById("sigil4")
- ];
+ document.getElementById("sigil1"),
+ document.getElementById("sigil2"),
+ document.getElementById("sigil3"),
+ document.getElementById("sigil4")
+];
const remaining = document.getElementById("unitsLeft");
const origional = document.getElementById("origionalPrice");
const topTextElement = document.getElementById("topText");
// handles all updates from the server data that are not required to be instantanious (2 times per second)
function update() {
- // fetch the item manifest and chache it
- fetch("./api/items", {cache: "default"})
- .then(data => data.json())
- .then(dataStatic => {
- fetch("./api", {cache: "no-store"})
- .then(data => data.json())
- .then(data => {
-
- // some variable setup
- let id = data.item_id;
- const items = dataStatic.items
- const item = items[id];
-
- // frame function variable hand-over
- discountHard = data.discount;
- discountRate = data.discount_change / 200; // happens 200x per second in the frame function
- topText = dataStatic.text.crawler_top[data.crawler_top_index]
- bottomText = [];
- for (let i of data.list_crawler_bottom) {bottomText.push(dataStatic.text.crawler_bottom[i])}
-
- // handle all optional elements showing/hiding
- if (data.bool_all) {all.classList.add("show");}
- else {all.classList.remove("show");}
-
- // set all timers to correct time and show/focus
- for (let t = 1; t <= 6; t++) {
- const timer = timers[t-1];
-
- if (timer != null) {
- let current = Math.round((Date.now() + data['timer_offset']) / 1000);
- var time = data[`end_timer_${t}`] - current;
- if (Math.sign(time) == -1) {time = 0;}
- var minutes = Math.floor(time / 60);
- var seconds = (time - (minutes * 60));
-
- var minutesString = minutes.toString().padStart(2, "0");
- var secondsString = seconds.toString().padStart(2, "0");
-
- timer.innerHTML = `${minutesString}:${secondsString}`;
-
- if (data[`bool_timer_${t}`]) {timer.classList.add("show");}
- else {timer.classList.remove("show");}
-
- if (data[`focus_timer_${t}`]) {timer.classList.add("focus");}
- else {timer.classList.remove("focus");}
-
- // additional styling for individual timers
- switch (t) {
- case 2: {
- let angle;
- if (minutes > 0) {angle = 360;}
- else {angle = Math.round(seconds * (360 / 60))};
- timer2.style.background = `
- conic-gradient(
- rgb(0,0,0,0) 0deg,
- rgb(0,0,0,0) ${angle}deg,
- var(--feature-light) ${angle}.1deg,
- var(--feature-light) 360deg
- ), var(--background)`;
- }
-
- case 3: {
- let radius;
- if (minutes > 0) {radius = 100;}
- else {radius = Math.round(seconds * (100 / 60));}
- timer3.style.background = `
- linear-gradient(
- rgb(0,0,0,0) 0%, rgb(0,0,0,0) ${radius}%,
- var(--feature-light) ${radius}.1%,
- var(--feature-dark) 100%
- ), var(--background)`;
- }
- }
- }
- }
-
- // banner placement
- if (data.bool_banner) {
- if (!banner.classList.contains("show")) { // only change the banner text when the banner is turning back on
- document.getElementById("bannerText").innerHTML = dataStatic.text.banner[data.banner_index];
- }
- banner.classList.add("show");
- timer2.classList.remove("movedUp");
- } else {
- banner.classList.remove("show");
- timer2.classList.add("movedUp");
- }
-
- // handle element showing
- // hierarchy: main product information -> extra information -> discount badge
- if (data.bool_product) {
- side.classList.add("show");
- banner.classList.remove("moved");
- timer2.classList.remove("moved");
-
- if (data.bool_extra) {
- side.classList.add("showExtra");
-
- if (discount <= 0.99) {
- badgeContainer.classList.add("show");
- } else {
- badgeContainer.classList.remove("show");
- }
-
- } else {
- side.classList.remove("showExtra");
- }
-
- } else {
- side.classList.remove("show");
- banner.classList.add("moved");
- timer2.classList.add("moved");
- side.classList.remove("showExtra");
- badgeContainer.classList.remove("show");
- }
-
- // Sigil handling
- for (let s = 0; s < 4; s++) {
- if (data[`bool_sigil_${s+1}`]) {
- let phrase = dataStatic.text.sigil[Math.floor(Math.random()*dataStatic.text.sigil.length)];
- let pointString = "";
- for (let i = 0; i < phrase.length; i++) {
- let point = phrase.charCodeAt(i) - 97; // get the coords of the letter
- if (i == 0) {var startPoint = dataStatic.text.square[point].split(",");} // get the start and end points
- else if (i == phrase.length - 1) {var endPoint = dataStatic.text.square[point].split(",");}
-
- pointString += `${dataStatic.text.square[point]} `;
- }
-
- sigilCap[s].setAttribute("x1", Number(startPoint[0]) - 5)
- sigilCap[s].setAttribute("y1", startPoint[1])
- sigilCap[s].setAttribute("x2", Number(startPoint[0]) + 5)
- sigilCap[s].setAttribute("y2", startPoint[1])
- sigilLine[s].setAttribute("points", pointString);
- sigilEnd[s].setAttribute("cx", endPoint[0]);
- sigilEnd[s].setAttribute("cy", endPoint[1]);
-
- sigils[s].classList.add("show");
- } else {
- sigils[s].classList.remove("show");
- }
- }
-
- // set item properties
- document.getElementById("code").innerHTML = item.code;
- document.getElementById("raiting").innerHTML = item.rating;
- document.getElementById("subtext").innerHTML = item.subtext;
- document.getElementById("description").innerHTML = item.description;
-
- // calculate the price sting with the pre/postfix
- let price = item.origional_price * discount;
- if (data.bool_rounding) {
- price = Math.round(price * 100) / 100;
- }
-
- let priceString;
- let ezString;
- if (data.bool_prefix) {priceString = `${data.currency_symbol}${price}`}
- else {priceString = `${price}${data.currency_symbol}`};
- let ezPrice = Math.round((price * 1.1) / 12);
- if (data.bool_prefix) {ezString = `${data.currency_symbol}${ezPrice}`}
- else {ezString = `${ezPrice}${data.currency_symbol}`};
-
- // set discount, pricing and ez pay
- document.getElementById("currentPrice").innerHTML = `<em>Now only:</em> ${priceString}`;
- document.getElementById("monthlyPrice").innerHTML = `12 monthly payments of <b>${ezString}</b>`;
- document.getElementById("badgeText").innerHTML = `${Math.round((1 - discount) * 100)}% OFF`;
- document.getElementById("stock").innerHTML = `${item.stock_count} units`;
- document.getElementById("sold").innerHTML = `${Math.round(item.stock_count * (1 - data.percent_remaining))} units`;
-
- if (discount <= 0.99) {
- let origionalPrice = Math.round(item.origional_price);
- if (data.bool_prefix) {origionalString = `${data.currency_symbol}${origionalPrice}`}
- else {origionalString = `${origionalPrice}${data.currency_symbol}`};
- origional.innerHTML = `<s><em>WAS:</em> ${origionalString}</s>`;
- } else {
- origional.innerHTML = `<em>NOW:</em> ${priceString}`;
- }
-
- if (data.percent_remaining == 0) {
- remaining.innerHTML = "Sold out!";
- }
- else { // work out the prefix for the discount to be more reactive
- let descriptior;
- if (data.percent_remaining < 0.1) {descriptor = "Quick! Only"}
- else if (data.percent_remaining < 0.25) {descriptor = "Only"}
- else if (data.percent_remaining < 0.5) {descriptor = "Just"}
- else {descriptor = ""}
- remaining.innerHTML = `
- ${descriptor} ${Math.round(item.stock_count * (data.percent_remaining))} left!
- `; }
- });
- });
+ // fetch the item manifest and chache it
+ fetch("./api/items", {cache: "default"})
+ .then(data => data.json())
+ .then(dataStatic => {
+ fetch("./api", {cache: "no-store"})
+ .then(data => data.json())
+ .then(data => {
+
+ // some variable setup
+ let id = data.item_id;
+ const items = dataStatic.items
+ const item = items[id];
+
+ // frame function variable hand-over
+ discountHard = data.discount_1;
+ discountRate = data.discount_change / 200; // happens 200x per second in the frame function
+ topText = dataStatic.text.crawler_top[data.crawler_top_index]
+ bottomText = [];
+ for (let i of data.list_crawler_bottom) {bottomText.push(dataStatic.text.crawler_bottom[i])}
+
+ // handle all optional elements showing/hiding
+ if (data.bool_all) {all.classList.add("show");}
+ else {all.classList.remove("show");}
+
+ // set all timers to correct time and show/focus
+ for (let t = 1; t <= 6; t++) {
+ const timer = timers[t-1];
+
+ if (timer != null) {
+ let current = Math.round((Date.now() + data['timer_offset']) / 1000);
+ var time = data[`end_timer_${t}`] - current;
+ if (Math.sign(time) == -1) {time = 0;}
+ var minutes = Math.floor(time / 60);
+ var seconds = (time - (minutes * 60));
+
+ var minutesString = minutes.toString().padStart(2, "0");
+ var secondsString = seconds.toString().padStart(2, "0");
+
+ timer.innerHTML = `${minutesString}:${secondsString}`;
+
+ if (data[`bool_timer_${t}`]) {timer.classList.add("show");}
+ else {timer.classList.remove("show");}
+
+ if (data[`focus_timer_${t}`]) {timer.classList.add("focus");}
+ else {timer.classList.remove("focus");}
+
+ // additional styling for individual timers
+ switch (t) {
+ case 2: {
+ let angle;
+ if (minutes > 0) {angle = 360;}
+ else {angle = Math.round(seconds * (360 / 60))};
+ timer2.style.background = `
+ conic-gradient(
+ rgb(0,0,0,0) 0deg,
+ rgb(0,0,0,0) ${angle}deg,
+ var(--feature-light) ${angle}.1deg,
+ var(--feature-light) 360deg
+ ), var(--background)`;
+ }
+
+ case 3: {
+ let radius;
+ if (minutes > 0) {radius = 100;}
+ else {radius = Math.round(seconds * (100 / 60));}
+ timer3.style.background = `
+ linear-gradient(
+ rgb(0,0,0,0) 0%, rgb(0,0,0,0) ${radius}%,
+ var(--feature-light) ${radius}.1%,
+ var(--feature-dark) 100%
+ ), var(--background)`;
+ }
+ }
+ }
+ }
+
+ // banner placement
+ if (data.bool_banner) {
+ if (!banner.classList.contains("show")) { // only change the banner text when the banner is turning back on
+ document.getElementById("bannerText").innerHTML = dataStatic.text.banner[data.banner_index];
+ }
+ banner.classList.add("show");
+ timer2.classList.remove("movedUp");
+ } else {
+ banner.classList.remove("show");
+ timer2.classList.add("movedUp");
+ }
+
+ // handle element showing
+ // hierarchy: main product information -> extra information -> discount badge
+ if (data.bool_product) {
+ side.classList.add("show");
+ banner.classList.remove("moved");
+ timer2.classList.remove("moved");
+
+ if (data.bool_extra) {
+ side.classList.add("showExtra");
+
+ if (discount <= 0.99) {
+ badgeContainer.classList.add("show");
+ } else {
+ badgeContainer.classList.remove("show");
+ }
+
+ } else {
+ side.classList.remove("showExtra");
+ }
+
+ } else {
+ side.classList.remove("show");
+ banner.classList.add("moved");
+ timer2.classList.add("moved");
+ side.classList.remove("showExtra");
+ badgeContainer.classList.remove("show");
+ }
+
+ // Sigil handling
+ for (let s = 0; s < 4; s++) {
+ if (data[`bool_sigil_${s+1}`]) {
+ let phrase = dataStatic.text.sigil[Math.floor(Math.random()*dataStatic.text.sigil.length)];
+ let pointString = "";
+ for (let i = 0; i < phrase.length; i++) {
+ let point = phrase.charCodeAt(i) - 97; // get the coords of the letter
+ if (i == 0) {var startPoint = dataStatic.text.square[point].split(",");} // get the start and end points
+ else if (i == phrase.length - 1) {var endPoint = dataStatic.text.square[point].split(",");}
+
+ pointString += `${dataStatic.text.square[point]} `;
+ }
+
+ sigilCap[s].setAttribute("x1", Number(startPoint[0]) - 5)
+ sigilCap[s].setAttribute("y1", startPoint[1])
+ sigilCap[s].setAttribute("x2", Number(startPoint[0]) + 5)
+ sigilCap[s].setAttribute("y2", startPoint[1])
+ sigilLine[s].setAttribute("points", pointString);
+ sigilEnd[s].setAttribute("cx", endPoint[0]);
+ sigilEnd[s].setAttribute("cy", endPoint[1]);
+
+ sigils[s].classList.add("show");
+ } else {
+ sigils[s].classList.remove("show");
+ }
+ }
+
+ // set item properties
+ document.getElementById("code").innerHTML = item.code;
+ document.getElementById("raiting").innerHTML = item.rating;
+ document.getElementById("subtext").innerHTML = item.subtext;
+ document.getElementById("description").innerHTML = item.description;
+
+ // calculate the price sting with the pre/postfix
+ let price = item.origional_price * discount;
+ if (data.bool_rounding) {
+ price = Math.round(price * 100) / 100;
+ }
+
+ let priceString;
+ let ezString;
+ if (data.bool_prefix) {priceString = `${data.currency_symbol}${price}`}
+ else {priceString = `${price}${data.currency_symbol}`};
+ let ezPrice = Math.round((price * 1.1) / 12);
+ if (data.bool_prefix) {ezString = `${data.currency_symbol}${ezPrice}`}
+ else {ezString = `${ezPrice}${data.currency_symbol}`};
+
+ // set discount, pricing and ez pay
+ document.getElementById("currentPrice").innerHTML = `<em>Now only:</em> ${priceString}`;
+ document.getElementById("monthlyPrice").innerHTML = `12 monthly payments of <b>${ezString}</b>`;
+ document.getElementById("badgeText").innerHTML = `${Math.round((1 - discount) * 100)}% OFF`;
+ document.getElementById("stock").innerHTML = `${item.stock_count} units`;
+ document.getElementById("sold").innerHTML = `${Math.round(item.stock_count * (1 - data.percent_remaining))} units`;
+
+ if (discount <= 0.99) {
+ let origionalPrice = Math.round(item.origional_price);
+ if (data.bool_prefix) {origionalString = `${data.currency_symbol}${origionalPrice}`}
+ else {origionalString = `${origionalPrice}${data.currency_symbol}`};
+ origional.innerHTML = `<s><em>WAS:</em> ${origionalString}</s>`;
+ } else {
+ origional.innerHTML = `<em>NOW:</em> ${priceString}`;
+ }
+
+ if (data.percent_remaining == 0) {
+ remaining.innerHTML = "Sold out!";
+ }
+ else { // work out the prefix for the discount to be more reactive
+ let descriptior;
+ if (data.percent_remaining < 0.1) {descriptor = "Quick! Only"}
+ else if (data.percent_remaining < 0.25) {descriptor = "Only"}
+ else if (data.percent_remaining < 0.5) {descriptor = "Just"}
+ else {descriptor = ""}
+ remaining.innerHTML = `
+ ${descriptor} ${Math.round(item.stock_count * (data.percent_remaining))} left!
+ `; }
+ });
+ });
}
// frame dynamic variables
// function handles all animated events that are required to look smooth (marquee movement / price changes) (200 times per second)
function frame() {
- // move the current discount towards the target distance at the supplied rate
- if (Math.abs(discount - discountHard) <= discountRate) { // if the discount is within one tick, snap to current discount
- discount = discountHard;
- } else if (discount > discountHard) {
- discount -= discountRate;
- } else {
- discount += discountRate;
- }
+ // move the current discount towards the target distance at the supplied rate
+ if (Math.abs(discount - discountHard) <= discountRate) { // if the discount is within one tick, snap to current discount
+ discount = discountHard;
+ } else if (discount > discountHard) {
+ discount -= discountRate;
+ } else {
+ discount += discountRate;
+ }
- bottomTextElement.style.transform = `translateX(${marqueeOffset}px)`;
- marqueeOffset += 0.2;
+ bottomTextElement.style.transform = `translateX(${marqueeOffset}px)`;
+ marqueeOffset += 0.2;
- // only replace text where neccicary or where the page has just loaded
- if (marqueeOffset >= marqueeContainer.offsetWidth || (topTextElement.innerHTML == "" && bottomTextElement.innerHTML == "")) {
+ // only replace text where neccicary or where the page has just loaded
+ if (marqueeOffset >= marqueeContainer.offsetWidth || (topTextElement.innerHTML == "" && bottomTextElement.innerHTML == "")) {
- topTextElement.innerHTML = topText;
+ topTextElement.innerHTML = topText;
- bottomTextElement.innerHTML = bottomText[bottomTextIndex];
- bottomTextIndex += 1;
- if (bottomTextIndex >= bottomText.length) { // make sure the changed list wont ovreflow the bottom text index
- bottomTextIndex = 0;
- }
+ bottomTextElement.innerHTML = bottomText[bottomTextIndex];
+ bottomTextIndex += 1;
+ if (bottomTextIndex >= bottomText.length) { // make sure the changed list wont ovreflow the bottom text index
+ bottomTextIndex = 0;
+ }
- // use a canvas element to measure the size of text precicely
- const canvas = document.getElementById("canvas");
- const ctx = canvas.getContext("2d");
+ // use a canvas element to measure the size of text precicely
+ const canvas = document.getElementById("canvas");
+ const ctx = canvas.getContext("2d");
- var style = window.getComputedStyle(bottomTextElement, null).getPropertyValue("font-size");
- var fontSize = parseFloat(style);
+ var style = window.getComputedStyle(bottomTextElement, null).getPropertyValue("font-size");
+ var fontSize = parseFloat(style);
- ctx.font = `bold ${fontSize}px sans-serif`;
- let text = ctx.measureText(bottomTextElement.innerHTML);
+ ctx.font = `bold ${fontSize}px sans-serif`;
+ let text = ctx.measureText(bottomTextElement.innerHTML);
- marqueeOffset = -1.1 * text.width; // place text just before its end-point
- }
+ marqueeOffset = -1.1 * text.width; // place text just before its end-point
+ }
}
setInterval(frame, 5);
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>XMDV</title>
+ </head>
+ <body>
+ <form action="/admin/price" method="POST">
+ <fieldset>
+ <legend>Products</legend>
+ {% for item in items %}
+ {% if loop.index0 == data.item_id %}
+ <input type='radio' value='{{loop.index0}}' name='item_id' checked='checked'>
+ <label style='color: red;'>{{item.subtext}}</label>
+ {% else %}
+ <input type='radio' value='{{loop.index0}}' name='item_id'>
+ <label>{{item.subtext}}</label>
+ {% endif %}
+ <br>
+ {% endfor %}
+ </fieldset>
+
+ <fieldset>
+ <legend>Showing</legend>
+ {% if data.bool_all %}
+ <input type='radio' name='bool_all' value='0'>
+ <label>Hide all</label>
+ <input type='radio' name='bool_all' value='1' checked='checked'>
+ <label style='color: red;'>Show all</label>
+ {% else %}
+ <input type='radio' name='bool_all' value='0' checked='checked'>
+ <label style='color: red;'>Hide all</label>
+ <input type='radio' name='bool_all' value='1'>
+ <label>Show all</label>
+ {% endif %}
+ <br>
+ {% if data.bool_product %}
+ <input type='radio' name='bool_product' value='0'>
+ <label>Hide product</label>
+ <input type='radio' name='bool_product' value='1' checked='checked'>
+ <label style='color: red;'>Show product</label>
+ {% else %}
+ <input type='radio' name='bool_product' value='0' checked='checked'>
+ <label style='color: red;'>Hide product</label>
+ <input type='radio' name='bool_product' value='1'>
+ <label>Show product</label>
+ {% endif %}
+ <br>
+ {% if data.bool_extra %}
+ <input type='radio' name='bool_extra' value='0'>
+ <label>Hide extra</label>
+ <input type='radio' name='bool_extra' value='1' checked='checked'>
+ <label style='color: red;'>Show extra</label>
+ {% else %}
+ <input type='radio' name='bool_extra' value='0' checked='checked'>
+ <label style='color: red;'>Hide extra</label>
+ <input type='radio' name='bool_extra' value='1'>
+ <label>Show extra</label>
+ {% endif %}
+ </fieldset>
+
+ <fieldset>
+ <legend>Pricing</legend>
+ <input type='text' name='currency_symbol' value='{{data.currency_symbol}}'>
+ <label>Currency</label>
+ {% if data.bool_prefix %}
+ <input type='radio' name='bool_prefix' value='0'>
+ <label>Postfix</label>
+ <input type='radio' name='bool_prefix' value='1' checked='checked'>
+ <label style='color: red;'>Prefix</label>
+ {% else %}
+ <input type='radio' name='bool_prefix' value='0' checked='checked'>
+ <label style='color: red;'>Postfix</label>
+ <input type='radio' name='bool_prefix' value='1'>
+ <label>Prefix</label>
+ {% endif %}
+ <br>
+
+ <input type='number' name='percent_remaining' value='{{data.percent_remaining}}'>
+ <label>Unsold (%)</label> <label style='color: red;'>was {{data.percent_remaining}}</label><br>
+
+ <br>
+ {% for d in (1,2,3,4) %}
+ <input type='number' name='discount_{{d}}' step='0.01' value='{{data['discount_' ~ d]}}'>
+ <label>Discount {{d}} (%)</label> <label style='color: red;'>was {{data['discount_' ~ d]}}</label><br>
+ {% endfor %}
+ <br>
+
+ <input type='number' name='discount_change' value='{{data.discount_change}}'>
+ <label>Discount change (%/s)</label> <label style='color: red;'>was {{data.discount_change}}</label><br>
+
+ {% if data.bool_rounding %}
+ <input type='radio' name='bool_rounding' value='0'>
+ <label>Don't round prices</label>
+ <input type='radio' name='bool_rounding' value='1' checked='checked'>
+ <label style='color: red;'>Round prices</label>
+ {% else %}
+ <input type='radio' name='bool_rounding' value='0' checked='checked'>
+ <label style='color: red;'>Don't round prices</label>
+ <input type='radio' name='bool_rounding' value='1'>
+ <label>Round prices</label>
+ {% endif %}
+ </fieldset>
+
+ <fieldset>
+ <legend>Note</legend>
+ <textarea name='note' rows='10' style='width: 100%;'>{{data.note}}</textarea>
+ </fieldset>
+ <input type="submit" name="update" value="Update">
+ </form>
+ </body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>XMDV</title>
+ </head>
+ <body>
+ <form action="/admin/text" method="POST">
+ <fieldset>
+ <legend>Crawler top text</legend>
+ {% for line in text.crawler_top %}
+ {% if loop.index0 == data.crawler_top_index %}
+ <input type='radio' value='{{loop.index0}}' name='crawler_top_index' checked='checked'>
+ <label style='color: red;'>{{line}}</label>
+ {% else %}
+ <input type='radio' value='{{loop.index0}}' name='crawler_top_index'>
+ <label>{{line}}</label>
+ {% endif %}
+ <br>
+ {% endfor %}
+ </fieldset>
+
+ <fieldset>
+ <legend>Bottom text</legend>
+ {% for line in text.crawler_bottom %}
+ {% if loop.index0 in data.list_crawler_bottom %}
+ <input type='checkbox' value='{{loop.index0}}' name='aggrigate_list_crawler_bottom_{{ '%04d' % loop.index0 }}' checked='checked'>
+ <label style='color: red;'>{{line}}</label>
+ {% else %}
+ <input type='checkbox' value='{{loop.index0}}' name='aggrigate_list_crawler_bottom_{{ '%04d' % loop.index0 }}'>
+ <label>{{line}}</label>
+ {% endif %}
+ <br>
+ {% endfor %}
+ </fieldset><fieldset>
+ <legend>Banner text</legend>
+ {% for line in text.banner %}
+ {% if loop.index0 == data.banner_index %}
+ <input type='radio' value='{{loop.index0}}' name='banner_index' checked='checked'>
+ <label style='color: red;'>{{line}}</label>
+ {% else %}
+ <input type='radio' value='{{loop.index0}}' name='banner_index'>
+ <label>{{line}}</label>
+ {% endif %}
+ <br>
+ {% endfor %}
+ <br>
+ {% if data.bool_banner %}
+ <input type='radio' name='bool_banner' value='0'>
+ <label>Hide banner</label>
+ <input type='radio' name='bool_banner' value='1' checked='checked'>
+ <label style='color: red;'>Show banner</label>
+ {% else %}
+ <input type='radio' name='bool_banner' value='0' checked='checked'>
+ <label style='color: red;'>Hide banner</label>
+ <input type='radio' name='bool_banner' value='1'>
+ <label>Show banner</label>
+ {% endif %}
+ </fieldset>
+
+
+ <fieldset>
+ <legend>Sigil display</legend>
+ {% for t in (1,2,3,4) %}
+ {% if data['bool_sigil_' ~ t] %}
+ <input type='radio' name='bool_sigil_{{t}}' value='0'>
+ <label>Hide sigil {{t}}</label>
+ <input type='radio' name='bool_sigil_{{t}}' value='1' checked='checked'>
+ <label style='color: red;'>Show sigil {{t}}</label>
+ {% else %}
+ <input type='radio' name='bool_sigil_{{t}}' value='0' checked='checked'>
+ <label style='color: red;'>Hide sigil {{t}}</label>
+ <input type='radio' name='bool_sigil_{{t}}' value='1'>
+ <label>Show sigil {{t}}</label>
+ {% endif %}
+ <br>
+ {% endfor %}
+ </fieldset>
+
+ <input type="submit" name="update" value="Update">
+ </form>
+ </body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>XMDV</title>
+ </head>
+ <body onload="update();">
+ <form action="/admin/timer" method="POST">
+ <fieldset>
+ <legend>Timers</legend>
+ <p style='color: red;'>Note: time is approximate and may not be accurate to the GFX display.</p>
+ {% for t in (1,2,3,4,5,6) %}
+ <label>Timer {{t}}</label> <label style='color: blue;' id='{{t}}'>00:00</label>
+ <input type='radio' value='0' name='end_timer_{{t}}'><label>Reset</label>
+ <input type='radio' value='30' name='end_timer_{{t}}'><label>+00:30</label>
+ <input type='radio' value='60' name='end_timer_{{t}}'><label>+01:00</label>
+ <input type='radio' value='120' name='end_timer_{{t}}'><label>+02:00</label>
+ <input type='radio' value='300' name='end_timer_{{t}}'><label>+05:00</label>
+ <input type='radio' value='600' name='end_timer_{{t}}'><label>+10:00</label>
+
+ {% if data['focus_timer_' ~ t] %}
+ <input type='radio' name='focus_timer_{{t}}' value='0' style='margin-left:50px;'>
+ <label>Hide timer {{t}}</label>
+ <input type='radio' name='focus_timer_{{t}}' value='1'>
+ <label>Show timer {{t}}</label>
+ <input type='radio' name='focus_timer_{{t}}' value='2' checked='checked'>
+ <label style='color: red;'>Focus timer {{t}}</label>
+ {% elif data['bool_timer_' ~ t] %}
+ <input type='radio' name='focus_timer_{{t}}' value='0' style='margin-left:50px;'>
+ <label>Hide timer {{t}}</label>
+ <input type='radio' name='focus_timer_{{t}}' value='1' checked='checked'>
+ <label style='color: red;'>Show timer {{t}}</label>
+ <input type='radio' name='focus_timer_{{t}}' value='2'>
+ <label>Focus timer {{t}}</label>
+ {% else %}
+ <input type='radio' name='focus_timer_{{t}}' value='0' style='margin-left:50px;' checked='checked'>
+ <label style='color: red;'>Hide timer {{t}}</label>
+ <input type='radio' name='focus_timer_{{t}}' value='1'>
+ <label>Show timer {{t}}</label>
+ <input type='radio' name='focus_timer_{{t}}' value='2'>
+ <label>Focus timer {{t}}</label>
+ {% endif %}
+ <br>
+ {% endfor %}
+
+ <br>
+ <input type='number' name='timer_offset' min='-5000' max='5000' step='1' value='{{data.timer_offset}}'>
+ <label> Offset (ms)</label> <label style='color: red;'> was {{data.timer_offset}}</label>
+ </fieldset>
+ <input type="submit" name="update" value="Update">
+ </form>
+
+ <script>
+
+function update() {
+ fetch("/api", {cache: "no-store"})
+ .then(data => data.json())
+ .then(data => {
+ for (let t = 1; t <= 6; t++) {
+ const timer = document.getElementById(t);
+
+ let current = Math.round((Date.now() + data['timer_offset']) / 1000);
+ var time = data[`end_timer_${t}`] - current;
+ if (Math.sign(time) == -1) {time = 0;}
+ var minutes = Math.floor(time / 60);
+ var seconds = (time - (minutes * 60));
+
+ var minutesString = minutes.toString().padStart(2, "0");
+ var secondsString = seconds.toString().padStart(2, "0");
+
+ timer.innerHTML = `${minutesString}:${secondsString}`;
+ if (minutes == 0) {
+ if (seconds == 0) {timer.style.color = "blue";}
+ else if (seconds % 2 == 0) {timer.style.color = "red";}
+ else {timer.style.color = "orange";}
+ } else {
+ timer.style.color = "green";
+ }
+ }
+ });
+}
+
+setInterval(update, 1000)
+
+ </script>
+ </body>
+</html>