chore: init

This commit is contained in:
Nick 2025-10-01 19:51:55 -05:00
commit 1b2c1ea359
891 changed files with 37053 additions and 0 deletions

1
templates/haskell/.envrc Executable file
View file

@ -0,0 +1 @@
use flake

4
templates/haskell/.gitignore vendored Executable file
View file

@ -0,0 +1,4 @@
.direnv
.pre-commit-config.yaml
.vscode
dist-newstyle

57
templates/haskell/flake.nix Executable file
View file

@ -0,0 +1,57 @@
{
description = "Haskell Environment";
inputs = {
flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
treefmt-nix.url = "github:numtide/treefmt-nix";
};
outputs =
inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ inputs.treefmt-nix.flakeModule ];
perSystem =
{ pkgs, ... }:
let
hp = pkgs.haskellPackages;
project = hp.callCabal2nix "project" ./. { };
in
{
devShells.default = hp.shellFor {
nativeBuildInputs = builtins.attrValues {
inherit (pkgs)
nil
stylish-haskell
ghc
;
inherit (hp)
cabal-install
cabal-gild
haskell-language-server
;
};
packages = _: [ project ];
};
packages.default = project;
treefmt = {
programs = {
cabal-fmt.enable = true;
deadnix.enable = true;
hlint.enable = true;
nixfmt.enable = true;
ormolu.enable = true;
statix.enable = true;
typstyle.enable = true;
yamlfmt.enable = true;
};
projectRootFile = "flake.nix";
};
};
systems = [ "x86_64-linux" ];
};
}

12
templates/haskell/fourmolu.yaml Executable file
View file

@ -0,0 +1,12 @@
indentation: 2
comma-style: trailing
import-export-style: diff-friendly
respectful: false
column-limit: 80
function-arrows: trailing
haddock-style: single-line
let-style: inline
in-style: right-align
unicode: never
pragma-style: leading
newlines-between-decls: 1

2
templates/haskell/justfile Executable file
View file

@ -0,0 +1,2 @@
cabal:
cabal clean ; cabal run

10
templates/haskell/project.cabal Executable file
View file

@ -0,0 +1,10 @@
cabal-version: 3.0
name: project
version: 0.1.0.0
executable main
main-is: Main.hs
build-depends:
, base
default-language: Haskell2010
hs-source-dirs: src

4
templates/haskell/src/Main.hs Executable file
View file

@ -0,0 +1,4 @@
module Main where
main :: IO ()
main = putStrLn "Hello, World!"

1
templates/typst/.envrc Executable file
View file

@ -0,0 +1 @@
use flake

4
templates/typst/.gitignore vendored Executable file
View file

@ -0,0 +1,4 @@
.direnv
.pre-commit-config.yaml
.vscode
*.pdf

27
templates/typst/flake.nix Executable file
View file

@ -0,0 +1,27 @@
{
description = "Typst Environment";
inputs = {
devshell.url = "github:numtide/devshell";
flake-root.url = "github:srid/flake-root";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
pre-commit-hooks-nix.url = "github:cachix/pre-commit-hooks.nix";
};
outputs =
inputs@{
nixpkgs,
flake-parts,
...
}:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
inputs.devshell.flakeModule
inputs.flake-root.flakeModule
inputs.pre-commit-hooks-nix.flakeModule
./parts
];
systems = nixpkgs.lib.systems.flakeExposed;
};
}

View file

@ -0,0 +1,26 @@
{
pkgs,
config,
...
}:
{
devShells = {
default = pkgs.mkShell {
packages = builtins.attrValues {
inherit (pkgs)
just
nil
typst
tinymist
typstyle
yamlfmt
nixfmt-rfc-style
;
# inherit (pkgs.nodePackages)
# "@commitlint/config-conventional"
# ;
};
};
shellHook = "${config.pre-commit.installationScript}";
};
}

View file

@ -0,0 +1,7 @@
{
pre-commit.settings.hooks = {
nixfmt.enable = true;
commitizen.enable = true;
statix.enable = true;
};
}

View file

@ -0,0 +1,15 @@
let
configPath = ./config;
devshellImports =
let
files = builtins.attrNames (builtins.readDir configPath);
in
map (name: configPath + "/${name}") (
builtins.filter (name: builtins.match ".*\\.nix$" name != null) files
);
in
{
imports = devshellImports;
}

8
templates/typst/src/refs.yml Executable file
View file

@ -0,0 +1,8 @@
citizen:
author: John Zerilli
chapter: 3
date: 2021
isbn: 9780262044813
publisher: The MIT Press
title: A Citizen's Guide To Artificial Intelligence
type: Book

View file

@ -0,0 +1,63 @@
// Catppuccin Latte
#let catppuccinLatteRosewater = rgb("#dc8a78")
#let catppuccinLatteFlamingo = rgb("#dd7878")
#let catppuccinLattePink = rgb("#ea76cb")
#let catppuccinLatteMauve = rgb("#8839ef")
#let catppuccinLatteRed = rgb("#d20f39")
#let catppuccinLatteMaroon = rgb("#e64553")
#let catppuccinLattePeach = rgb("#fe640b")
#let catppuccinLatteYellow = rgb("#df8e1d")
#let catppuccinLatteGreen = rgb("#40a02b")
#let catppuccinLatteTeal = rgb("#179299")
#let catppuccinLatteSky = rgb("#04a5e5")
#let catppuccinLatteSapphire = rgb("#209fb5")
#let catppuccinLatteBlue = rgb("#1e66f5")
#let catppuccinLatteLavender = rgb("#7287fd")
#let catppuccinLatteText = rgb("#4c4f69")
#let catppuccinLatteSubtext1 = rgb("#5c5f77")
#let catppuccinLatteSubtext0 = rgb("#6c6f85")
#let catppuccinLatteOverlay2 = rgb("#7c7f93")
#let catppuccinLatteOverlay1 = rgb("#8c8fa1")
#let catppuccinLatteOverlay0 = rgb("#9ca0b0")
#let catppuccinLatteSurface2 = rgb("#acb0be")
#let catppuccinLatteSurface1 = rgb("#bcc0cc")
#let catppuccinLatteSurface0 = rgb("#ccd0da")
#let catppuccinLatteBase = rgb("#eff1f5")
#let catppuccinLatteMantle = rgb("#e6e9ef")
#let catppuccinLatteCrust = rgb("#dce0e8")
// General:
#let project(author: (:), title: (), body) = {
show figure.caption: it => it.body
set quote(attribution: "content", block: true)
set document(author: author.firstName + " " + author.lastName, title: title)
set text(fill: catppuccinLatteText, font: ("New Computer Modern"), lang: "en")
let title = {
align(center)[
#block[
#text(size: 25pt, weight: "medium")[#title]
]
]
}
let name = {
align(
center,
)[
#block[
#text(size: 12pt, weight: "regular")[#author.firstName #author.lastName]
]
]
}
title
name
body
}

12
templates/typst/src/typst.typ Executable file
View file

@ -0,0 +1,12 @@
#import "template.typ": *
#show: project.with(author: (firstName: "First", lastName: "Last"), title: "Title")
Test
#quote(
attribution: <citizen>,
)[ Fact is, you can't satisfy both calibration and error rate balance if the base
rates differ... ]
#bibliography("refs.yml", style: "ieee")

1
templates/website/.envrc Executable file
View file

@ -0,0 +1 @@
use flake

1
templates/website/.gitignore vendored Executable file
View file

@ -0,0 +1 @@
.direnv

1
templates/website/backend/.gitignore vendored Executable file
View file

@ -0,0 +1 @@
/dist-newstyle

View file

@ -0,0 +1,12 @@
cabal-version: 3.0
name: backend
version: 0.1.0.0
executable main
main-is: Main.hs
build-depends:
, base
, scotty
, wai-cors
default-language: Haskell2010
hs-source-dirs: src

View file

@ -0,0 +1,4 @@
module Main where
main :: IO ()
main = putStrLn "Hello, World!"

147
templates/website/flake.lock generated Executable file
View file

@ -0,0 +1,147 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1730504689,
"narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "506278e768c2a08bec68eb62932193e341f55c90",
"type": "github"
},
"original": {
"id": "flake-parts",
"type": "indirect"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks-nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"haskell-flake": {
"locked": {
"lastModified": 1749105244,
"narHash": "sha256-gV/B1PWOwpLjy2OCHMS/fJ8GItMRoflW/g3kXB8/skg=",
"owner": "srid",
"repo": "haskell-flake",
"rev": "af2ba40f23824556b12d1cdfdf392e263876d644",
"type": "github"
},
"original": {
"owner": "srid",
"repo": "haskell-flake",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1750386251,
"narHash": "sha256-1ovgdmuDYVo5OUC5NzdF+V4zx2uT8RtsgZahxidBTyw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "076e8c6678d8c54204abcb4b1b14c366835a58bb",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1730504152,
"narHash": "sha256-lXvH/vOfb4aGYyvFmZK/HlsNsr/0CVWlwYvo2rxJk3s=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/cc2f28000298e1269cea6612cd06ec9979dd5d7f.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/cc2f28000298e1269cea6612cd06ec9979dd5d7f.tar.gz"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1730768919,
"narHash": "sha256-8AKquNnnSaJRXZxc5YmF/WfmxiHX6MMZZasRP6RRQkE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a04d33c0c3f1a59a2c1cb0c6e34cd24500e5a1dc",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks-nix": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1749636823,
"narHash": "sha256-WUaIlOlPLyPgz9be7fqWJA5iG6rHcGRtLERSCfUDne4=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "623c56286de5a3193aa38891a6991b28f9bab056",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"haskell-flake": "haskell-flake",
"nixpkgs": "nixpkgs",
"pre-commit-hooks-nix": "pre-commit-hooks-nix"
}
}
},
"root": "root",
"version": 7
}

97
templates/website/flake.nix Executable file
View file

@ -0,0 +1,97 @@
{
description = "Website Environment";
inputs = {
flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.05";
treefmt-nix.url = "github:numtide/treefmt-nix";
mkElmDerivation.url = "github:jeslie0/mkElmDerivation";
};
outputs =
inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ inputs.treefmt-nix.flakeModule ];
perSystem =
{ pkgs, lib, ... }:
let
hp = pkgs.haskellPackages;
backend = hp.callCabal2nix "backend" ./backend { };
in
{
devShells.default = hp.shellFor {
nativeBuildInputs = builtins.attrValues {
inherit (pkgs)
nil
stylish-haskell
ghc
vscode-langservers-extracted
nixfmt
;
inherit (hp)
cabal-install
cabal-gild
haskell-language-server
hlint
fourmolu
;
inherit (inputs.nixpkgs-stable.legacyPackages.${pkgs.system}.elmPackages)
elm
elm-format
elm-land
elm-language-server
elm-review
elm-test
;
};
packages = _: [ backend ];
};
packages =
let
pkgs' = pkgs.extend inputs.mkElmDerivation.overlays.mkElmDerivation;
in
{
websiteBackend = backend;
websiteFrontend = pkgs'.mkElmDerivation {
name = "website";
src = ./frontend;
nativeBuildInputs = builtins.attrValues {
inherit (inputs.nixpkgs-stable.legacyPackages.${pkgs.system}.elmPackages)
elm
elm-land
;
};
buildPhase = ''
${lib.getExe pkgs'.elm-land} build
'';
installPhase = ''
mkdir -p "$out"
cp -r dist/* "$out/"
'';
};
};
treefmt = {
programs = {
cabal-fmt.enable = true;
deadnix.enable = true;
hlint.enable = true;
nixfmt.enable = true;
fourmolu.enable = true;
statix.enable = true;
typstyle.enable = true;
yamlfmt.enable = true;
};
backendRootFile = "flake.nix";
};
};
systems = [ "x86_64-linux" ];
};
}

7
templates/website/frontend/.gitignore vendored Executable file
View file

@ -0,0 +1,7 @@
/dist
/.elm-land
/.env
/elm-stuff
/node_modules
.DS_Store
*.pem

View file

@ -0,0 +1,96 @@
{
"app": {
"elm": {
"development": { "debugger": false },
"production": { "debugger": false }
},
"env": [],
"html": {
"attributes": {
"html": { "lang": "en" },
"head": {}
},
"title": "website",
"meta": [
{ "charset": "UTF-8" },
{ "http-equiv": "X-UA-Compatible", "content": "IE=edge" },
{
"name": "viewport",
"content": "width=device-width, initial-scale=1.0"
},
{
"name": "description",
"content": "website description"
},
{
"property": "og:title",
"content": "website title"
},
{
"property": "og:description",
"content": "website description"
},
{
"property": "og:image",
"content": "card path"
},
{
"property": "og:image:alt",
"content": ""
},
{
"property": "og:image:type",
"content": "image/png"
},
{
"property": "og:url",
"content": "card path"
},
{
"property": "og:type",
"content": "website"
},
{
"property": "og:site_name",
"content": "website name"
},
{
"name": "twitter:card",
"content": "summary_large_image"
},
{
"name": "twitter:title",
"content": "website name"
},
{
"name": "twitter:description",
"content": "website description"
},
{
"name": "twitter:image",
"content": "card path"
},
{
"name": "twitter:site",
"content": "twitter @"
},
{
"name": "twitter:creator",
"content": "twitter @"
}
],
"link": [
{ "rel": "icon", "type": "png", "href": "/assets/favicon.png" },
{ "rel": "stylesheet", "href": "/styles.css" },
{
"rel": "stylesheet",
"href": "https://fonts.googleapis.com/css2?family=Montez&display=swap"
}
],
"script": []
},
"router": {
"useHashRouting": false
}
}
}

View file

@ -0,0 +1,53 @@
{
"type": "application",
"source-directories": [
"src",
".elm-land/src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"dillonkearns/elm-markdown": "7.0.1",
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/json": "1.1.3",
"elm/svg": "1.0.1",
"elm/url": "1.0.0",
"elm-community/list-extra": "8.7.0",
"elm-community/maybe-extra": "5.3.0",
"gampleman/elm-visualization": "2.4.2",
"hecrj/html-parser": "2.4.0",
"mdgriffith/elm-ui": "1.1.8"
},
"indirect": {
"avh4/elm-color": "1.0.0",
"elm/parser": "1.1.0",
"elm/random": "1.0.0",
"elm/regex": "1.0.0",
"elm/time": "1.0.0",
"elm/virtual-dom": "1.0.3",
"elmcraft/core-extra": "2.2.0",
"folkertdev/elm-deque": "3.0.1",
"folkertdev/one-true-path-experiment": "6.0.1",
"folkertdev/svg-path-lowlevel": "4.0.1",
"gampleman/elm-rosetree": "1.1.0",
"ianmackenzie/elm-1d-parameter": "1.0.1",
"ianmackenzie/elm-float-extra": "1.1.0",
"ianmackenzie/elm-geometry": "3.11.0",
"ianmackenzie/elm-interval": "3.1.0",
"ianmackenzie/elm-triangular-mesh": "1.1.0",
"ianmackenzie/elm-units": "2.10.0",
"ianmackenzie/elm-units-interval": "3.2.0",
"ianmackenzie/elm-units-prefixed": "2.8.0",
"justinmimbs/date": "4.1.0",
"justinmimbs/time-extra": "1.2.0",
"rtfeldman/elm-hex": "1.0.0",
"ryan-haskell/date-format": "1.0.0"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

View file

@ -0,0 +1,10 @@
module Config.Helpers.Response exposing (..)
import Config.Style.Colour.Helpers exposing (..)
import Element as E exposing (..)
import Element.Background as B exposing (color)
import Html.Attributes exposing (style)
dummy =
[]

View file

@ -0,0 +1,16 @@
module Config.Helpers.Viewport exposing
( Msg
, resetViewport
)
import Browser.Dom as Dom exposing (setViewport)
import Task exposing (attempt)
type Msg
= NoOp
resetViewport : Cmd Msg
resetViewport =
Task.attempt (\_ -> NoOp) (Dom.setViewportOf "scroll-container" 0 0)

View file

@ -0,0 +1,88 @@
module Config.Style.Colour.Helpers exposing
( darkColourTheme
, getColour
, getColourScheme
, getScheme
, lightColourTheme
)
import Config.Style.Colour.Types
exposing
( ColourScheme
, Theme(..)
)
import Element as E exposing (..)
import Element.Font as F exposing (color)
import Shared
lightColourTheme : ColourScheme
lightColourTheme =
{ -- Core brand colors
primary = rgb255 92 128 105 -- ll green
, secondary = rgb255 92 128 105 -- ll green
-- Surface colorsc
, background = rgb255 192 228 205 --3lite green--
, surface = rgb255 239 255 252 -- white green
, surfaceVariant = rgb255 229 255 242 -- dwhite green
-- Text colors
, onPrimary = rgb255 255 255 255
, onSecondary = rgb255 255 255 255
, onBackground = rgb255 72 108 85 -- l green
, onSurface = rgb255 32 68 45 -- m green
-- Special purpose
, shadow = rgba 34 139 34 0.15
, overlay = rgba 27 94 27 0.6
, border = rgb255 200 230 201
, focus = rgb255 102 187 106
, transparent = rgba 0 0 0 0
}
darkColourTheme : ColourScheme
darkColourTheme =
{ -- Core brand colors
primary = rgb255 92 128 105 -- ll green (unchanged)
, secondary = rgb255 32 68 45 -- g green (slightly deeper)
-- Surface colors
, background = rgb255 2 38 15 -- darker dk dk green
, surface = rgb255 18 50 28 -- dk green (slightly lighter than background)
, surfaceVariant = rgb255 38 78 50 -- mm green (lighter for contrast)
-- Text colors
, onPrimary = rgb255 92 128 105 -- ll green (unchanged)
, onSecondary = rgb255 122 168 145 -- lll green (slightly lighter for contrast)
, onBackground = rgb255 152 188 165 -- dlite green (brighter for clarity)
, onSurface = rgb255 102 138 115 -- medium green (brighter than before)
-- Special purpose
, shadow = rgba 0 0 0 0.4
, overlay = rgba 0 0 0 0.7
, border = rgb255 66 90 66 -- slightly darker than before
, focus = rgb255 129 199 132 -- unchanged
, transparent = rgba 0 0 0 0
}
getColourScheme : Theme -> ColourScheme
getColourScheme theme =
case theme of
Light ->
lightColourTheme
Dark ->
darkColourTheme
getColour : Theme -> (ColourScheme -> Color) -> Color
getColour theme colourAccessor =
colourAccessor (getColourScheme theme)
getScheme : Shared.Model -> ColourScheme
getScheme shared =
getColourScheme shared.theme

View file

@ -0,0 +1,60 @@
module Config.Style.Colour.Types exposing
( ColourScheme
, Theme(..)
, themeFromString
, themeToString
)
import Element exposing (Color)
import Json.Decode as D
import Json.Encode as E
type alias ColourScheme =
{ -- Core brand colors
primary : Color
, secondary : Color
-- Surface colors
, background : Color
, surface : Color
, surfaceVariant : Color
-- Text colors
, onPrimary : Color
, onSecondary : Color
, onBackground : Color
, onSurface : Color
-- Special purpose
, shadow : Color
, overlay : Color
, border : Color
, focus : Color
, transparent : Color
}
type Theme
= Light
| Dark
themeToString : Theme -> String
themeToString theme =
case theme of
Light ->
"light"
Dark ->
"dark"
themeFromString : String -> Theme
themeFromString str =
case str of
"dark" ->
Dark
_ ->
Light

View file

@ -0,0 +1,79 @@
module Config.Style.Fonts exposing
( FontScale
, FontSizes
, bodyFont
, createFontScale
, createFontSizes
, defaultFontScale
, headerFont
, paragraphSpacing
)
import Element
exposing
( Attr
, Attribute
, spacing
)
import Element.Font as F
exposing
( size
, typeface
)
headerFont : F.Font
headerFont =
F.typeface "Montez"
bodyFont : F.Font
bodyFont =
F.typeface "Playpen Sans"
paragraphSpacing : Attribute msg
paragraphSpacing =
spacing 0
type FontScale
= FontScale Float
createFontScale : Float -> FontScale
createFontScale baseFontSize =
FontScale baseFontSize
defaultFontScale : FontScale
defaultFontScale =
FontScale 16.0
type alias FontSizes msg =
{ verySmall : Attribute msg
, small : Attribute msg
, normal : Attribute msg
, medium : Attribute msg
, large : Attribute msg
, extraLarge : Attribute msg
, huge : Attribute msg
}
createFontSizes : FontScale -> FontSizes msg
createFontSizes (FontScale base) =
let
scaled multiplier =
F.size (round (base * multiplier))
in
{ -- assuming a 16px base:
verySmall = scaled 0.75 -- 12px
, small = scaled 0.875 -- 14px
, normal = scaled 1.0 -- 16px
, medium = scaled 1.125 -- 18px
, large = scaled 1.25 -- 20px
, extraLarge = scaled 1.5 -- 24px
, huge = scaled 2.0 -- 32px at
}

View file

@ -0,0 +1,18 @@
module Config.Style.Glow exposing (..)
import Config.Style.Colour.Helpers exposing (..)
import Element exposing (Attr)
import Element.Border as D exposing (glow)
import Html
import Html.Attributes as H
import Shared
hoverGlow : Shared.Model -> Attr decorative msg
hoverGlow shared =
D.glow (getScheme shared).onBackground 4
itemShadow : Html.Attribute msg
itemShadow =
H.style "filter" "drop-shadow(0px 0px 4px rgba(0,0,0,1))"

View file

@ -0,0 +1,33 @@
module Config.Style.Icons.Helpers exposing (buildSvg)
import Config.Style.Icons.Types as SvgTypes
exposing
( InnerPart
, OuterPart
)
import Element as E
exposing
( Element
, el
, html
)
import Svg exposing (svg)
-- , E.explain <| Debug.todo
{- buildSvg consumes an inner record to construct most of an SVG, and an outer record to supply
any potentially varying TypedSvg.Core.Attribute msgs and wrap it in an Element.el so it can be
used by elm-ui. It provides a consistent interface for inserting SVGs into elm-ui code.
-}
buildSvg : SvgTypes.OuterPart msg -> SvgTypes.InnerPart msg -> Element msg
buildSvg outer inner =
el
outer.elementAttributes
<|
html <|
Svg.svg
(outer.svgAttributes ++ inner.svgAttributes)
inner.svg

View file

@ -0,0 +1,176 @@
module Config.Style.Icons.Icons exposing (..)
import Config.Style.Icons.Helpers as HeSvg exposing (buildSvg)
import Config.Style.Icons.Types as SvgTypes
exposing
( InnerPart
, OuterPart
)
import Element as E exposing (Element)
import Html exposing (Html)
import Svg
exposing
( path
, svg
)
import Svg.Attributes as SvgAttr
discord : SvgTypes.OuterPart msg -> Element msg
discord inner =
HeSvg.buildSvg inner
{ svgAttributes =
[ SvgAttr.viewBox "0 0 640 512"
, SvgAttr.fill "currentColor"
]
, svg =
[ path
[ SvgAttr.d "M524.5 69.8a1.5 1.5 0 0 0 -.8-.7A485.1 485.1 0 0 0 404.1 32a1.8 1.8 0 0 0 -1.9 .9 337.5 337.5 0 0 0 -14.9 30.6 447.8 447.8 0 0 0 -134.4 0 309.5 309.5 0 0 0 -15.1-30.6 1.9 1.9 0 0 0 -1.9-.9A483.7 483.7 0 0 0 116.1 69.1a1.7 1.7 0 0 0 -.8 .7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 0 0 .8 1.4A487.7 487.7 0 0 0 176 479.9a1.9 1.9 0 0 0 2.1-.7A348.2 348.2 0 0 0 208.1 430.4a1.9 1.9 0 0 0 -1-2.6 321.2 321.2 0 0 1 -45.9-21.9 1.9 1.9 0 0 1 -.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8 0 0 1 1.9-.3c96.2 43.9 200.4 43.9 295.5 0a1.8 1.8 0 0 1 1.9 .2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9 0 0 1 -.2 3.1 301.4 301.4 0 0 1 -45.9 21.8 1.9 1.9 0 0 0 -1 2.6 391.1 391.1 0 0 0 30 48.8 1.9 1.9 0 0 0 2.1 .7A486 486 0 0 0 610.7 405.7a1.9 1.9 0 0 0 .8-1.4C623.7 277.6 590.9 167.5 524.5 69.8zM222.5 337.6c-29 0-52.8-26.6-52.8-59.2S193.1 219.1 222.5 219.1c29.7 0 53.3 26.8 52.8 59.2C275.3 311 251.9 337.6 222.5 337.6zm195.4 0c-29 0-52.8-26.6-52.8-59.2S388.4 219.1 417.9 219.1c29.7 0 53.3 26.8 52.8 59.2C470.7 311 447.5 337.6 417.9 337.6z"
]
[]
]
}
twitter : SvgTypes.OuterPart msg -> Element msg
twitter inner =
HeSvg.buildSvg inner
{ svgAttributes =
[ SvgAttr.viewBox "0 0 512 512"
, SvgAttr.fill "currentColor"
]
, svg =
[ path
[ SvgAttr.d "M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"
]
[]
]
}
facebook : SvgTypes.OuterPart msg -> Element msg
facebook inner =
HeSvg.buildSvg inner
{ svgAttributes =
[ SvgAttr.viewBox "0 0 640 640"
, SvgAttr.fill "currentColor"
]
, svg =
[ path
[ SvgAttr.d "M240 363.3L240 576L356 576L356 363.3L442.5 363.3L460.5 265.5L356 265.5L356 230.9C356 179.2 376.3 159.4 428.7 159.4C445 159.4 458.1 159.8 465.7 160.6L465.7 71.9C451.4 68 416.4 64 396.2 64C289.3 64 240 114.5 240 223.4L240 265.5L174 265.5L174 363.3L240 363.3z"
]
[]
]
}
tiktok : SvgTypes.OuterPart msg -> Element msg
tiktok inner =
HeSvg.buildSvg inner
{ svgAttributes =
[ SvgAttr.viewBox "0 0 640 640"
, SvgAttr.fill "currentColor"
]
, svg =
[ path
[ SvgAttr.d "M544.5 273.9C500.5 274 457.5 260.3 421.7 234.7L421.7 413.4C421.7 446.5 411.6 478.8 392.7 506C373.8 533.2 347.1 554 316.1 565.6C285.1 577.2 251.3 579.1 219.2 570.9C187.1 562.7 158.3 545 136.5 520.1C114.7 495.2 101.2 464.1 97.5 431.2C93.8 398.3 100.4 365.1 116.1 336C131.8 306.9 156.1 283.3 185.7 268.3C215.3 253.3 248.6 247.8 281.4 252.3L281.4 342.2C266.4 337.5 250.3 337.6 235.4 342.6C220.5 347.6 207.5 357.2 198.4 369.9C189.3 382.6 184.4 398 184.5 413.8C184.6 429.6 189.7 444.8 199 457.5C208.3 470.2 221.4 479.6 236.4 484.4C251.4 489.2 267.5 489.2 282.4 484.3C297.3 479.4 310.4 469.9 319.6 457.2C328.8 444.5 333.8 429.1 333.8 413.4L333.8 64L421.8 64C421.7 71.4 422.4 78.9 423.7 86.2C426.8 102.5 433.1 118.1 442.4 131.9C451.7 145.7 463.7 157.5 477.6 166.5C497.5 179.6 520.8 186.6 544.6 186.6L544.6 274z"
]
[]
]
}
instagram : SvgTypes.OuterPart msg -> Element msg
instagram inner =
HeSvg.buildSvg inner
{ svgAttributes =
[ SvgAttr.viewBox "0 0 640 640"
, SvgAttr.fill "currentColor"
]
, svg =
[ path
[ SvgAttr.d "M320.3 205C256.8 204.8 205.2 256.2 205 319.7C204.8 383.2 256.2 434.8 319.7 435C383.2 435.2 434.8 383.8 435 320.3C435.2 256.8 383.8 205.2 320.3 205zM319.7 245.4C360.9 245.2 394.4 278.5 394.6 319.7C394.8 360.9 361.5 394.4 320.3 394.6C279.1 394.8 245.6 361.5 245.4 320.3C245.2 279.1 278.5 245.6 319.7 245.4zM413.1 200.3C413.1 185.5 425.1 173.5 439.9 173.5C454.7 173.5 466.7 185.5 466.7 200.3C466.7 215.1 454.7 227.1 439.9 227.1C425.1 227.1 413.1 215.1 413.1 200.3zM542.8 227.5C541.1 191.6 532.9 159.8 506.6 133.6C480.4 107.4 448.6 99.2 412.7 97.4C375.7 95.3 264.8 95.3 227.8 97.4C192 99.1 160.2 107.3 133.9 133.5C107.6 159.7 99.5 191.5 97.7 227.4C95.6 264.4 95.6 375.3 97.7 412.3C99.4 448.2 107.6 480 133.9 506.2C160.2 532.4 191.9 540.6 227.8 542.4C264.8 544.5 375.7 544.5 412.7 542.4C448.6 540.7 480.4 532.5 506.6 506.2C532.8 480 541 448.2 542.8 412.3C544.9 375.3 544.9 264.5 542.8 227.5zM495 452C487.2 471.6 472.1 486.7 452.4 494.6C422.9 506.3 352.9 503.6 320.3 503.6C287.7 503.6 217.6 506.2 188.2 494.6C168.6 486.8 153.5 471.7 145.6 452C133.9 422.5 136.6 352.5 136.6 319.9C136.6 287.3 134 217.2 145.6 187.8C153.4 168.2 168.5 153.1 188.2 145.2C217.7 133.5 287.7 136.2 320.3 136.2C352.9 136.2 423 133.6 452.4 145.2C472 153 487.1 168.1 495 187.8C506.7 217.3 504 287.3 504 319.9C504 352.5 506.7 422.6 495 452z"
]
[]
]
}
patreon : SvgTypes.OuterPart msg -> Element msg
patreon inner =
HeSvg.buildSvg inner
{ svgAttributes =
[ SvgAttr.viewBox "0 0 640 640"
, SvgAttr.fill "currentColor"
]
, svg =
[ path
[ SvgAttr.d "M554 217.8C553.9 152.4 503 98.8 443.3 79.5C369.1 55.5 271.3 59 200.4 92.4C114.6 132.9 87.6 221.7 86.6 310.2C85.8 383 93 574.6 201.2 576C281.5 577 293.5 473.5 330.7 423.7C357.1 388.2 391.2 378.2 433.1 367.8C505.1 350 554.2 293.1 554.1 217.8L554 217.8z"
]
[]
]
}
reddit : SvgTypes.OuterPart msg -> Element msg
reddit inner =
HeSvg.buildSvg inner
{ svgAttributes =
[ SvgAttr.viewBox "0 0 640 640"
, SvgAttr.fill "currentColor"
]
, svg =
[ path
[ SvgAttr.d "M437 202.6C411.8 202.6 390.7 185.1 385.1 161.6C354.5 165.9 330.9 192.3 330.9 224L330.9 224.2C378.3 226 421.5 239.3 455.8 260.5C468.4 250.8 484.2 245 501.3 245C542.6 245 576 278.4 576 319.7C576 349.5 558.6 375.2 533.3 387.2C530.9 474 436.3 543.8 320.1 543.8C203.9 543.8 109.5 474.1 107 387.4C81.6 375.5 64 349.7 64 319.7C64 278.4 97.4 245 138.7 245C155.9 245 171.7 250.8 184.4 260.6C218.4 239.5 261.2 226.2 308.1 224.2L308.1 223.9C308.1 179.6 341.8 143 384.9 138.4C389.8 114.2 411.2 96 437 96C466.4 96 490.3 119.9 490.3 149.3C490.3 178.7 466.4 202.6 437 202.6zM221.5 319.3C200.6 319.3 182.6 340.1 181.3 367.2C180 394.3 198.4 405.3 219.3 405.3C240.2 405.3 255.9 395.5 257.1 368.4C258.3 341.3 242.4 319.3 221.4 319.3L221.5 319.3zM459 367.1C457.8 340 439.8 319.2 418.8 319.2C397.8 319.2 381.9 341.2 383.1 368.3C384.3 395.4 400 405.2 420.9 405.2C441.8 405.2 460.2 394.2 458.9 367.1L459 367.1zM398.9 437.9C400.4 434.3 397.9 430.2 394 429.8C371 427.5 346.1 426.2 320.2 426.2C294.3 426.2 269.4 427.5 246.4 429.8C242.5 430.2 240 434.3 241.5 437.9C254.4 468.7 284.8 490.3 320.2 490.3C355.6 490.3 386 468.7 398.9 437.9z"
]
[]
]
}
copyLink : SvgTypes.OuterPart msg -> Element msg
copyLink inner =
HeSvg.buildSvg inner
{ svgAttributes =
[ SvgAttr.viewBox "0 0 640 512"
, SvgAttr.fill "currentColor"
]
, svg =
[ path
[ SvgAttr.d "M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"
]
[]
]
}
sun : SvgTypes.OuterPart msg -> Element msg
sun inner =
HeSvg.buildSvg inner
{ svgAttributes =
[ SvgAttr.viewBox "0 0 512 512"
, SvgAttr.fill "currentColor"
]
, svg =
[ path
[ SvgAttr.d "M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"
]
[]
]
}
moon : SvgTypes.OuterPart msg -> Element msg
moon inner =
HeSvg.buildSvg inner
{ svgAttributes =
[ SvgAttr.viewBox "0 0 384 512"
, SvgAttr.fill "currentColor"
]
, svg =
[ path
[ SvgAttr.d "M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176 0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"
]
[]
]
}

View file

@ -0,0 +1,28 @@
module Config.Style.Icons.Types exposing
( InnerPart
, OuterPart
)
{-| The types used for SVG management.
-}
import Element exposing (Attribute)
import Shared exposing (Model)
import Svg exposing (svg)
{-| The outer record for the SVG builder. This is explained in ../Helpers/Svg.elm.
-}
type alias OuterPart msg =
{ elementAttributes : List (Element.Attribute msg)
, sharedModel : Shared.Model
, svgAttributes : List (Svg.Attribute msg)
}
{-| The inner record for the SVG builder. This is explained in ../Helpers/Svg.elm.
-}
type alias InnerPart msg =
{ svgAttributes : List (Svg.Attribute msg)
, svg : List (Svg.Svg msg)
}

View file

@ -0,0 +1,69 @@
module Config.Style.Transitions exposing (..)
import Config.Style.Colour.Helpers exposing (..)
import Config.Style.Glow exposing (..)
import Element
exposing
( Attribute
, htmlAttribute
, mouseOver
)
import Element.Background as B exposing (color)
import Element.Border as D exposing (color)
import Element.Font as F exposing (color)
import Html.Attributes as H exposing (style)
import Shared
slowTime : String
slowTime =
"0.5s"
fastTime : String
fastTime =
"0.1s"
transition : String -> Attribute msg
transition time =
htmlAttribute <| style "transition" ("all " ++ time ++ " ease-in-out")
-- Specific transition functions for separating fast and slow properties
cardTransitions : Attribute msg
cardTransitions =
htmlAttribute <|
style "transition"
("background-color "
++ slowTime
++ " ease-in-out, "
++ "color "
++ slowTime
++ " ease-in-out, "
++ "border-color "
++ slowTime
++ " ease-in-out, "
++ "box-shadow "
++ fastTime
++ " ease-in-out, "
++ "transform "
++ fastTime
++ " ease-in-out"
)
hoverButton : Shared.Model -> Attribute msg
hoverButton shared =
mouseOver
[ B.color (getScheme shared).secondary
, F.color (getScheme shared).primary
]
imageFadeTransition : String -> Attribute msg
imageFadeTransition time =
htmlAttribute <| style "transition" ("opacity " ++ time ++ " ease-in-out")

View file

@ -0,0 +1,205 @@
module Effect exposing
( Effect
, none, batch
, sendCmd, sendMsg, sendSharedMsg
, pushRoute, replaceRoute, loadExternalUrl
, map, toCmd
)
{-|
@docs Effect
@docs none, batch
@docs sendCmd, sendMsg, sendSharedMsg
@docs pushRoute, replaceRoute, loadExternalUrl
@docs map, toCmd
-}
import Browser.Navigation
import Dict exposing (Dict)
import Route exposing (Route)
import Route.Path
import Shared.Model
import Shared.Msg
import Task
import Url exposing (Url)
type Effect msg
= -- BASICS
None
| Batch (List (Effect msg))
| SendCmd (Cmd msg)
-- ROUTING
| PushUrl String
| ReplaceUrl String
| LoadExternalUrl String
-- SHARED
| SendSharedMsg Shared.Msg.Msg
-- BASICS
{-| Don't send any effect.
-}
none : Effect msg
none =
None
{-| Send multiple effects at once.
-}
batch : List (Effect msg) -> Effect msg
batch =
Batch
{-| Send a normal `Cmd msg` as an effect, something like `Http.get` or `Random.generate`.
-}
sendCmd : Cmd msg -> Effect msg
sendCmd =
SendCmd
{-| Send a message as an effect. Useful when emitting events from UI components.
-}
sendMsg : msg -> Effect msg
sendMsg msg =
Task.succeed msg
|> Task.perform identity
|> SendCmd
{-| Send a shared message as an effect. Useful for communicating between pages and shared state.
-}
sendSharedMsg : Shared.Msg.Msg -> Effect msg
sendSharedMsg sharedMsg =
SendSharedMsg sharedMsg
-- ROUTING
{-| Set the new route, and make the back button go back to the current route.
-}
pushRoute :
{ path : Route.Path.Path
, query : Dict String String
, hash : Maybe String
}
-> Effect msg
pushRoute route =
PushUrl (Route.toString route)
{-| Set given path as route (without any query params or hash), and make the back button go back to the current route.
-}
pushPath :
Route.Path.Path
-> Effect msg
pushPath path =
PushUrl (Route.toString { path = path, query = Dict.empty, hash = Nothing })
{-| Set the new route, but replace the previous one, so clicking the back
button **won't** go back to the previous route.
-}
replaceRoute :
{ path : Route.Path.Path
, query : Dict String String
, hash : Maybe String
}
-> Effect msg
replaceRoute route =
ReplaceUrl (Route.toString route)
{-| Set given path as route (without any query params or hash), but replace the previous route,
so clicking the back button **won't** go back to the previous route
-}
replacePath :
Route.Path.Path
-> Effect msg
replacePath path =
ReplaceUrl (Route.toString { path = path, query = Dict.empty, hash = Nothing })
{-| Redirect users to a new URL, somewhere external your web application.
-}
loadExternalUrl : String -> Effect msg
loadExternalUrl =
LoadExternalUrl
-- INTERNALS
{-| Elm Land depends on this function to connect pages and layouts
together into the overall app.
-}
map : (msg1 -> msg2) -> Effect msg1 -> Effect msg2
map fn effect =
case effect of
None ->
None
Batch list ->
Batch (List.map (map fn) list)
SendCmd cmd ->
SendCmd (Cmd.map fn cmd)
PushUrl url ->
PushUrl url
ReplaceUrl url ->
ReplaceUrl url
LoadExternalUrl url ->
LoadExternalUrl url
SendSharedMsg sharedMsg ->
SendSharedMsg sharedMsg
{-| Elm Land depends on this function to perform your effects.
-}
toCmd :
{ key : Browser.Navigation.Key
, url : Url
, shared : Shared.Model.Model
, fromSharedMsg : Shared.Msg.Msg -> msg
, batch : List msg -> msg
, toCmd : msg -> Cmd msg
}
-> Effect msg
-> Cmd msg
toCmd options effect =
case effect of
None ->
Cmd.none
Batch list ->
Cmd.batch (List.map (toCmd options) list)
SendCmd cmd ->
cmd
PushUrl url ->
Browser.Navigation.pushUrl options.key url
ReplaceUrl url ->
Browser.Navigation.replaceUrl options.key url
LoadExternalUrl url ->
Browser.Navigation.load url
SendSharedMsg sharedMsg ->
Task.succeed sharedMsg
|> Task.perform options.fromSharedMsg

View file

@ -0,0 +1,112 @@
module Pages.Home_ exposing (Model, Msg, page)
import Config.Helpers.Response exposing (..)
import Config.Helpers.Viewport exposing (resetViewport)
import Config.Pages.Home.Background exposing (..)
import Config.Pages.Home.CardMaker.Helpers.Icon exposing (..)
import Config.Pages.Home.CardMaker.Helpers.Image exposing (..)
import Config.Pages.Home.CardMaker.Records exposing (..)
import Config.Pages.Home.CardMaker.Types exposing (..)
import Config.Pages.Home.Helpers exposing (..)
import Config.Style.Colour.Helpers exposing (..)
import Config.Style.Colour.Types exposing (Theme(..))
import Config.Style.Fonts exposing (..)
import Config.Style.Glow exposing (..)
import Config.Style.Icons.Icons exposing (..)
import Config.Style.Transitions exposing (..)
import Effect exposing (Effect)
import Element as E exposing (..)
import Element.Background as B exposing (..)
import Element.Border as D exposing (..)
import Element.Events as V
import Element.Font as F exposing (..)
import Html exposing (Html)
import Html.Attributes as Attr
import Page exposing (Page)
import Ports
import Route exposing (Route)
import Shared exposing (Model)
import Shared.Msg
import Svg.Attributes as SvgAttr exposing (clip)
import View exposing (View)
page : Shared.Model -> Route () -> Page Model Msg
page shared route =
Page.new
{ init = init
, update = update
, subscriptions = subscriptions
, view = view shared
}
-- INIT
type alias Model =
{}
init : () -> ( Model, Effect Msg )
init () =
( {}
, Effect.map
(\_ -> NoOp)
(Effect.sendCmd resetViewport)
)
-- UPDATE
type Msg
= NoOp
| ToggleTheme
update : Msg -> Model -> ( Model, Effect Msg )
update msg model =
case msg of
NoOp ->
( model
, Effect.none
)
ToggleTheme ->
( model
, Effect.sendSharedMsg Shared.Msg.ToggleTheme
)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view : Shared.Model -> Model -> View Msg
view shared model =
{ title = ""
, attributes = [ F.family [ headerFont ] ]
, element = homePage shared (createFontSizes shared.fontScale)
}
paddingHelper : Attribute msg
paddingHelper =
padding 10
homePage : Shared.Model -> FontSizes Msg -> Element Msg
homePage shared fontSizes =
E.el [] <| text "Hello world!"

View file

@ -0,0 +1,76 @@
module Pages.NotFound_ exposing (Model, Msg, page)
import Config.Helpers.Viewport exposing (resetViewport)
import Effect exposing (Effect)
import Element as E exposing (..)
import Html exposing (..)
import Page exposing (Page)
import Route exposing (Route)
import Route.Path
import Shared
import View exposing (View)
page : Shared.Model -> Route () -> Page Model Msg
page shared route =
Page.new
{ init = init
, update = update
, subscriptions = subscriptions
, view = view shared
}
-- INIT
type alias Model =
{}
init : () -> ( Model, Effect Msg )
init () =
( {}
, Effect.map
(\_ -> NoOp)
(Effect.sendCmd resetViewport)
)
-- UPDATE
type Msg
= NoOp
update : Msg -> Model -> ( Model, Effect Msg )
update msg model =
case msg of
NoOp ->
( model
, Effect.none
)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view : Shared.Model -> Model -> View Msg
view shared model =
{ title = ""
, attributes = []
, element = E.row [] [ E.text "Page not found" ]
}

View file

@ -0,0 +1,28 @@
port module Ports exposing
( assetsLoaded
, checkAssetsLoaded
, copyToClipboard
, loadTheme
, saveTheme
, sendToLocalStorage
)
import Json.Encode as Encode
port sendToLocalStorage : { key : String, value : Encode.Value } -> Cmd msg
port copyToClipboard : String -> Cmd msg
port saveTheme : String -> Cmd msg
port loadTheme : (String -> msg) -> Sub msg
port checkAssetsLoaded : () -> Cmd msg
port assetsLoaded : (() -> msg) -> Sub msg

View file

@ -0,0 +1,159 @@
module Shared exposing
( Flags, decoder
, Model, Msg
, init, update, subscriptions
)
{-|
@docs Flags, decoder
@docs Model, Msg
@docs init, update, subscriptions
-}
import Browser.Events as BR exposing (..)
import Config.Style.Colour.Types
exposing
( Theme(..)
, themeFromString
, themeToString
)
import Config.Style.Fonts exposing (FontScale, createFontScale)
import Dict
import Effect exposing (Effect, none)
import Element as E exposing (..)
import Json.Decode exposing (..)
import Ports
import Process
import Route exposing (Route)
import Route.Path
import Shared.Model exposing (..)
import Shared.Msg
import Task
-- FLAGS
type alias Flags =
{ height : Int
, width : Int
, baseFontSize : Float
}
decoder : Json.Decode.Decoder Flags
decoder =
Json.Decode.map3
(\height width baseFontSize ->
{ height = height
, width = width
, baseFontSize = baseFontSize
}
)
(field "height" int)
(field "width" int)
(field "baseFontSize" float)
-- INIT
type alias Model =
Shared.Model.Model
init : Result Json.Decode.Error Flags -> Route () -> ( Model, Effect Msg )
init flagsResult route =
( modelFromFlagsResult flagsResult
, Effect.sendCmd (Ports.checkAssetsLoaded ())
)
modelFromFlagsResult : Result Error Flags -> Model
modelFromFlagsResult f =
case f of
Ok flags ->
{ device = classifyDevice flags
, height = flags.height
, width = flags.width
, theme = Dark
, fontScale = createFontScale flags.baseFontSize
, isLoading = True
, isFadingOut = False
}
Err e ->
{ device =
classifyDevice
{ height = 0
, width = 0
}
, height = 10
, width = 10
, theme = Dark
, fontScale = createFontScale 16.0
, isLoading = True
, isFadingOut = False
}
-- UPDATE
type alias Msg =
Shared.Msg.Msg
update : Route () -> Msg -> Model -> ( Model, Effect Msg )
update route msg model =
case msg of
Shared.Msg.LoadTheme themeString ->
( { model | theme = themeFromString themeString }
, Effect.none
)
Shared.Msg.ToggleTheme ->
( model
, Effect.sendCmd (Ports.saveTheme "toggle")
)
Shared.Msg.Resize width height ->
( { model
| device =
{ height = height
, width = width
}
|> E.classifyDevice
, height = height
, width = width
}
, Effect.none
)
Shared.Msg.LoadingComplete ->
( { model | isFadingOut = True }
, Effect.sendCmd (Process.sleep 600 |> Task.perform (\_ -> Shared.Msg.StartFadeOut))
)
Shared.Msg.StartFadeOut ->
( { model | isLoading = False, isFadingOut = False }
, Effect.none
)
-- SUBSCRIPTIONS
subscriptions : Route () -> Model -> Sub Msg
subscriptions route model =
Sub.batch
[ BR.onResize Shared.Msg.Resize
, Ports.loadTheme Shared.Msg.LoadTheme
, Ports.assetsLoaded (\_ -> Shared.Msg.LoadingComplete)
]

View file

@ -0,0 +1,23 @@
module Shared.Model exposing (Model)
import Config.Style.Colour.Types exposing (Theme(..))
import Config.Style.Fonts exposing (FontScale)
import Element exposing (Device)
{-| Normally, this value would live in "Shared.elm"
but that would lead to a circular dependency import cycle.
For that reason, both `Shared.Model` and `Shared.Msg` are in their
own file, so they can be imported by `Effect.elm`
-}
type alias Model =
{ height : Int
, width : Int
, device : Device
, theme : Theme
, fontScale : FontScale
, isLoading : Bool
, isFadingOut : Bool
}

View file

@ -0,0 +1,18 @@
module Shared.Msg exposing (Msg(..))
{-| -}
{-| Normally, this value would live in "Shared.elm"
but that would lead to a circular dependency import cycle.
For that reason, both `Shared.Model` and `Shared.Msg` are in their
own file, so they can be imported by `Effect.elm`
-}
type Msg
= Resize Int Int
| LoadTheme String
| ToggleTheme
| LoadingComplete
| StartFadeOut

View file

@ -0,0 +1,100 @@
module View exposing
( View, map
, none, fromString
, toBrowserDocument
)
{-|
@docs View, map
@docs none, fromString
@docs toBrowserDocument
-}
import Browser
import Config.Style.LoadingScreen exposing (loadingScreen)
import Element exposing (Attribute, Element, layout, mapAttribute, text)
import Html
import Route exposing (Route)
import Shared.Model
type alias View msg =
{ title : String
, attributes : List (Attribute msg)
, element : Element msg
}
{-| Used internally by Elm Land to create your application
so it works with Elm's expected `Browser.Document msg` type.
-}
toBrowserDocument :
{ shared : Shared.Model.Model
, route : Route ()
, view : View msg
}
-> Browser.Document msg
toBrowserDocument { shared, view } =
{ title = view.title
, body =
[ layout
(if shared.isLoading || shared.isFadingOut then
Element.inFront (loadingScreen shared shared.isLoading) :: view.attributes
else
view.attributes
)
view.element
]
}
-- toBrowserDocument :
-- { shared : Shared.Model.Model
-- , route : Route ()
-- , view : View msg
-- }
-- -> Browser.Document msg
-- toBrowserDocument { view } =
-- { title = view.title
-- , body = view.body
-- }
{-| Used internally by Elm Land to connect your pages together.
-}
map : (msg1 -> msg2) -> View msg1 -> View msg2
map fn view =
{ title = view.title
, attributes = List.map (mapAttribute fn) view.attributes
, element = Element.map fn view.element
}
{-| Used internally by Elm Land whenever transitioning between
authenticated pages.
-}
none : View msg
none =
{ title = ""
, attributes = []
, element = Element.none
}
{-| If you customize the `View` module, anytime you run `elm-land add page`,
the generated page will use this when adding your `view` function.
That way your app will compile after adding new pages, and you can see
the new page working in the web browser!
-}
fromString : String -> View msg
fromString moduleName =
{ title = moduleName
, attributes = []
, element = text moduleName
}

View file

@ -0,0 +1,134 @@
// This returns the flags passed into your Elm application
export const flags = async ({ env }: ElmLand.FlagsArgs) => {
// Get user's preferred font size by creating a temporary element
const tempDiv = document.createElement("div");
tempDiv.style.fontSize = "1rem";
tempDiv.style.position = "absolute";
tempDiv.style.visibility = "hidden";
document.body.appendChild(tempDiv);
const baseFontSize = parseFloat(window.getComputedStyle(tempDiv).fontSize);
document.body.removeChild(tempDiv);
return {
width: window.innerWidth,
height: window.innerHeight,
baseFontSize: baseFontSize,
};
};
// This function is called after your Elm app starts
export const onReady = ({ app, env }: ElmLand.OnReadyArgs) => {
console.log("Elm is ready", app);
// Simple loading screen with minimum display time
let loadingComplete = false;
function completeLoading() {
if (!loadingComplete && app.ports?.assetsLoaded?.send) {
loadingComplete = true;
console.log("Loading complete - starting fade out sequence");
app.ports.assetsLoaded.send(null);
}
}
// Set up the loading completion trigger
if (app.ports?.checkAssetsLoaded?.subscribe) {
app.ports.checkAssetsLoaded.subscribe(() => {
console.log("Elm app ready - starting loading timer");
// Minimum display time for loading screen (so users can see it)
const minDisplayTime = 1200; // 1.2 seconds
// Check if fonts are loaded, then wait for minimum time
if (document.fonts) {
document.fonts.ready.then(() => {
console.log(
"Fonts loaded, waiting for minimum display time of",
minDisplayTime,
"ms",
);
setTimeout(() => {
console.log("Minimum display time elapsed, triggering fade out");
completeLoading();
}, minDisplayTime);
});
} else {
// Fallback if fonts API not supported
console.log("Fonts API not supported, using fallback timing");
setTimeout(completeLoading, minDisplayTime + 300);
}
});
}
// Determine initial theme first
let initialTheme = "dark"; // Match Elm's hardcoded default
const savedTheme = localStorage.getItem("app-theme");
if (savedTheme) {
initialTheme = savedTheme;
} else if (window.matchMedia) {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
initialTheme = mediaQuery.matches ? "dark" : "light";
}
// Store the determined initial theme
localStorage.setItem("app-theme", initialTheme);
// Handle saving theme to localStorage AND toggle requests
if (app.ports?.saveTheme?.subscribe) {
app.ports.saveTheme.subscribe((themeOrSignal: unknown) => {
if (themeOrSignal === "toggle") {
// Handle toggle request - now we know localStorage has the current theme
const currentTheme = localStorage.getItem("app-theme") || initialTheme;
const newTheme = currentTheme === "light" ? "dark" : "light";
console.log("Toggling theme from", currentTheme, "to", newTheme);
localStorage.setItem("app-theme", newTheme);
// Send new theme back to Elm
if (app.ports?.loadTheme?.send) {
app.ports.loadTheme.send(newTheme);
}
} else {
// Handle normal theme saving (when theme is set directly)
console.log("Saving theme:", themeOrSignal);
localStorage.setItem("app-theme", themeOrSignal as string);
}
});
}
// Load theme from localStorage on startup
if (app.ports?.loadTheme?.send) {
console.log("Loading initial theme:", initialTheme);
app.ports.loadTheme.send(initialTheme);
}
// Optional: Listen for system theme changes
if (window.matchMedia) {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
mediaQuery.addEventListener("change", (e) => {
// Only apply if user hasn't manually saved a theme preference
if (!savedTheme) {
const systemTheme = e.matches ? "dark" : "light";
console.log("System theme changed:", systemTheme);
localStorage.setItem("app-theme", systemTheme);
if (app.ports?.loadTheme?.send) {
app.ports.loadTheme.send(systemTheme);
}
}
});
}
};
// Type definitions for Elm Land
namespace ElmLand {
export type FlagsArgs = {
env: Record<string, string>;
};
export type OnReadyArgs = {
env: Record<string, string>;
app: { ports?: Record<string, Port> };
};
export type Port = {
send?: (data: unknown) => void;
subscribe?: (callback: (data: unknown) => unknown) => void;
};
}

View file

@ -0,0 +1,3 @@
html {
font-size: 100%;
}

View file

@ -0,0 +1,52 @@
{
pkgs,
config,
...
}:
{
haskellProjects.default = {
devShell = {
enable = true;
tools = hp: {
inherit (hp)
cabal-fmt
haskell-language-server
;
inherit (pkgs)
dhall
dhall-json
dhall-lsp-server
helix-gpt
age
just
nil
nixd
sops
ssh-to-age
nixfmt-rfc-style
libz
ngrep
stripe-cli
vscode-langservers-extracted
zlib
;
inherit (pkgs.elmPackages)
elm
elm-format
elm-land
elm-language-server
elm-review
elm-test
;
inherit (pkgs.haskellPackages)
nixfmt
;
};
hlsCheck.enable = true;
mkShellArgs.shellHook = "${config.pre-commit.installationScript}";
};
};
}

View file

@ -0,0 +1,4 @@
{ self, ... }:
{
packages.default = self.packages.example;
}

View file

@ -0,0 +1,7 @@
{
pre-commit.settings.hooks = {
nixfmt-rfc-style.enable = true;
# commitizen.enable = true;
statix.enable = true;
};
}

View file

@ -0,0 +1,14 @@
let
configPath = ./config;
devshellImports =
let
files = builtins.attrNames (builtins.readDir configPath);
in
map (name: configPath + "/${name}") (
builtins.filter (name: builtins.match ".*\\.nix$" name != null) files
);
in
{
imports = devshellImports;
}