forked from Mirrors/keyoxide-web
Remove static files
This commit is contained in:
parent
c52d3228a2
commit
0fc4b8429e
6 changed files with 0 additions and 1790 deletions
|
@ -1,220 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2021 Yarmo Mackenbach
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU Affero General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer network,
|
||||
you should also make sure that it provides a way for users to get its source.
|
||||
For example, if your program is a web application, its interface could display
|
||||
a "Source" link that leads users to an archive of the code. There are many
|
||||
ways you could offer source, and different solutions will be better for different
|
||||
programs; see section 13 for the specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class Claim extends HTMLElement {
|
||||
// Specify the attributes to observe
|
||||
static get observedAttributes() {
|
||||
return ['data-claim'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
// Call super
|
||||
super();
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
this.updateContent(newValue);
|
||||
}
|
||||
|
||||
async verify() {
|
||||
const claim = new doip.Claim(JSON.parse(this.getAttribute('data-claim')));
|
||||
await claim.verify({
|
||||
proxy: {
|
||||
policy: 'adaptive',
|
||||
hostname: 'PLACEHOLDER__PROXY_HOSTNAME'
|
||||
}
|
||||
});
|
||||
this.setAttribute('data-claim', JSON.stringify(claim));
|
||||
}
|
||||
|
||||
updateContent(value) {
|
||||
const root = this;
|
||||
const claim = new doip.Claim(JSON.parse(value));
|
||||
|
||||
switch (claim.matches[0].serviceprovider.name) {
|
||||
case 'dns':
|
||||
case 'xmpp':
|
||||
case 'irc':
|
||||
root.querySelector('.info .subtitle').innerText = claim.matches[0].serviceprovider.name.toUpperCase();
|
||||
break;
|
||||
|
||||
default:
|
||||
root.querySelector('.info .subtitle').innerText = claim.matches[0].serviceprovider.name;
|
||||
break;
|
||||
}
|
||||
root.querySelector('.info .title').innerText = claim.matches[0].profile.display;
|
||||
|
||||
try {
|
||||
if (claim.status === 'verified') {
|
||||
root.querySelector('.icons .verificationStatus').setAttribute('data-value', claim.verification.result ? 'success' : 'failed');
|
||||
} else {
|
||||
root.querySelector('.icons .verificationStatus').setAttribute('data-value', 'running');
|
||||
}
|
||||
} catch (error) {
|
||||
root.querySelector('.icons .verificationStatus').setAttribute('data-value', 'failed');
|
||||
}
|
||||
|
||||
const elContent = root.querySelector('.content');
|
||||
elContent.innerHTML = ``;
|
||||
|
||||
// Handle failed ambiguous claim
|
||||
if (claim.status === 'verified' && !claim.verification.result && claim.isAmbiguous()) {
|
||||
root.querySelector('.info .subtitle').innerText = '---';
|
||||
|
||||
const subsection_alert = elContent.appendChild(document.createElement('div'));
|
||||
subsection_alert.setAttribute('class', 'subsection');
|
||||
const subsection_alert_icon = subsection_alert.appendChild(document.createElement('img'));
|
||||
subsection_alert_icon.setAttribute('src', '/static/img/alert-decagram.png');
|
||||
subsection_alert_icon.setAttribute('alt', '');
|
||||
subsection_alert_icon.setAttribute('aria-hidden', 'true');
|
||||
const subsection_alert_text = subsection_alert.appendChild(document.createElement('div'));
|
||||
|
||||
const message = subsection_alert_text.appendChild(document.createElement('p'));
|
||||
message.innerHTML = `None of the matched service providers could be verified. Keyoxide was not able to determine which was the correct service provider or why the verification process failed.`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Links to profile and proof
|
||||
const subsection_links = elContent.appendChild(document.createElement('div'));
|
||||
subsection_links.setAttribute('class', 'subsection');
|
||||
const subsection_links_icon = subsection_links.appendChild(document.createElement('img'));
|
||||
subsection_links_icon.setAttribute('src', '/static/img/link.png');
|
||||
subsection_links_icon.setAttribute('alt', '');
|
||||
subsection_links_icon.setAttribute('aria-hidden', 'true');
|
||||
const subsection_links_text = subsection_links.appendChild(document.createElement('div'));
|
||||
|
||||
const profile_link = subsection_links_text.appendChild(document.createElement('p'));
|
||||
if (claim.matches[0].profile.uri) {
|
||||
profile_link.innerHTML = `Profile link: <a rel="me" href="${claim.matches[0].profile.uri}" aria-label="link to profile">${claim.matches[0].profile.uri}</a>`;
|
||||
} else {
|
||||
profile_link.innerHTML = `Profile link: not accessible from browser`;
|
||||
}
|
||||
|
||||
const proof_link = subsection_links_text.appendChild(document.createElement('p'));
|
||||
if (claim.matches[0].proof.uri) {
|
||||
proof_link.innerHTML = `Proof link: <a href="${claim.matches[0].proof.uri}" aria-label="link to profile">${claim.matches[0].proof.uri}</a>`;
|
||||
} else {
|
||||
proof_link.innerHTML = `Proof link: not accessible from browser`;
|
||||
}
|
||||
|
||||
// QR Code
|
||||
if (claim.matches[0].profile.qr) {
|
||||
elContent.appendChild(document.createElement('hr'));
|
||||
|
||||
const subsection_qr = elContent.appendChild(document.createElement('div'));
|
||||
subsection_qr.setAttribute('class', 'subsection');
|
||||
const subsection_qr_icon = subsection_qr.appendChild(document.createElement('img'));
|
||||
subsection_qr_icon.setAttribute('src', '/static/img/qrcode.png');
|
||||
subsection_qr_icon.setAttribute('alt', '');
|
||||
subsection_qr_icon.setAttribute('aria-hidden', 'true');
|
||||
const subsection_qr_text = subsection_qr.appendChild(document.createElement('div'));
|
||||
|
||||
const button_profileQR = subsection_qr_text.appendChild(document.createElement('button'));
|
||||
button_profileQR.innerText = `Show profile QR`;
|
||||
button_profileQR.setAttribute('onClick', `showQR('${claim.matches[0].profile.qr}', 'url')`);
|
||||
button_profileQR.setAttribute('aria-label', `Show QR code linking to profile`);
|
||||
}
|
||||
|
||||
elContent.appendChild(document.createElement('hr'));
|
||||
|
||||
// Claim verification status
|
||||
const subsection_status = elContent.appendChild(document.createElement('div'));
|
||||
subsection_status.setAttribute('class', 'subsection');
|
||||
const subsection_status_icon = subsection_status.appendChild(document.createElement('img'));
|
||||
subsection_status_icon.setAttribute('src', '/static/img/decagram.png');
|
||||
subsection_status_icon.setAttribute('alt', '');
|
||||
subsection_status_icon.setAttribute('aria-hidden', 'true');
|
||||
const subsection_status_text = subsection_status.appendChild(document.createElement('div'));
|
||||
|
||||
const verification = subsection_status_text.appendChild(document.createElement('p'));
|
||||
if (claim.status === 'verified') {
|
||||
verification.innerHTML = `Claim verification has completed.`;
|
||||
subsection_status_icon.setAttribute('src', '/static/img/check-decagram.png');
|
||||
subsection_status_icon.setAttribute('alt', '');
|
||||
subsection_status_icon.setAttribute('aria-hidden', 'true');
|
||||
} else {
|
||||
verification.innerHTML = `Claim verification is in progress…`;
|
||||
return;
|
||||
}
|
||||
|
||||
elContent.appendChild(document.createElement('hr'));
|
||||
|
||||
// Result of claim verification
|
||||
const subsection_result = elContent.appendChild(document.createElement('div'));
|
||||
subsection_result.setAttribute('class', 'subsection');
|
||||
const subsection_result_icon = subsection_result.appendChild(document.createElement('img'));
|
||||
subsection_result_icon.setAttribute('src', '/static/img/shield-search.png');
|
||||
subsection_result_icon.setAttribute('alt', '');
|
||||
subsection_result_icon.setAttribute('aria-hidden', 'true');
|
||||
const subsection_result_text = subsection_result.appendChild(document.createElement('div'));
|
||||
|
||||
const result = subsection_result_text.appendChild(document.createElement('p'));
|
||||
result.innerHTML = `The claim <strong>${claim.verification.result ? 'HAS BEEN' : 'COULD NOT BE'}</strong> verified by the proof.`;
|
||||
|
||||
// Additional info
|
||||
if (claim.verification.proof.viaProxy) {
|
||||
elContent.appendChild(document.createElement('hr'));
|
||||
|
||||
const subsection_info = elContent.appendChild(document.createElement('div'));
|
||||
subsection_info.setAttribute('class', 'subsection');
|
||||
const subsection_info_icon = subsection_info.appendChild(document.createElement('img'));
|
||||
subsection_info_icon.setAttribute('src', '/static/img/information.png');
|
||||
subsection_info_icon.setAttribute('alt', '');
|
||||
subsection_info_icon.setAttribute('aria-hidden', 'true');
|
||||
const subsection_info_text = subsection_info.appendChild(document.createElement('div'));
|
||||
|
||||
const result_proxyUsed = subsection_info_text.appendChild(document.createElement('p'));
|
||||
result_proxyUsed.innerHTML = `A proxy was used to fetch the proof: <a href="https://PLACEHOLDER__PROXY_HOSTNAME" aria-label="Link to proxy server">PLACEHOLDER__PROXY_HOSTNAME</a>`;
|
||||
}
|
||||
|
||||
// TODO Display errors
|
||||
// if (claim.verification.errors.length > 0) {
|
||||
// console.log(claim.verification);
|
||||
// elContent.appendChild(document.createElement('hr'));
|
||||
|
||||
// const subsection_errors = elContent.appendChild(document.createElement('div'));
|
||||
// subsection_errors.setAttribute('class', 'subsection');
|
||||
// const subsection_errors_icon = subsection_errors.appendChild(document.createElement('img'));
|
||||
// subsection_errors_icon.setAttribute('src', '/static/img/alert-circle.png');
|
||||
// const subsection_errors_text = subsection_errors.appendChild(document.createElement('div'));
|
||||
|
||||
// claim.verification.errors.forEach(message => {
|
||||
// const error = subsection_errors_text.appendChild(document.createElement('p'));
|
||||
|
||||
// if (message instanceof Error) {
|
||||
// error.innerText = message.message;
|
||||
// } else {
|
||||
// error.innerText = message;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('kx-claim', Claim);
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2021 Yarmo Mackenbach
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU Affero General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer network,
|
||||
you should also make sure that it provides a way for users to get its source.
|
||||
For example, if your program is a web application, its interface could display
|
||||
a "Source" link that leads users to an archive of the code. There are many
|
||||
ways you could offer source, and different solutions will be better for different
|
||||
programs; see section 13 for the specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
class Key extends HTMLElement {
|
||||
// Specify the attributes to observe
|
||||
static get observedAttributes() {
|
||||
return ['data-keydata'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
// Call super
|
||||
super();
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
this.updateContent(newValue);
|
||||
}
|
||||
|
||||
updateContent(value) {
|
||||
const root = this;
|
||||
const data = JSON.parse(value);
|
||||
|
||||
root.querySelector('.info .subtitle').innerText = data.key.fetchMethod;
|
||||
root.querySelector('.info .title').innerText = data.fingerprint;
|
||||
|
||||
const elContent = root.querySelector('.content');
|
||||
elContent.innerHTML = ``;
|
||||
|
||||
// Link to key
|
||||
const subsection_links = elContent.appendChild(document.createElement('div'));
|
||||
subsection_links.setAttribute('class', 'subsection');
|
||||
const subsection_links_icon = subsection_links.appendChild(document.createElement('img'));
|
||||
subsection_links_icon.setAttribute('src', '/static/img/link.png');
|
||||
subsection_links_icon.setAttribute('alt', '');
|
||||
subsection_links_icon.setAttribute('aria-hidden', 'true');
|
||||
const subsection_links_text = subsection_links.appendChild(document.createElement('div'));
|
||||
|
||||
const profile_link = subsection_links_text.appendChild(document.createElement('p'));
|
||||
profile_link.innerHTML = `Key link: <a href="${data.key.uri}" aria-label="Link to cryptographic key">${data.key.uri}</a>`;
|
||||
|
||||
elContent.appendChild(document.createElement('hr'));
|
||||
|
||||
// QR Code
|
||||
const subsection_qr = elContent.appendChild(document.createElement('div'));
|
||||
subsection_qr.setAttribute('class', 'subsection');
|
||||
const subsection_qr_icon = subsection_qr.appendChild(document.createElement('img'));
|
||||
subsection_qr_icon.setAttribute('src', '/static/img/qrcode.png');
|
||||
subsection_qr_icon.setAttribute('alt', '');
|
||||
subsection_qr_icon.setAttribute('aria-hidden', 'true');
|
||||
const subsection_qr_text = subsection_qr.appendChild(document.createElement('div'));
|
||||
|
||||
const button_fingerprintQR = subsection_qr_text.appendChild(document.createElement('button'));
|
||||
button_fingerprintQR.innerText = `Show OpenPGP fingerprint QR`;
|
||||
button_fingerprintQR.setAttribute('onClick', `showQR('${data.fingerprint}', 'fingerprint')`);
|
||||
button_fingerprintQR.setAttribute('aria-label', `Show QR code for cryptographic fingerprint`);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('kx-key', Key);
|
|
@ -1,254 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2021 Yarmo Mackenbach
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU Affero General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer network,
|
||||
you should also make sure that it provides a way for users to get its source.
|
||||
For example, if your program is a web application, its interface could display
|
||||
a "Source" link that leads users to an archive of the code. There are many
|
||||
ways you could offer source, and different solutions will be better for different
|
||||
programs; see section 13 for the specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
details.kx-item {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
details.kx-item p {
|
||||
margin: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
details.kx-item a {
|
||||
color: var(--blue-700);
|
||||
}
|
||||
details.kx-item hr {
|
||||
border: none;
|
||||
border-top: 2px solid var(--purple-100);
|
||||
}
|
||||
details.kx-item .content {
|
||||
padding: 12px;
|
||||
border: solid 3px var(--purple-100);
|
||||
border-top: 0px;
|
||||
border-radius: 0px 0px 8px 8px;
|
||||
}
|
||||
details.kx-item summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background-color: var(--purple-100);
|
||||
border: solid 3px var(--purple-100);
|
||||
border-radius: 8px;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
details.kx-item summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
details.kx-item summary:hover, summary:focus {
|
||||
border-color: var(--purple-400);
|
||||
}
|
||||
details[open] summary {
|
||||
border-radius: 8px 8px 0px 0px;
|
||||
}
|
||||
details.kx-item summary .info {
|
||||
flex: 1;
|
||||
}
|
||||
details.kx-item summary .info .title {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
details.kx-item summary .claim__description p {
|
||||
font-size: 1.4rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
details.kx-item summary .claim__links p, p.subtle-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 1rem;
|
||||
color: var(--grey-700);
|
||||
}
|
||||
details.kx-item summary .claim__links a, summary .claim__links span, p.subtle-links a {
|
||||
font-size: 1rem;
|
||||
margin: 0 10px 0 0;
|
||||
color: var(--grey-700);
|
||||
}
|
||||
details.kx-item summary .subtitle {
|
||||
color: var(--purple-700);
|
||||
}
|
||||
details.kx-item summary .verificationStatus {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 100%;
|
||||
color: #fff;
|
||||
font-size: 2rem;
|
||||
user-select: none;
|
||||
}
|
||||
details.kx-item summary .verificationStatus::after {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
details.kx-item summary .verificationStatus .inProgress {
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
details.kx-item summary .verificationStatus[data-value="success"] {
|
||||
content: "v";
|
||||
background-color: var(--green-600);
|
||||
}
|
||||
details.kx-item summary .verificationStatus[data-value="success"]::after {
|
||||
content: "✔";
|
||||
}
|
||||
details.kx-item summary .verificationStatus[data-value="failed"] {
|
||||
background-color: var(--red-400);
|
||||
}
|
||||
details.kx-item summary .verificationStatus[data-value="failed"]::after {
|
||||
content: "✕";
|
||||
}
|
||||
details.kx-item summary .verificationStatus[data-value="running"] .inProgress {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
details.kx-item .subsection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
details.kx-item .subsection > img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
details.kx-item .inProgress {
|
||||
font-size: 10px;
|
||||
margin: 50px auto;
|
||||
text-indent: -9999em;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background: var(--purple-400);
|
||||
background: -moz-linear-gradient(left, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%);
|
||||
background: -webkit-linear-gradient(left, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%);
|
||||
background: -o-linear-gradient(left, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%);
|
||||
background: -ms-linear-gradient(left, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%);
|
||||
background: linear-gradient(to right, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%);
|
||||
position: relative;
|
||||
-webkit-animation: load3 1.4s infinite linear;
|
||||
animation: load3 1.4s infinite linear;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
details.kx-item .inProgress:before {
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
background: var(--purple-400);
|
||||
border-radius: 100% 0 0 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
content: '';
|
||||
}
|
||||
details.kx-item .inProgress:after {
|
||||
background: var(--purple-100);
|
||||
width: 65%;
|
||||
height: 65%;
|
||||
border-radius: 50%;
|
||||
content: '';
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
details.kx-item button {
|
||||
padding: 0.4rem 0.8rem;
|
||||
margin-right: 8px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
background-color: #fff;
|
||||
border: solid 2px var(--purple-400);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
details.kx-item button:hover {
|
||||
background-color: var(--purple-500);
|
||||
border-color: var(--purple-500);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
details.kx-item summary .claim__description p {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
details.kx-item summary .claim__links a, p.subtle-links a {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 480px) {
|
||||
summary .claim__description p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
details.kx-item summary .verificationStatus {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
details.kx-item .inProgress {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes load3 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes load3 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
|
@ -1,214 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2021 Yarmo Mackenbach
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU Affero General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer network,
|
||||
you should also make sure that it provides a way for users to get its source.
|
||||
For example, if your program is a web application, its interface could display
|
||||
a "Source" link that leads users to an archive of the code. There are many
|
||||
ways you could offer source, and different solutions will be better for different
|
||||
programs; see section 13 for the specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// Verify all claims
|
||||
const claims = document.querySelectorAll('kx-claim');
|
||||
claims.forEach(function(claim) {
|
||||
if (claim.hasAttribute('data-skip') && claim.getAttribute('data-skip')) {
|
||||
return;
|
||||
}
|
||||
claim.verify();
|
||||
});
|
||||
|
||||
// Register modals
|
||||
document.querySelectorAll('dialog').forEach(function(d) {
|
||||
dialogPolyfill.registerDialog(d);
|
||||
d.addEventListener('click', function(ev) {
|
||||
if (ev && ev.target != d) {
|
||||
return;
|
||||
}
|
||||
d.close();
|
||||
});
|
||||
});
|
||||
|
||||
// Register form listeners
|
||||
const elFormEncrypt = document.body.querySelector("#dialog--encryptMessage form");
|
||||
if (elFormEncrypt) {
|
||||
elFormEncrypt.onsubmit = async function (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
try {
|
||||
// Fetch a key if needed
|
||||
await fetchProfileKey();
|
||||
|
||||
// Encrypt the message
|
||||
let config = openpgp.config;
|
||||
config.show_comment = false;
|
||||
config.show_version = false;
|
||||
|
||||
encrypted = await openpgp.encrypt({
|
||||
message: openpgp.message.fromText(elFormEncrypt.querySelector('.input').value),
|
||||
publicKeys: window.kx.key.object,
|
||||
config: config
|
||||
});
|
||||
elFormEncrypt.querySelector('.output').value = encrypted.data;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
elFormEncrypt.querySelector('.output').value = `Could not encrypt message!\n==========================\n${e.message ? e.message : e}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const elFormVerify = document.body.querySelector("#dialog--verifySignature form");
|
||||
if (elFormVerify) {
|
||||
elFormVerify.onsubmit = async function (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
try {
|
||||
// Fetch a key if needed
|
||||
await fetchProfileKey();
|
||||
|
||||
// Try two different methods of signature reading
|
||||
let signature = null, verified = null, readError = null;
|
||||
try {
|
||||
signature = await openpgp.message.readArmored(elFormVerify.querySelector('.input').value);
|
||||
} catch(e) {
|
||||
readError = e;
|
||||
}
|
||||
try {
|
||||
signature = await openpgp.cleartext.readArmored(elFormVerify.querySelector('.input').value);
|
||||
} catch(e) {
|
||||
readError = e;
|
||||
}
|
||||
if (signature == null) { throw(readError) };
|
||||
|
||||
// Verify the signature
|
||||
verified = await openpgp.verify({
|
||||
message: signature,
|
||||
publicKeys: window.kx.key.object
|
||||
});
|
||||
|
||||
if (verified.signatures[0].valid) {
|
||||
elFormVerify.querySelector('.output').value = `The message was signed by the profile's key.`;
|
||||
} else {
|
||||
elFormVerify.querySelector('.output').value = `The message was NOT signed by the profile's key.`;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
elFormVerify.querySelector('.output').value = `Could not verify signature!\n===========================\n${e.message ? e.message : e}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const elFormSearch = document.body.querySelector("#search");
|
||||
if (elFormSearch) {
|
||||
elFormSearch.onsubmit = function (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
const protocol = elFormSearch.querySelector("input[type='radio']:checked").value;
|
||||
const identifier = elFormSearch.querySelector("input[type='search']").value;
|
||||
|
||||
if (protocol == 'sig') {
|
||||
window.location.href = `/${protocol}`;
|
||||
} else {
|
||||
window.location.href = `/${protocol}/${encodeURIComponent(identifier)}`;
|
||||
}
|
||||
}
|
||||
|
||||
const elSearchRadio = elFormSearch.querySelectorAll("input[type='radio']");
|
||||
elSearchRadio.forEach(function (el) {
|
||||
el.oninput = function (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
if (evt.target.getAttribute('id') === 'protocol-sig') {
|
||||
elFormSearch.querySelector("input[type='search']").setAttribute('disabled', true);
|
||||
} else {
|
||||
elFormSearch.querySelector("input[type='search']").removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
});
|
||||
elFormSearch.querySelector("input[type='radio']:checked").dispatchEvent(new Event('input'));
|
||||
}
|
||||
|
||||
// Functions
|
||||
const fetchProfileKey = async function() {
|
||||
if (window.kx.key.object && window.kx.key.object instanceof openpgp.key.Key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rawKeyData = await fetch(window.kx.key.url)
|
||||
let key, errorMsg
|
||||
|
||||
try {
|
||||
key = (await openpgp.key.read(new Uint8Array(await rawKeyData.clone().arrayBuffer()))).keys[0]
|
||||
} catch(error) {
|
||||
errorMsg = error.message
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
try {
|
||||
key = (await openpgp.key.readArmored(await rawKeyData.clone().text())).keys[0]
|
||||
} catch (error) {
|
||||
errorMsg = error.message
|
||||
}
|
||||
}
|
||||
|
||||
if (key) {
|
||||
window.kx.key.object = key
|
||||
return
|
||||
} else {
|
||||
throw new Error(`Public key could not be fetched (${errorMsg})`)
|
||||
}
|
||||
}
|
||||
|
||||
// Enable QR modal
|
||||
const showQR = function(input, type) {
|
||||
const qrTarget = document.getElementById('qr');
|
||||
const qrContext = qrTarget.getContext('2d');
|
||||
const qrOpts = {
|
||||
errorCorrectionLevel: 'L',
|
||||
margin: 1,
|
||||
width: 256,
|
||||
height: 256
|
||||
};
|
||||
|
||||
if (input) {
|
||||
if (type === 'url') {
|
||||
input = decodeURIComponent(input);
|
||||
}
|
||||
if (type === 'fingerprint') {
|
||||
input = `OPENPGP4FPR:${input.toUpperCase()}`
|
||||
}
|
||||
|
||||
QRCode.toCanvas(qrTarget, input, qrOpts, function(error) {
|
||||
if (error) {
|
||||
document.querySelector("#qr--altLink").innerText = "";
|
||||
document.querySelector("#qr--altLink").href = "#";
|
||||
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
||||
console.error(error);
|
||||
} else {
|
||||
document.querySelector("#qr--altLink").innerText = input;
|
||||
document.querySelector("#qr--altLink").href = input;
|
||||
document.querySelector('#dialog--qr').showModal();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
||||
}
|
||||
}
|
|
@ -1,276 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2021 Yarmo Mackenbach
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU Affero General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer network,
|
||||
you should also make sure that it provides a way for users to get its source.
|
||||
For example, if your program is a web application, its interface could display
|
||||
a "Source" link that leads users to an archive of the code. There are many
|
||||
ways you could offer source, and different solutions will be better for different
|
||||
programs; see section 13 for the specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
async function computeWKDLocalPart(message) {
|
||||
const data = openpgp.util.str_to_Uint8Array(message.toLowerCase());
|
||||
const hash = await openpgp.crypto.hash.sha1(data);
|
||||
return openpgp.util.encodeZBase32(hash);
|
||||
}
|
||||
async function generateProfileURL(data) {
|
||||
let hostname = window.location.hostname;
|
||||
|
||||
if (data.input == "") {
|
||||
return "Waiting for input...";
|
||||
}
|
||||
switch (data.source) {
|
||||
case "wkd":
|
||||
return `https://${hostname}/${data.input}`;
|
||||
break;
|
||||
case "hkp":
|
||||
if (/.*@.*\..*/.test(data.input)) {
|
||||
return `https://${hostname}/hkp/${data.input}`;
|
||||
} else {
|
||||
return `https://${hostname}/${data.input}`;
|
||||
}
|
||||
break;
|
||||
case "keybase":
|
||||
const re = /https\:\/\/keybase.io\/(.*)\/pgp_keys\.asc\?fingerprint\=(.*)/;
|
||||
if (!re.test(data.input)) {
|
||||
return "Incorrect Keybase public key URL.";
|
||||
}
|
||||
const match = data.input.match(re);
|
||||
return `https://${hostname}/keybase/${match[1]}/${match[2]}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let elFormSignatureProfile = document.body.querySelector("#formGenerateSignatureProfile"),
|
||||
elProfileUid = document.body.querySelector("#profileUid"),
|
||||
elProfileMode = document.body.querySelector("#profileMode"),
|
||||
elProfileServer = document.body.querySelector("#profileServer"),
|
||||
elModeSelect = document.body.querySelector("#modeSelect"),
|
||||
elUtilWKD = document.body.querySelector("#form-util-wkd"),
|
||||
elUtilQRFP = document.body.querySelector("#form-util-qrfp"),
|
||||
elUtilQR = document.body.querySelector("#form-util-qr"),
|
||||
elUtilProfileURL = document.body.querySelector("#form-util-profile-url");
|
||||
|
||||
if (elModeSelect) {
|
||||
elModeSelect.onchange = function (evt) {
|
||||
let elAllModes = document.body.querySelectorAll('.modes');
|
||||
elAllModes.forEach(function(el) {
|
||||
el.classList.remove('modes--visible');
|
||||
});
|
||||
document.body.querySelector(`.modes--${elModeSelect.value}`).classList.add('modes--visible');
|
||||
}
|
||||
elModeSelect.dispatchEvent(new Event("change"));
|
||||
}
|
||||
|
||||
if (elProfileUid) {
|
||||
let opts, profileUid = elProfileUid.innerHTML;
|
||||
switch (elProfileMode.innerHTML) {
|
||||
default:
|
||||
case "sig":
|
||||
elFormSignatureProfile.onsubmit = function (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
opts = {
|
||||
input: document.body.querySelector("#plaintext_input").value,
|
||||
mode: elProfileMode.innerHTML
|
||||
}
|
||||
|
||||
displayProfile(opts)
|
||||
}
|
||||
break;
|
||||
|
||||
case "auto":
|
||||
if (/.*@.*/.test(profileUid)) {
|
||||
// Match email for wkd
|
||||
opts = {
|
||||
input: profileUid,
|
||||
mode: "wkd"
|
||||
}
|
||||
} else {
|
||||
// Match fingerprint for hkp
|
||||
opts = {
|
||||
input: profileUid,
|
||||
mode: "hkp"
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "hkp":
|
||||
opts = {
|
||||
input: profileUid,
|
||||
server: elProfileServer.innerHTML,
|
||||
mode: elProfileMode.innerHTML
|
||||
}
|
||||
break;
|
||||
|
||||
case "wkd":
|
||||
opts = {
|
||||
input: profileUid,
|
||||
mode: elProfileMode.innerHTML
|
||||
}
|
||||
break;
|
||||
|
||||
case "keybase":
|
||||
let match = profileUid.match(/(.*)\/(.*)/);
|
||||
opts = {
|
||||
username: match[1],
|
||||
fingerprint: match[2],
|
||||
mode: elProfileMode.innerHTML
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (elProfileMode.innerHTML !== 'sig') {
|
||||
keyoxide.displayProfile(opts);
|
||||
}
|
||||
}
|
||||
|
||||
if (elUtilWKD) {
|
||||
elUtilWKD.onsubmit = function (evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
const elInput = document.body.querySelector("#input");
|
||||
const elOutput = document.body.querySelector("#output");
|
||||
const elOutputDirect = document.body.querySelector("#output_url_direct");
|
||||
const elOutputAdvanced = document.body.querySelector("#output_url_advanced");
|
||||
let match;
|
||||
|
||||
elInput.addEventListener("input", async function(evt) {
|
||||
if (evt.target.value) {
|
||||
if (/(.*)@(.{1,}\..{1,})/.test(evt.target.value)) {
|
||||
match = evt.target.value.match(/(.*)@(.*)/);
|
||||
elOutput.innerText = await computeWKDLocalPart(match[1]);
|
||||
elOutputDirect.innerText = `https://${match[2]}/.well-known/openpgpkey/hu/${elOutput.innerText}?l=${match[1]}`;
|
||||
elOutputAdvanced.innerText = `https://openpgpkey.${match[2]}/.well-known/openpgpkey/${match[2]}/hu/${elOutput.innerText}?l=${match[1]}`;
|
||||
} else {
|
||||
elOutput.innerText = await computeWKDLocalPart(evt.target.value);
|
||||
elOutputDirect.innerText = "Waiting for input";
|
||||
elOutputAdvanced.innerText = "Waiting for input";
|
||||
}
|
||||
} else {
|
||||
elOutput.innerText = "Waiting for input";
|
||||
elOutputDirect.innerText = "Waiting for input";
|
||||
elOutputAdvanced.innerText = "Waiting for input";
|
||||
}
|
||||
});
|
||||
|
||||
elInput.dispatchEvent(new Event("input"));
|
||||
}
|
||||
|
||||
if (elUtilQRFP) {
|
||||
elUtilQRFP.onsubmit = function (evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
const qrTarget = document.getElementById('qrcode');
|
||||
const qrContext = qrTarget.getContext('2d');
|
||||
const qrOpts = {
|
||||
errorCorrectionLevel: 'H',
|
||||
margin: 1,
|
||||
width: 256,
|
||||
height: 256
|
||||
};
|
||||
|
||||
const elInput = document.body.querySelector("#input");
|
||||
|
||||
elInput.addEventListener("input", async function(evt) {
|
||||
if (evt.target.value) {
|
||||
QRCode.toCanvas(qrTarget, evt.target.value, qrOpts, function (error) {
|
||||
if (error) {
|
||||
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
||||
}
|
||||
});
|
||||
|
||||
elInput.dispatchEvent(new Event("input"));
|
||||
}
|
||||
|
||||
if (elUtilQR) {
|
||||
elUtilQR.onsubmit = function (evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
const qrTarget = document.getElementById('qrcode');
|
||||
const qrContext = qrTarget.getContext('2d');
|
||||
const qrOpts = {
|
||||
errorCorrectionLevel: 'L',
|
||||
margin: 1,
|
||||
width: 256,
|
||||
height: 256
|
||||
};
|
||||
|
||||
const elInput = document.body.querySelector("#input");
|
||||
|
||||
if (elInput.innerText) {
|
||||
elInput.innerText = decodeURIComponent(elInput.innerText);
|
||||
|
||||
QRCode.toCanvas(qrTarget, elInput.innerText, qrOpts, function (error) {
|
||||
if (error) {
|
||||
document.body.querySelector("#qrcode--altLink").href = "#";
|
||||
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
||||
console.error(error);
|
||||
} else {
|
||||
document.body.querySelector("#qrcode--altLink").href = elInput.innerText;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
||||
}
|
||||
}
|
||||
|
||||
if (elUtilProfileURL) {
|
||||
elUtilProfileURL.onsubmit = function (evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
const elInput = document.body.querySelector("#input"),
|
||||
elSource = document.body.querySelector("#source"),
|
||||
elOutput = document.body.querySelector("#output");
|
||||
|
||||
let data = {
|
||||
input: elInput.value,
|
||||
source: elSource.value
|
||||
};
|
||||
|
||||
elInput.addEventListener("input", async function(evt) {
|
||||
data = {
|
||||
input: elInput.value,
|
||||
source: elSource.value
|
||||
};
|
||||
elOutput.innerText = await generateProfileURL(data);
|
||||
});
|
||||
|
||||
elSource.addEventListener("input", async function(evt) {
|
||||
data = {
|
||||
input: elInput.value,
|
||||
source: elSource.value
|
||||
};
|
||||
elOutput.innerText = await generateProfileURL(data);
|
||||
});
|
||||
|
||||
elInput.dispatchEvent(new Event("input"));
|
||||
}
|
|
@ -1,741 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2021 Yarmo Mackenbach
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
the terms of the GNU Affero General Public License as published by the Free
|
||||
Software Foundation, either version 3 of the License, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer network,
|
||||
you should also make sure that it provides a way for users to get its source.
|
||||
For example, if your program is a web application, its interface could display
|
||||
a "Source" link that leads users to an archive of the code. There are many
|
||||
ways you could offer source, and different solutions will be better for different
|
||||
programs; see section 13 for the specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
:root {
|
||||
--grey-500: hsl(0, 0%, 50%);
|
||||
--grey-600: hsl(0, 0%, 40%);
|
||||
--grey-700: hsl(0, 0%, 30%);
|
||||
--grey-900: hsl(0, 0%, 10%);
|
||||
--green-300: hsl(110, 45%, 70%);
|
||||
--green-400: hsl(110, 45%, 60%);
|
||||
--green-600: hsl(110, 45%, 40%);
|
||||
--red-400: hsl(10, 60%, 60%);
|
||||
--blue-500: hsl(201, 80%, 59%);
|
||||
--blue-700: hsl(201, 90%, 30%);
|
||||
--purple-50: hsl(250, 30%, 98%);
|
||||
--purple-100: hsl(250, 48%, 95%);
|
||||
--purple-200: hsl(250, 48%, 90%);
|
||||
--purple-300: hsl(250, 48%, 85%);
|
||||
--purple-400: hsl(250, 48%, 70%);
|
||||
--purple-500: hsl(250, 48%, 65%);
|
||||
--purple-600: hsl(250, 48%, 60%);
|
||||
--purple-700: hsl(250, 48%, 55%);
|
||||
--purple-900: hsl(250, 38%, 45%);
|
||||
--yellow-100: hsl(56, 100%, 95%);
|
||||
--yellow-200: hsl(56, 100%, 90%);
|
||||
--yellow-500: hsl(56, 100%, 65%);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px lightskyblue;
|
||||
}
|
||||
input:focus, textarea:focus {
|
||||
background: azure;
|
||||
}
|
||||
input[type="radio"]:focus + label {
|
||||
box-shadow: 0 0 0 3px lightskyblue;
|
||||
background: azure !important;
|
||||
color: var(--grey-900) !important;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 1.6rem 0 0;
|
||||
line-height: 1.4rem;
|
||||
font-family: sans-serif;
|
||||
color: var(--grey-900);
|
||||
}
|
||||
|
||||
/* HELPERS */
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
.no-margin {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.full-width {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* LAYOUT */
|
||||
header {
|
||||
margin: 0 1.6rem 1.6rem;
|
||||
}
|
||||
header nav {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
header nav a.logo {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
font-size: 1.6rem;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
color: var(--purple-700);
|
||||
}
|
||||
header nav a.logo img {
|
||||
width: 100%;
|
||||
}
|
||||
nav a.text {
|
||||
/* font-size: 0.9em; */
|
||||
margin: 0;
|
||||
padding: 0.5em 1em;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
color: var(--purple-700);
|
||||
border-radius: 4px;
|
||||
}
|
||||
nav a.text:hover, nav a.text:active {
|
||||
color: #fff;
|
||||
background-color: var(--purple-500);
|
||||
}
|
||||
main {
|
||||
flex: 1;
|
||||
margin: 0 1.6rem;
|
||||
}
|
||||
footer {
|
||||
margin: 4.8rem 0 0;
|
||||
padding: 0 1.6rem 1.6rem;
|
||||
background-color: var(--purple-900);
|
||||
color: var(--purple-200);
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
section.profile p, .demo p {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.demo {
|
||||
margin: 4.8rem auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 0 0 1.6rem;
|
||||
padding: 0 1.2rem;
|
||||
background-color: #fff;
|
||||
background-color: var(--purple-50);
|
||||
border: 2px solid var(--purple-200);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.card.card--transparent {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
.card--profileHeader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
.card--profileHeader p, .card--profileHeader small {
|
||||
margin: 0;
|
||||
}
|
||||
.card--small-profile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
.card--small-profile-dummy {
|
||||
opacity: 0.5;
|
||||
border: 0;
|
||||
}
|
||||
.card--small-profile .name {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
.card--small-profile p {
|
||||
margin-top: 0;
|
||||
}
|
||||
.card--small-profile p span.fingerprint {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
#profileName {
|
||||
font-size: 1.6rem;
|
||||
color: var(--grey-700);
|
||||
}
|
||||
#profileURLFingerprint {
|
||||
font-size: 1rem;
|
||||
margin: 0 0 1.2rem;
|
||||
}
|
||||
|
||||
.hcards {
|
||||
display: grid;
|
||||
grid-gap: 1.2rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
.hcards .card {
|
||||
margin: 0;
|
||||
}
|
||||
.hcards--col-1-2, .hcards--col-2-1 {
|
||||
grid-template-columns: repeat(auto-fit, minmax(256px, 1fr));
|
||||
}
|
||||
.hcards--col-1-2 .card, .hcards--col-2-1 .card {
|
||||
grid-column: 1 / 2;
|
||||
}
|
||||
@media screen and (min-width: 1024px) {
|
||||
.hcards--max-3 {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
.hcards--col-1-2, .hcards--col-2-1 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 720px) {
|
||||
.hcards--col-2-1 .card:nth-of-type(1) {
|
||||
grid-column: 1 / -2;
|
||||
}
|
||||
.hcards--col-2-1 .card:nth-of-type(2) {
|
||||
grid-column: -2 / -1;
|
||||
}
|
||||
.hcards--col-1-2 .card:nth-of-type(1) {
|
||||
grid-column: 1 / 2;
|
||||
}
|
||||
.hcards--col-1-2 .card:nth-of-type(2) {
|
||||
grid-column: 2 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.warning {
|
||||
padding: calc(0.8rem - 2px) 0.8rem;
|
||||
background-color: var(--yellow-200);
|
||||
border: solid 2px var(--yellow-500);
|
||||
}
|
||||
.warning p:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
.warning p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
kx-claim {
|
||||
display: block;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
#profileAvatar {
|
||||
display: inline-block;
|
||||
min-width: 96px;
|
||||
max-width: 128px;
|
||||
line-height: 0;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* TYPOGRAPHY */
|
||||
h1 {
|
||||
font-size: 1.6em;
|
||||
margin: 3.2rem 0 1.6rem;
|
||||
font-weight: normal;
|
||||
color: var(--purple-700);
|
||||
cursor: default;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.4em;
|
||||
margin: 3.2rem 0 1.6rem;
|
||||
font-weight: normal;
|
||||
color: var(--purple-700);
|
||||
cursor: default;
|
||||
}
|
||||
h2 small {
|
||||
margin-left: 0.8rem;
|
||||
padding: 3px 6px;
|
||||
background-color: var(--purple-600);
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
h3 {
|
||||
margin: 1.6rem 0;
|
||||
font-size: 1.3em;
|
||||
line-height: 1.6rem;
|
||||
color: var(--grey-700);
|
||||
font-weight: normal;
|
||||
/* text-align: center; */
|
||||
cursor: default;
|
||||
}
|
||||
h3 small {
|
||||
margin-left: 0.8rem;
|
||||
padding: 3px 6px;
|
||||
background-color: var(--purple-400);
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
h4 {
|
||||
margin: 1.6rem 0;
|
||||
font-size: 1em;
|
||||
line-height: 1.6rem;
|
||||
color: var(--grey-600);
|
||||
/* color: var(--purple-700); */
|
||||
font-weight: bold;
|
||||
cursor: default;
|
||||
}
|
||||
h4 small {
|
||||
margin-left: 0.8rem;
|
||||
padding: 3px 6px;
|
||||
background-color: var(--purple-400);
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
p {
|
||||
margin: 1.6rem 0;
|
||||
}
|
||||
p.warning {
|
||||
padding: 8px;
|
||||
background-color: #fffadc;
|
||||
border: solid 1px #ffeea8;
|
||||
}
|
||||
a {
|
||||
color: var(--blue-700);
|
||||
}
|
||||
ul {
|
||||
padding-left: 1em;
|
||||
list-style: '- ';
|
||||
}
|
||||
main h1:first-of-type {
|
||||
margin-top: 1.6rem;
|
||||
}
|
||||
footer h1 {
|
||||
margin-bottom: 0.8rem;
|
||||
color: var(--purple-200);
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
footer a {
|
||||
display: inline-block;
|
||||
color: var(--purple-100);
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 2px 4px;
|
||||
background-color: var(--purple-100);
|
||||
border: 1px solid var(--purple-500);
|
||||
}
|
||||
pre {
|
||||
padding: 8px 12px;
|
||||
background-color: var(--purple-100);
|
||||
border: 1px solid var(--purple-500);
|
||||
overflow-x: auto;
|
||||
line-height: 1.2rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
pre code {
|
||||
padding: 0;
|
||||
background-color: 0px;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
#qr {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
max-width: 256px !important;
|
||||
height: auto !important;
|
||||
margin: 0 auto 16px;
|
||||
}
|
||||
|
||||
/* FORM ELEMENTS */
|
||||
.form-wrapper {
|
||||
align-items: center;
|
||||
padding-top: 1.4rem;
|
||||
padding-bottom: 1.6rem;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
.form-wrapper form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
}
|
||||
.form-wrapper h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
form input[type="text"], form input[type="search"] {
|
||||
margin: 8px 0;
|
||||
padding: 4px;
|
||||
border: 1px solid #444;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
form textarea {
|
||||
width: 100%;
|
||||
height: 128px;
|
||||
margin: 8px 0;
|
||||
resize: vertical;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.radio-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.radio-wrapper input[type="radio"] {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
.radio-wrapper input[type="radio"] + label {
|
||||
margin: 0;
|
||||
padding: 2px 8px;
|
||||
background-color: #fff;
|
||||
border: solid var(--purple-400);
|
||||
border-width: 2px 1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.radio-wrapper input[type="radio"]:first-of-type + label {
|
||||
border-radius: 4px 0 0 4px;
|
||||
border-left-width: 2px;
|
||||
}
|
||||
.radio-wrapper input[type="radio"]:last-of-type + label {
|
||||
border-radius: 0 4px 4px 0;
|
||||
border-right-width: 2px;
|
||||
}
|
||||
.radio-wrapper input[type="radio"]:focus + label {
|
||||
z-index: 1;
|
||||
}
|
||||
.radio-wrapper input[type="radio"] + label:hover {
|
||||
background-color: var(--purple-100);
|
||||
border-color: var(--purple-500);
|
||||
}
|
||||
.radio-wrapper input[type="radio"]:checked + label {
|
||||
color: #fff;
|
||||
background-color: var(--purple-600);
|
||||
border-color: var(--purple-600);
|
||||
}
|
||||
|
||||
input[type="button"], input[type="submit"], button, a.button {
|
||||
display: inline-block;
|
||||
min-height: 36px;
|
||||
margin: 8px 0;
|
||||
padding: 4px 8px;
|
||||
font-family: sans-serif;
|
||||
font-size: 0.9rem;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
border: solid 2px var(--purple-400);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type="button"]:focus, input[type="submit"]:focus, button:focus, a.button:focus {
|
||||
background-color: azure;
|
||||
}
|
||||
input[type="button"]:hover, input[type="submit"]:hover, button:hover, a.button:hover {
|
||||
background-color: var(--purple-500);
|
||||
border-color: var(--purple-500);
|
||||
color: #fff;
|
||||
}
|
||||
a.button i {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
a.button.button--liberapay {
|
||||
padding: 8px 16px;
|
||||
font-size: 0.95rem;
|
||||
color: #333;
|
||||
background-color: #ffee16;
|
||||
border: 0;
|
||||
}
|
||||
a.button.button--liberapay:hover {
|
||||
background-color: #fff463;
|
||||
}
|
||||
|
||||
/* DIALOGS */
|
||||
dialog {
|
||||
width: 100% !important;
|
||||
max-width: 800px !important;
|
||||
padding: 0 !important;
|
||||
word-wrap: anywhere;
|
||||
}
|
||||
dialog > div {
|
||||
padding: 1em;
|
||||
}
|
||||
dialog form[method="Dialog"] {
|
||||
margin: 1em 0 0 !important;
|
||||
}
|
||||
dialog form[method="Dialog"] input {
|
||||
width: auto;
|
||||
}
|
||||
dialog p {
|
||||
font-size: 1rem !important;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
dialog p:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* KX-ITEM */
|
||||
.kx-item details {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.kx-item details p {
|
||||
margin: 0;
|
||||
word-break: break-word;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.kx-item details a {
|
||||
color: var(--blue-700);
|
||||
}
|
||||
.kx-item details hr {
|
||||
border: none;
|
||||
border-top: 2px solid var(--purple-100);
|
||||
}
|
||||
.kx-item details .content {
|
||||
padding: 12px;
|
||||
border: solid 3px var(--purple-100);
|
||||
border-top: 0px;
|
||||
border-radius: 0px 0px 8px 8px;
|
||||
}
|
||||
.kx-item details summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background-color: var(--purple-100);
|
||||
border: solid 3px var(--purple-100);
|
||||
border-radius: 8px;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.kx-item details summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
.kx-item details summary:hover, summary:focus {
|
||||
border-color: var(--purple-400);
|
||||
}
|
||||
details[open] summary {
|
||||
border-radius: 8px 8px 0px 0px;
|
||||
}
|
||||
.kx-item details summary .info {
|
||||
flex: 1;
|
||||
}
|
||||
.kx-item details summary .info .title {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.kx-item details summary .claim__description p {
|
||||
font-size: 1.4rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
.kx-item details summary .claim__links p, p.subtle-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 1rem;
|
||||
color: var(--grey-700);
|
||||
}
|
||||
.kx-item details summary .claim__links a, summary .claim__links span, p.subtle-links a {
|
||||
font-size: 1rem;
|
||||
margin: 0 10px 0 0;
|
||||
color: var(--grey-700);
|
||||
}
|
||||
.kx-item details summary .subtitle {
|
||||
color: var(--purple-700);
|
||||
}
|
||||
.kx-item details summary .verificationStatus {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 100%;
|
||||
color: #fff;
|
||||
font-size: 2rem;
|
||||
user-select: none;
|
||||
}
|
||||
.kx-item details summary .verificationStatus::after {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.kx-item details summary .verificationStatus .inProgress {
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
.kx-item details summary .verificationStatus[data-value="success"] {
|
||||
content: "v";
|
||||
background-color: var(--green-600);
|
||||
}
|
||||
.kx-item details summary .verificationStatus[data-value="success"]::after {
|
||||
content: "✔";
|
||||
}
|
||||
.kx-item details summary .verificationStatus[data-value="failed"] {
|
||||
background-color: var(--red-400);
|
||||
}
|
||||
.kx-item details summary .verificationStatus[data-value="failed"]::after {
|
||||
content: "✕";
|
||||
}
|
||||
.kx-item details summary .verificationStatus[data-value="running"] .inProgress {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.kx-item details .subsection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
.kx-item details .subsection > img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.kx-item details .inProgress {
|
||||
font-size: 10px;
|
||||
margin: 50px auto;
|
||||
text-indent: -9999em;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background: var(--purple-400);
|
||||
background: -moz-linear-gradient(left, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%);
|
||||
background: -webkit-linear-gradient(left, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%);
|
||||
background: -o-linear-gradient(left, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%);
|
||||
background: -ms-linear-gradient(left, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%);
|
||||
background: linear-gradient(to right, var(--purple-400) 10%, rgba(255, 255, 255, 0) 42%);
|
||||
position: relative;
|
||||
-webkit-animation: load3 1.4s infinite linear;
|
||||
animation: load3 1.4s infinite linear;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
.kx-item details .inProgress:before {
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
background: var(--purple-400);
|
||||
border-radius: 100% 0 0 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
content: '';
|
||||
}
|
||||
.kx-item details .inProgress:after {
|
||||
background: var(--purple-100);
|
||||
width: 65%;
|
||||
height: 65%;
|
||||
border-radius: 50%;
|
||||
content: '';
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.kx-item details button {
|
||||
padding: 0.4rem 0.8rem;
|
||||
margin-right: 8px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
background-color: #fff;
|
||||
border: solid 2px var(--purple-400);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.kx-item details button:hover {
|
||||
background-color: var(--purple-500);
|
||||
border-color: var(--purple-500);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
.kx-item details summary .claim__description p {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.kx-item details summary .claim__links a, p.subtle-links a {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 480px) {
|
||||
summary .claim__description p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.kx-item details summary .verificationStatus {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
.kx-item details .inProgress {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes load3 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes load3 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue