does it work?
|
@ -39,6 +39,7 @@ library
|
||||||
Handler.Common
|
Handler.Common
|
||||||
Handler.Home
|
Handler.Home
|
||||||
Handler.Station
|
Handler.Station
|
||||||
|
Handler.Ad
|
||||||
Import
|
Import
|
||||||
Import.NoFoundation
|
Import.NoFoundation
|
||||||
Model
|
Model
|
||||||
|
@ -72,6 +73,7 @@ library
|
||||||
, persistent >=2.9 && <2.15
|
, persistent >=2.9 && <2.15
|
||||||
, persistent-sqlite >=2.9 && <2.14
|
, persistent-sqlite >=2.9 && <2.14
|
||||||
, persistent-template >=2.5 && <2.14
|
, persistent-template >=2.5 && <2.14
|
||||||
|
, random
|
||||||
, safe
|
, safe
|
||||||
, shakespeare >=2.0 && <2.2
|
, shakespeare >=2.0 && <2.2
|
||||||
, template-haskell
|
, template-haskell
|
||||||
|
|
BIN
ads/image0.png
Normal file
After Width: | Height: | Size: 511 KiB |
BIN
ads/image1.png
Normal file
After Width: | Height: | Size: 1.7 MiB |
BIN
ads/image2.png
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
ads/image3.png
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
ads/image4.png
Normal file
After Width: | Height: | Size: 440 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 15 KiB |
|
@ -15,6 +15,8 @@
|
||||||
-- stationName/roundNumber
|
-- stationName/roundNumber
|
||||||
/station/#Text/#Int StationRoundR GET POST
|
/station/#Text/#Int StationRoundR GET POST
|
||||||
|
|
||||||
|
/ad/ AdR GET
|
||||||
|
|
||||||
|
|
||||||
-- /comments CommentR POST
|
-- /comments CommentR POST
|
||||||
|
|
||||||
|
|
|
@ -41,3 +41,5 @@ copyright: Insert copyright statement here
|
||||||
stations:
|
stations:
|
||||||
- station-id: "test"
|
- station-id: "test"
|
||||||
station-name: "Test"
|
station-name: "Test"
|
||||||
|
|
||||||
|
ad-files: "ads/"
|
||||||
|
|
26
flake.lock
|
@ -5,11 +5,11 @@
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1719994518,
|
"lastModified": 1727826117,
|
||||||
"narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=",
|
"narHash": "sha256-K5ZLCyfO/Zj9mPFldf3iwS6oZStJcU4tSpiXTMYaaL0=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7",
|
"rev": "3d04084d54bedc3d6b8b736c70ef449225c361b1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -20,11 +20,11 @@
|
||||||
},
|
},
|
||||||
"haskell-flake": {
|
"haskell-flake": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1720977934,
|
"lastModified": 1728227251,
|
||||||
"narHash": "sha256-k9kwz2lpUqafRUpuCMgkv4AWtHEoJPCds1ZPRkyW2XE=",
|
"narHash": "sha256-JLDhMFyGyFe0QJAbCuSxB7/kjUCA7uUAMa6BFRORwXk=",
|
||||||
"owner": "srid",
|
"owner": "srid",
|
||||||
"repo": "haskell-flake",
|
"repo": "haskell-flake",
|
||||||
"rev": "cd449f1c04175efdf5b553302d22916640090066",
|
"rev": "9cbfbfc38f1fbf9a0f471795c84780211875fd45",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -35,11 +35,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1721466660,
|
"lastModified": 1728279793,
|
||||||
"narHash": "sha256-pFSxgSZqZ3h+5Du0KvEL1ccDZBwu4zvOil1zzrPNb3c=",
|
"narHash": "sha256-W3D5YpNrUVTFPVU4jiEiboaaUDShaiH5fRl9aJLqUnU=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "6e14bbce7bea6c4efd7adfa88a40dac750d80100",
|
"rev": "f85a2d005e83542784a755ca8da112f4f65c4aa4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -51,14 +51,14 @@
|
||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1719876945,
|
"lastModified": 1727825735,
|
||||||
"narHash": "sha256-Fm2rDDs86sHy0/1jxTOKB1118Q0O3Uc7EC0iXvXKpbI=",
|
"narHash": "sha256-0xHYkMkeLVQAMa7gvkddbPqpxph+hDzdu1XdGPJR+Os=",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/5daf0514482af3f97abaefc78a6606365c9108e2.tar.gz"
|
"url": "https://github.com/NixOS/nixpkgs/archive/fb192fec7cc7a4c26d51779e9bab07ce6fa5597a.tar.gz"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/5daf0514482af3f97abaefc78a6606365c9108e2.tar.gz"
|
"url": "https://github.com/NixOS/nixpkgs/archive/fb192fec7cc7a4c26d51779e9bab07ce6fa5597a.tar.gz"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
};
|
};
|
||||||
outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
|
outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
|
||||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
|
flake = {
|
||||||
|
nixosModules.OwOpointTracker = { import ./module.nix self
|
||||||
|
};
|
||||||
systems = nixpkgs.lib.systems.flakeExposed;
|
systems = nixpkgs.lib.systems.flakeExposed;
|
||||||
imports = [ inputs.haskell-flake.flakeModule ];
|
imports = [ inputs.haskell-flake.flakeModule ];
|
||||||
|
|
||||||
|
|
89
module.nix
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
self: {config, pkgs, lib ...}:
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.owOPointTracker;
|
||||||
|
format = pkgs.formats.yaml {};
|
||||||
|
configFile = format.generate "settings.yml" cfg.config
|
||||||
|
databaseOptionType = types.submodule {
|
||||||
|
options = {
|
||||||
|
database = mkOption {
|
||||||
|
default = "_env:YESOD_SQLITE_DATABASE:OwOpointTracker.sqlite3";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
poolsize = mkOption {
|
||||||
|
default = "_env:YESOD_SQLITE_POOLSIZE:10";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
stationType = types.submodule {
|
||||||
|
options = {
|
||||||
|
station-id = mkOption {
|
||||||
|
default = "Station";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
station-name = mkOption {
|
||||||
|
types = types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
{
|
||||||
|
imports = [];
|
||||||
|
options.services.owOPointTracker = {
|
||||||
|
enable = mkEnableOption "OwOPointTracker";
|
||||||
|
config = {
|
||||||
|
static-dir = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
};
|
||||||
|
host = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "_env:YESOD_HOST:*4";
|
||||||
|
ad-files = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
};
|
||||||
|
port = mkOption {
|
||||||
|
default = "_env:YESOD_PORT:3000";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
ip-from-header = mkOption {
|
||||||
|
default = "_env:YESOD_IP_FROM_HEADER:false";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
database = {
|
||||||
|
type = databaseOptionType;
|
||||||
|
};
|
||||||
|
copyright = mkOption {
|
||||||
|
default = "copyright for this mess?";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
station = mkOption {
|
||||||
|
type = types.listOf stationType;
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
users.users.owopoint = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "owopoint";
|
||||||
|
createHome = true;
|
||||||
|
home = "/var/owopoint";
|
||||||
|
};
|
||||||
|
users.groups = {
|
||||||
|
owopoint = {};
|
||||||
|
};
|
||||||
|
systemd.services.owOPointTracker = {
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
after = ["network.target"];
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${self.packages.${pkgs.system}.default} ${configFile}";
|
||||||
|
Restart = always;
|
||||||
|
User = "owopoint";
|
||||||
|
Group = "owopoint";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
|
@ -43,6 +43,7 @@ import System.Log.FastLogger (defaultBufSize, newStdoutLoggerSet,
|
||||||
import Handler.Common
|
import Handler.Common
|
||||||
import Handler.Home
|
import Handler.Home
|
||||||
import Handler.Station
|
import Handler.Station
|
||||||
|
import Handler.Ad
|
||||||
|
|
||||||
-- This line actually creates our YesodDispatch instance. It is the second half
|
-- This line actually creates our YesodDispatch instance. It is the second half
|
||||||
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the
|
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the
|
||||||
|
|
|
@ -146,8 +146,10 @@ instance Yesod App where
|
||||||
-- value passed to hamletToRepHtml cannot be a widget, this allows
|
-- value passed to hamletToRepHtml cannot be a widget, this allows
|
||||||
-- you to use normal widget features in default-layout.
|
-- you to use normal widget features in default-layout.
|
||||||
|
|
||||||
|
-- randomAddNumber <- liftIO $ getStdRandom (randomR (0,4))
|
||||||
|
|
||||||
pc <- widgetToPageContent $ do
|
pc <- widgetToPageContent $ do
|
||||||
addStylesheet $ StaticR css_bootstrap_css
|
-- addStylesheet $ StaticR css_bootstrap_css
|
||||||
-- ^ generated from @Settings/StaticFiles.hs@
|
-- ^ generated from @Settings/StaticFiles.hs@
|
||||||
$(widgetFile "default-layout")
|
$(widgetFile "default-layout")
|
||||||
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
|
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
|
||||||
|
|
15
src/Handler/Ad.hs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{-# LANGUAGE NoImplicitPrelude #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
module Handler.Ad where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
|
||||||
|
getAdR :: Handler Html
|
||||||
|
getAdR = do
|
||||||
|
basePath <- getsYesod (appAdFilePath . appSettings)
|
||||||
|
number <- applyAtomicGen (randomR (0 :: Int ,4)) globalStdGen
|
||||||
|
sendFile typePng $ basePath <> "image" <> show number <> ".png"
|
|
@ -19,7 +19,8 @@ checkStation stationIdentifier = do
|
||||||
Nothing -> notFound
|
Nothing -> notFound
|
||||||
Just m -> return m
|
Just m -> return m
|
||||||
|
|
||||||
repsertBy :: (MonadIO m, PersistUniqueRead backend, PersistRecordBackend record backend, AtLeastOneUniqueKey record, SafeToInsert record) => record -> ReaderT backend m ()
|
-- TODO works without the type annotation, but I don't know why this one is wrong. No time to find out now
|
||||||
|
-- repsertBy :: (MonadIO m, PersistUniqueRead backend, PersistRecordBackend record backend, AtLeastOneUniqueKey record, SafeToInsert record) => record -> ReaderT backend m ()
|
||||||
repsertBy val = do
|
repsertBy val = do
|
||||||
valM <- getByValue val
|
valM <- getByValue val
|
||||||
case valM of
|
case valM of
|
||||||
|
|
|
@ -10,3 +10,5 @@ import Settings.StaticFiles as Import
|
||||||
import Yesod.Auth as Import
|
import Yesod.Auth as Import
|
||||||
import Yesod.Core.Types as Import (loggerSet)
|
import Yesod.Core.Types as Import (loggerSet)
|
||||||
import Yesod.Default.Config2 as Import
|
import Yesod.Default.Config2 as Import
|
||||||
|
import System.Random as Import
|
||||||
|
import System.Random.Stateful as Import
|
||||||
|
|
|
@ -71,6 +71,7 @@ data AppSettings = AppSettings
|
||||||
-- ^ Copyright text to appear in the footer of the page
|
-- ^ Copyright text to appear in the footer of the page
|
||||||
|
|
||||||
, appStations :: Map Text Text
|
, appStations :: Map Text Text
|
||||||
|
, appAdFilePath :: String
|
||||||
}
|
}
|
||||||
|
|
||||||
instance FromJSON AppSettings where
|
instance FromJSON AppSettings where
|
||||||
|
@ -98,6 +99,7 @@ instance FromJSON AppSettings where
|
||||||
|
|
||||||
appCopyright <- o .: "copyright"
|
appCopyright <- o .: "copyright"
|
||||||
appStations <- M.fromList . map (\x -> (stationId x, stationName x)) <$> o .: "stations"
|
appStations <- M.fromList . map (\x -> (stationId x, stationName x)) <$> o .: "stations"
|
||||||
|
appAdFilePath <- o .: "ad-files"
|
||||||
|
|
||||||
return AppSettings {..}
|
return AppSettings {..}
|
||||||
|
|
||||||
|
|
3
static/css/test.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.blubb {
|
||||||
|
border-width: 300px
|
||||||
|
}
|
BIN
static/grauenhafte_werbung.gif
Normal file
After Width: | Height: | Size: 3.1 MiB |
|
@ -1,16 +1,6 @@
|
||||||
<!-- Page Contents -->
|
<!-- Page Contents -->
|
||||||
|
|
||||||
|
<div .container>
|
||||||
$if (Just HomeR == mcurrentRoute)
|
<div .ad>
|
||||||
|
<img src=@{AdR}>
|
||||||
^{widget}
|
^{widget}
|
||||||
$else
|
|
||||||
<div .container>
|
|
||||||
<div .row>
|
|
||||||
<div .col-md-12>
|
|
||||||
^{widget}
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<footer .footer>
|
|
||||||
<div .container>
|
|
||||||
<p .text-muted>
|
|
||||||
#{appCopyright $ appSettings master}
|
|
||||||
|
|
|
@ -1,28 +1,11 @@
|
||||||
.masthead,
|
body {
|
||||||
.navbar {
|
font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;
|
||||||
background-color: rgb(27, 28, 29);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-default .navbar-nav > .active > a {
|
|
||||||
background-color: transparent;
|
|
||||||
border-bottom: 2px solid white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav {
|
|
||||||
padding-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.masthead {
|
|
||||||
margin-top: -21px;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
min-height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.masthead .header {
|
.masthead .header {
|
||||||
max-width: 700px;
|
max-width: 700px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.masthead h1.header {
|
.masthead h1.header {
|
||||||
|
@ -38,36 +21,15 @@
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.masthead .btn {
|
aside,
|
||||||
margin: 1em 0;
|
.ad {
|
||||||
|
width: 25%;
|
||||||
|
padding-left: 15px;
|
||||||
|
margin-left: 15px;
|
||||||
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
/* Common styles for all types */
|
max-width: 100%;
|
||||||
.bs-callout {
|
max-height: 100%;
|
||||||
padding: 20px;
|
|
||||||
margin: 20px 0;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-left-width: 5px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bs-callout p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bs-callout-info {
|
|
||||||
border-left-color: #1b809e;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Space things out */
|
|
||||||
.bs-docs-section {
|
|
||||||
margin-bottom: 60px;
|
|
||||||
}
|
|
||||||
.bs-docs-section:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#message {
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,10 @@
|
||||||
<div .masthead>
|
<div .masthead>
|
||||||
<div .container>
|
|
||||||
<div .row>
|
|
||||||
<h1 .header>
|
<h1 .header>
|
||||||
OwO Geländespiel
|
OWO Geländespiel
|
||||||
|
|
||||||
<div .container>
|
<div .points .tutor>
|
||||||
<!-- Starting
|
<h1 #Tutors>Tutoren: #{tutorP}
|
||||||
================================================== -->
|
|
||||||
<div .bs-docs-section>
|
|
||||||
<div .row>
|
|
||||||
<div .col-lg-12>
|
|
||||||
<div .page-header>
|
|
||||||
<h1 #Tutors>Tutors
|
|
||||||
#{tutorP}
|
|
||||||
|
|
||||||
<hr>
|
<div .points .erstie>
|
||||||
|
<h1 #Ersties>Ersties: #{erstieP}
|
||||||
<!-- Forms
|
|
||||||
================================================== -->
|
|
||||||
<div .bs-docs-section>
|
|
||||||
<div .row>
|
|
||||||
<div .col-lg-12>
|
|
||||||
<div .page-header>
|
|
||||||
<h1 #Ersties>Ersties
|
|
||||||
#{erstieP}
|
|
||||||
|
|
||||||
|
|
|
@ -2,3 +2,19 @@ li {
|
||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
font-size: 16px
|
font-size: 16px
|
||||||
}
|
}
|
||||||
|
.points {
|
||||||
|
border-radius: 25px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: auto;
|
||||||
|
max-width: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
.tutor {
|
||||||
|
background-color: #f68afc;
|
||||||
|
}
|
||||||
|
.erstie {
|
||||||
|
background-color: #00FF00;
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
<div .masthead>
|
<div .masthead>
|
||||||
<div .container>
|
|
||||||
<div .row>
|
|
||||||
<h1 .header>
|
<h1 .header>
|
||||||
OwO Geländespiel Station #{station} Round #{roundNumber}
|
OwO Geländespiel Station #{station} Round #{roundNumber}
|
||||||
|
|
||||||
<div .container>
|
<div .container>
|
||||||
<div .bs-docs-section>
|
<div .form>
|
||||||
<div .row>
|
|
||||||
<div .col-lg-12>
|
|
||||||
<div .page-header>
|
|
||||||
<form method=post action=@{StationRoundR stationIdentifier roundNumber} enctype=#{roundFormEnctype}>
|
<form method=post action=@{StationRoundR stationIdentifier roundNumber} enctype=#{roundFormEnctype}>
|
||||||
^{roundFormWidget}
|
^{roundFormWidget}
|
||||||
<button> Submit
|
<button> Submit
|
||||||
|
|
5
templates/round.lucious
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
|
@ -1,14 +1,8 @@
|
||||||
<div .masthead>
|
<div .masthead>
|
||||||
<div .container>
|
|
||||||
<div .row>
|
|
||||||
<h1 .header>
|
<h1 .header>
|
||||||
OwO Geländespiel Station #{station}
|
OwO Geländespiel Station #{station}
|
||||||
|
|
||||||
<div .container>
|
<div .container .form>
|
||||||
<div .bs-docs-section>
|
|
||||||
<div .row>
|
|
||||||
<div .col-lg-12>
|
|
||||||
<div .page-header>
|
|
||||||
<ul>
|
<ul>
|
||||||
$forall round <- rounds
|
$forall round <- rounds
|
||||||
<li>
|
<li>
|
||||||
|
|
5
templates/station.lucious
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|