diff --git a/assets/login/resources/css/custom.css b/assets/login/resources/css/custom.css new file mode 100644 index 0000000..6eb553b --- /dev/null +++ b/assets/login/resources/css/custom.css @@ -0,0 +1,70 @@ +/* Overrides for the keycloak.v2 base theme (loaded first via theme.properties' + `styles` list) - keep using its tokens/selectors instead of re-implementing + layout, since that's what actually sizes/positions the login card. */ + +/* keycloak.v2 draws a top accent border on the card using this token. */ +:root { + --keycloak-card-top-color: #6d28d9; +} + +/* Real selector confirmed from the rendered page: . + A bare `body` rule loses to keycloak.v2's `.login-pf body` by specificity, + so this needs the id to actually win. */ +#keycloak-bg { + /* bg-picker.js sets --login-bg-image on daily; the var() fallback + below is what shows if JS doesn't run. A local declaration here would + shadow the inherited value regardless of what JS sets, so don't add one. */ + background: linear-gradient(rgba(15, 23, 42, 0.55), rgba(15, 23, 42, 0.55)), + var(--login-bg-image, url("../img/background.svg")) center center / cover no-repeat fixed; + font-family: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif; +} + +#kc-header-wrapper { + font-weight: 600; + text-shadow: 0 2px 10px rgba(0, 0, 0, 0.45); +} + +/* PatternFly draws the login card's background/shadow on .pf-v5-c-login__main + itself via these two custom properties - override the tokens rather than + fighting the selector. */ +.pf-v5-c-login__main { + --pf-v5-c-login__main--BackgroundColor: rgba(255, 255, 255, 0.92); + --pf-v5-c-login__main--BoxShadow: 0 20px 60px rgba(0, 0, 0, 0.35); + backdrop-filter: blur(10px); + border-radius: 16px; + overflow: hidden; +} + +@media (prefers-color-scheme: dark) { + .pf-v5-c-login__main { + --pf-v5-c-login__main--BackgroundColor: rgba(15, 23, 42, 0.82); + } +} + +/* #kc-login is the actual submit button's id - safe to target directly + without fighting PatternFly's own hover/focus custom-property chain. + Shares the same dynamic accent as the card's top border, so the two + change together as bg-picker.js recomputes it per background image. */ +#kc-login { + background-color: var(--keycloak-card-top-color, #4338ca); + border-radius: 8px; + transition: filter 0.2s ease; +} + +#kc-login:hover, +#kc-login:focus { + filter: brightness(0.85); +} + +.pf-v5-c-form-control, +.pf-v5-c-form-control input { + border-radius: 8px; +} + +.pf-v5-c-input-group .pf-v5-c-form-control { + border-radius: 8px 0 0 8px; +} + +.pf-v5-c-input-group .pf-v5-c-button.pf-m-control { + border-radius: 0 8px 8px 0; +} diff --git a/assets/login/resources/css/styles.css b/assets/login/resources/css/styles.css deleted file mode 100644 index c9431e8..0000000 --- a/assets/login/resources/css/styles.css +++ /dev/null @@ -1,39 +0,0 @@ -body { - background: linear-gradient(rgba(15, 23, 42, 0.45), rgba(15, 23, 42, 0.45)), - url("../img/background.svg") center center / cover no-repeat fixed; - font-family: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif; -} - -/* Keycloak's built-in theme markup/classes vary by version, so several - selectors are listed as best-effort hooks for the login card - inspect - the rendered page and adjust whichever one actually matches. */ -.login-pf-page .card-pf, -#kc-form-wrapper, -.pf-c-login__main-body, -.pf-v5-c-login__main-body { - background: rgba(255, 255, 255, 0.92); - backdrop-filter: blur(12px); - border-radius: 16px; - border: none; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35); -} - -#kc-header-wrapper, -.pf-c-login__header, -.pf-v5-c-login__header { - color: #fff; - text-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); -} - -.btn-primary, -.pf-c-button.pf-m-primary, -.pf-v5-c-button.pf-m-primary { - border-radius: 8px; - transition: background-color 0.2s ease; -} - -input.form-control, -.pf-c-form-control, -.pf-v5-c-form-control { - border-radius: 8px; -} diff --git a/assets/login/resources/js/bg-picker.js b/assets/login/resources/js/bg-picker.js new file mode 100644 index 0000000..adb824f --- /dev/null +++ b/assets/login/resources/js/bg-picker.js @@ -0,0 +1,112 @@ +(function () { + // Add new filenames here as they're uploaded to resources/img/. + var IMAGES = ["1.jpg"]; + + var epochDay = Math.floor(Date.now() / 86400000); + var chosen = IMAGES[epochDay % IMAGES.length]; + var url = new URL("../img/" + chosen, document.currentScript.src).href; + + document.documentElement.style.setProperty("--login-bg-image", "url(" + JSON.stringify(url) + ")"); + + // Derive the login card's accent color (--keycloak-card-top-color, the top + // border strip) from whichever background photo was picked above, instead + // of hard-coding one color that only suits a single image. + var img = new Image(); + img.crossOrigin = "anonymous"; + img.onload = function () { + var accent = dominantAccentColor(img); + if (accent) { + document.documentElement.style.setProperty("--keycloak-card-top-color", accent); + } + }; + img.src = url; + + function dominantAccentColor(img) { + var canvas = document.createElement("canvas"); + var w = 100; + var h = Math.max(1, Math.round((img.naturalHeight / img.naturalWidth) * w)); + canvas.width = w; + canvas.height = h; + var ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0, w, h); + + var data; + try { + data = ctx.getImageData(0, 0, w, h).data; + } catch (e) { + return null; // canvas tainted (image not same-origin / no CORS headers) + } + + // Bucket pixels by hue. A large, pale, uniform area (sky) still adds up + // to a bigger SUM of vividness than a smaller, richly-colored subject + // (lavender rows), so summing alone still lets area win. Score each bin + // by its MEAN vividness instead - that measures "how saturated is this + // hue, typically" rather than "how much area does this hue cover" - with + // a minimum-population floor so a handful of stray outlier pixels can't + // win by having a freakishly high average. + var HUE_BINS = 36; // 10 degrees each + var binCount = new Array(HUE_BINS).fill(0); + var binScore = new Array(HUE_BINS).fill(0); + var binR = new Array(HUE_BINS).fill(0); + var binG = new Array(HUE_BINS).fill(0); + var binB = new Array(HUE_BINS).fill(0); + var totalPixels = 0; + + for (var i = 0; i < data.length; i += 4) { + var r = data[i], g = data[i + 1], b = data[i + 2]; + totalPixels++; + var hsl = rgbToHsl(r, g, b); + if (hsl.s < 0.12) continue; // skip near-greys/whites/blacks + + var lightnessWeight = 1 - Math.abs(hsl.l - 0.5) * 2; + var score = hsl.s * lightnessWeight; + if (score <= 0) continue; + + var bin = Math.floor(hsl.h / (360 / HUE_BINS)) % HUE_BINS; + binCount[bin]++; + binScore[bin] += score; + binR[bin] += r * score; + binG[bin] += g * score; + binB[bin] += b * score; + } + + var minPopulation = totalPixels * 0.02; // ignore hues covering <2% of the image + var best = -1; + var bestMean = 0; + for (var j = 0; j < HUE_BINS; j++) { + if (binCount[j] < minPopulation) continue; + var mean = binScore[j] / binCount[j]; + if (mean > bestMean) { + bestMean = mean; + best = j; + } + } + if (best === -1) return null; + + var r2 = Math.round(binR[best] / binScore[best]); + var g2 = Math.round(binG[best] / binScore[best]); + var b2 = Math.round(binB[best] / binScore[best]); + return "rgb(" + r2 + ", " + g2 + ", " + b2 + ")"; + } + + function rgbToHsl(r, g, b) { + r /= 255; + g /= 255; + b /= 255; + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2; + if (max === min) { + h = s = 0; + } else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + default: h = (r - g) / d + 4; break; + } + h *= 60; + } + return { h: h, s: s, l: l }; + } +})(); diff --git a/assets/login/theme.properties b/assets/login/theme.properties index 0a8633f..5494d10 100644 --- a/assets/login/theme.properties +++ b/assets/login/theme.properties @@ -1,2 +1,3 @@ parent=keycloak.v2 -styles=css/styles.css +styles=css/styles.css css/custom.css +scripts=js/bg-picker.js diff --git a/dev/preview.html b/dev/preview.html new file mode 100644 index 0000000..c540705 --- /dev/null +++ b/dev/preview.html @@ -0,0 +1,176 @@ + + + + + + + + + + Sign in to Vilanet (local preview) + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+ + diff --git a/dev/vendor/fa-solid-900.woff2 b/dev/vendor/fa-solid-900.woff2 new file mode 100644 index 0000000..f0b9b0c Binary files /dev/null and b/dev/vendor/fa-solid-900.woff2 differ