commit c82a789d625faa7eb609116068020217640406ec Author: Filip Znachor Date: Thu Oct 5 13:41:55 2023 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e985853 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vercel diff --git a/demo/demo.css b/demo/demo.css new file mode 100644 index 0000000..4174734 --- /dev/null +++ b/demo/demo.css @@ -0,0 +1,32 @@ +html {background: var(--synergy-bg);} +html, body {color: var(--synergy-text-color); font-family: Cantarell, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Open Sans, Helvetica Neue, Arial, Noto Sans, Roboto, sans-serif; font-size: 18px; overflow-x: hidden;} + +*, *::before, *::after {box-sizing: border-box;} + +.content {padding: 20px; margin: auto; max-width: 1000px;} + +.grid {display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: center;} +@media screen and (max-width: 700px) { + .grid {grid-template-columns: 1fr; max-width: 500px;} +} +.form {display: flex; flex-direction: column; gap: 20px;} + +header {margin-bottom: 70px; text-align: center;} +header > * {margin: 30px 0;} +header h1 {font-size: 50px;} +header h1 .color {background-clip: text; -webkit-background-clip: text; background-image: linear-gradient(10deg, var(--synergy-border-active), var(--synergy-border)); color: transparent;} + +.colorselector {display: flex; gap: 10px; justify-content: center;} +.colorselector > * {border: 0; padding: 0; cursor: pointer; width: 50px; height: 50px; border-radius: 50%; overflow: hidden; box-shadow: 0 1px 3px #0004;} +::-webkit-color-swatch, ::-moz-color-swatch {border: 0;} +.colorselector div {display: flex; align-items: center; justify-content: center; box-shadow: 0 1px 3px #0004, inset 0 0 0 2px var(--synergy-border);} +.colorselector svg {width: 30px;} + +.settings {position: fixed; right: -600px; top: 0; width: 90%; max-width: 400px; height: 100%; box-shadow: 0 0 0 5px var(--synergy-border-active); border-radius: 40px 0 0 40px; padding: 40px; transition: all .3s; display: flex; flex-direction: column; gap: 15px; z-index: 100; background-color: var(--synergy-bg);} +.settings.open {right: 0;} + +.presets {display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px;} +.presets > * {height: 50px; border-radius: var(--synergy-border-radius); box-shadow: 0 1px 3px #0004; display: flex; overflow: hidden; cursor: pointer;} +.presets > * > * {flex: 1;} + +.settings .close {width: 64px; height: 64px; cursor: pointer; padding: 20px; position: absolute; right: 0; top: 0; z-index: 10;} \ No newline at end of file diff --git a/demo/demo.js b/demo/demo.js new file mode 100644 index 0000000..781bcd5 --- /dev/null +++ b/demo/demo.js @@ -0,0 +1,193 @@ +var style = document.createElement("style"); + +addEventListener("load", () => { + + let cs = document.querySelectorAll(".colorselector > *"); + cs.forEach(c => c.addEventListener("change", updateColors)); + document.querySelector("head").appendChild(style); + updateColors(); + showPresets(); + const p = presets[Math.floor(Math.random() * presets.length)]; + applyPreset(p); + + document.querySelector(".colorselector .config").addEventListener("click", toggleSettings); + document.querySelector(".settings .close").addEventListener("click", toggleSettings); + +}); + +function updateColors() { + let cs = document.querySelectorAll(".colorselector > *"); + let p = {main: cs[0].value, text: cs[1].value, bg: cs[2].value}; + console.log(p); + let t = new Theme(p); + t.apply(); +} + +function toggleSettings() { + document.querySelector(".settings").classList.toggle("open"); +} + +let presets = [ + {main: "#2ebdf5", text: "#ffffff", bg: "#040813"}, + {main: "#f5b62e", text: "#ffffff", bg: "#040813"}, + {main: "#FF6565", text: "#ffffff", bg: "#0f0413"}, + {main: "#9b8fe4", text: "#cfcef4", bg: "#090818", siteBg: "#100E22"}, + {main: "#337e2c", text: "#031601", bg: "#f3f7f2"}, + {main: "#1c71d8", text: "#030e1c", bg: "#ffffff"}, + {main: "#9141ac", text: "#613583", bg: "#f6edf7"}, + {main: "#a51d2d", text: "#3d3846", bg: "#f1e9e8"}, + {main: "#865e3c", text: "#63452c", bg: "#f9f7f4", siteBg: "#ffffff"} +]; + +function showPresets() { + let presetsEl = document.querySelector(".presets"); + presets.forEach(p => { + let el = document.createElement("div"); + el.innerHTML = ` +
+
+
`; + el.addEventListener("click", () => { + applyPreset(p); + }); + presetsEl.appendChild(el); + }); +} + +function applyPreset(p) { + let cs = document.querySelectorAll(".colorselector > *"); + cs[0].value = p.main; + cs[1].value = p.text; + cs[2].value = p.bg; + updateColors(); + let html = document.querySelector("html"); + html.style = ""; + if(p.siteBg) html.style.background = p.siteBg; +} + +class Color { + + r; + g; + b; + a; + + constructor(r, g, b, a = 1) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + clone() { + return new Color(this.r, this.g, this.b, this.a); + } + + static fromHex(hex) { + return new Color(...this.hexToRgb(hex)); + } + + static hexToRgb(hex) { + hex = hex.replace(/^#/, ''); + const r = parseInt(hex.slice(0, 2), 16) / 255; + const g = parseInt(hex.slice(2, 4), 16) / 255; + const b = parseInt(hex.slice(4, 6), 16) / 255; + return [r, g, b]; + } + + rgbFormat() { + return `rgba(${this.r*255}, ${this.g*255}, ${this.b*255}, ${this.a})`; + } + + contrast(otherColor) { + const getRelativeLuminance = (rgb) => { + const sRGB = rgb / 255; + return sRGB <= 0.03928 ? sRGB / 12.92 : Math.pow((sRGB + 0.055) / 1.055, 2.4); + }; + const luminance1 = getRelativeLuminance(this.r) * 0.2126 + + getRelativeLuminance(this.g) * 0.7152 + + getRelativeLuminance(this.b) * 0.0722; + const luminance2 = getRelativeLuminance(otherColor.r) * 0.2126 + + getRelativeLuminance(otherColor.g) * 0.7152 + + getRelativeLuminance(otherColor.b) * 0.0722; + const contrastRatio = (Math.max(luminance1, luminance2) + 0.05) / (Math.min(luminance1, luminance2) + 0.05); + return (contrastRatio*100)-100; + } + + equals(otherColor) { + return this.r == otherColor.r && this.g == otherColor.g && this.b == otherColor.b && this.a == otherColor.a; + } + +} + +class Theme { + + main; + text; + bg; + + constructor(opt) { + + this.main = Color.fromHex(opt.main); + this.text = Color.fromHex(opt.text); + this.bg = Color.fromHex(opt.bg); + + } + + apply() { + style.innerHTML = this.generate(); + } + + generate() { + + let variables = []; + + variables.push(this.var("border", this.cArgb(this.main, .4))); + variables.push(this.var("border-active", this.cArgb(this.main))); + + variables.push(this.var("label", this.cArgb(this.main, .8))); + variables.push(this.var("label-active", this.cArgb(this.main))); + + variables.push(this.var("btn-primary-bg", this.cArgb(this.main, .8))); + variables.push(this.var("btn-primary-bg-active", this.cArgb(this.main, .5))); + variables.push(this.var("btn-primary-bg-hover", this.cArgb(this.main))); + + let btnBg = new Color(.6, .6, .6); + variables.push(this.var("btn-bg", this.cArgb(btnBg, .3))); + variables.push(this.var("btn-bg-active", this.cArgb(btnBg, .6))); + variables.push(this.var("btn-bg-hover", this.cArgb(btnBg, .4))); + + variables.push(this.var("text-color", this.text.rgbFormat())); + variables.push(this.var("bg", this.bg.rgbFormat())); + + if(this.main.contrast(this.bg) < .3) alert("Contrast between main color and the background is low!"); + if(this.bg.contrast(this.text) < .3) alert("Contrast between text color and the background is low!"); + + let styles = [`:root {${variables.join("")}}`]; + + let btnColor = this.getBtnColor(this.main, this.text); + if(btnColor != this.text) styles.push(`.btn.btn-primary {${this.var("text-color", btnColor.rgbFormat())}}`); + + return styles.join("\n"); + + } + + getBtnColor(main, text) { + let white = new Color(1, 1, 1); + let black = new Color(0, 0, 0); + let cText = main.contrast(text); + let cWhite = main.contrast(white); + return cWhite > .3 ? white : cText > .3 ? text : black; + } + + cArgb(color, alpha = 1) { + let c = color.clone(); + c.a = alpha; + return c.rgbFormat(); + } + + var(name, value) { + return `--synergy-${name}: ${value};`; + } + +} \ No newline at end of file diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..bd04279 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,91 @@ + + + + Synergy UI + + + + + + +
+ +
+

+ Synergy UI +

+

Simple framework with CSS-only UI components

+
+ + + +
+ +
+
+
+ + + +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+ +
+ +
+ + + \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..7caae6b --- /dev/null +++ b/style.css @@ -0,0 +1,221 @@ +:root { + + --synergy-bg: rgba(249, 247, 244, 1); + + --synergy-border: rgba(134, 94, 60, 0.4); + --synergy-border-active: rgba(134, 94, 60, 1); + --synergy-border-width: 2px; + --synergy-border-radius: 0.375rem; + + --synergy-label: rgba(134, 94, 60, 0.8); + --synergy-label-active: rgba(134, 94, 60, 1); + + --synergy-text-color: rgba(99, 69, 44, 1); + + --synergy-btn-primary-bg: rgba(134, 94, 60, 0.8); + --synergy-btn-primary-bg-hover: rgba(134, 94, 60, 1); + --synergy-btn-primary-bg-active: rgba(134, 94, 60, 0.5); + + --synergy-btn-bg: rgba(153, 153, 153, 0.3); + --synergy-btn-bg-hover: rgba(153, 153, 153, 0.4); + --synergy-btn-bg-active: rgba(153, 153, 153, 0.6); + +} + +.btn.btn-primary {--synergy-text-color: rgba(255, 255, 255, 1);} + +/* +INPUTS +*/ + +.inp { + position: relative; + width: 100%; +} + +/* Label */ + +.inp label { + position: absolute; + top: 11px; + left: 7px; + font-size: 1rem; + font-weight: 700; + transform-origin: 0 0; + transform: translate3d(0, 0, 0); + transition: all .2s ease; + pointer-events: none; + background: var(--synergy-bg); + padding: 3px 8px; + border-radius: 10px; + line-height: 1; + color: var(--synergy-label); +} + +.inp :is( + :is(input, textarea, select):not(:placeholder-shown) + label, + :is(input, textarea):not(:-ms-input-placeholder) + label, + :is(input, textarea):not(:-moz-placeholder-shown) + label +), +.inp :is(input, textarea):focus + label { + color: var(--synergy-label-active); + transform: translate3d(0, -21px, 0) scale(.8); +} + +/* Common styles */ + +.inp :is(input, textarea, select) { + display: block; + width: 100%; + min-height: 47px; + font-family: inherit; + padding: 10px 12px; + font-size: 1rem; + font-weight: 500; + color: var(--synergy-text-color); + transition: border-color 0.15s ease; + border-radius: var(--synergy-border-radius); + border: var(--synergy-border-width) solid var(--synergy-border); + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .1); + background: var(--synergy-bg); + margin: 0px; +} + +.inp :is(input, textarea, select):focus { + outline: none; + border-color: var(--synergy-border-active); +} + +/* Specific styles */ + +.inp.select select { + appearance: none; + padding-right: 40px; +} + +.inp.select::after { + content: ""; + border: 1px solid var(--synergy-text-color); + border-top: 0; + border-left: 0; + display: block; + position: absolute; + top: 50%; + margin-top: -8px; + right: 15px; + padding: 5px; + transform: rotate(45deg); + pointer-events: none; +} + +.inp textarea { + height: 100px; + max-width: 100%; + resize: vertical; +} + + +/* +TOGGLE +*/ + +.toggle { + border: var(--synergy-border-width) solid var(--synergy-border); + background-color: var(--synergy-bg); + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .1); + border-radius: 100px; + padding: 3px; + display: flex; + max-width: 60px; + height: 30px; + width: 100%; + position: relative; +} + +.toggle input { + opacity: 0; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + cursor: pointer; + z-index: 100; +} + +.toggle .indicator { + transition: all .2s; + display: flex; + height: 100%; +} + +.toggle .indicator::after { + transition: all .2s; + margin-left: auto; + display: inline-block; + height: 100%; + aspect-ratio: 1 / 1; + background-color: var(--synergy-border); + border-radius: 100%; + content: ""; +} + +.toggle input:checked + .indicator { + flex: 1; +} + +.toggle input:checked + .indicator::after { + background-color: var(--synergy-border-active); +} + +/* Toggle with label */ + +.toggle-text { + display: grid; + align-items: center; + gap: 20px; + grid-template-columns: 1fr 50px; +} + +.toggle-text label { + cursor: pointer; +} + + +/* +BUTTON +*/ + +.btn-row { + display: flex; + flex-wrap: wrap; + gap: 15px; +} + +.btn { + background-color: var(--synergy-btn-bg); + padding: 12px 20px; + border: 0; + color: var(--synergy-text-color); + font: inherit; + font-weight: 700; + font-size: 1rem; + border-radius: var(--synergy-border-radius); + transition: .2s background-color; + cursor: pointer; +} + +.btn:hover { + background-color: var(--synergy-btn-bg-hover); +} + +.btn:active { + background-color: var(--synergy-btn-bg-active); +} + +.btn.btn-primary { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .1); + --synergy-btn-bg: var(--synergy-btn-primary-bg); + --synergy-btn-bg-hover: var(--synergy-btn-primary-bg-hover); + --synergy-btn-bg-active: var(--synergy-btn-primary-bg-active); +}