mirror of
https://github.com/uthvah/vaultguard.git
synced 2026-07-22 06:44:51 +00:00
commit
2097ca42b2
4 changed files with 477 additions and 141 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -16,5 +16,8 @@ data.json
|
|||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
||||
# VaultGuard
|
||||
data.json
|
||||
styles2.css
|
||||
locksidian-guard.json
|
||||
296
index.js
296
index.js
|
|
@ -37,7 +37,13 @@ const DEFAULT_SETTINGS = {
|
|||
fontWeight: '500',
|
||||
fontStyle: 'normal',
|
||||
clockFormat: '24h',
|
||||
atmosphere: 'minimal',
|
||||
clockStyle: 'digital',
|
||||
overlayOpacity: 30,
|
||||
widgets: {
|
||||
showDate: true,
|
||||
showUsername: true,
|
||||
clock: true,
|
||||
},
|
||||
inputRadius: 100,
|
||||
shakeIntensity: 6,
|
||||
}
|
||||
|
|
@ -195,6 +201,173 @@ class ProgressModal extends obsidian.Modal {
|
|||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ANALOGUE CLOCK HELPERS
|
||||
// ============================================================================
|
||||
|
||||
function buildAnalogueClockSVG() {
|
||||
const ns = 'http://www.w3.org/2000/svg';
|
||||
const svg = document.createElementNS(ns, 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 200 200');
|
||||
svg.setAttribute('class', 'vg-analogue-clock');
|
||||
svg.setAttribute('aria-hidden', 'true');
|
||||
|
||||
// Outer ring
|
||||
const ring = document.createElementNS(ns, 'circle');
|
||||
ring.setAttribute('cx', '100'); ring.setAttribute('cy', '100');
|
||||
ring.setAttribute('r', '88');
|
||||
ring.setAttribute('fill', 'none');
|
||||
ring.setAttribute('stroke', 'rgba(255,255,255,0.30)');
|
||||
ring.setAttribute('stroke-width', '1.5');
|
||||
svg.appendChild(ring);
|
||||
|
||||
// Hour tick marks — 12 ticks, 4 quarter ticks longer/bolder
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const isQ = i % 3 === 0;
|
||||
const angle = (i * 30) * Math.PI / 180;
|
||||
const inner = isQ ? 75 : 80;
|
||||
const tick = document.createElementNS(ns, 'line');
|
||||
tick.setAttribute('x1', (100 + Math.sin(angle) * inner).toFixed(3));
|
||||
tick.setAttribute('y1', (100 - Math.cos(angle) * inner).toFixed(3));
|
||||
tick.setAttribute('x2', (100 + Math.sin(angle) * 86).toFixed(3));
|
||||
tick.setAttribute('y2', (100 - Math.cos(angle) * 86).toFixed(3));
|
||||
tick.setAttribute('stroke', isQ ? 'rgba(255,255,255,0.70)' : 'rgba(255,255,255,0.40)');
|
||||
tick.setAttribute('stroke-width', isQ ? '2.5' : '1.5');
|
||||
tick.setAttribute('stroke-linecap', 'round');
|
||||
svg.appendChild(tick);
|
||||
}
|
||||
|
||||
// Hour hand — short and thick
|
||||
const hourHand = document.createElementNS(ns, 'line');
|
||||
hourHand.setAttribute('x1', '100'); hourHand.setAttribute('y1', '108');
|
||||
hourHand.setAttribute('x2', '100'); hourHand.setAttribute('y2', '52');
|
||||
hourHand.setAttribute('stroke', 'rgba(255,255,255,0.92)');
|
||||
hourHand.setAttribute('stroke-width', '4.5');
|
||||
hourHand.setAttribute('stroke-linecap', 'round');
|
||||
hourHand.setAttribute('class', 'vg-clock-hour-hand');
|
||||
svg.appendChild(hourHand);
|
||||
|
||||
// Minute hand — long and slender
|
||||
const minuteHand = document.createElementNS(ns, 'line');
|
||||
minuteHand.setAttribute('x1', '100'); minuteHand.setAttribute('y1', '105');
|
||||
minuteHand.setAttribute('x2', '100'); minuteHand.setAttribute('y2', '26');
|
||||
minuteHand.setAttribute('stroke', 'rgba(255,255,255,0.78)');
|
||||
minuteHand.setAttribute('stroke-width', '2.5');
|
||||
minuteHand.setAttribute('stroke-linecap', 'round');
|
||||
minuteHand.setAttribute('class', 'vg-clock-minute-hand');
|
||||
svg.appendChild(minuteHand);
|
||||
|
||||
// Second hand — hair-thin with counterweight tail
|
||||
const secondHand = document.createElementNS(ns, 'line');
|
||||
secondHand.setAttribute('x1', '100'); secondHand.setAttribute('y1', '118');
|
||||
secondHand.setAttribute('x2', '100'); secondHand.setAttribute('y2', '22');
|
||||
secondHand.setAttribute('stroke', 'rgba(255,255,255,0.58)');
|
||||
secondHand.setAttribute('stroke-width', '1.5');
|
||||
secondHand.setAttribute('stroke-linecap', 'round');
|
||||
secondHand.setAttribute('class', 'vg-clock-second-hand');
|
||||
svg.appendChild(secondHand);
|
||||
|
||||
// Center cap
|
||||
const cap = document.createElementNS(ns, 'circle');
|
||||
cap.setAttribute('cx', '100'); cap.setAttribute('cy', '100');
|
||||
cap.setAttribute('r', '4');
|
||||
cap.setAttribute('fill', 'rgba(255,255,255,0.90)');
|
||||
svg.appendChild(cap);
|
||||
|
||||
return svg;
|
||||
}
|
||||
|
||||
function updateAnalogueClock(svgEl, dateEl) {
|
||||
const now = new Date();
|
||||
const h = now.getHours() % 12;
|
||||
const m = now.getMinutes();
|
||||
const s = now.getSeconds();
|
||||
|
||||
// Smooth continuous motion — each hand interpolates sub-unit
|
||||
const hourDeg = h * 30 + m * 0.5 + s * (0.5 / 60);
|
||||
const minuteDeg = m * 6 + s * 0.1;
|
||||
const secondDeg = s * 6;
|
||||
|
||||
const hourHand = svgEl.querySelector('.vg-clock-hour-hand');
|
||||
const minuteHand = svgEl.querySelector('.vg-clock-minute-hand');
|
||||
const secondHand = svgEl.querySelector('.vg-clock-second-hand');
|
||||
|
||||
if (hourHand) hourHand.setAttribute('transform', `rotate(${hourDeg.toFixed(3)}, 100, 100)`);
|
||||
if (minuteHand) minuteHand.setAttribute('transform', `rotate(${minuteDeg.toFixed(3)}, 100, 100)`);
|
||||
if (secondHand) secondHand.setAttribute('transform', `rotate(${secondDeg.toFixed(3)}, 100, 100)`);
|
||||
|
||||
if (dateEl) {
|
||||
dateEl.textContent = now.toLocaleDateString(undefined, {
|
||||
weekday: 'long', month: 'long', day: 'numeric'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// WIDGET REGISTRY
|
||||
// Each entry: { id, label, alwaysOn, isEnabled(visual)?, render(plugin) }
|
||||
// - alwaysOn: never filtered by user prefs
|
||||
// - isEnabled: optional extra gate (e.g. clock respects clockFormat setting)
|
||||
// - render: returns an HTMLElement; may set plugin.clockInterval etc.
|
||||
// To add a new widget: push a new descriptor here and add a toggle in settings.
|
||||
// ============================================================================
|
||||
|
||||
const WIDGET_REGISTRY = [
|
||||
{
|
||||
id: 'clock',
|
||||
label: 'Clock',
|
||||
alwaysOn: false,
|
||||
isEnabled: (visual) => visual.clockFormat !== 'off',
|
||||
render(plugin) {
|
||||
const visual = plugin.state.settings.visual;
|
||||
const widgets = visual.widgets ?? {};
|
||||
const el = document.createElement('div');
|
||||
el.className = 'vg-time-widget';
|
||||
|
||||
let dateEl = null;
|
||||
if (widgets.showDate !== false) {
|
||||
dateEl = document.createElement('div');
|
||||
dateEl.className = 'vaultguard-date';
|
||||
}
|
||||
|
||||
if (visual.clockStyle === 'analogue') {
|
||||
const svg = buildAnalogueClockSVG();
|
||||
el.appendChild(svg);
|
||||
if (dateEl) el.appendChild(dateEl);
|
||||
updateAnalogueClock(svg, dateEl);
|
||||
plugin.clockInterval = setInterval(() => updateAnalogueClock(svg, dateEl), 1000);
|
||||
} else {
|
||||
const clockEl = document.createElement('div');
|
||||
clockEl.className = 'vaultguard-clock';
|
||||
el.appendChild(clockEl);
|
||||
if (dateEl) el.appendChild(dateEl);
|
||||
plugin.updateClock(clockEl, dateEl);
|
||||
plugin.clockInterval = setInterval(() => plugin.updateClock(clockEl, dateEl), 1000);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'login',
|
||||
label: 'Login',
|
||||
alwaysOn: true,
|
||||
render(plugin) {
|
||||
const visual = plugin.state.settings.visual;
|
||||
const widgets = visual.widgets ?? {};
|
||||
const card = document.createElement('div');
|
||||
card.className = 'vg-login-card';
|
||||
if (widgets.showUsername !== false) {
|
||||
const username = document.createElement('div');
|
||||
username.className = 'vaultguard-username';
|
||||
username.textContent = visual.username;
|
||||
card.appendChild(username);
|
||||
}
|
||||
card.appendChild(plugin.createPasswordInputElement());
|
||||
return card;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// MAIN PLUGIN CLASS
|
||||
// ============================================================================
|
||||
|
|
@ -888,13 +1061,15 @@ class VaultGuardPlugin extends obsidian.Plugin {
|
|||
}
|
||||
|
||||
_buildLockScreenUI(el) {
|
||||
// Background and overlay are position:absolute on the screen root so they
|
||||
// extend behind the navbar as well as the main content area.
|
||||
el.appendChild(this.createBackgroundElement());
|
||||
el.appendChild(this.createOverlayElement());
|
||||
if (this.needsNavbar) {
|
||||
const navbar = el.createDiv({ cls: 'vaultguard-navbar' });
|
||||
if (this.needsWindowControls) navbar.appendChild(this.createWindowControls());
|
||||
}
|
||||
const mainContentEl = el.createDiv({ cls: 'vaultguard-main-content' });
|
||||
mainContentEl.appendChild(this.createBackgroundElement());
|
||||
mainContentEl.appendChild(this.createOverlayElement());
|
||||
mainContentEl.appendChild(this.createContentElement());
|
||||
setTimeout(() => {
|
||||
const input = this.lockScreenEl?.querySelector('.vaultguard-input');
|
||||
|
|
@ -960,7 +1135,7 @@ class VaultGuardPlugin extends obsidian.Plugin {
|
|||
// Render a stripped-down panel: warning + password input only.
|
||||
this._buildTamperedUI(this.lockScreenEl);
|
||||
} else {
|
||||
this.lockScreenEl.setAttribute('data-atmosphere', this.state.settings.visual.atmosphere || 'minimal');
|
||||
this.lockScreenEl.style.setProperty('--vg-overlay-opacity', ((this.state.settings.visual.overlayOpacity ?? 30) / 100).toString());
|
||||
this._buildLockScreenUI(this.lockScreenEl);
|
||||
}
|
||||
}
|
||||
|
|
@ -977,7 +1152,7 @@ class VaultGuardPlugin extends obsidian.Plugin {
|
|||
if (this.state.phase === 'TAMPERED') {
|
||||
this._buildTamperedUI(this.lockScreenEl);
|
||||
} else {
|
||||
this.lockScreenEl.setAttribute('data-atmosphere', this.state.settings.visual.atmosphere || 'minimal');
|
||||
this.lockScreenEl.style.setProperty('--vg-overlay-opacity', ((this.state.settings.visual.overlayOpacity ?? 30) / 100).toString());
|
||||
this._buildLockScreenUI(this.lockScreenEl);
|
||||
}
|
||||
}
|
||||
|
|
@ -1014,9 +1189,10 @@ class VaultGuardPlugin extends obsidian.Plugin {
|
|||
const content = document.createElement('div');
|
||||
content.className = 'vaultguard-content';
|
||||
|
||||
const visual = this.state.settings.visual;
|
||||
const visual = this.state.settings.visual;
|
||||
const widgetPrefs = visual.widgets ?? {};
|
||||
|
||||
// Apply CSS custom properties
|
||||
// CSS custom properties
|
||||
content.style.setProperty('--vaultguard-ui-scale', (visual.uiScale / 100).toString());
|
||||
content.style.setProperty('--vaultguard-input-width', `${visual.inputWidth}px`);
|
||||
content.style.setProperty('--vaultguard-input-radius', String(visual.inputRadius ?? 100));
|
||||
|
|
@ -1027,19 +1203,44 @@ class VaultGuardPlugin extends obsidian.Plugin {
|
|||
content.style.setProperty('--vaultguard-font-weight', visual.fontWeight);
|
||||
content.style.setProperty('--vaultguard-font-style', visual.fontStyle);
|
||||
|
||||
// Clock
|
||||
const clockEl = content.createEl('div', { cls: 'vaultguard-clock' });
|
||||
const dateEl = content.createEl('div', { cls: 'vaultguard-date' });
|
||||
this.updateClock(clockEl, dateEl);
|
||||
this.clockInterval = setInterval(() => this.updateClock(clockEl, dateEl), 1000);
|
||||
// Build enabled widget list from registry
|
||||
const enabled = WIDGET_REGISTRY.filter(w =>
|
||||
w.alwaysOn ||
|
||||
(widgetPrefs[w.id] !== false && (!w.isEnabled || w.isEnabled(visual)))
|
||||
);
|
||||
|
||||
// Username display
|
||||
const username = content.createEl('div', { cls: 'vaultguard-username' });
|
||||
username.textContent = visual.username;
|
||||
// Tiling grid — master-stack layout
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'vg-tile-grid';
|
||||
|
||||
// Password input
|
||||
content.appendChild(this.createPasswordInputElement());
|
||||
if (enabled.length <= 1) {
|
||||
// Single widget (clock off): full-width fallback
|
||||
grid.classList.add('vg-tile-single');
|
||||
const item = document.createElement('div');
|
||||
item.className = 'vg-tile-item';
|
||||
item.appendChild(enabled[0].render(this));
|
||||
grid.appendChild(item);
|
||||
} else {
|
||||
// First widget → master pane (left); rest → stack pane (right)
|
||||
const [master, ...stack] = enabled;
|
||||
|
||||
const masterEl = document.createElement('div');
|
||||
masterEl.className = 'vg-tile-master';
|
||||
masterEl.appendChild(master.render(this));
|
||||
grid.appendChild(masterEl);
|
||||
|
||||
const stackEl = document.createElement('div');
|
||||
stackEl.className = 'vg-tile-stack';
|
||||
for (const widget of stack) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'vg-tile-item';
|
||||
item.appendChild(widget.render(this));
|
||||
stackEl.appendChild(item);
|
||||
}
|
||||
grid.appendChild(stackEl);
|
||||
}
|
||||
|
||||
content.appendChild(grid);
|
||||
return content;
|
||||
}
|
||||
|
||||
|
|
@ -1130,7 +1331,7 @@ class VaultGuardPlugin extends obsidian.Plugin {
|
|||
const fmt = this.state.settings.visual.clockFormat || '24h';
|
||||
if (fmt === 'off') {
|
||||
clockEl.style.display = 'none';
|
||||
dateEl.style.display = 'none';
|
||||
if (dateEl) dateEl.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
if (fmt === '12h') {
|
||||
|
|
@ -1138,7 +1339,7 @@ class VaultGuardPlugin extends obsidian.Plugin {
|
|||
} else {
|
||||
clockEl.textContent = now.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: false });
|
||||
}
|
||||
dateEl.textContent = now.toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' });
|
||||
if (dateEl) dateEl.textContent = now.toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' });
|
||||
}
|
||||
|
||||
_createWarningIcon() {
|
||||
|
|
@ -1480,16 +1681,42 @@ class VaultGuardSettingsTab extends obsidian.PluginSettingTab {
|
|||
containerEl.createEl('h3', { text: 'Lockscreen' });
|
||||
|
||||
new obsidian.Setting(containerEl)
|
||||
.setName('Atmosphere')
|
||||
.setDesc('Overall visual style of the lock screen.')
|
||||
.addDropdown(d => d
|
||||
.addOption('minimal', 'Minimal')
|
||||
.addOption('noir', 'Noir')
|
||||
.addOption('frosted', 'Frosted')
|
||||
.addOption('vivid', 'Vivid')
|
||||
.setValue(this.plugin.state.settings.visual.atmosphere)
|
||||
.setName('Show date')
|
||||
.setDesc('Display the date below the clock.')
|
||||
.addToggle(t => t
|
||||
.setValue(this.plugin.state.settings.visual.widgets?.showDate !== false)
|
||||
.onChange(async v => {
|
||||
this.plugin.state.settings.visual.atmosphere = v;
|
||||
this.plugin.state.settings.visual.widgets = {
|
||||
...(this.plugin.state.settings.visual.widgets ?? {}),
|
||||
showDate: v,
|
||||
};
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new obsidian.Setting(containerEl)
|
||||
.setName('Show username')
|
||||
.setDesc('Display the username inside the login card.')
|
||||
.addToggle(t => t
|
||||
.setValue(this.plugin.state.settings.visual.widgets?.showUsername !== false)
|
||||
.onChange(async v => {
|
||||
this.plugin.state.settings.visual.widgets = {
|
||||
...(this.plugin.state.settings.visual.widgets ?? {}),
|
||||
showUsername: v,
|
||||
};
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new obsidian.Setting(containerEl)
|
||||
.setName('Background darkness')
|
||||
.setDesc('How much the background is darkened behind the lockscreen (0 = none, 60 = dark).')
|
||||
.addSlider(s => s
|
||||
.setLimits(0, 60, 1)
|
||||
.setValue(this.plugin.state.settings.visual.overlayOpacity ?? 30)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async v => {
|
||||
this.plugin.state.settings.visual.overlayOpacity = v;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
|
@ -1508,6 +1735,19 @@ class VaultGuardSettingsTab extends obsidian.PluginSettingTab {
|
|||
})
|
||||
);
|
||||
|
||||
new obsidian.Setting(containerEl)
|
||||
.setName('Clock style')
|
||||
.setDesc('Digital shows a numeric readout. Analogue shows a minimal watch face.')
|
||||
.addDropdown(d => d
|
||||
.addOption('digital', 'Digital')
|
||||
.addOption('analogue', 'Analogue')
|
||||
.setValue(this.plugin.state.settings.visual.clockStyle ?? 'digital')
|
||||
.onChange(async v => {
|
||||
this.plugin.state.settings.visual.clockStyle = v;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new obsidian.Setting(containerEl)
|
||||
.setName('Lockscreen font')
|
||||
.setDesc('Applies to all text on the lock screen — clock, date, username.')
|
||||
|
|
|
|||
4
main.js
4
main.js
File diff suppressed because one or more lines are too long
315
styles.css
315
styles.css
|
|
@ -20,6 +20,16 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
/* ── Shared glass surface tokens ────────────────────────────────────────────
|
||||
All panels, navbar, and input decorations derive from these.
|
||||
Changing one variable updates every surface for instant cohesion. */
|
||||
--vg-glass-bg: rgba(255, 255, 255, 0.10);
|
||||
--vg-glass-blur: blur(30px) saturate(190%);
|
||||
--vg-glass-border: rgba(255, 255, 255, 0.18);
|
||||
--vg-glass-highlight: rgba(255, 255, 255, 0.18);
|
||||
--vg-glass-shadow: 0 20px 56px rgba(0, 0, 0, 0.50);
|
||||
--vg-glass-radius: 28px;
|
||||
}
|
||||
|
||||
.vaultguard-screen-loading {
|
||||
|
|
@ -33,37 +43,40 @@
|
|||
}
|
||||
|
||||
.vaultguard-screen.is-leaving {
|
||||
animation: unlockOut 0.45s cubic-bezier(0.4, 0, 1, 1) forwards;
|
||||
animation: unlockOut 0.4s cubic-bezier(0.4, 0, 1, 1) forwards;
|
||||
}
|
||||
|
||||
@keyframes unlockOut {
|
||||
0% { opacity: 1; transform: scale(1); filter: blur(0px); }
|
||||
100% { opacity: 0; transform: scale(1.04); filter: blur(2px); }
|
||||
100% { opacity: 0; transform: scale(1.07); filter: blur(6px); }
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
NAVBAR — shown for hidden and obsidian frame styles.
|
||||
Matches Obsidian's titlebar background so it blends seamlessly.
|
||||
Glass treatment is driven by the same surface tokens as the password bar,
|
||||
so both elements always look visually unified across all atmospheres.
|
||||
native frame: omitted entirely (OS chrome handles it).
|
||||
============================================================================ */
|
||||
|
||||
.vaultguard-navbar {
|
||||
flex-shrink: 0;
|
||||
height: 34px;
|
||||
height: 33px;
|
||||
width: 100%;
|
||||
-webkit-app-region: drag;
|
||||
background-color: var(--titlebar-background, var(--background-secondary));
|
||||
background: var(--vg-glass-bg);
|
||||
backdrop-filter: var(--vg-glass-blur);
|
||||
-webkit-backdrop-filter: var(--vg-glass-blur);
|
||||
border-bottom: 1px solid var(--vg-glass-border);
|
||||
/* Top highlight + bottom fade — visually reads as thick glass */
|
||||
box-shadow:
|
||||
inset 0 1px 0 var(--vg-glass-highlight),
|
||||
inset 0 -1px 0 rgba(0, 0, 0, 0.12);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Match Obsidian's own focused-window titlebar colour */
|
||||
body.is-focused .vaultguard-navbar {
|
||||
background-color: var(--titlebar-background-focused, var(--background-secondary-alt));
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
WINDOW CONTROLS — rendered inside .vaultguard-navbar on Windows/Linux
|
||||
where Electron's own controls are web content covered by the lockscreen.
|
||||
|
|
@ -84,7 +97,7 @@ body.is-focused .vaultguard-navbar {
|
|||
width: 46px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--titlebar-text-color, rgba(255, 255, 255, 0.55));
|
||||
color: rgba(255, 255, 255, 0.60);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
@ -101,8 +114,8 @@ body.is-focused .vaultguard-navbar {
|
|||
}
|
||||
|
||||
.vg-wc-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: var(--titlebar-text-color, white);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.vg-wc-close:hover {
|
||||
|
|
@ -120,9 +133,8 @@ body.is-focused .vaultguard-navbar {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: fadeInContent 0.6s 0.1s ease-in-out forwards;
|
||||
animation: fadeInContent 0.45s 0.08s ease-in-out forwards;
|
||||
opacity: 0;
|
||||
/* Prevent content from being selectable */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
|
@ -158,10 +170,9 @@ body.is-focused .vaultguard-navbar {
|
|||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(5px) saturate(120%);
|
||||
-webkit-backdrop-filter: blur(5px) saturate(120%);
|
||||
/* Prevent interaction */
|
||||
background-color: rgba(0, 0, 0, var(--vg-overlay-opacity, 0.30));
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
@ -173,20 +184,54 @@ body.is-focused .vaultguard-navbar {
|
|||
.vaultguard-content {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
animation: slideUpIn 0.7s 0.2s cubic-bezier(0.25, 1, 0.5, 1) forwards,
|
||||
fadeInContent 0.7s 0.2s ease-in-out forwards;
|
||||
transform: translateY(20px);
|
||||
transition: transform 0.3s ease;
|
||||
animation: slideUpIn 0.5s 0.12s cubic-bezier(0.25, 1, 0.5, 1) forwards,
|
||||
fadeInContent 0.5s 0.12s ease-in-out forwards;
|
||||
transform: translateY(12px);
|
||||
opacity: 0;
|
||||
/* Allow interaction with children */
|
||||
pointer-events: auto;
|
||||
/* Ensure content is above everything */
|
||||
isolation: isolate;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
--vaultguard-base-font: 1rem;
|
||||
font-family: var(--vaultguard-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
TILE GRID — master-stack tiling layout (Hyprland dwindle style)
|
||||
First widget = master pane (left, full height)
|
||||
Remaining widgets = stack pane (right, split equally)
|
||||
To add a widget: push to WIDGET_REGISTRY in index.js
|
||||
============================================================================ */
|
||||
|
||||
.vg-tile-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 2fr;
|
||||
gap: 20px;
|
||||
width: min(900px, 88vw);
|
||||
height: min(480px, 70vh);
|
||||
}
|
||||
|
||||
.vg-tile-single {
|
||||
grid-template-columns: 1fr;
|
||||
width: min(460px, 88vw);
|
||||
}
|
||||
|
||||
.vg-tile-master {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vg-tile-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vg-tile-item {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@keyframes slideUpIn {
|
||||
|
|
@ -195,55 +240,102 @@ body.is-focused .vaultguard-navbar {
|
|||
}
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
WIDGET PANELS — shared glass surface
|
||||
============================================================================ */
|
||||
|
||||
.vg-time-widget,
|
||||
.vg-login-card {
|
||||
background: var(--vg-glass-bg);
|
||||
backdrop-filter: var(--vg-glass-blur);
|
||||
-webkit-backdrop-filter: var(--vg-glass-blur);
|
||||
border: 1px solid var(--vg-glass-border);
|
||||
border-radius: var(--vg-glass-radius);
|
||||
box-shadow:
|
||||
var(--vg-glass-shadow),
|
||||
inset 0 1px 0 var(--vg-glass-highlight);
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.vg-time-widget {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: calc(10px * var(--vaultguard-ui-scale, 1));
|
||||
padding: calc(28px * var(--vaultguard-ui-scale, 1)) calc(40px * var(--vaultguard-ui-scale, 1));
|
||||
}
|
||||
|
||||
.vg-login-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: calc(20px * var(--vaultguard-ui-scale, 1));
|
||||
padding: calc(32px * var(--vaultguard-ui-scale, 1)) calc(36px * var(--vaultguard-ui-scale, 1)) calc(28px * var(--vaultguard-ui-scale, 1));
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
USERNAME DISPLAY
|
||||
============================================================================ */
|
||||
|
||||
.vaultguard-username {
|
||||
font-size: calc(38px * var(--vaultguard-ui-scale, 1));
|
||||
margin-bottom: calc(25px * var(--vaultguard-ui-scale, 1));
|
||||
text-shadow: 0px 2px 12px rgba(0, 0, 0, 0.5);
|
||||
font-size: calc(34px * var(--vaultguard-ui-scale, 1));
|
||||
text-shadow: 0px 1px 8px rgba(0, 0, 0, 0.5);
|
||||
font-family: var(--vaultguard-font-family, inherit);
|
||||
font-weight: var(--vaultguard-font-weight, 500);
|
||||
font-style: var(--vaultguard-font-style, normal);
|
||||
/* Prevent selection */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
/* Ensure it stays above overlay */
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.vaultguard-clock {
|
||||
font-size: calc(72px * var(--vaultguard-ui-scale, 1));
|
||||
font-weight: 200;
|
||||
letter-spacing: -0.02em;
|
||||
font-weight: 300;
|
||||
letter-spacing: -0.03em;
|
||||
color: rgba(255,255,255,0.92);
|
||||
text-shadow: 0 2px 20px rgba(0,0,0,0.3);
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 4px;
|
||||
font-family: var(--vaultguard-font-family, inherit);
|
||||
}
|
||||
|
||||
.vaultguard-date {
|
||||
font-size: calc(13px * var(--vaultguard-ui-scale, 1));
|
||||
font-weight: 400;
|
||||
color: rgba(255,255,255,0.50);
|
||||
font-size: calc(12px * var(--vaultguard-ui-scale, 1));
|
||||
font-weight: 500;
|
||||
color: rgba(255,255,255,0.78);
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: calc(40px * var(--vaultguard-ui-scale, 1));
|
||||
font-family: var(--vaultguard-font-family, inherit);
|
||||
text-shadow: 0 1px 6px rgba(0,0,0,0.45);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
PASSWORD INPUT WRAPPER & INPUT (CORRECTED FOR GLASSMORPHISM)
|
||||
ANALOGUE CLOCK
|
||||
============================================================================ */
|
||||
|
||||
.vg-analogue-clock {
|
||||
/* Fills the widget's flex space while staying square */
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
max-width: min(100%, 220px);
|
||||
aspect-ratio: 1 / 1;
|
||||
display: block;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
PASSWORD INPUT WRAPPER & INPUT
|
||||
============================================================================ */
|
||||
|
||||
.vaultguard-password-wrapper {
|
||||
position: relative;
|
||||
width: var(--vaultguard-input-width, 340px);
|
||||
max-width: 90vw;
|
||||
/* Constrained to card width in the tiling layout */
|
||||
max-width: 100%;
|
||||
transition: width 0.3s ease;
|
||||
z-index: 10;
|
||||
}
|
||||
|
|
@ -252,56 +344,60 @@ body.is-focused .vaultguard-navbar {
|
|||
animation: shake 0.4s linear;
|
||||
}
|
||||
|
||||
/* This is the corrected rule for the input bar */
|
||||
/* Input bar — "carved into" the glass card */
|
||||
.vaultguard-screen .vaultguard-input {
|
||||
width: 100%;
|
||||
|
||||
/* [THE FIX] Dark, translucent background for the glass effect */
|
||||
background: rgba(10, 10, 10, 0.25) !important;
|
||||
|
||||
/* [THE FIX] The essential blur effect */
|
||||
backdrop-filter: blur(12px) saturate(150%) !important;
|
||||
-webkit-backdrop-filter: blur(12px) saturate(150%) !important;
|
||||
|
||||
/* A subtle border to define the "edge" of the glass */
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
|
||||
background: rgba(0, 0, 0, 0.22) !important;
|
||||
backdrop-filter: none !important;
|
||||
-webkit-backdrop-filter: none !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
/* Top shadow reads as a carved recess; bottom edge gives subtle lift */
|
||||
box-shadow:
|
||||
inset 0 2px 8px rgba(0, 0, 0, 0.30),
|
||||
inset 0 1px 2px rgba(0, 0, 0, 0.20),
|
||||
0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
border-radius: calc(var(--vaultguard-input-radius, 100) * 1px);
|
||||
color: white;
|
||||
padding: calc(16px * var(--vaultguard-ui-scale, 1)) calc(30px * var(--vaultguard-ui-scale, 1));
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
padding: calc(15px * var(--vaultguard-ui-scale, 1)) calc(28px * var(--vaultguard-ui-scale, 1));
|
||||
font-size: calc(14px * var(--vaultguard-ui-scale, 1));
|
||||
letter-spacing: 0.5em;
|
||||
letter-spacing: 0.22em;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
|
||||
/* Adds a subtle shadow to the text for better readability */
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
text-shadow: 0 1px 4px rgba(0,0,0,0.5);
|
||||
caret-color: rgba(255, 255, 255, 0.80);
|
||||
}
|
||||
|
||||
.vaultguard-input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
letter-spacing: 0.1em;
|
||||
.vaultguard-screen .vaultguard-input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.7) !important;
|
||||
letter-spacing: 0.06em;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
/* Ensure the focus state also maintains the glass effect */
|
||||
.vaultguard-screen .vaultguard-input:focus {
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2),
|
||||
0 0 0 2px rgba(255, 255, 255, 0.1) inset;
|
||||
border-color: rgba(255, 255, 255, 0.32);
|
||||
box-shadow:
|
||||
inset 0 2px 8px rgba(0, 0, 0, 0.30),
|
||||
inset 0 1px 2px rgba(0, 0, 0, 0.20),
|
||||
0 0 0 3px rgba(255, 255, 255, 0.10),
|
||||
0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.vaultguard-input.is-error {
|
||||
border-color: #e74c3c !important;
|
||||
border-color: rgba(255, 90, 90, 0.65) !important;
|
||||
box-shadow:
|
||||
inset 0 2px 8px rgba(0, 0, 0, 0.30),
|
||||
0 0 0 3px rgba(255, 80, 80, 0.16) !important;
|
||||
}
|
||||
|
||||
.vaultguard-input.is-success {
|
||||
border-color: #2ecc71 !important;
|
||||
border-color: rgba(80, 220, 120, 0.65) !important;
|
||||
box-shadow:
|
||||
inset 0 2px 8px rgba(0, 0, 0, 0.30),
|
||||
0 0 0 3px rgba(80, 220, 130, 0.16) !important;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
|
|
@ -659,15 +755,40 @@ body.is-focused .vaultguard-navbar {
|
|||
RESPONSIVE ADJUSTMENTS
|
||||
============================================================================ */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.vaultguard-username {
|
||||
font-size: calc(32px * var(--vaultguard-ui-scale, 1));
|
||||
margin-bottom: calc(20px * var(--vaultguard-ui-scale, 1));
|
||||
/* Small window: stack tiles vertically so neither panel is crushed */
|
||||
@media (max-width: 640px) {
|
||||
.vg-tile-grid {
|
||||
grid-template-columns: 1fr;
|
||||
width: min(440px, 88vw);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.vaultguard-input {
|
||||
font-size: calc(16px * var(--vaultguard-ui-scale, 1));
|
||||
padding: calc(14px * var(--vaultguard-ui-scale, 1)) calc(25px * var(--vaultguard-ui-scale, 1));
|
||||
.vg-tile-master,
|
||||
.vg-tile-stack {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.vg-tile-item {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.vg-time-widget,
|
||||
.vg-login-card {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.vg-time-widget {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.vg-login-card {
|
||||
min-height: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.vaultguard-username {
|
||||
font-size: calc(26px * var(--vaultguard-ui-scale, 1));
|
||||
}
|
||||
|
||||
.vaultguard-thumbnail-grid {
|
||||
|
|
@ -702,7 +823,9 @@ body.is-focused .vaultguard-navbar {
|
|||
.vaultguard-main-content,
|
||||
.vaultguard-content,
|
||||
.vaultguard-input,
|
||||
.vaultguard-thumbnail-item {
|
||||
.vaultguard-thumbnail-item,
|
||||
.vg-time-widget,
|
||||
.vg-login-card {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
opacity: 1 !important;
|
||||
|
|
@ -714,36 +837,6 @@ body.is-focused .vaultguard-navbar {
|
|||
}
|
||||
}
|
||||
|
||||
/* === ATMOSPHERE VARIANTS === */
|
||||
[data-atmosphere="noir"] .vaultguard-overlay {
|
||||
background-color: rgba(0,0,0,0.75);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
animation: none;
|
||||
}
|
||||
[data-atmosphere="noir"] .vaultguard-screen .vaultguard-input {
|
||||
border-radius: 4px !important;
|
||||
background: rgba(0,0,0,0.65) !important;
|
||||
border: 1px solid rgba(255,255,255,0.22) !important;
|
||||
}
|
||||
[data-atmosphere="frosted"] .vaultguard-overlay {
|
||||
background-color: rgba(255,255,255,0.10);
|
||||
backdrop-filter: blur(22px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(22px) saturate(180%);
|
||||
}
|
||||
[data-atmosphere="frosted"] .vaultguard-screen .vaultguard-input {
|
||||
background: rgba(255,255,255,0.18) !important;
|
||||
border-color: rgba(255,255,255,0.30) !important;
|
||||
}
|
||||
[data-atmosphere="vivid"] .vaultguard-overlay {
|
||||
background: linear-gradient(135deg, rgba(99,102,241,0.45) 0%, rgba(168,85,247,0.45) 100%);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
}
|
||||
[data-atmosphere="vivid"] .vaultguard-screen .vaultguard-input {
|
||||
border-color: rgba(168,85,247,0.7) !important;
|
||||
box-shadow: 0 0 0 1px rgba(168,85,247,0.3), 0 4px 20px rgba(0,0,0,0.2) !important;
|
||||
}
|
||||
/* ============================================================================
|
||||
TAMPER WARNING / DANGER ZONE
|
||||
============================================================================ */
|
||||
|
|
|
|||
Loading…
Reference in a new issue