@nexub/themes 1.5.1 → 1.7.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 +3 -2
- package/src/base.css +218 -1
- package/src/view-transitions.css +30 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexub/themes",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "CSS theme files for Nexub Design System — light, dark, and brand themes (Leap, Bookub, Payub, Founderub) as importable CSS.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/Nexub-AI/nexub-ds#readme",
|
|
@@ -30,13 +30,14 @@
|
|
|
30
30
|
"./light": "./src/light.css",
|
|
31
31
|
"./dark": "./src/dark.css",
|
|
32
32
|
"./glass": "./src/glass.css",
|
|
33
|
+
"./view-transitions": "./src/view-transitions.css",
|
|
33
34
|
"./leap": "./src/leap.css",
|
|
34
35
|
"./bookub": "./src/bookub.css",
|
|
35
36
|
"./payub": "./src/payub.css",
|
|
36
37
|
"./founderub": "./src/founderub.css"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"@nexub/tokens": "1.
|
|
40
|
+
"@nexub/tokens": "1.7.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@fontsource-variable/inter": "5.3.0",
|
package/src/base.css
CHANGED
|
@@ -238,8 +238,10 @@ code, kbd, pre, samp {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
/* Reduced motion */
|
|
241
|
+
/* Reduced motion: everything is instant, except the designed fallbacks
|
|
242
|
+
(nx-motion, nx-reveal), which keep their crossfade with no travel. */
|
|
241
243
|
@media (prefers-reduced-motion: reduce) {
|
|
242
|
-
|
|
244
|
+
*:not(.nx-motion, .nx-reveal), *::before, *::after {
|
|
243
245
|
animation-duration: 0.01ms !important;
|
|
244
246
|
animation-iteration-count: 1 !important;
|
|
245
247
|
transition-duration: 0.01ms !important;
|
|
@@ -274,6 +276,209 @@ code, kbd, pre, samp {
|
|
|
274
276
|
}
|
|
275
277
|
.nx-press:active:not(:disabled, [aria-disabled='true'], [data-disabled]) { transform: scale(0.97); }
|
|
276
278
|
|
|
279
|
+
/* ─── Enter / exit (overlays, menus, tooltips, toasts) ───────────────────────
|
|
280
|
+
Opacity rides the effects spring; position and scale ride the spatial
|
|
281
|
+
spring, so a little overshoot reads as physical without flashing.
|
|
282
|
+
Keyframes, not transitions: Radix keeps closing content mounted until its
|
|
283
|
+
exit animation ends. Standalone translate/scale compose with the
|
|
284
|
+
component's own transform (e.g. a centred dialog's -50% offsets).
|
|
285
|
+
Modifiers set where the layer comes from: --nx-x, --nx-y, --nx-scale. */
|
|
286
|
+
@keyframes nx-fade-in { from { opacity: 0; } }
|
|
287
|
+
@keyframes nx-fade-out { to { opacity: 0; } }
|
|
288
|
+
@keyframes nx-move-in { from { translate: var(--nx-x, 0) var(--nx-y, 0); scale: var(--nx-scale, 1); } }
|
|
289
|
+
@keyframes nx-move-out { to { translate: var(--nx-x, 0) var(--nx-y, 0); scale: var(--nx-scale, 1); } }
|
|
290
|
+
|
|
291
|
+
.nx-motion:is([data-state='open'], [data-state='delayed-open'], [data-state='instant-open'], [data-motion^='from-']) {
|
|
292
|
+
animation:
|
|
293
|
+
nx-fade-in var(--motion-spring-effects-default-duration) var(--motion-spring-effects-default-easing),
|
|
294
|
+
nx-move-in var(--motion-spring-spatial-default-duration) var(--motion-spring-spatial-default-easing);
|
|
295
|
+
}
|
|
296
|
+
/* Exit is quicker than enter and never overshoots. */
|
|
297
|
+
.nx-motion:is([data-state='closed'], [data-motion^='to-']) {
|
|
298
|
+
animation:
|
|
299
|
+
nx-fade-out var(--motion-spring-effects-fast-duration) var(--motion-spring-effects-fast-easing) forwards,
|
|
300
|
+
nx-move-out var(--motion-spring-effects-fast-duration) var(--motion-spring-effects-fast-easing) forwards;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* Floating layers grow from their trigger: a small scale plus a 4px nudge
|
|
304
|
+
away from the side they open on (Radix sets data-side). Components set
|
|
305
|
+
transform-origin to Radix's --radix-*-transform-origin. */
|
|
306
|
+
.nx-pop { --nx-scale: 0.96; }
|
|
307
|
+
.nx-pop[data-side='bottom'] { --nx-y: -4px; }
|
|
308
|
+
.nx-pop[data-side='top'] { --nx-y: 4px; }
|
|
309
|
+
.nx-pop[data-side='left'] { --nx-x: 4px; }
|
|
310
|
+
.nx-pop[data-side='right'] { --nx-x: -4px; }
|
|
311
|
+
/* Modal dialogs rise slightly as they scale in. */
|
|
312
|
+
.nx-dialog { --nx-scale: 0.96; --nx-y: 8px; }
|
|
313
|
+
/* NavigationMenu content slides in from the item's direction. */
|
|
314
|
+
.nx-motion:is([data-motion='from-start'], [data-motion='to-start']) { --nx-x: -24px; }
|
|
315
|
+
.nx-motion:is([data-motion='from-end'], [data-motion='to-end']) { --nx-x: 24px; }
|
|
316
|
+
|
|
317
|
+
/* Reduced motion: a designed fallback, not a cut. Keep the crossfade, drop
|
|
318
|
+
the travel and scale. */
|
|
319
|
+
@media (prefers-reduced-motion: reduce) {
|
|
320
|
+
.nx-motion { --nx-x: 0px !important; --nx-y: 0px !important; --nx-scale: 1 !important; }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/* ─── Cards: lift, press, sheen ──────────────────────────────────────────────
|
|
324
|
+
Lift: hover raises 2px on the spatial spring with elevation 1 → 2; leaving
|
|
325
|
+
settles faster on the effects spring (a transition uses the *destination*
|
|
326
|
+
state's timing). Press sinks to 0.99. Hover only where a real pointer
|
|
327
|
+
hovers, so touch never gets a stuck lift. */
|
|
328
|
+
.nx-lift {
|
|
329
|
+
transition:
|
|
330
|
+
translate var(--motion-spring-effects-fast-duration) var(--motion-spring-effects-fast-easing),
|
|
331
|
+
scale var(--motion-spring-effects-fast-duration) var(--motion-spring-effects-fast-easing),
|
|
332
|
+
box-shadow var(--motion-spring-effects-fast-duration) var(--motion-spring-effects-fast-easing);
|
|
333
|
+
}
|
|
334
|
+
@media (hover: hover) {
|
|
335
|
+
.nx-lift:hover {
|
|
336
|
+
translate: 0 -2px;
|
|
337
|
+
transition:
|
|
338
|
+
translate var(--motion-spring-spatial-default-duration) var(--motion-spring-spatial-default-easing),
|
|
339
|
+
scale var(--motion-spring-spatial-fast-duration) var(--motion-spring-spatial-fast-easing),
|
|
340
|
+
box-shadow var(--motion-spring-effects-default-duration) var(--motion-spring-effects-default-easing);
|
|
341
|
+
}
|
|
342
|
+
/* Glass keeps its own shadow (it carries the specular edge). */
|
|
343
|
+
.nx-lift:not([data-material='glass']):hover { box-shadow: var(--elevation-2); }
|
|
344
|
+
}
|
|
345
|
+
.nx-lift:active { translate: 0 0; scale: 0.99; }
|
|
346
|
+
@media (prefers-reduced-motion: reduce) {
|
|
347
|
+
.nx-lift:hover, .nx-lift:active { translate: none; scale: none; }
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* Sheen (opt-in): a soft light that follows the pointer across the surface,
|
|
351
|
+
plus a brighter rim where the pointer is nearest the edge. JS sets
|
|
352
|
+
--nx-mx/--nx-my through the CSSOM (CSP-safe); --nx-sheen fades it in.
|
|
353
|
+
Primary-tinted on light surfaces, white on dark and on glass. Off for touch
|
|
354
|
+
(no hover), reduced motion and reduced transparency. */
|
|
355
|
+
@property --nx-sheen { syntax: '<number>'; inherits: true; initial-value: 0; }
|
|
356
|
+
.nx-sheen {
|
|
357
|
+
position: relative;
|
|
358
|
+
--nx-sheen-tint: light-dark(var(--color-primary), #fff);
|
|
359
|
+
background-image: radial-gradient(
|
|
360
|
+
28rem circle at var(--nx-mx, 50%) var(--nx-my, 0%),
|
|
361
|
+
color-mix(in srgb, var(--nx-sheen-tint) calc(var(--nx-sheen) * 9%), transparent),
|
|
362
|
+
transparent 70%
|
|
363
|
+
);
|
|
364
|
+
transition: --nx-sheen var(--motion-spring-effects-fast-duration) var(--motion-spring-effects-fast-easing);
|
|
365
|
+
}
|
|
366
|
+
.nx-sheen::after {
|
|
367
|
+
content: '';
|
|
368
|
+
position: absolute;
|
|
369
|
+
inset: -1px;
|
|
370
|
+
padding: 1px;
|
|
371
|
+
border-radius: inherit;
|
|
372
|
+
pointer-events: none;
|
|
373
|
+
background: radial-gradient(
|
|
374
|
+
14rem circle at var(--nx-mx, 50%) var(--nx-my, 0%),
|
|
375
|
+
color-mix(in srgb, var(--nx-sheen-tint) calc(var(--nx-sheen) * 70%), transparent),
|
|
376
|
+
transparent 70%
|
|
377
|
+
);
|
|
378
|
+
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
|
379
|
+
-webkit-mask-composite: xor;
|
|
380
|
+
mask-composite: exclude;
|
|
381
|
+
}
|
|
382
|
+
[data-material='glass'].nx-sheen { --nx-sheen-tint: #fff; }
|
|
383
|
+
/* On a sheen surface the light is the hover feedback; the grey state layer
|
|
384
|
+
would wash it out. Press still darkens. */
|
|
385
|
+
.nx-sheen.nx-state-layer:hover:not(:active)::before { opacity: 0; }
|
|
386
|
+
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
|
|
387
|
+
.nx-sheen:hover {
|
|
388
|
+
--nx-sheen: 1;
|
|
389
|
+
transition: --nx-sheen var(--motion-spring-effects-default-duration) var(--motion-spring-effects-default-easing);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
393
|
+
.nx-sheen { background-image: none; }
|
|
394
|
+
.nx-sheen::after { display: none; }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* ─── AI: thinking indicator ─────────────────────────────────────────────────
|
|
398
|
+
nx-morph: a small shape that breathes between a circle and a soft squircle
|
|
399
|
+
on the expressive spring. nx-shimmer: a decorative light sweep across label
|
|
400
|
+
text (the text stays in the DOM). Reduced motion: both static. */
|
|
401
|
+
@keyframes nx-morph {
|
|
402
|
+
from { border-radius: 50%; rotate: 0deg; }
|
|
403
|
+
to { border-radius: 38% 62% 38% 62% / 62% 38% 62% 38%; rotate: 90deg; }
|
|
404
|
+
}
|
|
405
|
+
@keyframes nx-shimmer {
|
|
406
|
+
from { background-position: 100% 0; }
|
|
407
|
+
to { background-position: -100% 0; }
|
|
408
|
+
}
|
|
409
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
410
|
+
.nx-morph {
|
|
411
|
+
animation: nx-morph calc(var(--motion-spring-expressive-slow-duration) * 2)
|
|
412
|
+
var(--motion-spring-expressive-slow-easing) infinite alternate;
|
|
413
|
+
}
|
|
414
|
+
.nx-shimmer {
|
|
415
|
+
background-image: linear-gradient(90deg,
|
|
416
|
+
currentColor 0%, currentColor 40%, var(--color-on-surface) 50%, currentColor 60%, currentColor 100%);
|
|
417
|
+
background-size: 200% 100%;
|
|
418
|
+
-webkit-background-clip: text;
|
|
419
|
+
background-clip: text;
|
|
420
|
+
-webkit-text-fill-color: transparent;
|
|
421
|
+
/* A steady sweep: linear, not a spring (a spring would pulse each loop). */
|
|
422
|
+
animation: nx-shimmer 2s linear infinite;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
@media (forced-colors: active) {
|
|
426
|
+
.nx-shimmer { background: none; -webkit-text-fill-color: currentColor; }
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/* Streaming caret: a steady blink while text is arriving. */
|
|
430
|
+
@keyframes nx-caret { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
|
|
431
|
+
.nx-caret { animation: nx-caret 1s steps(1, end) infinite; }
|
|
432
|
+
@media (prefers-reduced-motion: reduce) {
|
|
433
|
+
.nx-caret { animation: none; }
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/* ─── Touch targets ─────────────────────────────────────────────────────────
|
|
437
|
+
Small controls keep their visual size but get an invisible 44×44 hit area
|
|
438
|
+
on touch screens (Apple HIG, WCAG 2.5.5). Uses ::after; the state layer
|
|
439
|
+
uses ::before. */
|
|
440
|
+
@media (pointer: coarse) {
|
|
441
|
+
.nx-hit { position: relative; }
|
|
442
|
+
.nx-hit::after {
|
|
443
|
+
content: '';
|
|
444
|
+
position: absolute;
|
|
445
|
+
left: 50%;
|
|
446
|
+
top: 50%;
|
|
447
|
+
width: max(100%, 44px);
|
|
448
|
+
height: max(100%, 44px);
|
|
449
|
+
translate: -50% -50%;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/* ─── Micro-moments ──────────────────────────────────────────────────────────
|
|
454
|
+
Reveal: content replacing a skeleton crossfades in (no travel). */
|
|
455
|
+
.nx-reveal {
|
|
456
|
+
animation: nx-fade-in var(--motion-spring-effects-default-duration) var(--motion-spring-effects-default-easing);
|
|
457
|
+
}
|
|
458
|
+
/* Check draw: a checkmark path (pathLength="1") draws itself in. */
|
|
459
|
+
@keyframes nx-draw { from { stroke-dashoffset: 1; } }
|
|
460
|
+
.nx-check-draw {
|
|
461
|
+
stroke-dasharray: 1;
|
|
462
|
+
animation: nx-draw var(--motion-spring-effects-default-duration) var(--motion-spring-effects-default-easing);
|
|
463
|
+
}
|
|
464
|
+
@media (prefers-reduced-motion: reduce) {
|
|
465
|
+
.nx-check-draw { animation: none; }
|
|
466
|
+
}
|
|
467
|
+
/* Shake: a short horizontal "no" on the spatial-fast spring (async Button error). */
|
|
468
|
+
@keyframes nx-shake {
|
|
469
|
+
0%, 100% { translate: 0; }
|
|
470
|
+
20% { translate: -4px; }
|
|
471
|
+
40% { translate: 4px; }
|
|
472
|
+
60% { translate: -3px; }
|
|
473
|
+
80% { translate: 2px; }
|
|
474
|
+
}
|
|
475
|
+
.nx-shake {
|
|
476
|
+
animation: nx-shake var(--motion-spring-spatial-fast-duration) var(--motion-spring-spatial-fast-easing);
|
|
477
|
+
}
|
|
478
|
+
@media (prefers-reduced-motion: reduce) {
|
|
479
|
+
.nx-shake { animation: none; }
|
|
480
|
+
}
|
|
481
|
+
|
|
277
482
|
/* ─── Density ────────────────────────────────────────────────────────────────
|
|
278
483
|
Compact shifts every control one step down the ladder (md 40 → 32). */
|
|
279
484
|
[data-density='compact'] {
|
|
@@ -294,6 +499,18 @@ code, kbd, pre, samp {
|
|
|
294
499
|
One height for the fixed top header everywhere. Offset layouts with
|
|
295
500
|
var(--app-header-height); AppHeader's own spacer already does in flow. */
|
|
296
501
|
:root { --app-header-height: 64px; }
|
|
502
|
+
/* condense: scroll-driven, no JS. The page spacer stays 64px; only the bar
|
|
503
|
+
tightens. Glass keeps its own shadow. */
|
|
504
|
+
@keyframes nx-condense { to { height: 52px; } }
|
|
505
|
+
@keyframes nx-condense-lift { to { height: 52px; box-shadow: var(--elevation-1); } }
|
|
506
|
+
@supports (animation-timeline: scroll()) {
|
|
507
|
+
[data-condense] {
|
|
508
|
+
animation: nx-condense-lift linear both;
|
|
509
|
+
animation-timeline: scroll(root);
|
|
510
|
+
animation-range: 0 64px;
|
|
511
|
+
}
|
|
512
|
+
[data-condense][data-material='glass'] { animation-name: nx-condense; }
|
|
513
|
+
}
|
|
297
514
|
|
|
298
515
|
/* ─── Scroll lock (Sheet scrollLock="class") ─────────────────────────────────
|
|
299
516
|
A CSP-safe page scroll lock: @nexub/react toggles these classes on <html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* @nexub/themes/view-transitions — opt-in page transitions.
|
|
2
|
+
Import after base. Multi-page sites get them on navigation (@view-transition);
|
|
3
|
+
SPAs wrap their route update in startViewTransition() from @nexub/react.
|
|
4
|
+
The old page fades out fast; the new one fades in and rises 8px on the
|
|
5
|
+
spatial spring. Name an element (view-transition-name) to morph it between
|
|
6
|
+
pages instead. Reduced motion: crossfade only. */
|
|
7
|
+
|
|
8
|
+
@view-transition { navigation: auto; }
|
|
9
|
+
|
|
10
|
+
@keyframes nx-vt-rise { from { translate: 0 8px; } }
|
|
11
|
+
|
|
12
|
+
::view-transition-old(root) {
|
|
13
|
+
animation: nx-fade-out var(--motion-spring-effects-fast-duration) var(--motion-spring-effects-fast-easing) both;
|
|
14
|
+
}
|
|
15
|
+
::view-transition-new(root) {
|
|
16
|
+
animation:
|
|
17
|
+
nx-fade-in var(--motion-spring-effects-default-duration) var(--motion-spring-effects-default-easing) both,
|
|
18
|
+
nx-vt-rise var(--motion-spring-spatial-default-duration) var(--motion-spring-spatial-default-easing) both;
|
|
19
|
+
}
|
|
20
|
+
::view-transition-group(*) {
|
|
21
|
+
animation-duration: var(--motion-spring-spatial-default-duration);
|
|
22
|
+
animation-timing-function: var(--motion-spring-spatial-default-easing);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@media (prefers-reduced-motion: reduce) {
|
|
26
|
+
::view-transition-new(root) {
|
|
27
|
+
animation: nx-fade-in var(--motion-spring-effects-default-duration) var(--motion-spring-effects-default-easing) both;
|
|
28
|
+
}
|
|
29
|
+
::view-transition-group(*) { animation-duration: 0s; }
|
|
30
|
+
}
|