Home

js-spectre @main - refs - log -
-
https://git.jolheiser.com/js-spectre.git
JS implementation for spectre/masterpassword
tree log patch
example: standalone css Signed-off-by: jolheiser <git@jolheiser.com>
Signature
-----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgBTEvCQk6VqUAdN2RuH6bj1dNkY oOpbPWj+jw4ua1B1cAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQNvlEFcQ8vd0jKFdm5cZJExXj9vV3WoF/QFy8bHGR3Xex/1LPAzmWj69rGBoO1+sJD 4znEDMoan4cy1ML7PFzw0= -----END SSH SIGNATURE-----
jolheiser <git@jolheiser.com>
4 hours ago
2 changed files, 163 additions(+), 284 deletions(-)
example/index.htmlexample/sakura.css
M example/index.html -> example/index.html
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
diff --git a/example/index.html b/example/index.html
index a6f42778f099161aec25d6e50da213c583877e6e..818bba43e8ccc3a8d46d2d80934c2b785c13a614 100644
--- a/example/index.html
+++ b/example/index.html
@@ -1,20 +1,168 @@
 <!doctype html>
 
 <head>
+  <meta charset="utf-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1" />
+  <meta name="color-scheme" content="light dark" />
+  <title>Spectre</title>
   <script src="../lib/spectre.min.js"></script>
-  <link rel="stylesheet" href="sakura.css" />
   <style>
+    /* Catppuccin Latte */
+    :root {
+      color-scheme: light dark;
+      --base: #eff1f5;
+      --mantle: #e6e9ef;
+      --crust: #dce0e8;
+      --text: #4c4f69;
+      --subtext: #6c6f85;
+      --surface: #ccd0da;
+      --overlay: #9ca0b0;
+      --accent: #7287fd;
+      --green: #40a02b;
+      --shadow: rgba(76, 79, 105, 0.15);
+    }
+
+    /* Catppuccin Mocha */
+    @media (prefers-color-scheme: dark) {
+      :root {
+        --base: #1e1e2e;
+        --mantle: #181825;
+        --crust: #11111b;
+        --text: #cdd6f4;
+        --subtext: #a6adc8;
+        --surface: #313244;
+        --overlay: #6c7086;
+        --accent: #b4befe;
+        --green: #a6e3a1;
+        --shadow: rgba(0, 0, 0, 0.4);
+      }
+    }
+
+    * {
+      box-sizing: border-box;
+    }
+
+    body {
+      margin: 0;
+      min-height: 100vh;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      padding: 1rem;
+      font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
+      background: linear-gradient(135deg, var(--mantle), var(--crust));
+      color: var(--text);
+    }
+
+    form {
+      width: 100%;
+      max-width: 360px;
+      padding: 2rem;
+      background: var(--base);
+      border: 1px solid var(--surface);
+      border-radius: 12px;
+      box-shadow: 0 10px 30px var(--shadow);
+    }
+
+    h1 {
+      margin: 0 0 1.5rem;
+      font-size: 1.5rem;
+      text-align: center;
+    }
+
+    label {
+      display: block;
+      margin-bottom: 0.35rem;
+      font-size: 0.85rem;
+      font-weight: 600;
+      color: var(--subtext);
+    }
+
     input {
-      width: 50%;
+      display: block;
+      width: 100%;
+      margin-bottom: 1rem;
+      padding: 0.65rem 0.75rem;
+      font: inherit;
+      color: var(--text);
+      background: var(--mantle);
+      border: 1px solid var(--surface);
+      border-radius: 8px;
+      transition: border-color 0.15s, box-shadow 0.15s;
+    }
+
+    input::placeholder {
+      color: var(--overlay);
+    }
+
+    input:focus,
+    button:focus-visible {
+      outline: none;
+      border-color: var(--accent);
+      box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
+    }
+
+    .result {
+      position: relative;
+    }
+
+    #result {
+      display: block;
+      padding: 0.75rem 3rem;
+      min-height: 2.75rem;
+      font-family: ui-monospace, "SF Mono", Consolas, monospace;
+      font-size: 1.1rem;
+      text-align: center;
+      word-break: break-all;
+      background: var(--crust);
+      border: 1px solid var(--surface);
+      border-radius: 8px;
+    }
+
+    button {
+      position: absolute;
+      top: 50%;
+      right: 0.4rem;
+      transform: translateY(-50%);
+      width: 2rem;
+      height: 2rem;
+      padding: 0;
+      font-size: 1.1rem;
+      line-height: 1;
+      color: var(--green);
+      background: transparent;
+      border: 1px solid transparent;
+      border-radius: 6px;
+      cursor: pointer;
+      transition: background 0.15s;
+    }
+
+    button:hover:not(:disabled) {
+      background: var(--surface);
+    }
+
+    button:disabled {
+      opacity: 0.4;
+      cursor: not-allowed;
     }
   </style>
 </head>
 
 <body>
-  <input id="userKey" type="text" placeholder="Robert Lee Mitchell" /><br />
-  <input id="userSecret" type="text" placeholder="banana colored duckling" /><br />
-  <input id="siteKey" type="text" placeholder="masterpasswordapp.com" /><br />
-  <strong id="result"></strong>
+  <form onsubmit="return false">
+    <h1>Spectre</h1>
+    <label for="userKey">Name</label>
+    <input id="userKey" type="text" placeholder="Robert Lee Mitchell" autocomplete="off" />
+    <label for="userSecret">Secret</label>
+    <input id="userSecret" type="password" placeholder="banana colored duckling" autocomplete="off" />
+    <label for="siteKey">Site</label>
+    <input id="siteKey" type="text" placeholder="masterpasswordapp.com" autocomplete="off" />
+    <label for="result">Password</label>
+    <div class="result">
+      <output id="result"></output>
+      <button id="copy" type="button" title="Copy password" aria-label="Copy password" disabled>📋</button>
+    </div>
+  </form>
 </body>
 
 <script>
@@ -22,10 +170,12 @@   const $userKey = document.getElementById("userKey");
   const $userSecret = document.getElementById("userSecret");
   const $siteKey = document.getElementById("siteKey");
   const $result = document.getElementById("result");
+  const $copy = document.getElementById("copy");
 
   $userKey.addEventListener("change", onChange);
   $userSecret.addEventListener("change", onChange);
   $siteKey.addEventListener("keyup", generate);
+  $copy.addEventListener("click", copy);
 
   let s = null;
   function onChange() {
@@ -38,5 +188,11 @@   function generate() {
     if (s === null || $siteKey.value === "") return;
     const pw = s.site($siteKey.value);
     $result.innerText = pw;
+    $copy.disabled = !navigator.clipboard;
   }
-</script>
\ No newline at end of file
+  async function copy() {
+    await navigator.clipboard.writeText($result.innerText);
+    $copy.innerText = "\u2714\uFE0E";
+    setTimeout(() => ($copy.innerText = "📋"), 1500);
+  }
+</script>
D example/sakura.css
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
diff --git a/example/sakura.css b/example/sakura.css
deleted file mode 100644
index 53b47996dc0901c6b6ce7bead96da85b55cacbe5..0000000000000000000000000000000000000000
--- a/example/sakura.css
+++ /dev/null
@@ -1,277 +0,0 @@
-/* Sakura.css v1.5.0
- * ================
- * Minimal css theme.
- * Project: https://github.com/oxalorg/sakura/
- */
-/* Body */
-html {
-  font-size: 62.5%;
-  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
-    "Helvetica Neue", Arial, "Noto Sans", sans-serif;
-}
-
-body {
-  font-size: 1.8rem;
-  line-height: 1.618;
-  max-width: 38em;
-  margin: auto;
-  color: #4a4a4a;
-  background-color: #f9f9f9;
-  padding: 13px;
-}
-
-@media (max-width: 684px) {
-  body {
-    font-size: 1.53rem;
-  }
-}
-
-@media (max-width: 382px) {
-  body {
-    font-size: 1.35rem;
-  }
-}
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
-  line-height: 1.1;
-  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
-    "Helvetica Neue", Arial, "Noto Sans", sans-serif;
-  font-weight: 700;
-  margin-top: 3rem;
-  margin-bottom: 1.5rem;
-  overflow-wrap: break-word;
-  word-wrap: break-word;
-  -ms-word-break: break-all;
-  word-break: break-word;
-}
-
-h1 {
-  font-size: 2.35em;
-}
-
-h2 {
-  font-size: 2em;
-}
-
-h3 {
-  font-size: 1.75em;
-}
-
-h4 {
-  font-size: 1.5em;
-}
-
-h5 {
-  font-size: 1.25em;
-}
-
-h6 {
-  font-size: 1em;
-}
-
-p {
-  margin-top: 0px;
-  margin-bottom: 2.5rem;
-}
-
-small,
-sub,
-sup {
-  font-size: 75%;
-}
-
-hr {
-  border-color: #1d7484;
-}
-
-a {
-  text-decoration: none;
-  color: #1d7484;
-}
-
-a:visited {
-  color: #144f5a;
-}
-
-a:hover {
-  color: #982c61;
-  border-bottom: 2px solid #4a4a4a;
-}
-
-ul {
-  padding-left: 1.4em;
-  margin-top: 0px;
-  margin-bottom: 2.5rem;
-}
-
-li {
-  margin-bottom: 0.4em;
-}
-
-blockquote {
-  margin-left: 0px;
-  margin-right: 0px;
-  padding-left: 1em;
-  padding-top: 0.8em;
-  padding-bottom: 0.8em;
-  padding-right: 0.8em;
-  border-left: 5px solid #1d7484;
-  margin-bottom: 2.5rem;
-  background-color: #f1f1f1;
-}
-
-blockquote p {
-  margin-bottom: 0;
-}
-
-img,
-video {
-  height: auto;
-  max-width: 100%;
-  margin-top: 0px;
-  margin-bottom: 2.5rem;
-}
-
-/* Pre and Code */
-pre {
-  background-color: #f1f1f1;
-  display: block;
-  padding: 1em;
-  overflow-x: auto;
-  margin-top: 0px;
-  margin-bottom: 2.5rem;
-  font-size: 0.9em;
-}
-
-code,
-kbd,
-samp {
-  font-size: 0.9em;
-  padding: 0 0.5em;
-  background-color: #f1f1f1;
-  white-space: pre-wrap;
-}
-
-pre > code {
-  padding: 0;
-  background-color: transparent;
-  white-space: pre;
-  font-size: 1em;
-}
-
-/* Tables */
-table {
-  text-align: justify;
-  width: 100%;
-  border-collapse: collapse;
-  margin-bottom: 2rem;
-}
-
-td,
-th {
-  padding: 0.5em;
-  border-bottom: 1px solid #f1f1f1;
-}
-
-/* Buttons, forms and input */
-input,
-textarea {
-  border: 1px solid #4a4a4a;
-}
-
-input:focus,
-textarea:focus {
-  border: 1px solid #1d7484;
-}
-
-textarea {
-  width: 100%;
-}
-
-.button,
-button,
-input[type="submit"],
-input[type="reset"],
-input[type="button"],
-input[type="file"]::file-selector-button {
-  display: inline-block;
-  padding: 5px 10px;
-  text-align: center;
-  text-decoration: none;
-  white-space: nowrap;
-  background-color: #1d7484;
-  color: #f9f9f9;
-  border-radius: 1px;
-  border: 1px solid #1d7484;
-  cursor: pointer;
-  box-sizing: border-box;
-}
-
-.button[disabled],
-button[disabled],
-input[type="submit"][disabled],
-input[type="reset"][disabled],
-input[type="button"][disabled],
-input[type="file"]::file-selector-button[disabled] {
-  cursor: default;
-  opacity: 0.5;
-}
-
-.button:hover,
-button:hover,
-input[type="submit"]:hover,
-input[type="reset"]:hover,
-input[type="button"]:hover,
-input[type="file"]::file-selector-button:hover {
-  background-color: #982c61;
-  color: #f9f9f9;
-  outline: 0;
-}
-
-.button:focus-visible,
-button:focus-visible,
-input[type="submit"]:focus-visible,
-input[type="reset"]:focus-visible,
-input[type="button"]:focus-visible,
-input[type="file"]::file-selector-button:focus-visible {
-  outline-style: solid;
-  outline-width: 2px;
-}
-
-textarea,
-select,
-input {
-  color: #4a4a4a;
-  padding: 6px 10px;
-  /* The 6px vertically centers text on FF, ignored by Webkit */
-  margin-bottom: 10px;
-  background-color: #f1f1f1;
-  border: 1px solid #f1f1f1;
-  border-radius: 4px;
-  box-shadow: none;
-  box-sizing: border-box;
-}
-
-textarea:focus,
-select:focus,
-input:focus {
-  border: 1px solid #1d7484;
-  outline: 0;
-}
-
-input[type="checkbox"]:focus {
-  outline: 1px dotted #1d7484;
-}
-
-label,
-legend,
-fieldset {
-  display: block;
-  margin-bottom: 0.5rem;
-  font-weight: 600;
-}