at main 5.2 kB view raw
1import gleam/int 2import gleam/option.{type Option} 3import lustre/attribute as attr 4import lustre/element 5import lustre/element/html 6import tom 7import website/style 8 9pub type Post(a) { 10 Post( 11 id: String, 12 title: String, 13 author: String, 14 date_posted: tom.Date, 15 summary: String, 16 content: element.Element(a), 17 ) 18} 19 20pub type Link { 21 Link(title: String, url: String, img: String) 22} 23 24pub type Project(a) { 25 Project( 26 id: String, 27 title: String, 28 author: String, 29 img: String, 30 links: List(Link), 31 archived: Bool, 32 summary: String, 33 content: element.Element(a), 34 ) 35} 36 37pub type Anime { 38 Anime(title: String, link: Option(String), thoughts: Option(String)) 39} 40 41pub fn wrapper( 42 page: String, 43 elements: List(element.Element(a)), 44) -> element.Element(a) { 45 html.html([attr.attribute("lang", "en"), style.background_color()], [ 46 html.head([], [ 47 html.meta([attr.attribute("charset", "UTF-8")]), 48 html.title([], page <> " | Naomi Roberts"), 49 html.link([attr.rel("preconnect"), attr.href("https://fonts.bunny.net")]), 50 html.link([ 51 attr.rel("stylesheet"), 52 attr.href("https://fonts.bunny.net/css?family=almarai:400"), 53 ]), 54 ]), 55 style.body_query(), 56 html.body([attr.class("body-margin"), style.body_styles()], [ 57 navbar(), 58 html.div([attr.style("margin", "1em")], elements), 59 ]), 60 html.footer([style.footer()], [ 61 html.hr([attr.style("color", "lightgrey")]), 62 html.p([], [ 63 html.text("The source code for this website is available "), 64 link([], "here", "https://codeberg.org/naomi/website", True), 65 html.text(" or "), 66 link([], "here", "https://tangled.sh/@lesbian.skin/website", True), 67 html.text(" under the "), 68 link( 69 [], 70 "BSD 3 with Attribution", 71 "https://spdx.org/licenses/BSD-3-Clause-Attribution", 72 True, 73 ), 74 html.text(" license."), 75 ]), 76 html.p([], [ 77 html.text("If you wish to get in contact, please send an email to "), 78 link([], "mia@naomieow.xyz", "mailto:mia@naomieow.xyz", True), 79 html.text(", or contact me on "), 80 link([], "Discord", "https://chat.lesbian.skin", True), 81 html.text("."), 82 ]), 83 ]), 84 ]) 85} 86 87pub fn navbar() -> element.Element(a) { 88 html.div([style.navbar()], [ 89 navitem(name: "home", href: "/", external: False), 90 navitem(name: "projects", href: "/projects", external: False), 91 navitem(name: "posts", href: "/posts", external: False), 92 navitem( 93 name: "anime", 94 href: "https://anilist.co/user/naomieow/", 95 external: True, 96 ), 97 html.div([style.navbar_right()], [icon_link(Bluesky), icon_link(Tangled)]), 98 ]) 99} 100 101fn navitem( 102 name text: String, 103 href href: String, 104 external external: Bool, 105) -> element.Element(a) { 106 link([style.navitem()], text:, href:, external:) 107} 108 109pub type Icon { 110 Bluesky 111 Tangled 112} 113 114pub fn icon_link(icon icon: Icon) -> element.Element(a) { 115 let #(href, img) = case icon { 116 Bluesky -> #("https://bsky.app/profile/lesbian.skin", "/assets/bsky.svg") 117 Tangled -> #("https://tangled.sh/@lesbian.skin", "/assets/git.svg") 118 } 119 120 html.a([attr.href(href), style.link_icon()], [ 121 html.img([attr.src(img), style.icon_size()]), 122 ]) 123} 124 125pub fn link( 126 attributes attributes: List(attr.Attribute(a)), 127 text text: String, 128 href href: String, 129 external external: Bool, 130) -> element.Element(a) { 131 let attrs = [attr.href(href), style.link(), ..attributes] 132 html.a( 133 case external { 134 False -> attrs 135 True -> [ 136 attr.target("_blank"), 137 attr.rel("noopener noreferrer"), 138 attr.style("display", "inline-block"), 139 ..attrs 140 ] 141 }, 142 [ 143 element.text(text), 144 // Seems to cause some DOM issues? 145 // in some places it appears `<a href>text<p style>↗</p></a> 146 // but in other places it's `<p><a href>text<a></p><p style><a href>↗</a></p><p><p/>` 147 // case external { 148 // True -> 149 // html.p( 150 // [ 151 // attr.styles([ 152 // #("display", "inline-block"), 153 // #("font-size", "0.75em"), 154 // #("margin", "0"), 155 // #("vertical-align", "top"), 156 // ]), 157 // ], 158 // [element.text("↗")], 159 // ) 160 // False -> element.none() 161 // }, 162 ], 163 ) 164} 165 166pub fn raw_link( 167 attributes attributes: List(attr.Attribute(a)), 168 content content: List(element.Element(a)), 169 href href: String, 170 external external: Bool, 171) -> element.Element(a) { 172 let attrs = [attr.href(href), style.link(), ..attributes] 173 html.a( 174 case external { 175 False -> attrs 176 True -> [ 177 attr.target("_blank"), 178 attr.rel("noopener noreferrer"), 179 attr.style("display", "inline-block"), 180 ..attrs 181 ] 182 }, 183 content, 184 ) 185} 186 187pub fn warning(title: String, contents: String) -> element.Element(a) { 188 html.div([], [ 189 html.h3([], [element.text(title)]), 190 html.p([], [element.text(contents)]), 191 ]) 192} 193 194pub fn date_to_string(date: tom.Date) -> String { 195 date.day |> int.to_string() 196 <> "/" 197 <> date.month |> int.to_string() 198 <> "/" 199 <> date.year |> int.to_string() 200}