shakha 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 111afb5f4917b2fb01033e9b93c37fabc0b099a886c7cce7eb80d4485e69e9a1
4
- data.tar.gz: '008356dd3f1538432a84ff2e83c2b6d32e3ec5962ed252c79c8dc3229677c16d'
3
+ metadata.gz: 97e30df4b475b7c45fa0d75ef31ebe049760c19f6a65df602996b6aa2edd3ebe
4
+ data.tar.gz: 5e1b4b6cdc4897e74e289411fae040a9ba31e8aedb800460e776011ee4fbfac1
5
5
  SHA512:
6
- metadata.gz: 369843a745888523d8d3392e1111804d7113247005654de91c2fc75436a5a799ea505ad20858dbc057f9e81f5f3179dcaec77cd084923f3a1ae0121bcdb45960
7
- data.tar.gz: 8bb91783e33cbac949565f21abded2d958b652b95f62b62f3d51eaabeaaf979224163376c98e58d25ecae57fbd41556f4a0db7c2415316e24e3778700e06af1e
6
+ metadata.gz: 1569eafa9f6f7054dd8457301815f6676cb29ba0b3658e5c00edb06896ed4cb700f4e6be3f2daaedc582519d9af12603fc08229170df1e162cd255f60b8bf3d4
7
+ data.tar.gz: 2e92274af426cb8eb2ba1ed6279a6ebc89cee3c67d4c3085ad623015df5e8d2ed0756a023a74cb396a133fd4bbcd6ea2ed8058ba94132d0439078293d9451c61
@@ -1,193 +1,580 @@
1
- @layer shakha.base {
1
+ /* Shakha Design System — Zero JS, CSS-only */
2
+
3
+ @layer shakha.tokens {
2
4
  :root {
3
- --shakha-bg: oklch(98% 0.002 250);
4
- --shakha-surface: oklch(100% 0 0);
5
- --shakha-border: oklch(87% 0.01 250);
6
- --shakha-text: oklch(20% 0.03 250);
7
- --shakha-text-muted: oklch(50% 0.02 250);
8
- --shakha-primary: oklch(55% 0.2 250);
9
- --shakha-primary-hover: oklch(50% 0.22 250);
10
- --shakha-error: oklch(60% 0.2 25);
11
- --shakha-radius: 8px;
12
- --shakha-shadow: 0 1px 3px oklch(0% 0 0 / 0.05), 0 1px 2px oklch(0% 0 0 / 0.1);
13
- }
14
-
15
- * {
5
+ /* Colors OKLCH for perceptual uniformity */
6
+ --sh-bg: oklch(98% 0.002 250);
7
+ --sh-surface: oklch(100% 0 0);
8
+ --sh-surface-elevated: oklch(100% 0 0);
9
+ --sh-border: oklch(87% 0.01 250);
10
+ --sh-text: oklch(20% 0.03 250);
11
+ --sh-text-secondary: oklch(45% 0.02 250);
12
+ --sh-text-tertiary: oklch(60% 0.01 250);
13
+ --sh-primary: oklch(55% 0.18 250);
14
+ --sh-primary-hover: oklch(50% 0.2 250);
15
+ --sh-primary-subtle: oklch(95% 0.03 250);
16
+ --sh-error: oklch(55% 0.18 25);
17
+ --sh-error-subtle: oklch(95% 0.03 25);
18
+ --sh-success: oklch(55% 0.15 145);
19
+ --sh-success-subtle: oklch(95% 0.03 145);
20
+
21
+ /* Spacing — modular scale */
22
+ --sh-space-1: 0.25rem;
23
+ --sh-space-2: 0.5rem;
24
+ --sh-space-3: 0.75rem;
25
+ --sh-space-4: 1rem;
26
+ --sh-space-5: 1.25rem;
27
+ --sh-space-6: 1.5rem;
28
+ --sh-space-8: 2rem;
29
+ --sh-space-10: 2.5rem;
30
+ --sh-space-12: 3rem;
31
+ --sh-space-16: 4rem;
32
+
33
+ /* Typography */
34
+ --sh-font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
35
+ --sh-font-mono: "SF Mono", "Fira Code", "Cascadia Code", monospace;
36
+ --sh-text-xs: 0.75rem;
37
+ --sh-text-sm: 0.875rem;
38
+ --sh-text-base: 1rem;
39
+ --sh-text-lg: 1.125rem;
40
+ --sh-text-xl: 1.25rem;
41
+ --sh-text-2xl: 1.5rem;
42
+ --sh-text-3xl: 1.875rem;
43
+
44
+ /* Effects */
45
+ --sh-radius-sm: 6px;
46
+ --sh-radius-md: 10px;
47
+ --sh-radius-lg: 14px;
48
+ --sh-radius-xl: 20px;
49
+ --sh-shadow-sm: 0 1px 2px oklch(0% 0 0 / 0.04);
50
+ --sh-shadow-md: 0 4px 12px oklch(0% 0 0 / 0.06), 0 1px 3px oklch(0% 0 0 / 0.04);
51
+ --sh-shadow-lg: 0 12px 40px oklch(0% 0 0 / 0.08), 0 4px 12px oklch(0% 0 0 / 0.04);
52
+ --sh-shadow-glow: 0 0 40px oklch(55% 0.18 250 / 0.15);
53
+
54
+ /* Transitions */
55
+ --sh-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
56
+ --sh-transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
57
+ --sh-transition-slow: 400ms cubic-bezier(0.4, 0, 0.2, 1);
58
+ }
59
+
60
+ @media (prefers-color-scheme: dark) {
61
+ :root {
62
+ --sh-bg: oklch(18% 0.02 250);
63
+ --sh-surface: oklch(22% 0.025 250);
64
+ --sh-surface-elevated: oklch(26% 0.03 250);
65
+ --sh-border: oklch(35% 0.04 250);
66
+ --sh-text: oklch(95% 0.01 250);
67
+ --sh-text-secondary: oklch(75% 0.015 250);
68
+ --sh-text-tertiary: oklch(55% 0.02 250);
69
+ --sh-primary: oklch(65% 0.18 250);
70
+ --sh-primary-hover: oklch(70% 0.2 250);
71
+ --sh-primary-subtle: oklch(30% 0.06 250);
72
+ --sh-error: oklch(65% 0.18 25);
73
+ --sh-error-subtle: oklch(30% 0.05 25);
74
+ --sh-success: oklch(65% 0.15 145);
75
+ --sh-success-subtle: oklch(30% 0.04 145);
76
+ --sh-shadow-sm: 0 1px 2px oklch(0% 0 0 / 0.2);
77
+ --sh-shadow-md: 0 4px 12px oklch(0% 0 0 / 0.3), 0 1px 3px oklch(0% 0 0 / 0.2);
78
+ --sh-shadow-lg: 0 12px 40px oklch(0% 0 0 / 0.4), 0 4px 12px oklch(0% 0 0 / 0.2);
79
+ --sh-shadow-glow: 0 0 60px oklch(65% 0.18 250 / 0.2);
80
+ }
81
+ }
82
+ }
83
+
84
+ @layer shakha.reset {
85
+ *, *::before, *::after {
16
86
  margin: 0;
17
87
  padding: 0;
18
88
  box-sizing: border-box;
19
89
  }
20
90
 
21
- body {
22
- font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
23
- background: var(--shakha-bg);
24
- color: var(--shakha-text);
25
- line-height: 1.5;
91
+ html {
26
92
  -webkit-font-smoothing: antialiased;
93
+ -moz-osx-font-smoothing: grayscale;
94
+ text-rendering: optimizeLegibility;
95
+ }
96
+
97
+ body {
98
+ font-family: var(--sh-font-sans);
99
+ font-size: var(--sh-text-base);
100
+ line-height: 1.6;
101
+ background: var(--sh-bg);
102
+ color: var(--sh-text);
103
+ min-height: 100vh;
27
104
  }
28
105
 
29
106
  a {
30
- color: var(--shakha-primary);
107
+ color: var(--sh-primary);
31
108
  text-decoration: none;
109
+ transition: color var(--sh-transition-fast);
32
110
  }
33
111
 
34
112
  a:hover {
35
- text-decoration: underline;
113
+ color: var(--sh-primary-hover);
114
+ }
115
+
116
+ a:focus-visible {
117
+ outline: 2px solid var(--sh-primary);
118
+ outline-offset: 2px;
119
+ border-radius: var(--sh-radius-sm);
120
+ }
121
+
122
+ img, svg {
123
+ display: block;
124
+ max-width: 100%;
125
+ }
126
+
127
+ button {
128
+ font-family: inherit;
129
+ cursor: pointer;
130
+ border: none;
131
+ background: none;
132
+ }
133
+
134
+ button:focus-visible {
135
+ outline: 2px solid var(--sh-primary);
136
+ outline-offset: 2px;
36
137
  }
37
138
  }
38
139
 
39
140
  @layer shakha.layout {
40
- .shakha-container {
141
+ .sh-layout {
41
142
  min-height: 100vh;
42
- display: flex;
43
- align-items: center;
44
- justify-content: center;
45
- padding: 1.5rem;
143
+ display: grid;
144
+ place-items: center;
145
+ padding: var(--sh-space-6);
146
+ position: relative;
147
+ overflow: hidden;
148
+ }
149
+
150
+ .sh-layout::before {
151
+ content: "";
152
+ position: absolute;
153
+ inset: 0;
154
+ background:
155
+ radial-gradient(ellipse 80% 50% at 20% 40%, oklch(55% 0.15 250 / 0.08), transparent 50%),
156
+ radial-gradient(ellipse 60% 40% at 80% 60%, oklch(65% 0.12 280 / 0.06), transparent 50%);
157
+ pointer-events: none;
158
+ z-index: 0;
46
159
  }
47
160
 
48
- .shakha-card {
161
+ .sh-card {
162
+ position: relative;
163
+ z-index: 1;
49
164
  width: 100%;
50
- max-width: 400px;
51
- background: var(--shakha-surface);
52
- border: 1px solid var(--shakha-border);
53
- border-radius: calc(var(--shakha-radius) + 4px);
54
- box-shadow: var(--shakha-shadow);
165
+ max-width: 420px;
166
+ background: var(--sh-surface);
167
+ border: 1px solid var(--sh-border);
168
+ border-radius: var(--sh-radius-xl);
169
+ box-shadow: var(--sh-shadow-lg);
55
170
  overflow: hidden;
171
+ transition: transform var(--sh-transition-base), box-shadow var(--sh-transition-base);
56
172
  }
57
173
 
58
- .shakha-card-center {
59
- display: flex;
60
- flex-direction: column;
61
- align-items: center;
62
- justify-content: center;
63
- min-height: 200px;
64
- padding: 2rem;
174
+ .sh-card:hover {
175
+ box-shadow: var(--sh-shadow-lg), var(--sh-shadow-glow);
65
176
  }
66
177
 
67
- .shakha-card-error {
178
+ .sh-card__header {
179
+ padding: var(--sh-space-8) var(--sh-space-8) var(--sh-space-4);
68
180
  text-align: center;
69
- padding: 2rem;
70
181
  }
71
182
 
72
- .shakha-header {
73
- padding: 1.5rem 1.5rem 0;
74
- text-align: center;
183
+ .sh-card__body {
184
+ padding: var(--sh-space-4) var(--sh-space-8) var(--sh-space-8);
75
185
  }
76
186
 
77
- .shakha-header h1 {
78
- font-size: 1.25rem;
79
- font-weight: 600;
80
- color: var(--shakha-text);
187
+ .sh-card__footer {
188
+ padding: var(--sh-space-4) var(--sh-space-8);
189
+ background: var(--sh-primary-subtle);
190
+ border-top: 1px solid var(--sh-border);
191
+ text-align: center;
81
192
  }
82
193
 
83
- .shakha-body {
84
- padding: 1.5rem;
194
+ @media (prefers-color-scheme: dark) {
195
+ .sh-card__footer {
196
+ background: oklch(25% 0.04 250);
197
+ }
85
198
  }
86
199
  }
87
200
 
88
201
  @layer shakha.components {
89
- .shakha-button {
202
+ /* Logo / Brand */
203
+ .sh-brand {
90
204
  display: flex;
91
205
  align-items: center;
92
206
  justify-content: center;
93
- gap: 0.75rem;
207
+ gap: var(--sh-space-3);
208
+ margin-bottom: var(--sh-space-6);
209
+ }
210
+
211
+ .sh-brand__icon {
212
+ width: 40px;
213
+ height: 40px;
214
+ background: linear-gradient(135deg, var(--sh-primary), oklch(65% 0.15 280));
215
+ border-radius: var(--sh-radius-md);
216
+ display: grid;
217
+ place-items: center;
218
+ color: white;
219
+ font-weight: 700;
220
+ font-size: var(--sh-text-lg);
221
+ box-shadow: 0 4px 12px oklch(55% 0.18 250 / 0.3);
222
+ }
223
+
224
+ .sh-brand__name {
225
+ font-size: var(--sh-text-xl);
226
+ font-weight: 700;
227
+ color: var(--sh-text);
228
+ letter-spacing: -0.02em;
229
+ }
230
+
231
+ .sh-brand__subtitle {
232
+ font-size: var(--sh-text-sm);
233
+ color: var(--sh-text-tertiary);
234
+ margin-top: var(--sh-space-1);
235
+ }
236
+
237
+ /* Headings */
238
+ .sh-heading {
239
+ font-size: var(--sh-text-2xl);
240
+ font-weight: 700;
241
+ color: var(--sh-text);
242
+ letter-spacing: -0.02em;
243
+ line-height: 1.2;
244
+ margin-bottom: var(--sh-space-2);
245
+ }
246
+
247
+ .sh-heading--center {
248
+ text-align: center;
249
+ }
250
+
251
+ .sh-subheading {
252
+ font-size: var(--sh-text-sm);
253
+ color: var(--sh-text-secondary);
254
+ text-align: center;
255
+ margin-bottom: var(--sh-space-6);
256
+ }
257
+
258
+ /* Buttons */
259
+ .sh-btn {
260
+ display: inline-flex;
261
+ align-items: center;
262
+ justify-content: center;
263
+ gap: var(--sh-space-3);
94
264
  width: 100%;
95
- padding: 0.75rem 1rem;
96
- border-radius: var(--shakha-radius);
97
- font-size: 0.9375rem;
98
- font-weight: 500;
265
+ padding: var(--sh-space-4) var(--sh-space-6);
266
+ border-radius: var(--sh-radius-md);
267
+ font-size: var(--sh-text-base);
268
+ font-weight: 600;
99
269
  text-decoration: none;
100
- cursor: pointer;
101
- transition: background 0.15s ease, border-color 0.15s ease;
270
+ transition: all var(--sh-transition-fast);
271
+ position: relative;
272
+ overflow: hidden;
102
273
  }
103
274
 
104
- .shakha-button-google {
105
- background: var(--shakha-surface);
106
- border: 1px solid var(--shakha-border);
107
- color: var(--shakha-text);
275
+ .sh-btn::after {
276
+ content: "";
277
+ position: absolute;
278
+ inset: 0;
279
+ background: linear-gradient(90deg, transparent, oklch(100% 0 0 / 0.1), transparent);
280
+ transform: translateX(-100%);
281
+ transition: transform var(--sh-transition-slow);
108
282
  }
109
283
 
110
- .shakha-button-google:hover {
111
- background: var(--shakha-bg);
112
- text-decoration: none;
284
+ .sh-btn:hover::after {
285
+ transform: translateX(100%);
113
286
  }
114
287
 
115
- .shakha-button-primary {
116
- background: var(--shakha-primary);
117
- border: 1px solid var(--shakha-primary);
288
+ .sh-btn--primary {
289
+ background: var(--sh-primary);
118
290
  color: white;
291
+ border: 1px solid transparent;
292
+ box-shadow: 0 1px 3px oklch(0% 0 0 / 0.1);
119
293
  }
120
294
 
121
- .shakha-button-primary:hover {
122
- background: var(--shakha-primary-hover);
295
+ .sh-btn--primary:hover {
296
+ background: var(--sh-primary-hover);
297
+ transform: translateY(-1px);
298
+ box-shadow: 0 4px 12px oklch(55% 0.18 250 / 0.25);
123
299
  text-decoration: none;
124
300
  }
125
301
 
126
- .shakha-google-icon {
127
- width: 18px;
128
- height: 18px;
302
+ .sh-btn--google {
303
+ background: var(--sh-surface);
304
+ color: var(--sh-text);
305
+ border: 1px solid var(--sh-border);
306
+ }
307
+
308
+ .sh-btn--google:hover {
309
+ background: var(--sh-surface-elevated);
310
+ border-color: var(--sh-primary);
311
+ transform: translateY(-1px);
312
+ box-shadow: var(--sh-shadow-md);
313
+ text-decoration: none;
314
+ }
315
+
316
+ .sh-btn--danger {
317
+ background: var(--sh-error);
318
+ color: white;
319
+ border: 1px solid transparent;
320
+ }
321
+
322
+ .sh-btn--danger:hover {
323
+ background: oklch(50% 0.2 25);
324
+ transform: translateY(-1px);
325
+ }
326
+
327
+ .sh-btn--ghost {
328
+ background: transparent;
329
+ color: var(--sh-text-secondary);
330
+ border: 1px solid var(--sh-border);
331
+ }
332
+
333
+ .sh-btn--ghost:hover {
334
+ background: var(--sh-primary-subtle);
335
+ color: var(--sh-primary);
336
+ }
337
+
338
+ .sh-btn__icon {
339
+ width: 20px;
340
+ height: 20px;
129
341
  flex-shrink: 0;
130
342
  }
131
343
 
132
- .shakha-privacy {
133
- margin-top: 1rem;
134
- font-size: 0.75rem;
135
- color: var(--shakha-text-muted);
344
+ /* Error states */
345
+ .sh-error {
136
346
  text-align: center;
347
+ padding: var(--sh-space-8);
137
348
  }
138
349
 
139
- .shakha-privacy a {
140
- color: var(--shakha-text-muted);
141
- text-decoration: underline;
350
+ .sh-error__icon {
351
+ width: 64px;
352
+ height: 64px;
353
+ margin: 0 auto var(--sh-space-6);
354
+ background: var(--sh-error-subtle);
355
+ border-radius: var(--sh-radius-lg);
356
+ display: grid;
357
+ place-items: center;
358
+ color: var(--sh-error);
359
+ animation: sh-pulse 2s ease-in-out infinite;
360
+ }
361
+
362
+ @keyframes sh-pulse {
363
+ 0%, 100% { transform: scale(1); opacity: 1; }
364
+ 50% { transform: scale(1.05); opacity: 0.8; }
365
+ }
366
+
367
+ .sh-error__title {
368
+ font-size: var(--sh-text-xl);
369
+ font-weight: 700;
370
+ color: var(--sh-text);
371
+ margin-bottom: var(--sh-space-2);
372
+ }
373
+
374
+ .sh-error__message {
375
+ font-size: var(--sh-text-base);
376
+ color: var(--sh-text-secondary);
377
+ margin-bottom: var(--sh-space-6);
378
+ line-height: 1.6;
379
+ }
380
+
381
+ /* Success states */
382
+ .sh-success {
383
+ text-align: center;
384
+ padding: var(--sh-space-8);
385
+ }
386
+
387
+ .sh-success__icon {
388
+ width: 64px;
389
+ height: 64px;
390
+ margin: 0 auto var(--sh-space-6);
391
+ background: var(--sh-success-subtle);
392
+ border-radius: var(--sh-radius-lg);
393
+ display: grid;
394
+ place-items: center;
395
+ color: var(--sh-success);
142
396
  }
143
- }
144
397
 
145
- @layer shakha.states {
146
- .shakha-loading {
398
+ /* Session list */
399
+ .sh-sessions {
400
+ padding: var(--sh-space-6);
401
+ }
402
+
403
+ .sh-sessions__header {
147
404
  display: flex;
148
- flex-direction: column;
149
405
  align-items: center;
150
- gap: 1rem;
406
+ justify-content: space-between;
407
+ margin-bottom: var(--sh-space-6);
408
+ padding-bottom: var(--sh-space-4);
409
+ border-bottom: 1px solid var(--sh-border);
151
410
  }
152
411
 
153
- .shakha-loading p {
154
- color: var(--shakha-text-muted);
155
- font-size: 0.875rem;
412
+ .sh-sessions__title {
413
+ font-size: var(--sh-text-lg);
414
+ font-weight: 700;
415
+ color: var(--sh-text);
156
416
  }
157
417
 
158
- .shakha-spinner {
159
- width: 32px;
160
- height: 32px;
161
- border: 3px solid var(--shakha-border);
162
- border-top-color: var(--shakha-primary);
163
- border-radius: 50%;
164
- animation: shakha-spin 0.8s linear infinite;
418
+ .sh-sessions__count {
419
+ font-size: var(--sh-text-sm);
420
+ color: var(--sh-text-tertiary);
421
+ background: var(--sh-primary-subtle);
422
+ padding: var(--sh-space-1) var(--sh-space-3);
423
+ border-radius: var(--sh-radius-sm);
165
424
  }
166
425
 
167
- @keyframes shakha-spin {
168
- to {
169
- transform: rotate(360deg);
170
- }
426
+ .sh-session {
427
+ display: flex;
428
+ align-items: center;
429
+ justify-content: space-between;
430
+ padding: var(--sh-space-4) var(--sh-space-4);
431
+ border-radius: var(--sh-radius-md);
432
+ border: 1px solid var(--sh-border);
433
+ margin-bottom: var(--sh-space-3);
434
+ transition: all var(--sh-transition-fast);
435
+ }
436
+
437
+ .sh-session:hover {
438
+ background: var(--sh-primary-subtle);
439
+ border-color: var(--sh-primary);
440
+ transform: translateX(2px);
441
+ }
442
+
443
+ .sh-session--current {
444
+ border-color: var(--sh-primary);
445
+ background: var(--sh-primary-subtle);
446
+ box-shadow: 0 0 0 1px var(--sh-primary);
447
+ }
448
+
449
+ .sh-session__info {
450
+ flex: 1;
451
+ }
452
+
453
+ .sh-session__device {
454
+ font-weight: 600;
455
+ color: var(--sh-text);
456
+ font-size: var(--sh-text-sm);
457
+ }
458
+
459
+ .sh-session__meta {
460
+ font-size: var(--sh-text-xs);
461
+ color: var(--sh-text-tertiary);
462
+ margin-top: var(--sh-space-1);
463
+ }
464
+
465
+ .sh-session__badge {
466
+ font-size: var(--sh-text-xs);
467
+ font-weight: 600;
468
+ padding: var(--sh-space-1) var(--sh-space-2);
469
+ border-radius: var(--sh-radius-sm);
470
+ background: var(--sh-primary);
471
+ color: white;
472
+ margin-right: var(--sh-space-3);
473
+ }
474
+
475
+ .sh-session__actions {
476
+ display: flex;
477
+ gap: var(--sh-space-2);
478
+ }
479
+
480
+ .sh-session__btn {
481
+ padding: var(--sh-space-2) var(--sh-space-3);
482
+ border-radius: var(--sh-radius-sm);
483
+ font-size: var(--sh-text-xs);
484
+ font-weight: 600;
485
+ border: 1px solid var(--sh-border);
486
+ background: var(--sh-surface);
487
+ color: var(--sh-text-secondary);
488
+ transition: all var(--sh-transition-fast);
489
+ }
490
+
491
+ .sh-session__btn:hover {
492
+ background: var(--sh-error);
493
+ color: white;
494
+ border-color: var(--sh-error);
171
495
  }
172
496
 
173
- .shakha-error-icon {
174
- margin-bottom: 1rem;
497
+ /* Empty state */
498
+ .sh-empty {
499
+ text-align: center;
500
+ padding: var(--sh-space-12);
501
+ color: var(--sh-text-tertiary);
175
502
  }
176
503
 
177
- .shakha-error-icon svg {
504
+ .sh-empty__icon {
178
505
  width: 48px;
179
506
  height: 48px;
180
- color: var(--shakha-error);
507
+ margin: 0 auto var(--sh-space-4);
508
+ opacity: 0.5;
181
509
  }
182
510
 
183
- .shakha-card-error h1 {
184
- font-size: 1.25rem;
185
- font-weight: 600;
186
- margin-bottom: 0.5rem;
511
+ /* Links */
512
+ .sh-link {
513
+ color: var(--sh-primary);
514
+ font-size: var(--sh-text-sm);
515
+ font-weight: 500;
516
+ transition: all var(--sh-transition-fast);
517
+ }
518
+
519
+ .sh-link:hover {
520
+ text-decoration: underline;
521
+ }
522
+
523
+ .sh-link--subtle {
524
+ color: var(--sh-text-tertiary);
525
+ }
526
+
527
+ .sh-link--subtle:hover {
528
+ color: var(--sh-text-secondary);
529
+ }
530
+
531
+ /* Divider */
532
+ .sh-divider {
533
+ display: flex;
534
+ align-items: center;
535
+ gap: var(--sh-space-4);
536
+ margin: var(--sh-space-6) 0;
537
+ color: var(--sh-text-tertiary);
538
+ font-size: var(--sh-text-xs);
539
+ text-transform: uppercase;
540
+ letter-spacing: 0.05em;
541
+ }
542
+
543
+ .sh-divider::before,
544
+ .sh-divider::after {
545
+ content: "";
546
+ flex: 1;
547
+ height: 1px;
548
+ background: var(--sh-border);
187
549
  }
188
550
 
189
- .shakha-error-message {
190
- color: var(--shakha-text-muted);
191
- margin-bottom: 1.5rem;
551
+ /* Animations */
552
+ @keyframes sh-fade-in {
553
+ from { opacity: 0; transform: translateY(10px); }
554
+ to { opacity: 1; transform: translateY(0); }
192
555
  }
193
- }
556
+
557
+ @keyframes sh-slide-up {
558
+ from { opacity: 0; transform: translateY(20px); }
559
+ to { opacity: 1; transform: translateY(0); }
560
+ }
561
+
562
+ .sh-animate-fade {
563
+ animation: sh-fade-in var(--sh-transition-slow) both;
564
+ }
565
+
566
+ .sh-animate-slide {
567
+ animation: sh-slide-up var(--sh-transition-slow) both;
568
+ animation-delay: 100ms;
569
+ }
570
+ }
571
+
572
+ @layer shakha.utilities {
573
+ .sh-text-center { text-align: center; }
574
+ .sh-mt-2 { margin-top: var(--sh-space-2); }
575
+ .sh-mt-4 { margin-top: var(--sh-space-4); }
576
+ .sh-mt-6 { margin-top: var(--sh-space-6); }
577
+ .sh-mb-2 { margin-bottom: var(--sh-space-2); }
578
+ .sh-mb-4 { margin-bottom: var(--sh-space-4); }
579
+ .sh-mb-6 { margin-bottom: var(--sh-space-6); }
580
+ }
@@ -54,6 +54,13 @@ module Shakha
54
54
  end
55
55
  end
56
56
 
57
+ def list
58
+ return redirect_to "/auth/shakha" unless signed_in?
59
+
60
+ @sessions = current_user.sessions.active.order(created_at: :desc)
61
+ @current_token = current_session&.token
62
+ end
63
+
57
64
  def revoke
58
65
  return render json: { error: "Authentication required" }, status: :unauthorized unless signed_in?
59
66
 
@@ -1,18 +1,30 @@
1
1
  <% content_for :title, "Authentication Error" %>
2
2
 
3
- <div class="shakha-container">
4
- <div class="shakha-card shakha-card-error">
5
- <div class="shakha-error-icon">
6
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
7
- <circle cx="12" cy="12" r="10"/>
8
- <line x1="12" y1="8" x2="12" y2="12"/>
9
- <line x1="12" y1="16" x2="12.01" y2="16"/>
10
- </svg>
11
- </div>
3
+ <div class="sh-layout sh-animate-fade">
4
+ <div class="sh-card sh-animate-slide">
5
+ <div class="sh-error">
6
+ <div class="sh-error__icon">
7
+ <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
8
+ <circle cx="12" cy="12" r="10"/>
9
+ <line x1="12" y1="8" x2="12" y2="12"/>
10
+ <line x1="12" y1="16" x2="12.01" y2="16"/>
11
+ </svg>
12
+ </div>
13
+
14
+ <h1 class="sh-error__title">Something went wrong</h1>
15
+ <p class="sh-error__message"><%= @message || "We couldn't sign you in. Please try again." %></p>
12
16
 
13
- <h1>Authentication Failed</h1>
14
- <p class="shakha-error-message"><%= params[:message] || "An error occurred" %></p>
17
+ <%= link_to "/auth/shakha", class: "sh-btn sh-btn--primary" do %>
18
+ <svg class="sh-btn__icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
19
+ <polyline points="1 4 1 10 7 10"/>
20
+ <path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
21
+ </svg>
22
+ Try Again
23
+ <% end %>
15
24
 
16
- <%= link_to "Try Again", "/auth/shakha", class: "shakha-button shakha-button-primary" %>
25
+ <p class="sh-mt-4">
26
+ <a href="mailto:support@shakha.dev" class="sh-link sh-link--subtle">Contact Support</a>
27
+ </p>
28
+ </div>
17
29
  </div>
18
30
  </div>
@@ -1,16 +1,25 @@
1
- <% content_for :title, "Sign In" %>
1
+ <% content_for :title, "Sign in — #{@client&.name || 'Shakha'}" %>
2
2
 
3
- <div class="shakha-container">
4
- <div class="shakha-card">
5
- <div class="shakha-header">
6
- <h1>Sign in to <%= @client&.name || "your app" %></h1>
3
+ <div class="sh-layout sh-animate-fade">
4
+ <div class="sh-card sh-animate-slide">
5
+ <div class="sh-card__header">
6
+ <div class="sh-brand">
7
+ <div class="sh-brand__icon">S</div>
8
+ <div>
9
+ <div class="sh-brand__name"><%= @client&.name || "Shakha" %></div>
10
+ <div class="sh-brand__subtitle">Secure authentication</div>
11
+ </div>
12
+ </div>
13
+
14
+ <h1 class="sh-heading sh-heading--center">Welcome back</h1>
15
+ <p class="sh-subheading">Sign in to continue to <%= @client&.name || "your app" %></p>
7
16
  </div>
8
17
 
9
- <div class="shakha-body">
18
+ <div class="sh-card__body">
10
19
  <%= link_to shakha.authorize_path(request_pii: 1),
11
- class: "shakha-button shakha-button-google",
20
+ class: "sh-btn sh-btn--google",
12
21
  data: { turbo: false } do %>
13
- <svg class="shakha-google-icon" viewBox="0 0 18 18" aria-hidden="true">
22
+ <svg class="sh-btn__icon" viewBox="0 0 18 18" aria-hidden="true">
14
23
  <path fill="#4285F4" d="M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.875 2.684-6.615z"/>
15
24
  <path fill="#34A853" d="M9 18c2.43 0 4.467-.806 5.956-2.184l-2.908-2.258c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18z"/>
16
25
  <path fill="#FBBC05" d="M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332z"/>
@@ -19,11 +28,19 @@
19
28
  Continue with Google
20
29
  <% end %>
21
30
 
22
- <p class="shakha-privacy">
31
+ <div class="sh-divider">or</div>
32
+
33
+ <p class="sh-text-center" style="color: var(--sh-text-tertiary); font-size: var(--sh-text-sm);">
23
34
  By signing in, you agree to our
24
- <%= link_to "Terms of Service", "#" %> and
25
- <%= link_to "Privacy Policy", "#" %>.
35
+ <a href="#">Terms</a> and
36
+ <a href="#">Privacy Policy</a>.
26
37
  </p>
27
38
  </div>
39
+
40
+ <div class="sh-card__footer">
41
+ <span style="color: var(--sh-text-tertiary); font-size: var(--sh-text-sm);">
42
+ Secured by <strong style="color: var(--sh-text-secondary);">Shakha</strong>
43
+ </span>
44
+ </div>
28
45
  </div>
29
- </div>
46
+ </div>
@@ -0,0 +1,66 @@
1
+ <% content_for :title, "Active Sessions" %>
2
+
3
+ <div class="sh-layout sh-animate-fade">
4
+ <div class="sh-card sh-animate-slide" style="max-width: 560px;">
5
+ <div class="sh-card__header">
6
+ <div class="sh-brand">
7
+ <div class="sh-brand__icon">S</div>
8
+ <div>
9
+ <div class="sh-brand__name">Active Sessions</div>
10
+ <div class="sh-brand__subtitle">Manage your signed-in devices</div>
11
+ </div>
12
+ </div>
13
+ </div>
14
+
15
+ <div class="sh-sessions">
16
+ <div class="sh-sessions__header">
17
+ <span class="sh-sessions__title">Devices</span>
18
+ <span class="sh-sessions__count"><%= @sessions&.size || 0 %> active</span>
19
+ </div>
20
+
21
+ <% if @sessions&.any? %>
22
+ <% @sessions.each do |session| %>
23
+ <div class="sh-session <%= 'sh-session--current' if session.token == @current_token %>">
24
+ <div class="sh-session__info">
25
+ <div style="display: flex; align-items: center; gap: var(--sh-space-2);">
26
+ <% if session.token == @current_token %>
27
+ <span class="sh-session__badge">Current</span>
28
+ <% end %>
29
+ <span class="sh-session__device">Web Browser</span>
30
+ </div>
31
+ <div class="sh-session__meta">
32
+ <%= session.ip_address || "Unknown IP" %> ·
33
+ <%= session.created_at.strftime("%b %d, %Y at %I:%M %p") %>
34
+ </div>
35
+ </div>
36
+
37
+ <% if session.token != @current_token %>
38
+ <%= button_to revoke_session_path(session.id),
39
+ method: :delete,
40
+ class: "sh-session__btn",
41
+ data: { turbo: false, confirm: "Revoke this session?" } do %>
42
+ Revoke
43
+ <% end %>
44
+ <% end %>
45
+ </div>
46
+ <% end %>
47
+ <% else %>
48
+ <div class="sh-empty">
49
+ <div class="sh-empty__icon">
50
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
51
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
52
+ <path d="M7 11V7a5 5 0 0 1 10 0v4"/>
53
+ </svg>
54
+ </div>
55
+ <p>No active sessions found.</p>
56
+ </div>
57
+ <% end %>
58
+ </div>
59
+
60
+ <div class="sh-card__footer">
61
+ <%= link_to "/", class: "sh-link" do %>
62
+ ← Back to app
63
+ <% end %>
64
+ </div>
65
+ </div>
66
+ </div>
@@ -1,12 +1,15 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html lang="en">
3
3
  <head>
4
- <title><%= yield :title %></title>
4
+ <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title><%= yield(:title).presence || "Shakha" %></title>
6
7
  <%= stylesheet_link_tag "shakha", "data-turbo-track": "reload" %>
7
8
  <%= csrf_meta_tags %>
9
+ <meta name="theme-color" content="#0f0f23" media="(prefers-color-scheme: dark)">
10
+ <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
8
11
  </head>
9
12
  <body>
10
13
  <%= yield %>
11
14
  </body>
12
- </html>
15
+ </html>
data/lib/shakha/engine.rb CHANGED
@@ -21,6 +21,7 @@ module Shakha
21
21
 
22
22
  get "session" => "session#show"
23
23
  get "sessions" => "session#index"
24
+ get "sessions/view" => "session#list"
24
25
  post "session/check" => "session#check"
25
26
  delete "session" => "session#destroy"
26
27
  delete "sessions/:id" => "session#revoke"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shakha
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.7"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shakha
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asrat
@@ -90,6 +90,7 @@ files:
90
90
  - app/views/shakha/auth/callback.html.erb
91
91
  - app/views/shakha/auth/error.html.erb
92
92
  - app/views/shakha/auth/new.html.erb
93
+ - app/views/shakha/auth/sessions.html.erb
93
94
  - app/views/shakha/errors/show.html.erb
94
95
  - app/views/shakha/layouts/shakha.html.erb
95
96
  - lib/generators/shakha/install_generator.rb