boltdocs 1.6.0 → 1.7.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.
Files changed (57) hide show
  1. package/dist/{SearchDialog-J3KNRGNO.mjs → SearchDialog-6Z7CUAYJ.mjs} +8 -1
  2. package/dist/{SearchDialog-3QICRMWF.css → SearchDialog-GOZ6X53X.css} +385 -113
  3. package/dist/{chunk-HSPDIRTW.mjs → chunk-SFVOGJ2W.mjs} +955 -737
  4. package/dist/client/index.css +385 -113
  5. package/dist/client/index.d.mts +19 -7
  6. package/dist/client/index.d.ts +19 -7
  7. package/dist/client/index.js +964 -577
  8. package/dist/client/index.mjs +118 -1
  9. package/dist/client/ssr.css +385 -113
  10. package/dist/client/ssr.d.mts +3 -1
  11. package/dist/client/ssr.d.ts +3 -1
  12. package/dist/client/ssr.js +743 -474
  13. package/dist/client/ssr.mjs +3 -2
  14. package/dist/{config-DkZg5aCf.d.ts → config-D68h41CA.d.mts} +21 -2
  15. package/dist/{config-DkZg5aCf.d.mts → config-D68h41CA.d.ts} +21 -2
  16. package/dist/node/index.d.mts +12 -2
  17. package/dist/node/index.d.ts +12 -2
  18. package/dist/node/index.js +48 -21
  19. package/dist/node/index.mjs +48 -21
  20. package/dist/{types-DGIo1VKD.d.mts → types-BbceAHA0.d.mts} +15 -0
  21. package/dist/{types-DGIo1VKD.d.ts → types-BbceAHA0.d.ts} +15 -0
  22. package/package.json +1 -1
  23. package/src/client/app/index.tsx +16 -11
  24. package/src/client/index.ts +2 -0
  25. package/src/client/ssr.tsx +4 -1
  26. package/src/client/theme/components/mdx/Table.tsx +151 -0
  27. package/src/client/theme/components/mdx/index.ts +3 -0
  28. package/src/client/theme/components/mdx/mdx-components.css +128 -0
  29. package/src/client/theme/styles/markdown.css +8 -3
  30. package/src/client/theme/styles/variables.css +34 -9
  31. package/src/client/theme/ui/ErrorBoundary/ErrorBoundary.tsx +46 -0
  32. package/src/client/theme/ui/ErrorBoundary/index.ts +1 -0
  33. package/src/client/theme/ui/Layout/Layout.tsx +10 -11
  34. package/src/client/theme/ui/Layout/base.css +15 -3
  35. package/src/client/theme/ui/Link/Link.tsx +2 -2
  36. package/src/client/theme/ui/Link/LinkPreview.tsx +9 -14
  37. package/src/client/theme/ui/Link/link-preview.css +30 -27
  38. package/src/client/theme/ui/Navbar/Navbar.tsx +65 -17
  39. package/src/client/theme/ui/Navbar/Tabs.tsx +99 -0
  40. package/src/client/theme/ui/Navbar/navbar.css +119 -5
  41. package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +66 -57
  42. package/src/client/theme/ui/OnThisPage/toc.css +30 -10
  43. package/src/client/theme/ui/ProgressBar/ProgressBar.css +17 -0
  44. package/src/client/theme/ui/ProgressBar/ProgressBar.tsx +51 -0
  45. package/src/client/theme/ui/ProgressBar/index.ts +1 -0
  46. package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +11 -1
  47. package/src/client/theme/ui/Sidebar/Sidebar.tsx +97 -57
  48. package/src/client/theme/ui/Sidebar/sidebar.css +61 -67
  49. package/src/client/types.ts +12 -0
  50. package/src/node/config.ts +19 -1
  51. package/src/node/plugin/entry.ts +5 -1
  52. package/src/node/plugin/index.ts +2 -1
  53. package/src/node/routes/index.ts +13 -1
  54. package/src/node/routes/parser.ts +32 -7
  55. package/src/node/routes/types.ts +11 -1
  56. package/src/node/ssg/index.ts +2 -1
  57. package/src/node/ssg/options.ts +2 -0
@@ -232,7 +232,7 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
232
232
  onClick={handleClick}
233
233
  {...rest}
234
234
  />
235
- {shouldShowPreview && (
235
+ {preview.visible && shouldShowPreview && (
236
236
  <LinkPreview
237
237
  isVisible={preview.visible}
238
238
  title={preview.title}
@@ -376,7 +376,7 @@ export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
376
376
  onClick={handleClick}
377
377
  {...rest}
378
378
  />
379
- {shouldShowPreview && (
379
+ {preview.visible && shouldShowPreview && (
380
380
  <LinkPreview
381
381
  isVisible={preview.visible}
382
382
  title={preview.title}
@@ -17,35 +17,28 @@ export function LinkPreview({
17
17
  x,
18
18
  y,
19
19
  }: LinkPreviewProps) {
20
- const [mounted, setMounted] = useState(false);
21
20
  const ref = useRef<HTMLDivElement>(null);
22
21
  const [position, setPosition] = useState({ top: 0, left: 0 });
23
22
 
24
23
  useEffect(() => {
25
- setMounted(true);
26
- }, []);
27
-
28
- useEffect(() => {
29
- if (isVisible && ref.current) {
24
+ if (ref.current) {
30
25
  const rect = ref.current.getBoundingClientRect();
31
- const padding = 15;
26
+ const padding = 12;
32
27
 
33
28
  let top = y + padding;
34
29
  let left = x + padding;
35
30
 
36
31
  // Keep within viewport
37
- if (left + rect.width > window.innerWidth) {
32
+ if (left + rect.width > window.innerWidth - 20) {
38
33
  left = x - rect.width - padding;
39
34
  }
40
- if (top + rect.height > window.innerHeight) {
35
+ if (top + rect.height > window.innerHeight - 20) {
41
36
  top = y - rect.height - padding;
42
37
  }
43
38
 
44
39
  setPosition({ top, left });
45
40
  }
46
- }, [isVisible, x, y]);
47
-
48
- if (!mounted) return null;
41
+ }, [x, y, isVisible]);
49
42
 
50
43
  return createPortal(
51
44
  <div
@@ -56,8 +49,10 @@ export function LinkPreview({
56
49
  left: position.left,
57
50
  }}
58
51
  >
59
- <span className="boltdocs-link-preview-title">{title}</span>
60
- {summary && <p className="boltdocs-link-preview-summary">{summary}</p>}
52
+ <div className="boltdocs-link-preview-content">
53
+ <span className="boltdocs-link-preview-title">{title}</span>
54
+ {summary && <p className="boltdocs-link-preview-summary">{summary}</p>}
55
+ </div>
61
56
  </div>,
62
57
  document.body,
63
58
  );
@@ -1,22 +1,13 @@
1
1
  .boltdocs-link-preview {
2
2
  position: fixed;
3
3
  z-index: 1000;
4
- width: 320px;
5
- padding: 1rem;
6
- background-color: var(--ld-navbar-bg);
7
- backdrop-filter: blur(var(--ld-navbar-blur));
8
- -webkit-backdrop-filter: blur(var(--ld-navbar-blur));
9
- border: 1px solid var(--ld-border-subtle);
10
- border-radius: var(--ld-radius-md);
11
- box-shadow:
12
- 0 10px 25px -5px rgba(0, 0, 0, 0.1),
13
- 0 8px 10px -6px rgba(0, 0, 0, 0.1);
4
+ width: 260px;
14
5
  pointer-events: none;
15
6
  opacity: 0;
16
- transform: translateY(10px) scale(0.95);
7
+ transform: translateY(8px) scale(0.98);
17
8
  transition:
18
- opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
19
- transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
9
+ opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1),
10
+ transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);
20
11
  font-family: var(--ld-font-sans);
21
12
  }
22
13
 
@@ -25,38 +16,50 @@
25
16
  transform: translateY(0) scale(1);
26
17
  }
27
18
 
19
+ .boltdocs-link-preview-content {
20
+ padding: 0.85rem 1rem;
21
+ background-color: var(--ld-navbar-bg);
22
+ backdrop-filter: blur(20px);
23
+ -webkit-backdrop-filter: blur(20px);
24
+ border: 1px solid var(--ld-border-subtle);
25
+ border-radius: var(--ld-radius-lg);
26
+ box-shadow:
27
+ 0 10px 30px -10px rgba(0, 0, 0, 0.2),
28
+ 0 4px 10px -5px rgba(0, 0, 0, 0.1);
29
+ }
30
+
28
31
  .boltdocs-link-preview-title {
29
32
  display: block;
30
33
  font-weight: 600;
31
- font-size: 0.95rem;
34
+ font-size: 0.875rem;
32
35
  color: var(--ld-text-main);
33
- margin-bottom: 0.5rem;
34
- line-height: 1.4;
36
+ margin-bottom: 0.35rem;
37
+ line-height: 1.3;
35
38
  }
36
39
 
37
40
  .boltdocs-link-preview-summary {
38
41
  display: block;
39
- font-size: 0.85rem;
42
+ font-size: 0.775rem;
40
43
  color: var(--ld-text-muted);
41
44
  line-height: 1.5;
42
45
  display: -webkit-box;
43
- -webkit-line-clamp: 4;
44
- line-clamp: 4;
46
+ -webkit-line-clamp: 3;
47
+ line-clamp: 3;
45
48
  -webkit-box-orient: vertical;
46
49
  overflow: hidden;
47
50
  }
48
51
 
49
- /* Dark mode adjustments */
50
- [data-theme="dark"] .boltdocs-link-preview {
51
- background-color: var(--ld-navbar-bg);
52
- border-color: var(--ld-border-subtle);
53
- box-shadow:
54
- 0 20px 25px -5px rgba(0, 0, 0, 0.3),
55
- 0 10px 10px -5px rgba(0, 0, 0, 0.2);
52
+ /* Dark mode refinements */
53
+ [data-theme="dark"] .boltdocs-link-preview-content {
54
+ background-color: rgba(15, 15, 20, 0.8);
55
+ border-color: rgba(255, 255, 255, 0.08);
56
+ box-shadow:
57
+ 0 20px 40px -15px rgba(0, 0, 0, 0.5),
58
+ 0 8px 16px -8px rgba(0, 0, 0, 0.3);
56
59
  }
57
60
 
58
61
  [data-theme="dark"] .boltdocs-link-preview-title {
59
- color: #f8fafc;
62
+ color: #fff;
60
63
  }
61
64
 
62
65
  [data-theme="dark"] .boltdocs-link-preview-summary {
@@ -1,17 +1,17 @@
1
- import React, { useEffect, useState } from "react";
2
- import { Link } from "../Link";
3
- import { Book, ChevronDown } from "lucide-react";
1
+ import React, { Suspense, lazy } from "react";
2
+ import { Link, useLocation } from "react-router-dom";
3
+ import { ChevronDown } from "lucide-react";
4
4
  import { BoltdocsConfig } from "../../../../node/config";
5
5
  import { ComponentRoute } from "../../../types";
6
6
  import { LanguageSwitcher } from "../LanguageSwitcher";
7
7
  import { VersionSwitcher } from "../VersionSwitcher";
8
8
  import { ThemeToggle } from "../ThemeToggle";
9
- import { getStarsRepo } from "../../../utils";
10
9
  import { Discord } from "../../icons/discord";
11
10
  import { XformerlyTwitter } from "../../icons/twitter";
12
11
  import { GithubStars } from "./GithubStars";
12
+ import { Tabs } from "./Tabs";
13
13
 
14
- const SearchDialog = React.lazy(() =>
14
+ const SearchDialog = lazy(() =>
15
15
  import("../SearchDialog").then((m) => ({ default: m.SearchDialog })),
16
16
  );
17
17
 
@@ -43,13 +43,56 @@ export function Navbar({
43
43
  currentLocale?: string;
44
44
  currentVersion?: string;
45
45
  }) {
46
+ const location = useLocation();
47
+ const isHomePage = location.pathname === "/";
46
48
  const title = config.themeConfig?.title || "Boltdocs";
47
49
  const navItems = config.themeConfig?.navbar || [];
48
50
  const socialLinks = config.themeConfig?.socialLinks || [];
51
+ const hasTabs =
52
+ !isHomePage &&
53
+ config.themeConfig?.tabs &&
54
+ config.themeConfig.tabs.length > 0;
55
+
56
+ const leftItems = navItems.filter((item) => item.position !== "right");
57
+ const rightItems = navItems.filter((item) => item.position === "right");
58
+
59
+ const renderNavItem = (item: any, i: number) => {
60
+ const text = item.label || item.text || "";
61
+ const href = item.to || item.href || item.link || "";
62
+ const isExternal =
63
+ href.startsWith("http") || href.startsWith("//") || href.includes("://");
64
+
65
+ return (
66
+ <Link key={i} to={href} target={isExternal ? "_blank" : undefined}>
67
+ {text}
68
+ {isExternal && (
69
+ <span className="navbar-external-icon">
70
+ <svg
71
+ viewBox="0 0 24 24"
72
+ width="13"
73
+ height="13"
74
+ stroke="currentColor"
75
+ strokeWidth="2"
76
+ fill="none"
77
+ strokeLinecap="round"
78
+ strokeLinejoin="round"
79
+ >
80
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
81
+ <polyline points="15 3 21 3 21 9"></polyline>
82
+ <line x1="10" y1="14" x2="21" y2="3"></line>
83
+ </svg>
84
+ </span>
85
+ )}
86
+ </Link>
87
+ );
88
+ };
49
89
 
50
90
  return (
51
- <header className="boltdocs-navbar">
52
- <div className="navbar-container">
91
+ <header className={`boltdocs-navbar ${hasTabs ? "has-tabs" : ""}`}>
92
+ <div
93
+ className="navbar-container"
94
+ style={{ height: "var(--ld-navbar-height)" }}
95
+ >
53
96
  {/* LEFT SECTION */}
54
97
  <div className="navbar-left">
55
98
  <div className="navbar-logo">
@@ -87,22 +130,20 @@ export function Navbar({
87
130
  </div>
88
131
  ) : null}
89
132
 
90
- <nav className="navbar-links" aria-label="Top Navigation">
91
- {navItems.map((item, i) => (
92
- <Link key={i} to={item.link}>
93
- {item.text}
94
- </Link>
95
- ))}
133
+ <nav className="navbar-links" aria-label="Top Navigation Left">
134
+ {leftItems.map(renderNavItem)}
96
135
  </nav>
97
136
  </div>
98
137
 
99
138
  {/* RIGHT SECTION */}
100
139
  <div className="navbar-right">
101
- <React.Suspense
102
- fallback={<div className="navbar-search-placeholder" />}
103
- >
140
+ <nav className="navbar-links" aria-label="Top Navigation Right">
141
+ {rightItems.map(renderNavItem)}
142
+ </nav>
143
+
144
+ <Suspense fallback={<div className="navbar-search-placeholder" />}>
104
145
  <SearchDialog routes={routes || []} />
105
- </React.Suspense>
146
+ </Suspense>
106
147
 
107
148
  {config.i18n && currentLocale && allRoutes && (
108
149
  <LanguageSwitcher
@@ -140,6 +181,13 @@ export function Navbar({
140
181
  </div>
141
182
  </div>
142
183
  </div>
184
+
185
+ {hasTabs && config.themeConfig?.tabs && (
186
+ <Tabs
187
+ tabs={config.themeConfig.tabs}
188
+ routes={allRoutes || routes || []}
189
+ />
190
+ )}
143
191
  </header>
144
192
  );
145
193
  }
@@ -0,0 +1,99 @@
1
+ import React, { useEffect, useRef, useState } from "react";
2
+ import { useLocation } from "react-router-dom";
3
+ import { Link } from "../Link";
4
+ import * as Icons from "lucide-react";
5
+
6
+ interface TabConfig {
7
+ id: string;
8
+ text: string;
9
+ icon?: string;
10
+ }
11
+
12
+ interface TabsProps {
13
+ tabs: TabConfig[];
14
+ routes: any[];
15
+ }
16
+
17
+ export function Tabs({ tabs, routes }: TabsProps) {
18
+ const location = useLocation();
19
+ const containerRef = useRef<HTMLDivElement>(null);
20
+ const tabRefs = useRef<(HTMLAnchorElement | null)[]>([]);
21
+ const [indicatorStyle, setIndicatorStyle] = useState<React.CSSProperties>({
22
+ opacity: 0,
23
+ transform: "translateX(0) scaleX(0)",
24
+ width: 0,
25
+ });
26
+
27
+ const currentRoute = routes.find((r) => r.path === location.pathname);
28
+ const currentTabId = currentRoute?.tab?.toLowerCase();
29
+
30
+ // Find the active index - default to 0 if no tab detected
31
+ const activeIndex = tabs.findIndex((tab) =>
32
+ currentTabId ? currentTabId === tab.id.toLowerCase() : false
33
+ );
34
+
35
+ const finalActiveIndex = activeIndex === -1 ? 0 : activeIndex;
36
+
37
+ useEffect(() => {
38
+ const activeTab = tabRefs.current[finalActiveIndex];
39
+ if (activeTab) {
40
+ setIndicatorStyle({
41
+ opacity: 1,
42
+ width: activeTab.offsetWidth,
43
+ transform: `translateX(${activeTab.offsetLeft}px)`,
44
+ });
45
+ }
46
+ }, [finalActiveIndex, tabs, location.pathname]);
47
+
48
+ if (!tabs || tabs.length === 0) return null;
49
+
50
+ const renderTabIcon = (iconName?: string) => {
51
+ if (!iconName) return null;
52
+
53
+ if (iconName.trim().startsWith("<svg")) {
54
+ return (
55
+ <span
56
+ className="tab-icon svg-icon"
57
+ dangerouslySetInnerHTML={{ __html: iconName }}
58
+ />
59
+ );
60
+ }
61
+
62
+ const LucideIcon = (Icons as any)[iconName];
63
+ if (LucideIcon) {
64
+ return <LucideIcon size={16} className="tab-icon lucide-icon" />;
65
+ }
66
+
67
+ return <img src={iconName} alt="" className="tab-icon img-icon" />;
68
+ };
69
+
70
+ return (
71
+ <div className="boltdocs-tabs-container">
72
+ <div className="boltdocs-tabs" ref={containerRef}>
73
+ {tabs.map((tab, index) => {
74
+ const isActive = index === finalActiveIndex;
75
+ const firstRoute = routes.find(
76
+ (r) => r.tab && r.tab.toLowerCase() === tab.id.toLowerCase()
77
+ );
78
+ const linkTo = firstRoute ? firstRoute.path : "#";
79
+
80
+ return (
81
+ <Link
82
+ key={tab.id}
83
+ to={linkTo}
84
+ ref={(el) => {
85
+ tabRefs.current[index] = el;
86
+ }}
87
+ className={`boltdocs-tab-item ${isActive ? "active" : ""}`}
88
+ >
89
+ {renderTabIcon(tab.icon)}
90
+ <span>{tab.text}</span>
91
+ </Link>
92
+ );
93
+ })}
94
+ {/* Sliding Indicator */}
95
+ <div className="boltdocs-tab-indicator" style={indicatorStyle} />
96
+ </div>
97
+ </div>
98
+ );
99
+ }
@@ -3,19 +3,25 @@
3
3
  ═══════════════════════════════════════════════════════════ */
4
4
  .boltdocs-navbar {
5
5
  display: flex;
6
- align-items: center;
6
+ flex-direction: column;
7
7
  justify-content: center;
8
- height: var(--ld-navbar-height);
9
- padding: 0; /* Padding moved to container */
10
8
  background-color: var(--ld-navbar-bg);
11
- backdrop-filter: blur(var(--ld-navbar-blur));
12
- -webkit-backdrop-filter: blur(var(--ld-navbar-blur));
13
9
  border-bottom: 1px solid var(--ld-border-subtle);
14
10
  position: sticky;
15
11
  top: 0;
16
12
  z-index: 100;
17
13
  }
18
14
 
15
+ .boltdocs-navbar.has-tabs {
16
+ --ld-header-height: 102px !important;
17
+ height: 102px;
18
+ }
19
+
20
+ .boltdocs-navbar:not(.has-tabs) {
21
+ --ld-header-height: var(--ld-navbar-height);
22
+ height: var(--ld-navbar-height);
23
+ }
24
+
19
25
  .navbar-container {
20
26
  display: flex;
21
27
  align-items: center;
@@ -114,6 +120,16 @@
114
120
  transition:
115
121
  color 0.2s,
116
122
  background-color 0.2s;
123
+ display: flex;
124
+ align-items: center;
125
+ gap: 0.35rem;
126
+ }
127
+
128
+ .navbar-external-icon {
129
+ display: inline-flex;
130
+ align-items: center;
131
+ opacity: 0.6;
132
+ margin-top: 1px;
117
133
  }
118
134
 
119
135
  .navbar-links a:hover {
@@ -231,3 +247,101 @@
231
247
  width: 18px;
232
248
  height: 18px;
233
249
  }
250
+
251
+ /* Tabs Styles */
252
+ .boltdocs-tabs-container {
253
+ position: relative;
254
+ background: var(--ld-navbar-bg);
255
+ padding: 0;
256
+ height: 46px;
257
+ }
258
+
259
+ /* Border pseudo-element to allow indicator overlap */
260
+ .boltdocs-tabs-container::after {
261
+ content: "";
262
+ position: absolute;
263
+ bottom: 0px;
264
+ left: 0;
265
+ right: 0;
266
+ height: 1px;
267
+ background: var(--ld-border-subtle);
268
+ z-index: 10;
269
+ }
270
+
271
+ .boltdocs-tabs {
272
+ max-width: 1440px;
273
+ margin: 0 auto;
274
+ display: flex;
275
+ gap: 2rem;
276
+ overflow-x: auto;
277
+ scrollbar-width: none;
278
+ padding: 0 1.5rem;
279
+ position: relative;
280
+ height: 100%;
281
+ }
282
+
283
+ .boltdocs-tabs::-webkit-scrollbar {
284
+ display: none;
285
+ }
286
+
287
+ .boltdocs-tab-item {
288
+ padding: 0.85rem 0;
289
+ padding-bottom: calc(0.85rem + 1px);
290
+ font-size: 0.875rem;
291
+ font-weight: 500;
292
+ color: var(--ld-text-muted);
293
+ text-decoration: none;
294
+ position: relative;
295
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
296
+ white-space: nowrap;
297
+ display: flex;
298
+ align-items: center;
299
+ gap: 0.6rem;
300
+ opacity: 0.7;
301
+ z-index: 20;
302
+ }
303
+
304
+ .boltdocs-tab-item:hover {
305
+ color: var(--ld-text-main);
306
+ opacity: 1;
307
+ }
308
+
309
+ .tab-icon {
310
+ flex-shrink: 0;
311
+ display: flex;
312
+ align-items: center;
313
+ justify-content: center;
314
+ transition: transform 0.2s ease;
315
+ }
316
+
317
+ .boltdocs-tab-item:hover .tab-icon {
318
+ transform: translateY(-1px);
319
+ }
320
+
321
+ .tab-icon.lucide-icon,
322
+ .tab-icon.svg-icon svg {
323
+ width: 18px;
324
+ height: 18px;
325
+ stroke-width: 2.25px;
326
+ }
327
+
328
+ .boltdocs-tab-item.active {
329
+ color: var(--ld-text-main);
330
+ font-weight: 600;
331
+ opacity: 1;
332
+ text-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
333
+ }
334
+
335
+ .boltdocs-tab-indicator {
336
+ position: absolute;
337
+ bottom: 0px; /* Aligned with the border pseudo-element */
338
+ left: 0;
339
+ height: 3px;
340
+ background: var(--ld-color-primary);
341
+ border-radius: 2px 2px 0 0;
342
+ box-shadow: 0 -2px 15px var(--ld-color-primary-glow);
343
+ transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), width 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
344
+ z-index: 100; /* Higher than border pseudo-element */
345
+ pointer-events: none;
346
+ transform-origin: left;
347
+ }