@junoput01/junoui 0.2.0

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 (136) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/README.md +117 -0
  3. package/dist/android/colors.xml +94 -0
  4. package/dist/android/dimens.xml +49 -0
  5. package/dist/css/juno-fonts.css +45 -0
  6. package/dist/css/juno-tokens.css +345 -0
  7. package/dist/css/juno.css +4481 -0
  8. package/dist/flutter/juno_tokens.dart +143 -0
  9. package/dist/fonts/b612-400.woff2 +0 -0
  10. package/dist/fonts/b612-700.woff2 +0 -0
  11. package/dist/fonts/b612-mono-400.woff2 +0 -0
  12. package/dist/fonts/b612-mono-700.woff2 +0 -0
  13. package/dist/icons/juno-icons.svg +69 -0
  14. package/dist/ios/JunoTokens.swift +149 -0
  15. package/dist/js/tokens.d.ts +11 -0
  16. package/dist/js/tokens.js +244 -0
  17. package/dist/json/tokens.json +921 -0
  18. package/dist/scss/_juno-tokens.scss +176 -0
  19. package/package.json +92 -0
  20. package/src/css/base.css +120 -0
  21. package/src/css/components/accordion.css +83 -0
  22. package/src/css/components/alert.css +90 -0
  23. package/src/css/components/avatar.css +82 -0
  24. package/src/css/components/badge.css +55 -0
  25. package/src/css/components/breadcrumb.css +64 -0
  26. package/src/css/components/button.css +59 -0
  27. package/src/css/components/card.css +68 -0
  28. package/src/css/components/checkbox.css +89 -0
  29. package/src/css/components/chip.css +89 -0
  30. package/src/css/components/divider.css +50 -0
  31. package/src/css/components/dock.css +74 -0
  32. package/src/css/components/dot.css +35 -0
  33. package/src/css/components/drawer.css +80 -0
  34. package/src/css/components/field.css +37 -0
  35. package/src/css/components/gauge.css +78 -0
  36. package/src/css/components/icon.css +35 -0
  37. package/src/css/components/input.css +60 -0
  38. package/src/css/components/list.css +135 -0
  39. package/src/css/components/loader.css +159 -0
  40. package/src/css/components/menu.css +114 -0
  41. package/src/css/components/modal.css +161 -0
  42. package/src/css/components/navbar.css +103 -0
  43. package/src/css/components/pagination.css +72 -0
  44. package/src/css/components/pillbar.css +111 -0
  45. package/src/css/components/popover.css +73 -0
  46. package/src/css/components/rail.css +96 -0
  47. package/src/css/components/readout.css +45 -0
  48. package/src/css/components/segmented.css +99 -0
  49. package/src/css/components/select.css +38 -0
  50. package/src/css/components/skeleton.css +62 -0
  51. package/src/css/components/slider.css +117 -0
  52. package/src/css/components/spark.css +26 -0
  53. package/src/css/components/stepper.css +121 -0
  54. package/src/css/components/switch.css +131 -0
  55. package/src/css/components/table.css +443 -0
  56. package/src/css/components/tabs.css +66 -0
  57. package/src/css/components/thumb.css +69 -0
  58. package/src/css/components/toast.css +109 -0
  59. package/src/css/components/toggle-button.css +91 -0
  60. package/src/css/components/tooltip.css +155 -0
  61. package/src/css/density.css +40 -0
  62. package/src/css/fonts.css +45 -0
  63. package/src/css/layout.css +99 -0
  64. package/src/css/typescale.css +27 -0
  65. package/src/css/utilities.css +76 -0
  66. package/src/fonts/LICENSE +93 -0
  67. package/src/fonts/b612-400.woff2 +0 -0
  68. package/src/fonts/b612-700.woff2 +0 -0
  69. package/src/fonts/b612-mono-400.woff2 +0 -0
  70. package/src/fonts/b612-mono-700.woff2 +0 -0
  71. package/src/icons/LICENSE +21 -0
  72. package/src/icons/arrow-clockwise.svg +1 -0
  73. package/src/icons/arrow-down.svg +1 -0
  74. package/src/icons/arrow-left.svg +1 -0
  75. package/src/icons/arrow-right.svg +1 -0
  76. package/src/icons/arrow-up.svg +1 -0
  77. package/src/icons/arrows-clockwise.svg +1 -0
  78. package/src/icons/arrows-out.svg +1 -0
  79. package/src/icons/bell.svg +1 -0
  80. package/src/icons/calendar-blank.svg +1 -0
  81. package/src/icons/caret-down.svg +1 -0
  82. package/src/icons/caret-left.svg +1 -0
  83. package/src/icons/caret-right.svg +1 -0
  84. package/src/icons/caret-up.svg +1 -0
  85. package/src/icons/chat-circle.svg +1 -0
  86. package/src/icons/check-circle.svg +1 -0
  87. package/src/icons/check.svg +1 -0
  88. package/src/icons/clock.svg +1 -0
  89. package/src/icons/cloud-arrow-down.svg +1 -0
  90. package/src/icons/cloud.svg +1 -0
  91. package/src/icons/copy.svg +1 -0
  92. package/src/icons/cpu.svg +1 -0
  93. package/src/icons/dots-three-vertical.svg +1 -0
  94. package/src/icons/dots-three.svg +1 -0
  95. package/src/icons/download.svg +1 -0
  96. package/src/icons/envelope.svg +1 -0
  97. package/src/icons/eye-slash.svg +1 -0
  98. package/src/icons/eye.svg +1 -0
  99. package/src/icons/file.svg +1 -0
  100. package/src/icons/film-strip.svg +1 -0
  101. package/src/icons/floppy-disk.svg +1 -0
  102. package/src/icons/folder.svg +1 -0
  103. package/src/icons/funnel.svg +1 -0
  104. package/src/icons/gear.svg +1 -0
  105. package/src/icons/hard-drives.svg +1 -0
  106. package/src/icons/heart.svg +1 -0
  107. package/src/icons/hexagon.svg +1 -0
  108. package/src/icons/house.svg +1 -0
  109. package/src/icons/images.svg +1 -0
  110. package/src/icons/info.svg +1 -0
  111. package/src/icons/link.svg +1 -0
  112. package/src/icons/list.svg +1 -0
  113. package/src/icons/lock-open.svg +1 -0
  114. package/src/icons/lock.svg +1 -0
  115. package/src/icons/magnifying-glass.svg +1 -0
  116. package/src/icons/minus.svg +1 -0
  117. package/src/icons/moon.svg +1 -0
  118. package/src/icons/pencil-simple.svg +1 -0
  119. package/src/icons/play.svg +1 -0
  120. package/src/icons/plus.svg +1 -0
  121. package/src/icons/puzzle-piece.svg +1 -0
  122. package/src/icons/question.svg +1 -0
  123. package/src/icons/sliders-horizontal.svg +1 -0
  124. package/src/icons/sliders.svg +1 -0
  125. package/src/icons/squares-four.svg +1 -0
  126. package/src/icons/star.svg +1 -0
  127. package/src/icons/sun.svg +1 -0
  128. package/src/icons/trash.svg +1 -0
  129. package/src/icons/upload-simple.svg +1 -0
  130. package/src/icons/upload.svg +1 -0
  131. package/src/icons/user.svg +1 -0
  132. package/src/icons/users.svg +1 -0
  133. package/src/icons/warning-circle.svg +1 -0
  134. package/src/icons/warning.svg +1 -0
  135. package/src/icons/x-circle.svg +1 -0
  136. package/src/icons/x.svg +1 -0
@@ -0,0 +1,37 @@
1
+ /* ════════════════════════════════════════════════════════════════════
2
+ * Component — Field
3
+ * Wraps any control with a label, optional help, and an error message.
4
+ * Usage:
5
+ * <div class="juno-field">
6
+ * <label class="juno-field__label" for="cs">Order ID
7
+ * <span class="juno-field__req" aria-hidden="true">*</span>
8
+ * </label>
9
+ * <input id="cs" class="juno-input juno-mono" type="text" />
10
+ * <span class="juno-field__help">Prefix + number</span>
11
+ * </div>
12
+ * Error: add aria-invalid="true" to the control and swap help → __error.
13
+ * ════════════════════════════════════════════════════════════════════ */
14
+
15
+ .juno-field {
16
+ display: flex;
17
+ flex-direction: column;
18
+ gap: var(--juno-space-4);
19
+ }
20
+
21
+ .juno-field__label {
22
+ font-family: var(--juno-font-family-sans);
23
+ font-size: var(--juno-font-size-10);
24
+ letter-spacing: var(--juno-font-tracking-wider);
25
+ text-transform: uppercase;
26
+ color: var(--juno-label);
27
+ }
28
+
29
+ /* required marker — warning hue, paired with aria-required on the control */
30
+ .juno-field__req {
31
+ color: var(--juno-warning);
32
+ margin-inline-start: var(--juno-space-2);
33
+ }
34
+
35
+ .juno-field__help { font-size: var(--juno-font-size-11); color: var(--juno-label); }
36
+
37
+ .juno-field__error { font-size: var(--juno-font-size-11); color: var(--juno-warning); }
@@ -0,0 +1,78 @@
1
+ /* ════════════════════════════════════════════════════════════════════
2
+ * Component — Gauge (determinate metric ring)
3
+ * A fixed-value ring for readouts (CPU / RAM / DISK …) — the metric
4
+ * cousin of the arc loader. Set --juno-gauge-value (0–100) inline or
5
+ * from JS; color comes from --juno-role. junoui ships no threshold
6
+ * policy: the app maps value → role class (see gauge.md for the
7
+ * 75/90 recipe).
8
+ * Usage:
9
+ * <div class="juno-gauge juno--nominal" style="--juno-gauge-value: 67">
10
+ * <span class="juno-gauge__value">67</span>
11
+ * </div>
12
+ * <span class="juno-gauge__label">DISK</span>
13
+ * ════════════════════════════════════════════════════════════════════ */
14
+
15
+ /* registered so the sweep can transition when the value changes */
16
+ @property --juno-gauge-value {
17
+ syntax: '<number>';
18
+ inherits: true;
19
+ initial-value: 0;
20
+ }
21
+
22
+ .juno-gauge {
23
+ --juno-role: var(--juno-active);
24
+ --juno-gauge-size: 64px;
25
+ --juno-gauge-width: 4px;
26
+
27
+ position: relative;
28
+ display: inline-flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ inline-size: var(--juno-gauge-size);
32
+ block-size: var(--juno-gauge-size);
33
+ }
34
+
35
+ /* the ring lives on a pseudo-element: the punch-out mask would clip a
36
+ child, so the value stays a real child of the unmasked wrapper */
37
+ .juno-gauge::before {
38
+ content: '';
39
+ position: absolute;
40
+ inset: 0;
41
+ border-radius: 50%;
42
+ background: conic-gradient(
43
+ var(--juno-role) calc(var(--juno-gauge-value) * 1%),
44
+ var(--juno-border) 0
45
+ );
46
+ -webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - var(--juno-gauge-width)), #000 calc(100% - var(--juno-gauge-width)));
47
+ mask: radial-gradient(farthest-side, #0000 calc(100% - var(--juno-gauge-width)), #000 calc(100% - var(--juno-gauge-width)));
48
+ transition: --juno-gauge-value var(--juno-motion-duration-base) var(--juno-motion-ease-standard);
49
+ }
50
+
51
+ .juno-gauge__value {
52
+ font-family: var(--juno-font-family-mono);
53
+ font-size: var(--juno-font-size-13);
54
+ font-weight: var(--juno-font-weight-bold);
55
+ color: var(--juno-role);
56
+ }
57
+
58
+ /* caption under (or beside) the ring — a sibling, app-positioned */
59
+ .juno-gauge__label {
60
+ font-family: var(--juno-font-family-sans);
61
+ font-size: var(--juno-font-size-10);
62
+ font-weight: var(--juno-font-weight-bold);
63
+ letter-spacing: var(--juno-font-tracking-caps);
64
+ text-transform: uppercase;
65
+ color: var(--juno-label);
66
+ }
67
+
68
+ .juno-gauge--sm {
69
+ --juno-gauge-size: 44px;
70
+ --juno-gauge-width: 3px;
71
+ }
72
+ .juno-gauge--sm .juno-gauge__value { font-size: var(--juno-font-size-11); }
73
+
74
+ .juno-gauge--lg {
75
+ --juno-gauge-size: 96px;
76
+ --juno-gauge-width: 5px;
77
+ }
78
+ .juno-gauge--lg .juno-gauge__value { font-size: var(--juno-font-size-20); }
@@ -0,0 +1,35 @@
1
+ /* ════════════════════════════════════════════════════════════════════
2
+ * Component — Icon · inline SVG glyph
3
+ * A zero-JS icon primitive. Reference a symbol from the sprite; the glyph
4
+ * inherits the current text color (so a .juno--<role> ancestor tints it)
5
+ * and sizes in `em`, tracking font-size and the [data-juno-text] scale.
6
+ * Usage:
7
+ * <svg class="juno-icon" aria-hidden="true">
8
+ * <use href="path/to/juno-icons.svg#juno-i-gear" />
9
+ * </svg>
10
+ * <svg class="juno-icon juno-icon--lg juno--warning" role="img"
11
+ * aria-label="Warning">
12
+ * <use href="…#juno-i-warning" />
13
+ * </svg>
14
+ * Sizing: --juno-icon-size (default 1.25em). Color: currentColor — set
15
+ * color, or add .juno-icon--role to read var(--juno-role).
16
+ * ════════════════════════════════════════════════════════════════════ */
17
+
18
+ .juno-icon {
19
+ --juno-icon-size: 1.25em;
20
+
21
+ display: inline-block;
22
+ inline-size: var(--juno-icon-size);
23
+ block-size: var(--juno-icon-size);
24
+ flex-shrink: 0;
25
+ vertical-align: -0.2em; /* optical baseline alignment next to text */
26
+ fill: currentcolor;
27
+ }
28
+
29
+ /* tint by semantic role instead of the surrounding text color */
30
+ .juno-icon--role { color: var(--juno-role); }
31
+
32
+ /* sizes — relative, so they ride the type scale */
33
+ .juno-icon--sm { --juno-icon-size: 1em; }
34
+ .juno-icon--lg { --juno-icon-size: 1.75em; }
35
+ .juno-icon--xl { --juno-icon-size: 2.5em; }
@@ -0,0 +1,60 @@
1
+ /* ════════════════════════════════════════════════════════════════════
2
+ * Component — Text input / textarea
3
+ * Inputs are precise: mono by default (codes, IDs, tokens, values),
4
+ * ACTIVE-cyan focus ring always visible, semantic color reserved for
5
+ * validation. Add .juno-input--sans for free-text fields.
6
+ * Usage: <input class="juno-input" type="text" value="ORD-519" />
7
+ * <input class="juno-input juno-input--sans" placeholder="Type…" />
8
+ * <input class="juno-input juno--target" value="38000" /> (managed)
9
+ * <input class="juno-input" aria-invalid="true" /> (error)
10
+ * <textarea class="juno-input juno-input--sans"></textarea>
11
+ *
12
+ * --juno-role defaults to the control edge, so the resting border is quiet;
13
+ * a role class or aria-invalid re-colors it. Focus shows ACTIVE + a 1px ring.
14
+ * ════════════════════════════════════════════════════════════════════ */
15
+
16
+ .juno-input {
17
+ --juno-role: var(--juno-control-edge-strong);
18
+
19
+ display: block;
20
+ inline-size: 100%;
21
+ min-block-size: var(--juno-size-tap-min);
22
+ background: var(--juno-control-surface);
23
+ border: var(--juno-border-width-1) solid var(--juno-role);
24
+ border-radius: var(--juno-radius-4);
25
+ padding: var(--juno-pad-control-block) var(--juno-space-12);
26
+ font-family: var(--juno-font-family-mono);
27
+ font-size: var(--juno-font-size-14);
28
+ color: var(--juno-data);
29
+ }
30
+
31
+ .juno-input--sans { font-family: var(--juno-font-family-sans); }
32
+
33
+ .juno-input::placeholder { color: var(--juno-muted); }
34
+
35
+ /* focus — ACTIVE border + 1px ring (replaces the base outline on inputs) */
36
+ .juno-input:focus-visible {
37
+ outline: none;
38
+ border-color: var(--juno-active);
39
+ box-shadow: 0 0 0 var(--juno-border-width-1) var(--juno-active);
40
+ }
41
+
42
+ /* invalid: warning border + text, tied to the a11y attribute */
43
+ .juno-input[aria-invalid='true'] {
44
+ --juno-role: var(--juno-warning);
45
+
46
+ color: var(--juno-warning);
47
+ }
48
+
49
+ .juno-input:disabled {
50
+ background: var(--juno-s1);
51
+ border-color: var(--juno-border);
52
+ color: var(--juno-muted);
53
+ cursor: not-allowed;
54
+ }
55
+
56
+ textarea.juno-input {
57
+ min-block-size: calc(var(--juno-size-tap-min) * 3);
58
+ resize: vertical;
59
+ line-height: var(--juno-font-lineHeight-loose);
60
+ }
@@ -0,0 +1,135 @@
1
+ /* ════════════════════════════════════════════════════════════════════
2
+ * Component — List (grouped rows / vertical data organization)
3
+ * The settings-screen pattern: labeled groups of full-width rows, each
4
+ * row a leading icon + label (+ support line) with a trailing value,
5
+ * control, or drill-in chevron. Rows are plain <div>s for static data
6
+ * or <a>/<button> when they navigate — the look is the same; only
7
+ * interactive rows get hover/press feedback. Zero JS.
8
+ * Usage:
9
+ * <section class="juno-list">
10
+ * <h3 class="juno-list__header">Playback</h3>
11
+ * <ul class="juno-list__group">
12
+ * <li><a class="juno-list__row" href="…">
13
+ * <svg class="juno-icon juno-list__icon" aria-hidden="true"><use href="…#juno-i-sliders" /></svg>
14
+ * <span class="juno-list__main">
15
+ * <span class="juno-list__label">Quality</span>
16
+ * <span class="juno-list__support">Streaming resolution</span>
17
+ * </span>
18
+ * <span class="juno-list__value">1080p</span>
19
+ * <svg class="juno-icon juno-list__chevron" aria-hidden="true"><use href="…#juno-i-caret-right" /></svg>
20
+ * </a></li>
21
+ * </ul>
22
+ * </section>
23
+ * ════════════════════════════════════════════════════════════════════ */
24
+
25
+ .juno-list {
26
+ display: flex;
27
+ flex-direction: column;
28
+ gap: var(--juno-space-8);
29
+ }
30
+
31
+ .juno-list + .juno-list { margin-block-start: var(--juno-space-20); }
32
+
33
+ .juno-list__header {
34
+ margin: 0;
35
+ padding-inline: var(--juno-space-16);
36
+ font-family: var(--juno-font-family-sans);
37
+ font-size: var(--juno-font-size-11);
38
+ font-weight: var(--juno-font-weight-bold);
39
+ letter-spacing: var(--juno-font-tracking-label);
40
+ text-transform: uppercase;
41
+ color: var(--juno-label);
42
+ }
43
+
44
+ .juno-list__group {
45
+ margin: 0;
46
+ padding: 0;
47
+ list-style: none;
48
+ background: var(--juno-s1);
49
+ border: var(--juno-border-width-1) solid var(--juno-border);
50
+ border-radius: var(--juno-radius-8);
51
+ overflow: hidden;
52
+ }
53
+
54
+ .juno-list__row {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: var(--juno-space-12);
58
+ inline-size: 100%;
59
+ min-block-size: var(--juno-size-tap-comfortable);
60
+ padding: var(--juno-space-8) var(--juno-space-16);
61
+ background: transparent;
62
+ border: none;
63
+ font-family: var(--juno-font-family-sans);
64
+ font-size: var(--juno-font-size-13);
65
+ text-align: start;
66
+ text-decoration: none;
67
+ color: var(--juno-data);
68
+ }
69
+
70
+ /* hairline between rows only — works for <li>-wrapped and bare rows */
71
+ .juno-list__group > li + li > .juno-list__row,
72
+ .juno-list__group > .juno-list__row + .juno-list__row {
73
+ border-block-start: var(--juno-border-width-1) solid var(--juno-border);
74
+ }
75
+
76
+ /* feedback only where a row actually goes somewhere */
77
+ a.juno-list__row,
78
+ button.juno-list__row { cursor: pointer; }
79
+
80
+ a.juno-list__row:hover,
81
+ button.juno-list__row:hover,
82
+ a.juno-list__row:focus-visible,
83
+ button.juno-list__row:focus-visible { background: var(--juno-s2); }
84
+
85
+ .juno-list__row:focus-visible {
86
+ outline: var(--juno-border-width-2) solid var(--juno-active);
87
+ outline-offset: calc(-1 * var(--juno-space-2));
88
+ }
89
+
90
+ button.juno-list__row:disabled {
91
+ opacity: var(--juno-opacity-disabled);
92
+ cursor: not-allowed;
93
+ }
94
+
95
+ .juno-list__icon {
96
+ flex-shrink: 0;
97
+ color: var(--juno-label);
98
+ }
99
+
100
+ .juno-list__main {
101
+ flex: 1 1 auto;
102
+ min-inline-size: 0;
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: var(--juno-space-2);
106
+ }
107
+
108
+ .juno-list__label {
109
+ overflow: hidden;
110
+ text-overflow: ellipsis;
111
+ white-space: nowrap;
112
+ }
113
+
114
+ .juno-list__support {
115
+ font-size: var(--juno-font-size-11);
116
+ color: var(--juno-label);
117
+ overflow: hidden;
118
+ text-overflow: ellipsis;
119
+ white-space: nowrap;
120
+ }
121
+
122
+ .juno-list__value {
123
+ flex-shrink: 0;
124
+ font-family: var(--juno-font-family-mono);
125
+ font-size: var(--juno-font-size-12);
126
+ color: var(--juno-label);
127
+ }
128
+
129
+ .juno-list__chevron {
130
+ flex-shrink: 0;
131
+
132
+ --juno-icon-size: var(--juno-space-16);
133
+
134
+ color: var(--juno-muted);
135
+ }
@@ -0,0 +1,159 @@
1
+ /* ════════════════════════════════════════════════════════════════════
2
+ * Component — Loaders (Arc · Beacon · Linear bar)
3
+ *
4
+ * Indeterminate variants loop on their own (CSS only). Determinate
5
+ * variants track real progress: set --juno-progress (0–100) on the
6
+ * element, inline or from JS:
7
+ * el.style.setProperty('--juno-progress', 63);
8
+ * Color comes from --juno-role (default = active).
9
+ * ════════════════════════════════════════════════════════════════════ */
10
+
11
+ @keyframes juno-arc-rot { to { transform: rotate(360deg); } }
12
+
13
+ @keyframes juno-beacon-pulse { 0% { transform: scale(0.28); opacity: 0.85; } 100% { transform: scale(1); opacity: 0; } }
14
+
15
+ @keyframes juno-bar-slide { 0% { left: -42%; } 100% { left: 100%; } }
16
+
17
+ /* ── ARC — circular ring ────────────────────────────────────────────── */
18
+ .juno-arc {
19
+ --juno-role: var(--juno-active);
20
+ --juno-arc-size: 76px;
21
+ --juno-arc-width: 4px;
22
+
23
+ position: relative;
24
+ width: var(--juno-arc-size);
25
+ height: var(--juno-arc-size);
26
+ border-radius: 50%;
27
+
28
+ /* determinate sweep: filled portion = progress */
29
+ background: conic-gradient(
30
+ var(--juno-role) calc(var(--juno-progress, 0) * 1%),
31
+ var(--juno-s3) 0
32
+ );
33
+
34
+ /* punch out the centre to leave a ring */
35
+ -webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - var(--juno-arc-width)), #000 calc(100% - var(--juno-arc-width)));
36
+ mask: radial-gradient(farthest-side, #0000 calc(100% - var(--juno-arc-width)), #000 calc(100% - var(--juno-arc-width)));
37
+ }
38
+
39
+ .juno-arc--indeterminate {
40
+ background: conic-gradient(var(--juno-role) 0 70%, #0000 0);
41
+ animation: juno-arc-rot 1.1s steps(12) infinite; /* mechanical, 12-step sweep */
42
+ }
43
+
44
+ /* centred readout for determinate arcs — place a span inside */
45
+ .juno-arc__label {
46
+ position: absolute;
47
+ inset: 0;
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ font-family: var(--juno-font-family-mono);
52
+ font-weight: var(--juno-font-weight-bold);
53
+ font-size: var(--juno-font-size-13);
54
+ color: var(--juno-data);
55
+
56
+ /* the mask clips the ring, not this label: keep it on a sibling when
57
+ a value is shown — see loader.md */
58
+ }
59
+
60
+ /* ── BEACON — radiating pulse ───────────────────────────────────────── */
61
+ .juno-beacon {
62
+ --juno-role: var(--juno-active);
63
+ --juno-beacon-size: 76px;
64
+
65
+ position: relative;
66
+ width: var(--juno-beacon-size);
67
+ height: var(--juno-beacon-size);
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ }
72
+
73
+ .juno-beacon__core {
74
+ width: 24%;
75
+ height: 24%;
76
+ border-radius: 50%;
77
+ background: var(--juno-role);
78
+ }
79
+
80
+ /* two pulse rings (staggered) */
81
+ .juno-beacon::before,
82
+ .juno-beacon::after {
83
+ content: '';
84
+ position: absolute;
85
+ inset: 0;
86
+ border-radius: 50%;
87
+ border: 1.5px solid var(--juno-role);
88
+ animation: juno-beacon-pulse 1.9s ease-out infinite;
89
+ }
90
+ .juno-beacon::after { animation-delay: 0.95s; }
91
+
92
+ /* determinate: fill grows centre → rim by progress, keeps pulsing + readout */
93
+ .juno-beacon--determinate { --juno-beacon-fill-stop: calc(44% + 0.56% * var(--juno-progress, 0)); }
94
+ .juno-beacon--determinate .juno-beacon__core { display: none; }
95
+
96
+ .juno-beacon__track {
97
+ position: absolute;
98
+ inset: 0;
99
+ border-radius: 50%;
100
+ border: 1px solid var(--juno-border);
101
+ }
102
+
103
+ .juno-beacon__fill {
104
+ position: absolute;
105
+ inset: 0;
106
+ border-radius: 50%;
107
+ background: var(--juno-role);
108
+ -webkit-mask: radial-gradient(circle closest-side, #0000 0 44%, #000 44% var(--juno-beacon-fill-stop), #0000 var(--juno-beacon-fill-stop));
109
+ mask: radial-gradient(circle closest-side, #0000 0 44%, #000 44% var(--juno-beacon-fill-stop), #0000 var(--juno-beacon-fill-stop));
110
+ }
111
+
112
+ .juno-beacon__hub {
113
+ position: absolute;
114
+ left: 50%; top: 50%;
115
+ transform: translate(-50%, -50%);
116
+ width: 42%; height: 42%;
117
+ border-radius: 50%;
118
+ background: var(--juno-s1);
119
+ border: 1px solid var(--juno-border);
120
+ }
121
+
122
+ .juno-beacon__label {
123
+ position: relative;
124
+ font-family: var(--juno-font-family-mono);
125
+ font-weight: var(--juno-font-weight-bold);
126
+ font-size: var(--juno-font-size-13);
127
+ color: var(--juno-data);
128
+ }
129
+
130
+ /* ── LINEAR BAR ─────────────────────────────────────────────────────── */
131
+ .juno-bar {
132
+ --juno-role: var(--juno-active);
133
+
134
+ position: relative;
135
+ width: 100%;
136
+ height: 6px;
137
+ border-radius: var(--juno-radius-3);
138
+ background: var(--juno-s2);
139
+ overflow: hidden;
140
+ }
141
+
142
+ .juno-bar__fill {
143
+ position: absolute;
144
+ top: 0;
145
+ left: 0;
146
+ height: 100%;
147
+ border-radius: var(--juno-radius-3);
148
+ background: var(--juno-role);
149
+ width: calc(var(--juno-progress, 0) * 1%);
150
+ transition: width var(--juno-motion-duration-quick) linear;
151
+ }
152
+
153
+ /* indeterminate: a 40% segment slides across */
154
+ .juno-bar--indeterminate .juno-bar__fill {
155
+ width: 40%;
156
+ left: -42%;
157
+ transition: none;
158
+ animation: juno-bar-slide 1.3s ease-in-out infinite;
159
+ }
@@ -0,0 +1,114 @@
1
+ /* ════════════════════════════════════════════════════════════════════
2
+ * Component — Menu / Dropdown
3
+ * A list of immediate actions anchored to its trigger. Section dividers,
4
+ * keyboard shortcuts, and a destructive item set apart by WARNING. junoui
5
+ * ships the look; the app owns roving focus. Open/close, light-dismiss, and
6
+ * ESC come from the native Popover API — the menu lives in the TOP LAYER, so
7
+ * it is never clipped by an ancestor's overflow and never fights z-index.
8
+ * Usage:
9
+ * <button popovertarget="acts" aria-haspopup="menu" aria-expanded="false">ACTIONS ▾</button>
10
+ * <ul class="juno-menu" id="acts" popover role="menu">
11
+ * <li><button class="juno-menu__item" role="menuitem"
12
+ * popovertarget="acts" popovertargetaction="hide">
13
+ * <span class="juno-menu__icon">⤓</span>Import data
14
+ * <span class="juno-menu__kbd">⌘O</span>
15
+ * </button></li>
16
+ * <li class="juno-menu__sep" role="separator"></li>
17
+ * <li><button class="juno-menu__item juno--warning" role="menuitem"
18
+ * popovertarget="acts" popovertargetaction="hide">
19
+ * <span class="juno-menu__icon">⨯</span>Delete project
20
+ * </button></li>
21
+ * </ul>
22
+ * (Each item carries popovertargetaction="hide" so activating it closes the
23
+ * menu with no JS. The invoker is the menu's implicit anchor.)
24
+ * ════════════════════════════════════════════════════════════════════ */
25
+
26
+ .juno-menu {
27
+ position: fixed;
28
+ margin: 0;
29
+ inset: auto;
30
+ position-area: block-end span-inline-start;
31
+ position-try-fallbacks: flip-block, flip-inline;
32
+ margin-block-start: var(--juno-space-8);
33
+ min-inline-size: 256px;
34
+ background: var(--juno-s2);
35
+ border: var(--juno-border-width-1) solid var(--juno-border);
36
+ border-radius: var(--juno-radius-5);
37
+ box-shadow: var(--juno-shadow-2);
38
+ padding: var(--juno-space-4);
39
+ list-style: none;
40
+ opacity: 0;
41
+ transform: translateY(calc(-1 * var(--juno-space-4)));
42
+ transition:
43
+ opacity var(--juno-motion-duration-quick) var(--juno-motion-ease-decel),
44
+ transform var(--juno-motion-duration-quick) var(--juno-motion-ease-decel),
45
+ overlay var(--juno-motion-duration-quick) allow-discrete,
46
+ display var(--juno-motion-duration-quick) allow-discrete;
47
+ }
48
+
49
+ .juno-menu:popover-open {
50
+ opacity: 1;
51
+ transform: none;
52
+ }
53
+
54
+ @starting-style {
55
+ .juno-menu:popover-open {
56
+ opacity: 0;
57
+ transform: translateY(calc(-1 * var(--juno-space-4)));
58
+ }
59
+ }
60
+
61
+ .juno-menu__item {
62
+ --juno-role: var(--juno-data);
63
+
64
+ display: flex;
65
+ align-items: center;
66
+ gap: var(--juno-gap-control);
67
+ inline-size: 100%;
68
+ padding: var(--juno-space-8) var(--juno-space-10);
69
+ background: transparent;
70
+ border: none;
71
+ border-radius: var(--juno-radius-4);
72
+ font-family: var(--juno-font-family-sans);
73
+ font-size: var(--juno-font-size-13);
74
+ color: var(--juno-role);
75
+ text-align: start;
76
+ text-decoration: none; /* items may be <a> (nav menus), not only <button> */
77
+ cursor: pointer;
78
+ }
79
+
80
+ .juno-menu__item:hover,
81
+ .juno-menu__item:focus-visible { background: var(--juno-s3); }
82
+
83
+ /* destructive item — .juno--warning sets --juno-role to warning */
84
+ .juno-menu__item.juno--warning:hover,
85
+ .juno-menu__item.juno--warning:focus-visible {
86
+ background: color-mix(in srgb, var(--juno-role) 14%, transparent);
87
+ }
88
+
89
+ .juno-menu__item:disabled,
90
+ .juno-menu__item[aria-disabled='true'] { opacity: var(--juno-opacity-disabled); cursor: not-allowed; }
91
+
92
+ .juno-menu__icon {
93
+ flex-shrink: 0;
94
+ inline-size: var(--juno-space-16);
95
+ font-family: var(--juno-font-family-mono);
96
+ font-size: var(--juno-font-size-11);
97
+ color: var(--juno-label);
98
+ }
99
+
100
+ .juno-menu__item.juno--warning .juno-menu__icon { color: var(--juno-role); }
101
+
102
+ .juno-menu__kbd {
103
+ margin-inline-start: auto;
104
+ font-family: var(--juno-font-family-mono);
105
+ font-size: var(--juno-font-size-10);
106
+ color: var(--juno-muted);
107
+ }
108
+
109
+ .juno-menu__sep {
110
+ block-size: var(--juno-border-width-1);
111
+ background: var(--juno-border);
112
+ margin-block: var(--juno-space-4);
113
+ list-style: none;
114
+ }