not my website

CSS Refactor + Dark Mode

+260 -82
+1 -1
index.html
··· 13 <script type="module" src="/priv/static/website.mjs"></script> 14 </head> 15 16 - <body> 17 <div id="app"></div> 18 </body> 19 </html>
··· 13 <script type="module" src="/priv/static/website.mjs"></script> 14 </head> 15 16 + <body style="margin: 0;"> 17 <div id="app"></div> 18 </body> 19 </html>
+4
src/ffi.mjs
··· 1 export function get_route() { 2 return window.location.pathname 3 }
··· 1 export function get_route() { 2 return window.location.pathname 3 + } 4 + 5 + export function prefers_dark_mode() { 6 + return window.matchMedia('(prefers-color-scheme:dark)').matches 7 }
+74 -73
src/website.gleam
··· 1 - import birl.{type Day} 2 - import gleam/int 3 import gleam/list 4 import gleam/string 5 import gleam/uri.{type Uri} ··· 8 import lustre/effect.{type Effect} 9 import lustre/element.{type Element} 10 import lustre/element/html 11 import lustre/ui 12 import lustre/ui/layout/cluster 13 import modem 14 import website/common 15 import website/posts 16 import website/projects 17 18 // Main 19 ··· 31 current_route: Route, 32 posts: List(posts.Post(Msg)), 33 projects: List(projects.Project(Msg)), 34 ) 35 } 36 ··· 45 @external(javascript, "./ffi.mjs", "get_route") 46 fn do_get_route() -> String 47 48 fn get_route() -> Route { 49 case do_get_route() |> string.split("/") { 50 ["", "projects", id] | ["", "project", id] -> Project(id) ··· 61 current_route: get_route(), 62 projects: projects.all(), 63 posts: posts.all(), 64 ), 65 modem.init(on_route_change), 66 ) ··· 80 81 pub opaque type Msg { 82 OnRouteChange(Route) 83 } 84 85 fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { ··· 88 Model(..model, current_route: route), 89 effect.none(), 90 ) 91 } 92 } 93 94 // View 95 96 fn view(model: Model) -> Element(Msg) { 97 - let styles = [ 98 - #("margin-left", "25vh"), 99 - #("margin-top", "3vh"), 100 - #("margin-right", "25vh"), 101 - #("font-family", "\"Inter\", sans-serif"), 102 - #("font-weight", "400"), 103 - #("font-style", "normal"), 104 - ] 105 - 106 let page = case model.current_route { 107 Home -> view_home(model) 108 Projects -> view_projects(model) ··· 111 Post(id) -> view_post(model, id) 112 } 113 114 - ui.stack([attribute.style(styles)], [view_navbar(model), page]) 115 } 116 117 - const navbar_style: List(#(String, String)) = [ 118 - #("display", "flex"), #("background-color", "#cae4e7"), 119 - #("border-radius", "1em"), #("padding", "1em"), #("margin-bottom", "3vh"), 120 - #("justify-conten", "center"), #("align-items", "center"), #("gap", "1em"), 121 - ] 122 - 123 - const navitem_style: List(#(String, String)) = [ 124 - #("text-decoration", "none"), #("background-color", "#b8cfd2"), 125 - #("padding", "0.75em"), #("border-radius", "1em"), #("color", "black"), 126 - ] 127 128 - fn view_navbar(_) -> Element(Msg) { 129 let view_nav_item = fn(path, text) { 130 html.a([attribute.href("/" <> path), attribute.style(navitem_style)], [ 131 element.text(text), 132 ]) 133 } 134 135 - cluster.of(html.nav, [attribute.style(navbar_style)], [ 136 - html.h1([attribute.style([#("margin", "0")])], [element.text("naomieow")]), 137 - view_nav_item("", "Home"), 138 - view_nav_item("projects", "Projects"), 139 - view_nav_item("posts", "Posts"), 140 - ]) 141 } 142 143 - const home_style: List(#(String, String)) = [ 144 - #("background-color", "#cae4e7"), #("border-radius", "1em"), 145 - #("padding", "1em"), #("margin-bottom", "1em"), #("font-size", "1.25em"), 146 - ] 147 148 - fn view_home(_model: Model) -> Element(Msg) { 149 html.div([attribute.style(home_style)], [ 150 html.p([], [ 151 element.text( ··· 197 ], 198 ), 199 ]), 200 - // html.p([], [ 201 // element.text("As of this coming September, I will be studying a BSc in Computer Games Programming @ "), 202 // common.link("Staffs", "https://staffs.ac.uk/") 203 // ]) 204 ]) 205 } 206 207 - const icon_style: List(#(String, String)) = [ 208 - #("max-width", "4em"), #("max-height", "4em"), #("border-radius", "1em"), 209 - ] 210 - 211 - const project_style: List(#(String, String)) = [ 212 - #("background-color", "#cae4e7"), #("border-radius", "1em"), 213 - #("padding", "1em"), #("margin-bottom", "1em"), 214 - ] 215 - 216 - const project_bar_style: List(#(String, String)) = [ 217 - #("display", "flex"), #("align-items", "center"), #("gap", "1em"), 218 - ] 219 - 220 fn view_projects(model: Model) -> Element(Msg) { 221 let projects = 222 model.projects 223 |> list.map(fn(project: projects.Project(Msg)) { 224 ui.stack( 225 [attribute.id("project-" <> project.id), attribute.style(project_style)], 226 [ 227 - cluster.of(html.div, [attribute.style(project_bar_style)], [ 228 - html.img([attribute.src(project.img), attribute.style(icon_style)]), 229 - html.h1([], [ 230 - common.styled_link(project.title, "projects/" <> project.id), 231 - ]), 232 cluster.of( 233 html.div, 234 - [attribute.style(project_bar_style)], 235 project.links 236 |> list.map(fn(link) { 237 html.a([attribute.href(link.url), attribute.alt(link.title)], [ 238 html.img([ 239 attribute.src(link.img), 240 - attribute.style(icon_style), 241 ]), 242 ]) 243 }), ··· 251 } 252 253 fn view_project(model: Model, id: String) -> Element(Msg) { 254 let project = 255 model.projects 256 |> list.find(fn(project) { project.id == id }) 257 html.div([attribute.style(project_style)], case project { 258 Ok(project) -> [ 259 - cluster.of(html.div, [attribute.style(project_bar_style)], [ 260 - html.img([attribute.src(project.img), attribute.style(icon_style)]), 261 html.h1([], [element.text(project.title)]), 262 ]), 263 html.p([], [project.summary]), ··· 271 }) 272 } 273 274 - const post_style: List(#(String, String)) = [ 275 - #("background-color", "#cae4e7"), #("border-radius", "1em"), 276 - #("padding", "1em"), #("margin-bottom", "1em"), 277 - ] 278 - 279 - fn day_to_string(day: Day) -> String { 280 - day.date |> int.to_string 281 - <> "/" 282 - <> day.month |> int.to_string 283 - <> "/" 284 - <> day.year |> int.to_string 285 - } 286 287 - fn view_posts(model: Model) -> Element(Msg) { 288 let posts = 289 model.posts 290 |> list.map(fn(post: posts.Post(Msg)) { ··· 299 ]), 300 ], 301 [ 302 - html.h1([], [common.styled_link(post.title, "posts/" <> post.id)]), 303 html.p([attribute.style([#("color", "gray")])], [ 304 element.text("Author: " <> post.author), 305 ]), 306 html.p([attribute.style([#("color", "gray")])], [ 307 - element.text("Date Posted: " <> post.date_posted |> day_to_string), 308 ]), 309 ], 310 ), ··· 315 } 316 317 fn view_post(model: Model, id: String) -> Element(Msg) { 318 let post = 319 model.posts 320 |> list.find(fn(post) { post.id == id }) ··· 335 element.text("Author: " <> post.author), 336 ]), 337 html.p([attribute.style([#("color", "gray")])], [ 338 - element.text("Date Posted: " <> post.date_posted |> day_to_string), 339 ]), 340 ], 341 ),
··· 1 import gleam/list 2 import gleam/string 3 import gleam/uri.{type Uri} ··· 6 import lustre/effect.{type Effect} 7 import lustre/element.{type Element} 8 import lustre/element/html 9 + import lustre/event 10 import lustre/ui 11 import lustre/ui/layout/cluster 12 import modem 13 import website/common 14 import website/posts 15 import website/projects 16 + import website/style 17 18 // Main 19 ··· 31 current_route: Route, 32 posts: List(posts.Post(Msg)), 33 projects: List(projects.Project(Msg)), 34 + dark_mode: Bool, 35 ) 36 } 37 ··· 46 @external(javascript, "./ffi.mjs", "get_route") 47 fn do_get_route() -> String 48 49 + @external(javascript, "./ffi.mjs", "prefers_dark_mode") 50 + fn prefers_dark_mode() -> Bool 51 + 52 fn get_route() -> Route { 53 case do_get_route() |> string.split("/") { 54 ["", "projects", id] | ["", "project", id] -> Project(id) ··· 65 current_route: get_route(), 66 projects: projects.all(), 67 posts: posts.all(), 68 + dark_mode: prefers_dark_mode(), 69 ), 70 modem.init(on_route_change), 71 ) ··· 85 86 pub opaque type Msg { 87 OnRouteChange(Route) 88 + ChangeDarkMode 89 } 90 91 fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { ··· 94 Model(..model, current_route: route), 95 effect.none(), 96 ) 97 + ChangeDarkMode -> #( 98 + Model(..model, dark_mode: !model.dark_mode), 99 + effect.none(), 100 + ) 101 } 102 } 103 104 // View 105 106 fn view(model: Model) -> Element(Msg) { 107 let page = case model.current_route { 108 Home -> view_home(model) 109 Projects -> view_projects(model) ··· 112 Post(id) -> view_post(model, id) 113 } 114 115 + ui.stack([attribute.style(style.page(model.dark_mode))], [ 116 + html.div([attribute.style(style.page_padding)], [view_navbar(model), page]), 117 + ]) 118 } 119 120 + fn view_navbar(model: Model) -> Element(Msg) { 121 + let navitem_style = style.navitem(model.dark_mode) 122 + let navbar_style = style.navbar(model.dark_mode) 123 124 let view_nav_item = fn(path, text) { 125 html.a([attribute.href("/" <> path), attribute.style(navitem_style)], [ 126 element.text(text), 127 ]) 128 } 129 130 + html.span( 131 + [ 132 + attribute.style([ 133 + #("display", "flex"), 134 + #("gap", "1em"), 135 + #("width", "100%"), 136 + ]), 137 + ], 138 + [ 139 + cluster.of(html.nav, [attribute.style(navbar_style)], [ 140 + html.h1([attribute.style([#("margin", "0"), #("color", "#f380b1")])], [ 141 + element.text("naomieow"), 142 + ]), 143 + view_nav_item("", "Home"), 144 + view_nav_item("projects", "Projects"), 145 + view_nav_item("posts", "Posts"), 146 + ]), 147 + ui.button( 148 + [ 149 + attribute.style(style.button(model.dark_mode)), 150 + event.on_click(ChangeDarkMode), 151 + ], 152 + [ 153 + html.h1([attribute.style([#("margin", "0")])], [ 154 + case model.dark_mode { 155 + True -> element.text("☀️") 156 + False -> element.text("🌕") 157 + }, 158 + ]), 159 + ], 160 + ), 161 + ], 162 + ) 163 } 164 165 + fn view_home(model: Model) -> Element(Msg) { 166 + let home_style = style.home(model.dark_mode) 167 168 html.div([attribute.style(home_style)], [ 169 html.p([], [ 170 element.text( ··· 216 ], 217 ), 218 ]), 219 + // html.p([], [ 220 // element.text("As of this coming September, I will be studying a BSc in Computer Games Programming @ "), 221 // common.link("Staffs", "https://staffs.ac.uk/") 222 // ]) 223 ]) 224 } 225 226 fn view_projects(model: Model) -> Element(Msg) { 227 + let project_style = style.project(model.dark_mode) 228 let projects = 229 model.projects 230 |> list.map(fn(project: projects.Project(Msg)) { 231 ui.stack( 232 [attribute.id("project-" <> project.id), attribute.style(project_style)], 233 [ 234 + cluster.of(html.div, [attribute.style(style.project_bar)], [ 235 + html.img([attribute.src(project.img), attribute.style(style.icon)]), 236 + html.h1([], [common.link(project.title, "projects/" <> project.id)]), 237 cluster.of( 238 html.div, 239 + [attribute.style(style.project_bar)], 240 project.links 241 |> list.map(fn(link) { 242 html.a([attribute.href(link.url), attribute.alt(link.title)], [ 243 html.img([ 244 attribute.src(link.img), 245 + attribute.style(style.icon), 246 ]), 247 ]) 248 }), ··· 256 } 257 258 fn view_project(model: Model, id: String) -> Element(Msg) { 259 + let project_style = style.project(model.dark_mode) 260 let project = 261 model.projects 262 |> list.find(fn(project) { project.id == id }) 263 html.div([attribute.style(project_style)], case project { 264 Ok(project) -> [ 265 + cluster.of(html.div, [attribute.style(style.project_bar)], [ 266 + html.img([attribute.src(project.img), attribute.style(style.icon)]), 267 html.h1([], [element.text(project.title)]), 268 ]), 269 html.p([], [project.summary]), ··· 277 }) 278 } 279 280 + fn view_posts(model: Model) -> Element(Msg) { 281 + let post_style = style.post(model.dark_mode) 282 283 let posts = 284 model.posts 285 |> list.map(fn(post: posts.Post(Msg)) { ··· 294 ]), 295 ], 296 [ 297 + html.h1([], [common.link(post.title, "posts/" <> post.id)]), 298 html.p([attribute.style([#("color", "gray")])], [ 299 element.text("Author: " <> post.author), 300 ]), 301 html.p([attribute.style([#("color", "gray")])], [ 302 + element.text( 303 + "Date Posted: " <> post.date_posted |> common.day_to_string, 304 + ), 305 ]), 306 ], 307 ), ··· 312 } 313 314 fn view_post(model: Model, id: String) -> Element(Msg) { 315 + let post_style = style.post(model.dark_mode) 316 + 317 let post = 318 model.posts 319 |> list.find(fn(post) { post.id == id }) ··· 334 element.text("Author: " <> post.author), 335 ]), 336 html.p([attribute.style([#("color", "gray")])], [ 337 + element.text( 338 + "Date Posted: " <> post.date_posted |> common.day_to_string, 339 + ), 340 ]), 341 ], 342 ),
+12 -8
src/website/common.gleam
··· 1 import lustre/attribute as attr 2 import lustre/element 3 import lustre/element/html 4 5 - const link_style: List(#(String, String)) = [ 6 - #("text-decoration", "underline"), #("color", "black"), 7 - ] 8 - 9 - pub fn styled_link(text: String, link: String) -> element.Element(a) { 10 - html.a([attr.href(link), attr.style(link_style)], [element.text(text)]) 11 } 12 13 - pub fn link(text: String, link: String) -> element.Element(a) { 14 - html.a([attr.href(link)], [element.text(text)]) 15 }
··· 1 + import birl.{type Day} 2 + import gleam/int 3 import lustre/attribute as attr 4 import lustre/element 5 import lustre/element/html 6 7 + pub fn link(text: String, link: String) -> element.Element(a) { 8 + html.a([attr.href(link), attr.style([#("color", "#f380b1")])], [ 9 + element.text(text), 10 + ]) 11 } 12 13 + pub fn day_to_string(day: Day) -> String { 14 + day.date |> int.to_string 15 + <> "/" 16 + <> day.month |> int.to_string 17 + <> "/" 18 + <> day.year |> int.to_string 19 }
+169
src/website/style.gleam
···
··· 1 + pub const page_padding: List(#(String, String)) = [ 2 + #("margin-left", "25vh"), #("margin-right", "25vh"), 3 + ] 4 + 5 + pub fn page(dark_mode: Bool) -> List(#(String, String)) { 6 + case dark_mode { 7 + True -> [ 8 + #("font-family", "\"Inter\", sans-serif"), 9 + #("font-weight", "400"), 10 + #("font-style", "normal"), 11 + #("min-height", "100vh"), 12 + #("background-color", "#151515"), 13 + #("padding", "3vh"), 14 + ] 15 + False -> [ 16 + #("font-family", "\"Inter\", sans-serif"), 17 + #("font-weight", "400"), 18 + #("font-style", "normal"), 19 + #("min-height", "100vh"), 20 + #("padding", "3vh"), 21 + ] 22 + } 23 + } 24 + 25 + pub fn navbar(dark_mode: Bool) -> List(#(String, String)) { 26 + case dark_mode { 27 + True -> [ 28 + #("display", "flex"), 29 + #("background-color", "#1e1e1e"), 30 + #("border-radius", "1em"), 31 + #("padding", "1em"), 32 + #("margin-bottom", "3vh"), 33 + #("justify-conten", "center"), 34 + #("align-items", "center"), 35 + #("gap", "1em"), 36 + #("border", "0"), 37 + #("flex-grow", "1"), 38 + ] 39 + False -> [ 40 + #("display", "flex"), 41 + #("background-color", "#cae4e7"), 42 + #("border-radius", "1em"), 43 + #("padding", "1em"), 44 + #("margin-bottom", "3vh"), 45 + #("justify-conten", "center"), 46 + #("align-items", "center"), 47 + #("gap", "1em"), 48 + #("border", "0"), 49 + #("flex-grow", "1"), 50 + ] 51 + } 52 + } 53 + 54 + pub fn button(dark_mode: Bool) -> List(#(String, String)) { 55 + case dark_mode { 56 + True -> [ 57 + #("background-color", "#1e1e1e"), 58 + #("border-radius", "1em"), 59 + #("padding", "1em"), 60 + #("margin-bottom", "3vh"), 61 + #("justify-conten", "center"), 62 + #("align-items", "center"), 63 + #("aspect-ratio", "1 / 1"), 64 + #("border", "0"), 65 + #("width", "6%") 66 + ] 67 + False -> [ 68 + #("background-color", "#cae4e7"), 69 + #("border-radius", "1em"), 70 + #("padding", "1em"), 71 + #("margin-bottom", "3vh"), 72 + #("justify-conten", "center"), 73 + #("align-items", "center"), 74 + #("aspect-ratio", "1 / 1"), 75 + #("border", "0"), 76 + #("width", "6%") 77 + ] 78 + } 79 + } 80 + 81 + pub fn navitem(dark_mode: Bool) -> List(#(String, String)) { 82 + case dark_mode { 83 + True -> [ 84 + #("text-decoration", "none"), 85 + #("background-color", "#151515"), 86 + #("padding", "0.75em"), 87 + #("border-radius", "1em"), 88 + #("color", "#dcdcdc"), 89 + ] 90 + False -> [ 91 + #("text-decoration", "none"), 92 + #("background-color", "#b8cfd2"), 93 + #("padding", "0.75em"), 94 + #("border-radius", "1em"), 95 + #("color", "black"), 96 + ] 97 + } 98 + } 99 + 100 + pub fn home(dark_mode: Bool) -> List(#(String, String)) { 101 + case dark_mode { 102 + True -> [ 103 + #("background-color", "#1e1e1e"), 104 + #("border-radius", "1em"), 105 + #("padding", "1em"), 106 + #("margin-bottom", "1em"), 107 + #("font-size", "1.25em"), 108 + #("color", "#dcdcdc"), 109 + ] 110 + False -> [ 111 + #("background-color", "#cae4e7"), 112 + #("border-radius", "1em"), 113 + #("padding", "1em"), 114 + #("margin-bottom", "1em"), 115 + #("font-size", "1.25em"), 116 + ] 117 + } 118 + } 119 + 120 + pub const icon: List(#(String, String)) = [ 121 + #("max-width", "4em"), #("max-height", "4em"), #("border-radius", "1em"), 122 + ] 123 + 124 + pub fn project(dark_mode: Bool) -> List(#(String, String)) { 125 + case dark_mode { 126 + True -> [ 127 + #("background-color", "#1e1e1e"), 128 + #("border-radius", "1em"), 129 + #("padding", "1em"), 130 + #("margin-bottom", "1em"), 131 + #("color", "#dcdcdc"), 132 + ] 133 + False -> [ 134 + #("background-color", "#cae4e7"), 135 + #("border-radius", "1em"), 136 + #("padding", "1em"), 137 + #("margin-bottom", "1em"), 138 + ] 139 + } 140 + } 141 + 142 + pub const project_bar: List(#(String, String)) = [ 143 + #("display", "flex"), #("align-items", "center"), #("gap", "1em"), 144 + ] 145 + 146 + pub fn post(dark_mode: Bool) -> List(#(String, String)) { 147 + case dark_mode { 148 + True -> [ 149 + #("background-color", "#1e1e1e"), 150 + #("border-radius", "1em"), 151 + #("padding", "1em"), 152 + #("margin-bottom", "1em"), 153 + #("color", "#dcdcdc"), 154 + ] 155 + False -> [ 156 + #("background-color", "#cae4e7"), 157 + #("border-radius", "1em"), 158 + #("padding", "1em"), 159 + #("margin-bottom", "1em"), 160 + ] 161 + } 162 + } 163 + 164 + pub fn link(dark_mode: Bool) -> List(#(String, String)) { 165 + case dark_mode { 166 + True -> [#("text-decoration", "underline"), #("color", "#dcdcdc")] 167 + False -> [#("text-decoration", "underline"), #("color", "black")] 168 + } 169 + }