diff --git a/icon-button.js b/button-social.js similarity index 89% rename from icon-button.js rename to button-social.js index 1e440d3..eca435f 100644 --- a/icon-button.js +++ b/button-social.js @@ -1,4 +1,5 @@ -class BotonSocial extends HTMLElement { +class SocialButton extends HTMLElement { + static TAG = 'button-social' static css = ` :host { all: initial; @@ -43,7 +44,7 @@ class BotonSocial extends HTMLElement { const style = document.createElement("style"); const container = document.createElement("div"); - style.innerHTML = BotonSocial.css; + style.innerHTML = SocialButton.css; container.innerHTML = ` ${this.getAttribute( `; @@ -52,4 +53,4 @@ class BotonSocial extends HTMLElement { } } -customElements.define('button-social', BotonSocial); +customElements.define(SocialButton.TAG, SocialButton); diff --git a/button.js b/button.js index 2f1b149..80c5833 100644 --- a/button.js +++ b/button.js @@ -1,8 +1,10 @@ - - class ButtonVirto extends HTMLElement { - static TAG = 'button-virto' + static TAG = 'virto-button' static css = ` + :host { + display: inline-block; + width: 100%; + } button { font-family: Outfit, sans-serif; cursor: pointer; @@ -11,34 +13,60 @@ class ButtonVirto extends HTMLElement { min-height: 44px; padding: 12px; border-radius: 1000px; - border: 1px solid var(--colors-alpha-dark-100, #1A1A1A1F) - opacity: 0px; + border: 1px solid #1A1A1A1F; + opacity: 1; background-color: var(--color-fill-btn); color: var(--color-bg); transition: background-color 500ms ease; - &:hover, &:focus { - background-color: var(--color-accent); - } } - -`; + button:hover { + background-color: var(--color-accent); + } + :host([variant="secondary"]) button { + background-color: var(--color-input); + color: var(--color-txt); + border: 1px solid var(--color-alt-rgb); + } + :host([variant="secondary"]) button:hover, + :host([variant="secondary"]) button:focus { + background-color: var(--color-fill-social-btn); + } + `; constructor() { super(); this.attachShadow({ mode: 'open' }); + } + + connectedCallback() { + this.render(); + } + + render() { const style = document.createElement("style"); const btn = document.createElement("button"); - style.innerHTML = ButtonVirto.css; - btn.innerText = this.getAttribute("label") || "Button"; + style.textContent = ButtonVirto.css; + btn.textContent = this.getAttribute("label") || "Button"; + this.shadowRoot.innerHTML = ''; this.shadowRoot.append(style, btn); } - connectedCallback() { - this.addEventListener('click', function () { - alert('auch') - }) + static get observedAttributes() { + return ['label', 'variant']; + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'label' && this.shadowRoot) { + const btn = this.shadowRoot.querySelector('button'); + if (btn) { + btn.textContent = newValue || "Button"; + } + } + if (name === 'variant') { + this.render(); + } } - } +} - customElements.define(ButtonVirto.TAG, ButtonVirto); \ No newline at end of file +customElements.define(ButtonVirto.TAG, ButtonVirto); \ No newline at end of file diff --git a/checkbox.js b/checkbox.js new file mode 100644 index 0000000..d111d81 --- /dev/null +++ b/checkbox.js @@ -0,0 +1,68 @@ +class CustomCheckbox extends HTMLElement { + static TAG = 'checkbox-virto'; + static css = ` + :host { + display: block; + width: 100%; + } + .checkbox-container { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + } + input[type="checkbox"] { + appearance: none; + width: 20px; + height: 20px; + border-radius: 4px; + background-color: var(--checkbox-bg, #e0e0e0); + border: 1px solid var(--checkbox-border, #ccc); + display: grid; + place-content: center; + margin-right: 10px; + } + input[type="checkbox"]:checked { + background-color: var(--checkbox-checked-bg, #24AF37); + border-color: var(--checkbox-checked-border, #24AF37); + } + input[type="checkbox"]:checked::before { + content: '✔'; + color: var(--checkbox-check-color, white); + font-size: 14px; + } + label { + color: var(--checkbox-label-color, #333); + font-size: 16px; + text-align: center; + } + `; + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + } + + connectedCallback() { + this.render(); + } + + render() { + const style = document.createElement('style'); + style.textContent = CustomCheckbox.css; + + const container = document.createElement('div'); + container.className = 'checkbox-container'; + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + + const label = document.createElement('label'); + label.textContent = this.getAttribute('label') || 'Checkbox Label'; + + container.append(checkbox, label); + this.shadowRoot.append(style, container); + } +} + +customElements.define(CustomCheckbox.TAG, CustomCheckbox); \ No newline at end of file diff --git a/dialog.js b/dialog.js index 6396ea7..52faa91 100644 --- a/dialog.js +++ b/dialog.js @@ -1,74 +1,115 @@ -class DialogoModal extends HTMLElement { - static css = ` - :host { - all: initial; - display: flex; - flex: 1; - justify-content: center; - align-items: center; - height: 100dvh; - overflow-x: auto; - } +import 'https://early.webawesome.com/webawesome@3.0.0-alpha.7/dist/components/dialog/dialog.js'; - div { - display: flex; - flex-direction: column; - gap: 1em; - font-family: Outfit, sans-serif; - width: 100%; - max-width: 528px; - height: fit-content; - background: linear-gradient(0deg, rgba(231, 247, 232, 0.5), rgba(231, 247, 232, 0.5)), - radial-gradient(84.04% 109.28% at 10.3% 12.14%, rgba(86, 201, 96, 0.5) 0%, rgba(198, 235, 199, 0) 98.5%); - border-radius: 12px; - box-shadow: 0px 2px var(--Blurblur-3, 3px) -1px rgba(26, 26, 26, 0.08), - 0px 1px var(--Blurblur-0, 0px) 0px rgba(26, 26, 26, 0.08); - backdrop-filter: blur(32px); - padding: 1em; - gap: clamp(4px, 1vw, var(--spacing7, 14px)); - opacity: 1; - } +const tagFn = fn => (strings, ...parts) => fn(parts.reduce((tpl, value, i) => `${tpl}${strings[i]}${value}`, '').concat(strings[parts.length])); +const html = tagFn(s => new DOMParser().parseFromString(``, 'text/html').querySelector('template')); +const css = tagFn(s => s); - header { - display: flex; - align-items: center; - gap: 1em; - } +const dialogTp = html` + +
+ + +
+
+ + +
+`; - header h2 { - font-size: 1.4em; - font-weight: 600; - margin: 0; - } +const dialogCss = css` +:host, wa-dialog { + font-family: 'Outfit', sans-serif !important; +} - hr { - border: none; - border-radius: 1px; - border-top: 1px solid var(--color-accent); - margin: 1em 0; - } - `; +wa-dialog::part(base) { + padding: 1em; + background: var(--color-dialog-bg); + border-radius: 12px; + box-shadow: 0px 2px var(--Blurblur-3, 3px) -1px rgba(26, 26, 26, 0.08), + 0px 1px var(--Blurblur-0, 0px) 0px rgba(26, 26, 26, 0.08); +} + +wa-dialog::part(header) { + display: flex; + align-items: center; + justify-content: space-between; +} + +wa-dialog::part(header-actions) { + order: 1; +} + +wa-dialog::part(title) { + display: flex; + align-items: center; + gap: 1em; +} + +[slot="buttons"] { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); + gap: 1em; + width: 100%; +} + +::slotted(virto-button) { + width: 100%; + min-width: 0; +} + +hr { + border-top: 1px solid var(--color-accent); +} + +[slot="label"] { + display: flex; + align-items: center; + gap: 1em; +} +`; +export class DialogoModal extends HTMLElement { + static TAG = 'virto-dialog'; + constructor() { super(); - this.attachShadow({ mode: 'open' }); - const style = document.createElement("style"); - const dialog = document.createElement("div"); - - style.innerHTML = DialogoModal.css; - this.username = ""; - - dialog.innerHTML = ` -
- -

${this.getAttribute("label") || "Login to Virto"}

-
-
- - `; - - this.shadowRoot.append(style, dialog); + this.attachShadow({ mode: "open" }); + this.shadowRoot.appendChild(dialogTp.content.cloneNode(true)); + + const style = document.createElement('style'); + style.textContent = dialogCss; + this.shadowRoot.appendChild(style); + + this.dialog = this.shadowRoot.querySelector('wa-dialog'); + this.dialog.open = false; + } + + connectedCallback() { + const nextButton = document.querySelector("[data-dialog='next']"); + const closeButton = document.querySelector("[data-dialog='close']"); + + nextButton.addEventListener("click", () => this.next()); + closeButton.addEventListener("click", () => this.close()); + } + + async open() { + await this.dialog.updateComplete; + this.dialog.open = true; + } + + async close() { + await this.dialog.updateComplete; + this.dialog.open = false; + } + + async next() { + const allDialogs = document.querySelectorAll("virto-dialog"); + const currentIndex = Array.from(allDialogs).indexOf(this); + if (currentIndex + 1 < allDialogs.length) { + await this.close(); + await allDialogs[currentIndex + 1].open(); + } } } -customElements.define('dialog-virto', DialogoModal); \ No newline at end of file +customElements.define(DialogoModal.TAG, DialogoModal); diff --git a/index.html b/index.html index 9d5f506..917cd82 100644 --- a/index.html +++ b/index.html @@ -3,10 +3,11 @@ + + + Login -
- - -
- - - -
    - - - - - -
-
-
- -
- + + + + + + Logo +

Login to Virto

+
+ + +
+ + +
+
+
+ + + Logo +

Login to Virto

+
+ + +
+ +
+
+
+ +
+ + - - - + \ No newline at end of file diff --git a/input-code.js b/input-code.js new file mode 100644 index 0000000..2205944 --- /dev/null +++ b/input-code.js @@ -0,0 +1,109 @@ +class VerificationCodeInput extends HTMLElement { + static TAG = 'input-code' + static css = ` + :host { + display: flex; + width: 100%; + gap: 8px; + } + input { + flex: 1; + min-width: 0; + box-sizing: border-box; + height: 68px; + border-radius: 12px; + padding: 0; + border: 1px solid var(--color-accent, #ccc); + text-align: center; + font-size: 1.2em; + -moz-appearance: textfield; + &:focus-visible { + outline: 2px solid var(--color-alt-rgb); + } + } + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + `; + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + } + + connectedCallback() { + this.render(); + this.setupEventListeners(); + } + + render() { + const style = document.createElement("style"); + style.textContent = VerificationCodeInput.css; + + const inputs = Array.from({ length: 6 }, () => { + const input = document.createElement("input"); + input.type = "number"; + input.min = 0; + input.max = 9; + input.required = true; + input.autocomplete = "off"; + input.inputMode = "numeric"; + return input; + }); + + this.shadowRoot.append(style, ...inputs); + } + + setupEventListeners() { + const inputs = this.shadowRoot.querySelectorAll('input'); + + inputs.forEach((input, index) => { + input.addEventListener('input', (e) => { + e.target.value = e.target.value.slice(0, 1); + if (e.target.value.length === 1 && index < inputs.length - 1) { + inputs[index + 1].focus(); + } + }); + + input.addEventListener('keydown', (e) => { + if (e.key === 'Backspace' && e.target.value.length === 0 && index > 0) { + inputs[index - 1].focus(); + } + }); + }); + + this.addEventListener('paste', this.handlePaste.bind(this)); + } + + handlePaste(e) { + e.preventDefault(); + const pastedText = (e.clipboardData || window.clipboardData).getData('text'); + const inputs = this.shadowRoot.querySelectorAll('input'); + const digits = pastedText.replace(/\D/g, '').split('').slice(0, 6); + + inputs.forEach((input, index) => { + input.value = digits[index] || ''; + }); + + if (digits.length > 0) { + inputs[Math.min(digits.length, 5)].focus(); + } + } + + getValue() { + return Array.from(this.shadowRoot.querySelectorAll('input')) + .map(input => input.value) + .join(''); + } + + reset() { + this.shadowRoot.querySelectorAll('input').forEach(input => { + input.value = ''; + }); + this.shadowRoot.querySelector('input').focus(); + } +} + +customElements.define(VerificationCodeInput.TAG, VerificationCodeInput); \ No newline at end of file diff --git a/input.js b/input.js index 5f34d31..bfe1277 100644 --- a/input.js +++ b/input.js @@ -1,34 +1,54 @@ - class Input extends HTMLElement { - + static TAG = 'virto-input' static css = ` :host { display: block; width: 100%; } - input { + input, textarea { width: 100%; box-sizing: border-box; line-height: 28px; border-radius: 12px; padding: 1em; border: 1px solid var(--color-accent); - &:focus { - outline: 1px solid var(--color-fill-btn); - } + font-family: Outfit, sans-serif; + } + input:focus { + outline: 1px solid var(--color-fill-btn); + } + input:invalid { + border-color: red; } -`; + textarea { + resize: vertical; + min-height: 100px; + } + `; constructor() { super(); this.attachShadow({ mode: 'open' }); - const style = document.createElement("style"); - const input = document.createElement("input"); + } - style.innerHTML = Input.css; + connectedCallback() { + this.render(); + } - this.shadowRoot.append(style, input); + render() { + const style = document.createElement("style"); + const type = this.getAttribute('type') || 'text'; + const placeholder = this.getAttribute('aria-placeholder') || ''; + const required = this.hasAttribute('required'); + + this.shadowRoot.innerHTML = ` + + ${type === 'textarea' + ? `` + : `` + } + `; } } - - customElements.define('input-virto', Input); + +customElements.define(Input.TAG, Input); \ No newline at end of file diff --git a/link.js b/link.js new file mode 100644 index 0000000..4ad6410 --- /dev/null +++ b/link.js @@ -0,0 +1,135 @@ +class LinkAuthenticate extends HTMLElement { + static TAG = 'link-authenticate'; + static css = ` + :host { + display: block; + width: 100%; + } + .copy-container { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + box-sizing: border-box; + line-height: 28px; + border-radius: 35px; + padding: 0.5em 1em; + border: 1px solid var(--color-accent); + background-color: var(--color-input); + font-family: inherit; + font-size: inherit; + color: var(--color-text, black); + cursor: pointer; + transition: all 0.3s ease; + } + .copy-container:hover, .copy-container:focus-within { + border-color: var(--color-accent, #ccc); + outline: none; + } + .copy-container:active { + background-color: var(--color-accent-light, #e0e0e0); + } + .link { + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-decoration: none; + color: var(--color-text, black); + } + .icon { + margin-left: 8px; + cursor: pointer; + user-select: none; + } + .icon svg { + width: 20px; + height: 20px; + fill: currentColor; + } + `; + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + } + + connectedCallback() { + this.render(); + this.setupEventListeners(); + } + + render() { + const style = document.createElement('style'); + style.textContent = LinkAuthenticate.css; + + const container = document.createElement('div'); + container.className = 'copy-container'; + container.tabIndex = 0; + + const link = document.createElement('span'); + link.className = 'link'; + link.textContent = this.getAttribute('link') || 'https://virto.network/invoice/go/lkewiow91TIB4235f23fd'; + + const icon = document.createElement('span'); + icon.className = 'icon'; + icon.innerHTML = ` + + + + `; + + container.append(link, icon); + this.shadowRoot.append(style, container); + } + + setupEventListeners() { + const container = this.shadowRoot.querySelector('.copy-container'); + const link = this.shadowRoot.querySelector('.link'); + + container.addEventListener('click', (e) => { + if (e.target.closest('.icon')) { + this.copyToClipboard(link.textContent); + } + }); + } + + copyToClipboard(text) { + navigator.clipboard.writeText(text).then(() => { + this.showTooltip('Copied!'); + }).catch(() => { + this.showTooltip('Failed to copy'); + }); + } + + showTooltip(message) { + const tooltip = document.createElement('div'); + tooltip.textContent = message; + tooltip.style.cssText = ` + position: absolute; + background: #333; + color: white; + padding: 5px 10px; + border-radius: 5px; + font-size: 14px; + top: 100%; + left: 50%; + transform: translateX(-50%); + opacity: 0; + transition: opacity 0.3s; + `; + + this.shadowRoot.querySelector('.copy-container').appendChild(tooltip); + + tooltip.offsetHeight; + + tooltip.style.opacity = '1'; + + setTimeout(() => { + tooltip.style.opacity = '0'; + tooltip.addEventListener('transitionend', () => tooltip.remove()); + }, 2000); + } +} + +customElements.define(LinkAuthenticate.TAG, LinkAuthenticate); \ No newline at end of file diff --git a/login.js b/login.js new file mode 100644 index 0000000..d243438 --- /dev/null +++ b/login.js @@ -0,0 +1,37 @@ + + +const tagFn = fn => (strings, ...parts) => fn(parts.reduce((tpl, value, i) => `${tpl}${strings[i]}${value}`, '').concat(strings[parts.length])); +const html = tagFn(s => new DOMParser().parseFromString(``, 'text/html').querySelector('template')); +const css = tagFn(s => s); + +const dialogTp = html` +
+ +
+`; + +const dialogCss = css` +:host { + display: flex; + height: 90dvh; + width: 90vw; + align-items: center; + border: 3px solid red; +} +`; + +export class Login extends HTMLElement { + static TAG = 'virto-login'; + + constructor() { + super(); + this.attachShadow({ mode: "open" }); + this.shadowRoot.appendChild(dialogTp.content.cloneNode(true)); + + const style = document.createElement('style'); + style.textContent = dialogCss; + this.shadowRoot.appendChild(style); + } +} + +customElements.define(Login.TAG, Login); diff --git a/logo.js b/logo.js index ee3939e..658a322 100644 --- a/logo.js +++ b/logo.js @@ -1,9 +1,10 @@ class Logo extends HTMLElement { + static TAG = 'community-logo' static css = ` :host { all: initial; display: block; - width: clamp(100px, 10vw, 200px); + width: clamp(70px, 5vw, 200px); height: auto; display: flex; justify-content: center; @@ -32,4 +33,4 @@ class Logo extends HTMLElement { } } -customElements.define('community-logo', Logo); +customElements.define(Logo.TAG, Logo); diff --git a/public/Welcome.svg b/public/Welcome.svg new file mode 100644 index 0000000..380c2e5 --- /dev/null +++ b/public/Welcome.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/qr.svg b/public/qr.svg new file mode 100644 index 0000000..a7c1cc8 --- /dev/null +++ b/public/qr.svg @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/verificated.svg b/public/verificated.svg new file mode 100644 index 0000000..1a70638 --- /dev/null +++ b/public/verificated.svg @@ -0,0 +1,4 @@ + + + +