Initial
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
16
AirportAlphabetGame.sln
Normal file
16
AirportAlphabetGame.sln
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "AirportAlphabetGame", "AirportAlphabetGame\AirportAlphabetGame.fsproj", "{80323801-F224-4E26-B6A0-5232AED36275}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{80323801-F224-4E26-B6A0-5232AED36275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{80323801-F224-4E26-B6A0-5232AED36275}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{80323801-F224-4E26-B6A0-5232AED36275}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{80323801-F224-4E26-B6A0-5232AED36275}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
26
AirportAlphabetGame/AirportAlphabetGame.fsproj
Normal file
26
AirportAlphabetGame/AirportAlphabetGame.fsproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Types.fs" />
|
||||
<Compile Include="Svg.fs" />
|
||||
<Compile Include="Htmx.fs" />
|
||||
<Compile Include="View.fs" />
|
||||
<Compile Include="Controller.fs" />
|
||||
<Compile Include="Router.fs" />
|
||||
<Compile Include="Program.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Saturn" Version="0.16.1" />
|
||||
<PackageReference Include="Thoth.Json.Net" Version="11.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="sass\core.sass" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
106
AirportAlphabetGame/Controller.fs
Normal file
106
AirportAlphabetGame/Controller.fs
Normal file
@@ -0,0 +1,106 @@
|
||||
module Controller
|
||||
|
||||
open System.Net.Http
|
||||
open System.Text.RegularExpressions
|
||||
open Giraffe.ViewEngine.HtmlElements
|
||||
open Types
|
||||
open System.Text
|
||||
open Thoth.Json.Net
|
||||
|
||||
|
||||
let parseUsername (username: string) =
|
||||
let pattern = @"https?://(?:www\.)?my\.flightradar24\.com/([^/?#]+)"
|
||||
let matches = Regex.Match(username, pattern)
|
||||
|
||||
if matches.Success then matches.Groups.[1].Value
|
||||
else username
|
||||
|
||||
let getJsonResult result =
|
||||
match result with
|
||||
| Error _ -> failwith "Invalid Json"
|
||||
| Ok finalResult -> finalResult
|
||||
|
||||
|
||||
let makePostRequest<'x> (url: string) payload =
|
||||
async {
|
||||
use httpClient = new HttpClient()
|
||||
let content = new StringContent(payload, Encoding.UTF8, "application/x-www-form-urlencoded")
|
||||
let request = new HttpRequestMessage(HttpMethod.Post, url)
|
||||
request.Content <- content
|
||||
let! response = httpClient.SendAsync(request) |> Async.AwaitTask
|
||||
let! responseContent = response.Content.ReadAsStringAsync() |> Async.AwaitTask
|
||||
return responseContent
|
||||
}
|
||||
|> Async.RunSynchronously
|
||||
|> Decode.Auto.fromString<'x>
|
||||
|> getJsonResult
|
||||
|
||||
|
||||
let decodeResponseData (data: AirportResponseData) =
|
||||
data
|
||||
|> Array.map (fun airport -> {Code=string airport[0];City=string airport[1]})
|
||||
|
||||
let groupAirportsByLetter (airports: Airport[]) =
|
||||
airports
|
||||
|> Array.groupBy (_.Code[0])
|
||||
|> Array.map snd
|
||||
|
||||
let getAirportAbbrTags airportGroup =
|
||||
airportGroup
|
||||
|> Array.distinct
|
||||
|> Array.sortBy (_.Code)
|
||||
|> Array.map View.airportAbbr
|
||||
|
||||
|
||||
let processAirports (alphabet: char[]) (allAirports: Airport[]) =
|
||||
|
||||
let intersperseCommas (nodes: XmlNode seq) =
|
||||
nodes
|
||||
|> Seq.mapi (fun i node -> if i < Seq.length nodes - 1 then [node; View.comma] else [node])
|
||||
|> Seq.concat
|
||||
|> Seq.toArray
|
||||
|
||||
// Group airports by the first letter of their code
|
||||
let groupedAirports =
|
||||
allAirports
|
||||
|> Seq.groupBy (fun airport -> airport.Code.[0])
|
||||
|> Seq.map (fun (key, group) ->
|
||||
let sortedGroup = group |> Seq.sortBy (fun airport -> airport.Code)
|
||||
key, (sortedGroup |> Seq.map View.airportAbbr |> intersperseCommas))
|
||||
|> dict
|
||||
|
||||
|
||||
let dictionary =
|
||||
alphabet
|
||||
|> Array.map (fun letter ->
|
||||
let key = letter.ToString().ToUpper()
|
||||
match groupedAirports.TryGetValue(letter) with
|
||||
| (true, value) -> key, value
|
||||
| _ -> key, [||])
|
||||
|
||||
dictionary
|
||||
let RenderAirportList (user: usernameQuery) =
|
||||
let username = parseUsername user.fr24user
|
||||
let alphabet = [|'A'..'Z'|]
|
||||
let airports =
|
||||
$"username={username}&listType=airports&order=no&limit=0"
|
||||
|> makePostRequest<AirportResponseData> "https://my.flightradar24.com/public-scripts/profileToplist"
|
||||
|> decodeResponseData
|
||||
|> processAirports alphabet
|
||||
|
||||
let numberOfAirportsNotFlown =
|
||||
airports
|
||||
|> Array.filter(fun (_, nodes) -> Array.isEmpty nodes)
|
||||
|> Array.length
|
||||
|
||||
let percentageOfLettersWithNoAirports = (float numberOfAirportsNotFlown / float alphabet.Length) * 100.0
|
||||
let message = $"{username} has flown {System.Math.Round(100. - percentageOfLettersWithNoAirports, 1)}%% of the alphabet!"
|
||||
|
||||
|
||||
airports
|
||||
|> Array.map (fun (letter, nodes) -> View.tableRow letter nodes)
|
||||
|> View.table message username
|
||||
|
||||
let RenderPageWithUser (username: string) =
|
||||
let airportList = RenderAirportList {fr24user = username}
|
||||
View.index [|airportList|]
|
||||
12
AirportAlphabetGame/Htmx.fs
Normal file
12
AirportAlphabetGame/Htmx.fs
Normal file
@@ -0,0 +1,12 @@
|
||||
module Htmx
|
||||
|
||||
open Giraffe.ViewEngine
|
||||
|
||||
let _hxGet = attr "data-hx-get"
|
||||
let _hxPost = attr "data-hx-post"
|
||||
let _hxTrigger = attr "data-hx-trigger"
|
||||
let _hxTarget = attr "data-hx-target"
|
||||
let _hxExt = attr "data-hx-ext"
|
||||
let _hxSwap = attr "data-hx-swap"
|
||||
let _hxReplaceUrl = attr "data-hx-replace-url"
|
||||
let _hxValidate = attr "data-hx-validate"
|
||||
37
AirportAlphabetGame/Program.fs
Normal file
37
AirportAlphabetGame/Program.fs
Normal file
@@ -0,0 +1,37 @@
|
||||
open Microsoft.AspNetCore.Mvc
|
||||
open Microsoft.Extensions.DependencyInjection
|
||||
open Microsoft.AspNetCore.Http
|
||||
open Saturn
|
||||
open Giraffe
|
||||
open Types
|
||||
|
||||
module Program =
|
||||
|
||||
let pipeline = pipeline {
|
||||
use_warbler
|
||||
}
|
||||
|
||||
let router = router {
|
||||
pipe_through pipeline
|
||||
not_found_handler (setStatusCode 404 >=> text "404")
|
||||
get "/" ( (View.index [||]) |> htmlView)
|
||||
getf "/%s" (fun username -> htmlView(Controller.RenderPageWithUser username))
|
||||
post "/search" (bindJson<usernameQuery> (fun username ->
|
||||
fun next ctx ->
|
||||
ctx.Response.Headers.Add("HX-Replace-Url", Controller.parseUsername username.fr24user)
|
||||
htmlView (Controller.RenderAirportList username) next ctx
|
||||
))
|
||||
}
|
||||
|
||||
let ServiceConfig (services: IServiceCollection) = services.AddHttpContextAccessor()
|
||||
let ipAddress = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList[2];
|
||||
let app =
|
||||
application {
|
||||
use_mime_types [(".woff", "application/font-woff")]
|
||||
use_static "public"
|
||||
use_router router
|
||||
service_config ServiceConfig
|
||||
url "http://*:5001"
|
||||
}
|
||||
|
||||
run app
|
||||
38
AirportAlphabetGame/Properties/launchSettings.json
Normal file
38
AirportAlphabetGame/Properties/launchSettings.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:4828",
|
||||
"sslPort": 44396
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5001",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7051;http://localhost:5001",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
AirportAlphabetGame/Router.fs
Normal file
93
AirportAlphabetGame/Router.fs
Normal file
@@ -0,0 +1,93 @@
|
||||
module Router
|
||||
|
||||
open System
|
||||
open System.Linq
|
||||
open System.Threading.Tasks
|
||||
open Microsoft.AspNetCore.Http
|
||||
open Microsoft.AspNetCore.Builder
|
||||
open Microsoft.Extensions.Hosting
|
||||
|
||||
let inline (~%) (x: ^A) : ^B = (^B : (static member From: ^A -> ^B) x)
|
||||
|
||||
type RouteMethod = Get | Post
|
||||
type Static<'x> =
|
||||
| HtmlDocument of (unit -> Giraffe.ViewEngine.HtmlElements.XmlNode)
|
||||
| HtmlElement of (unit -> Giraffe.ViewEngine.HtmlElements.XmlNode)
|
||||
| HtmlElements of (unit -> Giraffe.ViewEngine.HtmlElements.XmlNode list)
|
||||
| HtmlString of (unit -> string)
|
||||
| Text of (unit -> string)
|
||||
| Json of (unit -> 'x)
|
||||
|
||||
type Dynamic<'x, 'y> =
|
||||
| HtmlDocument of ('x -> Giraffe.ViewEngine.HtmlElements.XmlNode)
|
||||
| HtmlElement of ('x -> Giraffe.ViewEngine.HtmlElements.XmlNode)
|
||||
| HtmlElements of ('x -> Giraffe.ViewEngine.HtmlElements.XmlNode list)
|
||||
| HtmlString of ('x -> string)
|
||||
| Text of ('x -> string)
|
||||
| Json of ('x -> 'y)
|
||||
|
||||
|
||||
let private GetMimeTypeFromStaticResponseFunction (func: Static<'x>) =
|
||||
match func with
|
||||
| Static.HtmlDocument _
|
||||
| Static.HtmlElements _
|
||||
| Static.HtmlElement _
|
||||
| Static.HtmlString _ -> "text/html"
|
||||
| Static.Json _ -> "application/json"
|
||||
| Static.Text _ -> "text/plain"
|
||||
|
||||
let private GetMimeTypeFromDynamicResponseFunction (func: Dynamic<'x, 'y>) =
|
||||
match func with
|
||||
| Dynamic.HtmlDocument _
|
||||
| Dynamic.HtmlElements _
|
||||
| Dynamic.HtmlElement _
|
||||
| Dynamic.HtmlString _ -> "text/html"
|
||||
| Dynamic.Json _ -> "application/json"
|
||||
| Dynamic.Text _ -> "text/plain"
|
||||
|
||||
let StaticView (func: unit -> Giraffe.ViewEngine.HtmlElements.XmlNode) = Static.HtmlDocument func
|
||||
|
||||
|
||||
let BuildApp (args: string[]) =
|
||||
WebApplication
|
||||
.CreateBuilder(args)
|
||||
.Build()
|
||||
|
||||
|
||||
let AddStaticRoute (route: string) (func: Static<'x>) (app: WebApplication) =
|
||||
let htmlResponseFunc (context: HttpContext) =
|
||||
let htmlContent =
|
||||
match func with
|
||||
| Static.HtmlDocument view -> view () |> Giraffe.ViewEngine.RenderView.AsString.htmlDocument
|
||||
| Static.HtmlElement view -> view () |> Giraffe.ViewEngine.RenderView.AsString.htmlNode
|
||||
| Static.HtmlElements view -> view () |> Giraffe.ViewEngine.RenderView.AsString.htmlNodes
|
||||
| Static.HtmlString view -> view ()
|
||||
| Static.Text view -> view ()
|
||||
| Static.Json view -> view () |> Thoth.Json.Net.Encode.Auto.toString
|
||||
context.Response.ContentType <- GetMimeTypeFromStaticResponseFunction func
|
||||
context.Response.WriteAsync(htmlContent)
|
||||
|
||||
app.MapGet(route, Func<HttpContext, Task>(htmlResponseFunc))
|
||||
|> ignore
|
||||
app
|
||||
|
||||
let AddDynamicRoute (route: string) (func: Dynamic<'x, 'y>) (app: WebApplication) =
|
||||
let dynamicResponseFunc (context: HttpContext) =
|
||||
let param = context.Request.RouteValues.Values.First() :?> 'x
|
||||
let content =
|
||||
match func with
|
||||
| Dynamic.HtmlDocument view -> view param |> Giraffe.ViewEngine.RenderView.AsString.htmlDocument
|
||||
| Dynamic.HtmlElement view -> view param |> Giraffe.ViewEngine.RenderView.AsString.htmlNode
|
||||
| Dynamic.HtmlElements view -> view param |> Giraffe.ViewEngine.RenderView.AsString.htmlNodes
|
||||
| Dynamic.HtmlString view -> view param
|
||||
| Dynamic.Text view -> view param
|
||||
| Dynamic.Json view -> view param |> Thoth.Json.Net.Encode.Auto.toString
|
||||
context.Response.ContentType <- GetMimeTypeFromDynamicResponseFunction func
|
||||
context.Response.WriteAsync(content)
|
||||
|
||||
app.MapGet(route, dynamicResponseFunc) |> ignore
|
||||
app
|
||||
|
||||
let Run (app: WebApplication) =
|
||||
app.Run()
|
||||
0
|
||||
15
AirportAlphabetGame/Svg.fs
Normal file
15
AirportAlphabetGame/Svg.fs
Normal file
@@ -0,0 +1,15 @@
|
||||
module Svg
|
||||
open Giraffe.ViewEngine
|
||||
|
||||
let svg = tag "svg"
|
||||
let rect = tag "rect"
|
||||
let animate = tag "animate"
|
||||
|
||||
let _attributeName = attr "attributeName"
|
||||
let _attributeType = attr "attributeType"
|
||||
|
||||
let _values = attr "values"
|
||||
let _dur = attr "dur"
|
||||
let _fill = attr "fill"
|
||||
let _begin = attr "begin"
|
||||
let _repeatCount = attr "repeatCount"
|
||||
12
AirportAlphabetGame/Types.fs
Normal file
12
AirportAlphabetGame/Types.fs
Normal file
@@ -0,0 +1,12 @@
|
||||
module Types
|
||||
|
||||
type usernameQuery = {
|
||||
fr24user: string
|
||||
}
|
||||
|
||||
type AirportResponseData = obj[][]
|
||||
|
||||
type Airport = {
|
||||
Code: string
|
||||
City: string
|
||||
}
|
||||
84
AirportAlphabetGame/View.fs
Normal file
84
AirportAlphabetGame/View.fs
Normal file
@@ -0,0 +1,84 @@
|
||||
module View
|
||||
|
||||
open System
|
||||
open Htmx
|
||||
open Giraffe.ViewEngine
|
||||
open Giraffe.Core
|
||||
open Types
|
||||
|
||||
let index content =
|
||||
let hide =
|
||||
match content |> Seq.length with
|
||||
| 0 -> "hide"
|
||||
| _ -> ""
|
||||
|
||||
html [] [
|
||||
head [] [
|
||||
meta [_name "viewport"; _content "width=device-width"]
|
||||
title [] [str "Have you flown the alphabet?"]
|
||||
link [_rel "stylesheet" ; _href "/styles/core.css"]
|
||||
script [_src "/scripts/htmx.min.js"] []
|
||||
script [_src "/scripts/json-enc.js"] []
|
||||
script [_src "/scripts/index.js"] []
|
||||
]
|
||||
body [] [
|
||||
div [_id "pageContainer"] [
|
||||
section [_id "input"] [
|
||||
article [] [
|
||||
h1 [] [str "Have you flown the alphabet?"]
|
||||
form [
|
||||
_id "username"
|
||||
_hxTrigger "submit"
|
||||
_hxExt "json-enc"
|
||||
_hxPost "/search"
|
||||
_hxTarget "#results"
|
||||
_hxSwap "innerHTML show:top"
|
||||
] [
|
||||
input [_type "text"; _name "fr24user"; _autocomplete "off"; _placeholder "Enter your MyFlightRadar24 username..."; _required; _hxValidate "true"; _pattern ".{4,}"]
|
||||
button [_type "submit";] [str "Let's find out!"]
|
||||
Svg.svg [_width "200"; _height "30"] [
|
||||
Svg.rect [_width "200"; _height "30"; Svg._fill "lightgray"] []
|
||||
Svg.rect [_width "50"; _height "30"; Svg._fill "white"] [
|
||||
Svg.animate [Svg._attributeName "x"; Svg._attributeType "XML"; Svg._values "0;150;0"; Svg._dur "2s"; Svg._begin "0s"; Svg._repeatCount "indefinite"] []
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
section [_id "results"; _class hide; attr "hx-on::after-swap" "document.getElementById('results').style.display='flex'"] [
|
||||
yield! content
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
let airportAbbr airport = abbr [_title airport.City] [str airport.Code]
|
||||
let comma = str ", "
|
||||
|
||||
let tableRow (letter: string ) (codes: XmlNode[]) =
|
||||
let className =
|
||||
match codes.Length with
|
||||
| 0 -> "not-flown"
|
||||
| _ -> "flown"
|
||||
|
||||
tr [_class className] [
|
||||
th [] [str letter]
|
||||
td [] [
|
||||
yield! codes
|
||||
]
|
||||
]
|
||||
|
||||
let table (message) (user: string) (rows: XmlNode[]) =
|
||||
article [] [
|
||||
h1 [] [str message]
|
||||
table [] [
|
||||
tr [] [
|
||||
th [_colspan "2"] [str $"{user}'s Airports Flown"]
|
||||
]
|
||||
yield! rows
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
let name (name:usernameQuery) : HttpHandler = h1 [] [str name.fr24user] |> htmlView
|
||||
let error = b [] [str "Error"]
|
||||
8
AirportAlphabetGame/appsettings.Development.json
Normal file
8
AirportAlphabetGame/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
AirportAlphabetGame/appsettings.json
Normal file
9
AirportAlphabetGame/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
1
AirportAlphabetGame/public/scripts/htmx.min.js
vendored
Normal file
1
AirportAlphabetGame/public/scripts/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
AirportAlphabetGame/public/scripts/index.js
Normal file
1
AirportAlphabetGame/public/scripts/index.js
Normal file
@@ -0,0 +1 @@
|
||||
document.addEventListener("DOMContentLoaded", (event) => document.getElementById('results').scrollIntoView());
|
||||
12
AirportAlphabetGame/public/scripts/json-enc.js
Normal file
12
AirportAlphabetGame/public/scripts/json-enc.js
Normal file
@@ -0,0 +1,12 @@
|
||||
htmx.defineExtension('json-enc', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "htmx:configRequest") {
|
||||
evt.detail.headers['Content-Type'] = "application/json";
|
||||
}
|
||||
},
|
||||
|
||||
encodeParameters : function(xhr, parameters, elt) {
|
||||
xhr.overrideMimeType('text/json');
|
||||
return (JSON.stringify(parameters));
|
||||
}
|
||||
});
|
||||
198
AirportAlphabetGame/public/styles/core.css
Normal file
198
AirportAlphabetGame/public/styles/core.css
Normal file
@@ -0,0 +1,198 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
scroll-behavior: smooth;
|
||||
font-family: manrope, sans-serif;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
word-wrap: break-word;
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#pageContainer::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
#pageContainer {
|
||||
height: 100dvh;
|
||||
width: 100dvw;
|
||||
max-width: 100%;
|
||||
overflow-y: auto;
|
||||
scroll-snap-type: y mandatory;
|
||||
white-space: nowrap;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
#pageContainer abbr {
|
||||
cursor: pointer;
|
||||
}
|
||||
#pageContainer section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100dvh;
|
||||
width: 100dvw;
|
||||
max-width: 100%;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
#pageContainer section.hide {
|
||||
display: none;
|
||||
}
|
||||
#pageContainer section#input {
|
||||
background: lightcoral;
|
||||
}
|
||||
#pageContainer section#input article {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
height: 25%;
|
||||
}
|
||||
#pageContainer section#input article h1 {
|
||||
flex-basis: 75%;
|
||||
color: white;
|
||||
font-size: 5em;
|
||||
}
|
||||
#pageContainer section#input article form {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-basis: 25%;
|
||||
width: 100%;
|
||||
}
|
||||
#pageContainer section#input article form input {
|
||||
flex-basis: 75%;
|
||||
height: 100%;
|
||||
font-size: 2em;
|
||||
padding-left: 1em;
|
||||
color: #666;
|
||||
}
|
||||
#pageContainer section#input article form button {
|
||||
flex-basis: 25%;
|
||||
font-size: 2em;
|
||||
height: 100%;
|
||||
color: white;
|
||||
background: grey;
|
||||
}
|
||||
#pageContainer section#input article form svg {
|
||||
display: none;
|
||||
}
|
||||
@media screen and (max-width: 900px) {
|
||||
#pageContainer section#input article {
|
||||
gap: 2em;
|
||||
height: 100%;
|
||||
}
|
||||
#pageContainer section#input article h1 {
|
||||
font-size: 4em;
|
||||
}
|
||||
#pageContainer section#input article form {
|
||||
flex-direction: column;
|
||||
flex-basis: 60%;
|
||||
width: 90%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
#pageContainer section#input article form input, #pageContainer section#input article form button {
|
||||
flex-basis: 25%;
|
||||
width: 100%;
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
#pageContainer section#input article form.htmx-request input, #pageContainer section#input article form.htmx-request button {
|
||||
display: none;
|
||||
}
|
||||
#pageContainer section#input article form.htmx-request svg {
|
||||
display: block;
|
||||
}
|
||||
#pageContainer section#results {
|
||||
background: lightblue;
|
||||
min-height: 100dvh;
|
||||
height: auto;
|
||||
}
|
||||
#pageContainer section#results h1 {
|
||||
color: white;
|
||||
font-size: 5em;
|
||||
}
|
||||
#pageContainer section#results article {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
}
|
||||
#pageContainer section#results article table {
|
||||
border: solid 2px black;
|
||||
background: white;
|
||||
border-collapse: collapse;
|
||||
min-width: 50%;
|
||||
max-width: 90%;
|
||||
}
|
||||
#pageContainer section#results article table tr th:first-child,
|
||||
#pageContainer section#results article table tr td:first-child {
|
||||
width: 10%;
|
||||
min-width: 10%;
|
||||
max-width: 10%;
|
||||
word-break: break-all;
|
||||
}
|
||||
#pageContainer section#results article table th {
|
||||
background-color: gray;
|
||||
color: white;
|
||||
}
|
||||
#pageContainer section#results article table td, #pageContainer section#results article table th {
|
||||
padding: 0.3em;
|
||||
}
|
||||
#pageContainer section#results article table tr.flown th {
|
||||
border-right: 1px solid gray;
|
||||
}
|
||||
#pageContainer section#results article table tr.flown th, #pageContainer section#results article table tr.flown td {
|
||||
background-color: lightgreen;
|
||||
color: black;
|
||||
}
|
||||
#pageContainer section#results article table tr.not-flown th {
|
||||
border-right: 1px solid gray;
|
||||
}
|
||||
#pageContainer section#results article table tr.not-flown th, #pageContainer section#results article table tr.not-flown td {
|
||||
background-color: lightcoral;
|
||||
color: black;
|
||||
}
|
||||
@media screen and (max-width: 900px) {
|
||||
#pageContainer section#results article {
|
||||
min-height: 100%;
|
||||
}
|
||||
#pageContainer section#results article h1 {
|
||||
width: 90%;
|
||||
flex-basis: 25%;
|
||||
font-size: 2em;
|
||||
}
|
||||
#pageContainer section#results article table {
|
||||
width: 100dvw;
|
||||
}
|
||||
#pageContainer section#results article table tr th:first-child,
|
||||
#pageContainer section#results article table tr td:first-child {
|
||||
width: 10dvw;
|
||||
min-width: 10dvw;
|
||||
max-width: 10dvw;
|
||||
word-break: break-all;
|
||||
}
|
||||
#pageContainer section#results article table tr th:nth-child(2),
|
||||
#pageContainer section#results article table tr td:nth-child(2) {
|
||||
width: 90dvw;
|
||||
min-width: 90dvw;
|
||||
max-width: 90dvw;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=core.css.map */
|
||||
1
AirportAlphabetGame/public/styles/core.css.map
Normal file
1
AirportAlphabetGame/public/styles/core.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["../../sass/core.sass"],"names":[],"mappings":"AAAA;EACI;EACA;EACA;EACA;EACA;EACA;;;AACJ;EACI;;;AAeJ;EARI;EACA;EACA;EAQA;EACA;EACA;;;AAEJ;EACI;EACA;;;AAEJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAEJ;EA/BA;EACA;EACA;EA+BI;EACA;EACA;EACA;;AAEJ;EACI;;AAGJ;EACI;;AAEA;EA7CJ;EACA;EACA;EAIA;EAyCQ;;AAEA;EACI;EACA;EACA;;AAEJ;EAtDR;EACA;EACA;EAsDY;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;EACA;;AAEJ;EACI;;AA9EhB;EAiDI;IAiCQ;IACA;;EAEA;IACI;;EAEJ;IACI;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA;;;AAKR;EACI;;AAEJ;EACI;;AAEhB;EACI;EACA;EACA;;AAEA;EACI;EACA;;AAGJ;EAlHJ;EACA;EACA;EAIA;EA8GQ;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;AAAA;EAEI;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAEJ;EACI;;AAGA;EACI;;AAEJ;EACI;EACA;;AAGJ;EACI;;AACJ;EACI;EACA;;AA7JpB;EAsHI;IAyCQ;;EACA;IACI;IACA;IACA;;EAEJ;IACI;;EAEA;AAAA;IAEI;IACA;IACA;IACA;;EAEJ;AAAA;IAEI;IACA;IACA;IACA;IACA","file":"core.css"}
|
||||
193
AirportAlphabetGame/sass/core.sass
Normal file
193
AirportAlphabetGame/sass/core.sass
Normal file
@@ -0,0 +1,193 @@
|
||||
*
|
||||
margin: 0
|
||||
padding: 0
|
||||
box-sizing: border-box
|
||||
scroll-behavior: smooth
|
||||
font-family: manrope, sans-serif
|
||||
overscroll-behavior: none
|
||||
a
|
||||
cursor: pointer
|
||||
|
||||
@mixin mobile
|
||||
@media screen and (max-width: 900px)
|
||||
@content
|
||||
|
||||
@mixin flex
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
|
||||
@mixin flex-column
|
||||
@include flex
|
||||
flex-direction: column
|
||||
|
||||
h1
|
||||
@include flex
|
||||
word-wrap: break-word
|
||||
white-space: normal
|
||||
text-align: center
|
||||
|
||||
#pageContainer::-webkit-scrollbar
|
||||
width: 0
|
||||
height: 0
|
||||
|
||||
#pageContainer
|
||||
height: 100dvh
|
||||
width: 100dvw
|
||||
max-width: 100%
|
||||
overflow-y: auto
|
||||
scroll-snap-type: y mandatory
|
||||
white-space: nowrap
|
||||
scrollbar-width: none /* Firefox */
|
||||
-ms-overflow-style: none /* Internet Explorer 10+ */
|
||||
|
||||
abbr
|
||||
cursor: pointer
|
||||
|
||||
section
|
||||
@include flex
|
||||
height: 100dvh
|
||||
width: 100dvw
|
||||
max-width: 100%
|
||||
scroll-snap-align: start
|
||||
|
||||
section.hide
|
||||
display: none
|
||||
|
||||
|
||||
section#input
|
||||
background: lightcoral
|
||||
|
||||
article
|
||||
@include flex-column
|
||||
height: 25%
|
||||
|
||||
h1
|
||||
flex-basis: 75%
|
||||
color: white
|
||||
font-size: 5em
|
||||
|
||||
form
|
||||
@include flex
|
||||
flex-basis: 25%
|
||||
width: 100%
|
||||
|
||||
input
|
||||
flex-basis: 75%
|
||||
height: 100%
|
||||
font-size: 2em
|
||||
padding-left: 1em
|
||||
color: #666
|
||||
|
||||
button
|
||||
flex-basis: 25%
|
||||
font-size: 2em
|
||||
height: 100%
|
||||
color: white
|
||||
background: grey
|
||||
|
||||
svg
|
||||
display: none
|
||||
|
||||
|
||||
@include mobile
|
||||
gap: 2em
|
||||
height: 100%
|
||||
|
||||
h1
|
||||
font-size: 4em
|
||||
|
||||
form
|
||||
flex-direction: column
|
||||
flex-basis: 60%
|
||||
width: 90%
|
||||
justify-content: flex-start
|
||||
|
||||
input, button
|
||||
flex-basis: 25%
|
||||
width: 100%
|
||||
font-size: 1em
|
||||
|
||||
|
||||
|
||||
form.htmx-request
|
||||
input, button
|
||||
display: none
|
||||
|
||||
svg
|
||||
display: block
|
||||
|
||||
section#results
|
||||
background: lightblue
|
||||
min-height: 100dvh
|
||||
height: auto
|
||||
|
||||
h1
|
||||
color: white
|
||||
font-size: 5em
|
||||
|
||||
|
||||
article
|
||||
@include flex-column
|
||||
gap: 1em
|
||||
|
||||
table
|
||||
border: solid 2px black
|
||||
background: white
|
||||
border-collapse: collapse
|
||||
min-width: 50%
|
||||
max-width: 90%
|
||||
|
||||
tr th:first-child,
|
||||
tr td:first-child
|
||||
width: 10%
|
||||
min-width: 10%
|
||||
max-width: 10%
|
||||
word-break: break-all
|
||||
|
||||
|
||||
th
|
||||
background-color: gray
|
||||
color: white
|
||||
|
||||
td, th
|
||||
padding: 0.3em
|
||||
|
||||
tr.flown
|
||||
th
|
||||
border-right: 1px solid gray
|
||||
|
||||
th, td
|
||||
background-color: lightgreen
|
||||
color: black
|
||||
|
||||
tr.not-flown
|
||||
th
|
||||
border-right: 1px solid gray
|
||||
th, td
|
||||
background-color: lightcoral
|
||||
color: black
|
||||
@include mobile
|
||||
min-height: 100%
|
||||
h1
|
||||
width: 90%
|
||||
flex-basis: 25%
|
||||
font-size: 2em
|
||||
|
||||
table
|
||||
width: 100dvw
|
||||
|
||||
tr th:first-child,
|
||||
tr td:first-child
|
||||
width: 10dvw
|
||||
min-width: 10dvw
|
||||
max-width: 10dvw
|
||||
word-break: break-all
|
||||
|
||||
tr th:nth-child(2),
|
||||
tr td:nth-child(2)
|
||||
width: 90dvw
|
||||
min-width: 90dvw
|
||||
max-width: 90dvw
|
||||
white-space: normal
|
||||
word-break: break-all
|
||||
Reference in New Issue
Block a user