(ns brut.site (:require [hiccup2.core :as h] [babashka.fs :as fs] [clojure.string :as string] [babashka.process :refer [process]])) (defn html-pp [html-str] (let [xhtml (:out @(process ["tidy" "-i" "-asxhtml" "-quiet" "-utf8"] {:in html-str :out :string}))] (:out @(process ["hxselect" "-c" "body"] {:in xhtml :out :string})))) (def brutalism-img "h/img/brutalism.webp") (def demo-light-img "h/img/brut-demo-light.webp") (def demo-dark-img "h/img/brut-demo-dark.webp") (defn nav [rel-pref] (let [to (fn [path] (str rel-pref path))] [:div [:nav.nav {:tabindex "-1" :onclick "this.focus()"} [:div.container [:a.pagename {:href (to "index.html")} "BRUT"] [:a {:href (to "h/docs.html")} "Docs"] [:a {:href (to "h/download.html")} "Download"] [:a {:href "https://gitea.esy.fun/yogsototh/brutcss"} "Code"]]] [:button.btn.sn.btn-close "×"]])) (defn footer [] [:footer [:div.container [:p "By " [:a {:href "https://yannesposito.com"} "Yann Esposito"]]]]) (defn mk-page [rel-pref metas content] (let [{:keys [title footer? gapless?] :or {footer? true gapless? false}} metas] (h/html [:head [:meta {:http-equiv "Content-Type" :content "text/html; charset=UTF-8"}] [:meta {:name "viewport" :content "width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"}] [:title title] [:link {:href (str rel-pref "brut.min.css") :rel "stylesheet" :type "text/css"}]] [(if gapless? :body.col.gapless :body.col) (nav rel-pref) content (when footer? (footer))]))) (def dest "_site") (defn gen-page [file-path metas content-fn] (let [depth (or (some-> file-path fs/parent fs/components count) 0) rel-pref (apply str (repeat depth "../")) html (mk-page rel-pref metas (content-fn rel-pref metas)) dst (str dest "/" file-path)] (fs/create-dirs (fs/parent dst)) (println "Generates: " dst) (spit dst html))) (defn mk-index [rel-pref _metas] (let [to (fn [path] (str rel-pref path)) hero [:div.hero {:style (format "background: url(%s)" (to brutalism-img))} [:div.container.col.middle [:div.row.middle [:div.c2] [:div.c8.col.block.bg-neutral [:h1.tight.huge "BRUT"] [:h4.big "A Brutalist and Minimalist CSS Framework"] [:p.big "This CSS framework is ideal to be used for " [:strong "private admin interfaces"] "."] [:p.big " Just by the look of it, it will scream: " [:br] [:span.hl "THIS IS NOT FOR NON-TECHNICAL PEOPLE!!!"]]] [:div.c2]]]] cards [:div.container [:div.col [:div.row [:div.col.card [:h3 "Examples"] [:p "A few quick examples to show every components. " "This should be quick and straightforward."] [:p "This CSS framework makes its best to respect some expected composability accross classes." " Here are a few examples:"] [:div.row [:div [:p.tight "A block: "] [:div.card [:div.tb "textual btn"] [:br] [:div.btn "classic btn"] [:br] [:div.msg "Some message block"]]] [:div [:p.tight "Then add the class " [:code "big"] ": "] [:div.card.big [:div.tb "textual btn"] [:br] [:div.btn "classic btn"] [:br] [:div.msg "Some message block"]]]]] [:div.col.card [:h3 "Example"] [:img {:src (to demo-light-img) :style "border:solid 10px #777; padding: 10px; width: 80%; align-self: center" :alt "A demo of brut.css with a few forms and an error message"}]]] [:div.row [:div.col.card [:h3 "Docs"] [:a.tb.big.info.stretch {:href (to "h/docs.html")} "Docs"]] [:div.col.card [:h3 "Download"] [:a.tb.warn.big.stretch {:href (to "h/download.html")} "Download"]]]]]] [:div hero cards])) (defn mk-download [rel-pref _metas] (let [to (fn [path] (str rel-pref path))] [:div.central.fill {:style (str "background:url('" (to brutalism-img) "')")} [:a.btn.warn.huge {:href (to "brut.min.css")} "Download BRUT"]])) (defn to-pre [hc] (h/html {:escape-strings? true} [:pre (-> (str (h/html hc)) html-pp)])) (defn mk-docs [rel-pref _metas] (let [to (fn [path] (str rel-pref path)) txt [:div {:id "text"} [:h1 "title in h1"] [:p "Some text in

with different styles; " [:b "bold"] ", " [:strong "strong"] ", " [:i "italic"] ", " [:em "emphasis"] ", " [:code "code"] ". " "If you really want something to be extremly visible, " [:strong.hl "use the class "[:code.hl "hl"]] "."] [:p "Links will looks like this into text: " [:a {:href "#"} "This is a link inside a paragraph."]] [:p "The text should be " [:strong.hl "very dense"] " on purpose." " Forget your lessons about nice space in design here." " The goal of this design is to produce " [:em "professional"] " UI applications." " So no time to make it breathes." " We want to make it compact and efficient."] [:p "Note we still try to keep a coherent and nice vertical rythm."] [:blockquote [:p "Here is some blockquote." " This can give you an idea about the look and feel for them."]]] doc [:div {:id "doc"} [:p "Remark if you need to present a long text to read you can still use the class " [:code "doc"] "." " Take a look at the next paragraph for example: "] [:div.doc [:p "Lorem ipsum with pretty Lorem ipsum dolor sit amet consectetur adipiscing elit," " urna consequat felis vehicula class ultricies mollis dictumst, aenean non a in donec nulla." " Phasellus ante pellentesque erat cum risus consequat imperdiet aliquam, integer" " placerat et turpis mi eros nec lobortis taciti, vehicula nisl litora tellus ligula porttitor metus."] [:p "Vivamus integer non suscipit taciti mus etiam at primis tempor sagittis sit," " euismod libero facilisi aptent elementum felis blandit cursus gravida sociis" " erat ante, eleifend lectus nullam dapibus netus feugiat curae curabitur est ad." " Massa curae fringilla porttitor quam sollicitudin iaculis aptent leo ligula" " euismod dictumst, orci penatibus mauris eros etiam praesent erat volutpat" " posuere hac." " Metus fringilla nec ullamcorper odio aliquam lacinia conubia mauris tempor," " etiam ultricies proin quisque lectus sociis id tristique, integer phasellus" " taciti pretium adipiscing tortor sagittis ligula."]] [:p "The " [:code "doc"] " class ensure the width of the text is not too wide and use a very legible font."]] itemize [:div {:id "itemize"} [:h2 "itemization"] [:h3 "ul"] [:ul [:li "item 1"] [:li "item 2"] [:li "item 3; with a very long text that should wrap to the next line in any browser." " We'll see that the wrapped text should be aligned with the text of the other items." " This is due to the " [:code "list-style-position: outside"] "." ] [:li "item 4"]] [:h3 "ol"] [:ol [:li "item 1"] [:li "item 2"] [:li "item 3"] [:li "item 4"]]] headings [:div [:h3 "headings"] [:p "By default there is some space before and after any heading:"] [:h1 "h1"] [:h2 "h2"] [:h3 "h3"] [:h4 "h4"] [:h5 "h5"] [:h6 "h6"] [:h3 "tight headings"] [:p "You can remove the space by using the class " [:code "tight"]" to the heading:"] [:h1 {:class "tight"} "h1 tight"] [:h2 {:class "tight"} "h2 tight"] [:h3 {:class "tight"} "h3 tight"] [:h4 {:class "tight"} "h4 tight"] [:h5 {:class "tight"} "h5 tight"] [:h6 {:class "tight"} "h6 tight"]] textual-section {:title "Textual content" :cards [txt doc headings itemize]} images [:div {:id "images"} [:h1 "Images"] [:h2 "Inside a grid"] [:div.row [:div.card [:p "Inside a card"] [:img {:src (to brutalism-img)}]] [:div.block [:p "In a block"] [:img {:src (to brutalism-img)}]] [:div [:span "Directly in the column"] [:img {:src (to brutalism-img)}]] ]] images-section {:title "Images" :cards [images]} text-buttons [:div#text-buttons [:h3 "Textual buttons"] [:a.tb "tb"] [:a.tb.hl "tb hl"] [:a.tb.info "tb info"] [:a.tb.ok "tb ok"] [:a.tb.warn "tb warn"] [:a.tb.err "tb err"] [:a.tb.fatal "tb fatal"]] text-buttons-sizes [:div#text-buttons-sizes [:h3 "Textual Buttons Sizes"] [:div.block "Some text: " [:a {:class "tb sm"} "small button"] [:br] "Some text: " [:a {:class "tb ok"} "normal ok"] [:br] "Some text: " [:a {:class "tb big warn"} "big warn"] [:br] "Some text: " [:a {:class "tb huge err"} "huge err"]]] buttons [:div.col [:h2 "Buttons"] [:div.row [:div.card.c4 [:h3 "Classic"] [:a.btn "btn"] [:a.btn.hl "btn hl"] [:a.btn.info "btn info"] [:a.btn.ok "btn ok"] [:a.btn.warn "btn warn"] [:a.btn.err "btn err"] [:a.btn.fatal "btn fatal"]] [:div.card.c4 [:h3 "Sizes"] [:a.btn.sm "btn sm"] [:a.btn.info "btn info"] [:a.btn.big.warn "btn big warn"] [:a.btn.huge.err "btn huge err"]] [:div.col.c4.card [:h3 "Standardized Width " [:code "btn std"]] [:div.col.mid [:a.btn.std "btn std"] [:a.btn.std.hl "btn std hl"] [:a.btn.std.info "btn std info"] [:a.btn.std.ok "btn std ok"] [:a.btn.std.warn "btn std warn"] [:a.btn.std.err "btn std err"] [:a.btn.std.fatal "btn std fatal"]]] ] [:div.row [:div.col.card.c4 [:h3 "Big"] [:div.col.big.mid [:a.btn.std "btn std"] [:a.btn.std.hl "btn std hl"] [:a.btn.std.info "btn std info"] [:a.btn.std.ok "btn std ok"] [:a.btn.std.warn "btn std warn"] [:a.btn.std.err "btn std err"] [:a.btn.std.fatal "btn std fatal"]] ] [:div.col.card.c8 [:h3 "Huge"] [:div.col.huge.mid [:a.btn.std "btn std"] [:a.btn.std.hl "btn std hl"] [:a.btn.std.info "btn std info"] [:a.btn.std.ok "btn std ok"] [:a.btn.std.warn "btn std warn"] [:a.btn.std.err "btn std err"] [:a.btn.std.fatal "btn std fatal"]]]] ] tags [:div {:id "tags"} [:h2 "Tags"] [:ul [:li "item-1" [:span.tag "tag"]] [:li "item-2" [:span.tag.hl "hl tag"]] [:li "item-3" [:span.tag.info "info"]] [:li "item-4" [:span.tag.ok "ok"]] [:li "item-5" [:span.tag.warn "warn"]] [:li "item-6" [:span.tag.err "err"]] [:li "item-7" [:span.tag.fatal "fatal"]]]] tags-listed [:div {:id "tags-inline"} [:h2 "Inline Tags"] [:span.tag "tag"] [:span.tag.hl "tag hl"] [:span.tag.info "info"] [:span.tag.ok "ok"] [:span.tag.warn "warn"] [:span.tag.err "err"] [:span.tag.fatal "fatal"] ] buttons-section {:title "Buttons" :cards [tags tags-listed text-buttons text-buttons-sizes buttons]} messages [:div {:class "row"} [:div {:class "col c6 gapless"} [:h2 "Basic Messages"] [:br] [:div {:class "msg"} [:strong " Normal Message"] " This is a normal message with " [:code "msg"] "."] [:div {:class "msg info"} [:strong [:i.ico.big "☞"]" Info"] " This is done by adding " [:code "info"]" to the class.\n"] [:div {:class "msg ok"} [:strong [:i.ico.big "☑"]" OK"] " This is done by adding " [:code "ok"]" to the class.\n"] [:div {:class "msg warn"} [:strong [:i.ico.big "☣"]" Warning"] " This is done by adding " [:code "warn"]" to the class.\n"] [:div {:class "msg err"} [:strong [:i.ico.big "☒"]" Error"] " This is done by adding " [:code "err"]" to the class.\n"] [:div {:class "msg fatal"} [:strong [:i.ico.big "☠"]" Fatal"] " This is done by adding " [:code "fatal"]" to the class.\n"] [:div {:class "msg hl"} [:strong [:i.ico.big "☞"] "Highlighted"] " This is done by adding " [:code "hl"]" to the class.\n"]]] messages-section {:title "Messages" :cards [messages]} forms [:div [:label {:for "example1"}] [:input#example1 {:type "text" :placeholder "text input"}] [:br] [:br] [:label {:for "textarea1"}] [:textarea#textarea1 {:cols "30" :rows "3", :placeholder "textarea"}] [:br] [:br] [:div.form-block [:span {:class "addon info"} "$"] [:input {:type "text" :placeholder "with addon"}]] [:br] [:br] [:div {:class "msg"} [:strong "Be careful with addons!"] " If you do not want a space between the addon and the input make sure " "that there is no space between the " [:code ""] " and " [:code ""]" tags. Example: " [:pre "... " [:span.hl " "] " i 0) [:div.bg-neutral {:class cl} cl]) (when (> (- 12 i) 0) [:div.y {:class co-cl} co-cl])]))]] grid-gapless [:div [:h3 "Gapless"] [:div (for [i (range 12 -1 -1)] (let [cl (str "c" i) co-cl (str "c" (- 12 i))] [:div.row.gapless (when (> i 0) [:div.bg-neutral {:class cl} cl]) (when (> (- 12 i) 0) [:div.y {:class co-cl} co-cl])]))]] grid-3col [:div [:h3 "3 columns"] [:div (for [i (range 11 0 -1) j (range (- 11 i) -1 -1)] (let [k (- 12 i j) cli (str "c" i) clj (str "c" j) clk (str "c" k)] (if (= j 0) [:br] [:div.row (when (> j 0) [:div.b {:class clj} clj]) (when (> i 0) [:div.bg-neutral {:class cli} cli]) (when (> k 0) [:div.r {:class clk} clk]) ])))]] grid-section {:title "Grid" :cards [grid grid-gapless grid-3col]}] [:div.container [:h1 "Documentation"] [:p "Here a bunch of examples with their source code to make them happen."] (for [{:keys [title cards]} [textual-section grid-section images-section icons-section forms-section tables-section buttons-section messages-section navbar-section footer-section]] [:div.col [:h2 {:id title} [:a {:href (str "#" title)} [:i.ico "[§]"]] " " title] (for [[c1 c2] (partition 2 2 nil cards)] [:div.row [:div.card.c6 c1 [:br] [:details.push [:summary.bg-neutral "code"] (to-pre c1)]] (when c2 [:div.card.c6 c2 [:details.push [:summary.bg-neutral "code"] (to-pre c2)]])])])])) (defn brut-copy [path] (let [dst (str dest "/" path)] (println "Copying: " path " to " dst) (fs/copy-tree path dst {:replace-existing true}))) (defn -main [& _args] (gen-page "index.html" {:title "BRUT" :gapless? true} mk-index) (gen-page "h/download.html" {:title "BRUT - download" :footer? false :gapless? true} mk-download) (gen-page "h/docs.html" {:title "BRUT - documentation"} mk-docs) (brut-copy "h/img"))