diff --git a/package.json b/package.json index fe286f2..2b41eb2 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "ajv": "^8.6.3", "bent": "^7.3.12", "body-parser": "^1.19.0", - "doipjs": "^1.2.4", + "colorjs.io": "^0.4.5", + "doipjs": "^1.2.5", "dotenv": "^16.0.3", "express": "^4.17.1", "express-http-context2": "^1.0.0", diff --git a/src/routes/profile.js b/src/routes/profile.js index f03e2c4..431c125 100644 --- a/src/routes/profile.js +++ b/src/routes/profile.js @@ -32,7 +32,7 @@ import bodyParserImport from 'body-parser' import { rateLimit } from 'express-rate-limit' import { generateSignatureProfile, utils, generateWKDProfile, generateHKPProfile, generateAutoProfile, generateKeybaseProfile } from '../server/index.js' import { Profile } from 'doipjs' -import { getMetaFromReq } from '../server/utils.js' +import { generateProfileTheme, getMetaFromReq } from '../server/utils.js' import logger from '../log.js' const router = express.Router() @@ -133,6 +133,7 @@ router.get('/keybase/:username/:fingerprint', profileRateLimiter, async (req, re router.get('/:id', profileRateLimiter, async (req, res) => { const data = await generateAutoProfile(req.params.id) + const theme = generateProfileTheme(data) const title = utils.generatePageTitle('profile', data) res.set('ariadne-identity-proof', data.identifier) res.render('profile', { @@ -140,6 +141,7 @@ router.get('/:id', profileRateLimiter, async (req, res) => { data: data instanceof Profile ? data.toJSON() : data, enable_message_encryption: false, enable_signature_verification: false, + theme, meta: getMetaFromReq(req) }) }) diff --git a/src/server/utils.js b/src/server/utils.js index 6ca15ec..a4583ac 100644 --- a/src/server/utils.js +++ b/src/server/utils.js @@ -28,6 +28,8 @@ if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . */ import { webcrypto as crypto } from 'crypto' +import { Profile } from 'doipjs' +import Color from 'colorjs.io' export async function computeWKDLocalPart (localPart) { const localPartEncoded = new TextEncoder().encode(localPart.toLowerCase()) @@ -87,3 +89,51 @@ export function getMetaFromReq (req) { } } } + +export function generateProfileTheme (/** @type {Profile} */ profile) { + if (!(profile && profile instanceof Profile)) return null + + if (!profile.personas[profile.primaryPersonaIndex].themeColor) return null + + let base + try { + base = new Color(profile.personas[profile.primaryPersonaIndex].themeColor) + } catch (_) { + return null + } + + if (base.to('hsl').hsl[0].isNaN) return null + if (base.to('hsl').hsl[2] === 0) return null + + const primaryLight = base.to('hsl') + primaryLight.hsl[2] = 40 + const primaryDark = base.to('hsl') + primaryDark.hsl[2] = 80 + + const primarySubtleLight = base.to('hsl') + primarySubtleLight.hsl[2] = 50 + const primarySubtleDark = base.to('hsl') + primarySubtleDark.hsl[2] = 70 + + const backgroundLight = base.to('hsl') + backgroundLight.hsl[2] = 98 + const backgroundDark = base.to('hsl') + backgroundDark.hsl[1] = 20 + backgroundDark.hsl[2] = 5 + + return { + base: base.toString({ format: 'hex' }), + primary: { + light: primaryLight.toString(), + dark: primaryDark.toString() + }, + primarySubtle: { + light: primarySubtleLight.toString(), + dark: primarySubtleDark.toString() + }, + background: { + light: backgroundLight.toString(), + dark: backgroundDark.toString() + } + } +} diff --git a/static-src/kx-styles.scss b/static-src/kx-styles.scss index 41291a6..d9e08b3 100644 --- a/static-src/kx-styles.scss +++ b/static-src/kx-styles.scss @@ -58,8 +58,9 @@ kx-claim { } hr { + margin: 8px 0; border: none; - border-top: 2px solid var(--background-color); + border-top: 2px solid var(--header-background-color); } .content { diff --git a/static-src/styles/forms.scss b/static-src/styles/forms.scss index 7169ad9..835abf3 100644 --- a/static-src/styles/forms.scss +++ b/static-src/styles/forms.scss @@ -108,6 +108,18 @@ a.button { border-color: var(--button-border-color-hover); color: var(--button-text-color-hover); } + + &.themed { + padding: 8px 12px; + color: var(--text-color-inverse); + background-color: var(--primary-color); + border: 0; + + &:hover { + color: var(--text-color-inverse); + background-color: var(--primary-color-subtle); + } + } } button { diff --git a/static-src/styles/vars.scss b/static-src/styles/vars.scss index 60065e3..07300ad 100644 --- a/static-src/styles/vars.scss +++ b/static-src/styles/vars.scss @@ -64,14 +64,25 @@ more information on this, and how to apply and follow the GNU AGPL, see 0 - button(onClick=`showQR('${data.verifiers[0].url}', 'profile_verifier_url')` aria-label='Show profile ID QR') Keyoxide profile QR - button(onClick=`showQR('${data.identifier}', 'profile_identifier')` aria-label='Show profile ID QR') Profile ID QR + button.themed(onClick=`showQR('${data.verifiers[0].url}', 'profile_verifier_url')` aria-label='Show profile ID QR') Keyoxide profile QR + button.themed(onClick=`showQR('${data.identifier}', 'profile_identifier')` aria-label='Show profile ID QR') Profile ID QR section h2 Profile information diff --git a/yarn.lock b/yarn.lock index 94a2a4f..66c193f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1579,6 +1579,11 @@ colorette@^2.0.14: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== +colorjs.io@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/colorjs.io/-/colorjs.io-0.4.5.tgz#7775f787ff90aca7a38f6edb7b7c0f8cce1e6418" + integrity sha512-yCtUNCmge7llyfd/Wou19PMAcf5yC3XXhgFoAh6zsO2pGswhUPBaaUh8jzgHnXtXuZyFKzXZNAnyF5i+apICow== + colorspace@1.1.x: version "1.1.4" resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" @@ -1837,10 +1842,10 @@ doctypes@^1.1.0: resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" integrity sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ== -doipjs@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/doipjs/-/doipjs-1.2.4.tgz#f980ac4a3ca71b4496530a1e877230b7b6a4cec0" - integrity sha512-zs/kL/Yc3H4X8SrWW0zf6w6RLWDw2LQ6w/tgx8Kirqf7eMtOO0rur9/0ASCeHvoqRr7O6W7dXnW7nD/I5ZgxDg== +doipjs@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/doipjs/-/doipjs-1.2.5.tgz#98d67dad67ec0d0afafcda4e4740c93a964eecd3" + integrity sha512-366rS4LpzrGUgf1wfxjLpS46T4whB15m4dW8DfVYLLFYqNhwDVirRhysaexCQ8B4Ns+eCSa7qfpVszSxwGmgkA== dependencies: "@openpgp/hkp-client" "^0.0.3" "@openpgp/wkd-client" "^0.0.4"