@cosxai/ui 0.1.0 → 0.2.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosxai/ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.2",
4
4
  "description": "COSX design system — React 19 component primitives shared across product-meta and other consumers",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -23,6 +23,9 @@
23
23
  "access": "public",
24
24
  "registry": "https://registry.npmjs.org/"
25
25
  },
26
+ "scripts": {
27
+ "typecheck": "tsc --noEmit"
28
+ },
26
29
  "peerDependencies": {
27
30
  "react": "^19.0.0",
28
31
  "react-dom": "^19.0.0"
@@ -31,8 +34,5 @@
31
34
  "@types/react": "^19.0.0",
32
35
  "@types/react-dom": "^19.0.0",
33
36
  "typescript": "^5.6.0"
34
- },
35
- "scripts": {
36
- "typecheck": "tsc --noEmit"
37
37
  }
38
- }
38
+ }
package/src/index.ts CHANGED
@@ -21,6 +21,5 @@ export * from "./neobrutalism";
21
21
  export * from "./ambient";
22
22
  export * from "./terminal";
23
23
  export * from "./bento";
24
- export * from "./frutiger";
25
24
  export * from "./riso";
26
25
  export * from "./sketch";
@@ -4,8 +4,22 @@ import { cn } from "../lib/cn";
4
4
  // Styled text input with optional label + error + helper text.
5
5
  // Three states: idle / focused / invalid. Focus ring + invalid border
6
6
  // both via :focus-visible / aria-invalid CSS — no JS branching.
7
+ //
8
+ // Optional `prefix` / `suffix` slots render INSIDE the bordered
9
+ // field, separated from the input by a subtle hairline divider.
10
+ // Common patterns:
11
+ // <Input prefix="https://" …/> → `https://[ acme ]`
12
+ // <Input suffix=".cosx.dev" …/> → `[ acme ].cosx.dev`
13
+ // Both can be set together. Prefer plain strings; ReactNode is
14
+ // supported (e.g. icons) but inherits the muted helper colour.
7
15
 
8
- export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size"> {
16
+ // We Omit "size" (HTMLInputElement's number-of-visible-chars attr;
17
+ // conflicts with our absent layout-size prop and is rarely needed in
18
+ // CSS-styled inputs) AND "prefix" (HTML's RDFa metadata attr —
19
+ // taking that prop name here lets us expose an addon slot with the
20
+ // most intuitive name; consumers needing the rare RDFa attr can
21
+ // drop down to a native <input>).
22
+ export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "prefix"> {
9
23
  label?: ReactNode;
10
24
  helper?: ReactNode;
11
25
  // Validation error message. When set, the input renders an
@@ -14,18 +28,77 @@ export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>,
14
28
  error?: string | null;
15
29
  // Width preset — full, fixed, or auto (default full).
16
30
  fit?: "full" | "auto";
31
+ // Inline addons rendered inside the bordered field. Use for
32
+ // protocol prefixes ("https://"), domain suffixes (".cosx.dev"),
33
+ // currency symbols, unit labels, search icons, etc.
34
+ prefix?: ReactNode;
35
+ suffix?: ReactNode;
17
36
  }
18
37
 
38
+ const ADDON_STYLE = {
39
+ display: "inline-flex",
40
+ alignItems: "center",
41
+ padding: "0 12px",
42
+ font: "400 13px/1 var(--ck-font-mono, var(--ck-font-sans))",
43
+ color: "var(--ck-text-secondary)",
44
+ // `--ck-bg-muted` is the canonical "slightly recessed slab"
45
+ // background — gives the addon visible separation from the input
46
+ // (`--ck-bg-surface`) without needing a hard divider line. The
47
+ // bg shift carries across all chromes; tokens.css defines a
48
+ // muted tone per chrome (light + dark).
49
+ background: "var(--ck-bg-muted)",
50
+ whiteSpace: "nowrap" as const,
51
+ userSelect: "none" as const,
52
+ };
53
+
19
54
  export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
20
- { label, helper, error, fit = "full", className, id, ...rest },
55
+ { label, helper, error, fit = "full", prefix, suffix, className, id, style, ...rest },
21
56
  ref,
22
57
  ) {
23
58
  const autoId = useId();
24
59
  const inputId = id ?? autoId;
60
+ const hasAddon = prefix != null || suffix != null;
61
+
62
+ // Border / radius live on the WRAPPER when there's an addon so the
63
+ // addon + input share one continuous frame. Otherwise the input
64
+ // itself carries the border (preserves the prior visual contract).
65
+ const fieldBorder = `1px solid ${error ? "var(--ck-critical)" : "var(--ck-border-strong)"}`;
66
+ const fieldRadius = "var(--ck-radius-sm)";
67
+
68
+ const inputEl = (
69
+ <input
70
+ ref={ref}
71
+ id={inputId}
72
+ aria-invalid={error ? true : undefined}
73
+ aria-describedby={(helper || error) ? `${inputId}-helper` : undefined}
74
+ {...rest}
75
+ className={cn("ck-input", hasAddon ? "ck-input--with-addon" : undefined)}
76
+ style={{
77
+ flex: hasAddon ? "1 1 auto" : undefined,
78
+ minWidth: 0,
79
+ height: hasAddon ? "100%" : 36,
80
+ padding: "0 12px",
81
+ font: "400 13px/1 var(--ck-font-sans)",
82
+ background: "var(--ck-bg-surface)",
83
+ color: "var(--ck-text-primary)",
84
+ border: hasAddon ? "none" : fieldBorder,
85
+ borderRadius: hasAddon ? 0 : fieldRadius,
86
+ outline: "none",
87
+ transition: "border-color var(--ck-dur-fast) var(--ck-ease)",
88
+ ...(style ?? {}),
89
+ }}
90
+ />
91
+ );
92
+
25
93
  return (
26
94
  <div
27
95
  className={cn("ck-input-field", className)}
28
- style={{ display: "flex", flexDirection: "column", gap: 6, width: fit === "full" ? "100%" : undefined }}
96
+ style={{
97
+ display: "flex",
98
+ flexDirection: "column",
99
+ gap: 6,
100
+ width: fit === "full" ? "100%" : undefined,
101
+ }}
29
102
  >
30
103
  {label && (
31
104
  <label
@@ -36,25 +109,34 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
36
109
  {label}
37
110
  </label>
38
111
  )}
39
- <input
40
- ref={ref}
41
- id={inputId}
42
- aria-invalid={error ? true : undefined}
43
- aria-describedby={(helper || error) ? `${inputId}-helper` : undefined}
44
- {...rest}
45
- className="ck-input"
46
- style={{
47
- height: 34,
48
- padding: "0 12px",
49
- font: "400 13px/1 var(--ck-font-sans)",
50
- background: "var(--ck-bg-surface)",
51
- color: "var(--ck-text-primary)",
52
- border: `1px solid ${error ? "var(--ck-critical)" : "var(--ck-border-strong)"}`,
53
- borderRadius: "var(--ck-radius-sm)",
54
- outline: "none",
55
- transition: "border-color var(--ck-dur-fast) var(--ck-ease)",
56
- }}
57
- />
112
+ {hasAddon ? (
113
+ <div
114
+ className="ck-input-addon-wrap"
115
+ style={{
116
+ display: "flex",
117
+ alignItems: "stretch",
118
+ height: 36,
119
+ background: "var(--ck-bg-surface)",
120
+ border: fieldBorder,
121
+ borderRadius: fieldRadius,
122
+ overflow: "hidden",
123
+ }}
124
+ >
125
+ {prefix != null && (
126
+ <span className="ck-input-addon ck-input-addon--prefix" style={ADDON_STYLE}>
127
+ {prefix}
128
+ </span>
129
+ )}
130
+ {inputEl}
131
+ {suffix != null && (
132
+ <span className="ck-input-addon ck-input-addon--suffix" style={ADDON_STYLE}>
133
+ {suffix}
134
+ </span>
135
+ )}
136
+ </div>
137
+ ) : (
138
+ inputEl
139
+ )}
58
140
  {(helper || error) && (
59
141
  <div
60
142
  id={`${inputId}-helper`}
@@ -13,7 +13,6 @@
13
13
  @import "./chrome-swiss.css";
14
14
  @import "./chrome-terminal.css";
15
15
  @import "./chrome-bento.css";
16
- @import "./chrome-frutiger.css";
17
16
  @import "./chrome-riso.css";
18
17
  @import "./chrome-sketch.css";
19
18
 
@@ -683,100 +683,6 @@ html[data-ck-theme="dark"][data-ck-chrome="bento"] {
683
683
  top-highlight on every elevated element, soft blue-
684
684
  tinted shadows + outer glow on hover. Image-driven,
685
685
  optimistic, intentionally pretty. */
686
- html[data-ck-chrome="frutiger"] {
687
- /* Sky gradient as canvas — the whole page sits on it. */
688
- --ck-bg-canvas: linear-gradient(180deg, #87CEEB 0%, #B3E5FC 45%, #E1F5FE 100%);
689
- --ck-bg-sidebar: rgba(255, 255, 255, 0.55);
690
- --ck-bg-surface: rgba(255, 255, 255, 0.62);
691
- --ck-bg-surface-2: rgba(255, 255, 255, 0.78);
692
- --ck-bg-muted: rgba(255, 255, 255, 0.4);
693
-
694
- /* Cool, near-white borders with a hint of sky — they read as
695
- light catching the edge of glass. */
696
- --ck-border-subtle: rgba(255, 255, 255, 0.55);
697
- --ck-border-strong: rgba(26, 35, 126, 0.18);
698
- --ck-border-focus: var(--ck-accent);
699
-
700
- /* Deep navy ink — black is too harsh against sky. */
701
- --ck-text-primary: #0E1A6B;
702
- --ck-text-secondary: #38507A;
703
- --ck-text-tertiary: #6C82A8;
704
- --ck-text-disabled: #B7C6DD;
705
- --ck-text-inverse: #FFFFFF;
706
-
707
- /* Aqua accent — the canonical Aero blue. */
708
- --ck-accent: var(--ck-accent-light-override, #0288D1);
709
- --ck-accent-hover: #01579B;
710
- --ck-accent-muted: color-mix(in oklab, var(--ck-accent) 14%, transparent);
711
- --ck-accent-border: color-mix(in oklab, var(--ck-accent) 38%, transparent);
712
-
713
- /* Lime pop — used very sparingly for "go" / new / on-states. */
714
- --ck-frutiger-lime: #C6FF00;
715
- --ck-frutiger-orange: #FF9800;
716
-
717
- /* Glossy gradient stacks — applied to buttons via background-
718
- image. Two layers: the aqua tint at the bottom, the white
719
- gloss highlight on the top half. */
720
- --ck-frutiger-btn: linear-gradient(180deg, #4FC3F7 0%, #0288D1 50%, #01579B 100%);
721
- --ck-frutiger-btn-hover: linear-gradient(180deg, #81D4FA 0%, #039BE5 50%, #0277BD 100%);
722
- --ck-frutiger-gloss: linear-gradient(180deg, rgba(255,255,255,0.65) 0%, rgba(255,255,255,0) 50%, rgba(255,255,255,0) 100%);
723
-
724
- /* Generous "pebble" radii. */
725
- --ck-radius-xs: 8px;
726
- --ck-radius-sm: 12px;
727
- --ck-radius-md: 16px;
728
- --ck-radius-lg: 20px;
729
- --ck-radius-xl: 28px;
730
-
731
- /* Soft blue-tinted shadows — never dark/crisp. */
732
- --ck-shadow-1: 0 2px 8px rgba(0, 100, 200, 0.12);
733
- --ck-shadow-2: 0 4px 16px rgba(0, 100, 200, 0.18), 0 1px 2px rgba(0, 100, 200, 0.08);
734
- --ck-shadow-3: 0 12px 40px rgba(0, 100, 200, 0.28), 0 2px 6px rgba(0, 100, 200, 0.14);
735
-
736
- /* The "Aero glow" — used on hover/focus. */
737
- --ck-frutiger-glow: 0 0 24px rgba(79, 195, 247, 0.55);
738
- --ck-frutiger-glow-soft: 0 0 14px rgba(79, 195, 247, 0.35);
739
- }
740
-
741
- html[data-ck-theme="dark"][data-ck-chrome="frutiger"] {
742
- /* Twilight / aurora sky for dark — the brief lives in light,
743
- but a coherent dark mode keeps the chrome usable. Deep
744
- navy → indigo → soft lavender gradient. Glass surfaces
745
- become darker translucent panels. */
746
- --ck-bg-canvas: linear-gradient(180deg, #0B1029 0%, #1A2167 50%, #2E2280 100%);
747
- --ck-bg-sidebar: rgba(20, 30, 80, 0.55);
748
- --ck-bg-surface: rgba(30, 45, 110, 0.55);
749
- --ck-bg-surface-2: rgba(45, 60, 130, 0.65);
750
- --ck-bg-muted: rgba(20, 30, 80, 0.4);
751
-
752
- --ck-border-subtle: rgba(255, 255, 255, 0.16);
753
- --ck-border-strong: rgba(255, 255, 255, 0.28);
754
-
755
- --ck-text-primary: #EAF3FF;
756
- --ck-text-secondary: #B7C6E8;
757
- --ck-text-tertiary: #8094BE;
758
- --ck-text-disabled: #4B5C84;
759
- --ck-text-inverse: #0E1A6B;
760
-
761
- --ck-accent: var(--ck-accent-dark-override, var(--ck-accent-light-override, #4FC3F7));
762
-
763
- --ck-frutiger-btn: linear-gradient(180deg, #4FC3F7 0%, #0288D1 50%, #01579B 100%);
764
- --ck-frutiger-btn-hover: linear-gradient(180deg, #81D4FA 0%, #039BE5 50%, #0277BD 100%);
765
-
766
- --ck-shadow-1: 0 2px 8px rgba(0, 0, 0, 0.35);
767
- --ck-shadow-2: 0 4px 16px rgba(0, 0, 0, 0.42), 0 1px 2px rgba(0, 0, 0, 0.32);
768
- --ck-shadow-3: 0 12px 40px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.32);
769
-
770
- --ck-frutiger-glow: 0 0 24px rgba(129, 212, 250, 0.45);
771
- --ck-frutiger-glow-soft: 0 0 14px rgba(129, 212, 250, 0.3);
772
- }
773
-
774
- /* ---------- Risograph / Zine Print chrome — DIY print culture as
775
- a digital aesthetic. Warm uncoated paper canvas, 2-3
776
- fluorescent inks per composition (pink/blue/yellow
777
- signature trio), halftone dots, misregistration
778
- offset, hand-drawn imperfection, bold display type
779
- + workhorse mono body. Anti-glossy, anti-gradient. */
780
686
  html[data-ck-chrome="riso"] {
781
687
  /* Warm cream paper, never flat white. */
782
688
  --ck-bg-canvas: #F4EFE5;
@@ -6,6 +6,7 @@ import {
6
6
  type ReactNode,
7
7
  } from "react";
8
8
  import { ThemeContext } from "./theme-context";
9
+ import { BUILTIN_CHROMES } from "./types";
9
10
  import type { Theme, Chrome, ThemeContextValue } from "./types";
10
11
 
11
12
  const DEFAULT_THEME_KEY = "ck-theme";
@@ -63,7 +64,12 @@ export function ThemeProvider({
63
64
  return stored ?? preferredTheme(defaultTheme);
64
65
  });
65
66
  const [chrome, setChromeState] = useState<Chrome>(() => {
66
- const stored = readStored<Chrome>(chromeStorageKey, ["classic", "seamless"]);
67
+ // Allow every built-in chrome out of localStorage. Custom strings
68
+ // (the `(string & {})` half of the Chrome union) are not
69
+ // persisted across reloads — consumers that ship custom chromes
70
+ // can opt-in via a separate localStorage write upstream of this
71
+ // provider if they want persistence.
72
+ const stored = readStored<Chrome>(chromeStorageKey, BUILTIN_CHROMES);
67
73
  return stored ?? defaultChrome;
68
74
  });
69
75
 
@@ -2,4 +2,5 @@ export { ThemeProvider } from "./ThemeProvider";
2
2
  export type { ThemeProviderProps } from "./ThemeProvider";
3
3
  export { useTheme } from "./useTheme";
4
4
  export { getInlineThemeScript } from "./inline-script";
5
+ export { BUILTIN_CHROMES } from "./types";
5
6
  export type { Theme, Chrome, ThemeContextValue } from "./types";
@@ -1,3 +1,4 @@
1
+ import { BUILTIN_CHROMES } from "./types";
1
2
  import type { Theme, Chrome } from "./types";
2
3
 
3
4
  // Pre-mount inline script generator. Paste the returned string
@@ -22,6 +23,9 @@ export function getInlineThemeScript(opts: {
22
23
  const c = JSON.stringify(opts.chromeStorageKey ?? "ck-chrome");
23
24
  const dt = JSON.stringify(opts.defaultTheme ?? "system");
24
25
  const dc = JSON.stringify(opts.defaultChrome ?? "seamless");
26
+ // Inline the allowlist so the runtime check is a single
27
+ // Array.indexOf — matches ThemeProvider's readStored behaviour.
28
+ const allowed = JSON.stringify(BUILTIN_CHROMES);
25
29
  return `(function(){try{
26
30
  var t=localStorage.getItem(${t});
27
31
  if(t!=="light"&&t!=="dark"){
@@ -30,7 +34,7 @@ export function getInlineThemeScript(opts: {
30
34
  }
31
35
  document.documentElement.setAttribute("data-ck-theme",t);
32
36
  var c=localStorage.getItem(${c});
33
- if(c!=="classic"&&c!=="seamless")c=${dc};
37
+ if(${allowed}.indexOf(c)<0)c=${dc};
34
38
  document.documentElement.setAttribute("data-ck-chrome",c);
35
39
  }catch(e){}})();`;
36
40
  }
@@ -6,12 +6,29 @@
6
6
 
7
7
  export type Theme = "light" | "dark";
8
8
 
9
+ // BUILTIN_CHROMES is the source of truth for "chromes the kit ships
10
+ // with". The Chrome union below, the ThemeProvider localStorage
11
+ // validator, and the pre-mount inline script all key off this
12
+ // constant so adding (or removing) a chrome is a one-place change.
13
+ export const BUILTIN_CHROMES = [
14
+ "classic",
15
+ "seamless",
16
+ "editorial",
17
+ "neobrutalism",
18
+ "ambient",
19
+ "swiss",
20
+ "terminal",
21
+ "bento",
22
+ "riso",
23
+ "sketch",
24
+ ] as const;
25
+
9
26
  // Built-in chromes ship with the kit; custom strings are also
10
27
  // allowed at runtime (the kit just stamps the value onto
11
28
  // `data-ck-chrome`, your CSS supplies the overrides). The
12
29
  // `(string & {})` trick preserves autocomplete for the built-ins
13
30
  // while widening the union to any string for custom chromes.
14
- export type Chrome = "classic" | "seamless" | "editorial" | "neobrutalism" | "ambient" | "swiss" | "terminal" | "bento" | "frutiger" | "riso" | "sketch" | (string & {});
31
+ export type Chrome = (typeof BUILTIN_CHROMES)[number] | (string & {});
15
32
 
16
33
  export interface ThemeContextValue {
17
34
  theme: Theme;
@@ -1,79 +0,0 @@
1
- import type { CSSProperties } from "react";
2
-
3
- // Pure CSS 3D glass orb — the "Aero" / Vista / iOS 6 marble.
4
- // Layered radial gradients: deep accent base, top white highlight,
5
- // bottom internal reflection, soft outer glow. Drop it anywhere
6
- // you want a hero / empty-state decoration.
7
-
8
- export interface GlossyOrbProps {
9
- size?: number;
10
- // Tint of the orb. Defaults to aqua (Aero blue). Pass any color.
11
- tone?: "aqua" | "lime" | "rose" | "amber";
12
- // Disable the slow rotation animation.
13
- static?: boolean;
14
- className?: string;
15
- style?: CSSProperties;
16
- }
17
-
18
- const TONES: Record<NonNullable<GlossyOrbProps["tone"]>, { base: string; deep: string; glow: string }> = {
19
- aqua: { base: "#4FC3F7", deep: "#01579B", glow: "rgba(79, 195, 247, 0.55)" },
20
- lime: { base: "#DCFF66", deep: "#7CB342", glow: "rgba(198, 255, 0, 0.55)" },
21
- rose: { base: "#F48FB1", deep: "#AD1457", glow: "rgba(244, 143, 177, 0.55)" },
22
- amber: { base: "#FFCA28", deep: "#E65100", glow: "rgba(255, 152, 0, 0.55)" },
23
- };
24
-
25
- export function GlossyOrb({
26
- size = 96,
27
- tone = "aqua",
28
- static: noSpin,
29
- className,
30
- style,
31
- }: GlossyOrbProps) {
32
- const t = TONES[tone];
33
- return (
34
- <div
35
- className={className}
36
- aria-hidden
37
- style={{
38
- width: size,
39
- height: size,
40
- borderRadius: "50%",
41
- position: "relative",
42
- background: `
43
- radial-gradient(circle at 50% 28%, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.0) 32%),
44
- radial-gradient(circle at 50% 95%, ${t.base} 0%, transparent 40%),
45
- radial-gradient(circle at 50% 50%, ${t.base} 0%, ${t.deep} 100%)
46
- `,
47
- boxShadow: `
48
- 0 ${size * 0.12}px ${size * 0.28}px ${t.glow},
49
- inset 0 ${size * 0.08}px ${size * 0.12}px rgba(255, 255, 255, 0.55),
50
- inset 0 -${size * 0.08}px ${size * 0.16}px rgba(0, 0, 0, 0.22)
51
- `,
52
- animation: noSpin ? undefined : "ck-frutiger-orb-spin 18s linear infinite",
53
- ...style,
54
- }}
55
- >
56
- {/* Specular highlight — small bright cap top-left */}
57
- <span
58
- style={{
59
- position: "absolute",
60
- top: size * 0.1,
61
- left: size * 0.22,
62
- width: size * 0.36,
63
- height: size * 0.22,
64
- borderRadius: "50%",
65
- background: "radial-gradient(ellipse, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0) 70%)",
66
- }}
67
- />
68
- <style>{`
69
- @keyframes ck-frutiger-orb-spin {
70
- 0% { transform: rotate(0deg); }
71
- 100% { transform: rotate(360deg); }
72
- }
73
- @media (prefers-reduced-motion: reduce) {
74
- [data-ck-glossy-orb] { animation: none !important; }
75
- }
76
- `}</style>
77
- </div>
78
- );
79
- }
@@ -1,114 +0,0 @@
1
- import type { CSSProperties, ReactNode } from "react";
2
-
3
- // Drop-in section backdrop. The frutiger chrome already paints the
4
- // page with a sky gradient — this component is for opt-in moments
5
- // inside other chromes (a hero banner under chrome="classic", a
6
- // preview area in the theming page) without flipping the whole app.
7
-
8
- export interface SkyBackdropProps {
9
- children?: ReactNode;
10
- // Add the floating sparkle particles. Defaults to true.
11
- sparkles?: boolean;
12
- // Add soft white cumulus blobs near the bottom. Defaults to true.
13
- clouds?: boolean;
14
- // Variant: dusk swaps the gradient for indigo→lavender→pink.
15
- variant?: "day" | "dusk";
16
- className?: string;
17
- style?: CSSProperties;
18
- }
19
-
20
- const DAY_BG = "linear-gradient(180deg, #87CEEB 0%, #B3E5FC 45%, #E1F5FE 100%)";
21
- const DUSK_BG = "linear-gradient(180deg, #2E2280 0%, #6A4C93 40%, #F4A6CD 80%, #FFD8B1 100%)";
22
-
23
- export function SkyBackdrop({
24
- children,
25
- sparkles = true,
26
- clouds = true,
27
- variant = "day",
28
- className,
29
- style,
30
- }: SkyBackdropProps) {
31
- return (
32
- <div
33
- className={className}
34
- style={{
35
- position: "relative",
36
- overflow: "hidden",
37
- background: variant === "dusk" ? DUSK_BG : DAY_BG,
38
- ...style,
39
- }}
40
- >
41
- {clouds && <Clouds variant={variant} />}
42
- {sparkles && <Sparkles />}
43
- <div style={{ position: "relative", zIndex: 2 }}>{children}</div>
44
- </div>
45
- );
46
- }
47
-
48
- function Clouds({ variant }: { variant: "day" | "dusk" }) {
49
- const tint = variant === "dusk" ? "rgba(255, 220, 230, 0.5)" : "rgba(255, 255, 255, 0.6)";
50
- return (
51
- <div
52
- aria-hidden
53
- style={{
54
- position: "absolute",
55
- inset: 0,
56
- background: `
57
- radial-gradient(ellipse 280px 80px at 14% 78%, ${tint} 0%, transparent 70%),
58
- radial-gradient(ellipse 360px 90px at 72% 86%, ${tint} 0%, transparent 70%),
59
- radial-gradient(ellipse 200px 60px at 50% 92%, ${tint} 0%, transparent 70%)
60
- `,
61
- pointerEvents: "none",
62
- zIndex: 1,
63
- }}
64
- />
65
- );
66
- }
67
-
68
- function Sparkles() {
69
- // 14 deterministic positions — random feels jittery on reload.
70
- const dots = [
71
- { x: 8, y: 22, s: 3, o: 0.85 },
72
- { x: 18, y: 12, s: 2, o: 0.65 },
73
- { x: 31, y: 32, s: 4, o: 0.9 },
74
- { x: 44, y: 18, s: 2, o: 0.55 },
75
- { x: 56, y: 42, s: 3, o: 0.7 },
76
- { x: 68, y: 14, s: 2, o: 0.6 },
77
- { x: 76, y: 30, s: 5, o: 0.8 },
78
- { x: 88, y: 24, s: 2, o: 0.55 },
79
- { x: 22, y: 56, s: 3, o: 0.55 },
80
- { x: 38, y: 64, s: 2, o: 0.5 },
81
- { x: 60, y: 60, s: 4, o: 0.7 },
82
- { x: 82, y: 50, s: 2, o: 0.55 },
83
- { x: 12, y: 42, s: 2, o: 0.5 },
84
- { x: 50, y: 28, s: 2, o: 0.7 },
85
- ];
86
- return (
87
- <div
88
- aria-hidden
89
- style={{
90
- position: "absolute",
91
- inset: 0,
92
- pointerEvents: "none",
93
- zIndex: 1,
94
- }}
95
- >
96
- {dots.map((d, i) => (
97
- <span
98
- key={i}
99
- style={{
100
- position: "absolute",
101
- left: `${d.x}%`,
102
- top: `${d.y}%`,
103
- width: d.s,
104
- height: d.s,
105
- borderRadius: "50%",
106
- background: "#FFFFFF",
107
- opacity: d.o,
108
- boxShadow: `0 0 ${d.s * 4}px ${d.s * 1.5}px rgba(255, 255, 255, ${d.o * 0.55})`,
109
- }}
110
- />
111
- ))}
112
- </div>
113
- );
114
- }
@@ -1,2 +0,0 @@
1
- export * from "./SkyBackdrop";
2
- export * from "./GlossyOrb";
@@ -1,364 +0,0 @@
1
- /* Frutiger Aero / Glossy Revival chrome — Aero / Vista / iOS 6 /
2
- Vision Pro Liquid Glass. Glossy buttons with a two-layer gradient
3
- (aqua base + white gloss top half), glass cards with backdrop-
4
- filter, soft blue-tinted shadows, outer glow on hover, deep navy
5
- ink. Sparkle particles + shimmer sweeps are opt-in via classnames
6
- so the chrome only "performs" when you want it to.
7
-
8
- Implementation note: the sky-gradient canvas is applied via the
9
- tokens.css `--ck-bg-canvas`. The kit's base layer already does
10
- `body { background: var(--ck-bg-canvas); }` so the gradient
11
- value carries through. */
12
-
13
- /* Body — humanist sans, deep navy, slightly tighter letter-spacing
14
- on hero text. */
15
- html[data-ck-chrome="frutiger"] body {
16
- font-family: "Avenir Next", "Calibre", "SF Pro Rounded", var(--ck-font-sans);
17
- background-attachment: fixed;
18
- letter-spacing: -0.005em;
19
- }
20
-
21
- html[data-ck-chrome="frutiger"] h1,
22
- html[data-ck-chrome="frutiger"] .ck-h1 {
23
- font-weight: 700;
24
- letter-spacing: -0.01em;
25
- line-height: 1.05;
26
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8),
27
- 0 2px 4px rgba(0, 100, 200, 0.18);
28
- }
29
- html[data-ck-chrome="frutiger"] h2,
30
- html[data-ck-chrome="frutiger"] .ck-h2 {
31
- font-weight: 600;
32
- letter-spacing: -0.005em;
33
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
34
- }
35
- html[data-ck-chrome="frutiger"] h3,
36
- html[data-ck-chrome="frutiger"] .ck-h3 {
37
- font-weight: 600;
38
- }
39
-
40
- /* Dark mode: drop the white text-shadow (would look like a bug
41
- against the navy canvas). */
42
- html[data-ck-theme="dark"][data-ck-chrome="frutiger"] h1,
43
- html[data-ck-theme="dark"][data-ck-chrome="frutiger"] h2,
44
- html[data-ck-theme="dark"][data-ck-chrome="frutiger"] h3 {
45
- text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
46
- }
47
-
48
- /* ---------- Buttons — the signature glossy candy element ---------- */
49
- html[data-ck-chrome="frutiger"] .ck-btn,
50
- html[data-ck-chrome="frutiger"] .ck-actionbar-btn {
51
- border-radius: 12px;
52
- font-weight: 600;
53
- letter-spacing: -0.005em;
54
- height: 36px;
55
- border: 1px solid rgba(255, 255, 255, 0.5);
56
- box-shadow:
57
- inset 0 1px 0 rgba(255, 255, 255, 0.6),
58
- inset 0 -1px 0 rgba(0, 50, 120, 0.2),
59
- 0 2px 8px rgba(0, 100, 200, 0.18);
60
- transition:
61
- box-shadow 200ms ease,
62
- transform 200ms ease,
63
- background 200ms ease;
64
- }
65
-
66
- html[data-ck-chrome="frutiger"] .ck-btn--primary,
67
- html[data-ck-chrome="frutiger"] .ck-actionbar-btn--primary {
68
- background:
69
- var(--ck-frutiger-gloss),
70
- var(--ck-frutiger-btn);
71
- color: #FFFFFF;
72
- text-shadow: 0 1px 0 rgba(0, 50, 120, 0.45);
73
- border-color: rgba(2, 88, 155, 0.7);
74
- }
75
- html[data-ck-chrome="frutiger"] .ck-btn--primary:hover:not(:disabled),
76
- html[data-ck-chrome="frutiger"] .ck-actionbar-btn--primary:hover:not(:disabled) {
77
- background:
78
- var(--ck-frutiger-gloss),
79
- var(--ck-frutiger-btn-hover);
80
- box-shadow:
81
- inset 0 1px 0 rgba(255, 255, 255, 0.75),
82
- inset 0 -1px 0 rgba(0, 50, 120, 0.2),
83
- var(--ck-frutiger-glow);
84
- }
85
-
86
- html[data-ck-chrome="frutiger"] .ck-btn--secondary {
87
- background:
88
- linear-gradient(180deg, rgba(255,255,255,0.85) 0%, rgba(255,255,255,0.35) 50%, rgba(255,255,255,0.55) 100%);
89
- color: var(--ck-text-primary);
90
- border-color: rgba(255, 255, 255, 0.65);
91
- backdrop-filter: blur(8px);
92
- }
93
- html[data-ck-chrome="frutiger"] .ck-btn--secondary:hover:not(:disabled) {
94
- box-shadow:
95
- inset 0 1px 0 rgba(255, 255, 255, 0.8),
96
- inset 0 -1px 0 rgba(0, 50, 120, 0.18),
97
- var(--ck-frutiger-glow-soft);
98
- }
99
-
100
- html[data-ck-chrome="frutiger"] .ck-btn--ghost {
101
- background: rgba(255, 255, 255, 0.0);
102
- border-color: transparent;
103
- box-shadow: none;
104
- color: var(--ck-text-primary);
105
- }
106
- html[data-ck-chrome="frutiger"] .ck-btn--ghost:hover:not(:disabled) {
107
- background: rgba(255, 255, 255, 0.35);
108
- border-color: rgba(255, 255, 255, 0.45);
109
- box-shadow:
110
- inset 0 1px 0 rgba(255, 255, 255, 0.6),
111
- 0 1px 4px rgba(0, 100, 200, 0.14);
112
- }
113
-
114
- html[data-ck-chrome="frutiger"] .ck-btn:active:not(:disabled),
115
- html[data-ck-chrome="frutiger"] .ck-actionbar-btn:active:not(:disabled) {
116
- transform: translateY(1px);
117
- }
118
-
119
- /* ---------- Inputs — inset glass, glow on focus ---------- */
120
- html[data-ck-chrome="frutiger"] .ck-input,
121
- html[data-ck-chrome="frutiger"] .ck-textarea {
122
- background:
123
- linear-gradient(180deg, rgba(0, 50, 120, 0.06) 0%, rgba(255, 255, 255, 0.6) 18%);
124
- border: 1px solid rgba(0, 60, 140, 0.18);
125
- border-radius: 12px;
126
- box-shadow:
127
- inset 0 2px 4px rgba(0, 50, 120, 0.12),
128
- 0 1px 0 rgba(255, 255, 255, 0.85);
129
- color: var(--ck-text-primary);
130
- }
131
- html[data-ck-chrome="frutiger"] .ck-input::placeholder,
132
- html[data-ck-chrome="frutiger"] .ck-textarea::placeholder {
133
- color: rgba(56, 80, 122, 0.55);
134
- }
135
- html[data-ck-chrome="frutiger"] .ck-input:focus-visible,
136
- html[data-ck-chrome="frutiger"] .ck-textarea:focus-visible {
137
- outline: none;
138
- border-color: var(--ck-accent) !important;
139
- box-shadow:
140
- inset 0 2px 4px rgba(0, 50, 120, 0.12),
141
- var(--ck-frutiger-glow-soft) !important;
142
- }
143
-
144
- /* ---------- Cards — glass plate with top highlight + bottom shadow line */
145
- html[data-ck-chrome="frutiger"] .ck-card {
146
- background:
147
- linear-gradient(180deg, rgba(255,255,255,0.78) 0%, rgba(255,255,255,0.55) 100%);
148
- backdrop-filter: blur(20px) saturate(140%);
149
- -webkit-backdrop-filter: blur(20px) saturate(140%);
150
- border: 1px solid rgba(255, 255, 255, 0.6);
151
- border-radius: 18px;
152
- box-shadow:
153
- inset 0 1px 0 rgba(255, 255, 255, 0.85),
154
- inset 0 -1px 0 rgba(0, 50, 120, 0.14),
155
- var(--ck-shadow-2);
156
- position: relative;
157
- }
158
- html[data-ck-chrome="frutiger"] .ck-card__head {
159
- background: transparent;
160
- border-bottom: 1px solid rgba(0, 50, 120, 0.1);
161
- padding: 14px 20px;
162
- }
163
- html[data-ck-chrome="frutiger"] .ck-card__foot {
164
- background: transparent;
165
- border-top: 1px solid rgba(0, 50, 120, 0.08);
166
- padding: 12px 20px;
167
- }
168
-
169
- /* ---------- Tags — glossy pills ---------- */
170
- html[data-ck-chrome="frutiger"] [data-ck-tag] {
171
- border-radius: 999px !important;
172
- padding: 3px 10px !important;
173
- font-weight: 600 !important;
174
- font-size: 11px !important;
175
- letter-spacing: 0 !important;
176
- text-transform: none !important;
177
- border: 1px solid rgba(255, 255, 255, 0.5) !important;
178
- box-shadow:
179
- inset 0 1px 0 rgba(255, 255, 255, 0.7),
180
- inset 0 -1px 0 rgba(0, 50, 120, 0.12),
181
- 0 1px 3px rgba(0, 100, 200, 0.14) !important;
182
- background:
183
- linear-gradient(180deg, rgba(255,255,255,0.75) 0%, rgba(255,255,255,0.35) 100%) !important;
184
- color: var(--ck-text-primary) !important;
185
- }
186
- html[data-ck-chrome="frutiger"] [data-ck-tag][data-tone="accent"] {
187
- background:
188
- linear-gradient(180deg, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 50%),
189
- linear-gradient(180deg, #4FC3F7 0%, #0288D1 100%) !important;
190
- color: #FFFFFF !important;
191
- text-shadow: 0 1px 0 rgba(0, 50, 120, 0.4);
192
- border-color: rgba(2, 119, 189, 0.7) !important;
193
- }
194
- html[data-ck-chrome="frutiger"] [data-ck-tag][data-tone="success"] {
195
- background:
196
- linear-gradient(180deg, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0) 50%),
197
- linear-gradient(180deg, #DCFF66 0%, #C6FF00 100%) !important;
198
- color: #2A3A00 !important;
199
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
200
- border-color: rgba(120, 160, 0, 0.5) !important;
201
- }
202
- html[data-ck-chrome="frutiger"] [data-ck-tag][data-tone="warning"] {
203
- background:
204
- linear-gradient(180deg, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 50%),
205
- linear-gradient(180deg, #FFCA28 0%, #FF9800 100%) !important;
206
- color: #4A2400 !important;
207
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
208
- border-color: rgba(180, 100, 0, 0.5) !important;
209
- }
210
- html[data-ck-chrome="frutiger"] [data-ck-tag][data-tone="critical"] {
211
- background:
212
- linear-gradient(180deg, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 50%),
213
- linear-gradient(180deg, #FF7B7B 0%, #D32F2F 100%) !important;
214
- color: #FFFFFF !important;
215
- text-shadow: 0 1px 0 rgba(140, 20, 20, 0.6);
216
- border-color: rgba(150, 30, 30, 0.7) !important;
217
- }
218
-
219
- /* ---------- Layout chrome ---------- */
220
- html[data-ck-chrome="frutiger"] [data-ck-leftnav] {
221
- background:
222
- linear-gradient(180deg, rgba(255,255,255,0.7) 0%, rgba(255,255,255,0.45) 100%) !important;
223
- backdrop-filter: blur(28px) saturate(150%);
224
- -webkit-backdrop-filter: blur(28px) saturate(150%);
225
- border-right: 1px solid rgba(255, 255, 255, 0.55);
226
- box-shadow: inset -1px 0 0 rgba(0, 50, 120, 0.1),
227
- 4px 0 24px rgba(0, 100, 200, 0.12);
228
- }
229
-
230
- html[data-ck-chrome="frutiger"] [data-ck-topbar] {
231
- background:
232
- linear-gradient(180deg, rgba(255,255,255,0.78) 0%, rgba(255,255,255,0.55) 100%) !important;
233
- backdrop-filter: blur(28px) saturate(150%);
234
- -webkit-backdrop-filter: blur(28px) saturate(150%);
235
- border-bottom: 1px solid rgba(255, 255, 255, 0.5);
236
- box-shadow:
237
- inset 0 1px 0 rgba(255, 255, 255, 0.85),
238
- 0 2px 12px rgba(0, 100, 200, 0.1);
239
- }
240
-
241
- /* NavItem — glassy pill, accent on active */
242
- html[data-ck-chrome="frutiger"] [data-ck-navitem] {
243
- background: transparent !important;
244
- border-radius: 10px !important;
245
- font-weight: 500 !important;
246
- color: var(--ck-text-secondary) !important;
247
- transition: background 200ms ease, box-shadow 200ms ease;
248
- }
249
- html[data-ck-chrome="frutiger"] [data-ck-navitem]:not([data-active="true"]):hover {
250
- background:
251
- linear-gradient(180deg, rgba(255,255,255,0.7) 0%, rgba(255,255,255,0.35) 100%) !important;
252
- box-shadow:
253
- inset 0 1px 0 rgba(255, 255, 255, 0.7),
254
- 0 1px 4px rgba(0, 100, 200, 0.12) !important;
255
- color: var(--ck-text-primary) !important;
256
- }
257
- html[data-ck-chrome="frutiger"] [data-ck-navitem][data-active="true"] {
258
- background:
259
- linear-gradient(180deg, rgba(255,255,255,0.55) 0%, rgba(255,255,255,0) 50%),
260
- linear-gradient(180deg, #4FC3F7 0%, #0288D1 100%) !important;
261
- color: #FFFFFF !important;
262
- text-shadow: 0 1px 0 rgba(0, 50, 120, 0.4) !important;
263
- box-shadow:
264
- inset 0 1px 0 rgba(255, 255, 255, 0.6),
265
- inset 0 -1px 0 rgba(0, 50, 120, 0.18),
266
- 0 2px 8px rgba(0, 100, 200, 0.28) !important;
267
- font-weight: 600 !important;
268
- }
269
-
270
- /* Search trigger */
271
- html[data-ck-chrome="frutiger"] [data-ck-search-trigger] {
272
- background:
273
- linear-gradient(180deg, rgba(0, 50, 120, 0.06) 0%, rgba(255, 255, 255, 0.65) 18%) !important;
274
- border: 1px solid rgba(0, 60, 140, 0.18) !important;
275
- border-radius: 12px !important;
276
- box-shadow:
277
- inset 0 2px 4px rgba(0, 50, 120, 0.1),
278
- 0 1px 0 rgba(255, 255, 255, 0.85) !important;
279
- color: var(--ck-text-secondary) !important;
280
- }
281
-
282
- /* Action bar */
283
- html[data-ck-chrome="frutiger"] [data-ck-actionbar] {
284
- background:
285
- linear-gradient(180deg, rgba(255,255,255,0.85) 0%, rgba(255,255,255,0.55) 100%) !important;
286
- backdrop-filter: blur(28px) saturate(150%);
287
- -webkit-backdrop-filter: blur(28px) saturate(150%);
288
- border: 1px solid rgba(255, 255, 255, 0.6);
289
- border-radius: 999px;
290
- box-shadow:
291
- inset 0 1px 0 rgba(255, 255, 255, 0.9),
292
- inset 0 -1px 0 rgba(0, 50, 120, 0.14),
293
- var(--ck-shadow-3);
294
- }
295
-
296
- /* Modal — frosted plate */
297
- html[data-ck-chrome="frutiger"] [role="dialog"] > div:first-child {
298
- background: rgba(135, 206, 235, 0.35);
299
- backdrop-filter: blur(16px) saturate(160%);
300
- -webkit-backdrop-filter: blur(16px) saturate(160%);
301
- }
302
- html[data-ck-chrome="frutiger"] [role="dialog"] > div > div {
303
- background:
304
- linear-gradient(180deg, rgba(255,255,255,0.88) 0%, rgba(255,255,255,0.65) 100%) !important;
305
- backdrop-filter: blur(28px) saturate(160%);
306
- -webkit-backdrop-filter: blur(28px) saturate(160%);
307
- border: 1px solid rgba(255, 255, 255, 0.7);
308
- border-radius: 22px;
309
- box-shadow:
310
- inset 0 1px 0 rgba(255, 255, 255, 0.9),
311
- inset 0 -1px 0 rgba(0, 50, 120, 0.14),
312
- var(--ck-shadow-3);
313
- }
314
-
315
- /* Code / pre blocks — semi-transparent glass too */
316
- html[data-ck-chrome="frutiger"] pre,
317
- html[data-ck-chrome="frutiger"] code {
318
- background: rgba(255, 255, 255, 0.55) !important;
319
- border: 1px solid rgba(255, 255, 255, 0.5);
320
- border-radius: 10px;
321
- color: var(--ck-text-primary);
322
- }
323
-
324
- /* ---------- Shimmer sweep — opt-in via .ck-frutiger-shimmer.
325
- Diagonal white gradient sweeps across the element every few
326
- seconds. Apply to "shiny" elements: progress fills, loading
327
- pills, the hero CTA. */
328
- @keyframes ck-frutiger-shimmer {
329
- 0% { transform: translateX(-150%) skewX(-20deg); }
330
- 60%, 100% { transform: translateX(250%) skewX(-20deg); }
331
- }
332
- .ck-frutiger-shimmer {
333
- position: relative;
334
- overflow: hidden;
335
- }
336
- .ck-frutiger-shimmer::after {
337
- content: "";
338
- position: absolute;
339
- inset: 0;
340
- pointer-events: none;
341
- background: linear-gradient(110deg,
342
- transparent 0%,
343
- transparent 35%,
344
- rgba(255, 255, 255, 0.55) 50%,
345
- transparent 65%,
346
- transparent 100%);
347
- animation: ck-frutiger-shimmer 3.4s ease-in-out infinite;
348
- }
349
-
350
- /* Slow pulse for the "aero glow" — opt-in via .ck-frutiger-glow */
351
- @keyframes ck-frutiger-glow-pulse {
352
- 0%, 100% { box-shadow: 0 0 16px rgba(79, 195, 247, 0.35); }
353
- 50% { box-shadow: 0 0 28px rgba(79, 195, 247, 0.65); }
354
- }
355
- .ck-frutiger-glow {
356
- animation: ck-frutiger-glow-pulse 4.2s ease-in-out infinite;
357
- }
358
-
359
- @media (prefers-reduced-motion: reduce) {
360
- .ck-frutiger-shimmer::after,
361
- .ck-frutiger-glow {
362
- animation: none;
363
- }
364
- }