portosaurus 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +116 -0
- package/bin/portosaurus.js +337 -0
- package/internal/notes/index.md +10 -0
- package/internal/sidebars.js +20 -0
- package/internal/src/components/AboutSection/index.js +67 -0
- package/internal/src/components/AboutSection/styles.module.css +492 -0
- package/internal/src/components/ContactSection/index.js +94 -0
- package/internal/src/components/ContactSection/styles.module.css +327 -0
- package/internal/src/components/ExperienceSection/index.js +25 -0
- package/internal/src/components/ExperienceSection/styles.module.css +180 -0
- package/internal/src/components/HeroSection/index.js +61 -0
- package/internal/src/components/HeroSection/styles.module.css +471 -0
- package/internal/src/components/NoteIndex/index.js +127 -0
- package/internal/src/components/NoteIndex/styles.module.css +143 -0
- package/internal/src/components/ProjectsSection/index.js +529 -0
- package/internal/src/components/ProjectsSection/styles.module.css +830 -0
- package/internal/src/components/ScrollToTop/index.js +98 -0
- package/internal/src/components/ScrollToTop/styles.module.css +96 -0
- package/internal/src/components/SocialLinks/index.js +129 -0
- package/internal/src/components/SocialLinks/styles.module.css +55 -0
- package/internal/src/components/Tooltip/index.js +30 -0
- package/internal/src/components/Tooltip/styles.module.css +92 -0
- package/internal/src/config/iconMappings.js +329 -0
- package/internal/src/config/metaTags.js +240 -0
- package/internal/src/config/prism.js +179 -0
- package/internal/src/config/sidebar.js +20 -0
- package/internal/src/css/bootstrap.css +6 -0
- package/internal/src/css/catppuccin.css +632 -0
- package/internal/src/css/custom.css +186 -0
- package/internal/src/css/tasks.css +868 -0
- package/internal/src/pages/index.js +98 -0
- package/internal/src/pages/notes.js +88 -0
- package/internal/src/pages/tasks.js +310 -0
- package/internal/src/utils/HashNavigation.js +250 -0
- package/internal/src/utils/appVersion.js +27 -0
- package/internal/src/utils/compileConfig.js +82 -0
- package/internal/src/utils/cssUtils.js +99 -0
- package/internal/src/utils/filterEnabledItems.js +21 -0
- package/internal/src/utils/generateFavicon.js +256 -0
- package/internal/src/utils/generateRobotsTxt.js +97 -0
- package/internal/src/utils/iconExtractor.js +159 -0
- package/internal/src/utils/imageDownloader.js +88 -0
- package/internal/src/utils/imageProcessor.js +134 -0
- package/internal/src/utils/linkShortner.js +0 -0
- package/internal/src/utils/updateTitle.js +107 -0
- package/package.json +51 -0
- package/template/.github/workflows/deploy.yml +57 -0
- package/template/README.md +70 -0
- package/template/blog/authors.yml +5 -0
- package/template/blog/welcome.md +10 -0
- package/template/config.js +233 -0
- package/template/notes/getting-started.md +7 -0
- package/template/static/README.md +33 -0
- package/utils/createConfig.js +227 -0
- package/utils/logger.js +19 -0
- package/utils/packageManager.js +88 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { IoIosArrowUp } from 'react-icons/io';
|
|
3
|
+
import styles from './styles.module.css';
|
|
4
|
+
|
|
5
|
+
export default function ScrollToTop({ hideDelay = 1500 }) {
|
|
6
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
7
|
+
const [isHovering, setIsHovering] = useState(false);
|
|
8
|
+
const timeoutRef = useRef(null);
|
|
9
|
+
const lastScrollTopRef = useRef(0);
|
|
10
|
+
|
|
11
|
+
const startHideTimer = () => {
|
|
12
|
+
|
|
13
|
+
// Clear any existing timeout
|
|
14
|
+
if (timeoutRef.current) {
|
|
15
|
+
clearTimeout(timeoutRef.current);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Only start timer if not hovering
|
|
19
|
+
if (!isHovering) {
|
|
20
|
+
timeoutRef.current = setTimeout(() => {
|
|
21
|
+
setIsVisible(false);
|
|
22
|
+
}, hideDelay);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
|
|
28
|
+
const handleScroll = () => {
|
|
29
|
+
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
30
|
+
const isScrollingUp = scrollTop < lastScrollTopRef.current;
|
|
31
|
+
|
|
32
|
+
// Save the current scroll position
|
|
33
|
+
lastScrollTopRef.current = scrollTop;
|
|
34
|
+
|
|
35
|
+
// Show button when scrolling up past threshold
|
|
36
|
+
if (isScrollingUp && scrollTop > 300) {
|
|
37
|
+
|
|
38
|
+
setIsVisible(true);
|
|
39
|
+
startHideTimer();
|
|
40
|
+
} else {
|
|
41
|
+
|
|
42
|
+
setIsVisible(false);
|
|
43
|
+
|
|
44
|
+
if (timeoutRef.current) {
|
|
45
|
+
clearTimeout(timeoutRef.current);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Set up event listener
|
|
51
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
52
|
+
|
|
53
|
+
// Clean up
|
|
54
|
+
return () => {
|
|
55
|
+
window.removeEventListener('scroll', handleScroll);
|
|
56
|
+
if (timeoutRef.current) {
|
|
57
|
+
clearTimeout(timeoutRef.current);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}, [hideDelay, isHovering]);
|
|
61
|
+
|
|
62
|
+
const handleMouseEnter = () => {
|
|
63
|
+
|
|
64
|
+
setIsHovering(true);
|
|
65
|
+
|
|
66
|
+
if (timeoutRef.current) {
|
|
67
|
+
clearTimeout(timeoutRef.current);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const handleMouseLeave = () => {
|
|
72
|
+
|
|
73
|
+
setIsHovering(false);
|
|
74
|
+
startHideTimer();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const scrollToTop = () => {
|
|
78
|
+
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
79
|
+
|
|
80
|
+
window.scrollTo({
|
|
81
|
+
top: 0,
|
|
82
|
+
behavior: prefersReducedMotion ? 'auto' : 'smooth'
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<button
|
|
88
|
+
className={`${styles.scrollToTop} ${isVisible ? styles.visible : ''}`}
|
|
89
|
+
onClick={scrollToTop}
|
|
90
|
+
onMouseEnter={handleMouseEnter}
|
|
91
|
+
onMouseLeave={handleMouseLeave}
|
|
92
|
+
aria-label="Scroll to top"
|
|
93
|
+
title="Scroll to top"
|
|
94
|
+
>
|
|
95
|
+
<IoIosArrowUp />
|
|
96
|
+
</button>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
.scrollToTop {
|
|
2
|
+
position: fixed;
|
|
3
|
+
bottom: 30px;
|
|
4
|
+
right: 30px;
|
|
5
|
+
background-color: var(--ifm-color-primary);
|
|
6
|
+
color: var(--ifm-background-color);
|
|
7
|
+
width: 50px;
|
|
8
|
+
height: 50px;
|
|
9
|
+
border-radius: 50%;
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
15
|
+
border: none;
|
|
16
|
+
z-index: 100;
|
|
17
|
+
opacity: 0;
|
|
18
|
+
transform: translateY(20px) scale(0.9);
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
transition: opacity 0.3s ease, transform 0.3s ease, background-color 0.3s ease;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.scrollToTop.visible {
|
|
24
|
+
opacity: 1;
|
|
25
|
+
transform: translateY(0) scale(1);
|
|
26
|
+
pointer-events: auto;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.scrollToTop:hover {
|
|
30
|
+
background-color: var(--ifm-color-primary-dark);
|
|
31
|
+
transform: translateY(-3px) scale(1.05);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.scrollToTop:active {
|
|
35
|
+
transform: translateY(-1px) scale(1.02);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.scrollToTop svg {
|
|
39
|
+
width: 28px;
|
|
40
|
+
height: 28px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.scrollToTop:hover svg {
|
|
44
|
+
transform: scale(1.1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Responsive adjustments */
|
|
48
|
+
@media (max-width: 768px) {
|
|
49
|
+
|
|
50
|
+
.scrollToTop {
|
|
51
|
+
width: 45px;
|
|
52
|
+
height: 45px;
|
|
53
|
+
bottom: 25px;
|
|
54
|
+
right: 25px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.scrollToTop svg {
|
|
58
|
+
width: 24px;
|
|
59
|
+
height: 24px;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@media (max-width: 480px) {
|
|
64
|
+
|
|
65
|
+
.scrollToTop {
|
|
66
|
+
width: 40px;
|
|
67
|
+
height: 40px;
|
|
68
|
+
bottom: 20px;
|
|
69
|
+
right: 50%;
|
|
70
|
+
transform: translateX(50%) translateY(20px) scale(0.9);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.scrollToTop.visible {
|
|
74
|
+
transform: translateX(50%) scale(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.scrollToTop:hover {
|
|
78
|
+
transform: translateX(50%) translateY(-3px) scale(1.05);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.scrollToTop:active {
|
|
82
|
+
transform: translateX(50%) translateY(-1px) scale(1.02);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.scrollToTop svg {
|
|
86
|
+
width: 22px;
|
|
87
|
+
height: 22px;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Accessibility preferences */
|
|
92
|
+
@media (prefers-reduced-motion: reduce) {
|
|
93
|
+
.scrollToTop {
|
|
94
|
+
transition: none;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
+
import styles from './styles.module.css';
|
|
3
|
+
import { FaQuestionCircle } from 'react-icons/fa';
|
|
4
|
+
|
|
5
|
+
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
6
|
+
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
7
|
+
|
|
8
|
+
import Tooltip from '@site/src/components/Tooltip';
|
|
9
|
+
import { iconMap } from '@site/src/config/iconMappings';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
// Default icon & icon
|
|
13
|
+
const DEFAULT_ICON = FaQuestionCircle;
|
|
14
|
+
const DEFAULT_COLOR = 'var(--ifm-color-primary)';
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export default function SocialIcons({ showAll = false }) {
|
|
18
|
+
const { siteConfig } = useDocusaurusContext();
|
|
19
|
+
const { customFields } = siteConfig;
|
|
20
|
+
const isBrowser = useIsBrowser();
|
|
21
|
+
|
|
22
|
+
const [animationDelays, setAnimationDelays] = useState({});
|
|
23
|
+
|
|
24
|
+
const allSocialLinks = customFields.socialLinks.links || [];
|
|
25
|
+
|
|
26
|
+
// FIX: `to prevent unnecessary recalculations`
|
|
27
|
+
const socialLinks = useMemo(() => {
|
|
28
|
+
return showAll
|
|
29
|
+
? allSocialLinks
|
|
30
|
+
: allSocialLinks.filter(link => link.pin);
|
|
31
|
+
}, [allSocialLinks, showAll]);
|
|
32
|
+
|
|
33
|
+
// Calculate delays based on screen size
|
|
34
|
+
const calculateDelays = useCallback(() => {
|
|
35
|
+
if (!isBrowser) return {};
|
|
36
|
+
|
|
37
|
+
const isTablet = window.innerWidth <= 768;
|
|
38
|
+
const isMobile = window.innerWidth <= 480;
|
|
39
|
+
const delays = {};
|
|
40
|
+
|
|
41
|
+
const baseDelay = isMobile ? 0.7 : (isTablet ? 0.9 : 1.3);
|
|
42
|
+
const incrementDelay = 0.1;
|
|
43
|
+
|
|
44
|
+
socialLinks.forEach((_, index) => {
|
|
45
|
+
delays[index] = `${baseDelay + (index * incrementDelay)}s`;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return delays;
|
|
49
|
+
}, [isBrowser, socialLinks]);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!isBrowser) return;
|
|
53
|
+
|
|
54
|
+
// Set initial delays
|
|
55
|
+
setAnimationDelays(calculateDelays());
|
|
56
|
+
|
|
57
|
+
const handleResize = () => {
|
|
58
|
+
setAnimationDelays(calculateDelays());
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
window.addEventListener('resize', handleResize);
|
|
62
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
63
|
+
}, [isBrowser, calculateDelays]);
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
// Get icon component and color
|
|
67
|
+
const getIconDetails = (iconName) => {
|
|
68
|
+
|
|
69
|
+
if (!iconName) {
|
|
70
|
+
return {
|
|
71
|
+
icon: DEFAULT_ICON,
|
|
72
|
+
color: DEFAULT_COLOR
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const formattedIconName = iconName.toLowerCase();
|
|
77
|
+
const iconDetails = iconMap[formattedIconName];
|
|
78
|
+
|
|
79
|
+
if (!iconDetails) {
|
|
80
|
+
return {
|
|
81
|
+
icon: DEFAULT_ICON,
|
|
82
|
+
color: DEFAULT_COLOR
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
icon: iconDetails.icon,
|
|
88
|
+
color: iconDetails.color || DEFAULT_COLOR
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
if (socialLinks.length === 0) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className={styles.socialIcons}>
|
|
98
|
+
{
|
|
99
|
+
socialLinks.map((social, index) => {
|
|
100
|
+
const { icon: IconComponent, color: iconColor } = getIconDetails(social.icon);
|
|
101
|
+
const href = social.url || '#';
|
|
102
|
+
const displayColor = social.color || iconColor;
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<Tooltip
|
|
106
|
+
key={index}
|
|
107
|
+
content={social.desc || social.icon || 'Link'}
|
|
108
|
+
position="top"
|
|
109
|
+
color={displayColor}
|
|
110
|
+
>
|
|
111
|
+
<a
|
|
112
|
+
href={href}
|
|
113
|
+
target="_blank"
|
|
114
|
+
rel="noopener noreferrer"
|
|
115
|
+
className={styles.socialLink}
|
|
116
|
+
style={{
|
|
117
|
+
'--hover-color': displayColor,
|
|
118
|
+
animationDelay: animationDelays[index] || '0s'
|
|
119
|
+
}}
|
|
120
|
+
aria-label={social.icon || 'social link'}
|
|
121
|
+
>
|
|
122
|
+
<IconComponent size={24} />
|
|
123
|
+
</a>
|
|
124
|
+
</Tooltip>
|
|
125
|
+
);
|
|
126
|
+
})}
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
|
|
2
|
+
/* Animation */
|
|
3
|
+
@keyframes fadeIn {
|
|
4
|
+
from { opacity: 0; }
|
|
5
|
+
to { opacity: 1; }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
.socialIcons {
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: 18px;
|
|
13
|
+
height: 38px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.socialLink {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
color: var(--ifm-color-primary);
|
|
21
|
+
transition: color 0.3s, transform 0.2s;
|
|
22
|
+
position: relative;
|
|
23
|
+
animation: fadeIn 0.3s ease-out forwards;
|
|
24
|
+
opacity: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.socialLink:hover {
|
|
28
|
+
opacity: 0.9;
|
|
29
|
+
transform: translateY(-4px) scale(1.15);
|
|
30
|
+
text-decoration: none;
|
|
31
|
+
color: var(--hover-color, var(--ifm-color-primary-dark));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
/* Responsive */
|
|
36
|
+
@media (max-width: 768px) {
|
|
37
|
+
.socialIcons {
|
|
38
|
+
justify-content: center;
|
|
39
|
+
width: 100%;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.socialLink {
|
|
43
|
+
margin: 0 9px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Accessibility */
|
|
49
|
+
@media (prefers-reduced-motion: reduce) {
|
|
50
|
+
.socialLink {
|
|
51
|
+
animation: none !important;
|
|
52
|
+
opacity: 1;
|
|
53
|
+
transition: none;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import styles from './styles.module.css';
|
|
3
|
+
|
|
4
|
+
export default function Tooltip({ children, content, position = 'top', color }) {
|
|
5
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
6
|
+
|
|
7
|
+
const tooltipStyle = color ? { '--tooltip-color': color } : {};
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
className={styles.tooltipContainer}
|
|
12
|
+
onMouseEnter={() => setIsVisible(true)}
|
|
13
|
+
onMouseLeave={() => setIsVisible(false)}
|
|
14
|
+
onFocus={() => setIsVisible(true)}
|
|
15
|
+
onBlur={() => setIsVisible(false)}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
{isVisible && (
|
|
19
|
+
<div
|
|
20
|
+
className={`${styles.tooltip} ${styles[position]}`}
|
|
21
|
+
style={tooltipStyle}
|
|
22
|
+
role="tooltip"
|
|
23
|
+
>
|
|
24
|
+
{content}
|
|
25
|
+
<div className={styles.arrow} />
|
|
26
|
+
</div>
|
|
27
|
+
)}
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
.tooltipContainer {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
cursor: pointer;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.tooltip {
|
|
8
|
+
position: absolute;
|
|
9
|
+
background-color: var(--tooltip-color, var(--ifm-background-surface-color));
|
|
10
|
+
color: var(--ifm-font-color-base);
|
|
11
|
+
padding: 6px 10px;
|
|
12
|
+
border-radius: 4px;
|
|
13
|
+
font-size: 0.8rem;
|
|
14
|
+
font-weight: 400;
|
|
15
|
+
white-space: nowrap;
|
|
16
|
+
z-index: 10;
|
|
17
|
+
opacity: 0;
|
|
18
|
+
animation: tooltipFadeIn 0.15s ease-out forwards;
|
|
19
|
+
box-shadow: none;
|
|
20
|
+
pointer-events: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.arrow {
|
|
24
|
+
position: absolute;
|
|
25
|
+
width: 8px;
|
|
26
|
+
height: 8px;
|
|
27
|
+
background-color: inherit;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Positioning variations */
|
|
31
|
+
.top {
|
|
32
|
+
bottom: 100%;
|
|
33
|
+
left: 50%;
|
|
34
|
+
transform: translateX(-50%) translateY(-10px);
|
|
35
|
+
margin-bottom: 4px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.top .arrow {
|
|
39
|
+
top: 100%;
|
|
40
|
+
left: 50%;
|
|
41
|
+
transform: translateX(-50%) rotate(45deg);
|
|
42
|
+
margin-top: -4px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.bottom {
|
|
46
|
+
top: 100%;
|
|
47
|
+
left: 50%;
|
|
48
|
+
transform: translateX(-50%) translateY(6px);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.bottom .arrow {
|
|
52
|
+
bottom: 100%;
|
|
53
|
+
left: 50%;
|
|
54
|
+
transform: translateX(-50%) rotate(45deg);
|
|
55
|
+
margin-bottom: -4px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.left {
|
|
59
|
+
right: 100%;
|
|
60
|
+
top: 50%;
|
|
61
|
+
transform: translateY(-50%) translateX(-6px);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.left .arrow {
|
|
65
|
+
right: -4px;
|
|
66
|
+
top: 50%;
|
|
67
|
+
transform: translateY(-50%) rotate(45deg);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.right {
|
|
71
|
+
left: 100%;
|
|
72
|
+
top: 50%;
|
|
73
|
+
transform: translateY(-50%) translateX(6px);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.right .arrow {
|
|
77
|
+
left: -4px;
|
|
78
|
+
top: 50%;
|
|
79
|
+
transform: translateY(-50%) rotate(45deg);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@keyframes tooltipFadeIn {
|
|
83
|
+
from { opacity: 0; }
|
|
84
|
+
to { opacity: 1; }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Accessibility */
|
|
88
|
+
@media (prefers-reduced-motion: reduce) {
|
|
89
|
+
.tooltip {
|
|
90
|
+
animation: none;
|
|
91
|
+
}
|
|
92
|
+
}
|