@apliteni/apliteni-ui 0.4.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 +8 -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 +46 -4
- 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 +16 -15
- package/src/index.css +8 -0
- package/src/index.js +5 -0
- package/src/inline.js +19 -3
- package/src/motion.js +68 -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/tokens/brand.generated.css +121 -0
- package/src/tokens/tokens.css +18 -5
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Drawer — edge-anchored overlay panel (Sheet / Slide-over): a panel that
|
|
3
|
+
* slides in from any screen edge over a scrim. Fully token-driven so it
|
|
4
|
+
* re-themes across accents + light/dark. Shares its scrim + focus-trap concept
|
|
5
|
+
* with a future Modal (see drawer.js). `prefers-reduced-motion` drops the slide
|
|
6
|
+
* for a plain fade.
|
|
7
|
+
*
|
|
8
|
+
* Local tokens (surface/shadow/scrim) so the panel + scrim re-theme cleanly:
|
|
9
|
+
* --drawer-surface panel background --drawer-shadow panel elevation
|
|
10
|
+
* --drawer-scrim backdrop colour (overridden for light below)
|
|
11
|
+
* ========================================================================== */
|
|
12
|
+
|
|
13
|
+
.ui-drawer {
|
|
14
|
+
--drawer-surface: var(--surface-2);
|
|
15
|
+
--drawer-shadow: var(--shadow-lg);
|
|
16
|
+
--drawer-scrim: rgba(0, 0, 0, 0.58);
|
|
17
|
+
--drawer-w: 420px; /* left|right panels: width */
|
|
18
|
+
--drawer-h: 45vh; /* top|bottom panels: height */
|
|
19
|
+
|
|
20
|
+
position: fixed;
|
|
21
|
+
inset: 0;
|
|
22
|
+
z-index: var(--z-overlay);
|
|
23
|
+
pointer-events: none; /* container never blocks; scrim/panel opt back in */
|
|
24
|
+
}
|
|
25
|
+
:root[data-theme="light"] .ui-drawer { --drawer-scrim: rgba(20, 22, 40, 0.38); }
|
|
26
|
+
|
|
27
|
+
/* Scrim — fades in; visibility trails the fade so the panel can animate out. */
|
|
28
|
+
.ui-drawer__scrim {
|
|
29
|
+
position: absolute;
|
|
30
|
+
inset: 0;
|
|
31
|
+
background: var(--drawer-scrim);
|
|
32
|
+
pointer-events: auto;
|
|
33
|
+
opacity: 0;
|
|
34
|
+
visibility: hidden;
|
|
35
|
+
transition: opacity var(--dur-med) var(--ease), visibility var(--dur-med) var(--ease);
|
|
36
|
+
}
|
|
37
|
+
.ui-drawer.is-open .ui-drawer__scrim { opacity: 1; visibility: visible; }
|
|
38
|
+
|
|
39
|
+
/* Panel — the sliding surface. Flex column: header / scrollable body / footer. */
|
|
40
|
+
.ui-drawer__panel {
|
|
41
|
+
position: absolute;
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
background: var(--drawer-surface);
|
|
45
|
+
box-shadow: var(--drawer-shadow);
|
|
46
|
+
pointer-events: auto;
|
|
47
|
+
opacity: 1;
|
|
48
|
+
visibility: hidden;
|
|
49
|
+
transition:
|
|
50
|
+
transform var(--dur-med) var(--ease),
|
|
51
|
+
opacity var(--dur-med) var(--ease),
|
|
52
|
+
visibility var(--dur-med) var(--ease);
|
|
53
|
+
}
|
|
54
|
+
.ui-drawer__panel:focus-visible { outline: none; box-shadow: var(--drawer-shadow), var(--ring); }
|
|
55
|
+
.ui-drawer.is-open .ui-drawer__panel { visibility: visible; transform: none; }
|
|
56
|
+
|
|
57
|
+
/* -- Edge anchoring + slide-in direction ---------------------------------- */
|
|
58
|
+
.ui-drawer--right .ui-drawer__panel,
|
|
59
|
+
.ui-drawer--left .ui-drawer__panel {
|
|
60
|
+
top: 0; bottom: 0;
|
|
61
|
+
width: min(var(--drawer-w), 100vw);
|
|
62
|
+
max-width: 100vw;
|
|
63
|
+
}
|
|
64
|
+
.ui-drawer--right .ui-drawer__panel {
|
|
65
|
+
right: 0;
|
|
66
|
+
border-left: 1px solid var(--border);
|
|
67
|
+
transform: translateX(100%);
|
|
68
|
+
}
|
|
69
|
+
.ui-drawer--left .ui-drawer__panel {
|
|
70
|
+
left: 0;
|
|
71
|
+
border-right: 1px solid var(--border);
|
|
72
|
+
transform: translateX(-100%);
|
|
73
|
+
}
|
|
74
|
+
.ui-drawer--top .ui-drawer__panel,
|
|
75
|
+
.ui-drawer--bottom .ui-drawer__panel {
|
|
76
|
+
left: 0; right: 0;
|
|
77
|
+
height: min(var(--drawer-h), 100vh);
|
|
78
|
+
max-height: 100vh;
|
|
79
|
+
}
|
|
80
|
+
.ui-drawer--top .ui-drawer__panel {
|
|
81
|
+
top: 0;
|
|
82
|
+
border-bottom: 1px solid var(--border);
|
|
83
|
+
transform: translateY(-100%);
|
|
84
|
+
}
|
|
85
|
+
.ui-drawer--bottom .ui-drawer__panel {
|
|
86
|
+
bottom: 0;
|
|
87
|
+
border-top: 1px solid var(--border);
|
|
88
|
+
transform: translateY(100%);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* -- Sizes ----------------------------------------------------------------- */
|
|
92
|
+
/* left|right → width; top|bottom → height. Same names, axis chosen by side. */
|
|
93
|
+
.ui-drawer--right.ui-drawer--sm, .ui-drawer--left.ui-drawer--sm { --drawer-w: 320px; }
|
|
94
|
+
.ui-drawer--right.ui-drawer--md, .ui-drawer--left.ui-drawer--md { --drawer-w: 420px; }
|
|
95
|
+
.ui-drawer--right.ui-drawer--lg, .ui-drawer--left.ui-drawer--lg { --drawer-w: 560px; }
|
|
96
|
+
.ui-drawer--top.ui-drawer--sm, .ui-drawer--bottom.ui-drawer--sm { --drawer-h: 30vh; }
|
|
97
|
+
.ui-drawer--top.ui-drawer--md, .ui-drawer--bottom.ui-drawer--md { --drawer-h: 45vh; }
|
|
98
|
+
.ui-drawer--top.ui-drawer--lg, .ui-drawer--bottom.ui-drawer--lg { --drawer-h: 72vh; }
|
|
99
|
+
|
|
100
|
+
/* -- Slots ----------------------------------------------------------------- */
|
|
101
|
+
.ui-drawer__header {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: space-between;
|
|
105
|
+
gap: 12px;
|
|
106
|
+
padding: 18px 20px;
|
|
107
|
+
border-bottom: 1px solid var(--border);
|
|
108
|
+
flex: none;
|
|
109
|
+
}
|
|
110
|
+
.ui-drawer__title {
|
|
111
|
+
margin: 0;
|
|
112
|
+
font-family: var(--font-sans);
|
|
113
|
+
font-size: 15px;
|
|
114
|
+
font-weight: 600;
|
|
115
|
+
letter-spacing: 0.01em;
|
|
116
|
+
color: var(--strong);
|
|
117
|
+
}
|
|
118
|
+
.ui-drawer__close {
|
|
119
|
+
display: inline-flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
justify-content: center;
|
|
122
|
+
width: 30px; height: 30px;
|
|
123
|
+
flex: none;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
color: var(--muted);
|
|
126
|
+
background: transparent;
|
|
127
|
+
border: 1px solid transparent;
|
|
128
|
+
border-radius: var(--radius-sm);
|
|
129
|
+
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
|
|
130
|
+
}
|
|
131
|
+
.ui-drawer__close:hover { color: var(--strong); background: var(--surface); }
|
|
132
|
+
.ui-drawer__close:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
|
|
133
|
+
.ui-drawer__close svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.8; }
|
|
134
|
+
|
|
135
|
+
.ui-drawer__body {
|
|
136
|
+
flex: 1 1 auto;
|
|
137
|
+
min-height: 0;
|
|
138
|
+
overflow-y: auto;
|
|
139
|
+
padding: 20px;
|
|
140
|
+
color: var(--text);
|
|
141
|
+
font-family: var(--font-sans);
|
|
142
|
+
font-size: 13px;
|
|
143
|
+
line-height: 1.55;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.ui-drawer__footer {
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
justify-content: flex-end;
|
|
150
|
+
gap: 10px;
|
|
151
|
+
padding: 16px 20px;
|
|
152
|
+
border-top: 1px solid var(--border);
|
|
153
|
+
background: var(--surface-2);
|
|
154
|
+
flex: none;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* -- Reduced motion: no slide, just a fade -------------------------------- */
|
|
158
|
+
@media (prefers-reduced-motion: reduce) {
|
|
159
|
+
.ui-drawer__panel {
|
|
160
|
+
transform: none !important;
|
|
161
|
+
opacity: 0;
|
|
162
|
+
transition: opacity var(--dur-fast) linear, visibility var(--dur-fast) linear;
|
|
163
|
+
}
|
|
164
|
+
.ui-drawer.is-open .ui-drawer__panel { opacity: 1; }
|
|
165
|
+
.ui-drawer__scrim { transition: opacity var(--dur-fast) linear, visibility var(--dur-fast) linear; }
|
|
166
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Dropdown — the kit's popover-list primitive (trigger + panel + item rows).
|
|
3
|
+
* Fully token-driven so it re-themes across accents + light/dark. Shares its
|
|
4
|
+
* open/close JS with the topbar's version switcher and account menu, which keep
|
|
5
|
+
* their own bespoke classes; this file styles the standalone `dropdown()`.
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
.ui-dropdown { position: relative; display: inline-flex; }
|
|
9
|
+
|
|
10
|
+
/* Trigger — pill button: [pre] [value] [chevron] */
|
|
11
|
+
.ui-dropdown__trigger {
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: 8px;
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
font-family: var(--font-sans);
|
|
17
|
+
font-size: 12.5px;
|
|
18
|
+
font-weight: 500;
|
|
19
|
+
color: var(--text);
|
|
20
|
+
background: var(--surface);
|
|
21
|
+
border: 1px solid var(--border);
|
|
22
|
+
border-radius: var(--radius-pill);
|
|
23
|
+
padding: 7px 11px 7px 13px;
|
|
24
|
+
transition: border-color 0.2s ease, background 0.2s ease;
|
|
25
|
+
}
|
|
26
|
+
.ui-dropdown__trigger:hover { border-color: var(--accent); }
|
|
27
|
+
.ui-dropdown__trigger:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
|
|
28
|
+
.ui-dropdown__pre { color: var(--muted); }
|
|
29
|
+
.ui-dropdown__value { color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
|
|
30
|
+
|
|
31
|
+
/* Chevron — border caret that flips when the container is .open */
|
|
32
|
+
.ui-dropdown__chevron {
|
|
33
|
+
width: 8px; height: 8px;
|
|
34
|
+
border-right: 1.6px solid var(--muted);
|
|
35
|
+
border-bottom: 1.6px solid var(--muted);
|
|
36
|
+
transform: rotate(45deg) translateY(-1px);
|
|
37
|
+
transition: transform 0.2s ease;
|
|
38
|
+
flex: none;
|
|
39
|
+
}
|
|
40
|
+
.ui-dropdown.open .ui-dropdown__chevron { transform: rotate(-135deg) translateY(2px); }
|
|
41
|
+
|
|
42
|
+
/* Panel — the popover surface */
|
|
43
|
+
.ui-dropdown__panel {
|
|
44
|
+
position: absolute;
|
|
45
|
+
top: calc(100% + 9px);
|
|
46
|
+
left: 0;
|
|
47
|
+
min-width: 240px;
|
|
48
|
+
background: var(--surface-2);
|
|
49
|
+
border: 1px solid var(--border);
|
|
50
|
+
border-radius: var(--radius-lg);
|
|
51
|
+
padding: 6px;
|
|
52
|
+
box-shadow: var(--shadow-lg);
|
|
53
|
+
opacity: 0;
|
|
54
|
+
visibility: hidden;
|
|
55
|
+
transform: translateY(-6px);
|
|
56
|
+
transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s ease;
|
|
57
|
+
z-index: var(--z-dropdown);
|
|
58
|
+
}
|
|
59
|
+
.ui-dropdown__panel.is-end { left: auto; right: 0; }
|
|
60
|
+
.ui-dropdown__panel.is-scroll { max-height: 300px; overflow-y: auto; }
|
|
61
|
+
.ui-dropdown.open .ui-dropdown__panel { opacity: 1; visibility: visible; transform: translateY(0); }
|
|
62
|
+
|
|
63
|
+
/* Item row — [icon] [main: label + desc] [badge] [tick] */
|
|
64
|
+
.ui-dropdown__item {
|
|
65
|
+
display: flex;
|
|
66
|
+
gap: 11px;
|
|
67
|
+
align-items: center;
|
|
68
|
+
padding: 9px 12px;
|
|
69
|
+
border-radius: var(--radius-sm);
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
text-decoration: none;
|
|
72
|
+
color: var(--text);
|
|
73
|
+
transition: background 0.15s ease;
|
|
74
|
+
}
|
|
75
|
+
.ui-dropdown__item:hover { background: var(--surface); }
|
|
76
|
+
.ui-dropdown__item:focus-visible { outline: none; background: var(--surface); box-shadow: var(--ring); }
|
|
77
|
+
.ui-dropdown__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
78
|
+
.ui-dropdown__label { font-family: var(--font-sans); font-size: 12.5px; color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
|
|
79
|
+
.ui-dropdown__desc { font-size: 11px; color: var(--muted); line-height: 1.35; }
|
|
80
|
+
.ui-dropdown__ic { display: inline-flex; flex: none; color: var(--muted); }
|
|
81
|
+
.ui-dropdown__ic svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.8; }
|
|
82
|
+
|
|
83
|
+
/* Trailing selected tick (listbox variant) */
|
|
84
|
+
.ui-dropdown__tick { margin-left: auto; flex: none; display: none; color: var(--accent); }
|
|
85
|
+
.ui-dropdown__tick svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2.2; }
|
|
86
|
+
.ui-dropdown__item.is-selected .ui-dropdown__tick { display: inline-flex; }
|
|
87
|
+
.ui-dropdown__item.is-selected .ui-dropdown__label { color: var(--accent); }
|
|
88
|
+
|
|
89
|
+
/* When a row has both a badge and a tick, keep the badge from eating the margin */
|
|
90
|
+
.ui-dropdown__badge + .ui-dropdown__tick { margin-left: 8px; }
|
|
91
|
+
|
|
92
|
+
/* Trailing status badge */
|
|
93
|
+
.ui-dropdown__badge {
|
|
94
|
+
font-size: 9.5px;
|
|
95
|
+
letter-spacing: 0.12em;
|
|
96
|
+
text-transform: uppercase;
|
|
97
|
+
font-weight: 700;
|
|
98
|
+
padding: 2px 7px;
|
|
99
|
+
border-radius: var(--radius-pill);
|
|
100
|
+
margin-left: auto;
|
|
101
|
+
flex: none;
|
|
102
|
+
color: var(--muted);
|
|
103
|
+
background: var(--surface);
|
|
104
|
+
}
|
|
105
|
+
.ui-dropdown__badge.is-live { color: var(--green); background: var(--glow-green); }
|
|
106
|
+
.ui-dropdown__badge.is-accent { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
|
|
107
|
+
|
|
108
|
+
/* Disabled + danger rows */
|
|
109
|
+
.ui-dropdown__item.is-disabled { opacity: 0.45; cursor: not-allowed; pointer-events: none; }
|
|
110
|
+
.ui-dropdown__item.is-danger .ui-dropdown__label { color: var(--muted); }
|
|
111
|
+
.ui-dropdown__item.is-danger:hover .ui-dropdown__label { color: var(--accent); }
|
|
112
|
+
|
|
113
|
+
/* Grouped sections */
|
|
114
|
+
.ui-dropdown__section + .ui-dropdown__section { border-top: 1px solid var(--border); margin-top: 5px; padding-top: 5px; }
|
|
115
|
+
.ui-dropdown__group {
|
|
116
|
+
font-size: 10px;
|
|
117
|
+
letter-spacing: 0.1em;
|
|
118
|
+
text-transform: uppercase;
|
|
119
|
+
font-weight: 700;
|
|
120
|
+
color: var(--muted);
|
|
121
|
+
padding: 7px 12px 4px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Separator */
|
|
125
|
+
.ui-dropdown__sep { height: 1px; background: var(--border); margin: 5px 4px; }
|
|
126
|
+
|
|
127
|
+
/* Optional pinned header/footer blocks */
|
|
128
|
+
.ui-dropdown__panel > .ui-dropdown__head {
|
|
129
|
+
padding: 11px 13px;
|
|
130
|
+
margin: -6px -6px 5px;
|
|
131
|
+
border-bottom: 1px solid var(--border);
|
|
132
|
+
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
|
133
|
+
}
|
|
@@ -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
|
+
}
|