@apliteni/apliteni-ui 0.3.0 → 0.5.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.
- package/package.json +10 -3
- package/src/assets/brand.generated/apliteni-logo-dark.svg +27 -0
- package/src/assets/brand.generated/apliteni-logo.svg +27 -0
- package/src/assets/brand.generated/apliteni-mark.svg +19 -0
- package/src/assets/brand.generated/index.js +5 -0
- package/src/assets/icons.js +106 -24
- package/src/assets/illustrations.js +68 -0
- package/src/components/drawer.js +200 -0
- package/src/components/dropdown.js +229 -0
- package/src/components/feedback.js +6 -0
- package/src/components/footer.js +95 -0
- package/src/components/index.js +115 -10
- package/src/components/nav.js +190 -0
- package/src/components/success.js +126 -0
- package/src/components/toasts.js +94 -0
- package/src/components/topbar.js +17 -15
- package/src/index.css +9 -0
- package/src/index.js +5 -0
- package/src/inline.js +21 -3
- package/src/motion.js +68 -0
- package/src/styles/aurora.css +93 -0
- package/src/styles/button.css +40 -11
- package/src/styles/callout.css +79 -12
- package/src/styles/drawer.css +166 -0
- package/src/styles/dropdown.css +133 -0
- package/src/styles/empty.css +84 -0
- package/src/styles/feedback.css +1 -1
- package/src/styles/footer.css +123 -0
- package/src/styles/motion.css +166 -0
- package/src/styles/nav.css +218 -0
- package/src/styles/success.css +189 -0
- package/src/styles/table.css +2 -15
- package/src/styles/topbar.css +9 -3
- package/src/tokens/brand.generated.css +121 -0
- package/src/tokens/tokens.css +18 -5
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Empty state — the placeholder shown when a list, table or page has no rows.
|
|
3
|
+
* A duotone spot illustration (in the active accent, with a brand-green spark),
|
|
4
|
+
* a title, an optional line of guidance, and an optional action.
|
|
5
|
+
*
|
|
6
|
+
* Markup:
|
|
7
|
+
* <div class="ui-empty">
|
|
8
|
+
* <div class="ui-empty__art"><svg class="ui-illo">…</svg></div>
|
|
9
|
+
* <div class="ui-empty__title">No people yet</div>
|
|
10
|
+
* <div class="ui-empty__sub">Contractors and staff you add show up here.</div>
|
|
11
|
+
* <div class="ui-empty__actions"><button class="ui-btn ui-btn--primary">…</button></div>
|
|
12
|
+
* </div>
|
|
13
|
+
*
|
|
14
|
+
* Illustrations live in assets/illustrations.js; emptyState() in components
|
|
15
|
+
* assembles the block. The legacy line-icon slot (.ui-empty__icon) still works.
|
|
16
|
+
* ========================================================================== */
|
|
17
|
+
|
|
18
|
+
.ui-empty {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
align-items: center;
|
|
22
|
+
text-align: center;
|
|
23
|
+
gap: 8px;
|
|
24
|
+
padding: 48px 24px;
|
|
25
|
+
color: var(--muted);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Duotone illustration slot. */
|
|
29
|
+
.ui-empty__art {
|
|
30
|
+
margin-bottom: 4px;
|
|
31
|
+
}
|
|
32
|
+
.ui-illo {
|
|
33
|
+
display: block;
|
|
34
|
+
}
|
|
35
|
+
.ui-illo__stroke {
|
|
36
|
+
stroke: var(--accent);
|
|
37
|
+
stroke-width: 2.4;
|
|
38
|
+
fill: none;
|
|
39
|
+
stroke-linecap: round;
|
|
40
|
+
stroke-linejoin: round;
|
|
41
|
+
}
|
|
42
|
+
.ui-illo__fill {
|
|
43
|
+
fill: color-mix(in srgb, var(--accent) 13%, transparent);
|
|
44
|
+
stroke: none;
|
|
45
|
+
}
|
|
46
|
+
.ui-illo__spark {
|
|
47
|
+
fill: var(--illo-spark, #12a150);
|
|
48
|
+
stroke: none;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ui-empty__title {
|
|
52
|
+
color: var(--text);
|
|
53
|
+
font-size: var(--text-md);
|
|
54
|
+
font-weight: var(--weight-medium);
|
|
55
|
+
margin: 2px 0 0;
|
|
56
|
+
}
|
|
57
|
+
.ui-empty__sub {
|
|
58
|
+
font-size: var(--text-sm);
|
|
59
|
+
max-width: 340px;
|
|
60
|
+
margin: 0;
|
|
61
|
+
line-height: 1.55;
|
|
62
|
+
}
|
|
63
|
+
.ui-empty__actions {
|
|
64
|
+
display: flex;
|
|
65
|
+
gap: 10px;
|
|
66
|
+
justify-content: center;
|
|
67
|
+
margin-top: 12px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Legacy line-icon slot — kept so existing .ui-empty__icon markup still works. */
|
|
71
|
+
.ui-empty__icon {
|
|
72
|
+
width: 44px;
|
|
73
|
+
height: 44px;
|
|
74
|
+
margin: 0 auto 14px;
|
|
75
|
+
color: var(--muted);
|
|
76
|
+
opacity: 0.6;
|
|
77
|
+
}
|
|
78
|
+
.ui-empty__icon svg {
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 100%;
|
|
81
|
+
stroke: currentColor;
|
|
82
|
+
fill: none;
|
|
83
|
+
stroke-width: 1.5;
|
|
84
|
+
}
|
package/src/styles/feedback.css
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
font-family: var(--font-sans); font-size: 13px; font-weight: var(--weight-semibold); letter-spacing: .01em;
|
|
26
26
|
padding: 9px 17px 9px 15px; border-radius: 999px; cursor: pointer; white-space: nowrap; overflow: hidden;
|
|
27
27
|
box-shadow: 0 10px 26px var(--ui-fb-pill-glow), 0 2px 6px rgba(0,0,0,.28), inset 0 1px 0 rgba(255,255,255,.28);
|
|
28
|
-
transition: transform .2s cubic-bezier(.34,1.56,.64,1), opacity .16s ease, box-shadow .2s ease;
|
|
28
|
+
transition: transform .2s var(--easing-spring, cubic-bezier(.34,1.56,.64,1)), opacity .16s ease, box-shadow .2s ease;
|
|
29
29
|
}
|
|
30
30
|
.ui-fbpill.show { opacity: 1; pointer-events: auto; transform: translate(-50%, -100%) translateY(-9px) scale(1); }
|
|
31
31
|
.ui-fbpill:hover { box-shadow: 0 14px 34px var(--ui-fb-pill-glow), 0 3px 9px rgba(0,0,0,.3), inset 0 1px 0 rgba(255,255,255,.34); }
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Footer — the canonical site/app footer (brand lockup, grouped link columns,
|
|
3
|
+
* legal/copyright row, optional social icons + theme/accent switcher slot).
|
|
4
|
+
* Three variants: full (marketing), slim (single legal row), app (in-product).
|
|
5
|
+
* Local --footer-* tokens map to the global scale, so it re-themes across every
|
|
6
|
+
* accent + light/dark and a host can override just the footer if it needs to.
|
|
7
|
+
* ========================================================================== */
|
|
8
|
+
|
|
9
|
+
.ui-footer {
|
|
10
|
+
--footer-surface: transparent;
|
|
11
|
+
--footer-border: var(--border);
|
|
12
|
+
--footer-link: var(--muted);
|
|
13
|
+
--footer-link-hover: var(--strong);
|
|
14
|
+
--footer-title: var(--strong);
|
|
15
|
+
--footer-text: var(--dim);
|
|
16
|
+
|
|
17
|
+
background: var(--footer-surface);
|
|
18
|
+
border-top: 1px solid var(--footer-border);
|
|
19
|
+
color: var(--footer-text);
|
|
20
|
+
font-family: var(--font-sans);
|
|
21
|
+
}
|
|
22
|
+
.ui-footer__in {
|
|
23
|
+
width: 100%;
|
|
24
|
+
max-width: 1180px;
|
|
25
|
+
margin: 0 auto;
|
|
26
|
+
padding: 40px clamp(14px, 2.4vw, 26px) 30px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Top block: brand + tagline + social on the left, link columns on the right. */
|
|
30
|
+
.ui-footer__top {
|
|
31
|
+
display: flex;
|
|
32
|
+
justify-content: space-between;
|
|
33
|
+
gap: 40px 60px;
|
|
34
|
+
flex-wrap: wrap;
|
|
35
|
+
padding-bottom: 30px;
|
|
36
|
+
}
|
|
37
|
+
.ui-footer__brand { max-width: 300px; }
|
|
38
|
+
.ui-footer .ui-footer__brand .brand { font-size: 15px; }
|
|
39
|
+
.ui-footer__tagline {
|
|
40
|
+
margin: 12px 0 0;
|
|
41
|
+
font-size: 13px;
|
|
42
|
+
line-height: 1.6;
|
|
43
|
+
color: var(--footer-text);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Social row */
|
|
47
|
+
.ui-footer__socials { display: flex; gap: 10px; margin-top: 16px; }
|
|
48
|
+
.ui-footer__social {
|
|
49
|
+
width: 34px; height: 34px;
|
|
50
|
+
display: grid; place-items: center;
|
|
51
|
+
border-radius: 10px;
|
|
52
|
+
border: 1px solid var(--footer-border);
|
|
53
|
+
color: var(--footer-link);
|
|
54
|
+
background: var(--surface);
|
|
55
|
+
transition: color 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
|
|
56
|
+
}
|
|
57
|
+
.ui-footer__social:hover { color: var(--accent); border-color: var(--accent); transform: translateY(-1px); }
|
|
58
|
+
.ui-footer__social svg { width: 17px; height: 17px; display: block; }
|
|
59
|
+
|
|
60
|
+
/* Link columns */
|
|
61
|
+
.ui-footer__nav {
|
|
62
|
+
display: grid;
|
|
63
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, max-content));
|
|
64
|
+
gap: 30px 56px;
|
|
65
|
+
}
|
|
66
|
+
.ui-footer__col-title {
|
|
67
|
+
font-size: 11px;
|
|
68
|
+
font-weight: 700;
|
|
69
|
+
letter-spacing: 0.1em;
|
|
70
|
+
text-transform: uppercase;
|
|
71
|
+
color: var(--footer-text);
|
|
72
|
+
margin: 0 0 14px;
|
|
73
|
+
}
|
|
74
|
+
.ui-footer__links { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 11px; }
|
|
75
|
+
.ui-footer__links a {
|
|
76
|
+
font-size: 13.5px;
|
|
77
|
+
color: var(--footer-link);
|
|
78
|
+
text-decoration: none;
|
|
79
|
+
transition: color 0.15s ease;
|
|
80
|
+
}
|
|
81
|
+
.ui-footer__links a:hover { color: var(--footer-link-hover); }
|
|
82
|
+
|
|
83
|
+
/* Legal bar — shared by every variant */
|
|
84
|
+
.ui-footer__bar {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
justify-content: space-between;
|
|
88
|
+
gap: 16px 22px;
|
|
89
|
+
flex-wrap: wrap;
|
|
90
|
+
padding-top: 22px;
|
|
91
|
+
border-top: 1px solid var(--footer-border);
|
|
92
|
+
font-size: 13px;
|
|
93
|
+
color: var(--footer-link);
|
|
94
|
+
}
|
|
95
|
+
.ui-footer__legal { color: var(--footer-link); }
|
|
96
|
+
.ui-footer__legal-links { display: flex; gap: 22px; flex-wrap: wrap; }
|
|
97
|
+
.ui-footer__legal-links a { color: var(--footer-link); text-decoration: none; transition: color 0.15s ease; }
|
|
98
|
+
.ui-footer__legal-links a:hover { color: var(--footer-link-hover); }
|
|
99
|
+
.ui-footer__switcher { display: inline-flex; align-items: center; gap: 12px; margin-left: auto; }
|
|
100
|
+
|
|
101
|
+
/* slim — a single legal row, no divider above it. */
|
|
102
|
+
.ui-footer--slim .ui-footer__in { padding-top: 24px; padding-bottom: 24px; }
|
|
103
|
+
.ui-footer--slim .ui-footer__bar { padding-top: 0; border-top: 0; }
|
|
104
|
+
|
|
105
|
+
/* app — compact, in-product: surface background + tighter padding. */
|
|
106
|
+
.ui-footer--app {
|
|
107
|
+
--footer-surface: var(--surface);
|
|
108
|
+
border-top: 1px solid var(--footer-border);
|
|
109
|
+
}
|
|
110
|
+
.ui-footer--app .ui-footer__in { max-width: none; padding: 12px clamp(14px, 2.4vw, 22px); }
|
|
111
|
+
.ui-footer--app .ui-footer__bar { padding-top: 0; border-top: 0; font-size: 12.5px; }
|
|
112
|
+
|
|
113
|
+
/* Responsive — columns collapse to a stacked layout on narrow screens. */
|
|
114
|
+
@media (max-width: 720px) {
|
|
115
|
+
.ui-footer__top { flex-direction: column; gap: 30px; }
|
|
116
|
+
.ui-footer__nav { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 26px 30px; }
|
|
117
|
+
.ui-footer__brand { max-width: none; }
|
|
118
|
+
}
|
|
119
|
+
@media (max-width: 460px) {
|
|
120
|
+
.ui-footer__nav { grid-template-columns: 1fr; }
|
|
121
|
+
.ui-footer__bar { flex-direction: column; align-items: flex-start; }
|
|
122
|
+
.ui-footer__switcher { margin-left: 0; }
|
|
123
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* apliteni-ui — MOTION LIBRARY
|
|
3
|
+
*
|
|
4
|
+
* A small, token-driven, framework-agnostic motion system: reusable entrance,
|
|
5
|
+
* micro-interaction, attention, and scroll-reveal effects as plain classes.
|
|
6
|
+
* Every timing reads the brand motion vocabulary (--duration-* / --easing-*,
|
|
7
|
+
* synced from design-system) so motion re-themes and stays consistent.
|
|
8
|
+
*
|
|
9
|
+
* Per-effect overrides via custom properties on the element:
|
|
10
|
+
* --m-dur duration (default: per effect)
|
|
11
|
+
* --m-ease timing function (default: per effect)
|
|
12
|
+
* --m-delay start delay (default: 0s)
|
|
13
|
+
* --m-dist travel distance for slides (default: 16px)
|
|
14
|
+
*
|
|
15
|
+
* Scroll reveals need one line of JS — the optional `initReveal()` hook from
|
|
16
|
+
* apliteni-ui/motion. Without JS, [data-reveal] content stays fully visible
|
|
17
|
+
* (progressive enhancement, never hidden behind a script that didn't run).
|
|
18
|
+
*
|
|
19
|
+
* A single global prefers-reduced-motion block at the end neutralises ALL of
|
|
20
|
+
* the above — plus any component animation in the kit — as the safety net.
|
|
21
|
+
* ========================================================================== */
|
|
22
|
+
|
|
23
|
+
/* -- Entrances -------------------------------------------------------------
|
|
24
|
+
* `both` fill so the element starts at the "from" frame (hidden) and holds
|
|
25
|
+
* the "to" frame. Compose with a delay for staggered groups. */
|
|
26
|
+
.m-fade-in,
|
|
27
|
+
.m-slide-up,
|
|
28
|
+
.m-slide-down,
|
|
29
|
+
.m-slide-left,
|
|
30
|
+
.m-slide-right,
|
|
31
|
+
.m-scale-in,
|
|
32
|
+
.m-blur-in {
|
|
33
|
+
animation-duration: var(--m-dur, var(--dur-slow));
|
|
34
|
+
animation-timing-function: var(--m-ease, var(--easing-ease-out, cubic-bezier(0, 0, 0.2, 1)));
|
|
35
|
+
animation-delay: var(--m-delay, 0s);
|
|
36
|
+
animation-fill-mode: both;
|
|
37
|
+
}
|
|
38
|
+
.m-fade-in { animation-name: m-fade; }
|
|
39
|
+
.m-slide-up { animation-name: m-slide-up; }
|
|
40
|
+
.m-slide-down { animation-name: m-slide-down; }
|
|
41
|
+
.m-slide-left { animation-name: m-slide-left; }
|
|
42
|
+
.m-slide-right{ animation-name: m-slide-right; }
|
|
43
|
+
.m-scale-in { animation-name: m-scale-in; }
|
|
44
|
+
.m-blur-in { animation-name: m-blur-in; }
|
|
45
|
+
|
|
46
|
+
@keyframes m-fade { from { opacity: 0; } to { opacity: 1; } }
|
|
47
|
+
@keyframes m-slide-up {
|
|
48
|
+
from { opacity: 0; transform: translateY(var(--m-dist, 16px)); }
|
|
49
|
+
to { opacity: 1; transform: none; }
|
|
50
|
+
}
|
|
51
|
+
@keyframes m-slide-down {
|
|
52
|
+
from { opacity: 0; transform: translateY(calc(-1 * var(--m-dist, 16px))); }
|
|
53
|
+
to { opacity: 1; transform: none; }
|
|
54
|
+
}
|
|
55
|
+
@keyframes m-slide-left {
|
|
56
|
+
from { opacity: 0; transform: translateX(var(--m-dist, 16px)); }
|
|
57
|
+
to { opacity: 1; transform: none; }
|
|
58
|
+
}
|
|
59
|
+
@keyframes m-slide-right {
|
|
60
|
+
from { opacity: 0; transform: translateX(calc(-1 * var(--m-dist, 16px))); }
|
|
61
|
+
to { opacity: 1; transform: none; }
|
|
62
|
+
}
|
|
63
|
+
@keyframes m-scale-in {
|
|
64
|
+
from { opacity: 0; transform: scale(0.94); }
|
|
65
|
+
to { opacity: 1; transform: none; }
|
|
66
|
+
}
|
|
67
|
+
@keyframes m-blur-in {
|
|
68
|
+
from { opacity: 0; filter: blur(8px); }
|
|
69
|
+
to { opacity: 1; filter: blur(0); }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* -- Micro-interactions ---------------------------------------------------- */
|
|
73
|
+
.m-lift {
|
|
74
|
+
transition: transform var(--dur-fast) var(--ease), box-shadow var(--dur-med) var(--ease);
|
|
75
|
+
}
|
|
76
|
+
.m-lift:hover {
|
|
77
|
+
transform: translateY(-3px);
|
|
78
|
+
box-shadow: var(--shadow-md, 0 8px 24px rgba(0, 0, 0, 0.18));
|
|
79
|
+
}
|
|
80
|
+
.m-press {
|
|
81
|
+
transition: transform var(--duration-instant, 80ms) var(--easing-sharp, cubic-bezier(0.4, 0, 0.6, 1));
|
|
82
|
+
}
|
|
83
|
+
.m-press:active { transform: scale(0.97); }
|
|
84
|
+
|
|
85
|
+
/* Shimmer / skeleton — a loading placeholder that reads the accent-neutral. */
|
|
86
|
+
.m-skeleton {
|
|
87
|
+
position: relative;
|
|
88
|
+
overflow: hidden;
|
|
89
|
+
background: color-mix(in srgb, var(--text) 8%, transparent);
|
|
90
|
+
border-radius: var(--radius-sm, 8px);
|
|
91
|
+
}
|
|
92
|
+
.m-skeleton::after {
|
|
93
|
+
content: "";
|
|
94
|
+
position: absolute;
|
|
95
|
+
inset: 0;
|
|
96
|
+
transform: translateX(-100%);
|
|
97
|
+
background: linear-gradient(
|
|
98
|
+
90deg,
|
|
99
|
+
transparent,
|
|
100
|
+
color-mix(in srgb, var(--text) 12%, transparent),
|
|
101
|
+
transparent
|
|
102
|
+
);
|
|
103
|
+
animation: m-shimmer var(--duration-crawl, 1s) var(--easing-linear, linear) infinite;
|
|
104
|
+
}
|
|
105
|
+
@keyframes m-shimmer { to { transform: translateX(100%); } }
|
|
106
|
+
|
|
107
|
+
/* -- Attention ------------------------------------------------------------- */
|
|
108
|
+
.m-pulse { animation: m-pulse 2s var(--ease) infinite; }
|
|
109
|
+
@keyframes m-pulse {
|
|
110
|
+
0%, 100% { transform: scale(1); opacity: 1; }
|
|
111
|
+
50% { transform: scale(1.04); opacity: 0.86; }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.m-shake { animation: m-shake var(--dur-slow) var(--ease) both; }
|
|
115
|
+
@keyframes m-shake {
|
|
116
|
+
0%, 100% { transform: translateX(0); }
|
|
117
|
+
20% { transform: translateX(-6px); }
|
|
118
|
+
40% { transform: translateX(5px); }
|
|
119
|
+
60% { transform: translateX(-3px); }
|
|
120
|
+
80% { transform: translateX(2px); }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Success draw-in — put on an SVG; strokes draw themselves. Set --m-len to the
|
|
124
|
+
* path length (getTotalLength) for an exact draw; 48 is a sane default. */
|
|
125
|
+
.m-draw path,
|
|
126
|
+
.m-draw [data-draw] {
|
|
127
|
+
stroke-dasharray: var(--m-len, 48);
|
|
128
|
+
stroke-dashoffset: var(--m-len, 48);
|
|
129
|
+
animation: m-draw var(--dur-slow) var(--easing-ease-out, cubic-bezier(0, 0, 0.2, 1)) forwards;
|
|
130
|
+
animation-delay: var(--m-delay, 0s);
|
|
131
|
+
}
|
|
132
|
+
@keyframes m-draw { to { stroke-dashoffset: 0; } }
|
|
133
|
+
|
|
134
|
+
/* -- Scroll reveals --------------------------------------------------------
|
|
135
|
+
* Progressive enhancement: [data-reveal] is only hidden once JS marks the
|
|
136
|
+
* document (<html class="js-reveal">). initReveal() then adds .is-revealed as
|
|
137
|
+
* each element enters the viewport. Stagger a group with --reveal-i (0,1,2…).
|
|
138
|
+
* If JS never runs, content shows normally — nothing is trapped hidden. */
|
|
139
|
+
.js-reveal [data-reveal] {
|
|
140
|
+
opacity: 0;
|
|
141
|
+
transform: translateY(18px);
|
|
142
|
+
transition:
|
|
143
|
+
opacity var(--dur-slow) var(--easing-ease-out, cubic-bezier(0, 0, 0.2, 1)),
|
|
144
|
+
transform var(--dur-slow) var(--easing-ease-out, cubic-bezier(0, 0, 0.2, 1));
|
|
145
|
+
transition-delay: calc(var(--reveal-i, 0) * var(--delay-2));
|
|
146
|
+
}
|
|
147
|
+
.js-reveal [data-reveal].is-revealed {
|
|
148
|
+
opacity: 1;
|
|
149
|
+
transform: none;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* -- Global reduced-motion safety net -------------------------------------
|
|
153
|
+
* Neutralises every animation/transition in the kit — the library's effects,
|
|
154
|
+
* component motion (button loader, badge pulse, feedback check), and smooth
|
|
155
|
+
* scroll. iteration-count:1 lets one-shot animations settle on their final
|
|
156
|
+
* frame (e.g. a checkmark stays drawn) instead of snapping to the start. */
|
|
157
|
+
@media (prefers-reduced-motion: reduce) {
|
|
158
|
+
html { scroll-behavior: auto !important; }
|
|
159
|
+
*, ::before, ::after {
|
|
160
|
+
animation-duration: 0.01ms !important;
|
|
161
|
+
animation-iteration-count: 1 !important;
|
|
162
|
+
transition-duration: 0.01ms !important;
|
|
163
|
+
scroll-behavior: auto !important;
|
|
164
|
+
}
|
|
165
|
+
[data-reveal] { opacity: 1 !important; transform: none !important; }
|
|
166
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Navigation — sidebar rail, horizontal tabs, breadcrumbs. One token-driven
|
|
3
|
+
* class family (ui-nav__…) so every variant re-themes across accents +
|
|
4
|
+
* light/dark. Mirrors the look the app shells draw inline (.ui-side) but as a
|
|
5
|
+
* first-class, standalone component. Active/hover surfaces are pure tokens.
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
.ui-nav { font-family: var(--font-sans); }
|
|
9
|
+
.ui-nav ul, .ui-nav ol { list-style: none; margin: 0; padding: 0; }
|
|
10
|
+
|
|
11
|
+
/* -- Sidebar rail -------------------------------------------------------- */
|
|
12
|
+
.ui-nav--side {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: 4px;
|
|
16
|
+
width: 216px;
|
|
17
|
+
}
|
|
18
|
+
.ui-nav__section + .ui-nav__section { margin-top: 14px; }
|
|
19
|
+
.ui-nav__list { display: flex; flex-direction: column; gap: 2px; }
|
|
20
|
+
.ui-nav__cap {
|
|
21
|
+
font-size: 11px;
|
|
22
|
+
font-weight: 600;
|
|
23
|
+
letter-spacing: 0.16em;
|
|
24
|
+
text-transform: uppercase;
|
|
25
|
+
color: var(--muted);
|
|
26
|
+
padding: 4px 12px 8px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Item row — link, group toggle, and leaf share the same skin */
|
|
30
|
+
.ui-nav__item {
|
|
31
|
+
position: relative;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: 11px;
|
|
35
|
+
width: 100%;
|
|
36
|
+
padding: 9px 12px;
|
|
37
|
+
border-radius: 10px;
|
|
38
|
+
color: var(--text);
|
|
39
|
+
text-decoration: none;
|
|
40
|
+
font-size: 14.5px;
|
|
41
|
+
font-weight: 400;
|
|
42
|
+
line-height: 1.2;
|
|
43
|
+
background: none;
|
|
44
|
+
border: 0;
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
text-align: left;
|
|
47
|
+
font-family: inherit;
|
|
48
|
+
transition: background 0.16s var(--ease), color 0.16s var(--ease);
|
|
49
|
+
}
|
|
50
|
+
.ui-nav__item:hover { background: var(--surface-2); color: var(--strong); }
|
|
51
|
+
.ui-nav__item.is-active {
|
|
52
|
+
background: var(--surface);
|
|
53
|
+
color: var(--strong);
|
|
54
|
+
font-weight: 500;
|
|
55
|
+
}
|
|
56
|
+
.ui-nav--side .ui-nav__item.is-active {
|
|
57
|
+
box-shadow: inset 0 0 0 1px var(--border);
|
|
58
|
+
}
|
|
59
|
+
/* Accent rail marker on the active item */
|
|
60
|
+
.ui-nav--side .ui-nav__item.is-active::before {
|
|
61
|
+
content: "";
|
|
62
|
+
position: absolute;
|
|
63
|
+
left: 4px; top: 50%;
|
|
64
|
+
width: 3px; height: 17px;
|
|
65
|
+
border-radius: 3px;
|
|
66
|
+
background: var(--accent);
|
|
67
|
+
transform: translateY(-50%);
|
|
68
|
+
}
|
|
69
|
+
.ui-nav__item:focus-visible { outline: none; box-shadow: var(--ring); }
|
|
70
|
+
.ui-nav__item.is-active:focus-visible { box-shadow: var(--ring); }
|
|
71
|
+
|
|
72
|
+
.ui-nav__ic { display: inline-flex; flex: none; }
|
|
73
|
+
.ui-nav__ic svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 1.7; opacity: 0.72; }
|
|
74
|
+
.ui-nav__item.is-active .ui-nav__ic svg,
|
|
75
|
+
.ui-nav__item.is-current .ui-nav__ic svg { opacity: 1; stroke: var(--accent); }
|
|
76
|
+
.ui-nav__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
77
|
+
|
|
78
|
+
.ui-nav__item.is-danger { color: var(--pink); }
|
|
79
|
+
.ui-nav__item.is-danger:hover { background: var(--glow-pink); color: var(--pink); }
|
|
80
|
+
.ui-nav__item.is-disabled { color: var(--muted); opacity: 0.55; cursor: default; pointer-events: none; }
|
|
81
|
+
|
|
82
|
+
/* Per-item badge / counter */
|
|
83
|
+
.ui-nav__badge {
|
|
84
|
+
flex: none;
|
|
85
|
+
min-width: 20px;
|
|
86
|
+
height: 19px;
|
|
87
|
+
padding: 0 6px;
|
|
88
|
+
border-radius: var(--radius-pill);
|
|
89
|
+
display: inline-flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
justify-content: center;
|
|
92
|
+
font-size: 11px;
|
|
93
|
+
font-weight: 600;
|
|
94
|
+
line-height: 1;
|
|
95
|
+
background: var(--surface-3);
|
|
96
|
+
color: var(--dim);
|
|
97
|
+
}
|
|
98
|
+
.ui-nav__badge.is-accent { background: var(--glow-purple); color: var(--accent); }
|
|
99
|
+
.ui-nav__badge.is-live { background: var(--glow-green); color: var(--green); }
|
|
100
|
+
.ui-nav__badge.is-danger { background: var(--glow-pink); color: var(--pink); }
|
|
101
|
+
.ui-nav__item.is-active .ui-nav__badge.is-neutral { background: var(--surface-3); color: var(--strong); }
|
|
102
|
+
|
|
103
|
+
/* Collapsible group */
|
|
104
|
+
.ui-nav__group { display: flex; flex-direction: column; }
|
|
105
|
+
.ui-nav__caret {
|
|
106
|
+
flex: none;
|
|
107
|
+
width: 7px; height: 7px;
|
|
108
|
+
border-right: 1.6px solid var(--muted);
|
|
109
|
+
border-bottom: 1.6px solid var(--muted);
|
|
110
|
+
transform: rotate(-45deg);
|
|
111
|
+
transition: transform 0.18s var(--ease);
|
|
112
|
+
}
|
|
113
|
+
.ui-nav__group.is-open > .ui-nav__toggle .ui-nav__caret { transform: rotate(45deg); }
|
|
114
|
+
.ui-nav__sub {
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-direction: column;
|
|
117
|
+
gap: 2px;
|
|
118
|
+
margin: 2px 0 2px 15px;
|
|
119
|
+
padding-left: 11px;
|
|
120
|
+
border-left: 1px solid var(--border);
|
|
121
|
+
}
|
|
122
|
+
.ui-nav__sub[hidden] { display: none; }
|
|
123
|
+
.ui-nav__item--sub { font-size: 14px; padding: 7px 12px; }
|
|
124
|
+
.ui-nav--side .ui-nav__item--sub.is-active { box-shadow: none; }
|
|
125
|
+
.ui-nav--side .ui-nav__item--sub.is-active::before { left: -12px; height: 15px; }
|
|
126
|
+
|
|
127
|
+
/* Footer slot (divider + trailing links, e.g. sign-out) */
|
|
128
|
+
.ui-nav__foot { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); }
|
|
129
|
+
|
|
130
|
+
/* Collapsed (icon-only) rail */
|
|
131
|
+
.ui-nav--side.is-collapsed { width: 60px; align-items: stretch; }
|
|
132
|
+
.ui-nav--side.is-collapsed .ui-nav__cap,
|
|
133
|
+
.ui-nav--side.is-collapsed .ui-nav__label,
|
|
134
|
+
.ui-nav--side.is-collapsed .ui-nav__badge,
|
|
135
|
+
.ui-nav--side.is-collapsed .ui-nav__caret,
|
|
136
|
+
.ui-nav--side.is-collapsed .ui-nav__sub { display: none; }
|
|
137
|
+
.ui-nav--side.is-collapsed .ui-nav__item { justify-content: center; padding: 11px 0; gap: 0; }
|
|
138
|
+
.ui-nav--side.is-collapsed .ui-nav__ic svg { width: 19px; height: 19px; }
|
|
139
|
+
.ui-nav--side.is-collapsed .ui-nav__item.is-active::before { left: 3px; }
|
|
140
|
+
.ui-nav--side.is-collapsed .ui-nav__foot { text-align: center; }
|
|
141
|
+
|
|
142
|
+
/* -- Horizontal tabs ----------------------------------------------------- */
|
|
143
|
+
.ui-nav--tabs {
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: stretch;
|
|
146
|
+
gap: 4px;
|
|
147
|
+
border-bottom: 1px solid var(--border);
|
|
148
|
+
}
|
|
149
|
+
.ui-nav--tabs.is-pill { border-bottom: 0; gap: 6px; }
|
|
150
|
+
.ui-nav__tab {
|
|
151
|
+
position: relative;
|
|
152
|
+
display: inline-flex;
|
|
153
|
+
align-items: center;
|
|
154
|
+
gap: 8px;
|
|
155
|
+
padding: 11px 14px;
|
|
156
|
+
color: var(--dim);
|
|
157
|
+
text-decoration: none;
|
|
158
|
+
font-size: 14px;
|
|
159
|
+
font-weight: 500;
|
|
160
|
+
border-radius: 10px 10px 0 0;
|
|
161
|
+
transition: color 0.16s var(--ease), background 0.16s var(--ease);
|
|
162
|
+
}
|
|
163
|
+
.ui-nav__tab:hover { color: var(--strong); }
|
|
164
|
+
.ui-nav__tab:focus-visible { outline: none; box-shadow: var(--ring); border-radius: 8px; }
|
|
165
|
+
.ui-nav__tab.is-active { color: var(--strong); }
|
|
166
|
+
|
|
167
|
+
/* underline variant */
|
|
168
|
+
.ui-nav--tabs.is-underline .ui-nav__tab::after {
|
|
169
|
+
content: "";
|
|
170
|
+
position: absolute;
|
|
171
|
+
left: 10px; right: 10px; bottom: -1px;
|
|
172
|
+
height: 2px;
|
|
173
|
+
border-radius: 2px;
|
|
174
|
+
background: var(--accent);
|
|
175
|
+
opacity: 0;
|
|
176
|
+
transform: scaleX(0.4);
|
|
177
|
+
transition: opacity 0.16s var(--ease), transform 0.16s var(--ease);
|
|
178
|
+
}
|
|
179
|
+
.ui-nav--tabs.is-underline .ui-nav__tab.is-active::after { opacity: 1; transform: scaleX(1); }
|
|
180
|
+
|
|
181
|
+
/* pill variant */
|
|
182
|
+
.ui-nav--tabs.is-pill .ui-nav__tab { border-radius: var(--radius-pill); padding: 8px 15px; }
|
|
183
|
+
.ui-nav--tabs.is-pill .ui-nav__tab:hover { background: var(--surface-2); }
|
|
184
|
+
.ui-nav--tabs.is-pill .ui-nav__tab.is-active {
|
|
185
|
+
background: var(--glow-purple);
|
|
186
|
+
color: var(--accent);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.ui-nav__tab.is-disabled { color: var(--muted); opacity: 0.5; cursor: default; }
|
|
190
|
+
.ui-nav__tab .ui-nav__badge { height: 18px; }
|
|
191
|
+
|
|
192
|
+
/* -- Breadcrumbs --------------------------------------------------------- */
|
|
193
|
+
.ui-nav__crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; }
|
|
194
|
+
.ui-nav__crumb-item { display: inline-flex; align-items: center; }
|
|
195
|
+
.ui-nav__crumb-item + .ui-nav__crumb-item::before {
|
|
196
|
+
content: "";
|
|
197
|
+
width: 6px; height: 6px;
|
|
198
|
+
margin: 0 8px;
|
|
199
|
+
border-right: 1.4px solid var(--muted);
|
|
200
|
+
border-bottom: 1.4px solid var(--muted);
|
|
201
|
+
transform: rotate(-45deg);
|
|
202
|
+
opacity: 0.7;
|
|
203
|
+
}
|
|
204
|
+
.ui-nav__crumb {
|
|
205
|
+
display: inline-flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
gap: 6px;
|
|
208
|
+
padding: 3px 6px;
|
|
209
|
+
border-radius: 7px;
|
|
210
|
+
color: var(--dim);
|
|
211
|
+
text-decoration: none;
|
|
212
|
+
font-size: 13px;
|
|
213
|
+
transition: color 0.16s var(--ease), background 0.16s var(--ease);
|
|
214
|
+
}
|
|
215
|
+
a.ui-nav__crumb:hover { color: var(--strong); background: var(--surface-2); }
|
|
216
|
+
a.ui-nav__crumb:focus-visible { outline: none; box-shadow: var(--ring); }
|
|
217
|
+
.ui-nav__crumb.is-current { color: var(--strong); font-weight: 500; }
|
|
218
|
+
.ui-nav__crumb .ui-nav__ic svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.7; }
|