@mediquo/medicore 0.3.1 → 0.4.1

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.
@@ -5,6 +5,12 @@ export interface AuthPanelProps extends Omit<ComponentProps<"div">, "children">
5
5
  * (`scrim-brand`) always goes on top so the white headline reads over any image.
6
6
  */
7
7
  imageSrc?: string;
8
+ /**
9
+ * A full-bleed decoration drawn with tokens instead of (or over) the photo: the
10
+ * `BrandBubbles`. It sits above the veil and under the headline, hidden from
11
+ * assistive technology.
12
+ */
13
+ decoration?: ReactNode;
8
14
  /**
9
15
  * Optional context for the step: "Escribe tus datos exactamente como figuran en tu
10
16
  * documento oficial." Don't repeat the form's title here. It is a `<p>`, not a
@@ -16,5 +22,8 @@ export interface AuthPanelProps extends Omit<ComponentProps<"div">, "children">
16
22
  * Left panel of the access screens on desktop (≥1024px), 560px wide and full height.
17
23
  * It doesn't exist on mobile: the form takes the whole screen, so hide it below
18
24
  * `lg` in the layout (`className="hidden lg:flex"`).
25
+ *
26
+ * Without a photo, give it a `decoration` (`BrandBubbles`): a flat brand-dark block
27
+ * next to a form reads as unfinished.
19
28
  */
20
- export declare function AuthPanel({ imageSrc, headline, className, ...props }: AuthPanelProps): React.JSX.Element;
29
+ export declare function AuthPanel({ imageSrc, decoration, headline, className, ...props }: AuthPanelProps): React.JSX.Element;
@@ -2,6 +2,7 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { cn } from "../../lib/cn.js";
3
3
  function AuthPanel({
4
4
  imageSrc,
5
+ decoration,
5
6
  headline,
6
7
  className,
7
8
  ...props
@@ -25,6 +26,15 @@ function AuthPanel({
25
26
  }
26
27
  ),
27
28
  /* @__PURE__ */ jsx("div", { "aria-hidden": true, className: "absolute inset-0 bg-scrim-brand" }),
29
+ decoration && /* @__PURE__ */ jsx(
30
+ "div",
31
+ {
32
+ "aria-hidden": true,
33
+ "data-slot": "auth-panel-decoration",
34
+ className: "pointer-events-none absolute inset-0 overflow-hidden",
35
+ children: decoration
36
+ }
37
+ ),
28
38
  headline && /* @__PURE__ */ jsx("p", { className: "relative text-heading-h2 text-text-inverse", children: headline })
29
39
  ]
30
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auth-panel.js","sources":["../../../src/components/auth-panel/auth-panel.tsx"],"sourcesContent":["import type { ComponentProps, ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\n\nexport interface AuthPanelProps\n extends Omit<ComponentProps<\"div\">, \"children\"> {\n /**\n * The photo, full-bleed. It is decorative: it renders with `alt=\"\"`. The brand veil\n * (`scrim-brand`) always goes on top so the white headline reads over any image.\n */\n imageSrc?: string;\n /**\n * Optional context for the step: \"Escribe tus datos exactamente como figuran en tu\n * documento oficial.\" Don't repeat the form's title here. It is a `<p>`, not a\n * heading.\n */\n headline?: ReactNode;\n}\n\n/**\n * Left panel of the access screens on desktop (≥1024px), 560px wide and full height.\n * It doesn't exist on mobile: the form takes the whole screen, so hide it below\n * `lg` in the layout (`className=\"hidden lg:flex\"`).\n */\nexport function AuthPanel({\n imageSrc,\n headline,\n className,\n ...props\n}: AuthPanelProps): React.JSX.Element {\n return (\n <div\n data-slot=\"auth-panel\"\n className={cn(\n \"relative flex w-140 shrink-0 flex-col justify-center overflow-hidden bg-brand-dark px-16\",\n className,\n )}\n {...props}\n >\n {imageSrc && (\n <img\n src={imageSrc}\n alt=\"\"\n className=\"absolute inset-0 size-full object-cover\"\n />\n )}\n <div aria-hidden className=\"absolute inset-0 bg-scrim-brand\" />\n {headline && (\n <p className=\"relative text-heading-h2 text-text-inverse\">{headline}</p>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;AAwBO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsC;AACpC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,YACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAK;AAAA,YACL,KAAI;AAAA,YACJ,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAGd,oBAAC,OAAA,EAAI,eAAW,MAAC,WAAU,mCAAkC;AAAA,QAC5D,YACC,oBAAC,KAAA,EAAE,WAAU,8CAA8C,UAAA,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAI5E;"}
1
+ {"version":3,"file":"auth-panel.js","sources":["../../../src/components/auth-panel/auth-panel.tsx"],"sourcesContent":["import type { ComponentProps, ReactNode } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\n\nexport interface AuthPanelProps\n extends Omit<ComponentProps<\"div\">, \"children\"> {\n /**\n * The photo, full-bleed. It is decorative: it renders with `alt=\"\"`. The brand veil\n * (`scrim-brand`) always goes on top so the white headline reads over any image.\n */\n imageSrc?: string;\n /**\n * A full-bleed decoration drawn with tokens instead of (or over) the photo: the\n * `BrandBubbles`. It sits above the veil and under the headline, hidden from\n * assistive technology.\n */\n decoration?: ReactNode;\n /**\n * Optional context for the step: \"Escribe tus datos exactamente como figuran en tu\n * documento oficial.\" Don't repeat the form's title here. It is a `<p>`, not a\n * heading.\n */\n headline?: ReactNode;\n}\n\n/**\n * Left panel of the access screens on desktop (≥1024px), 560px wide and full height.\n * It doesn't exist on mobile: the form takes the whole screen, so hide it below\n * `lg` in the layout (`className=\"hidden lg:flex\"`).\n *\n * Without a photo, give it a `decoration` (`BrandBubbles`): a flat brand-dark block\n * next to a form reads as unfinished.\n */\nexport function AuthPanel({\n imageSrc,\n decoration,\n headline,\n className,\n ...props\n}: AuthPanelProps): React.JSX.Element {\n return (\n <div\n data-slot=\"auth-panel\"\n className={cn(\n \"relative flex w-140 shrink-0 flex-col justify-center overflow-hidden bg-brand-dark px-16\",\n className,\n )}\n {...props}\n >\n {imageSrc && (\n <img\n src={imageSrc}\n alt=\"\"\n className=\"absolute inset-0 size-full object-cover\"\n />\n )}\n <div aria-hidden className=\"absolute inset-0 bg-scrim-brand\" />\n {decoration && (\n <div\n aria-hidden\n data-slot=\"auth-panel-decoration\"\n className=\"pointer-events-none absolute inset-0 overflow-hidden\"\n >\n {decoration}\n </div>\n )}\n {headline && (\n <p className=\"relative text-heading-h2 text-text-inverse\">{headline}</p>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;AAiCO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsC;AACpC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,YACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAK;AAAA,YACL,KAAI;AAAA,YACJ,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAGd,oBAAC,OAAA,EAAI,eAAW,MAAC,WAAU,mCAAkC;AAAA,QAC5D,cACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAW;AAAA,YACX,aAAU;AAAA,YACV,WAAU;AAAA,YAET,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAGJ,YACC,oBAAC,KAAA,EAAE,WAAU,8CAA8C,UAAA,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAI5E;"}
@@ -0,0 +1,18 @@
1
+ import { type ComponentProps } from "react";
2
+ export interface BrandBubblesProps extends Omit<ComponentProps<"svg">, "children"> {
3
+ }
4
+ /**
5
+ * mediQuo's brand backdrop on a dark brand surface: translucent bubbles that float and
6
+ * rise, breathing glows, a twinkling constellation and the cross. A seamless loop that
7
+ * reads like a short video, in the same language as the portal's home hero, for the
8
+ * `AuthPanel` and any other full-bleed brand surface.
9
+ *
10
+ * - Decorative: `aria-hidden`, no pointer events, never carries meaning.
11
+ * - Fills its parent (`absolute inset-0`, `slice`), so the parent is `relative`
12
+ * and clips overflow.
13
+ * - Coloured with tokens (`text-inverse`, `brand`), so it follows a clinic's brand.
14
+ * - Motion only under `prefers-reduced-motion: no-preference`; otherwise it is a
15
+ * still composition. Transforms and opacity only, with gradients instead of blur
16
+ * filters, so it stays smooth on large panels.
17
+ */
18
+ export declare function BrandBubbles({ className, ...props }: BrandBubblesProps): React.JSX.Element;
@@ -0,0 +1,163 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useId } from "react";
3
+ import { cn } from "../../lib/cn.js";
4
+ const float = "motion-safe:animate-float";
5
+ const rise = "motion-safe:animate-rise";
6
+ const scaled = "origin-center [transform-box:fill-box]";
7
+ const breathe = cn("motion-safe:animate-breathe", scaled);
8
+ const twinkle = cn("motion-safe:animate-twinkle", scaled);
9
+ const RISING_BUBBLES = [
10
+ { cx: 70, r: 10, timing: "[animation-duration:15s]" },
11
+ { cx: 150, r: 22, timing: "[animation-duration:19s] [animation-delay:-11s]" },
12
+ { cx: 235, r: 7, timing: "[animation-duration:13s] [animation-delay:-4s]" },
13
+ { cx: 300, r: 16, timing: "[animation-duration:17s] [animation-delay:-8s]" },
14
+ { cx: 380, r: 9, timing: "[animation-duration:14s] [animation-delay:-2s]" },
15
+ { cx: 445, r: 26, timing: "[animation-duration:21s] [animation-delay:-15s]" },
16
+ { cx: 510, r: 12, timing: "[animation-duration:16s] [animation-delay:-6s]" },
17
+ { cx: 110, r: 6, timing: "[animation-duration:12s] [animation-delay:-9s]" },
18
+ { cx: 345, r: 5, timing: "[animation-duration:11s] [animation-delay:-5s]" }
19
+ ];
20
+ const CONSTELLATION = [
21
+ { cx: 120, cy: 330, timing: "[animation-delay:0s]" },
22
+ { cx: 300, cy: 520, timing: "[animation-delay:-0.8s]" },
23
+ { cx: 380, cy: 420, timing: "[animation-delay:-1.6s]" },
24
+ { cx: 180, cy: 700, timing: "[animation-delay:-2.4s]" }
25
+ ];
26
+ function BrandBubbles({
27
+ className,
28
+ ...props
29
+ }) {
30
+ const glowId = useId();
31
+ return /* @__PURE__ */ jsxs(
32
+ "svg",
33
+ {
34
+ viewBox: "0 0 560 1024",
35
+ preserveAspectRatio: "xMidYMid slice",
36
+ "aria-hidden": "true",
37
+ focusable: "false",
38
+ "data-slot": "brand-bubbles",
39
+ className: cn(
40
+ "pointer-events-none absolute inset-0 size-full select-none",
41
+ className
42
+ ),
43
+ ...props,
44
+ children: [
45
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("radialGradient", { id: glowId, children: [
46
+ /* @__PURE__ */ jsx(
47
+ "stop",
48
+ {
49
+ offset: "0%",
50
+ className: "[stop-color:var(--color-brand)] [stop-opacity:0.6]"
51
+ }
52
+ ),
53
+ /* @__PURE__ */ jsx(
54
+ "stop",
55
+ {
56
+ offset: "100%",
57
+ className: "[stop-color:var(--color-brand)] [stop-opacity:0]"
58
+ }
59
+ )
60
+ ] }) }),
61
+ /* @__PURE__ */ jsx(
62
+ "circle",
63
+ {
64
+ cx: "440",
65
+ cy: "180",
66
+ r: "320",
67
+ fill: `url(#${glowId})`,
68
+ className: cn(breathe, "[animation-duration:11s]")
69
+ }
70
+ ),
71
+ /* @__PURE__ */ jsx(
72
+ "circle",
73
+ {
74
+ cx: "90",
75
+ cy: "860",
76
+ r: "300",
77
+ fill: `url(#${glowId})`,
78
+ className: cn(
79
+ breathe,
80
+ "[animation-duration:13s] [animation-delay:-5s]"
81
+ )
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsxs("g", { className: "fill-text-inverse/5", children: [
85
+ /* @__PURE__ */ jsx(
86
+ "circle",
87
+ {
88
+ cx: "430",
89
+ cy: "150",
90
+ r: "92",
91
+ className: cn(float, "[animation-duration:12s]")
92
+ }
93
+ ),
94
+ /* @__PURE__ */ jsx(
95
+ "circle",
96
+ {
97
+ cx: "470",
98
+ cy: "760",
99
+ r: "140",
100
+ className: cn(
101
+ float,
102
+ "[animation-duration:16s] [animation-delay:-6s]"
103
+ )
104
+ }
105
+ ),
106
+ /* @__PURE__ */ jsx(
107
+ "circle",
108
+ {
109
+ cx: "120",
110
+ cy: "330",
111
+ r: "62",
112
+ className: cn(
113
+ float,
114
+ "[animation-duration:10s] [animation-delay:-3s]"
115
+ )
116
+ }
117
+ )
118
+ ] }),
119
+ /* @__PURE__ */ jsx("g", { "data-slot": "brand-bubbles-rising", className: "fill-text-inverse/15", children: RISING_BUBBLES.map((bubble) => /* @__PURE__ */ jsx(
120
+ "circle",
121
+ {
122
+ cx: bubble.cx,
123
+ cy: "1080",
124
+ r: bubble.r,
125
+ className: cn(rise, bubble.timing)
126
+ },
127
+ bubble.cx
128
+ )) }),
129
+ /* @__PURE__ */ jsxs(
130
+ "g",
131
+ {
132
+ className: cn(float, "[animation-duration:18s] [animation-delay:-4s]"),
133
+ children: [
134
+ /* @__PURE__ */ jsxs("g", { className: "stroke-text-inverse opacity-15", strokeWidth: "1", children: [
135
+ /* @__PURE__ */ jsx("path", { d: "M120 330 L300 520" }),
136
+ /* @__PURE__ */ jsx("path", { d: "M300 520 L380 420" }),
137
+ /* @__PURE__ */ jsx("path", { d: "M300 520 L180 700" })
138
+ ] }),
139
+ /* @__PURE__ */ jsx("g", { className: "fill-text-inverse/40", children: CONSTELLATION.map((dot) => /* @__PURE__ */ jsx(
140
+ "circle",
141
+ {
142
+ cx: dot.cx,
143
+ cy: dot.cy,
144
+ r: "5",
145
+ className: cn(twinkle, dot.timing)
146
+ },
147
+ `${dot.cx}-${dot.cy}`
148
+ )) }),
149
+ /* @__PURE__ */ jsxs("g", { className: "fill-text-inverse/25", children: [
150
+ /* @__PURE__ */ jsx("rect", { x: "412", y: "258", width: "36", height: "10", rx: "5" }),
151
+ /* @__PURE__ */ jsx("rect", { x: "425", y: "245", width: "10", height: "36", rx: "5" })
152
+ ] })
153
+ ]
154
+ }
155
+ )
156
+ ]
157
+ }
158
+ );
159
+ }
160
+ export {
161
+ BrandBubbles
162
+ };
163
+ //# sourceMappingURL=brand-bubbles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brand-bubbles.js","sources":["../../../src/components/brand-bubbles/brand-bubbles.tsx"],"sourcesContent":["import { type ComponentProps, useId } from \"react\";\n\nimport { cn } from \"../../lib/cn\";\n\nexport interface BrandBubblesProps\n extends Omit<ComponentProps<\"svg\">, \"children\"> {}\n\nconst float = \"motion-safe:animate-float\";\nconst rise = \"motion-safe:animate-rise\";\nconst scaled = \"origin-center [transform-box:fill-box]\";\nconst breathe = cn(\"motion-safe:animate-breathe\", scaled);\nconst twinkle = cn(\"motion-safe:animate-twinkle\", scaled);\n\nconst RISING_BUBBLES = [\n { cx: 70, r: 10, timing: \"[animation-duration:15s]\" },\n { cx: 150, r: 22, timing: \"[animation-duration:19s] [animation-delay:-11s]\" },\n { cx: 235, r: 7, timing: \"[animation-duration:13s] [animation-delay:-4s]\" },\n { cx: 300, r: 16, timing: \"[animation-duration:17s] [animation-delay:-8s]\" },\n { cx: 380, r: 9, timing: \"[animation-duration:14s] [animation-delay:-2s]\" },\n { cx: 445, r: 26, timing: \"[animation-duration:21s] [animation-delay:-15s]\" },\n { cx: 510, r: 12, timing: \"[animation-duration:16s] [animation-delay:-6s]\" },\n { cx: 110, r: 6, timing: \"[animation-duration:12s] [animation-delay:-9s]\" },\n { cx: 345, r: 5, timing: \"[animation-duration:11s] [animation-delay:-5s]\" },\n] as const;\n\nconst CONSTELLATION = [\n { cx: 120, cy: 330, timing: \"[animation-delay:0s]\" },\n { cx: 300, cy: 520, timing: \"[animation-delay:-0.8s]\" },\n { cx: 380, cy: 420, timing: \"[animation-delay:-1.6s]\" },\n { cx: 180, cy: 700, timing: \"[animation-delay:-2.4s]\" },\n] as const;\n\n/**\n * mediQuo's brand backdrop on a dark brand surface: translucent bubbles that float and\n * rise, breathing glows, a twinkling constellation and the cross. A seamless loop that\n * reads like a short video, in the same language as the portal's home hero, for the\n * `AuthPanel` and any other full-bleed brand surface.\n *\n * - Decorative: `aria-hidden`, no pointer events, never carries meaning.\n * - Fills its parent (`absolute inset-0`, `slice`), so the parent is `relative`\n * and clips overflow.\n * - Coloured with tokens (`text-inverse`, `brand`), so it follows a clinic's brand.\n * - Motion only under `prefers-reduced-motion: no-preference`; otherwise it is a\n * still composition. Transforms and opacity only, with gradients instead of blur\n * filters, so it stays smooth on large panels.\n */\nexport function BrandBubbles({\n className,\n ...props\n}: BrandBubblesProps): React.JSX.Element {\n const glowId = useId();\n\n return (\n <svg\n viewBox=\"0 0 560 1024\"\n preserveAspectRatio=\"xMidYMid slice\"\n aria-hidden=\"true\"\n focusable=\"false\"\n data-slot=\"brand-bubbles\"\n className={cn(\n \"pointer-events-none absolute inset-0 size-full select-none\",\n className,\n )}\n {...props}\n >\n <defs>\n <radialGradient id={glowId}>\n <stop\n offset=\"0%\"\n className=\"[stop-color:var(--color-brand)] [stop-opacity:0.6]\"\n />\n <stop\n offset=\"100%\"\n className=\"[stop-color:var(--color-brand)] [stop-opacity:0]\"\n />\n </radialGradient>\n </defs>\n\n <circle\n cx=\"440\"\n cy=\"180\"\n r=\"320\"\n fill={`url(#${glowId})`}\n className={cn(breathe, \"[animation-duration:11s]\")}\n />\n <circle\n cx=\"90\"\n cy=\"860\"\n r=\"300\"\n fill={`url(#${glowId})`}\n className={cn(\n breathe,\n \"[animation-duration:13s] [animation-delay:-5s]\",\n )}\n />\n\n <g className=\"fill-text-inverse/5\">\n <circle\n cx=\"430\"\n cy=\"150\"\n r=\"92\"\n className={cn(float, \"[animation-duration:12s]\")}\n />\n <circle\n cx=\"470\"\n cy=\"760\"\n r=\"140\"\n className={cn(\n float,\n \"[animation-duration:16s] [animation-delay:-6s]\",\n )}\n />\n <circle\n cx=\"120\"\n cy=\"330\"\n r=\"62\"\n className={cn(\n float,\n \"[animation-duration:10s] [animation-delay:-3s]\",\n )}\n />\n </g>\n\n <g data-slot=\"brand-bubbles-rising\" className=\"fill-text-inverse/15\">\n {RISING_BUBBLES.map((bubble) => (\n <circle\n key={bubble.cx}\n cx={bubble.cx}\n cy=\"1080\"\n r={bubble.r}\n className={cn(rise, bubble.timing)}\n />\n ))}\n </g>\n\n <g\n className={cn(float, \"[animation-duration:18s] [animation-delay:-4s]\")}\n >\n <g className=\"stroke-text-inverse opacity-15\" strokeWidth=\"1\">\n <path d=\"M120 330 L300 520\" />\n <path d=\"M300 520 L380 420\" />\n <path d=\"M300 520 L180 700\" />\n </g>\n <g className=\"fill-text-inverse/40\">\n {CONSTELLATION.map((dot) => (\n <circle\n key={`${dot.cx}-${dot.cy}`}\n cx={dot.cx}\n cy={dot.cy}\n r=\"5\"\n className={cn(twinkle, dot.timing)}\n />\n ))}\n </g>\n <g className=\"fill-text-inverse/25\">\n <rect x=\"412\" y=\"258\" width=\"36\" height=\"10\" rx=\"5\" />\n <rect x=\"425\" y=\"245\" width=\"10\" height=\"36\" rx=\"5\" />\n </g>\n </g>\n </svg>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export { BrandBubbles, type BrandBubblesProps } from "./brand-bubbles";
package/dist/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export * from "./components/avatar";
9
9
  export * from "./components/badge";
10
10
  export * from "./components/banner";
11
11
  export * from "./components/bottom-bar";
12
+ export * from "./components/brand-bubbles";
12
13
  export * from "./components/brand-mark";
13
14
  export * from "./components/breadcrumb";
14
15
  export * from "./components/button";
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import { Avatar, avatarStatusVariants, avatarVariants } from "./components/avata
11
11
  import { Badge, badgeVariants } from "./components/badge/badge.js";
12
12
  import { Banner } from "./components/banner/banner.js";
13
13
  import { BottomBar, BottomBarItem, bottomBarItemVariants } from "./components/bottom-bar/bottom-bar.js";
14
+ import { BrandBubbles } from "./components/brand-bubbles/brand-bubbles.js";
14
15
  import { BrandMark } from "./components/brand-mark/brand-mark.js";
15
16
  import { Breadcrumb, BreadcrumbItem, BreadcrumbPage } from "./components/breadcrumb/breadcrumb.js";
16
17
  import { Button, buttonVariants } from "./components/button/button.js";
@@ -107,6 +108,7 @@ export {
107
108
  Banner,
108
109
  BottomBar,
109
110
  BottomBarItem,
111
+ BrandBubbles,
110
112
  BrandMark,
111
113
  Breadcrumb,
112
114
  BreadcrumbItem,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -50,6 +50,76 @@
50
50
  --shadow-lg: 0 8px 16px -2px rgb(10 10 34 / 0.08);
51
51
  --shadow-xl: 0 16px 32px -4px rgb(10 10 34 / 0.12);
52
52
 
53
+ /*
54
+ * Motion for brand backdrops (BrandBubbles), a seamless loop like a short video:
55
+ * bubbles float on a wide path, small ones rise through the panel, glows breathe and
56
+ * the constellation twinkles. Transforms and opacity only. Always behind `motion-safe:`.
57
+ */
58
+ --animate-drift: float 16s ease-in-out infinite;
59
+ --animate-float: float 12s ease-in-out infinite;
60
+ --animate-rise: rise 16s linear infinite;
61
+ --animate-breathe: breathe 10s ease-in-out infinite alternate;
62
+ --animate-twinkle: twinkle 3s ease-in-out infinite;
63
+
64
+ @keyframes float {
65
+ 0%,
66
+ 100% {
67
+ transform: translate3d(0, 0, 0);
68
+ }
69
+ 25% {
70
+ transform: translate3d(16px, -28px, 0);
71
+ }
72
+ 50% {
73
+ transform: translate3d(-8px, -48px, 0);
74
+ }
75
+ 75% {
76
+ transform: translate3d(-20px, -20px, 0);
77
+ }
78
+ }
79
+
80
+ @keyframes rise {
81
+ 0% {
82
+ transform: translate3d(0, 0, 0);
83
+ opacity: 0;
84
+ }
85
+ 12% {
86
+ opacity: 1;
87
+ }
88
+ 50% {
89
+ transform: translate3d(24px, -600px, 0);
90
+ }
91
+ 88% {
92
+ opacity: 1;
93
+ }
94
+ 100% {
95
+ transform: translate3d(0, -1200px, 0);
96
+ opacity: 0;
97
+ }
98
+ }
99
+
100
+ @keyframes breathe {
101
+ from {
102
+ transform: scale(1);
103
+ opacity: 0.7;
104
+ }
105
+ to {
106
+ transform: scale(1.3);
107
+ opacity: 1;
108
+ }
109
+ }
110
+
111
+ @keyframes twinkle {
112
+ 0%,
113
+ 100% {
114
+ transform: scale(1);
115
+ opacity: 1;
116
+ }
117
+ 50% {
118
+ transform: scale(1.8);
119
+ opacity: 0.4;
120
+ }
121
+ }
122
+
53
123
  /* Sizing (Figma "Sizing" collection) → size-icon-md, h-control-sm, size-avatar-lg… */
54
124
  --spacing-icon-xs: 12px;
55
125
  --spacing-icon-sm: 16px;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mediquo/medicore",
3
3
  "description": "MediCore — design system de las plataformas web de mediQuo",
4
- "version": "0.3.1",
4
+ "version": "0.4.1",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
7
7
  "sideEffects": [