From 2cf926450949b14b0fee4fd49049ba3514577b30 Mon Sep 17 00:00:00 2001 From: Bram Date: Thu, 28 Dec 2023 09:29:27 +0100 Subject: [PATCH] Add Elm day 1 --- elm/elm.json | 34 + elm/index.html | 14835 +++++++++++++++++++++++++++++++++++++ elm/src/Main.elm | 317 + elm/src/Puzzles/Day1.elm | 61 + elm/src/Tools/Colors.elm | 562 ++ 5 files changed, 15809 insertions(+) create mode 100644 elm/elm.json create mode 100644 elm/index.html create mode 100644 elm/src/Main.elm create mode 100644 elm/src/Puzzles/Day1.elm create mode 100644 elm/src/Tools/Colors.elm diff --git a/elm/elm.json b/elm/elm.json new file mode 100644 index 0000000..17064ad --- /dev/null +++ b/elm/elm.json @@ -0,0 +1,34 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "Orasund/elm-ui-widgets": "3.4.0", + "avh4/elm-color": "1.0.0", + "elm/browser": "1.0.2", + "elm/core": "1.0.5", + "elm/html": "1.0.0", + "elm/svg": "1.0.1", + "mdgriffith/elm-ui": "1.1.8", + "miniBill/elm-fast-dict": "1.1.0" + }, + "indirect": { + "elm/json": "1.1.3", + "elm/regex": "1.0.0", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.3", + "elm-community/intdict": "3.0.0", + "fredcy/elm-parseint": "2.0.1", + "noahzgordon/elm-color-extra": "1.0.2", + "turboMaCk/queue": "1.1.0" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/elm/index.html b/elm/index.html new file mode 100644 index 0000000..06ab891 --- /dev/null +++ b/elm/index.html @@ -0,0 +1,14835 @@ + + + + + Main + + + + + +

+
+
+
+
+
\ No newline at end of file
diff --git a/elm/src/Main.elm b/elm/src/Main.elm
new file mode 100644
index 0000000..18789ae
--- /dev/null
+++ b/elm/src/Main.elm
@@ -0,0 +1,317 @@
+module Main exposing (main)
+
+import Browser
+import Element
+import Tools.Colors as C
+import Widget
+import Widget.Material as Material
+import Widget.Material.Typography as Typography
+import Element.Background
+import Element.Input
+import Task
+
+import Puzzles.Day1 as Day1
+import Widget.Customize
+
+
+main : Program () Model Msg
+main =
+    Browser.document
+        { init = init
+        , view = view
+        , update = update
+        , subscriptions = subscriptions
+        }
+
+
+type alias Model =
+    { view : Window
+    , input : String
+    , output1 : Calculating
+    , output2 : Calculating
+    }
+
+type Calculating
+    = Outcome String
+    | Calculating
+    | InvalidInput String
+    | NoInput
+
+type Window
+    = Home
+    | Day Int
+
+type alias Puzzle = String -> Result String String
+
+getFunctions : Window -> ( Puzzle, Puzzle )
+getFunctions window =
+    case window of
+        Home ->
+            ( always <| Err "There is no puzzle for this page!"
+            , always <| Err "There is no puzzle for this page!"
+            )
+        
+        Day 1 ->
+            ( Day1.puzzle1, Day1.puzzle2 )
+        
+        _ ->
+            ( always <| Err "This puzzle has no implementation yet!"
+            , always <| Err "This puzzle has no implementation yet!"
+            )
+
+init : () -> ( Model, Cmd Msg )
+init () =
+    ( { view = Home
+      , input = ""
+      , output1 = NoInput
+      , output2 = NoInput
+      }
+    , Cmd.none
+    )
+
+
+type Msg
+    = ChangeWindow Window
+    | OnInput String
+    | DoNothing
+    | Calculate1 Window (Result String String)
+    | Calculate2 Window (Result String String)
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update msg model =
+    case msg of
+        ChangeWindow window ->
+            ( { view = window
+              , input = ""
+              , output1 = NoInput
+              , output2 = NoInput
+              }
+            , Cmd.none
+            )
+        
+        OnInput input ->
+            ( { model
+              | input = input
+              , output1 = Calculating
+              , output2 = Calculating
+              }
+            , if input == model.input then
+                Cmd.none
+            else
+                Cmd.batch
+                    [ Task.succeed input
+                        |> Task.map (getFunctions model.view |> Tuple.first)
+                        |> Task.perform (Calculate1 model.view)
+                    , Task.succeed input
+                        |> Task.map (getFunctions model.view |> Tuple.second)
+                        |> Task.perform (Calculate2 model.view)
+                    ]
+            )
+        
+        DoNothing ->
+            ( model, Cmd.none )
+        
+        Calculate1 w (Ok s) ->
+            if w /= model.view then
+                ( model, Cmd.none )
+            
+            else
+                ( { model | output1 = Outcome s }
+                , Cmd.none
+                )
+        
+        Calculate1 w (Err s) ->
+            if w /= model.view then
+                ( model, Cmd.none )
+            
+            else
+                ( { model | output1 = InvalidInput s }
+                , Cmd.none
+                )
+        
+        Calculate2 w (Ok s) ->
+            if w /= model.view then
+                ( model, Cmd.none )
+            
+            else
+                ( { model | output2 = Outcome s }
+                , Cmd.none
+                )
+        
+        Calculate2 w (Err s) ->
+            if w /= model.view then
+                ( model, Cmd.none )
+            
+            else
+                ( { model | output2 = InvalidInput s }
+                , Cmd.none
+                )
+
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+    Sub.none
+
+
+view : Model -> Browser.Document Msg
+view model =
+    { title = "Document Title"
+    , body =
+        [ Widget.menuBar
+            ( Material.menuBar palette )
+            { title =
+                "aoc2023"
+                    |> Element.text
+                    |> Element.el Typography.h3
+            , deviceClass =
+                Element.Desktop
+            , openLeftSheet = Nothing
+            , openRightSheet = Nothing
+            , openTopSheet = Nothing
+            , primaryActions = []
+            , search = Nothing
+            }
+        , Element.row [ Element.height Element.fill, Element.width Element.fill ]
+            [ Element.el
+                [ Element.fill
+                    |> Element.maximum 200
+                    |> Element.width
+                , Element.alignTop
+                ]
+                ( List.map
+                    (\(window, text) ->
+                        Widget.fullBleedItem
+                            ( Material.fullBleedItem palette )
+                            { text = text
+                            , onPress = Just (ChangeWindow window)
+                            , icon = always Element.none
+                            }
+                    )
+                    [ ( Home, "Home" )
+                    , ( Day 1, "Day 1" )
+                    , ( Day 2, "Day 2" )
+                    , ( Day 3, "Day 3" )
+                    ]
+                    |> Widget.itemList Material.column
+                )
+            , Element.column
+                [ Element.height Element.fill
+                , Element.width Element.fill
+                , C.background (picker C.Dark C.White)
+                , Element.padding 20
+                , Element.spacing 20
+                ]
+                ( case model.view of
+                    Home ->
+                        [ "Advent of Code 2023"
+                            |> Element.text
+                            |> Element.el Typography.h3
+                        ]
+                    
+                    Day i ->
+                        [ [ "Using the following input for day " ++ String.fromInt i ++  ": "
+                              |> Element.text
+                          , Element.Input.multiline
+                              [ Element.fill
+                                    |> Element.maximum 500
+                                    |> Element.height
+                              , Element.scrollbarX
+                              ]
+                              { onChange = OnInput
+                              , text = model.input
+                              , placeholder =
+                                  "Insert puzzle input here..."
+                                      |> Element.text
+                                      |> Element.Input.placeholder []
+                                      |> Just
+                              , label = Element.Input.labelHidden "input"
+                              , spellcheck = False
+                              }
+                          ]
+                        , case model.output1 of
+                            Outcome s ->
+                                [ "Part 1 answer:"
+                                    |> Element.text
+                                , Element.Input.multiline
+                                    [ Element.width Element.fill ]
+                                    { onChange = always DoNothing
+                                    , text = s
+                                    , placeholder = Nothing
+                                    , label = Element.Input.labelHidden "answer-1"
+                                    , spellcheck = False
+                                    }
+                                ]
+                            
+                            Calculating ->
+                                [ "Calculating part 1..."
+                                    |> Element.text
+                                ]
+                            
+                            InvalidInput s ->
+                                [ "INVALID INPUT FOR PART 1"
+                                    |> Element.text
+                                , Element.Input.multiline
+                                    [ Element.width Element.fill ]
+                                    { onChange = always DoNothing
+                                    , text = s
+                                    , placeholder = Nothing
+                                    , label = Element.Input.labelHidden "answer-1"
+                                    , spellcheck = False
+                                    }
+                                ]
+                            
+                            NoInput ->
+                                List.singleton Element.none
+                        , case model.output2 of
+                            Outcome s ->
+                                [ "Part 2 answer:"
+                                    |> Element.text
+                                , Element.Input.multiline
+                                    [ Element.width Element.fill ]
+                                    { onChange = always DoNothing
+                                    , text = s
+                                    , placeholder = Nothing
+                                    , label = Element.Input.labelHidden "answer-2"
+                                    , spellcheck = False
+                                    }
+                                ]
+                            
+                            Calculating ->
+                                [ "Calculating part 2..."
+                                    |> Element.text
+                                ]
+                            
+                            InvalidInput s ->
+                                [ "INVALID INPUT FOR PART 2"
+                                    |> Element.text
+                                , Element.Input.multiline
+                                    [ Element.width Element.fill ]
+                                    { onChange = always DoNothing
+                                    , text = s
+                                    , placeholder = Nothing
+                                    , label = Element.Input.labelHidden "answer-2"
+                                    , spellcheck = False
+                                    }
+                                ]
+                            
+                            NoInput ->
+                                List.singleton Element.none
+                        ]
+                            |> List.concat
+                )
+            ]
+        ]
+            |> Element.column [ Element.width Element.fill, Element.height Element.fill ]
+            |> Element.layout [ Element.height Element.fill ]
+            |> List.singleton
+    }
+
+picker : C.Picker
+picker =
+    C.get C.Trichromatic C.LightMode
+
+palette : Material.Palette
+palette =
+    C.defaultPalette picker
diff --git a/elm/src/Puzzles/Day1.elm b/elm/src/Puzzles/Day1.elm
new file mode 100644
index 0000000..b64606e
--- /dev/null
+++ b/elm/src/Puzzles/Day1.elm
@@ -0,0 +1,61 @@
+module Puzzles.Day1 exposing (puzzle1, puzzle2)
+
+puzzle1 : String -> Result String String
+puzzle1 input =
+    input
+        |> String.split "\n"
+        |> List.map puzzle1Nums
+        |> List.indexedMap
+            (\i items ->
+                case items of
+                    head :: tail ->
+                        Ok (calibrationValue head tail)
+                    
+                    [] ->
+                        Err i
+            )
+        |> List.foldl
+            (\value sum ->
+                case (value, sum) of
+                    (_, Err _) ->
+                        sum
+                    
+                    (Err i, Ok _) ->
+                        Err ("Line" ++ (String.fromInt <| i + 1) ++ "does not contain any numbers")
+                    
+                    (Ok a, Ok b) ->
+                        Ok (a + b)
+            )
+            (Ok 0)
+        |> Result.map String.fromInt
+        
+
+puzzle1Nums : String -> List Int
+puzzle1Nums s =
+    s
+        |> String.toList
+        |> List.map String.fromChar
+        |> List.filterMap String.toInt
+
+calibrationValue : Int -> List Int -> Int
+calibrationValue head tail =
+    tail
+        |> List.reverse
+        |> List.head
+        |> Maybe.withDefault head
+        |> (+) ( head * 10 )
+
+puzzle2 : String -> Result String String
+puzzle2 input =
+    input
+        |> String.replace "zero" "z0ero"
+        |> String.replace "one" "o1ne"
+        |> String.replace "two" "t2wo"
+        |> String.replace "three" "t3hree"
+        |> String.replace "four" "f4our"
+        |> String.replace "five" "f5ive"
+        |> String.replace "six" "s6ix"
+        |> String.replace "seven" "s7even"
+        |> String.replace "eight" "e8ight"
+        |> String.replace "nine" "n9ine"
+        |> puzzle1
diff --git a/elm/src/Tools/Colors.elm b/elm/src/Tools/Colors.elm
new file mode 100644
index 0000000..a8debc1
--- /dev/null
+++ b/elm/src/Tools/Colors.elm
@@ -0,0 +1,562 @@
+module Tools.Colors exposing (..)
+
+{-| This module aims to create color palettes that fit the Noordstar color scheme.
+-}
+
+import Color exposing (rgb255)
+import Element
+import Element.Background
+import Element.Font
+import Svg
+import Svg.Attributes exposing (fill)
+import Widget.Material exposing (Palette)
+
+
+type alias Color =
+    Color.Color
+
+
+type Name
+    = Primary
+    | Secondary
+    | Tertiary
+    | Quaternary
+    | Extra
+    | Black
+    | White
+
+
+type Shade
+    = Light
+    | Medium
+    | Dark
+
+
+{-| Based on the user's preferences, the website can be displayed in light mode or dark mode.
+-}
+type Mode
+    = LightMode
+    | DarkMode
+
+
+{-| The website supports color blindness friendly color palettes.
+This way, everyone can enjoy the website's graphs without having to distinguish
+colors that they cannot distinguish.
+-}
+type Blindness
+    = Trichromatic -- ALL THREE
+    | Protanomaly -- BARELY RED
+    | Deuteranomaly -- BARELY GREEN
+    | Tritanomaly -- BARELY BLUE
+    | Protanopia -- NO RED
+    | Deuteranopia -- NO GREEN
+    | Tritanopia -- NO BLUE
+    | Monochromacy -- NO COLOR
+    | BlueConeMonochromacy -- BARELY COLOR
+
+
+type alias Picker =
+    Shade -> Name -> Color
+
+
+{-| Get a color based on the right criteria.
+-}
+get : Blindness -> Mode -> Shade -> Name -> Color
+get blindness mode shade name =
+    let
+        trueName : Name
+        trueName =
+            name |> flipName mode
+
+        trueShade : Shade
+        trueShade =
+            shade |> flipShade mode
+    in
+    toBlindnessPalette blindness
+        |> toColor blindness trueName
+        |> fromShade trueShade
+        |> (\( r, g, b ) -> rgb255 r g b)
+
+
+defaultPalette : Picker -> Palette
+defaultPalette p =
+    { primary = p Medium Primary
+    , secondary = p Medium Secondary
+    , background = p Dark White
+    , surface = p Light White
+    , error = p Light Secondary
+    , on =
+        { primary = p Light White
+        , secondary = p Light White
+        , background = p Light Black
+        , surface = p Dark Black
+        , error = p Medium White
+        }
+    }
+
+
+{-| Get a blindness color palette based on a blindness input.
+-}
+toBlindnessPalette : Blindness -> BlindnessPalette
+toBlindnessPalette blindness =
+    case blindness of
+        Trichromatic ->
+            trichromatic
+
+        Protanomaly ->
+            protanomaly
+
+        Deuteranomaly ->
+            deuteranomaly
+
+        Tritanomaly ->
+            tritanomaly
+
+        Protanopia ->
+            protanopia
+
+        Deuteranopia ->
+            deuteranopia
+
+        Tritanopia ->
+            tritanopia
+
+        Monochromacy ->
+            monochromacy
+
+        BlueConeMonochromacy ->
+            blueConeMonochromacy
+
+
+flipName : Mode -> Name -> Name
+flipName mode name =
+    case mode of
+        LightMode ->
+            name
+
+        DarkMode ->
+            case name of
+                Black ->
+                    White
+
+                White ->
+                    Black
+
+                _ ->
+                    name
+
+
+flipShade : Mode -> Shade -> Shade
+flipShade mode shade =
+    case ( mode, shade ) of
+        ( LightMode, _ ) ->
+            shade
+
+        ( DarkMode, Dark ) ->
+            Light
+
+        ( DarkMode, Medium ) ->
+            Medium
+
+        ( DarkMode, Light ) ->
+            Dark
+
+
+{-| We distringuish the following colours:
+
+           | Protan | Deuter | Tritan | Mono   |
+
+  - Blue | Blue | Blue | Blue | Blue |
+  - Green | Green | Green | Green | XXXXXX | [Orange]
+  - Yellow | Yellow | Yellow | XXXXXX | Yellow | [Orange]
+  - Orange | XXXXXX | XXXXXX | Orange | Orange | [Green,Red]
+  - Red | Red | Red | Red | Red |
+  - Black | Black | Black | Black | Black |
+  - White | White | White | White | White |
+
+In other words:
+
+Primary | Blue | Blue | Blue | Blue |
+Secondary | Red | Red | Red | Red |
+Tertiary | Yellow | Yellow | Orange | Yellow |
+Quaternary | Green | Green | Green | Orange |
+-----------|--------|--------|--------|--------|
+Rest | Orange | Orange | Yellow | Green |
+
+-}
+toColor : Blindness -> Name -> (BlindnessPalette -> ColorPalette)
+toColor blindness name =
+    case name of
+        Primary ->
+            .blue
+
+        Secondary ->
+            .red
+
+        Tertiary ->
+            case blindness of
+                Tritanopia ->
+                    .orange
+
+                _ ->
+                    .yellow
+
+        Quaternary ->
+            case blindness of
+                Monochromacy ->
+                    .orange
+
+                _ ->
+                    .green
+
+        Extra ->
+            case blindness of
+                Tritanopia ->
+                    .yellow
+
+                Monochromacy ->
+                    .green
+
+                _ ->
+                    .orange
+
+        Black ->
+            .black
+
+        White ->
+            .white
+
+
+fromShade : Shade -> ColorPalette -> ( Int, Int, Int )
+fromShade shade =
+    case shade of
+        Light ->
+            .light
+
+        Medium ->
+            .medium
+
+        Dark ->
+            .dark
+
+
+type alias ColorPalette =
+    { light : ( Int, Int, Int )
+    , medium : ( Int, Int, Int )
+    , dark : ( Int, Int, Int )
+    }
+
+
+type alias BlindnessPalette =
+    { blue : ColorPalette
+    , green : ColorPalette
+    , yellow : ColorPalette
+    , orange : ColorPalette
+    , red : ColorPalette
+    , black : ColorPalette
+    , white : ColorPalette
+    }
+
+
+{-| No color blindness
+-}
+trichromatic : BlindnessPalette
+trichromatic =
+    { blue = { light = ( 0x42, 0x87, 0xFF ), medium = ( 0x42, 0x7F, 0xF0 ), dark = ( 0x00, 0x54, 0xBD ) }
+    , green = { light = ( 0x86, 0xEA, 0xD1 ), medium = ( 0x5E, 0xA4, 0x93 ), dark = ( 0x3E, 0x6D, 0x62 ) }
+    , yellow = { light = ( 0xFC, 0xF9, 0x2B ), medium = ( 0xD2, 0xD0, 0x24 ), dark = ( 0xAF, 0xAD, 0x1E ) }
+    , orange = { light = ( 0xFF, 0xBB, 0x93 ), medium = ( 0xCC, 0x95, 0x75 ), dark = ( 0xA3, 0x77, 0x5E ) }
+    , red = { light = ( 0xDC, 0x00, 0x00 ), medium = ( 0xB0, 0x00, 0x00 ), dark = ( 0x8C, 0x00, 0x00 ) }
+    , black = { light = ( 0x2C, 0x2C, 0x48 ), medium = ( 0x1D, 0x1D, 0x30 ), dark = ( 0x00, 0x00, 0x00 ) }
+    , white = { light = ( 0xFF, 0xFF, 0xFF ), medium = ( 0xFE, 0xFA, 0xF5 ), dark = ( 0xF2, 0xEF, 0xEA ) }
+    }
+
+
+{-| Weak red vision
+-}
+protanomaly : BlindnessPalette
+protanomaly =
+    { blue = { light = ( 0x49, 0x86, 0xFE ), medium = ( 0x46, 0x7E, 0xEF ), dark = ( 0x00, 0x55, 0xB9 ) }
+    , green = { light = ( 0xBE, 0xDD, 0xCA ), medium = ( 0x85, 0x9B, 0x8E ), dark = ( 0x58, 0x67, 0x5F ) }
+    , yellow = { light = ( 0xFE, 0xF4, 0x88 ), medium = ( 0xDD, 0xCC, 0x23 ), dark = ( 0xB8, 0xAA, 0x1D ) }
+    , orange = { light = ( 0xE6, 0xC4, 0x97 ), medium = ( 0xB8, 0x9D, 0x78 ), dark = ( 0x93, 0x7D, 0x61 ) }
+    , red = { light = ( 0x9F, 0x47, 0x12 ), medium = ( 0x7F, 0x39, 0x0F ), dark = ( 0x65, 0x2D, 0x0C ) }
+    , black = { light = ( 0x27, 0x2D, 0x49 ), medium = ( 0x1A, 0x1E, 0x31 ), dark = ( 0x00, 0x00, 0x00 ) }
+    , white = { light = ( 0xFF, 0xFF, 0xFF ), medium = ( 0xFF, 0xFA, 0xF6 ), dark = ( 0xF4, 0xEE, 0xEA ) }
+    }
+
+
+{-| Weak green vision
+-}
+deuteranomaly : BlindnessPalette
+deuteranomaly =
+    { blue = { light = ( 0x18, 0x8A, 0xFA ), medium = ( 0x18, 0x82, 0xEC ), dark = ( 0x00, 0x59, 0xA9 ) }
+    , green = { light = ( 0xC6, 0xD9, 0xD5 ), medium = ( 0x8B, 0x98, 0x95 ), dark = ( 0x5C, 0x65, 0x64 ) }
+    , yellow = { light = ( 0xFE, 0xF3, 0x9C ), medium = ( 0xEE, 0xC5, 0x2B ), dark = ( 0xC6, 0xA4, 0x23 ) }
+    , orange = { light = ( 0xF5, 0xBF, 0x92 ), medium = ( 0xC4, 0x99, 0x74 ), dark = ( 0x9D, 0x7A, 0x5D ) }
+    , red = { light = ( 0xA9, 0x43, 0x00 ), medium = ( 0x87, 0x36, 0x00 ), dark = ( 0x6C, 0x2B, 0x00 ) }
+    , black = { light = ( 0x26, 0x2E, 0x48 ), medium = ( 0x19, 0x1E, 0x30 ), dark = ( 0x00, 0x00, 0x00 ) }
+    , white = { light = ( 0xFF, 0xFF, 0xFF ), medium = ( 0xFE, 0xFA, 0xF5 ), dark = ( 0xFA, 0xEC, 0xEC ) }
+    }
+
+
+{-| Weak blue vision
+-}
+tritanomaly : BlindnessPalette
+tritanomaly =
+    { blue = { light = ( 0x18, 0x93, 0xC5 ), medium = ( 0x18, 0x8A, 0xBA ), dark = ( 0x00, 0x5E, 0x88 ) }
+    , green = { light = ( 0x8C, 0xE7, 0xE9 ), medium = ( 0x62, 0xA2, 0xA4 ), dark = ( 0x41, 0x6B, 0x6D ) }
+    , yellow = { light = ( 0xFE, 0xF1, 0xAC ), medium = ( 0xDA, 0xC7, 0x92 ), dark = ( 0xB6, 0xA5, 0x79 ) }
+    , orange = { light = ( 0xFF, 0xB8, 0xB2 ), medium = ( 0xCE, 0x92, 0x8D ), dark = ( 0xA5, 0x74, 0x71 ) }
+    , red = { light = ( 0xDB, 0x0D, 0x00 ), medium = ( 0xAF, 0x0A, 0x00 ), dark = ( 0x8B, 0x08, 0x00 ) }
+    , black = { light = ( 0x29, 0x2F, 0x3B ), medium = ( 0x1B, 0x1F, 0x27 ), dark = ( 0x00, 0x00, 0x00 ) }
+    , white = { light = ( 0xFF, 0xFF, 0xFF ), medium = ( 0xFD, 0xFA, 0xFB ), dark = ( 0xF4, 0xED, 0xF7 ) }
+    }
+
+
+{-| Red-blind vision
+-}
+protanopia : BlindnessPalette
+protanopia =
+    { blue = { light = ( 0x4D, 0x86, 0xFE ), medium = ( 0x48, 0x7E, 0xEF ), dark = ( 0x00, 0x56, 0xB6 ) }
+    , green = { light = ( 0xDE, 0xD6, 0xC6 ), medium = ( 0x9C, 0x96, 0x8B ), dark = ( 0x67, 0x64, 0x5D ) }
+    , yellow = { light = ( 0xFF, 0xF2, 0xBE ), medium = ( 0xE3, 0xCA, 0x22 ), dark = ( 0xBD, 0xA8, 0x1D ) }
+    , orange = { light = ( 0xD8, 0xCA, 0x9A ), medium = ( 0xAD, 0xA1, 0x7A ), dark = ( 0x8A, 0x81, 0x62 ) }
+    , red = { light = ( 0x7D, 0x6F, 0x1C ), medium = ( 0x64, 0x59, 0x17 ), dark = ( 0x4F, 0x47, 0x12 ) }
+    , black = { light = ( 0x24, 0x2E, 0x4A ), medium = ( 0x18, 0x1E, 0x31 ), dark = ( 0x00, 0x00, 0x00 ) }
+    , white = { light = ( 0xFF, 0xFF, 0xFF ), medium = ( 0xFF, 0xFA, 0xF6 ), dark = ( 0xF5, 0xEE, 0xEA ) }
+    }
+
+
+{-| Green-blind vision
+-}
+deuteranopia : BlindnessPalette
+deuteranopia =
+    { blue = { light = ( 0x00, 0x8C, 0xF8 ), medium = ( 0x00, 0x84, 0xEA ), dark = ( 0x00, 0x5B, 0x9D ) }
+    , green = { light = ( 0xEB, 0xD0, 0xD7 ), medium = ( 0xA5, 0x92, 0x97 ), dark = ( 0x6D, 0x61, 0x65 ) }
+    , yellow = { light = ( 0xFF, 0xEF, 0xDC ), medium = ( 0xFE, 0xBF, 0x2E ), dark = ( 0xD3, 0x9F, 0x26 ) }
+    , orange = { light = ( 0xF0, 0xC2, 0x92 ), medium = ( 0xBF, 0x9B, 0x74 ), dark = ( 0x99, 0x7B, 0x5D ) }
+    , red = { light = ( 0x8C, 0x69, 0x00 ), medium = ( 0x70, 0x54, 0x00 ), dark = ( 0x59, 0x43, 0x00 ) }
+    , black = { light = ( 0x23, 0x2F, 0x47 ), medium = ( 0x16, 0x1F, 0x30 ), dark = ( 0x00, 0x00, 0x00 ) }
+    , white = { light = ( 0xFF, 0xFF, 0xFF ), medium = ( 0xFF, 0xF9, 0xFA ), dark = ( 0xFF, 0xEA, 0xED ) }
+    }
+
+
+{-| Blue-blind vision
+-}
+tritanopia : BlindnessPalette
+tritanopia =
+    { blue = { light = ( 0x00, 0x99, 0xA4 ), medium = ( 0x00, 0x90, 0x9B ), dark = ( 0x00, 0x63, 0x69 ) }
+    , green = { light = ( 0x90, 0xE5, 0xF7 ), medium = ( 0x65, 0xA0, 0xAD ), dark = ( 0x43, 0x6B, 0x73 ) }
+    , yellow = { light = ( 0xFF, 0xED, 0xF6 ), medium = ( 0xDF, 0xC2, 0xD1 ), dark = ( 0xBA, 0xA1, 0xAE ) }
+    , orange = { light = ( 0xFF, 0xB7, 0xC3 ), medium = ( 0xCF, 0x90, 0x9B ), dark = ( 0xA6, 0x73, 0x7C ) }
+    , red = { light = ( 0xDA, 0x14, 0x00 ), medium = ( 0xAF, 0x10, 0x00 ), dark = ( 0x8B, 0x0D, 0x00 ) }
+    , black = { light = ( 0x27, 0x30, 0x34 ), medium = ( 0x1A, 0x20, 0x22 ), dark = ( 0x00, 0x00, 0x00 ) }
+    , white = { light = ( 0xFF, 0xFF, 0xFF ), medium = ( 0xFC, 0xFA, 0xFF ), dark = ( 0xF5, 0xEC, 0xFE ) }
+    }
+
+
+{-| Color-less vision
+-}
+monochromacy : BlindnessPalette
+monochromacy =
+    { blue = { light = ( 0x80, 0x80, 0x80 ), medium = ( 0x7A, 0x7A, 0x7A ), dark = ( 0x47, 0x47, 0x47 ) }
+    , green = { light = ( 0xC9, 0xC9, 0xC9 ), medium = ( 0x8D, 0x8D, 0x8D ), dark = ( 0x5E, 0x5E, 0x5E ) }
+    , yellow = { light = ( 0xE2, 0xE2, 0xE2 ), medium = ( 0xBD, 0xBD, 0xBD ), dark = ( 0x9D, 0x9D, 0x9D ) }
+    , orange = { light = ( 0xCB, 0xCB, 0xCB ), medium = ( 0xA2, 0xA2, 0xA2 ), dark = ( 0x81, 0x81, 0x81 ) }
+    , red = { light = ( 0x42, 0x42, 0x42 ), medium = ( 0x35, 0x35, 0x35 ), dark = ( 0x2A, 0x2A, 0x2A ) }
+    , black = { light = ( 0x2F, 0x2F, 0x2F ), medium = ( 0x1F, 0x1F, 0x1F ), dark = ( 0x00, 0x00, 0x00 ) }
+    , white = { light = ( 0xFF, 0xFF, 0xFF ), medium = ( 0xFB, 0xFB, 0xFB ), dark = ( 0xEF, 0xEF, 0xEF ) }
+    }
+
+
+{-| Blue cone monochromacy
+-}
+blueConeMonochromacy : BlindnessPalette
+blueConeMonochromacy =
+    { blue = { light = ( 0x69, 0x83, 0xAE ), medium = ( 0x66, 0x7C, 0xA5 ), dark = ( 0x2D, 0x4C, 0x72 ) }
+    , green = { light = ( 0xB1, 0xD5, 0xCC ), medium = ( 0x7C, 0x95, 0x8F ), dark = ( 0x52, 0x63, 0x5F ) }
+    , yellow = { light = ( 0xEB, 0xEA, 0x9F ), medium = ( 0xC5, 0xC4, 0x85 ), dark = ( 0xA4, 0xA3, 0x6F ) }
+    , orange = { light = ( 0xDE, 0xC5, 0xB7 ), medium = ( 0xB1, 0x9D, 0x92 ), dark = ( 0x8D, 0x7D, 0x74 ) }
+    , red = { light = ( 0x7A, 0x2A, 0x2A ), medium = ( 0x62, 0x22, 0x22 ), dark = ( 0x4E, 0x1B, 0x1B ) }
+    , black = { light = ( 0x2E, 0x2E, 0x38 ), medium = ( 0x1E, 0x1E, 0x25 ), dark = ( 0x00, 0x00, 0x00 ) }
+    , white = { light = ( 0xFF, 0xFF, 0xFF ), medium = ( 0xFC, 0xFB, 0xF9 ), dark = ( 0xF0, 0xEF, 0xED ) }
+    }
+
+
+svgFill : Color -> Svg.Attribute msg
+svgFill =
+    Color.toCssString >> fill
+
+
+svgStroke : Color -> Svg.Attribute msg
+svgStroke =
+    Color.toCssString >> Svg.Attributes.stroke
+
+
+font : Color -> Element.Attribute msg
+font =
+    Color.toRgba >> Element.fromRgb >> Element.Font.color
+
+
+background : Color -> Element.Attribute msg
+background =
+    Color.toRgba >> Element.fromRgb >> Element.Background.color
+
+
+transparent : Color
+transparent =
+    Color.rgba 0 0 0 0
+
+
+-- PRIMARY COLOR
+
+
+primaryColor : Color
+primaryColor =
+    noordstarBlue
+
+
+darkPrimaryColor : Color
+darkPrimaryColor =
+    rgb255 0x00 0x54 0xBD
+
+
+lightPrimaryColor : Color
+lightPrimaryColor =
+    rgb255 0x80 0xAE 0xFF
+
+
+textOnPrimaryColor : Color
+textOnPrimaryColor =
+    Color.white
+
+
+
+-- SECONDARY COLOR
+
+
+secondaryColor : Color
+secondaryColor =
+    rgb255 0xFF 0x8A 0x65
+
+
+darkSecondaryColor : Color
+darkSecondaryColor =
+    rgb255 0xC7 0x5B 0x39
+
+
+lightSecondaryColor : Color
+lightSecondaryColor =
+    rgb255 0xFF 0xBB 0x93
+
+
+textOnSecondaryColor : Color
+textOnSecondaryColor =
+    Color.black
+
+
+
+-- NOORDSTAR PALETTE
+
+
+noordstarBlue : Color
+noordstarBlue =
+    rgb255 0x42 0x7F 0xF0
+
+
+noordstarYellow : Color
+noordstarYellow =
+    rgb255 0xD2 0xD0 0x24
+
+
+noordstarGreen : Color
+noordstarGreen =
+    rgb255 0x5E 0xA4 0x93
+
+
+noordstarRed : Color
+noordstarRed =
+    rgb255 0xB0 0x00 0x00
+
+
+noordstarBlack : Color
+noordstarBlack =
+    rgb255 0x2C 0x2C 0x48
+
+
+noordstarWhite : Color
+noordstarWhite =
+    rgb255 0xF2 0xEF 0xEA
+
+
+
+-- PALETTES
+
+
+primaryPalette : Palette
+primaryPalette =
+    { primary = primaryColor
+    , secondary = secondaryColor
+    , background = noordstarWhite
+    , surface = noordstarWhite
+    , error = noordstarRed
+    , on =
+        { primary = textOnPrimaryColor
+        , secondary = textOnSecondaryColor
+        , background = Color.black
+        , surface = Color.black
+        , error = Color.white
+        }
+    }
+
+
+darkPalette : Palette
+darkPalette =
+    { primary = darkPrimaryColor
+    , secondary = darkSecondaryColor
+    , background = noordstarWhite
+    , surface = noordstarWhite
+    , error = noordstarRed
+    , on =
+        { primary = Color.white
+        , secondary = Color.white
+        , background = Color.black
+        , surface = Color.black
+        , error = Color.white
+        }
+    }
+
+
+lightPalette : Palette
+lightPalette =
+    { primary = lightPrimaryColor
+    , secondary = lightSecondaryColor
+    , background = noordstarWhite
+    , surface = noordstarWhite
+    , error = noordstarRed
+    , on =
+        { primary = Color.black
+        , secondary = Color.black
+        , background = Color.black
+        , surface = Color.black
+        , error = Color.white
+        }
+    }
+
+
+secondaryPalette : Palette
+secondaryPalette =
+    { primary = secondaryColor
+    , secondary = primaryColor
+    , background = Color.white
+    , surface = Color.white
+    , error = noordstarRed
+    , on =
+        { primary = textOnSecondaryColor
+        , secondary = textOnPrimaryColor
+        , background = Color.black
+        , surface = Color.black
+        , error = Color.white
+        }
+    }