psiphon-pilot / components /power-button.js
hosseinhimself's picture
the vpn is run using this script in terminal ubuntu:
a8d9584 verified
class PowerButton extends HTMLElement {
static get observedAttributes() {
return ['active'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.active = false;
this.render();
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'active') {
this.active = newValue === 'true';
this.updateState();
}
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
position: relative;
}
.button-wrapper {
position: relative;
width: 140px;
height: 140px;
display: flex;
align-items: center;
justify-content: center;
}
.ripple {
position: absolute;
inset: 0;
border-radius: 50%;
border: 2px solid #06b6d4;
opacity: 0;
transform: scale(1);
}
.ripple.active {
animation: ripple 2s linear infinite;
}
@keyframes ripple {
0% { transform: scale(1); opacity: 0.5; }
100% { transform: scale(1.5); opacity: 0; }
}
.button {
width: 100px;
height: 100px;
border-radius: 50%;
border: none;
background: linear-gradient(145deg, #1f2937, #111827);
box-shadow:
20px 20px 40px #0b0f19,
-20px -20px 40px #27354f,
inset 0 0 0 1px rgba(255,255,255,0.1);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
position: relative;
z-index: 10;
}
.button:hover {
transform: scale(1.05);
box-shadow:
25px 25px 50px #0b0f19,
-25px -25px 50px #27354f,
inset 0 0 0 1px rgba(255,255,255,0.2);
}
.button:active {
transform: scale(0.95);
}
.button.active {
background: linear-gradient(145deg, #0891b2, #06b6d4);
box-shadow:
0 0 40px rgba(6, 182, 212, 0.5),
inset 0 0 20px rgba(255,255,255,0.2);
}
.button.active svg {
color: white;
filter: drop-shadow(0 0 10px rgba(255,255,255,0.8));
}
.icon {
width: 40px;
height: 40px;
color: #6b7280;
transition: all 0.3s ease;
}
.button:hover .icon {
color: #9ca3af;
}
.status-ring {
position: absolute;
inset: 10px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #06b6d4;
opacity: 0;
transition: opacity 0.3s;
}
.button.active + .status-ring {
opacity: 1;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
<div class="button-wrapper">
<div class="ripple" id="ripple1"></div>
<div class="ripple" id="ripple2" style="animation-delay: 0.5s"></div>
<div class="ripple" id="ripple3" style="animation-delay: 1s"></div>
<button class="button" id="powerBtn">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path>
<line x1="12" y1="2" x2="12" y2="12"></line>
</svg>
</button>
<div class="status-ring"></div>
</div>
`;
this.button = this.shadowRoot.getElementById('powerBtn');
this.ripples = this.shadowRoot.querySelectorAll('.ripple');
this.button.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('power-toggle', {
bubbles: true,
composed: true
}));
});
this.updateState();
}
updateState() {
if (!this.button) return;
if (this.active) {
this.button.classList.add('active');
this.ripples.forEach(r => r.classList.add('active'));
} else {
this.button.classList.remove('active');
this.ripples.forEach(r => r.classList.remove('active'));
}
}
}
customElements.define('power-button', PowerButton);