kinda working

This commit is contained in:
Chris 2023-10-18 22:42:01 +02:00
parent 8b6ea946bf
commit b3ded91d01
18 changed files with 1535 additions and 15 deletions

View File

@ -8,6 +8,19 @@
## Start Dev
### Beim ersten Start
Config file erstellen bzw kopieren
1. `cd web/api/inc`
2. Datei `example.config.inc.php` umbenennen auf `config.inc.php`
3. Gegebenenfalls Werte anpassen in der Config
Composer sachen holen
1. `cd web/api/inc`
2. `composer install`
### Linux
Erst Tailwind starten

2
web/api/inc/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
vendor
config.inc.php

View File

@ -0,0 +1,113 @@
<?php
/**
* Page is the Controller Class
* which should be extended by any page
*
* @author Christian Haschek
*/
class Page
{
protected $_controller;
protected $_action;
protected $_template;
public $variables;
public $params;
public $render;
public $menu_text;
public $menu_image;
public $menu_priority;
public $submenu;
function __construct($controller, $action, $r = 1, $params = [])
{
$this->_controller = $controller;
$this->_action = $action;
$this->render = $r;
$this->submenu = array();
$this->setMenu();
$this->params = $params;
if(is_array($GLOBALS['vars']) && is_array($this->params))
$this->params = array_merge($this->params, $GLOBALS['vars']);
$this->menu_image = '/css/imgs/empty.png';
}
function setMenu()
{
$this->menu_text = '';
$this->menu_priority = 1;
}
function redirect($url)
{
header("Location: $url");
exit();
}
/**
* override this function to check if a user can use this object
* @return true -> user will be able to access
* @return false -> user will not be able to access and this page won't
* be shown in the menu
*
*/
public function maySeeThisPage()
{
return true;
}
function set($name, $value)
{
$this->variables[$name] = $value;
}
function get($name)
{
return $this->variables[$name];
}
function __destruct()
{
// if($this->render)
// $this->_template->render();
}
function addHelpers($m)
{
$m->addHelper('case', [
'lower' => function($value) { return strtolower((string) $value); },
'upper' => function($value) { return strtoupper((string) $value); }
]);
$m->addHelper('!!', function($value) { return $value . '!!'; });
return $m;
}
function renderPagecontent()
{
$controller_dir = ROOT . DS . 'pages' . DS . $this->_controller . DS;
$partials = [];
if(is_dir($controller_dir.'partials'))
$partials[] = new Mustache_Loader_FilesystemLoader($controller_dir.'partials');
$partials[] = new Mustache_Loader_FilesystemLoader(ROOT.DS.'..'.DS.'templates'.DS.'partials');
$m = new Mustache_Engine(array(
'entity_flags' => ENT_QUOTES,
'partials_loader' => new Mustache_Loader_CascadingLoader($partials)
));
$this->variables['translate'] = function($value) { return translate($value); };
$m = $this->addHelpers($m);
if($this->variables['template'] && file_exists(ROOT . DS . 'pages' . DS . $this->_controller . DS . $this->variables['template']))
$template = file_get_contents(ROOT . DS . 'pages' . DS . $this->_controller . DS . $this->variables['template']);
else
$template = file_get_contents(ROOT . DS . 'views' . DS . 'defaultcontainer.mustache');
$pagecontent = $m->render($template, $this->variables);
return $pagecontent;
}
}

View File

@ -0,0 +1,5 @@
{
"require": {
"mustache/mustache": "^2.14"
}
}

69
web/api/inc/composer.lock generated Normal file
View File

@ -0,0 +1,69 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e982fabf51f3a80b1d9fcbcdb4621616",
"packages": [
{
"name": "mustache/mustache",
"version": "v2.14.2",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/mustache.php.git",
"reference": "e62b7c3849d22ec55f3ec425507bf7968193a6cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/e62b7c3849d22ec55f3ec425507bf7968193a6cb",
"reference": "e62b7c3849d22ec55f3ec425507bf7968193a6cb",
"shasum": ""
},
"require": {
"php": ">=5.2.4"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~1.11",
"phpunit/phpunit": "~3.7|~4.0|~5.0"
},
"type": "library",
"autoload": {
"psr-0": {
"Mustache": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Justin Hileman",
"email": "justin@justinhileman.info",
"homepage": "http://justinhileman.com"
}
],
"description": "A Mustache implementation in PHP.",
"homepage": "https://github.com/bobthecow/mustache.php",
"keywords": [
"mustache",
"templating"
],
"support": {
"issues": "https://github.com/bobthecow/mustache.php/issues",
"source": "https://github.com/bobthecow/mustache.php/tree/v2.14.2"
},
"time": "2022-08-23T13:07:01+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.2.0"
}

72
web/api/inc/core.php Normal file
View File

@ -0,0 +1,72 @@
<?php
spl_autoload_register('autoload');
function autoload($className)
{
//one of the global classes?
if (file_exists(ROOT . DS . 'inc'. DS. 'classes' . DS . $className . '.class.php'))
require_once(ROOT . DS . 'inc'. DS. 'classes' . DS . $className . '.class.php');
else if (file_exists(ROOT . DS . 'pages' . DS . strtolower($className) . DS . 'controller.php'))
require_once(ROOT . DS . 'pages' . DS . strtolower($className) . DS . 'controller.php');
}
function includeManagement()
{
require_once(ROOT.DS.'inc'.DS.'helpers.php');
require_once(ROOT.DS.'inc'.DS.'config.inc.php');
//settings from config
if(defined('DEV') && DEV===true)
ini_set("display_errors", 1);
else ini_set("display_errors", 0);
if(file_exists(ROOT.DS.'inc'.DS.'vendor'.DS.'autoload.php'))
require_once(ROOT.DS.'inc'.DS.'vendor'.DS.'autoload.php');
}
function callHook($url)
{
$queryString = array();
if (!$url[0]) {
$component = 'home';
$action = 'index';
} else {
$urlArray = $url;
$component = $urlArray[0];
array_shift($urlArray);
$params = $urlArray;
if (isset($urlArray[0])) {
$action = $urlArray[0];
array_shift($urlArray);
} else
$action = 'index'; // Default Action
$queryString = $urlArray;
}
if (!file_exists(ROOT . DS . 'pages' . DS . $component . DS . 'controller.php')) {
$component = 'err';
$action = 'notfound';
$queryString = array($url);
}
$componentName = ucfirst($component);
$dispatch = new $componentName($component, $action, false);
if (!$dispatch->maySeeThisPage()) {
$componentName = 'err';
$action = 'notallowed';
$dispatch = new $componentName('error', $action, true);
} else
$dispatch = new $componentName($component, $action, true, $queryString);
if (method_exists($componentName, $action)) {
call_user_func_array(array($dispatch, $action), $queryString);
} else if (method_exists($componentName, 'catchAll'))
call_user_func_array(array($dispatch, 'catchAll'), array($params));
return $dispatch->renderPagecontent();
}

View File

@ -0,0 +1,2 @@
<?php

170
web/api/inc/helpers.php Normal file
View File

@ -0,0 +1,170 @@
<?php
function sendMail($rcpt,$subject,$markdown)
{
$mail = new PHPMailer();
$pd = new Parsedown();
$html = $pd->text($markdown);
ob_start();
$mail->CharSet ="UTF-8";
$mail->SMTPDebug = SMTP::DEBUG_SERVER; // Enable verbose debug output
$mail->isSMTP(); // Send using SMTP
$mail->Host = SMTP_HOST; // Set the SMTP server to send through
$mail->SMTPAuth = (defined('SMTP_AUTH')?SMTP_AUTH:true); // Enable SMTP authentication
$mail->Username = SMTP_USER; // SMTP username
$mail->Password = SMTP_PW; // SMTP password
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` also accepted
$mail->Port = (defined('SMTP_PORT')?SMTP_PORT:587); // TCP port to connect to
if(defined('SMTP_EHLO_DOMAIN') && SMTP_EHLO_DOMAIN)
$mail->Hostname = SMTP_EHLO_DOMAIN;
//make sure we use ipv4
$mail->SMTPOptions = [
'socket' => [
'bindto' => "0:0",
],
];
//Recipients
$mail->setFrom(EMAIL_FROM_EMAIL, EMAIL_FROM_NAME);
$mail->addAddress($rcpt); // Add a recipient
// Content
$mail->isHTML(true); // Set email format to HTML
$mail->Subject = $subject;
$mail->Body = $html;
$mail->AltBody = $markdown;
$mail->send();
$output = ob_get_clean();
addToMailLog($rcpt,$subject,$output);
return $output;
}
// found on https://html-online.com/articles/php-get-ip-cloudflare-proxy/
function getUserIP() {
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
$_SERVER['HTTP_CLIENT_IP'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
}
$client = @$_SERVER['HTTP_CLIENT_IP'];
$forward = @$_SERVER['HTTP_X_FORWARDED_FOR'];
$remote = $_SERVER['REMOTE_ADDR'];
if(filter_var($client, FILTER_VALIDATE_IP)) { $ip = $client; }
elseif(filter_var($forward, FILTER_VALIDATE_IP)) { $ip = $forward; }
else { $ip = $remote; }
return $ip;
}
// from https://stackoverflow.com/a/834355/1174516
function startsWith( $haystack, $needle ) {
$length = strlen( $needle );
return substr( $haystack, 0, $length ) === $needle;
}
function endsWith( $haystack, $needle ) {
$length = strlen( $needle );
if( !$length ) {
return true;
}
return substr( $haystack, -$length ) === $needle;
}
function is_cli()
{
if ( defined('STDIN') )
return true;
if ( php_sapi_name() === 'cli' )
return true;
if ( array_key_exists('SHELL', $_ENV) )
return true;
if ( empty($_SERVER['REMOTE_ADDR']) and !isset($_SERVER['HTTP_USER_AGENT']) and count($_SERVER['argv']) > 0)
return true;
if ( !array_key_exists('REQUEST_METHOD', $_SERVER) )
return true;
return false;
}
function addToLog($text,$module='general')
{
$fp = fopen(ROOT.DS.'..'.DS.'log'.DS.$module.'.log','a');
fwrite($fp,'['.date("y.m.d H:i").']'.$text.PHP_EOL);
fclose($fp);
}
function addToMailLog($rcpt,$subject,$response)
{
$rcpt_esc = str_replace('@','_at_',$rcpt);
$dir = ROOT.DS.'..'.DS.'log'.DS.'maillog';
if(!is_dir($dir))
mkdir($dir);
$fp = fopen($dir.DS.$rcpt_esc.'.log','a');
fwrite($fp,"========= NEW MAIL ========\n[".date("y.m.d H:i")."] To: $rcpt\nSubject: $subject\n\n$response\n\n");
fclose($fp);
}
function translate($what)
{
$what = trim($what);
return ($GLOBALS['translations'][$what]?:$what);
}
function getFilesOfFolder($dir)
{
return array_diff(scandir($dir), array('.', '..'));
}
function dbNeedsToBeUpgraded()
{
if(DB_TYPE=='sqlite' && !file_exists(ROOT.DS.'..'.DS.'data'.DS.'db.sqlite3'))
return true;
else if(!file_exists(ROOT.DS.'..'.DS.'log'.DS.'db_version'))
return true;
else if($GLOBALS['db_version']<getHighestSQLVersion())
return true;
return false;
}
function getHighestSQLVersion()
{
$dir = ROOT.DS.'..'.DS.'sql'.DS;
$files = array_diff(scandir($dir), array('..', '.'));
$files = array_map(function($e){
return pathinfo($e, PATHINFO_FILENAME);
}, $files);
sort($files);
return end($files);
}
function gen_uuid() {
return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
// 16 bits for "time_mid"
mt_rand( 0, 0xffff ),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
mt_rand( 0, 0x0fff ) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
mt_rand( 0, 0x3fff ) | 0x8000,
// 48 bits for "node"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
);
}

33
web/api/index.php Normal file
View File

@ -0,0 +1,33 @@
<?php
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(__FILE__));
// if(file_exists(ROOT.DS.'inc'.DS.'config.inc.php'))
// include_once(ROOT.DS.'inc'.DS.'config.inc.php');
// else die('ERR: no config file found :(');
include_once(ROOT.DS.'inc'.DS.'core.php');
includeManagement();
if($_GET['url'])
$url = explode('/',ltrim(parse_url($_GET['url'], PHP_URL_PATH),'/'));
else if($_SERVER['HTTP_HX_CURRENT_URL'])
$url = explode('/',ltrim(parse_url($_SERVER['HTTP_HX_CURRENT_URL'], PHP_URL_PATH),'/'));
else $url = array_filter(explode('/',ltrim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH),'/')));
if(count($url) > 0 && ($url[0]=='api' || $url[0]=='backend'))
array_shift($url);
//echo print_r(['url'=>$url,'server'=>$_SERVER,'request'=>$_REQUEST,'cookie'=>$_COOKIE,'session'=>$_SESSION],true);
$response = callHook($url);
if(is_string($response))
echo $response;
else if(is_array($response))
echo json_encode($response);
else if(is_object($response))
echo json_encode($response);
else echo json_encode(['error'=>'unknown response type']);

View File

@ -0,0 +1,19 @@
<?php
class Home extends Page
{
function setMenu()
{
$this->menu_text = 'home';
$this->menu_image = 'mdi-action-home';
$this->menu_priority = 0;
}
function index()
{
$this->set('template', "home.mustache");
return $this->renderPagecontent();
}
function maySeeThisPage(){return true;}
}

View File

@ -0,0 +1,20 @@
<div class="hidden sm:mb-8 sm:flex sm:justify-center">
<div
class="relative rounded-full px-3 py-1 text-sm leading-6 text-gray-600 ring-1 ring-gray-900/10 hover:ring-gray-900/20">
Announcing our next round of funding. <a href="#" class="font-semibold text-indigo-600"><span
class="absolute inset-0" aria-hidden="true"></span>Read more <span
aria-hidden="true">&rarr;</span></a>
</div>
</div>
<div class="text-center">
<h1 class="text-4xl font-bold tracking-tight text-gray-900 sm:text-6xl">Dogstats - der Agilityhelfer</h1>
<p class="mt-6 text-lg leading-8 text-gray-600">Anim aute id magna aliqua ad ad non deserunt sunt.
Qui irure qui lorem cupidatat commodo. Elit sunt amet fugiat veniam occaecat fugiat aliqua.</p>
<div class="mt-10 flex items-center justify-center gap-x-6">
<a href="#"
class="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Get
started</a>
<a href="#" class="text-sm font-semibold leading-6 text-gray-900">Learn more <span
aria-hidden="true">→</span></a>
</div>
</div>

View File

@ -534,27 +534,622 @@ video {
--tw-backdrop-sepia: ;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.fixed {
position: fixed;
}
.absolute {
position: absolute;
}
.relative {
position: relative;
}
.inset-0 {
inset: 0px;
}
.inset-x-0 {
left: 0px;
right: 0px;
}
.inset-y-0 {
top: 0px;
bottom: 0px;
}
.-top-40 {
top: -10rem;
}
.left-\[calc\(50\%\+3rem\)\] {
left: calc(50% + 3rem);
}
.left-\[calc\(50\%-11rem\)\] {
left: calc(50% - 11rem);
}
.right-0 {
right: 0px;
}
.top-0 {
top: 0px;
}
.top-\[calc\(100\%-13rem\)\] {
top: calc(100% - 13rem);
}
.isolate {
isolation: isolate;
}
.-z-10 {
z-index: -10;
}
.z-50 {
z-index: 50;
}
.-m-1 {
margin: -0.25rem;
}
.-m-1\.5 {
margin: -0.375rem;
}
.-m-2 {
margin: -0.5rem;
}
.-m-2\.5 {
margin: -0.625rem;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.-mx-3 {
margin-left: -0.75rem;
margin-right: -0.75rem;
}
.-my-6 {
margin-top: -1.5rem;
margin-bottom: -1.5rem;
}
.mt-10 {
margin-top: 2.5rem;
}
.mt-6 {
margin-top: 1.5rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
.mb-6 {
margin-bottom: 1.5rem;
}
.ml-3 {
margin-left: 0.75rem;
}
.mr-2 {
margin-right: 0.5rem;
}
.block {
display: block;
}
.flex {
display: flex;
}
.inline-flex {
display: inline-flex;
}
.table {
display: table;
}
.flow-root {
display: flow-root;
}
.hidden {
display: none;
}
.aspect-\[1155\/678\] {
aspect-ratio: 1155/678;
}
.h-24 {
height: 6rem;
}
.h-6 {
height: 1.5rem;
}
.h-8 {
height: 2rem;
}
.h-12 {
height: 3rem;
}
.h-4 {
height: 1rem;
}
.h-5 {
height: 1.25rem;
}
.w-24 {
width: 6rem;
}
.w-6 {
width: 1.5rem;
}
.w-\[36\.125rem\] {
width: 36.125rem;
}
.w-auto {
width: auto;
}
.w-full {
width: 100%;
}
.w-12 {
width: 3rem;
}
.w-4 {
width: 1rem;
}
.w-8 {
width: 2rem;
}
.max-w-2xl {
max-width: 42rem;
}
.max-w-sm {
max-width: 24rem;
}
.shrink-0 {
flex-shrink: 0;
}
.-translate-x-1\/2 {
--tw-translate-x: -50%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.rotate-\[30deg\] {
--tw-rotate: 30deg;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.transform-gpu {
transform: translate3d(var(--tw-translate-x), var(--tw-translate-y), 0) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.flex-col {
flex-direction: column;
}
.items-start {
align-items: flex-start;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.justify-between {
justify-content: space-between;
}
.gap-x-6 {
-moz-column-gap: 1.5rem;
column-gap: 1.5rem;
}
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
}
.space-y-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
}
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.divide-y > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}
.divide-gray-500\/10 > :not([hidden]) ~ :not([hidden]) {
border-color: rgb(107 114 128 / 0.1);
}
.overflow-hidden {
overflow: hidden;
}
.overflow-y-auto {
overflow-y: auto;
}
.rounded-full {
border-radius: 9999px;
}
.rounded-xl {
border-radius: 0.75rem;
}
.rounded-lg {
border-radius: 0.5rem;
}
.rounded-md {
border-radius: 0.375rem;
}
.rounded {
border-radius: 0.25rem;
}
.border {
border-width: 1px;
}
.border-gray-300 {
--tw-border-opacity: 1;
border-color: rgb(209 213 219 / var(--tw-border-opacity));
}
.bg-slate-100 {
--tw-bg-opacity: 1;
background-color: rgb(241 245 249 / var(--tw-bg-opacity));
}
.bg-indigo-600 {
--tw-bg-opacity: 1;
background-color: rgb(79 70 229 / var(--tw-bg-opacity));
}
.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.bg-gray-50 {
--tw-bg-opacity: 1;
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
}
.bg-gradient-to-tr {
background-image: linear-gradient(to top right, var(--tw-gradient-stops));
}
.from-\[\#ff80b5\] {
--tw-gradient-from: #ff80b5 var(--tw-gradient-from-position);
--tw-gradient-to: rgb(255 128 181 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-\[\#9089fc\] {
--tw-gradient-to: #9089fc var(--tw-gradient-to-position);
}
.p-8 {
padding: 2rem;
}
.p-1 {
padding: 0.25rem;
}
.p-1\.5 {
padding: 0.375rem;
}
.p-2 {
padding: 0.5rem;
}
.p-2\.5 {
padding: 0.625rem;
}
.p-6 {
padding: 1.5rem;
}
.px-3 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.px-3\.5 {
padding-left: 0.875rem;
padding-right: 0.875rem;
}
.px-6 {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.py-1 {
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.py-2\.5 {
padding-top: 0.625rem;
padding-bottom: 0.625rem;
}
.py-32 {
padding-top: 8rem;
padding-bottom: 8rem;
}
.py-6 {
padding-top: 1.5rem;
padding-bottom: 1.5rem;
}
.px-5 {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.py-8 {
padding-top: 2rem;
padding-bottom: 2rem;
}
.py-5 {
padding-top: 1.25rem;
padding-bottom: 1.25rem;
}
.pt-6 {
padding-top: 1.5rem;
}
.pt-14 {
padding-top: 3.5rem;
}
.text-center {
text-align: center;
}
.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
}
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
}
.text-4xl {
font-size: 2.25rem;
line-height: 2.5rem;
}
.text-base {
font-size: 1rem;
line-height: 1.5rem;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.font-bold {
font-weight: 700;
}
.font-medium {
font-weight: 500;
}
.font-semibold {
font-weight: 600;
}
.font-light {
font-weight: 300;
}
.leading-6 {
line-height: 1.5rem;
}
.leading-7 {
line-height: 1.75rem;
}
.leading-8 {
line-height: 2rem;
}
.leading-tight {
line-height: 1.25;
}
.tracking-tight {
letter-spacing: -0.025em;
}
.text-sky-500 {
--tw-text-opacity: 1;
color: rgb(14 165 233 / var(--tw-text-opacity));
}
.text-slate-700 {
--tw-text-opacity: 1;
color: rgb(51 65 85 / var(--tw-text-opacity));
}
.text-gray-600 {
--tw-text-opacity: 1;
color: rgb(75 85 99 / var(--tw-text-opacity));
}
.text-gray-700 {
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
}
.text-gray-900 {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity));
}
.text-indigo-600 {
--tw-text-opacity: 1;
color: rgb(79 70 229 / var(--tw-text-opacity));
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.text-black {
--tw-text-opacity: 1;
color: rgb(0 0 0 / var(--tw-text-opacity));
}
.text-slate-500 {
--tw-text-opacity: 1;
color: rgb(100 116 139 / var(--tw-text-opacity));
}
.text-gray-500 {
--tw-text-opacity: 1;
color: rgb(107 114 128 / var(--tw-text-opacity));
}
.underline {
text-decoration-line: underline;
}
.opacity-30 {
opacity: 0.3;
}
.shadow-sm {
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-lg {
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.ring-1 {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
.ring-gray-900\/10 {
--tw-ring-color: rgb(17 24 39 / 0.1);
}
.blur-3xl {
--tw-blur: blur(64px);
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
@ -569,4 +1164,270 @@ video {
.\[a-zA-Z\:\\-\\\.\] {
a-z-a--z: \-\.;
}
.hover\:bg-gray-50:hover {
--tw-bg-opacity: 1;
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
}
.hover\:bg-indigo-500:hover {
--tw-bg-opacity: 1;
background-color: rgb(99 102 241 / var(--tw-bg-opacity));
}
.hover\:underline:hover {
text-decoration-line: underline;
}
.hover\:ring-gray-900\/20:hover {
--tw-ring-color: rgb(17 24 39 / 0.2);
}
.focus\:outline-none:focus {
outline: 2px solid transparent;
outline-offset: 2px;
}
.focus\:ring-4:focus {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
.focus-visible\:outline:focus-visible {
outline-style: solid;
}
.focus-visible\:outline-2:focus-visible {
outline-width: 2px;
}
.focus-visible\:outline-offset-2:focus-visible {
outline-offset: 2px;
}
.focus-visible\:outline-indigo-600:focus-visible {
outline-color: #4f46e5;
}
@media (prefers-color-scheme: dark) {
.dark\:border {
border-width: 1px;
}
.dark\:border-gray-600 {
--tw-border-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-border-opacity));
}
.dark\:border-gray-700 {
--tw-border-opacity: 1;
border-color: rgb(55 65 81 / var(--tw-border-opacity));
}
.dark\:bg-slate-800 {
--tw-bg-opacity: 1;
background-color: rgb(30 41 59 / var(--tw-bg-opacity));
}
.dark\:bg-gray-700 {
--tw-bg-opacity: 1;
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
}
.dark\:bg-gray-800 {
--tw-bg-opacity: 1;
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
}
.dark\:bg-gray-900 {
--tw-bg-opacity: 1;
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
}
.dark\:text-sky-400 {
--tw-text-opacity: 1;
color: rgb(56 189 248 / var(--tw-text-opacity));
}
.dark\:text-slate-500 {
--tw-text-opacity: 1;
color: rgb(100 116 139 / var(--tw-text-opacity));
}
.dark\:text-gray-300 {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
}
.dark\:text-gray-400 {
--tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity));
}
.dark\:text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.dark\:placeholder-gray-400::-moz-placeholder {
--tw-placeholder-opacity: 1;
color: rgb(156 163 175 / var(--tw-placeholder-opacity));
}
.dark\:placeholder-gray-400::placeholder {
--tw-placeholder-opacity: 1;
color: rgb(156 163 175 / var(--tw-placeholder-opacity));
}
.dark\:ring-offset-gray-800 {
--tw-ring-offset-color: #1f2937;
}
.dark\:focus\:border-blue-500:focus {
--tw-border-opacity: 1;
border-color: rgb(59 130 246 / var(--tw-border-opacity));
}
.dark\:focus\:ring-blue-500:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
}
}
@media (min-width: 640px) {
.sm\:-top-80 {
top: -20rem;
}
.sm\:left-\[calc\(50\%\+36rem\)\] {
left: calc(50% + 36rem);
}
.sm\:left-\[calc\(50\%-30rem\)\] {
left: calc(50% - 30rem);
}
.sm\:top-\[calc\(100\%-30rem\)\] {
top: calc(100% - 30rem);
}
.sm\:mb-8 {
margin-bottom: 2rem;
}
.sm\:flex {
display: flex;
}
.sm\:w-\[72\.1875rem\] {
width: 72.1875rem;
}
.sm\:max-w-sm {
max-width: 24rem;
}
.sm\:max-w-md {
max-width: 28rem;
}
.sm\:justify-center {
justify-content: center;
}
.sm\:p-8 {
padding: 2rem;
}
.sm\:py-48 {
padding-top: 12rem;
padding-bottom: 12rem;
}
.sm\:text-6xl {
font-size: 3.75rem;
line-height: 1;
}
.sm\:text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.sm\:ring-1 {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
.sm\:ring-gray-900\/10 {
--tw-ring-color: rgb(17 24 39 / 0.1);
}
}
@media (min-width: 768px) {
.md\:mt-0 {
margin-top: 0px;
}
.md\:h-screen {
height: 100vh;
}
.md\:space-y-6 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
}
.md\:text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
}
@media (min-width: 1024px) {
.lg\:flex {
display: flex;
}
.lg\:hidden {
display: none;
}
.lg\:flex-1 {
flex: 1 1 0%;
}
.lg\:justify-end {
justify-content: flex-end;
}
.lg\:gap-x-12 {
-moz-column-gap: 3rem;
column-gap: 3rem;
}
.lg\:px-8 {
padding-left: 2rem;
padding-right: 2rem;
}
.lg\:py-56 {
padding-top: 14rem;
padding-bottom: 14rem;
}
.lg\:py-0 {
padding-top: 0px;
padding-bottom: 0px;
}
}
@media (min-width: 1280px) {
.xl\:p-0 {
padding: 0px;
}
}

BIN
web/imgs/dogstats-50.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
web/imgs/dogstats.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -1,17 +1,112 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- Tailwind CSS -->
<link href="/css/output.css" rel="stylesheet">
<!-- htmx -->
<script src="/js/htmx.min"></script>
</head>
<body>
<h1 class="text-3xl font-bold underline">
Hello world!
</h1>
</body>
</html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dogstats</title>
<link href="/css/output.css" rel="stylesheet">
<script src="/js/htmx.min.js"></script>
</head>
<body></body>
<div class="bg-white">
<header class="absolute inset-x-0 top-0 z-50">
<nav class="flex items-center justify-between p-6 lg:px-8" aria-label="Global">
<div class="flex lg:flex-1">
<a href="#" hx-get="/api" hx-target="#main" class="-m-1.5 p-1.5">
<span class="sr-only">Dogstats</span>
<img class="h-8 w-auto" src="/imgs/dogstats-50.png" alt="">
</a>
</div>
<div class="flex lg:hidden">
<button type="button"
class="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700">
<span class="sr-only">Open main menu</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
</button>
</div>
<div class="hidden lg:flex lg:gap-x-12">
<a href="#" hx-get="/templates/product.html" hx-target="#main" class="text-sm font-semibold leading-6 text-gray-900">Product</a>
<a href="#" hx-get="/templates/features.html" hx-target="#main" class="text-sm font-semibold leading-6 text-gray-900">Features</a>
</div>
<div class="hidden lg:flex lg:flex-1 lg:justify-end">
<a href="#" hx-get="/templates/login.html" hx-target="#main" class="text-sm font-semibold leading-6 text-gray-900">Log in <span
aria-hidden="true">&rarr;</span></a>
</div>
</nav>
<!-- Mobile menu, show/hide based on menu open state. -->
<div class="lg:hidden" role="dialog" aria-modal="true">
<!-- Background backdrop, show/hide based on slide-over state. -->
<div class="fixed inset-0 z-50"></div>
<div
class="fixed inset-y-0 right-0 z-50 w-full overflow-y-auto bg-white px-6 py-6 sm:max-w-sm sm:ring-1 sm:ring-gray-900/10">
<div class="flex items-center justify-between">
<a href="#" class="-m-1.5 p-1.5">
<span class="sr-only">Dogstatsy</span>
<img class="h-8 w-auto"
src="/imgs/dogstats-50.png" alt="">
</a>
<button type="button" class="-m-2.5 rounded-md p-2.5 text-gray-700">
<span class="sr-only">Close menu</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="mt-6 flow-root">
<div class="-my-6 divide-y divide-gray-500/10">
<div class="space-y-2 py-6">
<a href="#"
class="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50">Product</a>
<a href="#"
class="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50">Features</a>
</div>
<div class="py-6">
<a href="#"
class="-mx-3 block rounded-lg px-3 py-2.5 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50">Log
in</a>
</div>
</div>
</div>
</div>
</div>
</header>
<div class="relative isolate px-6 pt-14 lg:px-8">
<div class="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80"
aria-hidden="true">
<div class="relative left-[calc(50%-11rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-30 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]"
style="clip-path: polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)">
</div>
</div>
<div id="main" hx-get="/api" hx-trigger="load" class="mx-auto max-w-2xl">
<div class="hidden sm:mb-8 sm:flex sm:justify-center">
<div
class="relative rounded-full px-3 py-1 text-sm leading-6 text-gray-600 ring-1 ring-gray-900/10 hover:ring-gray-900/20">
Announcing our next round of funding. <a href="#" class="font-semibold text-indigo-600"><span
class="absolute inset-0" aria-hidden="true"></span>Read more <span
aria-hidden="true">&rarr;</span></a>
</div>
</div>
<div class="text-center">
<h1 class="text-4xl font-bold tracking-tight text-gray-900 sm:text-6xl">Dogstats - der Agilityhelfer</h1>
<p class="mt-6 text-lg leading-8 text-gray-600">Anim aute id magna aliqua ad ad non deserunt sunt.
Qui irure qui lorem cupidatat commodo. Elit sunt amet fugiat veniam occaecat fugiat aliqua.</p>
<div class="mt-10 flex items-center justify-center gap-x-6">
<a href="#"
class="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Get
started</a>
<a href="#" class="text-sm font-semibold leading-6 text-gray-900">Learn more <span
aria-hidden="true">→</span></a>
</div>
</div>
</div>
</div>
</div>
</html>

View File

@ -0,0 +1 @@
soo many features prolly

36
web/templates/login.html Normal file
View File

@ -0,0 +1,36 @@
<section class="">
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<div class="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Sign in to your account
</h1>
<form class="space-y-4 md:space-y-6" action="#">
<div>
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
<input type="email" name="email" id="email" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" required="">
</div>
<div>
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
<input type="password" name="password" id="password" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required="">
</div>
<div class="flex items-center justify-between">
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="remember" aria-describedby="remember" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-primary-600 dark:ring-offset-gray-800" required="">
</div>
<div class="ml-3 text-sm">
<label for="remember" class="text-gray-500 dark:text-gray-300">Remember me</label>
</div>
</div>
<a href="#" class="text-sm font-medium text-primary-600 hover:underline dark:text-primary-500">Forgot password?</a>
</div>
<button type="submit" class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Sign in</button>
<p class="text-sm font-light text-gray-500 dark:text-gray-400">
Dont have an account yet? <a href="#" class="font-medium text-primary-600 hover:underline dark:text-primary-500">Sign up</a>
</p>
</form>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,9 @@
<div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4">
<div class="shrink-0">
<img class="h-12 w-12" src="/imgs/dogstats.png" alt="Logo">
</div>
<div>
<div class="text-xl font-medium text-black">Dogstats is super</div>
<p class="text-slate-500">You have a new message!</p>
</div>
</div>