astralkit 0.6.0 → 0.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.
@@ -799,7 +799,12 @@ body.ak-ew-resizing, body.ak-ew-resizing * {
799
799
  }
800
800
 
801
801
  /* ── Focus Ring Utility ── */
802
- .ak-focus-ring:focus-visible { outline: 2px solid var(--color-ak-border-focus, var(--color-ak-primary)); outline-offset: 2px; }
802
+ .ak-focus-ring:focus-visible {
803
+ /* Material-drivable: packs may restyle focus (neo-brutal = 3px dashed ink).
804
+ Default reproduces the house ring exactly. */
805
+ outline: var(--ak-focus-outline, 2px solid var(--color-ak-border-focus, var(--color-ak-primary)));
806
+ outline-offset: var(--ak-focus-offset, 2px);
807
+ }
803
808
 
804
809
  /* ── Price Fade Transition ── */
805
810
  .ak-price-fade { transition: opacity 0.15s ease-in-out; }
@@ -815,3 +820,309 @@ body.ak-ew-resizing, body.ak-ew-resizing * {
815
820
  @keyframes ak-slideIn { from { opacity: 0; } to { opacity: 1; } }
816
821
  @keyframes ak-fadeOut { from { opacity: 1; } to { opacity: 0; } }
817
822
  }
823
+
824
+ /* ── Typography roles (2026-08-02) ─────────────────────────────────
825
+ Five roles, each an authored family+weight pair driven by variables so a
826
+ font pairing re-tunes the whole hierarchy coherently (family swaps without
827
+ weight compensation always look wrong — different families have different
828
+ weight curves). Components consume ROLE classes, never literal
829
+ font-family/font-weight for themed text. Weight vars live outside the
830
+ --font-weight-* namespace on purpose (v4 would generate colliding font-*
831
+ utilities). Mono role = the existing font-ak-mono utility. */
832
+ .ak-font-heading {
833
+ font-family: var(--font-ak-heading, var(--font-ak-sans, Inter, sans-serif));
834
+ font-weight: var(--ak-weight-heading, 600);
835
+ }
836
+ .ak-font-body {
837
+ font-family: var(--font-ak-sans, Inter, sans-serif);
838
+ font-weight: var(--ak-weight-body, 400);
839
+ }
840
+ .ak-font-ui {
841
+ font-family: var(--font-ak-ui, var(--font-ak-sans, Inter, sans-serif));
842
+ font-weight: var(--ak-weight-ui, 500);
843
+ }
844
+ /* Emphasized UI — buttons, pressed/active controls, row titles. The library
845
+ calibration is semibold on every button and active state (conventions:
846
+ "titles are font-semibold"; forms references all carry font-semibold CTAs)
847
+ while quiet UI (menu rows, inputs, nav) stays at the ui weight. Pairings
848
+ override via --ak-weight-ui-strong. */
849
+ .ak-font-ui-strong {
850
+ font-family: var(--font-ak-ui, var(--font-ak-sans, Inter, sans-serif));
851
+ font-weight: var(--ak-weight-ui-strong, 600);
852
+ }
853
+ .ak-font-label {
854
+ font-family: var(--font-ak-label, var(--font-ak-sans, Inter, sans-serif));
855
+ font-weight: var(--ak-weight-label, 600);
856
+ letter-spacing: var(--ak-tracking-label, 0.02em);
857
+ }
858
+
859
+ /* ── Material recipes (2026-08-02) ──────────────────────────────
860
+ The consumption layer for material appearances (astralkit/presets
861
+ MATERIAL_APPEARANCES). Components carry these classes; appearance packs
862
+ fill the --ak-* slots. Every default reproduces the pre-material look,
863
+ so unthemed apps are pixel-identical. */
864
+ .ak-material-control {
865
+ box-shadow: var(--ak-control-shadow, none);
866
+ transition: box-shadow 0.15s ease, transform 0.15s ease;
867
+ }
868
+ .ak-material-control:hover {
869
+ transform: var(--ak-control-hover-transform, none);
870
+ box-shadow: var(--ak-control-shadow-hover, var(--ak-control-shadow, none));
871
+ }
872
+ .ak-material-control:active {
873
+ box-shadow: var(--ak-control-shadow-active, var(--ak-control-shadow, none));
874
+ }
875
+ .ak-material-surface {
876
+ position: relative;
877
+ /* Two-layer background: an optional SHEEN (falls off from the single
878
+ top-left light source) over the fill. Defaults collapse to plain fill. */
879
+ background: var(--ak-surface-sheen, none), var(--ak-surface-fill, var(--color-ak-surface));
880
+ border: var(--ak-surface-border-w, 1px) solid var(--ak-surface-border-color, var(--color-ak-border));
881
+ box-shadow: var(--ak-surface-shadow, none);
882
+ -webkit-backdrop-filter: var(--ak-surface-blur, none);
883
+ backdrop-filter: var(--ak-surface-blur, none);
884
+ }
885
+ /* Directional rim — a gradient border ring (hot toward the light source,
886
+ dying opposite) via padding + mask-composite. Invisible until a pack sets
887
+ --ak-surface-rim. */
888
+ .ak-material-surface::before {
889
+ content: "";
890
+ position: absolute;
891
+ inset: 0;
892
+ border-radius: inherit;
893
+ padding: var(--ak-surface-rim-w, 1.5px);
894
+ background: var(--ak-surface-rim, none);
895
+ -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
896
+ -webkit-mask-composite: xor;
897
+ mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
898
+ mask-composite: exclude;
899
+ pointer-events: none;
900
+ }
901
+ /* Depth ladder: nested material surfaces step their tint up one level so
902
+ stacked glass reads as stacked panes. Packs opt in via the -2 slot. */
903
+ .ak-material-surface .ak-material-surface {
904
+ background: var(--ak-surface-sheen-2, var(--ak-surface-sheen, none)),
905
+ var(--ak-surface-fill-2, var(--ak-surface-fill, var(--color-ak-surface-2)));
906
+ box-shadow: var(--ak-surface-shadow-2, var(--ak-surface-shadow, none));
907
+ }
908
+
909
+ /* ── Small-control material parts (batch 2) ─────────────────────────────
910
+ Glass Law 4: glass is for surfaces — active parts stay flat solid. The
911
+ knob (switch), thumb (slider) and track are material-slotted so the
912
+ Appearance dial re-materializes running form controls. Position is done
913
+ with the CSS `translate` property in components; recipes only ever touch
914
+ `transform`, so squish/tilt compose with position instead of clobbering it. */
915
+ .ak-control-knob {
916
+ background: var(--ak-knob-fill, #ffffff);
917
+ box-shadow: var(--ak-knob-shadow, 0 1px 3px rgba(0, 0, 0, 0.25));
918
+ transition:
919
+ translate 200ms var(--ak-control-spring, cubic-bezier(0.4, 0, 0.2, 1)),
920
+ transform 200ms var(--ak-control-spring, cubic-bezier(0.4, 0, 0.2, 1)),
921
+ background-color 150ms ease;
922
+ }
923
+ [data-state="checked"] > .ak-control-knob {
924
+ background: var(--ak-knob-fill-checked, var(--ak-knob-fill, #ffffff));
925
+ }
926
+ /* The squish: origin follows the travel direction so the stretch reads as
927
+ the knob leaning into the push. */
928
+ [data-ak-component="switch"]:active .ak-control-knob {
929
+ transform: var(--ak-knob-press, scaleX(1.12));
930
+ transform-origin: left center;
931
+ }
932
+ [data-ak-component="switch"][data-state="checked"]:active .ak-control-knob {
933
+ transform-origin: right center;
934
+ }
935
+ .ak-control-thumb {
936
+ background: var(--ak-thumb-fill, #ffffff);
937
+ border-radius: var(--ak-thumb-radius, 9999px);
938
+ border: var(--ak-thumb-border-w, 0px) solid var(--ak-thumb-border-color, transparent);
939
+ box-shadow: var(--ak-thumb-shadow, 0 1px 4px rgba(0, 0, 0, 0.3));
940
+ transition: transform 180ms var(--ak-control-spring, cubic-bezier(0.4, 0, 0.2, 1)),
941
+ box-shadow 180ms ease;
942
+ }
943
+ .ak-control-thumb:hover {
944
+ transform: var(--ak-thumb-hover-transform, scale(1.1));
945
+ }
946
+ .ak-control-thumb:active {
947
+ transform: var(--ak-thumb-active-transform, scale(0.96));
948
+ }
949
+ /* Track border renders as an INSET RING, not a real border — layout never
950
+ shifts, so knob travel constants hold across every material. */
951
+ .ak-control-track {
952
+ box-shadow:
953
+ inset 0 0 0 var(--ak-track-border-w, 0px) var(--ak-track-border-color, transparent),
954
+ var(--ak-track-shadow, 0 0 0 0 transparent);
955
+ }
956
+ @media (prefers-reduced-motion: reduce) {
957
+ .ak-control-knob,
958
+ .ak-control-thumb {
959
+ transition: none;
960
+ }
961
+ }
962
+
963
+ /* ── Themed scrollbar (2026-08-03) ──────────────────────────────────────
964
+ A thin, ALWAYS-VISIBLE scrollbar on the token layer — thumb follows the
965
+ theme (border-strong, text-muted on hover), track transparent. Visible
966
+ at rest on purpose: hidden-until-hover scrollbars fail discoverability.
967
+ Firefox via scrollbar-*, WebKit via the pseudo-elements. */
968
+ .ak-scrollbar {
969
+ scrollbar-width: thin;
970
+ scrollbar-color: var(--color-ak-border-strong) transparent;
971
+ }
972
+ .ak-scrollbar::-webkit-scrollbar {
973
+ width: 8px;
974
+ height: 8px;
975
+ }
976
+ .ak-scrollbar::-webkit-scrollbar-thumb {
977
+ background: var(--color-ak-border-strong);
978
+ border-radius: 9999px;
979
+ }
980
+ .ak-scrollbar::-webkit-scrollbar-thumb:hover {
981
+ background: var(--color-ak-text-muted);
982
+ }
983
+ .ak-scrollbar::-webkit-scrollbar-track {
984
+ background: transparent;
985
+ }
986
+
987
+ /* ── Quiet-hover bar (2026-08-03) ───────────────────────────────────────
988
+ The hover treatment for quiet controls and text links: a bar under the
989
+ WHOLE control (::after), not text-decoration — underlines don't propagate
990
+ into flex children, so any link with an icon shows a cut-off underline.
991
+ Materials fill the slots (brutal: 3px ink bar offset 4px = the backface's
992
+ bottom edge). Defaults are 0/invisible; a component can opt into a
993
+ default bar via the -default fallback vars (the link button does), which
994
+ materials still override via the primary vars. */
995
+ .ak-quiet-hover {
996
+ position: relative;
997
+ }
998
+ .ak-quiet-hover::after {
999
+ content: "";
1000
+ position: absolute;
1001
+ inset-inline: 0;
1002
+ bottom: calc(-1 * var(--ak-quiet-hover-offset, var(--ak-quiet-hover-offset-default, 0px)));
1003
+ height: var(--ak-quiet-hover-thickness, var(--ak-quiet-hover-thickness-default, 0px));
1004
+ background: var(--ak-quiet-hover-color, currentColor);
1005
+ opacity: 0;
1006
+ transition: opacity 120ms ease;
1007
+ pointer-events: none;
1008
+ }
1009
+ .ak-quiet-hover:hover::after,
1010
+ .ak-quiet-hover:focus-visible::after {
1011
+ opacity: 1;
1012
+ }
1013
+ @media (prefers-reduced-motion: reduce) {
1014
+ .ak-quiet-hover::after {
1015
+ transition: none;
1016
+ }
1017
+ }
1018
+
1019
+ /* ── Overlay motion (core batch 4) ──────────────────────────────────────
1020
+ In/out animations for portalled overlays, driven by Radix data-state.
1021
+ Plain CSS so both Tailwind pipelines get them. Dialogs center with
1022
+ inset-0 + m-auto (NOT translate) so the pop transform never fights
1023
+ positioning; sheets own their transform entirely. */
1024
+ @keyframes ak-overlay-in { from { opacity: 0; } to { opacity: 1; } }
1025
+ @keyframes ak-overlay-out { from { opacity: 1; } to { opacity: 0; } }
1026
+ @keyframes ak-pop-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }
1027
+ @keyframes ak-pop-out { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.96); } }
1028
+ @keyframes ak-slide-in-right { from { transform: translateX(100%); } to { transform: translateX(0); } }
1029
+ @keyframes ak-slide-out-right { from { transform: translateX(0); } to { transform: translateX(100%); } }
1030
+ @keyframes ak-slide-in-left { from { transform: translateX(-100%); } to { transform: translateX(0); } }
1031
+ @keyframes ak-slide-out-left { from { transform: translateX(0); } to { transform: translateX(-100%); } }
1032
+ @keyframes ak-slide-in-bottom { from { transform: translateY(100%); } to { transform: translateY(0); } }
1033
+ @keyframes ak-slide-out-bottom { from { transform: translateY(0); } to { transform: translateY(100%); } }
1034
+ @keyframes ak-slide-in-top { from { transform: translateY(-100%); } to { transform: translateY(0); } }
1035
+ @keyframes ak-slide-out-top { from { transform: translateY(0); } to { transform: translateY(-100%); } }
1036
+ .ak-overlay-anim[data-state="open"] { animation: ak-overlay-in 150ms ease; }
1037
+ .ak-overlay-anim[data-state="closed"] { animation: ak-overlay-out 130ms ease forwards; }
1038
+ .ak-pop-anim[data-state="open"],
1039
+ .ak-pop-anim[data-state="delayed-open"] { animation: ak-pop-in 160ms cubic-bezier(0.16, 1, 0.3, 1); }
1040
+ .ak-pop-anim[data-state="closed"] { animation: ak-pop-out 120ms ease forwards; }
1041
+ .ak-sheet-anim[data-state="open"][data-side="right"] { animation: ak-slide-in-right 260ms cubic-bezier(0.16, 1, 0.3, 1); }
1042
+ .ak-sheet-anim[data-state="closed"][data-side="right"] { animation: ak-slide-out-right 200ms ease forwards; }
1043
+ .ak-sheet-anim[data-state="open"][data-side="left"] { animation: ak-slide-in-left 260ms cubic-bezier(0.16, 1, 0.3, 1); }
1044
+ .ak-sheet-anim[data-state="closed"][data-side="left"] { animation: ak-slide-out-left 200ms ease forwards; }
1045
+ .ak-sheet-anim[data-state="open"][data-side="bottom"] { animation: ak-slide-in-bottom 260ms cubic-bezier(0.16, 1, 0.3, 1); }
1046
+ .ak-sheet-anim[data-state="closed"][data-side="bottom"] { animation: ak-slide-out-bottom 200ms ease forwards; }
1047
+ .ak-sheet-anim[data-state="open"][data-side="top"] { animation: ak-slide-in-top 260ms cubic-bezier(0.16, 1, 0.3, 1); }
1048
+ .ak-sheet-anim[data-state="closed"][data-side="top"] { animation: ak-slide-out-top 200ms ease forwards; }
1049
+ /* Height-reveal for Radix Accordion/Collapsible content. */
1050
+ @keyframes ak-accordion-down { from { height: 0; } to { height: var(--radix-accordion-content-height); } }
1051
+ @keyframes ak-accordion-up { from { height: var(--radix-accordion-content-height); } to { height: 0; } }
1052
+ @keyframes ak-collapsible-down { from { height: 0; } to { height: var(--radix-collapsible-content-height); } }
1053
+ @keyframes ak-collapsible-up { from { height: var(--radix-collapsible-content-height); } to { height: 0; } }
1054
+ .ak-accordion-anim[data-state="open"] { animation: ak-accordion-down 200ms cubic-bezier(0.16, 1, 0.3, 1); }
1055
+ .ak-accordion-anim[data-state="closed"] { animation: ak-accordion-up 180ms ease forwards; }
1056
+ .ak-collapsible-anim[data-state="open"] { animation: ak-collapsible-down 200ms cubic-bezier(0.16, 1, 0.3, 1); }
1057
+ .ak-collapsible-anim[data-state="closed"] { animation: ak-collapsible-up 180ms ease forwards; }
1058
+ @media (prefers-reduced-motion: reduce) {
1059
+ .ak-overlay-anim,
1060
+ .ak-pop-anim,
1061
+ .ak-sheet-anim,
1062
+ .ak-accordion-anim,
1063
+ .ak-collapsible-anim {
1064
+ animation-duration: 0.01ms !important;
1065
+ }
1066
+ }
1067
+
1068
+ /* ── Button group (core) ────────────────────────────────────────────────
1069
+ Container-aware styling for attached button groups (the CORE-STANDARD
1070
+ pattern: plain CSS attribute selectors, works in both Tailwind pipelines).
1071
+ Inner corners collapse, outer corners keep the radius tokens, adjacent
1072
+ borders overlap by 1px, and the focused member wins the z-order. */
1073
+ /* Any direct child participates in the corner system — buttons today,
1074
+ inputs/selects/ButtonGroupText when those cores land. Separators opt out. */
1075
+ .ak-button-group {
1076
+ display: inline-flex;
1077
+ align-items: stretch;
1078
+ }
1079
+ .ak-button-group > :focus-visible {
1080
+ z-index: 1;
1081
+ }
1082
+ .ak-button-group[data-ak-orientation="vertical"] {
1083
+ flex-direction: column;
1084
+ }
1085
+ /* Fusing applies only to a leaf group — a group of groups is a toolbar
1086
+ (below) and its sub-groups keep their own corners. */
1087
+ .ak-button-group:not(:has(> .ak-button-group)) > * {
1088
+ position: relative;
1089
+ border-radius: 0;
1090
+ }
1091
+ .ak-button-group[data-ak-orientation="horizontal"]:not(:has(> .ak-button-group)) > * + :not(.ak-button-group-separator) {
1092
+ margin-left: -1px;
1093
+ }
1094
+ .ak-button-group[data-ak-orientation="horizontal"]:not(:has(> .ak-button-group)) > .ak-button-group-separator + * {
1095
+ margin-left: 0;
1096
+ }
1097
+ .ak-button-group[data-ak-orientation="horizontal"]:not(:has(> .ak-button-group)) > :first-child {
1098
+ border-top-left-radius: var(--radius-ak-md);
1099
+ border-bottom-left-radius: var(--radius-ak-md);
1100
+ }
1101
+ .ak-button-group[data-ak-orientation="horizontal"]:not(:has(> .ak-button-group)) > :last-child {
1102
+ border-top-right-radius: var(--radius-ak-md);
1103
+ border-bottom-right-radius: var(--radius-ak-md);
1104
+ }
1105
+ .ak-button-group[data-ak-orientation="vertical"]:not(:has(> .ak-button-group)) > * + :not(.ak-button-group-separator) {
1106
+ margin-top: -1px;
1107
+ }
1108
+ .ak-button-group[data-ak-orientation="vertical"]:not(:has(> .ak-button-group)) > .ak-button-group-separator + * {
1109
+ margin-top: 0;
1110
+ }
1111
+ .ak-button-group[data-ak-orientation="vertical"]:not(:has(> .ak-button-group)) > :first-child {
1112
+ border-top-left-radius: var(--radius-ak-md);
1113
+ border-top-right-radius: var(--radius-ak-md);
1114
+ }
1115
+ .ak-button-group[data-ak-orientation="vertical"]:not(:has(> .ak-button-group)) > :last-child {
1116
+ border-bottom-left-radius: var(--radius-ak-md);
1117
+ border-bottom-right-radius: var(--radius-ak-md);
1118
+ }
1119
+ /* The seam flips with the group's axis. */
1120
+ .ak-button-group[data-ak-orientation="vertical"] > .ak-button-group-separator {
1121
+ width: auto;
1122
+ height: 1px;
1123
+ align-self: stretch;
1124
+ }
1125
+ /* A group of groups is a toolbar: sub-groups space out instead of fusing. */
1126
+ .ak-button-group:has(> .ak-button-group) {
1127
+ gap: var(--spacing-ak-1, 0.5rem);
1128
+ }
@@ -0,0 +1,12 @@
1
+ export type AppearancePresetId = 'clean' | 'soft-mesh' | 'neon-edge' | 'cinematic-backlight' | 'circuit-spark' | 'portfolio-grain';
2
+ type AppearanceVariableName = `--ak-effect-${string}` | `--gradient-ak-${string}`;
3
+ export interface AppearancePreset {
4
+ id: AppearancePresetId;
5
+ name: string;
6
+ description: string;
7
+ variables: Partial<Record<AppearanceVariableName, string>>;
8
+ }
9
+ export declare const DEFAULT_APPEARANCE_PRESET_ID: AppearancePresetId;
10
+ export declare const APPEARANCE_PRESETS: AppearancePreset[];
11
+ export declare function appearancePresetToVariables(preset: AppearancePreset): Partial<Record<AppearanceVariableName, string>>;
12
+ export {};
@@ -0,0 +1,102 @@
1
+ export const DEFAULT_APPEARANCE_PRESET_ID = 'clean';
2
+ export const APPEARANCE_PRESETS = [
3
+ {
4
+ id: 'clean',
5
+ name: 'Clean',
6
+ description: 'No ambient atmosphere. Keeps components border-led and quiet.',
7
+ variables: {
8
+ '--ak-effect-mesh-opacity': '0',
9
+ '--ak-effect-grain-opacity': '0',
10
+ '--ak-effect-grid-opacity': '0',
11
+ '--ak-effect-backlight-opacity': '0',
12
+ '--ak-effect-vignette-opacity': '0',
13
+ '--ak-effect-border-opacity': '0.55',
14
+ '--ak-effect-glow-opacity': '0.22',
15
+ '--ak-effect-border-speed': '8s',
16
+ '--ak-effect-spark-size': '34%',
17
+ },
18
+ },
19
+ {
20
+ id: 'soft-mesh',
21
+ name: 'Soft Mesh',
22
+ description: 'A calm radial mesh with light grain for premium product surfaces.',
23
+ variables: {
24
+ '--ak-effect-mesh-opacity': '0.18',
25
+ '--ak-effect-grain-opacity': '0.018',
26
+ '--ak-effect-grid-opacity': '0',
27
+ '--ak-effect-backlight-opacity': '0.08',
28
+ '--ak-effect-vignette-opacity': '0.035',
29
+ '--ak-effect-border-opacity': '0.62',
30
+ '--ak-effect-glow-opacity': '0.32',
31
+ '--ak-effect-border-speed': '9s',
32
+ '--ak-effect-spark-size': '32%',
33
+ },
34
+ },
35
+ {
36
+ id: 'neon-edge',
37
+ name: 'Neon Edge',
38
+ description: 'Restrained glow and animated edge energy for dark premium demos.',
39
+ variables: {
40
+ '--ak-effect-mesh-opacity': '0.12',
41
+ '--ak-effect-grain-opacity': '0.014',
42
+ '--ak-effect-grid-opacity': '0',
43
+ '--ak-effect-backlight-opacity': '0.14',
44
+ '--ak-effect-vignette-opacity': '0.06',
45
+ '--ak-effect-border-opacity': '0.92',
46
+ '--ak-effect-glow-opacity': '0.44',
47
+ '--ak-effect-border-speed': '6s',
48
+ '--ak-effect-spark-size': '24%',
49
+ },
50
+ },
51
+ {
52
+ id: 'cinematic-backlight',
53
+ name: 'Cinematic',
54
+ description: 'Large soft backlights and a faint vignette for hero-style staging.',
55
+ variables: {
56
+ '--ak-effect-mesh-opacity': '0.24',
57
+ '--ak-effect-grain-opacity': '0.022',
58
+ '--ak-effect-grid-opacity': '0',
59
+ '--ak-effect-backlight-opacity': '0.28',
60
+ '--ak-effect-vignette-opacity': '0.12',
61
+ '--ak-effect-border-opacity': '0.7',
62
+ '--ak-effect-glow-opacity': '0.5',
63
+ '--ak-effect-border-speed': '10s',
64
+ '--ak-effect-spark-size': '36%',
65
+ },
66
+ },
67
+ {
68
+ id: 'circuit-spark',
69
+ name: 'Circuit Spark',
70
+ description: 'Fine grid texture with faster comet borders for systems UI.',
71
+ variables: {
72
+ '--ak-effect-mesh-opacity': '0.15',
73
+ '--ak-effect-grain-opacity': '0.016',
74
+ '--ak-effect-grid-opacity': '0.12',
75
+ '--ak-effect-backlight-opacity': '0.16',
76
+ '--ak-effect-vignette-opacity': '0.08',
77
+ '--ak-effect-border-opacity': '1',
78
+ '--ak-effect-glow-opacity': '0.48',
79
+ '--ak-effect-border-speed': '4.8s',
80
+ '--ak-effect-spark-size': '18%',
81
+ },
82
+ },
83
+ {
84
+ id: 'portfolio-grain',
85
+ name: 'Portfolio Grain',
86
+ description: 'Editorial grain, muted backlight, and quiet texture.',
87
+ variables: {
88
+ '--ak-effect-mesh-opacity': '0.1',
89
+ '--ak-effect-grain-opacity': '0.055',
90
+ '--ak-effect-grid-opacity': '0',
91
+ '--ak-effect-backlight-opacity': '0.07',
92
+ '--ak-effect-vignette-opacity': '0.1',
93
+ '--ak-effect-border-opacity': '0.5',
94
+ '--ak-effect-glow-opacity': '0.28',
95
+ '--ak-effect-border-speed': '12s',
96
+ '--ak-effect-spark-size': '40%',
97
+ },
98
+ },
99
+ ];
100
+ export function appearancePresetToVariables(preset) {
101
+ return preset.variables;
102
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Curated chart palettes — authored 6-color series sets driving the SDK's
3
+ * --color-ak-chart-1..6 slots at runtime. The default (Signal) is the SDK's
4
+ * Tableau-derived colorblind-safe set; the others are authored alternatives,
5
+ * sequenced so adjacent series stay distinguishable. Independent of the UI
6
+ * palette on purpose (the shadcn insight worth keeping: chart hues are a
7
+ * separate design decision from brand hues).
8
+ */
9
+ export type ChartPaletteId = 'signal' | 'oceanic' | 'ember' | 'prism' | 'ink';
10
+ type ChartVariableName = `--color-ak-chart-${1 | 2 | 3 | 4 | 5 | 6}`;
11
+ export interface ChartPalette {
12
+ id: ChartPaletteId;
13
+ name: string;
14
+ description: string;
15
+ variables: Record<ChartVariableName, string>;
16
+ darkVariables?: Record<ChartVariableName, string>;
17
+ }
18
+ export declare const DEFAULT_CHART_PALETTE_ID: ChartPaletteId;
19
+ export declare const CHART_PALETTES: ChartPalette[];
20
+ export declare function chartPaletteToVariables(palette: ChartPalette, opts?: { mode?: 'light' | 'dark' }): Record<string, string>;
21
+ export type RadiusOptionId = 'none' | 'small' | 'default' | 'large' | 'xl';
22
+ export interface RadiusOption {
23
+ id: RadiusOptionId;
24
+ name: string;
25
+ value: string;
26
+ }
27
+ export declare const DEFAULT_RADIUS_OPTION_ID: RadiusOptionId;
28
+ export declare const RADIUS_OPTIONS: RadiusOption[];
29
+ export {};
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Curated chart palettes — authored 6-color series sets driving the SDK's
3
+ * --color-ak-chart-1..6 slots at runtime. The default (Signal) is the SDK's
4
+ * Tableau-derived colorblind-safe set; the others are authored alternatives,
5
+ * sequenced so adjacent series stay distinguishable. Independent of the UI
6
+ * palette on purpose (the shadcn insight worth keeping: chart hues are a
7
+ * separate design decision from brand hues).
8
+ */
9
+ export const DEFAULT_CHART_PALETTE_ID = 'signal';
10
+ export const CHART_PALETTES = [
11
+ {
12
+ id: 'signal',
13
+ name: 'Default',
14
+ description: 'The SDK default — Tableau-derived, colorblind-safe.',
15
+ variables: {
16
+ '--color-ak-chart-1': '#4e79a7',
17
+ '--color-ak-chart-2': '#f28e2c',
18
+ '--color-ak-chart-3': '#e15759',
19
+ '--color-ak-chart-4': '#76b7b2',
20
+ '--color-ak-chart-5': '#59a14f',
21
+ '--color-ak-chart-6': '#edc949',
22
+ },
23
+ },
24
+ {
25
+ id: 'oceanic',
26
+ name: 'Blues',
27
+ description: 'Blues and teals — calm, financial, product dashboards.',
28
+ variables: {
29
+ '--color-ak-chart-1': '#2563eb',
30
+ '--color-ak-chart-2': '#0891b2',
31
+ '--color-ak-chart-3': '#14b8a6',
32
+ '--color-ak-chart-4': '#6366f1',
33
+ '--color-ak-chart-5': '#0ea5e9',
34
+ '--color-ak-chart-6': '#1e40af',
35
+ },
36
+ },
37
+ {
38
+ id: 'ember',
39
+ name: 'Warm',
40
+ description: 'Warm ambers and reds — energy, alerts, intensity scales.',
41
+ variables: {
42
+ '--color-ak-chart-1': '#dc2626',
43
+ '--color-ak-chart-2': '#f59e0b',
44
+ '--color-ak-chart-3': '#ea580c',
45
+ '--color-ak-chart-4': '#eab308',
46
+ '--color-ak-chart-5': '#9a3412',
47
+ '--color-ak-chart-6': '#fbbf24',
48
+ },
49
+ },
50
+ {
51
+ id: 'prism',
52
+ name: 'Vivid',
53
+ description: 'Saturated multi-hue — marketing analytics, category splits.',
54
+ variables: {
55
+ '--color-ak-chart-1': '#8b5cf6',
56
+ '--color-ak-chart-2': '#ec4899',
57
+ '--color-ak-chart-3': '#06b6d4',
58
+ '--color-ak-chart-4': '#22c55e',
59
+ '--color-ak-chart-5': '#f59e0b',
60
+ '--color-ak-chart-6': '#6366f1',
61
+ },
62
+ },
63
+ {
64
+ id: 'ink',
65
+ name: 'Mono',
66
+ description: 'Grayscale ramp — editorial, print-adjacent, monochrome brands.',
67
+ variables: {
68
+ '--color-ak-chart-1': '#171717',
69
+ '--color-ak-chart-2': '#737373',
70
+ '--color-ak-chart-3': '#a3a3a3',
71
+ '--color-ak-chart-4': '#404040',
72
+ '--color-ak-chart-5': '#d4d4d4',
73
+ '--color-ak-chart-6': '#525252',
74
+ },
75
+ // Grayscale must INVERT in dark mode or the ramp disappears into the bg.
76
+ darkVariables: {
77
+ '--color-ak-chart-1': '#e5e5e5',
78
+ '--color-ak-chart-2': '#8a8a8a',
79
+ '--color-ak-chart-3': '#525252',
80
+ '--color-ak-chart-4': '#bdbdbd',
81
+ '--color-ak-chart-5': '#404040',
82
+ '--color-ak-chart-6': '#a3a3a3',
83
+ },
84
+ },
85
+ ];
86
+ export function chartPaletteToVariables(palette, opts) {
87
+ const mode = (opts && opts.mode) || 'light';
88
+ if (mode === 'dark' && palette.darkVariables)
89
+ return { ...palette.darkVariables };
90
+ return { ...palette.variables };
91
+ }
92
+ export const DEFAULT_RADIUS_OPTION_ID = 'default';
93
+ export const RADIUS_OPTIONS = [
94
+ { id: 'none', name: 'Square', value: '0rem' },
95
+ { id: 'small', name: 'Small', value: '0.25rem' },
96
+ { id: 'default', name: 'Default', value: '0.5rem' },
97
+ { id: 'large', name: 'Large', value: '0.875rem' },
98
+ { id: 'xl', name: 'Extra Round', value: '9999rem' },
99
+ ];
@@ -0,0 +1,31 @@
1
+ export interface DialPreset<Id extends string = string> {
2
+ id: Id
3
+ name: string
4
+ description: string
5
+ variables: Record<string, string>
6
+ darkVariables?: Record<string, string>
7
+ swatch?: string
8
+ }
9
+
10
+ export interface DialModeOptions {
11
+ mode?: 'light' | 'dark'
12
+ }
13
+
14
+ export type BaseColorId = 'neutral' | 'stone' | 'zinc' | 'mauve' | 'olive' | 'mist' | 'taupe'
15
+ export declare const DEFAULT_BASE_COLOR_ID: BaseColorId
16
+ export declare const BASE_COLORS: DialPreset<BaseColorId>[]
17
+ export declare function baseColorToVariables(base: DialPreset<BaseColorId>, opts?: DialModeOptions): Record<string, string>
18
+
19
+ export type AccentThemeId =
20
+ | 'default' | 'blue' | 'indigo' | 'violet' | 'purple' | 'pink' | 'rose'
21
+ | 'red' | 'orange' | 'amber' | 'emerald' | 'teal' | 'cyan'
22
+ export declare const DEFAULT_ACCENT_THEME_ID: AccentThemeId
23
+ export declare const ACCENT_THEMES: DialPreset<AccentThemeId>[]
24
+ export declare function accentThemeToVariables(theme: DialPreset<AccentThemeId>, opts?: DialModeOptions): Record<string, string>
25
+
26
+ export declare function radiusToVariables(value: number | string): Record<string, string>
27
+ export type TypeScaleId = 'default' | 'major-third' | 'perfect-fourth' | 'augmented-fourth' | 'perfect-fifth' | 'golden'
28
+ export declare const DEFAULT_TYPE_SCALE_ID: TypeScaleId
29
+ export declare const TYPE_SCALES: DialPreset<TypeScaleId>[]
30
+ export declare function typeScaleToVariables(scale: DialPreset<TypeScaleId>): Record<string, string>
31
+