pitangent commited on
Commit
d5bf7fe
·
verified ·
1 Parent(s): 7b67241

Implement cryptographically secure password generator with glassmorphism UI

Browse files
Files changed (3) hide show
  1. app.js +249 -0
  2. index.html +136 -18
  3. style.css +610 -18
app.js ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener("DOMContentLoaded", () => {
2
+ // DOM Elements
3
+ const passwordOutput = document.getElementById("password-output");
4
+ const btnRegenerate = document.getElementById("btn-regenerate");
5
+ const btnCopy = document.getElementById("btn-copy");
6
+ const copyTooltip = document.getElementById("copy-tooltip");
7
+ const lengthSlider = document.getElementById("length-slider");
8
+ const lengthVal = document.getElementById("length-val");
9
+ const chkUpper = document.getElementById("chk-upper");
10
+ const chkLower = document.getElementById("chk-lower");
11
+ const chkNumbers = document.getElementById("chk-numbers");
12
+ const chkSymbols = document.getElementById("chk-symbols");
13
+ const chkExcludeAmbiguous = document.getElementById("chk-exclude-ambiguous");
14
+ const entropyVal = document.getElementById("entropy-val");
15
+ const strengthLabel = document.getElementById("strength-label");
16
+ const strengthMeter = document.getElementById("strength-meter");
17
+ const crackTime = document.getElementById("crack-time");
18
+
19
+ // Cryptographically secure random integer generation [0, max - 1]
20
+ function getRandomInt(max) {
21
+ const array = new Uint32Array(1);
22
+ const maxVal = 4294967296; // 2^32
23
+ const limit = maxVal - (maxVal % max);
24
+
25
+ do {
26
+ window.crypto.getRandomValues(array);
27
+ } while (array[0] >= limit);
28
+
29
+ return array[0] % max;
30
+ }
31
+
32
+ // Cryptographically secure Fisher-Yates shuffle
33
+ function secureShuffle(array) {
34
+ for (let i = array.length - 1; i > 0; i--) {
35
+ const j = getRandomInt(i + 1);
36
+ const temp = array[i];
37
+ array[i] = array[j];
38
+ array[j] = temp;
39
+ }
40
+ return array;
41
+ }
42
+
43
+ // Core Password Generation Logic
44
+ function generatePassword(length, useUpper, useLower, useNumbers, useSymbols, excludeAmbiguous) {
45
+ let upperPool = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
46
+ let lowerPool = "abcdefghijklmnopqrstuvwxyz";
47
+ let numberPool = "0123456789";
48
+ let symbolPool = "!@#$%^&*()_+-=[]{}|;':\",./<>?~`";
49
+
50
+ if (excludeAmbiguous) {
51
+ upperPool = upperPool.replace(/[IO]/g, "");
52
+ lowerPool = lowerPool.replace(/[lo]/g, "");
53
+ numberPool = numberPool.replace(/[01]/g, "");
54
+ symbolPool = symbolPool.replace(/[|]/g, ""); // Exclude vertical bar as it looks like I/l/1
55
+ }
56
+
57
+ const pools = [];
58
+ if (useUpper) pools.push(upperPool);
59
+ if (useLower) pools.push(lowerPool);
60
+ if (useNumbers) pools.push(numberPool);
61
+ if (useSymbols) pools.push(symbolPool);
62
+
63
+ if (pools.length === 0) return { password: "", poolSize: 0 };
64
+
65
+ const combinedPool = pools.join("");
66
+ const passwordChars = [];
67
+
68
+ // 1. Guarantee at least one character from each selected class to satisfy constraints
69
+ pools.forEach(pool => {
70
+ const randIdx = getRandomInt(pool.length);
71
+ passwordChars.push(pool[randIdx]);
72
+ });
73
+
74
+ // 2. Fill the remaining spots up to target length
75
+ while (passwordChars.length < length) {
76
+ const randIdx = getRandomInt(combinedPool.length);
77
+ passwordChars.push(combinedPool[randIdx]);
78
+ }
79
+
80
+ // 3. Shuffle array elements securely to remove sequential pattern
81
+ secureShuffle(passwordChars);
82
+
83
+ return {
84
+ password: passwordChars.join(""),
85
+ poolSize: combinedPool.length
86
+ };
87
+ }
88
+
89
+ // Determine Strength and Crack Time Estimates
90
+ function getStrengthMetrics(entropy) {
91
+ if (entropy === 0) {
92
+ return {
93
+ rating: 0,
94
+ label: "Rating: -",
95
+ crackTime: "Select at least one character class",
96
+ colorClass: ""
97
+ };
98
+ }
99
+ if (entropy < 28) {
100
+ return {
101
+ rating: 0,
102
+ label: "Rating: 0/5 (Very Weak)",
103
+ crackTime: "Can be broken in seconds/minutes",
104
+ colorClass: "strength-label-0"
105
+ };
106
+ } else if (entropy < 40) {
107
+ return {
108
+ rating: 1,
109
+ label: "Rating: 1/5 (Weak)",
110
+ crackTime: "Can be broken in hours",
111
+ colorClass: "strength-label-1"
112
+ };
113
+ } else if (entropy < 60) {
114
+ return {
115
+ rating: 2,
116
+ label: "Rating: 2/5 (Fair)",
117
+ crackTime: "Can be broken in days or months",
118
+ colorClass: "strength-label-2"
119
+ };
120
+ } else if (entropy < 80) {
121
+ return {
122
+ rating: 3,
123
+ label: "Rating: 3/5 (Good)",
124
+ crackTime: "Can be broken in years",
125
+ colorClass: "strength-label-3"
126
+ };
127
+ } else if (entropy < 100) {
128
+ return {
129
+ rating: 4,
130
+ label: "Rating: 4/5 (Strong)",
131
+ crackTime: "Takes decades to break",
132
+ colorClass: "strength-label-4"
133
+ };
134
+ } else {
135
+ return {
136
+ rating: 5,
137
+ label: "Rating: 5/5 (Centurial)",
138
+ crackTime: "Takes centuries or millennia to break",
139
+ colorClass: "strength-label-5"
140
+ };
141
+ }
142
+ }
143
+
144
+ // Update UI Elements
145
+ function updateApp() {
146
+ const length = parseInt(lengthSlider.value, 10);
147
+ lengthVal.textContent = length;
148
+
149
+ const useUpper = chkUpper.checked;
150
+ const useLower = chkLower.checked;
151
+ const useNumbers = chkNumbers.checked;
152
+ const useSymbols = chkSymbols.checked;
153
+ const excludeAmbiguous = chkExcludeAmbiguous.checked;
154
+
155
+ const result = generatePassword(length, useUpper, useLower, useNumbers, useSymbols, excludeAmbiguous);
156
+
157
+ passwordOutput.value = result.password;
158
+
159
+ // Entropy: L * log2(R)
160
+ let entropy = 0;
161
+ if (result.poolSize > 0 && result.password.length > 0) {
162
+ entropy = result.password.length * Math.log2(result.poolSize);
163
+ }
164
+ entropyVal.textContent = entropy.toFixed(1);
165
+
166
+ // Strength metrics
167
+ const metrics = getStrengthMetrics(entropy);
168
+ strengthLabel.textContent = metrics.label;
169
+ strengthLabel.className = "metric-label " + metrics.colorClass;
170
+ crackTime.textContent = metrics.crackTime;
171
+
172
+ // Visual Strength Meter Bars Update
173
+ const segments = strengthMeter.querySelectorAll(".meter-segment");
174
+ segments.forEach(segment => {
175
+ segment.className = "meter-segment";
176
+ });
177
+
178
+ if (result.poolSize > 0) {
179
+ const activeCount = Math.max(1, metrics.rating); // light up at least 1 bar if pool is selected
180
+ for (let i = 0; i < activeCount; i++) {
181
+ segments[i].classList.add(`active-${metrics.rating}`);
182
+ }
183
+ }
184
+ }
185
+
186
+ // Clipboard Copy Action
187
+ function copyToClipboard(text) {
188
+ if (navigator.clipboard && navigator.clipboard.writeText) {
189
+ return navigator.clipboard.writeText(text);
190
+ } else {
191
+ const textArea = document.createElement("textarea");
192
+ textArea.value = text;
193
+ textArea.style.position = "fixed";
194
+ document.body.appendChild(textArea);
195
+ textArea.focus();
196
+ textArea.select();
197
+ try {
198
+ document.execCommand("copy");
199
+ document.body.removeChild(textArea);
200
+ return Promise.resolve();
201
+ } catch (err) {
202
+ document.body.removeChild(textArea);
203
+ return Promise.reject(err);
204
+ }
205
+ }
206
+ }
207
+
208
+ // Event Handlers
209
+ lengthSlider.addEventListener("input", updateApp);
210
+ [chkUpper, chkLower, chkNumbers, chkSymbols, chkExcludeAmbiguous].forEach(el => {
211
+ el.addEventListener("change", updateApp);
212
+ });
213
+
214
+ btnRegenerate.addEventListener("click", () => {
215
+ // Trigger subtle spin animation on click
216
+ const icon = btnRegenerate.querySelector(".icon-refresh");
217
+ icon.classList.add("spinning");
218
+ setTimeout(() => {
219
+ icon.classList.remove("spinning");
220
+ }, 500);
221
+
222
+ updateApp();
223
+ });
224
+
225
+ btnCopy.addEventListener("click", () => {
226
+ if (!passwordOutput.value) return;
227
+
228
+ copyToClipboard(passwordOutput.value)
229
+ .then(() => {
230
+ btnCopy.classList.add("copied");
231
+ copyTooltip.textContent = "Copied!";
232
+
233
+ setTimeout(() => {
234
+ btnCopy.classList.remove("copied");
235
+ copyTooltip.textContent = "Copy";
236
+ }, 2000);
237
+ })
238
+ .catch(err => {
239
+ console.error("Copy failed:", err);
240
+ copyTooltip.textContent = "Failed!";
241
+ setTimeout(() => {
242
+ copyTooltip.textContent = "Copy";
243
+ }, 2000);
244
+ });
245
+ });
246
+
247
+ // Initialize state
248
+ updateApp();
249
+ });
index.html CHANGED
@@ -1,19 +1,137 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Fortress - Cryptographically Secure Password Generator</title>
7
+ <!-- Google Fonts Inter -->
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="style.css" />
12
+ </head>
13
+ <body>
14
+ <div class="background-decor">
15
+ <div class="glow-orb orb-1"></div>
16
+ <div class="glow-orb orb-2"></div>
17
+ </div>
18
+
19
+ <div class="app-container">
20
+ <header class="app-header">
21
+ <div class="logo">
22
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="logo-icon">
23
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
24
+ <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
25
+ </svg>
26
+ <h1>FORTRESS</h1>
27
+ </div>
28
+ <p class="subtitle">Cryptographically Secure Passwords</p>
29
+ </header>
30
+
31
+ <main class="glass-card">
32
+ <!-- Password Display Section -->
33
+ <div class="password-display-wrapper">
34
+ <input type="text" id="password-output" class="password-input" readonly placeholder="Select options..." value="" />
35
+
36
+ <div class="display-actions">
37
+ <!-- Refresh / Regenerate Button -->
38
+ <button id="btn-regenerate" class="action-btn" title="Regenerate Password" aria-label="Regenerate Password">
39
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-refresh">
40
+ <path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67"></path>
41
+ </svg>
42
+ </button>
43
+ <!-- Copy Button -->
44
+ <button id="btn-copy" class="action-btn copy-btn" title="Copy to Clipboard" aria-label="Copy to Clipboard">
45
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-copy">
46
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
47
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
48
+ </svg>
49
+ <span class="tooltip" id="copy-tooltip">Copy</span>
50
+ </button>
51
+ </div>
52
+ </div>
53
+
54
+ <!-- Parameters Configuration Section -->
55
+ <div class="controls-section">
56
+ <!-- Length Slider -->
57
+ <div class="control-group">
58
+ <div class="control-header">
59
+ <label for="length-slider">Password Length</label>
60
+ <span id="length-val" class="badge">16</span>
61
+ </div>
62
+ <div class="slider-wrapper">
63
+ <input type="range" id="length-slider" min="8" max="64" value="16" class="range-slider" />
64
+ </div>
65
+ </div>
66
+
67
+ <!-- Character Group Checkboxes -->
68
+ <div class="control-group">
69
+ <label class="section-label">Character Classes</label>
70
+ <div class="checkbox-grid">
71
+ <label class="custom-checkbox">
72
+ <input type="checkbox" id="chk-upper" checked />
73
+ <span class="checkmark"></span>
74
+ <span class="label-text">Uppercase (A-Z)</span>
75
+ </label>
76
+ <label class="custom-checkbox">
77
+ <input type="checkbox" id="chk-lower" checked />
78
+ <span class="checkmark"></span>
79
+ <span class="label-text">Lowercase (a-z)</span>
80
+ </label>
81
+ <label class="custom-checkbox">
82
+ <input type="checkbox" id="chk-numbers" checked />
83
+ <span class="checkmark"></span>
84
+ <span class="label-text">Numbers (0-9)</span>
85
+ </label>
86
+ <label class="custom-checkbox">
87
+ <input type="checkbox" id="chk-symbols" checked />
88
+ <span class="checkmark"></span>
89
+ <span class="label-text">Symbols (&@#...)</span>
90
+ </label>
91
+ </div>
92
+ </div>
93
+
94
+ <!-- Extra Options Toggle -->
95
+ <div class="control-group border-top">
96
+ <label class="custom-checkbox toggle-option">
97
+ <input type="checkbox" id="chk-exclude-ambiguous" checked />
98
+ <span class="checkmark"></span>
99
+ <span class="label-text">Exclude Ambiguous Characters <span class="subtext">(e.g., 1, l, I, 0, O)</span></span>
100
+ </label>
101
+ </div>
102
+ </div>
103
+
104
+ <!-- Password Strength & Metrics Section -->
105
+ <div class="metrics-section">
106
+ <div class="metrics-header">
107
+ <span class="metric-label">Entropy: <strong id="entropy-val">0.0</strong> bits</span>
108
+ <span class="metric-label" id="strength-label">Rating: -</span>
109
+ </div>
110
+
111
+ <!-- 5-Segment Strength Meter -->
112
+ <div class="strength-meter" id="strength-meter">
113
+ <div class="meter-segment"></div>
114
+ <div class="meter-segment"></div>
115
+ <div class="meter-segment"></div>
116
+ <div class="meter-segment"></div>
117
+ <div class="meter-segment"></div>
118
+ </div>
119
+
120
+ <div class="crack-time-wrapper">
121
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon-clock">
122
+ <circle cx="12" cy="12" r="10"></circle>
123
+ <polyline points="12 6 12 12 16 14"></polyline>
124
+ </svg>
125
+ <span id="crack-time">Select at least one character class</span>
126
+ </div>
127
+ </div>
128
+ </main>
129
+
130
+ <footer class="app-footer">
131
+ <p>Secured by Web Crypto API. Zero server communication.</p>
132
+ </footer>
133
+ </div>
134
+
135
+ <script src="app.js"></script>
136
+ </body>
137
  </html>
style.css CHANGED
@@ -1,28 +1,620 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
 
 
 
 
 
 
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
 
1
+ /* Modern CSS Reset & CSS Variables */
2
+ :root {
3
+ --bg-color: #0b0d19;
4
+ --card-bg: rgba(16, 20, 38, 0.6);
5
+ --border-color: rgba(255, 255, 255, 0.08);
6
+ --text-primary: #f3f4f6;
7
+ --text-secondary: #9ca3af;
8
+ --text-muted: #6b7280;
9
+
10
+ /* Neon Accent Colors */
11
+ --accent-cyan: #00f2fe;
12
+ --accent-purple: #9b51e0;
13
+ --accent-magenta: #f35588;
14
+
15
+ /* Strength colors */
16
+ --str-0: #ff3860; /* Crimson Red */
17
+ --str-1: #ff7626; /* Neon Orange */
18
+ --str-2: #ffb800; /* Neon Amber */
19
+ --str-3: #4facfe; /* Neon Blue */
20
+ --str-4: #00f2fe; /* Neon Cyan */
21
+ --str-5: #a18cd1; /* Neon Purple/Pink Gradient target */
22
+
23
+ --transition-speed: 0.2s;
24
+ }
25
+
26
+ * {
27
+ margin: 0;
28
+ padding: 0;
29
+ box-sizing: border-box;
30
+ }
31
+
32
  body {
33
+ background-color: var(--bg-color);
34
+ color: var(--text-primary);
35
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
36
+ min-height: 100vh;
37
+ display: flex;
38
+ justify-content: center;
39
+ align-items: center;
40
+ overflow-x: hidden;
41
+ position: relative;
42
+ }
43
+
44
+ /* Background Ambient Orbs */
45
+ .background-decor {
46
+ position: absolute;
47
+ top: 0;
48
+ left: 0;
49
+ width: 100%;
50
+ height: 100%;
51
+ z-index: 0;
52
+ overflow: hidden;
53
+ pointer-events: none;
54
+ }
55
+
56
+ .glow-orb {
57
+ position: absolute;
58
+ border-radius: 50%;
59
+ filter: blur(120px);
60
+ opacity: 0.35;
61
+ animation: pulse 12s infinite alternate ease-in-out;
62
+ }
63
+
64
+ .orb-1 {
65
+ width: 400px;
66
+ height: 400px;
67
+ background: radial-gradient(circle, var(--accent-cyan) 0%, rgba(0, 242, 254, 0) 70%);
68
+ top: -10%;
69
+ left: -10%;
70
+ }
71
+
72
+ .orb-2 {
73
+ width: 500px;
74
+ height: 500px;
75
+ background: radial-gradient(circle, var(--accent-purple) 0%, rgba(155, 81, 224, 0) 70%);
76
+ bottom: -15%;
77
+ right: -10%;
78
+ animation-delay: -6s;
79
+ }
80
+
81
+ @keyframes pulse {
82
+ 0% {
83
+ transform: translate(0, 0) scale(1);
84
+ }
85
+ 100% {
86
+ transform: translate(80px, 40px) scale(1.2);
87
+ }
88
+ }
89
+
90
+ /* App Layout Container */
91
+ .app-container {
92
+ width: 100%;
93
+ max-width: 480px;
94
+ padding: 20px;
95
+ z-index: 10;
96
+ }
97
+
98
+ .app-header {
99
+ text-align: center;
100
+ margin-bottom: 24px;
101
+ }
102
+
103
+ .logo {
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ gap: 10px;
108
+ margin-bottom: 6px;
109
+ }
110
+
111
+ .logo-icon {
112
+ color: var(--accent-cyan);
113
+ filter: drop-shadow(0 0 8px rgba(0, 242, 254, 0.6));
114
+ }
115
+
116
+ .app-header h1 {
117
+ font-size: 24px;
118
+ font-weight: 800;
119
+ letter-spacing: 4px;
120
+ background: linear-gradient(135deg, #ffffff 30%, var(--accent-cyan) 100%);
121
+ -webkit-background-clip: text;
122
+ -webkit-text-fill-color: transparent;
123
+ filter: drop-shadow(0 2px 10px rgba(0, 242, 254, 0.15));
124
+ }
125
+
126
+ .subtitle {
127
+ font-size: 13px;
128
+ color: var(--text-secondary);
129
+ letter-spacing: 0.5px;
130
+ }
131
+
132
+ /* Glassmorphic Card Panel */
133
+ .glass-card {
134
+ background: var(--card-bg);
135
+ backdrop-filter: blur(20px) saturate(180%);
136
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
137
+ border: 1px solid var(--border-color);
138
+ border-radius: 24px;
139
+ padding: 30px;
140
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
141
+ transition: box-shadow var(--transition-speed), border var(--transition-speed);
142
+ }
143
+
144
+ .glass-card:hover {
145
+ border-color: rgba(255, 255, 255, 0.12);
146
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 0 30px rgba(0, 242, 254, 0.03);
147
+ }
148
+
149
+ /* Password Display Wrapper */
150
+ .password-display-wrapper {
151
+ position: relative;
152
+ background: rgba(8, 10, 21, 0.7);
153
+ border: 1px solid rgba(255, 255, 255, 0.05);
154
+ border-radius: 16px;
155
+ padding: 8px 16px;
156
+ display: flex;
157
+ align-items: center;
158
+ justify-content: space-between;
159
+ margin-bottom: 24px;
160
+ transition: border var(--transition-speed), box-shadow var(--transition-speed);
161
+ }
162
+
163
+ .password-display-wrapper:focus-within {
164
+ border-color: rgba(0, 242, 254, 0.4);
165
+ box-shadow: 0 0 15px rgba(0, 242, 254, 0.15);
166
+ }
167
+
168
+ .password-input {
169
+ width: 100%;
170
+ background: transparent;
171
+ border: none;
172
+ outline: none;
173
+ color: #ffffff;
174
+ font-family: 'JetBrains Mono', monospace;
175
+ font-size: 19px;
176
+ font-weight: 500;
177
+ letter-spacing: 0.5px;
178
+ padding: 12px 0;
179
+ overflow-x: auto;
180
+ white-space: nowrap;
181
+ text-overflow: ellipsis;
182
+ }
183
+
184
+ @keyframes spin {
185
+ 0% { transform: rotate(0deg); }
186
+ 100% { transform: rotate(360deg); }
187
+ }
188
+
189
+ .spinning {
190
+ animation: spin 0.5s cubic-bezier(0.4, 0, 0.2, 1);
191
+ }
192
+
193
+ /* Hide scrollbar for password input */
194
+ .password-input::-webkit-scrollbar {
195
+ display: none;
196
+ }
197
+ .password-input {
198
+ -ms-overflow-style: none; /* IE and Edge */
199
+ scrollbar-width: none; /* Firefox */
200
+ }
201
+
202
+ .display-actions {
203
+ display: flex;
204
+ align-items: center;
205
+ gap: 8px;
206
+ margin-left: 12px;
207
+ }
208
+
209
+ .action-btn {
210
+ background: rgba(255, 255, 255, 0.03);
211
+ border: 1px solid rgba(255, 255, 255, 0.08);
212
+ border-radius: 12px;
213
+ width: 42px;
214
+ height: 42px;
215
+ display: flex;
216
+ justify-content: center;
217
+ align-items: center;
218
+ color: var(--text-secondary);
219
+ cursor: pointer;
220
+ position: relative;
221
+ transition: all var(--transition-speed) cubic-bezier(0.4, 0, 0.2, 1);
222
+ }
223
+
224
+ .action-btn:hover {
225
+ background: rgba(255, 255, 255, 0.08);
226
+ border-color: rgba(255, 255, 255, 0.2);
227
+ color: #ffffff;
228
+ transform: translateY(-2px);
229
+ }
230
+
231
+ .action-btn:active {
232
+ transform: translateY(0);
233
+ }
234
+
235
+ /* Specific button styles */
236
+ #btn-regenerate:hover {
237
+ color: var(--accent-purple);
238
+ box-shadow: 0 0 12px rgba(155, 81, 224, 0.3);
239
+ border-color: rgba(155, 81, 224, 0.4);
240
+ }
241
+
242
+ #btn-regenerate:hover .icon-refresh {
243
+ transform: rotate(180deg);
244
+ }
245
+
246
+ .icon-refresh {
247
+ transition: transform 0.5s ease;
248
+ }
249
+
250
+ #btn-copy:hover {
251
+ color: var(--accent-cyan);
252
+ box-shadow: 0 0 12px rgba(0, 242, 254, 0.3);
253
+ border-color: rgba(0, 242, 254, 0.4);
254
+ }
255
+
256
+ /* Tooltip design */
257
+ .tooltip {
258
+ position: absolute;
259
+ bottom: calc(100% + 10px);
260
+ left: 50%;
261
+ transform: translateX(-50%) translateY(4px);
262
+ background: rgba(15, 17, 28, 0.95);
263
+ border: 1px solid rgba(255, 255, 255, 0.15);
264
+ color: #ffffff;
265
+ font-size: 11px;
266
+ font-weight: 500;
267
+ padding: 5px 10px;
268
+ border-radius: 6px;
269
+ opacity: 0;
270
+ pointer-events: none;
271
+ white-space: nowrap;
272
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
273
+ transition: all var(--transition-speed) ease;
274
+ }
275
+
276
+ .tooltip::after {
277
+ content: '';
278
+ position: absolute;
279
+ top: 100%;
280
+ left: 50%;
281
+ transform: translateX(-50%);
282
+ border-width: 5px;
283
+ border-style: solid;
284
+ border-color: rgba(15, 17, 28, 0.95) transparent transparent transparent;
285
+ }
286
+
287
+ .action-btn:hover .tooltip {
288
+ opacity: 1;
289
+ transform: translateX(-50%) translateY(0);
290
+ }
291
+
292
+ /* Copy State Classes */
293
+ .action-btn.copied {
294
+ background: rgba(0, 242, 254, 0.1);
295
+ color: var(--accent-cyan);
296
+ border-color: var(--accent-cyan);
297
+ }
298
+
299
+ /* Settings Controls Section */
300
+ .controls-section {
301
+ display: flex;
302
+ flex-direction: column;
303
+ gap: 20px;
304
+ margin-bottom: 24px;
305
+ }
306
+
307
+ .control-group {
308
+ display: flex;
309
+ flex-direction: column;
310
+ gap: 12px;
311
+ }
312
+
313
+ .control-group.border-top {
314
+ border-top: 1px solid rgba(255, 255, 255, 0.06);
315
+ padding-top: 20px;
316
+ }
317
+
318
+ .control-header {
319
+ display: flex;
320
+ justify-content: space-between;
321
+ align-items: center;
322
+ }
323
+
324
+ .control-header label {
325
+ font-size: 14px;
326
+ font-weight: 500;
327
+ color: var(--text-secondary);
328
+ }
329
+
330
+ .badge {
331
+ background: rgba(255, 255, 255, 0.05);
332
+ border: 1px solid rgba(255, 255, 255, 0.1);
333
+ padding: 4px 10px;
334
+ border-radius: 8px;
335
+ font-family: 'JetBrains Mono', monospace;
336
+ font-size: 13px;
337
+ font-weight: 700;
338
+ color: var(--accent-cyan);
339
+ box-shadow: 0 0 10px rgba(0, 242, 254, 0.05);
340
+ }
341
+
342
+ .section-label {
343
+ font-size: 13px;
344
+ font-weight: 600;
345
+ color: var(--text-muted);
346
+ text-transform: uppercase;
347
+ letter-spacing: 1px;
348
+ }
349
+
350
+ /* Custom Range Slider */
351
+ .slider-wrapper {
352
+ position: relative;
353
+ width: 100%;
354
+ height: 6px;
355
+ margin: 10px 0;
356
+ }
357
+
358
+ .range-slider {
359
+ -webkit-appearance: none;
360
+ width: 100%;
361
+ height: 100%;
362
+ border-radius: 3px;
363
+ background: rgba(255, 255, 255, 0.06);
364
+ outline: none;
365
+ transition: background 0.3s;
366
+ }
367
+
368
+ .range-slider::-webkit-slider-thumb {
369
+ -webkit-appearance: none;
370
+ appearance: none;
371
+ width: 18px;
372
+ height: 18px;
373
+ border-radius: 50%;
374
+ background: #ffffff;
375
+ border: 2px solid var(--accent-cyan);
376
+ box-shadow: 0 0 10px rgba(0, 242, 254, 0.5);
377
+ cursor: pointer;
378
+ transition: transform var(--transition-speed) ease, background-color var(--transition-speed);
379
+ }
380
+
381
+ .range-slider::-webkit-slider-thumb:hover {
382
+ transform: scale(1.2);
383
+ background-color: var(--accent-cyan);
384
+ }
385
+
386
+ .range-slider::-moz-range-thumb {
387
+ width: 14px;
388
+ height: 14px;
389
+ border-radius: 50%;
390
+ background: #ffffff;
391
+ border: 2px solid var(--accent-cyan);
392
+ box-shadow: 0 0 10px rgba(0, 242, 254, 0.5);
393
+ cursor: pointer;
394
+ transition: transform var(--transition-speed) ease, background-color var(--transition-speed);
395
+ }
396
+
397
+ .range-slider::-moz-range-thumb:hover {
398
+ transform: scale(1.2);
399
+ background-color: var(--accent-cyan);
400
+ }
401
+
402
+ /* Grid Checkboxes */
403
+ .checkbox-grid {
404
+ display: grid;
405
+ grid-template-columns: 1fr 1fr;
406
+ gap: 12px;
407
+ }
408
+
409
+ /* Custom Checkbox Design */
410
+ .custom-checkbox {
411
+ display: flex;
412
+ align-items: center;
413
+ position: relative;
414
+ padding-left: 28px;
415
+ cursor: pointer;
416
+ font-size: 14px;
417
+ font-weight: 500;
418
+ color: var(--text-primary);
419
+ user-select: none;
420
+ height: 24px;
421
+ }
422
+
423
+ .custom-checkbox input {
424
+ position: absolute;
425
+ opacity: 0;
426
+ cursor: pointer;
427
+ height: 0;
428
+ width: 0;
429
+ }
430
+
431
+ .checkmark {
432
+ position: absolute;
433
+ top: 2px;
434
+ left: 0;
435
+ height: 18px;
436
+ width: 18px;
437
+ background-color: rgba(255, 255, 255, 0.03);
438
+ border: 1px solid rgba(255, 255, 255, 0.15);
439
+ border-radius: 6px;
440
+ transition: all var(--transition-speed) cubic-bezier(0.4, 0, 0.2, 1);
441
+ }
442
+
443
+ .custom-checkbox:hover input ~ .checkmark {
444
+ border-color: rgba(255, 255, 255, 0.3);
445
+ background-color: rgba(255, 255, 255, 0.06);
446
+ }
447
+
448
+ .custom-checkbox input:checked ~ .checkmark {
449
+ background-color: var(--accent-cyan);
450
+ border-color: var(--accent-cyan);
451
+ box-shadow: 0 0 8px rgba(0, 242, 254, 0.4);
452
+ }
453
+
454
+ /* Create the checkmark indicator */
455
+ .checkmark::after {
456
+ content: "";
457
+ position: absolute;
458
+ display: none;
459
+ left: 6px;
460
+ top: 2px;
461
+ width: 4px;
462
+ height: 9px;
463
+ border: solid #000;
464
+ border-width: 0 2px 2px 0;
465
+ transform: rotate(45deg);
466
+ }
467
+
468
+ .custom-checkbox input:checked ~ .checkmark::after {
469
+ display: block;
470
+ }
471
+
472
+ /* Special styling for custom toggle option */
473
+ .toggle-option {
474
+ padding-left: 32px;
475
+ height: auto;
476
+ }
477
+
478
+ .toggle-option .checkmark {
479
+ height: 20px;
480
+ width: 20px;
481
+ border-radius: 6px;
482
+ top: 0;
483
+ }
484
+
485
+ .toggle-option .checkmark::after {
486
+ left: 6px;
487
+ top: 2px;
488
+ }
489
+
490
+ .subtext {
491
+ display: block;
492
+ font-size: 11px;
493
+ color: var(--text-muted);
494
+ font-weight: 400;
495
+ margin-top: 2px;
496
+ }
497
+
498
+ /* Password Strength & Metrics Panel */
499
+ .metrics-section {
500
+ background: rgba(0, 0, 0, 0.18);
501
+ border: 1px solid rgba(255, 255, 255, 0.04);
502
+ border-radius: 16px;
503
+ padding: 16px 20px;
504
+ display: flex;
505
+ flex-direction: column;
506
+ gap: 12px;
507
+ }
508
+
509
+ .metrics-header {
510
+ display: flex;
511
+ justify-content: space-between;
512
+ align-items: center;
513
+ font-size: 12px;
514
+ font-weight: 600;
515
+ color: var(--text-secondary);
516
+ letter-spacing: 0.5px;
517
+ }
518
+
519
+ .metrics-header strong {
520
+ font-family: 'JetBrains Mono', monospace;
521
+ font-size: 13px;
522
+ color: #ffffff;
523
+ }
524
+
525
+ /* 5-segment meter styling */
526
+ .strength-meter {
527
+ display: flex;
528
+ gap: 6px;
529
+ width: 100%;
530
+ height: 6px;
531
+ border-radius: 3px;
532
+ }
533
+
534
+ .meter-segment {
535
+ flex: 1;
536
+ height: 100%;
537
+ background-color: rgba(255, 255, 255, 0.06);
538
+ border-radius: 3px;
539
+ transition: background-color 0.4s ease, box-shadow 0.4s ease;
540
+ }
541
+
542
+ /* Visual Glow classes */
543
+ .meter-segment.active-0 {
544
+ background-color: var(--str-0);
545
+ box-shadow: 0 0 8px rgba(255, 56, 96, 0.5);
546
+ }
547
+ .meter-segment.active-1 {
548
+ background-color: var(--str-1);
549
+ box-shadow: 0 0 8px rgba(255, 118, 38, 0.5);
550
+ }
551
+ .meter-segment.active-2 {
552
+ background-color: var(--str-2);
553
+ box-shadow: 0 0 8px rgba(255, 184, 0, 0.5);
554
+ }
555
+ .meter-segment.active-3 {
556
+ background-color: var(--str-3);
557
+ box-shadow: 0 0 8px rgba(79, 172, 254, 0.5);
558
+ }
559
+ .meter-segment.active-4 {
560
+ background-color: var(--str-4);
561
+ box-shadow: 0 0 8px rgba(0, 242, 254, 0.5);
562
+ }
563
+ .meter-segment.active-5 {
564
+ background: linear-gradient(90deg, var(--accent-cyan), var(--accent-purple));
565
+ box-shadow: 0 0 10px rgba(0, 242, 254, 0.6);
566
+ }
567
+
568
+ .crack-time-wrapper {
569
+ display: flex;
570
+ align-items: center;
571
+ gap: 8px;
572
+ font-size: 12px;
573
+ color: var(--text-secondary);
574
+ }
575
+
576
+ .icon-clock {
577
+ color: var(--text-muted);
578
  }
579
 
580
+ #crack-time {
581
+ font-weight: 500;
 
582
  }
583
 
584
+ /* Active coloring for crack time text based on strength rating */
585
+ .strength-label-0 { color: var(--str-0) !important; text-shadow: 0 0 10px rgba(255, 56, 96, 0.2); }
586
+ .strength-label-1 { color: var(--str-1) !important; text-shadow: 0 0 10px rgba(255, 118, 38, 0.2); }
587
+ .strength-label-2 { color: var(--str-2) !important; text-shadow: 0 0 10px rgba(255, 184, 0, 0.2); }
588
+ .strength-label-3 { color: var(--str-3) !important; text-shadow: 0 0 10px rgba(79, 172, 254, 0.2); }
589
+ .strength-label-4 { color: var(--str-4) !important; text-shadow: 0 0 10px rgba(0, 242, 254, 0.2); }
590
+ .strength-label-5 {
591
+ background: linear-gradient(135deg, var(--accent-cyan) 0%, var(--accent-purple) 100%);
592
+ -webkit-background-clip: text;
593
+ -webkit-text-fill-color: transparent;
594
+ font-weight: 700 !important;
595
  }
596
 
597
+ /* App Footer Info */
598
+ .app-footer {
599
+ text-align: center;
600
+ margin-top: 24px;
601
+ font-size: 11px;
602
+ color: var(--text-muted);
603
  }
604
 
605
+ /* Responsiveness overrides */
606
+ @media (max-width: 480px) {
607
+ .glass-card {
608
+ padding: 20px;
609
+ border-radius: 20px;
610
+ }
611
+
612
+ .checkbox-grid {
613
+ grid-template-columns: 1fr;
614
+ gap: 10px;
615
+ }
616
+
617
+ .password-input {
618
+ font-size: 17px;
619
+ }
620
  }