diff --git a/.gitignore b/.gitignore index 3164d8b..40a6426 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ .claude -DEV.md \ No newline at end of file +DEV.md +dist/ \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..0166cd4 --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +node_modules/ +.claude +DEV.md +demo/ +dist/demo/ \ No newline at end of file diff --git a/README.md b/README.md index 649aa50..edb18b2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A lightweight, customizable solid glass effect library for modern web applications. Create Apple-like glass effects with dynamic distortion, customizable tints, and smooth animations. -![Solid Glass Demo](demo.png) +![Solid Glass Demo](https://github.com/iplanwebsites/solid-glass/blob/main/demo/demo.png?raw=true) 🚀 **[Live Demo](https://iplanwebsites.github.io/solid-glass/)** diff --git a/demo.png b/demo/demo.png similarity index 100% rename from demo.png rename to demo/demo.png diff --git a/dist/react.esm.js b/dist/react.esm.js deleted file mode 100644 index 9392174..0000000 --- a/dist/react.esm.js +++ /dev/null @@ -1,72 +0,0 @@ -import T, { useRef as l, useEffect as E } from "react"; -import { h as L, L as $ } from "./utils-BuTN5SEH.mjs"; -const z = ({ - children: g, - shadowColor: f = "rgba(255, 255, 255, 0.7)", - shadowBlur: s = 7, - shadowSpread: t = 0, - shadowOffset: n = 0, - tintColor: e = "#ffffff", - tintOpacity: r = 0.04, - frostBlur: u = 2, - noiseFrequency: m = 8e-3, - distortionStrength: p = 77, - borderRadius: x = 28, - width: G = "300px", - height: W = "200px", - className: b = "", - style: i = {}, - ...j -}) => { - const R = l(null), c = l(null); - return E(() => { - if (R.current) { - const a = { - shadowColor: f, - shadowBlur: s, - shadowSpread: t, - shadowOffset: n, - tintColor: e.startsWith("#") ? L(e) : e, - tintOpacity: r, - frostBlur: u, - noiseFrequency: m, - distortionStrength: p, - borderRadius: x - }; - c.current = new $(R.current, a); - } - return () => { - c.current && c.current.destroy(); - }; - }, []), E(() => { - if (c.current) { - const a = { - shadowColor: f, - shadowBlur: s, - shadowSpread: t, - shadowOffset: n, - tintColor: e.startsWith("#") ? L(e) : e, - tintOpacity: r, - frostBlur: u, - noiseFrequency: m, - distortionStrength: p, - borderRadius: x - }; - Object.entries(a).forEach(([v, N]) => { - c.current.updateOption(v, N); - }); - } - }, [f, s, t, n, e, r, u, m, p, x]), /* @__PURE__ */ T.createElement( - "div", - { - ref: R, - className: `solid-glass-wrapper ${b}`, - style: { width: G, height: W, ...i }, - ...j - }, - g - ); -}; -export { - z as LiquidGlassReact -}; diff --git a/dist/react.js b/dist/react.js deleted file mode 100644 index 7b1477b..0000000 --- a/dist/react.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react"),b=require("./utils-DYbhshKv.js"),v=({children:m,shadowColor:s="rgba(255, 255, 255, 0.7)",shadowBlur:u=7,shadowSpread:f=0,shadowOffset:n=0,tintColor:e="#ffffff",tintOpacity:r=.04,frostBlur:l=2,noiseFrequency:i=.008,distortionStrength:R=77,borderRadius:a=28,width:x="300px",height:E="200px",className:q="",style:G={},...L})=>{const g=t.useRef(null),c=t.useRef(null);return t.useEffect(()=>{if(g.current){const p={shadowColor:s,shadowBlur:u,shadowSpread:f,shadowOffset:n,tintColor:e.startsWith("#")?b.hexToRgb(e):e,tintOpacity:r,frostBlur:l,noiseFrequency:i,distortionStrength:R,borderRadius:a};c.current=new b.LiquidGlass(g.current,p)}return()=>{c.current&&c.current.destroy()}},[]),t.useEffect(()=>{if(c.current){const p={shadowColor:s,shadowBlur:u,shadowSpread:f,shadowOffset:n,tintColor:e.startsWith("#")?b.hexToRgb(e):e,tintOpacity:r,frostBlur:l,noiseFrequency:i,distortionStrength:R,borderRadius:a};Object.entries(p).forEach(([T,j])=>{c.current.updateOption(T,j)})}},[s,u,f,n,e,r,l,i,R,a]),t.createElement("div",{ref:g,className:`solid-glass-wrapper ${q}`,style:{width:x,height:E,...G},...L},m)};exports.LiquidGlassReact=v; diff --git a/dist/solid-glass.esm.js b/dist/solid-glass.esm.js deleted file mode 100644 index c36da23..0000000 --- a/dist/solid-glass.esm.js +++ /dev/null @@ -1,11 +0,0 @@ -import { L as r, h as e, r as i } from "./utils-BuTN5SEH.mjs"; -import { default as u } from "./vue.esm.js"; -import { LiquidGlassReact as d } from "./react.esm.js"; -import "./vanilla.esm.js"; -export { - r as LiquidGlass, - d as LiquidGlassReact, - u as LiquidGlassVue, - e as hexToRgb, - i as rgbToHex -}; diff --git a/dist/solid-glass.js b/dist/solid-glass.js deleted file mode 100644 index 986b447..0000000 --- a/dist/solid-glass.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./utils-DYbhshKv.js"),i=require("./vue.js"),s=require("./react.js");require("./vanilla.js");exports.LiquidGlass=e.LiquidGlass;exports.hexToRgb=e.hexToRgb;exports.rgbToHex=e.rgbToHex;exports.LiquidGlassVue=i;exports.LiquidGlassReact=s.LiquidGlassReact; diff --git a/dist/style.css b/dist/style.css deleted file mode 100644 index 5aadaa4..0000000 --- a/dist/style.css +++ /dev/null @@ -1 +0,0 @@ -.solid-glass-wrapper[data-v-6eef1c7e]{position:relative}.solid-glass{position:relative;isolation:isolate}.solid-glass:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;z-index:0;border-radius:var(--lg-border-radius, 28px);box-shadow:inset var(--lg-shadow-offset, 0) var(--lg-shadow-offset, 0) var(--lg-shadow-blur, 7px) var(--lg-shadow-spread, 0px) var(--lg-shadow-color, rgba(255, 255, 255, .7));background-color:rgba(var(--lg-tint-color, 255, 255, 255),var(--lg-tint-opacity, .04))}.solid-glass:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;border-radius:var(--lg-border-radius, 28px);backdrop-filter:blur(var(--lg-frost-blur, 2px));-webkit-backdrop-filter:blur(var(--lg-frost-blur, 2px));filter:var(--lg-filter-id);-webkit-filter:var(--lg-filter-id);isolation:isolate} diff --git a/dist/utils-BuTN5SEH.mjs b/dist/utils-BuTN5SEH.mjs deleted file mode 100644 index dd3f144..0000000 --- a/dist/utils-BuTN5SEH.mjs +++ /dev/null @@ -1,83 +0,0 @@ -class o { - constructor(t, e = {}) { - this.element = t, this.options = { - // Inner shadow - shadowColor: "rgba(255, 255, 255, 0.7)", - shadowBlur: 7, - shadowSpread: 0, - shadowOffset: 0, - // Glass tint - tintColor: "255, 255, 255", - tintOpacity: 0.04, - // Frost blur - frostBlur: 2, - // Noise distortion - noiseFrequency: 8e-3, - distortionStrength: 77, - // Shape - borderRadius: 28, - ...e - }, this.svgId = `glass-distortion-${Math.random().toString(36).substr(2, 9)}`, this.init(); - } - init() { - this.element.classList.add("solid-glass"), this.createSVGFilter(), this.updateStyles(); - } - createSVGFilter() { - if (!document.getElementById(this.svgId)) { - const t = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - t.setAttribute("width", "0"), t.setAttribute("height", "0"), t.style.position = "absolute", t.style.overflow = "hidden", t.innerHTML = ` - - - - - - - - `, document.body.appendChild(t); - } - } - updateStyles() { - const t = { - "--lg-shadow-color": this.options.shadowColor, - "--lg-shadow-blur": `${this.options.shadowBlur}px`, - "--lg-shadow-spread": `${this.options.shadowSpread}px`, - "--lg-shadow-offset": this.options.shadowOffset, - "--lg-tint-color": this.options.tintColor, - "--lg-tint-opacity": this.options.tintOpacity, - "--lg-frost-blur": `${this.options.frostBlur}px`, - "--lg-border-radius": `${this.options.borderRadius}px`, - "--lg-filter-id": `url(#${this.svgId})` - }; - Object.entries(t).forEach(([e, i]) => { - this.element.style.setProperty(e, i); - }); - } - updateOption(t, e) { - this.options[t] = e, (t === "noiseFrequency" || t === "distortionStrength") && this.updateSVGFilter(), this.updateStyles(); - } - updateSVGFilter() { - const t = document.getElementById(this.svgId); - if (t) { - const e = t.querySelector("feTurbulence"), i = t.querySelector("feDisplacementMap"); - e && e.setAttribute("baseFrequency", `${this.options.noiseFrequency} ${this.options.noiseFrequency}`), i && i.setAttribute("scale", this.options.distortionStrength); - } - } - destroy() { - var e; - this.element.classList.remove("solid-glass"); - const t = (e = document.getElementById(this.svgId)) == null ? void 0 : e.parentElement; - t && t.remove(); - } -} -function r(s) { - const t = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(s); - return t ? `${parseInt(t[1], 16)}, ${parseInt(t[2], 16)}, ${parseInt(t[3], 16)}` : "255, 255, 255"; -} -function n(s) { - return "#" + s.split(",").map((e) => parseInt(e.trim())).map((e) => e.toString(16).padStart(2, "0")).join(""); -} -export { - o as L, - r as h, - n as r -}; diff --git a/dist/utils-DYbhshKv.js b/dist/utils-DYbhshKv.js deleted file mode 100644 index 1dc6697..0000000 --- a/dist/utils-DYbhshKv.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict";class o{constructor(t,e={}){this.element=t,this.options={shadowColor:"rgba(255, 255, 255, 0.7)",shadowBlur:7,shadowSpread:0,shadowOffset:0,tintColor:"255, 255, 255",tintOpacity:.04,frostBlur:2,noiseFrequency:.008,distortionStrength:77,borderRadius:28,...e},this.svgId=`glass-distortion-${Math.random().toString(36).substr(2,9)}`,this.init()}init(){this.element.classList.add("solid-glass"),this.createSVGFilter(),this.updateStyles()}createSVGFilter(){if(!document.getElementById(this.svgId)){const t=document.createElementNS("http://www.w3.org/2000/svg","svg");t.setAttribute("width","0"),t.setAttribute("height","0"),t.style.position="absolute",t.style.overflow="hidden",t.innerHTML=` - - - - - - - - `,document.body.appendChild(t)}}updateStyles(){const t={"--lg-shadow-color":this.options.shadowColor,"--lg-shadow-blur":`${this.options.shadowBlur}px`,"--lg-shadow-spread":`${this.options.shadowSpread}px`,"--lg-shadow-offset":this.options.shadowOffset,"--lg-tint-color":this.options.tintColor,"--lg-tint-opacity":this.options.tintOpacity,"--lg-frost-blur":`${this.options.frostBlur}px`,"--lg-border-radius":`${this.options.borderRadius}px`,"--lg-filter-id":`url(#${this.svgId})`};Object.entries(t).forEach(([e,i])=>{this.element.style.setProperty(e,i)})}updateOption(t,e){this.options[t]=e,(t==="noiseFrequency"||t==="distortionStrength")&&this.updateSVGFilter(),this.updateStyles()}updateSVGFilter(){const t=document.getElementById(this.svgId);if(t){const e=t.querySelector("feTurbulence"),i=t.querySelector("feDisplacementMap");e&&e.setAttribute("baseFrequency",`${this.options.noiseFrequency} ${this.options.noiseFrequency}`),i&&i.setAttribute("scale",this.options.distortionStrength)}}destroy(){var e;this.element.classList.remove("solid-glass");const t=(e=document.getElementById(this.svgId))==null?void 0:e.parentElement;t&&t.remove()}}function r(s){const t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(s);return t?`${parseInt(t[1],16)}, ${parseInt(t[2],16)}, ${parseInt(t[3],16)}`:"255, 255, 255"}function n(s){return"#"+s.split(",").map(e=>parseInt(e.trim())).map(e=>e.toString(16).padStart(2,"0")).join("")}exports.LiquidGlass=o;exports.hexToRgb=r;exports.rgbToHex=n; diff --git a/dist/vanilla.esm.js b/dist/vanilla.esm.js deleted file mode 100644 index 048ef26..0000000 --- a/dist/vanilla.esm.js +++ /dev/null @@ -1,6 +0,0 @@ -import { L as o, h as r, r as e } from "./utils-BuTN5SEH.mjs"; -export { - o as LiquidGlass, - r as hexToRgb, - e as rgbToHex -}; diff --git a/dist/vanilla.js b/dist/vanilla.js deleted file mode 100644 index bae22ae..0000000 --- a/dist/vanilla.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./utils-DYbhshKv.js");exports.LiquidGlass=e.LiquidGlass;exports.hexToRgb=e.hexToRgb;exports.rgbToHex=e.rgbToHex; diff --git a/dist/vue.esm.js b/dist/vue.esm.js deleted file mode 100644 index b946caa..0000000 --- a/dist/vue.esm.js +++ /dev/null @@ -1,104 +0,0 @@ -import { createElementBlock as i, openBlock as u, normalizeStyle as f, renderSlot as c, ref as h, computed as s, onMounted as y, onUnmounted as m, watch as w } from "vue"; -import { h as p, L as g } from "./utils-BuTN5SEH.mjs"; -const b = (t, r) => { - const e = t.__vccOpts || t; - for (const [o, a] of r) - e[o] = a; - return e; -}, S = { - name: "LiquidGlass", - props: { - shadowColor: { - type: String, - default: "rgba(255, 255, 255, 0.7)" - }, - shadowBlur: { - type: Number, - default: 7 - }, - shadowSpread: { - type: Number, - default: 0 - }, - shadowOffset: { - type: Number, - default: 0 - }, - tintColor: { - type: String, - default: "#ffffff" - }, - tintOpacity: { - type: Number, - default: 0.04 - }, - frostBlur: { - type: Number, - default: 2 - }, - noiseFrequency: { - type: Number, - default: 8e-3 - }, - distortionStrength: { - type: Number, - default: 77 - }, - borderRadius: { - type: Number, - default: 28 - }, - width: { - type: String, - default: "300px" - }, - height: { - type: String, - default: "200px" - } - }, - setup(t) { - const r = h(null); - let e = null; - const o = s(() => ({ - width: t.width, - height: t.height - })), a = s(() => ({ - shadowColor: t.shadowColor, - shadowBlur: t.shadowBlur, - shadowSpread: t.shadowSpread, - shadowOffset: t.shadowOffset, - tintColor: t.tintColor.startsWith("#") ? p(t.tintColor) : t.tintColor, - tintOpacity: t.tintOpacity, - frostBlur: t.frostBlur, - noiseFrequency: t.noiseFrequency, - distortionStrength: t.distortionStrength, - borderRadius: t.borderRadius - })); - return y(() => { - r.value && (e = new g(r.value, a.value)); - }), m(() => { - e && e.destroy(); - }), w(a, (n) => { - e && Object.entries(n).forEach(([d, l]) => { - e.updateOption(d, l); - }); - }, { deep: !0 }), { - glassElement: r, - wrapperStyles: o - }; - } -}; -function _(t, r, e, o, a, n) { - return u(), i("div", { - ref: "glassElement", - class: "solid-glass-wrapper", - style: f(o.wrapperStyles) - }, [ - c(t.$slots, "default", {}, void 0, !0) - ], 4); -} -const C = /* @__PURE__ */ b(S, [["render", _], ["__scopeId", "data-v-6eef1c7e"]]); -export { - C as default -}; diff --git a/dist/vue.js b/dist/vue.js deleted file mode 100644 index 1393944..0000000 --- a/dist/vue.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";const e=require("vue"),d=require("./utils-DYbhshKv.js"),i=(t,o)=>{const r=t.__vccOpts||t;for(const[a,n]of o)r[a]=n;return r},f={name:"LiquidGlass",props:{shadowColor:{type:String,default:"rgba(255, 255, 255, 0.7)"},shadowBlur:{type:Number,default:7},shadowSpread:{type:Number,default:0},shadowOffset:{type:Number,default:0},tintColor:{type:String,default:"#ffffff"},tintOpacity:{type:Number,default:.04},frostBlur:{type:Number,default:2},noiseFrequency:{type:Number,default:.008},distortionStrength:{type:Number,default:77},borderRadius:{type:Number,default:28},width:{type:String,default:"300px"},height:{type:String,default:"200px"}},setup(t){const o=e.ref(null);let r=null;const a=e.computed(()=>({width:t.width,height:t.height})),n=e.computed(()=>({shadowColor:t.shadowColor,shadowBlur:t.shadowBlur,shadowSpread:t.shadowSpread,shadowOffset:t.shadowOffset,tintColor:t.tintColor.startsWith("#")?d.hexToRgb(t.tintColor):t.tintColor,tintOpacity:t.tintOpacity,frostBlur:t.frostBlur,noiseFrequency:t.noiseFrequency,distortionStrength:t.distortionStrength,borderRadius:t.borderRadius}));return e.onMounted(()=>{o.value&&(r=new d.LiquidGlass(o.value,n.value))}),e.onUnmounted(()=>{r&&r.destroy()}),e.watch(n,s=>{r&&Object.entries(s).forEach(([u,l])=>{r.updateOption(u,l)})},{deep:!0}),{glassElement:o,wrapperStyles:a}}};function c(t,o,r,a,n,s){return e.openBlock(),e.createElementBlock("div",{ref:"glassElement",class:"solid-glass-wrapper",style:e.normalizeStyle(a.wrapperStyles)},[e.renderSlot(t.$slots,"default",{},void 0,!0)],4)}const h=i(f,[["render",c],["__scopeId","data-v-6eef1c7e"]]);module.exports=h; diff --git a/package-lock.json b/package-lock.json index 53553d2..7ec3c03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "solid-glass", - "version": "1.0.0", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "solid-glass", - "version": "1.0.0", + "version": "0.0.2", "license": "MIT", "devDependencies": { "@vitejs/plugin-vue": "^5.0.4", diff --git a/package.json b/package.json index a355eb4..d8c65e6 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ }, "scripts": { "dev": "vite", + "dev:demo": "vite --mode demo", "build": "vite build", "build:demo": "vite build --mode demo", "preview": "vite preview" diff --git a/src/core/LiquidGlass.js b/src/core/LiquidGlass.js index 9749a6b..8ca4b35 100644 --- a/src/core/LiquidGlass.js +++ b/src/core/LiquidGlass.js @@ -1,4 +1,6 @@ export class LiquidGlass { + static shouldUseAlternativeFallback; + constructor(element, options = {}) { this.element = element; this.options = { @@ -26,12 +28,22 @@ export class LiquidGlass { }; this.svgId = `glass-distortion-${Math.random().toString(36).substr(2, 9)}`; + + if (LiquidGlass.shouldUseAlternativeFallback === undefined) { + LiquidGlass.shouldUseAlternativeFallback = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; + } + this.init(); } init() { - this.element.classList.add('solid-glass'); - this.createSVGFilter(); + if (LiquidGlass.shouldUseAlternativeFallback) { + this.element.classList.add('solid-glass-alternative'); + } else { + this.element.classList.add('solid-glass'); + this.createSVGFilter(); + } + this.updateStyles(); } @@ -102,7 +114,7 @@ export class LiquidGlass { } destroy() { - this.element.classList.remove('solid-glass'); + this.element.classList.remove('solid-glass', 'solid-glass-alternative'); const svg = document.getElementById(this.svgId)?.parentElement; if (svg) { svg.remove(); diff --git a/src/styles/solid-glass.css b/src/styles/solid-glass.css index 8d9cba3..bc842c1 100644 --- a/src/styles/solid-glass.css +++ b/src/styles/solid-glass.css @@ -24,4 +24,27 @@ filter: var(--lg-filter-id); -webkit-filter: var(--lg-filter-id); isolation: isolate; -} \ No newline at end of file +} + +.solid-glass-alternative { + position: relative; + isolation: isolate; + backdrop-filter: blur(var(--lg-frost-blur, 2px)); + -webkit-backdrop-filter: blur(var(--lg-frost-blur, 2px)); + border-radius: var(--lg-border-radius, 28px); +} + +.solid-glass-alternative::before { + content: ''; + position: absolute; + inset: 0; + z-index: 0; + border-radius: var(--lg-border-radius, 28px); + box-shadow: inset var(--lg-shadow-offset, 0) var(--lg-shadow-offset, 0) + calc(var(--lg-shadow-blur, 7px) / 2) var(--lg-shadow-spread, 0px) + var(--lg-shadow-color, rgba(255, 255, 255, 0.7)); + background-color: rgba( + var(--lg-tint-color, 255, 255, 255), + var(--lg-tint-opacity, 0.04) + ); +}