parent
a85582b94e
commit
856a694f0b
@ -1 +1 @@ |
||||
_test_env.php |
||||
pages/_test_env.php |
||||
|
@ -0,0 +1,29 @@ |
||||
<?php |
||||
|
||||
return [ |
||||
'%term_title%' => 'ESP8266 Wireless Terminal', |
||||
|
||||
'%b1%' => '1', |
||||
'%b2%' => '2', |
||||
'%b3%' => '3', |
||||
'%b4%' => '4', |
||||
'%b5%' => '5', |
||||
'%screenData%' => '{ |
||||
"w": 26, "h": 10, |
||||
"x": 0, "y": 0, |
||||
"cv": 1, |
||||
"screen": "70 t259" |
||||
}', |
||||
|
||||
'%ap_enable%' => '1', |
||||
'%tpw%' => '60', |
||||
'%ap_channel%' => '7', |
||||
'%ap_ssid%' => 'ESP-123456', |
||||
'%ap_password%' => 'Passw0rd!', |
||||
'%ap_hidden%' => '0', |
||||
'%sta_ssid%' => 'LibraryFreeWifi', |
||||
'%sta_password%' => 'windows XP is The Best', |
||||
'%sta_active_ip%' => '', |
||||
|
||||
'%sta_enable%' => '0', |
||||
]; |
@ -0,0 +1,3 @@ |
||||
<?php |
||||
|
||||
define("ESP_IP", "192.168.0.19"); |
@ -0,0 +1,25 @@ |
||||
<?php |
||||
|
||||
$pages = []; |
||||
|
||||
/** Add a page */ |
||||
function pg($key, $bc, $path) { |
||||
global $pages; |
||||
$pages[$key] = (object) [ |
||||
'bodyclass' => $bc, |
||||
'path' => $path, |
||||
'label' => tr("menu.$key"), |
||||
]; |
||||
} |
||||
|
||||
pg('cfg_wifi', 'cfg', '/cfg/wifi'); |
||||
pg('cfg_network', 'cfg', '/cfg/network'); |
||||
pg('cfg_term', 'cfg', '/cfg/term'); |
||||
pg('about', 'cfg', '/about'); |
||||
pg('help', 'cfg', '/help'); |
||||
pg('term', 'term', '/'); |
||||
|
||||
// technical |
||||
pg('wifi_set', '', '/cfg/wifi/set'); |
||||
|
||||
return $pages; |
@ -1,144 +0,0 @@ |
||||
<?php |
||||
|
||||
require '_test_env.php'; |
||||
|
||||
$prod = defined('STDIN'); |
||||
$root = $prod ? '' : ('http://' . ESP_IP); |
||||
|
||||
$menu = [ |
||||
'home' => [ $prod ? '/status' : '/page_status.php', 'Home' ], |
||||
'wifi' => [ $prod ? '/wifi' : '/page_wifi.php', 'WiFi config' ], |
||||
'about' => [ $prod ? '/about' : '/page_about.php', 'About' ], |
||||
]; |
||||
|
||||
$appname = 'Current Analyser'; |
||||
|
||||
function e($s) { |
||||
return htmlspecialchars($s, ENT_HTML5|ENT_QUOTES); |
||||
} |
||||
|
||||
?><!doctype html> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
||||
|
||||
<title><?= e($menu[$page][1]) ?> - <?= e($appname) ?></title>
|
||||
|
||||
<link href="/css/app.css" rel="stylesheet"> |
||||
<script src="/js/all.js"></script> |
||||
<script> |
||||
// server root (or URL) - used for local development with remote AJAX calls |
||||
// (this needs CORS working on the target - which I added to esp-httpd) |
||||
var _root = <?= json_encode($root) ?>;
|
||||
</script> |
||||
</head> |
||||
<body class="page-<?=$page?>">
|
||||
<div id="outer"> |
||||
<nav id="menu"> |
||||
<div id="brand" onclick="$('#menu').toggleClass('expanded')"><?= e($appname) ?></div>
|
||||
<?php |
||||
// generate the menu |
||||
foreach($menu as $k => $m) { |
||||
$sel = ($page == $k) ? ' class="selected"' : ''; |
||||
$text = e($m[1]); |
||||
$url = e($m[0]); |
||||
echo "<a href=\"$url\"$sel>$text</a>"; |
||||
} |
||||
?> |
||||
</nav> |
||||
<div id="content"> |
||||
<img src="/img/loader.gif" alt="Loading…" id="loader"> |
||||
|
||||
|
||||
|
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta name="viewport" content="width=device-width,shrink-to-fit=no,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0"> |
||||
|
||||
<title>WiFi Settings - ESP8266 Remote Terminal</title> |
||||
|
||||
<link rel="stylesheet" href="/css/app.css"> |
||||
<script src="/js/app.js"></script> |
||||
</head> |
||||
<body class="page-wifi"> |
||||
|
||||
<img src="/img/loader.gif" alt="Loading…" id="loader"> |
||||
|
||||
<h1 onclick="location.href='/'">WiFi settings</h1> |
||||
|
||||
<div class="Box" id="wificonfbox"> |
||||
<table> |
||||
<tr> |
||||
<th>WiFi mode</th> |
||||
<td id="opmodebox">%WiFiMode%</td> |
||||
</tr> |
||||
<tr class="x-hide-noip x-hide-2"> |
||||
<th>IP</th> |
||||
<td>%StaIP%</td> |
||||
</tr> |
||||
<tr> |
||||
<th>Switch to</th> |
||||
<td id="modeswitch"></td> |
||||
</tr> |
||||
<tr class="x-hide-1"> |
||||
<th><label for="channel">AP channel</label></th> |
||||
<td> |
||||
<form action="/wifi/setchannel" method="GET"> |
||||
<input name="ch" id="channel" type="number" step=1 min=1 max=14 value="%WiFiChannel%"><!-- |
||||
--><input type="submit" value="Set" class="narrow btn-green x-hide-3"> |
||||
</form> |
||||
</td> |
||||
</tr> |
||||
<tr class="x-hide-1"> |
||||
<th><label for="channel">AP name</label></th> |
||||
<td> |
||||
<form action="/wifi/setname" method="GET"> |
||||
<input name="name" type="text" value="%APName%"><!-- |
||||
--><input type="submit" value="Set" class="narrow btn-green"> |
||||
</form> |
||||
</td> |
||||
</tr> |
||||
<tr><td colspan=2 style="white-space: normal;"> |
||||
<p>Some changes require a reboot, dropping connection. It can take a while to re-connect.</p> |
||||
<p> |
||||
<b>If you lose access</b>, hold the BOOT button for 2 seconds (the Tx LED starts blinking) to re-enable AP mode. |
||||
If that fails, hold the BOOT button for over 5 seconds (rapid Tx LED flashing) to perform a factory reset. |
||||
<p> |
||||
</td></tr> |
||||
</table> |
||||
</div> |
||||
|
||||
<div class="Box" id="ap-box"> |
||||
<h2>Select AP to join</h2> |
||||
<div id="ap-loader" class="x-hide-2">Scanning<span class="anim-dots">.</span></div> |
||||
<div id="ap-noscan" class="x-hide-1 x-hide-3">Can't scan in AP-only mode.</div> |
||||
<div id="ap-list" style="display:none"></div> |
||||
</div> |
||||
|
||||
<nav id="botnav"> |
||||
<a href="/">Terminal</a><!-- |
||||
--><a href="/help">Help</a><!-- |
||||
--><a href="/about">About</a> |
||||
</nav> |
||||
|
||||
<div class="Modal hidden" id="psk-modal"> |
||||
<div class="Dialog"> |
||||
<form action="/wifi/connect" method="post" id="conn-form"> |
||||
<input type="hidden" id="conn-essid" name="essid"><!-- |
||||
--><label for="conn-passwd">Password:</label><!-- |
||||
--><input type="password" id="conn-passwd" name="passwd"><!-- |
||||
--><input type="submit" value="Connect!"> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
|
||||
<script> |
||||
_root = window.location.host; |
||||
wifiInit({staSSID: '%StaSSID%', staIP: '%StaIP%', mode: '%WiFiModeNum%'}); |
||||
</script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,64 @@ |
||||
<?php |
||||
|
||||
require '_env.php'; |
||||
$prod = defined('STDIN'); |
||||
define ('DEBUG', !$prod); |
||||
$root = DEBUG ? ESP_IP : ''; |
||||
define ('LIVE_ROOT', $root); |
||||
|
||||
define('CUR_PAGE', $_GET['page'] ?: 'term'); |
||||
define('LOCALE', $_GET['locale'] ?: 'en'); |
||||
|
||||
$_messages = require(__DIR__ . '/messages/' . LOCALE . '.php'); |
||||
$_pages = require('_pages.php'); |
||||
|
||||
define('APP_NAME', 'ESPTerm'); |
||||
define('PAGE_TITLE', $_pages[CUR_PAGE]->label . ' :: ' . APP_NAME); |
||||
define('BODYCLASS', $_pages[CUR_PAGE]->bodyclass); |
||||
|
||||
/** URL (dev or production) */ |
||||
function url($name, $root=false) { |
||||
global $_pages; |
||||
if ($root) return LIVE_ROOT . $_pages[$name]->path; |
||||
|
||||
if (DEBUG) return "/index.php?page=$name"; |
||||
else return $_pages[$name]->path; |
||||
} |
||||
|
||||
/** URL label for a button */ |
||||
function label($name) { |
||||
global $_pages; |
||||
return $_pages[$name]->label; |
||||
} |
||||
|
||||
function e($s) { |
||||
return htmlspecialchars($s, ENT_HTML5|ENT_QUOTES); |
||||
} |
||||
|
||||
function tr($key) { |
||||
global $_messages; |
||||
return $_messages[$key] ?: ('??'.$key.'??'); |
||||
} |
||||
|
||||
/** Like eval, but allows <?php and ?> */
|
||||
function include_str($code) { |
||||
$tmp = tmpfile(); |
||||
$tmpf = stream_get_meta_data($tmp); |
||||
$tmpf = $tmpf ['uri']; |
||||
fwrite($tmp, $code); |
||||
$ret = include($tmpf); |
||||
fclose($tmp); |
||||
return $ret; |
||||
} |
||||
|
||||
require 'pages/_head.php'; |
||||
$_pf = 'pages/'.CUR_PAGE.'.php'; |
||||
if (file_exists($_pf)) { |
||||
$f = file_get_contents($_pf); |
||||
$reps = require('_debug_replacements.php'); |
||||
$str = str_replace(array_keys($reps), array_values($reps), $f); |
||||
include_str($str); |
||||
} else { |
||||
echo "404"; |
||||
} |
||||
require 'pages/_tail.php'; |
@ -0,0 +1,35 @@ |
||||
<?php |
||||
|
||||
return [ |
||||
'appname' => 'ESPTerm', |
||||
|
||||
'menu.cfg_wifi' => 'WiFi Settings', |
||||
'menu.cfg_network' => 'Network Configuration', |
||||
'menu.cfg_term' => 'Terminal Settings', |
||||
'menu.about' => 'About ESPTerm', |
||||
'menu.help' => 'Help', |
||||
'menu.term' => 'Back to Terminal', |
||||
|
||||
'box.ap' => 'Built-in Access Point', |
||||
'box.sta' => 'Client Mode', |
||||
|
||||
'wifi.enable' => 'Enabled:', |
||||
'wifi.tpw' => 'Transmit Power:', |
||||
'wifi.ap_channel' => 'Channel:', |
||||
'wifi.ap_ssid' => 'AP SSID:', |
||||
'wifi.ap_password' => 'Password:', |
||||
'wifi.ap_hidden' => 'Hide SSID:', |
||||
'wifi.sta_info' => 'Selected Network:', |
||||
|
||||
'wifi.sta_ssid' => 'Network SSID:', |
||||
'wifi.sta_password' => 'Password:', |
||||
'wifi.not_conn' => 'Not connected.', |
||||
'wifi.forget' => '', |
||||
|
||||
'wifi.submit' => 'Apply!', |
||||
|
||||
'enabled' => 'Enabled', |
||||
'disabled' => 'Disabled', |
||||
'yes' => 'Yes', |
||||
'no' => 'No', |
||||
]; |
@ -0,0 +1,15 @@ |
||||
|
||||
<nav id="menu"> |
||||
<div id="brand" onclick="$('#menu').toggleClass('expanded')"><?= tr('appname') ?></div>
|
||||
<?php |
||||
// generate the menu |
||||
foreach($_pages as $k => $page) { |
||||
if ($page->bodyclass !== 'cfg') continue; |
||||
$sel = (CUR_PAGE == $k) ? ' class="selected"' : ''; |
||||
$text = $page->label; |
||||
$url = e(url($k)); |
||||
echo "<a href=\"$url\"$sel>$text</a>"; |
||||
} |
||||
?><a href="<?= e(url('term')) ?>"><?= tr('menu.term') ?></a>
|
||||
|
||||
</nav> |
@ -0,0 +1,26 @@ |
||||
<!doctype html> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
||||
<title><?= PAGE_TITLE ?></title>
|
||||
<link href="/css/app.css" rel="stylesheet"> |
||||
<script src="/js/app.js"></script> |
||||
<script>var _root = <?= json_encode(LIVE_ROOT) ?>;</script>
|
||||
</head> |
||||
<body class="<?= BODYCLASS ?>">
|
||||
<div id="outer"> |
||||
<?php |
||||
$cfg = false; |
||||
if ($_pages[CUR_PAGE]->bodyclass == 'cfg') { |
||||
$cfg = true; |
||||
require __DIR__ . '/_cfg_menu.php'; |
||||
} |
||||
?> |
||||
|
||||
<div id="content"> |
||||
<img src="/img/loader.gif" alt="Loading…" id="loader"> |
||||
<?php if ($cfg): ?> |
||||
<h1><?= tr('menu.' . CUR_PAGE) ?></h1>
|
||||
<?php endif; ?> |
@ -0,0 +1,5 @@ |
||||
</div> |
||||
|
||||
</div> |
||||
</body> |
||||
</html> |
@ -0,0 +1,123 @@ |
||||
<form class="Box str mobcol" action="<?= e(url('wifi_set')) ?>" method="POST">
|
||||
<h2><?= tr('box.ap') ?></h2>
|
||||
|
||||
<div class="Row checkbox"> |
||||
<label><?= tr('wifi.enable') ?></label><!--
|
||||
--><span class="box"></span> |
||||
<input type="hidden" name="ap_enable" value="%ap_enable%"> |
||||
</div> |
||||
|
||||
<div class="Row"> |
||||
<label for="ap_ssid"><?= tr('wifi.ap_ssid') ?></label>
|
||||
<input type="text" name="ap_ssid" id="ap_ssid" value="%ap_ssid%" required> |
||||
</div> |
||||
|
||||
<div class="Row"> |
||||
<label for="ap_password"><?= tr('wifi.ap_password') ?></label>
|
||||
<input type="text" name="ap_password" id="ap_password" value="%ap_password%"> |
||||
</div> |
||||
|
||||
<div class="Row"> |
||||
<label for="ap_channel"><?= tr('wifi.ap_channel') ?></label>
|
||||
<input type="number" name="ap_channel" id="ap_channel" min=1 max=14 value="%ap_channel%" required> |
||||
</div> |
||||
|
||||
<div class="Row range"> |
||||
<label for="tpw"> |
||||
<?= tr('wifi.tpw') ?> |
||||
<span class="display x-disp1 mq-phone"></span> |
||||
</label> |
||||
<input type="range" name="tpw" id="tpw" step=1 min=0 max=82 value="%tpw%"> |
||||
<span class="display x-disp2 mq-tablet-min"></span> |
||||
</div> |
||||
|
||||
<div class="Row checkbox"> |
||||
<label><?= tr('wifi.ap_hidden') ?></label><!--
|
||||
--><span class="box"></span> |
||||
<input type="hidden" name="ap_hidden" value="%ap_hidden%"> |
||||
</div> |
||||
|
||||
<div class="Row buttons"> |
||||
<input type="submit" value="<?= tr('wifi.submit') ?>">
|
||||
</div> |
||||
</form> |
||||
|
||||
<form class="Box str mobcol" action="<?= e(url('wifi_set')) ?>" method="POST">
|
||||
<h2><?= tr('box.sta') ?></h2>
|
||||
|
||||
<div class="Row checkbox"> |
||||
<label><?= tr('wifi.enable') ?></label><!--
|
||||
--><span class="box"></span> |
||||
<input type="hidden" name="sta_enable" value="%sta_enable%"> |
||||
</div> |
||||
|
||||
<input type="hidden" name="sta_ssid" id="sta_ssid" value="%sta_ssid%"> |
||||
<input type="hidden" name="sta_password" id="sta_password" value="%sta_password%"> |
||||
|
||||
<div class="Row sta-info"> |
||||
<label><?= tr('wifi.sta_info') ?></label>
|
||||
<div class="AP-preview" id="sta-nw"> |
||||
<div class="wrap"> |
||||
<div class="inner"> |
||||
<div class="essid"></div> |
||||
<div class="passwd"></div> |
||||
<div class="ip"></div> |
||||
</div> |
||||
<a class="forget" id="forget-sta">×</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="Row buttons"> |
||||
<input type="submit" value="<?= tr('wifi.submit') ?>">
|
||||
</div> |
||||
</form> |
||||
|
||||
<script> |
||||
$('.Row.checkbox').forEach(function(x) { |
||||
var inp = x.querySelector('input'); |
||||
var box = x.querySelector('.box'); |
||||
|
||||
$(box).toggleClass('checked', inp.value); |
||||
|
||||
$(x).on('click', function() { |
||||
inp.value = 1 - inp.value; |
||||
$(box).toggleClass('checked', inp.value) |
||||
}); |
||||
}); |
||||
|
||||
$('.Box.mobcol').forEach(function(x) { |
||||
var h = x.querySelector('h2'); |
||||
$(h).on('click', function() { |
||||
$(x).toggleClass('expanded'); |
||||
}); |
||||
}); |
||||
|
||||
function rangePt(inp) { |
||||
return Math.round(((inp.value / inp.max)*100)) + '%'; |
||||
} |
||||
|
||||
$('.Row.range').forEach(function(x) { |
||||
var inp = x.querySelector('input'); |
||||
var disp1 = x.querySelector('.x-disp1'); |
||||
var disp2 = x.querySelector('.x-disp2'); |
||||
var t = rangePt(inp); |
||||
$(disp1).html(t); |
||||
$(disp2).html(t); |
||||
$(inp).on('input', function() { |
||||
t = rangePt(inp); |
||||
$(disp1).html(t); |
||||
$(disp2).html(t); |
||||
}); |
||||
}); |
||||
|
||||
function wifiShowSelected(name, password, ip) { |
||||
$('#sta-nw .essid').html(e(name)); |
||||
var nopw = undef(password) || password.length == 0; |
||||
$('#sta-nw .passwd').html(e(password)).toggleClass('hidden', nopw); |
||||
$('#sta-nw .ip').html(ip.length>0 ? ip : '<?=tr('wifi.not_conn')?>');
|
||||
} |
||||
|
||||
wifiShowSelected('%sta_ssid%', '%sta_password%', '%sta_active_ip%'); |
||||
|
||||
</script> |
@ -0,0 +1,37 @@ |
||||
<script> |
||||
// Workaround for badly loaded page |
||||
setTimeout(function() { |
||||
if (typeof termInit == 'undefined' || typeof $ == 'undefined') { |
||||
location.reload(true); |
||||
} |
||||
}, 2000); |
||||
</script> |
||||
|
||||
<h1 onclick="location.href='<?= e(url('cfg_wifi_simple')) ?>'">%term_title%</h1>
|
||||
|
||||
<div id="termwrap"> |
||||
<div id="screen"></div> |
||||
|
||||
<div id="buttons"> |
||||
<button data-n="1" class="btn-blue">%b1%</button><!-- |
||||
--><button data-n="2" class="btn-blue">%b2%</button><!-- |
||||
--><button data-n="3" class="btn-blue">%b3%</button><!-- |
||||
--><button data-n="4" class="btn-blue">%b4%</button><!-- |
||||
--><button data-n="5" class="btn-blue">%b5%</button> |
||||
</div> |
||||
</div> |
||||
|
||||
<nav id="botnav"> |
||||
<a href="<?= url('cfg_wifi_simple') ?>"><?= tr('menu.cfg_wifi') ?></a><!--
|
||||
--><a href="<?= url('help') ?>">Help</a><!--
|
||||
--><a href="<?= url('about') ?>">About</a>
|
||||
</nav> |
||||
|
||||
<script> |
||||
try { |
||||
termInit(%screenData%); |
||||
} catch(e) { |
||||
console.error("Fail, reloading..."); |
||||
location.reload(true); |
||||
} |
||||
</script> |
@ -1,178 +0,0 @@ |
||||
html { |
||||
font-family: Arial, sans-serif; |
||||
color: #D0D0D0; |
||||
background: #131315; |
||||
} |
||||
|
||||
html, body { |
||||
@include naked(); |
||||
width: 100%; |
||||
height: 100%; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
a, a:visited, a:link { |
||||
cursor: pointer; |
||||
color: #5abfff; |
||||
text-decoration: none; |
||||
} |
||||
|
||||
a:hover { |
||||
color: #5abfff; |
||||
text-decoration: underline; |
||||
} |
||||
|
||||
.Box { |
||||
display: block; |
||||
max-width: 900px; |
||||
|
||||
margin-top: dist(0); |
||||
padding: dist(-1) dist(0); |
||||
|
||||
@include media($phone) { |
||||
margin-top: dist(-1); |
||||
padding: dist(-3) dist(-2); |
||||
} |
||||
// |
||||
//h1 + & { |
||||
// margin-top: 0; |
||||
//} |
||||
// |
||||
//h2 { |
||||
// margin-top: 0; |
||||
//} |
||||
|
||||
p:first-child { |
||||
margin-top:0; |
||||
} |
||||
|
||||
border-radius: 3px; |
||||
background-color: rgba(white, .07); |
||||
|
||||
//&.wide { |
||||
// width: initial; |
||||
// max-width: initial; |
||||
//} |
||||
// |
||||
//&.medium { |
||||
// max-width: 1200px; |
||||
//} |
||||
// |
||||
//.Valfield { |
||||
// display: inline-block; |
||||
// min-width: 10em; |
||||
//} |
||||
} |
||||
|
||||
body { |
||||
position: relative; |
||||
|
||||
padding: dist(0); |
||||
@include media($phone) { |
||||
padding: dist(-1); |
||||
} |
||||
|
||||
overflow-y: auto; |
||||
|
||||
& > * { |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
} |
||||
} |
||||
|
||||
h1,h2 { |
||||
@include noselect(); |
||||
} |
||||
|
||||
h1 { |
||||
text-align: center; |
||||
font-size: fsize(6); |
||||
margin-top: 0; |
||||
margin-bottom: dist(0); |
||||
|
||||
@include media($phone) { |
||||
font-size: fsize(3); |
||||
margin-bottom: dist(-1); |
||||
} |
||||
|
||||
@include media($tablet) { |
||||
font-size: fsize(5); |
||||
} |
||||
} |
||||
|
||||
h2 { |
||||
font-size: fsize(2); |
||||
margin-bottom: dist(-1); |
||||
//&:first-child{margin-top:0} |
||||
} |
||||
|
||||
td, th { |
||||
padding: dist(-2); |
||||
white-space: nowrap; |
||||
|
||||
@include media($phone) { |
||||
padding: dist(-3); |
||||
} |
||||
} |
||||
|
||||
tbody th { |
||||
text-align: right; |
||||
width: $form-label-w; |
||||
color: $c-form-label-fg; |
||||
|
||||
@include media($phone) { |
||||
width: auto; |
||||
} |
||||
} |
||||
|
||||
tbody td { |
||||
input[type="text"], input[type="number"] { |
||||
width: 10em; |
||||
|
||||
@include media($phone) { |
||||
width: 8em; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Loader wheel in top right corner |
||||
#loader { |
||||
position: absolute; |
||||
right: dist(1); |
||||
top: dist(1); |
||||
|
||||
transition: opacity .2s; |
||||
opacity: 0; |
||||
|
||||
@include media($phone) { |
||||
top: dist(0); |
||||
right: dist(0); |
||||
} |
||||
|
||||
&.show { |
||||
opacity:1; |
||||
} |
||||
} |
||||
|
||||
ul > * { |
||||
padding-top: .1em; |
||||
padding-bottom: .1em; |
||||
} |
||||
|
||||
#botnav { |
||||
padding-top: 1.5em; |
||||
text-align: center; |
||||
|
||||
a { |
||||
padding: 0 dist(-2); |
||||
text-decoration: underline; |
||||
|
||||
&, &:visited, &:link { |
||||
color: #2e4d6e; |
||||
} |
||||
|
||||
&:hover { |
||||
color: #5abfff; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
html { |
||||
font-family: Arial, sans-serif; |
||||
color: #D0D0D0; |
||||
background: #131315; |
||||
} |
||||
|
||||
html, body { |
||||
@include naked(); |
||||
width: 100%; |
||||
height: 100%; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
a, a:visited, a:link { |
||||
cursor: pointer; |
||||
color: #5abfff; |
||||
text-decoration: none; |
||||
} |
||||
|
||||
a:hover { |
||||
color: #5abfff; |
||||
text-decoration: underline; |
||||
} |
||||
|
||||
.hidden { |
||||
display: none !important; |
||||
} |
||||
|
||||
[onclick] { |
||||
cursor: pointer; |
||||
} |
@ -0,0 +1,91 @@ |
||||
.Box { |
||||
display: block; |
||||
max-width: 900px; |
||||
|
||||
margin-top: dist(0); |
||||
padding: dist(-1) dist(0); |
||||
|
||||
@include media($phone) { |
||||
margin-top: dist(-1); |
||||
} |
||||
|
||||
h1 + & { |
||||
margin-top: 0; |
||||
} |
||||
|
||||
h2 { |
||||
margin-top: 0; |
||||
} |
||||
|
||||
border-radius: 3px; |
||||
background-color: rgba(white, .07); |
||||
|
||||
&.wide { |
||||
width: initial; |
||||
max-width: initial; |
||||
} |
||||
|
||||
&.medium { |
||||
max-width: 1200px; |
||||
} |
||||
|
||||
//.Valfield { |
||||
// display: inline-block; |
||||
// min-width: 10em; |
||||
//} |
||||
|
||||
&.str { |
||||
position: relative; |
||||
|
||||
.Row.buttons { |
||||
position: absolute; |
||||
right: dist(0); |
||||
top: 2.7em; |
||||
margin: 12px auto; |
||||
} |
||||
|
||||
@include media($tablet-min) { |
||||
.Row.buttons { |
||||
//position: absolute; |
||||
right: 0; |
||||
top: 0; |
||||
//margin: 12px auto; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@include media($phone) { |
||||
.Box.mobcol { |
||||
h2 { |
||||
position: relative; |
||||
cursor: pointer; |
||||
|
||||
&::after { |
||||
position: absolute; |
||||
right: 0; |
||||
content: '▸'; |
||||
|
||||
top:50%; |
||||
font-size: 120%; |
||||
font-weight: bold; |
||||
transform: translate(0,-50%) rotate(90deg); |
||||
} |
||||
|
||||
margin-bottom: 0 !important; |
||||
} |
||||
|
||||
&.expanded h2::after { |
||||
transform: translate(-25%,-50%) rotate(-90deg); |
||||
margin-bottom: dist(0); |
||||
} |
||||
|
||||
.Row { |
||||
display: none; |
||||
} |
||||
|
||||
&.expanded .Row { |
||||
display: flex; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,45 @@ |
||||
#content { |
||||
flex-grow: 1; |
||||
position: relative; |
||||
|
||||
padding: dist(0); |
||||
@include media($phone) { |
||||
padding: dist(-1); |
||||
} |
||||
|
||||
overflow-y: auto; |
||||
|
||||
& > * { |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
} |
||||
|
||||
h1 { |
||||
text-align: center; |
||||
font-size: fsize(7); |
||||
margin-top: 0; |
||||
margin-bottom: dist(0); |
||||
} |
||||
|
||||
@include media($phone) { |
||||
h1 { |
||||
font-size: fsize(5); |
||||
margin-bottom: dist(-1); |
||||
} |
||||
} |
||||
|
||||
h2 { |
||||
font-size: fsize(3); |
||||
margin-bottom: dist(-1); |
||||
} |
||||
|
||||
td, th { |
||||
padding: dist(-2); |
||||
} |
||||
|
||||
tbody th { |
||||
text-align: right; |
||||
width: $form-label-w; |
||||
color: $c-form-label-fg; |
||||
} |
||||
} |
@ -0,0 +1,76 @@ |
||||
|
||||
ul > * { |
||||
padding-top: .1em; |
||||
padding-bottom: .1em; |
||||
} |
||||
|
||||
h1,h2 { |
||||
@include noselect(); |
||||
} |
||||
|
||||
h1 { |
||||
text-align: center; |
||||
font-size: fsize(6); |
||||
margin-top: 0; |
||||
margin-bottom: dist(0); |
||||
|
||||
@include media($phone) { |
||||
font-size: fsize(3); |
||||
margin-bottom: dist(-1); |
||||
} |
||||
|
||||
@include media($tablet) { |
||||
font-size: fsize(5); |
||||
} |
||||
} |
||||
|
||||
h2 { |
||||
font-size: fsize(2); |
||||
margin-bottom: dist(-1); |
||||
//&:first-child{margin-top:0} |
||||
} |
||||
|
||||
td, th { |
||||
padding: dist(-2); |
||||
white-space: nowrap; |
||||
|
||||
@include media($phone) { |
||||
padding: dist(-3); |
||||
} |
||||
} |
||||
|
||||
tbody th { |
||||
text-align: right; |
||||
width: $form-label-w; |
||||
color: $c-form-label-fg; |
||||
|
||||
@include media($phone) { |
||||
width: auto; |
||||
} |
||||
} |
||||
|
||||
tbody td { |
||||
input[type="text"], input[type="number"] { |
||||
width: 10em; |
||||
|
||||
@include media($phone) { |
||||
width: 8em; |
||||
} |
||||
} |
||||
} |
||||
|
||||
body { |
||||
position: relative; |
||||
|
||||
padding: dist(0); |
||||
@include media($phone) { |
||||
padding: dist(-1); |
||||
} |
||||
|
||||
overflow-y: auto; |
||||
|
||||
& > * { |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
@import "base"; |
||||
|
||||
@import "outer-wrap"; |
||||
@import "menu"; |
||||
@import "content"; |
||||
@import "loader"; |
||||
|
||||
@import "box"; |
||||
@import "modal"; |
@ -0,0 +1,18 @@ |
||||
// Loader wheel in top right corner |
||||
#loader { |
||||
position: absolute; |
||||
right: dist(1); |
||||
top: dist(1); |
||||
|
||||
transition: opacity .2s; |
||||
opacity: 0; |
||||
|
||||
@include media($phone) { |
||||
top: dist(0); |
||||
right: dist(0); |
||||
} |
||||
|
||||
&.show { |
||||
opacity:1; |
||||
} |
||||
} |
@ -0,0 +1,98 @@ |
||||
#menu { |
||||
$menu-bg: #3983CD; |
||||
$menu-hl: #5badff; //#1bd886; |
||||
$menu-fg: white; |
||||
|
||||
flex: 0 0 15rem; |
||||
background: $menu-bg; |
||||
|
||||
& > * { |
||||
display: block; |
||||
text-decoration: none; |
||||
padding: dist(-1) dist(0); |
||||
|
||||
@include nowrap; |
||||
@include noselect; |
||||
} |
||||
|
||||
#brand { |
||||
color: $menu-fg; |
||||
background: darken($menu-bg, 10%); |
||||
font-size: 120%; |
||||
text-align: center; |
||||
position:relative; |
||||
transition: none; |
||||
font-weight: bold; |
||||
|
||||
margin-bottom: dist(0); |
||||
|
||||
@include media($phone) { |
||||
background: $menu-bg; |
||||
cursor: pointer; |
||||
margin-bottom: dist(-2); |
||||
|
||||
&::after { |
||||
position: absolute; |
||||
color: rgba(black, .4); |
||||
right: dist(0); |
||||
content: '▸'; |
||||
|
||||
top:50%; |
||||
font-size: 120%; |
||||
font-weight: bold; |
||||
transform: translate(0,-50%) rotate(90deg); |
||||
} |
||||
} |
||||
} |
||||
&.expanded #brand { |
||||
background: darken($menu-bg, 10%); |
||||
|
||||
@include media($phone) { |
||||
&:after { transform: translate(-25%,-50%) rotate(-90deg) } |
||||
} |
||||
} |
||||
|
||||
a { |
||||
font-size: 130%; |
||||
color: $menu-fg; |
||||
|
||||
transition: background-color 0.2s; |
||||
text-shadow: 0 0 5px rgba(black, .4); |
||||
|
||||
&:hover, &.selected { |
||||
background: $menu-hl; |
||||
text-shadow: 0 0 5px rgba(black, .6); |
||||
} |
||||
|
||||
&.selected { |
||||
position: relative; |
||||
box-shadow: 0 0 5px rgba(black, .5); |
||||
} |
||||
|
||||
&::before { |
||||
content: "▸"; |
||||
padding-right: .5rem; |
||||
position: relative; |
||||
top: -0.1rem; |
||||
} |
||||
|
||||
@include media($phone) { |
||||
display: none; |
||||
} |
||||
} |
||||
|
||||
&.expanded a { display:block } |
||||
|
||||
@include media($tablet) { |
||||
#brand { |
||||
font-size: 95%; |
||||
margin-bottom: dist(-1); |
||||
} |
||||
|
||||
a { font-size: 105%; } |
||||
|
||||
flex-basis: 10rem; |
||||
|
||||
& > * { padding: dist(-2) dist(-1); } |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
/* Main outer container */ |
||||
#outer { |
||||
display: flex; |
||||
|
||||
position: absolute; |
||||
width: 100%; |
||||
height: 100%; |
||||
left: 0; |
||||
right: 0; |
||||
top: 0; |
||||
bottom: 0; |
||||
overflow: hidden; |
||||
|
||||
flex-direction: row; |
||||
} |
||||
|
||||
@include media($phone) { |
||||
#outer { |
||||
display: block; |
||||
overflow-y: scroll; |
||||
} |
||||
} |
@ -0,0 +1,68 @@ |
||||
|
||||
// Utilities for background tiling |
||||
|
||||
// Use a tile as background (w, h - size of time) |
||||
@mixin tile_xy($w, $h, $x, $y) { |
||||
background-position: (-$x*$w) (-$y*$h); |
||||
} |
||||
|
||||
|
||||
// Use a square tile as background (size - w & h of time) |
||||
@mixin tile($size, $x, $y) { |
||||
@include tile_xy($size, $size, $x, $y); |
||||
} |
||||
|
||||
|
||||
// Button with sprite-sheet |
||||
// A B |
||||
// B:hover B:hover |
||||
@mixin tile_btn_h($w, $h, $x) { |
||||
@include tile_xy($w, $h, $x, 0); |
||||
&:hover { |
||||
@include tile_xy($w, $h, $x, 1); |
||||
} |
||||
} |
||||
|
||||
// active the same as hover |
||||
@mixin tile_btn_h_act($w, $h, $x) { |
||||
@include tile_xy($w, $h, $x, 0); |
||||
&:hover, &.active { |
||||
@include tile_xy($w, $h, $x, 1); |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
// Button with sprite-sheet |
||||
// A A:hover |
||||
// B B:hover |
||||
@mixin tile_btn_v($w, $h, $y) { |
||||
@include tile_xy($w, $h, 0, $y); |
||||
&:hover { |
||||
@include tile_xy($w, $h, 1, $y); |
||||
} |
||||
} |
||||
|
||||
// active the same as hover |
||||
@mixin tile_btn_v_act($w, $h, $y) { |
||||
@include tile_xy($w, $h, 0, $y); |
||||
&:hover, &.active { |
||||
@include tile_xy($w, $h, 1, $y); |
||||
} |
||||
} |
||||
|
||||
@mixin inset-shadow-top($w, $c) { |
||||
box-shadow: inset 0 $w ($w*2) (-$w) $c; |
||||
} |
||||
|
||||
@mixin inset-shadow-bottom($w, $c) { |
||||
box-shadow: inset 0 (-$w) ($w*2) (-$w) $c; |
||||
} |
||||
|
||||
@mixin inset-shadow-left($w, $c) { |
||||
box-shadow: inset $w 0 ($w*2) (-$w) $c; |
||||
} |
||||
|
||||
@mixin inset-shadow-right($w, $c) { |
||||
box-shadow: inset (-$w) 0 ($w*2) (-$w) $c; |
||||
} |
@ -0,0 +1,3 @@ |
||||
@import "background-tiling"; |
||||
@import "pointer"; |
||||
@import "misc"; |
@ -0,0 +1,34 @@ |
||||
// Add a highlight for debugging |
||||
@mixin highlight($color) { |
||||
outline: 1px solid $color; |
||||
background: rgba($color, .05); |
||||
box-shadow: 0 0 2px 2px rgba($color, .2), inset 0 0 2px 2px rgba($color, .2); |
||||
} |
||||
|
||||
// Ellipsis, but for block elements |
||||
@mixin block-ellipsis($width: 100%) { |
||||
display: block; |
||||
max-width: $width; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
word-wrap: normal; |
||||
} |
||||
|
||||
// No margins, padding, borders |
||||
@mixin naked() { |
||||
border: 0 none; |
||||
margin: 0; |
||||
padding: 0; |
||||
text-decoration: none; |
||||
} |
||||
|
||||
@mixin translate($x, $y) { |
||||
@include transform(translate($x, $y)); |
||||
} |
||||
|
||||
// Disallow wrapping |
||||
@mixin nowrap() { |
||||
white-space: nowrap; |
||||
word-wrap: normal; |
||||
} |
@ -0,0 +1,26 @@ |
||||
|
||||
@mixin click-through() { |
||||
pointer-events: none; |
||||
} |
||||
|
||||
|
||||
// Disallow text selection |
||||
@mixin noselect() { |
||||
-webkit-user-select: none; |
||||
-khtml-user-select: none; |
||||
-moz-user-select: none; |
||||
-ms-user-select: none; |
||||
user-select: none; |
||||
} |
||||
|
||||
|
||||
// Allow text selection |
||||
@mixin can-select() { |
||||
-webkit-user-select: text; |
||||
-khtml-user-select: text; |
||||
-moz-user-select: text; |
||||
-ms-user-select: text; |
||||
user-select: text; |
||||
|
||||
cursor: text; |
||||
} |
@ -1,3 +1,3 @@ |
||||
#!/bin/bash |
||||
|
||||
xterm -e "php -S localhost:2000" |
||||
xterm -e "php -S 0.0.0.0:2000" |
||||
|
Loading…
Reference in new issue