keepup-http 0.5.0__py3-none-any.whl

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.
Files changed (43) hide show
  1. keepup_http/__init__.py +38 -0
  2. keepup_http/api_docs.py +76 -0
  3. keepup_http/body_limit.py +156 -0
  4. keepup_http/events_api.py +237 -0
  5. keepup_http/factory.py +562 -0
  6. keepup_http/lock_routes.py +155 -0
  7. keepup_http/plugin.py +53 -0
  8. keepup_http/routes.py +549 -0
  9. keepup_http/static/css/aos.css +1 -0
  10. keepup_http/static/css/main_nebula.css +232 -0
  11. keepup_http/static/css/main_new.css +869 -0
  12. keepup_http/static/css/tailwind.css +1 -0
  13. keepup_http/static/index_nebula.html +282 -0
  14. keepup_http/static/index_new.html +275 -0
  15. keepup_http/static/js/aos.js +1 -0
  16. keepup_http/static/js/feather-icons.js +13 -0
  17. keepup_http/static/js/layout_bootstrap.js +29 -0
  18. keepup_http/static/js/main_new.js +2076 -0
  19. keepup_http/static/js/tailwind.js +83 -0
  20. keepup_http/static/modules/css/background_tasks.css +233 -0
  21. keepup_http/static/modules/css/cluster.css +16 -0
  22. keepup_http/static/modules/css/event_manager.css +386 -0
  23. keepup_http/static/modules/css/integration_logs.css +33 -0
  24. keepup_http/static/modules/css/metrics.css +115 -0
  25. keepup_http/static/modules/css/modules.css +189 -0
  26. keepup_http/static/modules/css/themes.css +563 -0
  27. keepup_http/static/modules/css/users.css +278 -0
  28. keepup_http/static/modules/js/background_tasks.js +695 -0
  29. keepup_http/static/modules/js/chart.js +14 -0
  30. keepup_http/static/modules/js/chartjs-adapter-date-fns.bundle.min.js +7 -0
  31. keepup_http/static/modules/js/cluster.js +374 -0
  32. keepup_http/static/modules/js/event_manager.js +1007 -0
  33. keepup_http/static/modules/js/integration_logs.js +795 -0
  34. keepup_http/static/modules/js/metrics.js +929 -0
  35. keepup_http/static/modules/js/modules.js +1128 -0
  36. keepup_http/static/modules/js/themes.js +684 -0
  37. keepup_http/static/modules/js/users.js +898 -0
  38. keepup_http/web.py +214 -0
  39. keepup_http-0.5.0.dist-info/METADATA +29 -0
  40. keepup_http-0.5.0.dist-info/RECORD +43 -0
  41. keepup_http-0.5.0.dist-info/WHEEL +5 -0
  42. keepup_http-0.5.0.dist-info/entry_points.txt +2 -0
  43. keepup_http-0.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,869 @@
1
+ /* ==========================================================================
2
+ Design tokens of the active theme.
3
+
4
+ The theme owns the admin chrome: the sidebar, the navigation buttons, the
5
+ collapsed menu and the content area. Module stylesheets are scoped to their
6
+ own section and must not declare these selectors -- module CSS is injected
7
+ after the page stylesheets, so a module rule would win on cascade order and
8
+ the chrome would depend on which sections a role happens to be granted.
9
+
10
+ Every var() below carries a fallback: a theme that does not declare a token
11
+ must fall back to the default look, not to no styling at all.
12
+ ========================================================================== */
13
+ :root {
14
+ /* Sidebar */
15
+ --sidebar-bg: #1d4ed8;
16
+ --sidebar-fg: #ffffff;
17
+ --sidebar-header-bg: #1e40af;
18
+ --sidebar-width: 16rem;
19
+ --sidebar-width-collapsed: 64px;
20
+ --modules-nav-max-height: calc(100vh - 200px);
21
+ --modules-nav-padding: 8px 0 16px 0;
22
+ --sidebar-scrollbar-track: #1e3a8a;
23
+ --sidebar-scrollbar-thumb: #60a5fa;
24
+ --sidebar-scrollbar-thumb-hover: #93c5fd;
25
+
26
+ /* Navigation button */
27
+ --nav-item-padding: 0.75rem 1.5rem;
28
+ --nav-item-padding-collapsed: 0.75rem;
29
+ --nav-item-radius: 0;
30
+ --nav-item-fg: #dbeafe;
31
+ --nav-item-bg: transparent;
32
+ --nav-item-fg-hover: #dbeafe;
33
+ --nav-item-bg-hover: #2563eb;
34
+ --nav-item-fg-active: #ffffff;
35
+ --nav-item-bg-active: #1e40af;
36
+ --nav-item-icon-gap: 0.75rem;
37
+
38
+ /* Adaptive layout. --layout-breakpoint is the single source of the
39
+ narrow/wide boundary: main_new.js reads it off :root, drives a
40
+ matchMedia off it and keeps the .is-narrow class on <html>, and the
41
+ theme's head script feeds the same value to tailwind.config so the
42
+ md: utilities in the sections switch at the same width as the chrome.
43
+ A media query cannot read a custom property, and the project has no
44
+ CSS build step to expand one, so the class is what the rules match on.
45
+ The default is 768px because that is where the md: utilities the
46
+ sections already use switch today. */
47
+ --layout-breakpoint: 768px;
48
+ --drawer-width: 16rem;
49
+ /* The drawer floats over the content, so a sidebar background that is
50
+ translucent against the page (nebula's is) would let the content read
51
+ through it. A theme with such a background overrides this one. */
52
+ --drawer-bg: var(--sidebar-bg, #1d4ed8);
53
+ --backdrop-color: rgba(15, 23, 42, 0.55);
54
+ --content-padding-narrow: 1rem;
55
+ --tap-target-min: 44px;
56
+ /* Above this many columns a table stops folding into cards and scrolls
57
+ sideways instead: six narrow columns still read as a card, six wide
58
+ ones do not, so the threshold is a value to tune, not a constant. */
59
+ --table-card-columns: 6;
60
+ --table-card-bg: #ffffff;
61
+ --table-card-border: #e5e7eb;
62
+ --table-card-label-fg: #6b7280;
63
+ /* A pinned column needs an opaque background of its own, or the columns
64
+ scrolling past show through it. */
65
+ --table-sticky-bg: #ffffff;
66
+ --table-sticky-header-bg: #f9fafb;
67
+
68
+ /* Shared */
69
+ --chrome-transition: all 0.3s ease;
70
+ }
71
+
72
+ /* Tabs */
73
+ .tab-content {
74
+ display: none;
75
+ }
76
+
77
+ .tab-content.active {
78
+ display: block;
79
+ }
80
+
81
+ .tab-button {
82
+ border: none;
83
+ background: none;
84
+ cursor: pointer;
85
+ padding: 0.5rem 1rem;
86
+ border-bottom: 2px solid transparent;
87
+ }
88
+
89
+ .tab-button.active {
90
+ border-bottom-color: #3b82f6;
91
+ color: #3b82f6;
92
+ }
93
+
94
+ /* Content sections */
95
+ .content-section {
96
+ display: none;
97
+ }
98
+
99
+ .content-section.active {
100
+ display: block;
101
+ }
102
+
103
+ /* Shared modals */
104
+ .modal {
105
+ display: none;
106
+ position: fixed;
107
+ z-index: 1000;
108
+ left: 0;
109
+ top: 0;
110
+ width: 100%;
111
+ height: 100%;
112
+ background-color: rgba(0, 0, 0, 0.5);
113
+ }
114
+
115
+ .modal-content {
116
+ background-color: white;
117
+ margin: 10% auto;
118
+ padding: 2rem;
119
+ border-radius: 0.5rem;
120
+ width: 90%;
121
+ max-width: 400px;
122
+ }
123
+
124
+ .modal.hidden {
125
+ opacity: 0;
126
+ pointer-events: none;
127
+ }
128
+
129
+ /* Notifications */
130
+ .notification {
131
+ transition: all 0.3s ease;
132
+ }
133
+
134
+ /* ==========================================================================
135
+ Admin chrome. Owned by the theme; module stylesheets must not declare these
136
+ selectors. See the tokens above and tests/frontend_style_boundary_tests.py.
137
+ ========================================================================== */
138
+
139
+ /* Sidebar. Position and stacking used to be Tailwind utilities in the theme
140
+ markup (fixed inset-y-0 left-0 w-64 z-50 -translate-x-full md:translate-x-0);
141
+ they live here so the theme owns the panel on every width, not just its
142
+ colours. */
143
+ #sidebar {
144
+ position: fixed;
145
+ top: 0;
146
+ bottom: 0;
147
+ left: 0;
148
+ z-index: 50;
149
+ width: var(--sidebar-width, 16rem);
150
+ background-color: var(--sidebar-bg, #1d4ed8);
151
+ color: var(--sidebar-fg, #ffffff);
152
+ transition: var(--chrome-transition, all 0.3s ease);
153
+ /* A long menu has to scroll. This used to come from documentation.css, so
154
+ the sidebar only scrolled for roles that were granted that section. */
155
+ overflow-x: hidden;
156
+ overflow-y: auto;
157
+ }
158
+
159
+ /* Content sits beside the panel; this used to be md:ml-64 in the markup. */
160
+ .main-content {
161
+ margin-left: var(--sidebar-width, 16rem);
162
+ transition: var(--chrome-transition, all 0.3s ease);
163
+ }
164
+
165
+ /* The button that opens the drawer belongs to the narrow layout only. */
166
+ .nav-toggle-btn {
167
+ display: none;
168
+ }
169
+
170
+ #modules-nav-container {
171
+ width: 100%;
172
+ max-height: var(--modules-nav-max-height, calc(100vh - 200px));
173
+ padding: var(--modules-nav-padding, 8px 0 16px 0);
174
+ overflow-x: hidden;
175
+ overflow-y: auto;
176
+ scrollbar-width: thin;
177
+ }
178
+
179
+ #modules-nav-container::-webkit-scrollbar {
180
+ width: 4px;
181
+ height: 6px;
182
+ }
183
+
184
+ #modules-nav-container::-webkit-scrollbar-track,
185
+ #modules-nav-container::-webkit-scrollbar-corner {
186
+ background: var(--sidebar-scrollbar-track, #1e3a8a);
187
+ border-radius: 3px;
188
+ }
189
+
190
+ #modules-nav-container::-webkit-scrollbar-thumb {
191
+ background: var(--sidebar-scrollbar-thumb, #60a5fa);
192
+ border-radius: 3px;
193
+ }
194
+
195
+ #modules-nav-container::-webkit-scrollbar-thumb:hover {
196
+ background: var(--sidebar-scrollbar-thumb-hover, #93c5fd);
197
+ }
198
+
199
+ /* Navigation button. Its whole appearance lives here: main_new.js gives the
200
+ element only the semantic classes, so the theme is the single owner. */
201
+ .nav-item {
202
+ display: flex;
203
+ align-items: center;
204
+ padding: var(--nav-item-padding, 0.75rem 1.5rem);
205
+ border-radius: var(--nav-item-radius, 0);
206
+ color: var(--nav-item-fg, #dbeafe);
207
+ background-color: var(--nav-item-bg, transparent);
208
+ transition: var(--chrome-transition, all 0.3s ease);
209
+ }
210
+
211
+ .nav-item:hover {
212
+ color: var(--nav-item-fg-hover, #dbeafe);
213
+ background-color: var(--nav-item-bg-hover, #2563eb);
214
+ }
215
+
216
+ .nav-item.active {
217
+ color: var(--nav-item-fg-active, #ffffff);
218
+ background-color: var(--nav-item-bg-active, #1e40af);
219
+ }
220
+
221
+ .nav-item i {
222
+ flex-shrink: 0;
223
+ margin-right: var(--nav-item-icon-gap, 0.75rem);
224
+ transition: margin 0.3s ease;
225
+ }
226
+
227
+ /* Collapsed sidebar. Declared once, for the whole panel, and only for the wide
228
+ layout: below the breakpoint the panel is a drawer, so a collapsed flag saved
229
+ on a desktop would narrow the drawer and shift the content 64px to the right
230
+ of a column that is not on screen. The saved value is left alone -- it is the
231
+ wide layout's state, and the panel comes back collapsed when the window
232
+ widens again. */
233
+ :root:not(.is-narrow) .sidebar-collapsed {
234
+ width: var(--sidebar-width-collapsed, 64px) !important;
235
+ }
236
+
237
+ :root:not(.is-narrow) .sidebar-collapsed .nav-text {
238
+ display: none;
239
+ }
240
+
241
+ :root:not(.is-narrow) .sidebar-collapsed .logo-text {
242
+ display: none;
243
+ }
244
+
245
+ :root:not(.is-narrow) .sidebar-collapsed .nav-item {
246
+ justify-content: center;
247
+ padding-left: var(--nav-item-padding-collapsed, 0.75rem);
248
+ padding-right: var(--nav-item-padding-collapsed, 0.75rem);
249
+ }
250
+
251
+ :root:not(.is-narrow) .sidebar-collapsed .nav-item i {
252
+ margin-right: 0;
253
+ }
254
+
255
+ :root:not(.is-narrow) .sidebar-collapsed ~ .main-content {
256
+ margin-left: var(--sidebar-width-collapsed, 64px) !important;
257
+ }
258
+
259
+ .toggle-sidebar-btn {
260
+ transition: var(--chrome-transition, all 0.3s ease);
261
+ min-width: var(--tap-target-min, 44px);
262
+ min-height: var(--tap-target-min, 44px);
263
+ display: inline-flex;
264
+ align-items: center;
265
+ justify-content: center;
266
+ }
267
+
268
+ :root:not(.is-narrow) .sidebar-collapsed .toggle-sidebar-btn {
269
+ transform: rotate(180deg);
270
+ }
271
+
272
+ /* ==========================================================================
273
+ Narrow layout. Matched on the .is-narrow class that main_new.js keeps on
274
+ <html> from the --layout-breakpoint token -- a media query cannot read a
275
+ custom property, and there is no CSS build step here to expand one.
276
+
277
+ Declared once, for every theme. A theme redefines the token values above;
278
+ it must not restate these rules, or the drawer would behave differently
279
+ depending on which theme is active.
280
+ ========================================================================== */
281
+ .is-narrow #sidebar {
282
+ width: var(--drawer-width, 16rem);
283
+ /* Never the whole screen: the content behind has to stay visible enough to
284
+ read as content, so tapping the backdrop is an obvious way out. */
285
+ max-width: 85vw;
286
+ background-color: var(--drawer-bg, var(--sidebar-bg, #1d4ed8));
287
+ transform: translateX(-100%);
288
+ }
289
+
290
+ .is-narrow.nav-open #sidebar {
291
+ transform: translateX(0);
292
+ }
293
+
294
+ .is-narrow .main-content {
295
+ margin-left: 0;
296
+ }
297
+
298
+ /* The content well is padded p-6 in the theme markup: 48px of a 360px screen
299
+ spent on margins before the section gets to draw anything. */
300
+ .is-narrow .main-content > main {
301
+ padding: var(--content-padding-narrow, 1rem);
302
+ }
303
+
304
+ .is-narrow .nav-toggle-btn {
305
+ display: inline-flex;
306
+ align-items: center;
307
+ justify-content: center;
308
+ min-width: var(--tap-target-min, 44px);
309
+ min-height: var(--tap-target-min, 44px);
310
+ }
311
+
312
+ .is-narrow .nav-item {
313
+ min-height: var(--tap-target-min, 44px);
314
+ }
315
+
316
+ .nav-backdrop {
317
+ position: fixed;
318
+ inset: 0;
319
+ z-index: 40;
320
+ background-color: var(--backdrop-color, rgba(15, 23, 42, 0.55));
321
+ opacity: 0;
322
+ visibility: hidden;
323
+ transition: opacity 0.2s ease, visibility 0.2s ease;
324
+ }
325
+
326
+ .is-narrow.nav-open .nav-backdrop {
327
+ opacity: 1;
328
+ visibility: visible;
329
+ }
330
+
331
+ /* Scrolling with the drawer open belongs to the list of sections, not to the
332
+ page behind it. A class rather than an inline style, so there is no previous
333
+ value to remember and restore. */
334
+ body.nav-locked {
335
+ overflow: hidden;
336
+ }
337
+
338
+ /* Top row. At 360px the section title, the user's name, role and status badges
339
+ and three buttons do not fit on one line, so the row wraps and the parts that
340
+ are not needed to navigate give way first. */
341
+ .is-narrow .app-header-row {
342
+ flex-wrap: wrap;
343
+ gap: 0.5rem;
344
+ padding-left: 0.75rem;
345
+ padding-right: 0.75rem;
346
+ }
347
+
348
+ .is-narrow .app-header-title {
349
+ /* Menu button and title take the first line and keep it whole. */
350
+ flex: 1 1 100%;
351
+ min-width: 0;
352
+ }
353
+
354
+ .is-narrow #current-section-title {
355
+ font-size: 1rem;
356
+ line-height: 1.25;
357
+ min-width: 0;
358
+ overflow: hidden;
359
+ text-overflow: ellipsis;
360
+ white-space: nowrap;
361
+ }
362
+
363
+ .is-narrow .app-header-actions {
364
+ flex: 1 1 100%;
365
+ flex-wrap: wrap;
366
+ justify-content: flex-start;
367
+ min-width: 0;
368
+ }
369
+
370
+ .is-narrow #userInfo {
371
+ min-width: 0;
372
+ font-size: 0.75rem;
373
+ }
374
+
375
+ .is-narrow #usernameDisplay {
376
+ display: inline-block;
377
+ max-width: 9rem;
378
+ overflow: hidden;
379
+ text-overflow: ellipsis;
380
+ white-space: nowrap;
381
+ vertical-align: bottom;
382
+ }
383
+
384
+ /* The bell has no handler behind it; on a phone the space is worth more. */
385
+ .is-narrow .app-header-bell {
386
+ display: none;
387
+ }
388
+
389
+ .is-narrow .app-header-actions button {
390
+ min-width: var(--tap-target-min, 44px);
391
+ min-height: var(--tap-target-min, 44px);
392
+ display: inline-flex;
393
+ align-items: center;
394
+ justify-content: center;
395
+ }
396
+
397
+ /* Build version strip: four labelled fields laid out in a row stretch the
398
+ document instead of wrapping, because space-x-* sets margins, not gaps. */
399
+ .is-narrow .version-panel-fields {
400
+ flex-wrap: wrap;
401
+ gap: 0.25rem 0.75rem;
402
+ }
403
+
404
+ .is-narrow .version-panel-fields > span {
405
+ margin-left: 0 !important;
406
+ }
407
+
408
+ /* --------------------------------------------------------------------------
409
+ Responsive tables. The mode is chosen by the helper in main_new.js and
410
+ published as data-responsive-mode; everything below is the drawing of it.
411
+ Above the breakpoint none of this applies and the table stays a table.
412
+ -------------------------------------------------------------------------- */
413
+
414
+ /* Wide tables: the scroll belongs to this container, never to the document. */
415
+ .table-scroll {
416
+ overflow-x: auto;
417
+ -webkit-overflow-scrolling: touch;
418
+ }
419
+
420
+ .is-narrow .responsive-table[data-responsive-mode="scroll"] th:first-child,
421
+ .is-narrow .responsive-table[data-responsive-mode="scroll"] td:first-child {
422
+ position: sticky;
423
+ left: 0;
424
+ z-index: 1;
425
+ background-color: var(--table-sticky-bg, #ffffff);
426
+ }
427
+
428
+ .is-narrow .responsive-table[data-responsive-mode="scroll"] thead th:first-child {
429
+ background-color: var(--table-sticky-header-bg, #f9fafb);
430
+ }
431
+
432
+ /* Narrow tables: one card per row, each cell labelled with its heading. */
433
+ .is-narrow .responsive-table[data-responsive-mode="cards"] thead {
434
+ display: none;
435
+ }
436
+
437
+ .is-narrow .responsive-table[data-responsive-mode="cards"],
438
+ .is-narrow .responsive-table[data-responsive-mode="cards"] tbody,
439
+ .is-narrow .responsive-table[data-responsive-mode="cards"] tr,
440
+ .is-narrow .responsive-table[data-responsive-mode="cards"] td {
441
+ display: block;
442
+ width: auto;
443
+ min-width: 0;
444
+ }
445
+
446
+ .is-narrow .responsive-table[data-responsive-mode="cards"] tr {
447
+ margin-bottom: 0.75rem;
448
+ padding: 0.25rem 0.75rem;
449
+ border: 1px solid var(--table-card-border, #e5e7eb);
450
+ border-radius: 0.5rem;
451
+ background-color: var(--table-card-bg, #ffffff);
452
+ }
453
+
454
+ .is-narrow .responsive-table[data-responsive-mode="cards"] td {
455
+ display: flex;
456
+ align-items: baseline;
457
+ justify-content: space-between;
458
+ gap: 0.75rem;
459
+ padding: 0.4rem 0;
460
+ border: 0;
461
+ text-align: right;
462
+ white-space: normal;
463
+ overflow-wrap: anywhere;
464
+ }
465
+
466
+ .is-narrow .responsive-table[data-responsive-mode="cards"] td::before {
467
+ content: attr(data-label);
468
+ flex: 0 0 40%;
469
+ text-align: left;
470
+ font-weight: 600;
471
+ color: var(--table-card-label-fg, #6b7280);
472
+ }
473
+
474
+ /* Rows the helper deliberately left unlabelled -- empty states, spanning
475
+ summary rows -- read as plain text rather than growing an empty label. */
476
+ .is-narrow .responsive-table[data-responsive-mode="cards"] td:not([data-label]) {
477
+ display: block;
478
+ text-align: left;
479
+ }
480
+
481
+ .is-narrow .responsive-table[data-responsive-mode="cards"] td:not([data-label])::before {
482
+ content: none;
483
+ }
484
+
485
+ /* --------------------------------------------------------------------------
486
+ Modals. Both shapes in this file -- the plain .modal and the .modal-overlay
487
+ one -- carry a fixed max-width and generous margins that leave a phone with
488
+ a window it cannot read. Here they fill the screen minus a margin and scroll
489
+ inside themselves, with the header and its close button staying put.
490
+ -------------------------------------------------------------------------- */
491
+ /* One scroll container, and it is the overlay.
492
+
493
+ The sections build their dialogs two ways -- the .modal-overlay pair in this
494
+ file, and, far more often, a Tailwind overlay
495
+ (`fixed inset-0 ... flex items-center justify-center`) holding a panel. Both
496
+ broke the same way on a phone, for two reasons:
497
+
498
+ * a flex item centred in a container it is taller than overflows equally
499
+ at both ends, and neither end can be scrolled to, so the top of the
500
+ dialog and the buttons at its bottom were simply unreachable;
501
+ * the `flex flex-col` panels put their scrolling body in a `flex-1`
502
+ child, whose default `min-height: auto` refuses to shrink below its
503
+ content. The panel grew past its own max-height instead of the body
504
+ scrolling inside it, and the footer went off-screen.
505
+
506
+ Rather than constrain the panel and rely on an inner scroller, the narrow
507
+ layout lets the panel be its natural height and gives the scroll to the
508
+ overlay. That removes both failures at once, needs nothing from the panel's
509
+ markup, and sidesteps `vh` on mobile browsers counting an address bar that
510
+ has already slid away. A sticky header inside a panel still sticks -- the
511
+ overlay is its scrollport. */
512
+ .is-narrow .modal-overlay,
513
+ .is-narrow .fixed.inset-0.items-center {
514
+ align-items: flex-start;
515
+ overflow-y: auto;
516
+ padding: 0.75rem;
517
+ }
518
+
519
+ .is-narrow .modal-overlay > *,
520
+ .is-narrow .fixed.inset-0.items-center > * {
521
+ /* w-96 is 384px, wider than a 360px screen on its own. */
522
+ max-width: 100%;
523
+ max-height: none;
524
+ margin-top: 0;
525
+ margin-bottom: 0;
526
+ }
527
+
528
+ .is-narrow .modal-content {
529
+ width: 100%;
530
+ padding: 1rem;
531
+ border-radius: 1rem;
532
+ }
533
+
534
+ .is-narrow .modal-header {
535
+ position: sticky;
536
+ top: 0;
537
+ z-index: 10;
538
+ padding: 1rem;
539
+ border-radius: 1rem 1rem 0 0;
540
+ }
541
+
542
+ .is-narrow .modal-body {
543
+ padding: 1rem;
544
+ }
545
+
546
+ .is-narrow .modal-title {
547
+ font-size: 1.125rem;
548
+ }
549
+
550
+ /* The .modal-overlay shape pads its own content, so the sticky header has to
551
+ sit flush; the .modal shape pads the box instead. Both end up with the close
552
+ button reachable without horizontal scrolling. */
553
+ .is-narrow .modal-overlay .modal-content {
554
+ padding: 0;
555
+ }
556
+
557
+ /* --------------------------------------------------------------------------
558
+ Section content: the two shapes that overflow a phone in almost every
559
+ section, fixed once here rather than in twenty stylesheets.
560
+ -------------------------------------------------------------------------- */
561
+
562
+ /* A title on the left and a row of buttons on the right is the standard header
563
+ of a section and of its filter panel. At 360px the pair does not fit on one
564
+ line, and without wrapping the buttons are pushed past the edge. */
565
+ .is-narrow .content-section .flex.justify-between {
566
+ flex-wrap: wrap;
567
+ gap: 0.5rem;
568
+ }
569
+
570
+ /* Inputs sized in ch or with a fixed width are common in filter panels and
571
+ push the panel wider than the screen. */
572
+ .is-narrow .content-section input:not([type="checkbox"]):not([type="radio"]),
573
+ .is-narrow .content-section select,
574
+ .is-narrow .content-section textarea {
575
+ max-width: 100%;
576
+ }
577
+
578
+ /* A safety net, not the fix: a section that overflows is meant to scroll inside
579
+ its own container, and the checks in tests/frontend_mobile_layout_tests.py
580
+ are what keep it that way. This only stops one missed section from making the
581
+ whole document slide sideways. */
582
+ .is-narrow body {
583
+ overflow-x: hidden;
584
+ }
585
+
586
+ /* A user's status and role */
587
+ .status-badge {
588
+ padding: 0.25rem 0.5rem;
589
+ border-radius: 0.25rem;
590
+ font-size: 0.75rem;
591
+ font-weight: 500;
592
+ }
593
+
594
+ .status-active {
595
+ background-color: #dcfce7;
596
+ color: #166534;
597
+ }
598
+
599
+ .status-blocked {
600
+ background-color: #fee2e2;
601
+ color: #991b1b;
602
+ }
603
+
604
+ .status-inactive {
605
+ background-color: #fee2e2;
606
+ color: #991b1b;
607
+ }
608
+
609
+ .role-badge {
610
+ padding: 2px 8px;
611
+ border-radius: 12px;
612
+ font-size: 0.75rem;
613
+ font-weight: 600;
614
+ }
615
+
616
+ /* The application's badge in the role's place (keepup-37): the tones are the
617
+ theme's to colour, the application only names one. */
618
+ .header-badge-neutral {
619
+ background-color: var(--surface-muted, #eef2f7);
620
+ color: var(--text-strong, #1f2937);
621
+ }
622
+
623
+ .header-badge-positive {
624
+ background-color: #dcfce7;
625
+ color: #166534;
626
+ }
627
+
628
+ .header-badge-warning {
629
+ background-color: #fef3c7;
630
+ color: #92400e;
631
+ }
632
+
633
+ .role-admin {
634
+ background-color: #e9d5ff;
635
+ color: #7c3aed;
636
+ }
637
+
638
+ .role-client {
639
+ background-color: #dbeafe;
640
+ color: #1d4ed8;
641
+ }
642
+
643
+ /* File upload */
644
+ .upload-area {
645
+ border: 2px dashed #d1d5db;
646
+ border-radius: 0.5rem;
647
+ padding: 2rem;
648
+ text-align: center;
649
+ transition: border-color 0.3s;
650
+ }
651
+
652
+ .upload-area:hover {
653
+ border-color: #3b82f6;
654
+ }
655
+
656
+ /* XML sources */
657
+ .source-item {
658
+ transition: box-shadow 0.3s;
659
+ }
660
+
661
+ .source-item:hover {
662
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
663
+ }
664
+
665
+ /* The version panel */
666
+ #versionPanel {
667
+ z-index: 1000;
668
+ font-family: monospace;
669
+ }
670
+
671
+ /* Further shared classes */
672
+ .flex-center {
673
+ display: flex;
674
+ align-items: center;
675
+ justify-content: center;
676
+ }
677
+
678
+ .text-truncate {
679
+ overflow: hidden;
680
+ text-overflow: ellipsis;
681
+ white-space: nowrap;
682
+ }
683
+
684
+ .cursor-help {
685
+ cursor: help;
686
+ }
687
+
688
+ .hidden {
689
+ display: none !important;
690
+ }
691
+
692
+ .user-info-field {
693
+ transition: all 0.3s ease;
694
+ }
695
+
696
+ .terms-link {
697
+ color: #2563eb;
698
+ text-decoration: underline;
699
+ transition: color 0.2s;
700
+ }
701
+
702
+ .terms-link:hover {
703
+ color: #1d4ed8;
704
+ }
705
+
706
+ .phone-input {
707
+ font-family: 'Roboto Mono', monospace;
708
+ }
709
+ .auth-block {
710
+ position: fixed;
711
+ top: 0;
712
+ left: 0;
713
+ width: 100%;
714
+ height: 100%;
715
+ background-color: #f3f4f6;
716
+ z-index: 9999;
717
+ display: flex;
718
+ justify-content: center;
719
+ align-items: center;
720
+ }
721
+
722
+ /* The modal */
723
+ .modal-overlay {
724
+ position: fixed;
725
+ inset: 0;
726
+ background: rgba(0, 0, 0, 0.5);
727
+ backdrop-filter: blur(4px);
728
+ z-index: 99999;
729
+ display: flex;
730
+ align-items: center;
731
+ justify-content: center;
732
+ padding: 1rem;
733
+ opacity: 0;
734
+ transition: opacity 0.3s ease;
735
+ pointer-events: none;
736
+ }
737
+
738
+ .modal-overlay.active {
739
+ opacity: 1;
740
+ pointer-events: all;
741
+ }
742
+
743
+ .modal-content {
744
+ background: white;
745
+ border-radius: 1.5rem;
746
+ width: 100%;
747
+ max-width: 600px;
748
+ max-height: 90vh;
749
+ overflow-y: auto;
750
+ transform: scale(0.95);
751
+ transition: transform 0.3s ease;
752
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
753
+ margin: auto;
754
+ }
755
+
756
+ .modal-overlay.active .modal-content {
757
+ transform: scale(1);
758
+ }
759
+
760
+ .modal-header {
761
+ padding: 1.5rem;
762
+ border-bottom: 1px solid #e2e8f0;
763
+ display: flex;
764
+ align-items: center;
765
+ justify-content: space-between;
766
+ background: linear-gradient(135deg, #0ea5e9 0%, #3b82f6 100%);
767
+ border-radius: 1.5rem 1.5rem 0 0;
768
+ color: white;
769
+ position: sticky;
770
+ top: 0;
771
+ z-index: 10;
772
+ }
773
+
774
+ .modal-body {
775
+ padding: 1.5rem;
776
+ font-size: 0.95rem;
777
+ line-height: 1.6;
778
+ }
779
+
780
+ .modal-section {
781
+ margin-bottom: 2rem;
782
+ }
783
+
784
+ .modal-section:last-child {
785
+ margin-bottom: 0;
786
+ }
787
+
788
+ .modal-title {
789
+ font-size: 1.5rem;
790
+ font-weight: 600;
791
+ margin: 0;
792
+ color: white;
793
+ }
794
+
795
+ .section-title {
796
+ font-size: 1.25rem;
797
+ font-weight: 600;
798
+ color: #1e293b;
799
+ margin-bottom: 1rem;
800
+ padding-bottom: 0.5rem;
801
+ border-bottom: 2px solid #e2e8f0;
802
+ }
803
+
804
+ .terms-content {
805
+ background: #f8fafc;
806
+ padding: 1rem;
807
+ border-radius: 0.75rem;
808
+ max-height: 300px;
809
+ overflow-y: auto;
810
+ font-size: 0.9rem;
811
+ line-height: 1.5;
812
+ }
813
+
814
+ .terms-content h3 {
815
+ font-size: 1.1rem;
816
+ font-weight: 600;
817
+ color: #1e293b;
818
+ margin: 1rem 0 0.5rem 0;
819
+ }
820
+
821
+ .terms-content h3:first-child {
822
+ margin-top: 0;
823
+ }
824
+
825
+ .terms-content p {
826
+ margin-bottom: 0.75rem;
827
+ color: #475569;
828
+ }
829
+
830
+ .close-btn {
831
+ background: rgba(255, 255, 255, 0.2);
832
+ border: none;
833
+ border-radius: 0.75rem;
834
+ padding: 0.5rem;
835
+ cursor: pointer;
836
+ transition: all 0.2s ease;
837
+ color: white;
838
+ }
839
+
840
+ .close-btn:hover {
841
+ background: rgba(255, 255, 255, 0.3);
842
+ transform: rotate(90deg);
843
+ }
844
+
845
+ /* Smoother scrolling */
846
+ .terms-content::-webkit-scrollbar {
847
+ width: 6px;
848
+ }
849
+
850
+ .terms-content::-webkit-scrollbar-track {
851
+ background: #f1f5f9;
852
+ border-radius: 3px;
853
+ }
854
+
855
+ .terms-content::-webkit-scrollbar-thumb {
856
+ background: #cbd5e1;
857
+ border-radius: 3px;
858
+ }
859
+
860
+ .terms-content::-webkit-scrollbar-thumb:hover {
861
+ background: #94a3b8;
862
+ }
863
+ /* The terms arrive as plain text from the server and are inserted as text, not
864
+ as markup: whatever the file says, it cannot bring tags into the page. */
865
+ .terms-plain {
866
+ white-space: pre-wrap;
867
+ font-size: 0.9rem;
868
+ line-height: 1.6;
869
+ }