@ansiversa/components 0.0.3 → 0.0.5

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/index.ts CHANGED
@@ -1,6 +1,20 @@
1
1
  // Do not write code directly here, instead use the `src` folder!
2
2
  // Then, use this file to export everything you want your user to access.
3
3
 
4
- import MyComponent from './src/MyComponent.astro';
4
+ export { default as AvBrand } from './src/AvBrand.astro';
5
+ export { default as AvButton } from './src/AvButton.astro';
6
+ export { default as AvCard } from './src/AvCard.astro';
7
+ export { default as AvCheckbox } from './src/AvCheckbox.astro';
8
+ export { default as AvContainer } from './src/AvContainer.astro';
9
+ export { default as AvDivider } from './src/AvDivider.astro';
10
+ export { default as AvFeatureItem } from './src/AvFeatureItem.astro';
11
+ export { default as AvFeatureList } from './src/AvFeatureList.astro';
12
+ export { default as AvFooter } from './src/AvFooter.astro';
13
+ export { default as AvInput } from './src/AvInput.astro';
14
+ export { default as AvNavbar } from './src/AvNavbar.astro';
15
+ export { default as AvNavbarActions } from './src/AvNavbarActions.astro';
16
+ export { default as AvTimeline } from './src/AvTimeline.astro';
17
+ export { default as MyComponent } from './src/MyComponent.astro';
5
18
 
6
- export default MyComponent;
19
+ // Preserve default export for backwards compatibility.
20
+ export { default } from './src/MyComponent.astro';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ansiversa/components",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Shared UI components and layouts for the Ansiversa ecosystem",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,41 @@
1
+ ---
2
+ interface Props {
3
+ /** Link destination (default: home) */
4
+ href?: string;
5
+
6
+ /** Title text — default ANSIVERSA */
7
+ title?: string;
8
+
9
+ /** Subtitle text — default Curated Apps */
10
+ subtitle?: string;
11
+
12
+ /** Path to the logo icon (default: /favicon.svg) */
13
+ logo?: string;
14
+
15
+ /** Extra classes if needed */
16
+ className?: string;
17
+ }
18
+
19
+ const {
20
+ href = "/",
21
+ title = "ANSIVERSA",
22
+ subtitle = "Curated Apps",
23
+ logo = "/favicon.svg",
24
+ className = "",
25
+ } = Astro.props;
26
+
27
+ const classes = ["av-navbar-brand"];
28
+ if (className) classes.push(className);
29
+ const classAttr = classes.join(" ");
30
+ ---
31
+
32
+ <a href={href} class={classAttr}>
33
+ <div class="av-navbar-brand__logo">
34
+ <img src={logo} alt="Ansiversa logo" />
35
+ </div>
36
+
37
+ <div class="av-navbar-brand__meta">
38
+ <span class="av-navbar-brand__title av-gradient-text">{title}</span>
39
+ <span class="av-navbar-brand__subtitle">{subtitle}</span>
40
+ </div>
41
+ </a>
@@ -0,0 +1,69 @@
1
+ ---
2
+ interface Props {
3
+ /** visual style */
4
+ variant?: "primary" | "ghost" | "outline";
5
+ /** size */
6
+ size?: "sm" | "md" | "lg";
7
+ /** full width */
8
+ block?: boolean;
9
+ /** if set, renders as <a> instead of <button> */
10
+ href?: string;
11
+ /** button type when rendering <button> */
12
+ type?: "button" | "submit" | "reset";
13
+ /** disabled state (for <button>) */
14
+ disabled?: boolean;
15
+ /** extra classes if ever needed */
16
+ className?: string;
17
+ }
18
+
19
+ const {
20
+ variant = "primary",
21
+ size = "md",
22
+ block = false,
23
+ href,
24
+ type = "button",
25
+ disabled = false,
26
+ className,
27
+ } = Astro.props;
28
+
29
+ // Build class list based on our global.css system
30
+ const classes: string[] = [];
31
+
32
+ // Variant (each variant class already includes av-btn base via @apply)
33
+ if (variant === "primary") {
34
+ classes.push("av-btn-primary");
35
+ } else if (variant === "ghost") {
36
+ classes.push("av-btn-ghost");
37
+ } else if (variant === "outline") {
38
+ classes.push("av-btn-outline");
39
+ }
40
+
41
+ // Size
42
+ if (size === "sm") {
43
+ classes.push("av-btn-sm");
44
+ } else if (size === "lg") {
45
+ classes.push("av-btn-lg");
46
+ }
47
+
48
+ // Block
49
+ if (block) {
50
+ classes.push("av-btn-block");
51
+ }
52
+
53
+ // Extra classes (optional)
54
+ if (className) {
55
+ classes.push(className);
56
+ }
57
+
58
+ const classAttr = classes.join(" ");
59
+ ---
60
+
61
+ {href ? (
62
+ <a href={href} class={classAttr}>
63
+ <slot />
64
+ </a>
65
+ ) : (
66
+ <button type={type} class={classAttr} disabled={disabled}>
67
+ <slot />
68
+ </button>
69
+ )}
@@ -0,0 +1,38 @@
1
+ ---
2
+ interface Props {
3
+ /** Visual style of the card */
4
+ variant?: "default" | "auth" | "soft";
5
+ /** Extra classes for the outer wrapper */
6
+ className?: string;
7
+ }
8
+
9
+ const {
10
+ variant = "default",
11
+ className = "",
12
+ } = Astro.props as Props;
13
+
14
+ const classes: string[] = [];
15
+
16
+ // Base card class
17
+ if (variant === "soft") {
18
+ classes.push("av-card-soft");
19
+ } else {
20
+ // default and auth both use av-card base
21
+ classes.push("av-card");
22
+ }
23
+
24
+ // Auth-specific modifier
25
+ if (variant === "auth") {
26
+ classes.push("av-card-auth");
27
+ }
28
+
29
+ if (className) {
30
+ classes.push(className);
31
+ }
32
+
33
+ const classAttr = classes.join(" ");
34
+ ---
35
+
36
+ <div class={classAttr}>
37
+ <slot />
38
+ </div>
@@ -0,0 +1,56 @@
1
+ ---
2
+ interface Props {
3
+ /** Checkbox label text (if no children slot is provided) */
4
+ label?: string;
5
+ /** Input name */
6
+ name?: string;
7
+ /** Input id */
8
+ id?: string;
9
+ /** Whether the checkbox is required */
10
+ required?: boolean;
11
+ /** Whether the checkbox is checked by default */
12
+ checked?: boolean;
13
+ /** Extra classes for the outer wrapper */
14
+ className?: string;
15
+ /** Optional hint text below the checkbox */
16
+ hint?: string;
17
+ }
18
+
19
+ const props = Astro.props as Props;
20
+
21
+ const {
22
+ label,
23
+ name,
24
+ required = false,
25
+ checked = false,
26
+ className = "",
27
+ hint = "",
28
+ } = props;
29
+
30
+ const id = props.id ?? name ?? `checkbox-${Math.random().toString(36).slice(2)}`;
31
+
32
+ const wrapperClasses: string[] = [];
33
+ if (className) wrapperClasses.push(className);
34
+ ---
35
+
36
+ <div class={wrapperClasses.join(" ")}>
37
+ <label class="av-inline-check" for={id}>
38
+ <input
39
+ id={id}
40
+ name={name}
41
+ type="checkbox"
42
+ class="av-checkbox"
43
+ required={required}
44
+ checked={checked}
45
+ />
46
+ <span>
47
+ {label ? label : <slot />}
48
+ </span>
49
+ </label>
50
+
51
+ {hint && (
52
+ <p class="av-form-hint">
53
+ {hint}
54
+ </p>
55
+ )}
56
+ </div>
@@ -0,0 +1,30 @@
1
+ ---
2
+ interface Props {
3
+ /** Container size variant */
4
+ size?: "default" | "narrow" | "wide" | "full";
5
+
6
+ /** Extra classes */
7
+ className?: string;
8
+ }
9
+
10
+ const {
11
+ size = "default",
12
+ className = "",
13
+ } = Astro.props;
14
+
15
+ // Base
16
+ const classes = ["av-container"];
17
+
18
+ // Variants mapped to our custom classes from global.css
19
+ if (size === "narrow") classes.push("av-container-narrow");
20
+ if (size === "wide") classes.push("av-container-wide");
21
+ if (size === "full") classes.push("av-container-full");
22
+
23
+ if (className) classes.push(className);
24
+
25
+ const classAttr = classes.join(" ");
26
+ ---
27
+
28
+ <div class={classAttr}>
29
+ <slot />
30
+ </div>
@@ -0,0 +1,13 @@
1
+ ---
2
+ interface Props {
3
+ /** Extra classes for customization */
4
+ className?: string;
5
+ }
6
+
7
+ const { className = "" } = Astro.props as Props;
8
+ const classes = ["av-divider"];
9
+ if (className) classes.push(className);
10
+ const classAttr = classes.join(" ");
11
+ ---
12
+
13
+ <div class={classAttr} />
@@ -0,0 +1,27 @@
1
+ ---
2
+ interface Props {
3
+ /** Dot style */
4
+ variant?: "primary" | "secondary" | "default";
5
+ /** Extra classes for the item wrapper */
6
+ className?: string;
7
+ }
8
+
9
+ const {
10
+ variant = "default",
11
+ className = "",
12
+ } = Astro.props as Props;
13
+
14
+ const itemClasses = ["av-feature-item"];
15
+ if (className) itemClasses.push(className);
16
+
17
+ const dotClasses = ["av-feature-dot"];
18
+ if (variant === "primary") dotClasses.push("av-feature-dot--primary");
19
+ if (variant === "secondary") dotClasses.push("av-feature-dot--secondary");
20
+ ---
21
+
22
+ <div class={itemClasses.join(" ")}>
23
+ <span class={dotClasses.join(" ")}></span>
24
+ <span>
25
+ <slot />
26
+ </span>
27
+ </div>
@@ -0,0 +1,15 @@
1
+ ---
2
+ interface Props {
3
+ /** Extra classes for the list wrapper */
4
+ className?: string;
5
+ }
6
+
7
+ const { className = "" } = Astro.props as Props;
8
+ const classes = ["av-feature-list"];
9
+ if (className) classes.push(className);
10
+ const classAttr = classes.join(" ");
11
+ ---
12
+
13
+ <div class={classAttr}>
14
+ <slot />
15
+ </div>
@@ -0,0 +1,48 @@
1
+ ---
2
+ interface FooterLink {
3
+ label: string;
4
+ href: string;
5
+ }
6
+
7
+ interface Props {
8
+ /** List of footer navigation links */
9
+ links?: FooterLink[];
10
+
11
+ /** Footer tagline text */
12
+ tagline?: string;
13
+
14
+ /** Extra classes for the wrapper */
15
+ className?: string;
16
+ }
17
+
18
+ const {
19
+ links = [
20
+ { label: "Terms", href: "#terms" },
21
+ { label: "Privacy", href: "#privacy" },
22
+ ],
23
+ tagline = "Advanced Next-Gen Software Innovation and Versatility.",
24
+ className = "",
25
+ } = Astro.props;
26
+
27
+ const classes = ["av-footer"];
28
+ if (className) classes.push(className);
29
+ const classAttr = classes.join(" ");
30
+ ---
31
+
32
+ <footer class={classAttr}>
33
+ <div class="av-footer-inner">
34
+ <p class="av-caption">
35
+ © {new Date().getFullYear()} Ansiversa. All rights reserved.
36
+ </p>
37
+
38
+ <div class="av-footer-meta">
39
+ {links.map((item) => (
40
+ <a href={item.href} class="av-nav-link">{item.label}</a>
41
+ ))}
42
+
43
+ <span class="av-footer-separator">•</span>
44
+
45
+ <span class="av-footer-quote">{tagline}</span>
46
+ </div>
47
+ </div>
48
+ </footer>
@@ -0,0 +1,96 @@
1
+ ---
2
+ interface Props {
3
+ label?: string;
4
+ name?: string;
5
+ placeholder?: string;
6
+ type?: astroHTML.JSX.HTMLInputTypeAttribute;
7
+ value?: string;
8
+ required?: boolean;
9
+ disabled?: boolean;
10
+ className?: string;
11
+ inputClass?: string;
12
+ id?: string;
13
+ hint?: string;
14
+ autocomplete?: string;
15
+ }
16
+
17
+ const props = Astro.props as Props;
18
+
19
+ const {
20
+ label,
21
+ name,
22
+ placeholder = "",
23
+ type = "text",
24
+ value,
25
+ required = false,
26
+ disabled = false,
27
+ className = "",
28
+ inputClass = "",
29
+ id = name ?? `input-${Math.random().toString(36).slice(2)}`,
30
+ hint = "",
31
+ autocomplete,
32
+ } = props;
33
+
34
+ const wrapperClasses = ["av-form-group", "av-input-wrapper"];
35
+ if (className) wrapperClasses.push(className);
36
+
37
+ const inputClasses = ["av-input"];
38
+ if (inputClass) inputClasses.push(inputClass);
39
+
40
+ const isPassword = type === "password";
41
+ ---
42
+
43
+ <div
44
+ class={wrapperClasses.join(" ")}
45
+ x-data={isPassword ? `{ show: false }` : undefined}
46
+ >
47
+ {label && (
48
+ <label class="av-label" for={id}>
49
+ {label}
50
+ {required && <span aria-hidden="true"> *</span>}
51
+ </label>
52
+ )}
53
+
54
+ <div class="av-input-password-container">
55
+ <input
56
+ id={id}
57
+ name={name}
58
+ type={isPassword ? undefined : type}
59
+ :type={isPassword ? `(show ? 'text' : 'password')` : undefined}
60
+ class={inputClasses.join(" ")}
61
+ placeholder={placeholder}
62
+ value={value}
63
+ required={required}
64
+ disabled={disabled}
65
+ autocomplete={autocomplete}
66
+ />
67
+
68
+ <!-- PASSWORD TOGGLE BUTTON -->
69
+ {isPassword && (
70
+ <button
71
+ type="button"
72
+ class="av-password-toggle"
73
+ @click="show = !show"
74
+ :aria-label="show ? 'Hide password' : 'Show password'"
75
+ >
76
+ <template x-if="!show">
77
+ <svg xmlns="http://www.w3.org/2000/svg" class="av-password-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
78
+ <path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/>
79
+ <circle cx="12" cy="12" r="3"/>
80
+ </svg>
81
+ </template>
82
+
83
+ <template x-if="show">
84
+ <svg xmlns="http://www.w3.org/2000/svg" class="av-password-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
85
+ <path d="M17.94 17.94A10.94 10.94 0 0 1 12 19c-7 0-11-7-11-7a21.77 21.77 0 0 1 5.06-5.94m3.07-1.6A10.94 10.94 0 0 1 12 5c7 0 11 7 11 7a21.77 21.77 0 0 1-2.25 3.2"/>
86
+ <line x1="1" y1="1" x2="23" y2="23"/>
87
+ </svg>
88
+ </template>
89
+ </button>
90
+ )}
91
+ </div>
92
+
93
+ {hint && (
94
+ <p class="av-form-hint">{hint}</p>
95
+ )}
96
+ </div>
@@ -0,0 +1,20 @@
1
+ ---
2
+ interface Props {
3
+ /** Extra classes for the header element */
4
+ className?: string;
5
+ }
6
+
7
+ const {
8
+ className = "",
9
+ } = Astro.props;
10
+
11
+ const classes = ["av-navbar"];
12
+ if (className) classes.push(className);
13
+ const classAttr = classes.join(" ");
14
+ ---
15
+
16
+ <header class={classAttr}>
17
+ <div class="av-navbar-inner">
18
+ <slot />
19
+ </div>
20
+ </header>
@@ -0,0 +1,168 @@
1
+ ---
2
+ import AvButton from "./AvButton.astro";
3
+ import { actions } from "astro:actions";
4
+
5
+ interface NavLink {
6
+ label: string;
7
+ href: string;
8
+ variant?: "primary" | "ghost" | "outline";
9
+ className?: string;
10
+ }
11
+
12
+ interface User {
13
+ id: string;
14
+ email: string;
15
+ name?: string;
16
+ }
17
+
18
+ interface Props {
19
+ className?: string;
20
+ user?: User;
21
+ }
22
+
23
+ const { className = "", user } = Astro.props as Props;
24
+
25
+ const classes = ["av-navbar-actions"];
26
+ if (className) classes.push(className);
27
+ const classAttr = classes.join(" ");
28
+
29
+ const iconMap: Record<string, string> = {
30
+ Apps: `
31
+ <svg class="av-icon" viewBox='0 0 24 24'>
32
+ <rect x='3' y='3' width='7' height='7' rx='2'/>
33
+ <rect x='14' y='3' width='7' height='7' rx='2'/>
34
+ <rect x='3' y='14' width='7' height='7' rx='2'/>
35
+ <rect x='14' y='14' width='7' height='7' rx='2'/>
36
+ </svg>
37
+ `,
38
+ Pricing: `
39
+ <svg class="av-icon" viewBox='0 0 24 24'>
40
+ <path d='M6 3h7.586a2 2 0 0 1 1.414.586l4.414 4.414a2 2 0 0 1 0 2.828L13 18.242a2 2 0 0 1-2.828 0L3.586 11.657A2 2 0 0 1 3 10.243V6a3 3 0 0 1 3-3Z'/>
41
+ <path d='M9 6h.01'/>
42
+ </svg>
43
+ `,
44
+ About: `
45
+ <svg class="av-icon" viewBox='0 0 24 24'>
46
+ <circle cx='12' cy='8' r='3.25'/>
47
+ <path d='M6 19v-.5a5.5 5.5 0 0 1 11 0V19'/>
48
+ <path d='M12 12.5v2'/>
49
+ </svg>
50
+ `,
51
+ default: `
52
+ <svg class="av-icon" viewBox='0 0 24 24'>
53
+ <circle cx='12' cy='12' r='9'/>
54
+ </svg>
55
+ `,
56
+ };
57
+
58
+ function renderIcon(label: string) {
59
+ return iconMap[label] ?? iconMap.default;
60
+ }
61
+
62
+ // ALWAYS SHOW THESE LINKS
63
+ const baseLinks: NavLink[] = [
64
+ { label: "Apps", href: "/apps" },
65
+ { label: "Pricing", href: "/pricing" },
66
+ { label: "About", href: "/about" },
67
+ ];
68
+
69
+ // SIGN-IN link only for guests
70
+ const authLink: NavLink = {
71
+ label: "Sign in",
72
+ href: "/login",
73
+ variant: "primary",
74
+ };
75
+
76
+ const userInitial =
77
+ (user?.name?.charAt(0) ?? user?.email?.charAt(0) ?? "?").toUpperCase();
78
+ ---
79
+
80
+ <div class={classAttr}>
81
+ <!-- Left: Always visible -->
82
+ {baseLinks.map((item) => (
83
+ <AvButton
84
+ href={item.href}
85
+ variant="ghost"
86
+ className="av-nowrap"
87
+ >
88
+ <span class="av-nav-icon" set:html={renderIcon(item.label)}></span>
89
+ <span class="av-nav-label">{item.label}</span>
90
+ </AvButton>
91
+ ))}
92
+
93
+ <!-- Right: Sign-in OR User dropdown -->
94
+ {user ? (
95
+ <div class="av-user-menu-wrapper" x-data="{ open: false }">
96
+ <button
97
+ type="button"
98
+ class="av-user-avatar-button"
99
+ aria-label="Account menu"
100
+ aria-haspopup="menu"
101
+ x-on:click="open = !open"
102
+ x-bind:aria-expanded="open"
103
+ >
104
+ <span class="av-user-avatar-circle" aria-hidden="true">
105
+ {userInitial}
106
+ </span>
107
+ <span class="sr-only">Open user menu</span>
108
+ </button>
109
+
110
+ <div
111
+ x-show="open"
112
+ x-cloak
113
+ x-transition.origin.top.right
114
+ x-on:click.outside="open = false"
115
+ class="av-user-menu av-nav-user-menu"
116
+ role="menu"
117
+ aria-label="User menu"
118
+ >
119
+ <div class="av-user-menu-header">
120
+ <div class="av-user-menu-avatar">{userInitial}</div>
121
+ <div class="av-user-menu-meta">
122
+ <p class="av-user-menu-name">{user.name}</p>
123
+ <p class="av-user-menu-email">{user.email}</p>
124
+ </div>
125
+ </div>
126
+
127
+ <div class="av-user-menu-divider"></div>
128
+
129
+ <div class="av-nav-user-menu">
130
+ <a href="/dashboard" class="av-user-menu-item av-dropdown-item" role="menuitem">
131
+ <span>Dashboard</span>
132
+ </a>
133
+
134
+ <a
135
+ href="/change-password"
136
+ class="av-user-menu-item av-dropdown-item"
137
+ role="menuitem"
138
+ >
139
+ <span>Change password</span>
140
+ </a>
141
+
142
+ <a href="/settings" class="av-user-menu-item av-dropdown-item" role="menuitem">
143
+ <span>Settings</span>
144
+ </a>
145
+
146
+ <form method="POST" action="/signout">
147
+ <button
148
+ type="submit"
149
+ class="av-user-menu-item av-dropdown-item av-user-menu-item--danger"
150
+ role="menuitem"
151
+ >
152
+ <span>Sign out</span>
153
+ </button>
154
+ </form>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ ) : (
159
+ <AvButton
160
+ href={authLink.href}
161
+ variant={authLink.variant}
162
+ className="av-nowrap"
163
+ >
164
+ <span class="av-nav-icon" set:html={renderIcon(authLink.label)}></span>
165
+ <span class="av-nav-label">{authLink.label}</span>
166
+ </AvButton>
167
+ )}
168
+ </div>
@@ -0,0 +1,99 @@
1
+ ---
2
+ export interface TimelineItem {
3
+ date: string;
4
+ title?: string;
5
+ body: string;
6
+ }
7
+
8
+ interface Props {
9
+ items: TimelineItem[];
10
+ }
11
+
12
+ const { items = [] } = Astro.props;
13
+ ---
14
+
15
+ <div class="av-timeline">
16
+ {items.map((item, index) => (
17
+ <div
18
+ class={`av-timeline-row${
19
+ index === items.length - 1 ? " av-timeline-row--last" : ""
20
+ }`}
21
+ >
22
+ <div class="av-timeline-dot"></div>
23
+
24
+ <div class="av-timeline-content">
25
+ <h3 class="av-timeline-date">{item.date}</h3>
26
+ {item.title && <h4 class="av-timeline-title">{item.title}</h4>}
27
+ <p class="av-timeline-body">{item.body}</p>
28
+ </div>
29
+ </div>
30
+ ))}
31
+ </div>
32
+
33
+ <style>
34
+ .av-timeline {
35
+ margin-top: 2rem;
36
+ text-align: left; /* override parent center alignment */
37
+ }
38
+
39
+ .av-timeline-row {
40
+ position: relative;
41
+ padding-left: 2.2rem;
42
+ padding-bottom: 1.8rem;
43
+ }
44
+
45
+ /* Vertical line (per row, so it connects dot → next dot) */
46
+ .av-timeline-row::before {
47
+ content: "";
48
+ position: absolute;
49
+ left: 7px;
50
+ top: 0;
51
+ bottom: 0;
52
+ width: 2px;
53
+ background: var(--ans-border-subtle, rgba(148, 163, 184, 0.6));
54
+ border-radius: 999px;
55
+ }
56
+
57
+ /* Shorten the line on the last item so it ends nicely */
58
+ .av-timeline-row--last::before {
59
+ bottom: 0.9rem;
60
+ }
61
+
62
+ /* Dot sitting on the line */
63
+ .av-timeline-dot {
64
+ position: absolute;
65
+ left: 0;
66
+ top: 0.25rem;
67
+ width: 15px;
68
+ height: 15px;
69
+ border-radius: 50%;
70
+ background: var(--ans-primary, #6366f1);
71
+ border: 2px solid var(--ans-bg, #020617);
72
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.35);
73
+ z-index: 1;
74
+ }
75
+
76
+ /* Text content */
77
+ .av-timeline-content {
78
+ position: relative;
79
+ }
80
+
81
+ .av-timeline-date {
82
+ margin: 0;
83
+ font-size: 1.05rem;
84
+ font-weight: 700;
85
+ }
86
+
87
+ .av-timeline-title {
88
+ margin: 0.15rem 0 0.4rem;
89
+ font-size: 0.98rem;
90
+ font-weight: 500;
91
+ opacity: 0.9;
92
+ }
93
+
94
+ .av-timeline-body {
95
+ margin: 0;
96
+ line-height: 1.6;
97
+ opacity: 0.88;
98
+ }
99
+ </style>