commit 84b9da46983ece285801189ae7b613b074485d82 parent fc34477adff6ca894a073cd2e1bd8a17ec1533ec Author: massi <mdsiboldi@gmail.com> Date: Sun, 28 Jul 2024 22:10:33 -0700 to monorepo, add swatchbuckler Diffstat:
29 files changed, 591 insertions(+), 241 deletions(-)
diff --git a/.gitignore b/.gitignore @@ -3,4 +3,5 @@ build **/#*# **/*.draft .cpcache -.nrepl-port -\ No newline at end of file +.nrepl-port +node_modules +\ No newline at end of file diff --git a/Makefile b/Makefile @@ -1,16 +1,32 @@ +ifeq ($(MW_ENV), dev) + SITE_ROOT := /massi.world + SB_ROOT := /swatchbuckler.massi.world +endif + clean: rm -r build + build: - mkdir -p build/static - cp -r static build/ - cp server-root/* build/ - tsc --outDir build/js src/*.ts - clj -X massi-world/build + mkdir -p build/shared + cp -r shared/static/* build/shared + mkdir -p build/massi.world + cp -r site/static/* build/massi.world + cd build/massi.world && ln -s ../shared/* . + WEB_ROOT=$(SITE_ROOT) clj -X site/build + + mkdir -p build/swatchbuckler.massi.world/js + cp -r shared/static/* build/swatchbuckler.massi.world + cp swatchbuckler/node_modules/d3-color/dist/d3-color.min.js build/swatchbuckler.massi.world/js + WEB_ROOT=$(SB_ROOT) clj -X swatchbuckler/build + serve: + make clean + MW_ENV=dev make build http-server build -p 8080 & while true; do \ inotifywait @./build -qre close_write .; \ make clean; \ - make build; \ + MW_ENV=dev make build; \ done + publish: build - rsync -r --delete build/ "${MASSIWORLD_HOST}:/home/protected/builds/massi-world" + rsync -rl --delete build/ "${MASSIWORLD_HOST}:/home/protected/builds/massi-world" diff --git a/server-root/manifest.json b/server-root/manifest.json @@ -1,16 +0,0 @@ -{ - "name": "massi.world", - "icons": [ - { - "src": "/assets/massi-world-192-192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "/assets/massi-world-192-192.png", - "type": "image/png", - "sizes": "192x192" - }, - ], - "start_url": "/index.html" -} diff --git a/server-root/favicon.ico b/shared/static/favicon.ico Binary files differ. diff --git a/static/massi-world-16-16.png b/shared/static/shared-assets/icon-16-16.png Binary files differ. diff --git a/static/massi-world-180-180.png b/shared/static/shared-assets/icon-180-180.png Binary files differ. diff --git a/static/massi-world-192-192.png b/shared/static/shared-assets/icon-192-192.png Binary files differ. diff --git a/static/massi-world-32-32.png b/shared/static/shared-assets/icon-32-32.png Binary files differ. diff --git a/static/massi-world-48-48.png b/shared/static/shared-assets/icon-48-48.png Binary files differ. diff --git a/static/spiral-for-dark-mode.svg b/shared/static/shared-assets/spiral-for-dark-mode.svg diff --git a/static/spiral-for-light-mode.svg b/shared/static/shared-assets/spiral-for-light-mode.svg diff --git a/site/manifest.templ.json b/site/manifest.templ.json @@ -0,0 +1,11 @@ +{ + "name": "massi world", + "icons": [ + { + "src": "@(web-root)/shared-assets/icon-192-192.png", + "type": "image/png", + "sizes": "192x192" + } + ], + "start_url": "@(web-root)/index.html" +} diff --git a/src/color-synth.md b/site/md/color-synth.templ.md diff --git a/static/color-synth-sample-1.webp b/site/static/assets/color-synth-sample-1.webp Binary files differ. diff --git a/static/color-synth-sample-2.webp b/site/static/assets/color-synth-sample-2.webp Binary files differ. diff --git a/static/color-synth-sample-3.webp b/site/static/assets/color-synth-sample-3.webp Binary files differ. diff --git a/static/color-synth-sample-4.webp b/site/static/assets/color-synth-sample-4.webp Binary files differ. diff --git a/site/style.templ.css b/site/style.templ.css @@ -0,0 +1,70 @@ +:root { + --dark-bg: #1d2c3a; + --dark-fg: #acbdcf; + --light-bg: #def0ff; + --light-fg: #455564; +} + +html { + font-size: 24px; + color: var(--light-fg); + background: var(--light-bg); + +} + +body { + line-height: 1.5; + margin: 0px auto; + max-width: 35rem; + padding: 0.75rem; +} + +h1 { + font-size: 2rem; +} + +ul > li { + list-style: url("@(web-root)/shared-assets/spiral-for-light-mode.svg") +} + +@media (prefers-color-scheme: dark) { + html { + background: var(--dark-bg); + color: var(--dark-fg); + } + ul > li { + list-style: url("@(web-root)/shared-assets/spiral-for-dark-mode.svg") + } +} + +.color-synth .samples { + display: flex; + max-width: 100%; +} + +.color-synth .samples > * { + flex: auto; + min-width: 1px; + max-width: 8rem; + margin: 0 0 0.5rem 0.5rem; + border-radius: 0.2rem; +} + +.color-synth .samples > *:first-child { + margin-left: 0; +} + +@media (max-width: 600px) { + html { + font-size: 18px; + } + .color-synth .samples { + flex-wrap: wrap; + } + .color-synth .samples > * { + max-width: calc(50% - 0.25rem); + } + .color-synth .samples > *:nth-child(2n+1) { + margin-left: 0; + } +} diff --git a/src/core.clj b/src/core.clj @@ -0,0 +1,74 @@ +(ns core + (:require [hiccup2.core :as h] + [clojure.java.io :as io] + [markdown.core :as md] + [clojure.string :as cstr])) + +(def WEB_ROOT (or (System/getenv "WEB_ROOT") "")) +(println WEB_ROOT) + +(defn p [path] (str WEB_ROOT path)) + +(defn base-head-stuff [ {:keys [path title desc img]}] + (list [:meta {:charset "UTF-8"}] + [:meta {:name "viewport" + :content "width=device-width" + :initial-screen-scale "1"}] + [:title title] + [:script {:type "module"} (h/raw "document.documentElement.classList.remove('no-js'); +document.documentElement.classList.add('js');")] + [:meta {:name "description" :content desc}] + [:meta {:property "og:title" :content title}] + [:meta {:property "og:image" :content (get img :src)}] + [:meta {:property "og:image:alt" :content (get img :desc)}] + [:meta {:property "og:locale" :content "en_US"}] + [:meta {:property "og:type" :content "website"}] + [:meta {:property "og:url" :content path}] + [:meta {:name "theme-color" :content "#000033"}] + [:link {:rel "canonical" :href path}])) + +(defn massi-world-domain-stuff [{:or {img {:src "https://massi.world/shared-assets/icon-180-180.png" + :desc "an undiscovered planet."}} + :as argmap}] + (concat (base-head-stuff argmap) + (list [:link {:rel "shortcut icon" :href (p "/favicon.ico")}] + [:link {:rel "icon" :type "image/png" :sizes "16x16" :href (p "/shared-assets/icon-16-16.png")}] + [:link {:rel "icon" :type "image/png" :sizes "32x32" :href (p "/shared-assets/icon-32-32.png")}] + [:link {:rel "icon" :type "image/png" :sizes "48x48" :href (p "/shared-assets/icon-48-48.png")}] + [:link {:rel "apple-touch-icon" :href (p "/shared-assets/icon-180-180.png")}]))) + +(def linkback-footer + (list [:footer "made by " [:a {:href "https://massi.world"} "massi"]])) + +(defn fill-templ [templstr hole-map] + (clojure.string/replace + templstr + #"@\(([^\s\)]+)\)" + (fn [match] + (let [hole-filler (get hole-map (keyword (second match)))] + (cond + (fn? hole-filler) (hole-filler) + (or (seq? hole-filler) (vector? hole-filler)) (str (h/html hole-filler)) + (nil? hole-filler) (throw (Exception. (str "No filler for hole " (second match)))) + true hole-filler))))) + +(defn process-templ [in-file out-file hole-map] + (if (or (not (string? in-file)) + (not (string? out-file)) + (= in-file out-file)) + (throw (Exception. "unable to process template. either in/out files not specified or they're the same. which would be weird."))) + (spit out-file (fill-templ (slurp in-file) hole-map))) + +(defn md-hole [hole-map] + (fn [text state] + [(fill-templ text hole-map) state])) + +(defn md + ([file] (md file nil)) + ([file hole-fillers] + (let* [base-args [:heading-anchors true] + args (if (map? hole-fillers) + (into base-args [:custom-transformers [(md-hole hole-fillers)]]) + base-args)] + (h/raw (apply md/md-to-html-string (slurp file) args))))) + diff --git a/src/js-check.ts b/src/js-check.ts @@ -1,2 +0,0 @@ -document.documentElement.classList.remove('no-js'); -document.documentElement.classList.add('js'); diff --git a/src/massi_world.clj b/src/massi_world.clj @@ -1,77 +0,0 @@ -(ns massi-world - (:require [hiccup2.core :as h] - [clojure.java.io :as io] - [markdown.core :as md] - [clojure.string :as cstr] - [share :as shr])) - -(defn massi-world-site-stuff [argmap] - (concat (shr/massi-world-domain-stuff argmap) - (list [:link {:rel "stylesheet" :href "/static/style.css"}] - [:link {:rel "manifest" :href "/manifest.json"}]))) - - -(defn with-page [argmap & body] - (h/html (h/raw "<!DOCTYPE html>") - [:html.no-js - [:head - (massi-world-site-stuff - (merge argmap - {:path (str "https://massi.world" (get argmap :path))}))]] - [:body (seq body)])) - -(defn header [] - [:header "massi world 🦝"]) -(defn footer [] - [:footer - "Last generated: " - (.format (java.text.SimpleDateFormat. "MM/dd/yyyy hh:mm:ssa z") - (new java.util.Date))]) - -(defn mk-home [path] - (with-page {:path path - :title "massi world" - :desc "home"} - (header) - [:section - [:h1 "projects"] - [:ul - [:li [:a {:href "/projects/color-synth.html"} "color-synth"]]] - (footer)])) - -(defn mk-page [{:keys [path fn]}] - (let* [out-dir "build" - out-file (str out-dir path) - out-str (str (apply fn `(~path)))] - (io/make-parents out-file) - (spit out-file out-str) - (println "wrote" out-file) - out-str)) - - -;; _ arg is so we can call this with clj -X massi-world/build -;; _ is expected to be a map, and clojure complains if we don't accept it -(defn build [_] - (println "building site...") - (time - (doseq [spec [{:path "/index.html" :fn mk-home} - {:path "/projects/color-synth.html" - :fn #(with-page {:path % - :title "projects -> color-synth" - :desc "a digital video synthesizer on the web"} - (header) - [:main.color-synth - [:div.md (shr/md "src/color-synth.md" - {:samples [:div.samples - [:img {:src "/static/color-synth-sample-1.webp" - :alt "a pixelated wobbling smiley face"}] - [:img {:src "/static/color-synth-sample-2.webp" - :alt "jagged vibrant waves"}] - [:img {:src "/static/color-synth-sample-3.webp" - :alt "a complicated animation. fuzzy, sharp, and checkered"}] - [:img {:src "/static/color-synth-sample-4.webp" - :alt "mellow swampy waves"}]]})]] - (footer))}]] - (mk-page spec)))) - -(build nil) diff --git a/src/share.clj b/src/share.clj @@ -1,67 +0,0 @@ -(ns share - (:require [hiccup2.core :as h] - [clojure.java.io :as io] - [markdown.core :as md] - [clojure.string :as cstr])) - -(defn base-head-stuff [ {:keys [path title desc img]}] - (list [:meta {:charset "UTF-8"}] - [:meta {:name "viewport" - :content "width=device-width" - :initial-screen-scale "1"}] - [:title title] - [:script {:type "module"} (h/raw "document.documentElement.classList.remove('no-js'); -document.documentElement.classList.add('js');")] - [:meta {:name "description" :content desc}] - [:meta {:property "og:title" :content title}] - [:meta {:property "og:image" :content (get img :src)}] - [:meta {:property "og:image:alt" :content (get img :desc)}] - [:meta {:property "og:locale" :content "en_US"}] - [:meta {:property "og:type" :content "website"}] - [:meta {:property "og:url" :content path}] - [:meta {:name "theme-color" :content "#000033"}] - [:link {:rel "canonical" :href path}])) - -(defn massi-world-domain-stuff [{:or {img {:src "https://massi.world/static/massi-world-180-180.png" - :desc "an undiscovered planet."}} - :as argmap}] - (concat (base-head-stuff argmap) - (list [:link {:rel "shortcut icon" :href "/favicon.ico"}] - [:link {:rel "icon" :type "image/png" :sizes "16x16" :href "/static/massi-world-16-16.png"}] - [:link {:rel "icon" :type "image/png" :sizes "32x32" :href "/static/massi-world-32-32.png"}] - [:link {:rel "icon" :type "image/png" :sizes "48x48" :href "/static/massi-world-48-48.png"}] - [:link {:rel "apple-touch-icon" :href "/static/massi-world-180-180.png"}]))) - -(def linkback-footer - (list [:footer "made by " [:a {:href "https://massi.world"} "massi"]])) - -(defn fill-templ [templstr hole-map] - (clojure.string/replace - templstr - #"@\(([^\s]+)\)" - (fn [match] - (let [hole-filler (get hole-map (keyword (second match)))] - (cond - (fn? hole-filler) (hole-filler) - (vector? hole-filler) (str (h/html hole-filler)) - (nil? hole-filler) (throw (Exception. (str "No filler for hole " (second match)))) - true hole-filler))))) - -(defn process-templ [in-file out-file hole-map] - (if (or (not (string? in-file)) (not (string? out-file)) (= in-file out-file)) - (throw (Exception. "unable to process template. either in/out files not specified or they're the same. which would be weird."))) - (spit out-file (fill-templ (slurp in-file) hole-map))) - -(defn md-hole [hole-map] - (fn [text state] - [(fill-templ text hole-map) state])) - -(defn md - ([file] (md file nil)) - ([file hole-fillers] - (let* [base-args [:heading-anchors true] - args (if (map? hole-fillers) - (into base-args [:custom-transformers [(md-hole hole-fillers)]]) - base-args)] - (h/raw (apply md/md-to-html-string (slurp file) args))))) - diff --git a/src/site.clj b/src/site.clj @@ -0,0 +1,89 @@ +(ns site + (:require [hiccup2.core :as h] + [clojure.java.io :as io] + [markdown.core :as md] + [clojure.string :as cstr] + [core])) + +(defn site-header [argmap] + (concat (core/massi-world-domain-stuff argmap) + (list [:link {:rel "stylesheet" :href (core/p "/style.css")}] + [:link {:rel "manifest" :href (core/p "/manifest.json")}]))) + + +(defn with-page [argmap & body] + (h/html (h/raw "<!DOCTYPE html>") + [:html.no-js + [:head + (site-header + (merge argmap + {:path (str "https://massi.world" (get argmap :path))}))]] + [:body (seq body)])) + +(defn header [] + [:header "massi world 🦝"]) +(defn footer [] + [:footer + "Last generated: " + (.format (java.text.SimpleDateFormat. "MM/dd/yyyy hh:mm:ssa z") + (new java.util.Date))]) + +(defn mk-home [path] + (with-page {:path path + :title "massi world" + :desc "home"} + (header) + [:section + [:h1 "projects"] + [:ul + [:li [:a {:href (core/p "/projects/color-synth.html")} "color-synth"]]] + (footer)])) + +(defn mk-page [{:keys [path fn]}] + (let* [out-dir "build/massi.world" + out-file (str out-dir path) + out-str (str (apply fn `(~path)))] + (io/make-parents out-file) + (spit out-file out-str) + (println "wrote" out-file) + out-str)) + +(defn build-manifest [] + (core/process-templ "site/manifest.templ.json" + "build/massi.world/manifest.json" + {:web-root core/WEB_ROOT}) + (println "wrote manifest.")) + +(defn build-css [] + (core/process-templ "site/style.templ.css" + "build/massi.world/style.css" + {:web-root core/WEB_ROOT}) + (println "wrote style.css")) + +;; _ arg is so we can call this with clj -X massi-world/build +;; _ is expected to be a map, and clojure complains if we don't accept it +(defn build [_] + (println "building site...") + (build-manifest) + (build-css) + (time + (doseq [spec [{:path "/index.html" :fn mk-home} + {:path "/projects/color-synth.html" + :fn #(with-page {:path % + :title "projects -> color-synth" + :desc "a digital video synthesizer on the web"} + (header) + [:main.color-synth + [:div.md (core/md "site/md/color-synth.templ.md" + {:samples [:div.samples + [:img {:src (core/p "/assets/color-synth-sample-1.webp") + :alt "a pixelated wobbling smiley face"}] + [:img {:src (core/p "/assets/color-synth-sample-2.webp") + :alt "jagged vibrant waves"}] + [:img {:src (core/p "/assets/color-synth-sample-3.webp") + :alt "a complicated animation. fuzzy, sharp, and checkered"}] + [:img {:src (core/p "/assets/color-synth-sample-4.webp") + :alt "mellow swampy waves"}]]})]] + (footer))}]] + (mk-page spec)))) + diff --git a/src/swatchbuckler.clj b/src/swatchbuckler.clj @@ -0,0 +1,16 @@ +(ns swatchbuckler + (:require [core])) + +(defn build [_] + (core/process-templ + "swatchbuckler/index.templ.html" + "build/swatchbuckler.massi.world/index.html" + {:head-content (core/massi-world-domain-stuff {:path "https://swatchbuckler.massi.world" + :title "swatchbuckler" + :desc "generate fg/bg pairs from a midtone"})}) + (println "wrote build/swatchbuckler.massi.world/index.html") + + (core/process-templ + "swatchbuckler/manifest.templ.json" + "build/swatchbuckler.massi.world/manifest.json" + {:web-root core/WEB_ROOT})) diff --git a/static/style.css b/static/style.css @@ -1,70 +0,0 @@ -:root { - --dark-bg: #1d2c3a; - --dark-fg: #acbdcf; - --light-bg: #def0ff; - --light-fg: #455564; -} - -html { - font-size: 24px; - color: var(--light-fg); - background: var(--light-bg); - -} - -body { - line-height: 1.5; - margin: 0px auto; - max-width: 35rem; - padding: 0.75rem; -} - -h1 { - font-size: 2rem; -} - -ul > li { - list-style: url("/static/spiral-for-light-mode.svg") -} - -@media (prefers-color-scheme: dark) { - html { - background: var(--dark-bg); - color: var(--dark-fg); - } - ul > li { - list-style: url("/static/spiral-for-dark-mode.svg") - } -} - -.color-synth .samples { - display: flex; - max-width: 100%; -} - -.color-synth .samples > * { - flex: auto; - min-width: 1px; - max-width: 8rem; - margin: 0 0 0.5rem 0.5rem; - border-radius: 0.2rem; -} - -.color-synth .samples > *:first-child { - margin-left: 0; -} - -@media (max-width: 600px) { - html { - font-size: 18px; - } - .color-synth .samples { - flex-wrap: wrap; - } - .color-synth .samples > * { - max-width: calc(50% - 0.25rem); - } - .color-synth .samples > *:nth-child(2n+1) { - margin-left: 0; - } -} diff --git a/swatchbuckler/index.templ.html b/swatchbuckler/index.templ.html @@ -0,0 +1,267 @@ +<!DOCTYPE html> +<html class="no-js"> + + <head> + @(head-content) + <link href="/manifest.json" rel="manifest" /> + <style> + html { + font-family: sans-serif; + font-size: 20px; + background: linear-gradient(#ddd, #777); + min-height: 100vh; + } + + html.no-js { + background: #ddd; + } + + html.no-js>body>header { + opacity: 1; + } + + html.no-js>body>* { + opacity: 0.5; + pointer-events: none; + } + + #no-js-message { + display: none; + background: black; + color: white; + } + + html.no-js #no-js-message { + display: block; + } + + body { + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + } + + h1, + h2, + h3, + h4, + p { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + } + + header h1 { + font-size: 1.5rem; + } + + header h2 { + font-size: 1.2rem; + } + + input, + button { + font-size: 1rem; + outline: solid 1px #bbb; + border: none; + } + + #controls { + display: flex; + flex-wrap: wrap; + } + + .margined { + margin: 0.5rem 1rem; + } + + .padded { + padding: 0.25rem; + } + + .rounded { + border-radius: 0.2rem; + } + + #thebutton { + font-size: 1rem; + } + + #renderzone { + display: flex; + flex-wrap: wrap; + margin: 0 0.75rem; + } + + .renderblock { + flex-grow: 1; + box-sizing: border-box; + min-width: 25%; + padding: 2rem 3rem; + display: flex; + flex-direction: column; + margin: 0.25rem; + } + + .renderblock button { + outline: none; + border: none; + border-radius: 0.2rem; + padding: 0.15rem 0.5rem 0.3rem; + font-size: 1rem; + width: 5rem; + margin: 0.5rem 0; + cursor: pointer; + transition: transform 0.1s; + } + + .renderblock button:active { + transform: scale(0.95); + } + + @media (max-width: 600px) { + html { + font-size: 18px; + } + + .margined { + margin: 0.5rem; + } + + #renderzone { + margin: 0.5em; + } + + .renderblock { + margin: 0.25em 0; + } + + label { + width: 100%; + } + } + </style> + <script type="module" src="js/d3-color.min.js"></script> + <script> + const INC = 0.4; + const RANGE = 4.1; + + // copied from https://gist.github.com/jfsiii/5641126 + //// from http://www.w3.org/TR/WCAG20/#relativeluminancedef + function luminance({ r, g, b }) { + var RsRGB = r / 255; + var GsRGB = g / 255; + var BsRGB = b / 255; + + var R = (RsRGB <= 0.03928) ? RsRGB / 12.92 : Math.pow((RsRGB + 0.055) / 1.055, 2.4); + var G = (GsRGB <= 0.03928) ? GsRGB / 12.92 : Math.pow((GsRGB + 0.055) / 1.055, 2.4); + var B = (BsRGB <= 0.03928) ? BsRGB / 12.92 : Math.pow((BsRGB + 0.055) / 1.055, 2.4); + + // For the sRGB colorspace, the relative luminance of a color is defined as: + var L = 0.2126 * R + 0.7152 * G + 0.0722 * B; + return L; + } + function contrast(lumA, lumB) { + if (lumA < lumB) { + let tmp = lumA; + lumA = lumB; + lumB = tmp; + } + return (lumA + 0.05) / (lumB + 0.05); + } + function makeColors() { + const parentEl = document.getElementById("renderzone"); + const colorEl = document.getElementById("midcolor") + const midColor = d3.lch(colorEl.value); + const minContrast = Number(document.getElementById("mincontrast").value); + const heading = document.getElementById("msg").value; + + console.log(midColor); + if (Number.isNaN(midColor.h)) { + colorEl.style.background = "salmon"; + function handlePress() { + colorEl.style.background = "white"; + } + colorEl.addEventListener("keydown", handlePress, { once: true }); + return; + } + + parentEl.innerHTML = ""; + for (let i = -1 * RANGE; i <= RANGE; i += INC) { + const bg = d3.lch(midColor).brighter(i).formatHex(); + if (bg == "#000000" || bg == "#ffffff") { + continue; + } + const bgLum = luminance(d3.rgb(bg)); + const sign = bgLum > 0.5 ? 1 : -1 + for (let j = sign * -1 * RANGE; sign * j <= RANGE; j += sign * INC) { + const fg = d3.lch(midColor).brighter(j).formatHex(); + const swatchContrast = contrast(luminance(d3.rgb(fg)), bgLum); + if (fg == "#000000" || fg == "#ffffff" || swatchContrast < minContrast) { + continue; + } + const el = document.createElement("div"); + el.classList.add("renderblock", "rounded"); + el.style.backgroundColor = bg; + el.style.color = fg; + el.innerHTML = `<h1>${heading}</h1> + <h2>bg: ${bg}</h2> + <h3>fg: ${fg}</h3> + <p>contrast ratio: ${swatchContrast.toPrecision(4)}</p>` + const b = document.createElement('button'); + b.innerText = "copy css"; + b.style.color = bg; + b.style.backgroundColor = fg; + b.addEventListener('click', () => { + navigator.clipboard.writeText(`background-color: ${bg};\ncolor: ${fg};`) + }) + el.appendChild(b); + parentEl.appendChild(el); + } + } + } + + document.addEventListener("DOMContentLoaded", () => { + makeColors(); + document.addEventListener("keypress", (e) => { + if (e.keyCode === 13) { + makeColors(); + } + }) + document.getElementById("thebutton").addEventListener("click", () => { + makeColors(); + }) + }) + + </script> +</head> + +<body> + <header class="margined"> + <h1>swatchbuckler 🗡</h1> + <h2>generate foreground/background pairs from a midtone</h2> + <div id="no-js-message" class="padded rounded"> + This page requires JS to function. + </div> + </header> + <div id="controls"> + <label class="margined"> + <div>starting color<br /><small>choose a midtone</small></div> + <input id="midcolor" class="padded rounded" value="steelblue" /> + </label> + <label class="margined"> + <div>minimum contrast<br /><small>for normal text, AAA is 7, AA is 4.5</small></div> + <input type="number" id="mincontrast" class="padded rounded" value="7" /> + </label> + <label class="margined"> + <div>message<br /><small>optional heading for each swatch</small></div> + <input id="msg" class="padded rounded" value="example text" /> + </label> + </div> + <div class="margined"> + <button id="thebutton" class="padded rounded">generate</button> + </div> + <div id="renderzone" class="margined"></div> +</body> + +</html> diff --git a/swatchbuckler/manifest.templ.json b/swatchbuckler/manifest.templ.json @@ -0,0 +1,11 @@ +{ + "name": "swatchbuckler", + "icons": [ + { + "src": "@(web-root)/shared-assets/icon-192-192.png", + "type": "image/png", + "sizes": "192x192" + } + ], + "start_url": "@(web-root)/index.html" +} diff --git a/swatchbuckler/package-lock.json b/swatchbuckler/package-lock.json @@ -0,0 +1,21 @@ +{ + "name": "swatchbuckler", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "d3-color": "^3.1.0" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/swatchbuckler/package.json b/swatchbuckler/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "dependencies": { + "d3-color": "^3.1.0" + } +}