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"