@portosaur/theme 0.2.0 → 0.3.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/package.json +4 -2
- package/theme/DocCategoryGeneratedIndexPage/index.jsx +1 -1
- package/theme/MDXComponents.jsx +2 -1
- package/theme/components/Indent/index.jsx +10 -0
- package/theme/components/{NoteIndex → NoteCards}/index.jsx +10 -16
- package/theme/components/ScrollToTop/index.jsx +93 -0
- package/theme/components/ScrollToTop/styles.module.css +97 -0
- package/theme/components/Tooltip/index.jsx +59 -6
- package/theme/components/Tooltip/styles.module.css +13 -3
- package/theme/components/TopicList/index.jsx +31 -0
- package/theme/css/animations.css +61 -0
- package/theme/css/custom.css +16 -253
- package/theme/css/overrides/base.css +37 -0
- package/theme/css/overrides/footer.css +14 -0
- package/theme/css/overrides/markdown.css +62 -0
- package/theme/css/overrides/navbar.css +37 -0
- package/theme/css/overrides/pagination.css +64 -0
- package/theme/css/overrides/search.css +28 -0
- package/theme/css/{catppuccin.css → overrides/variables.css} +1 -1
- package/theme/pages/index.jsx +2 -0
- package/theme/pages/notes.jsx +3 -2
- package/theme/pages/tasks.jsx +135 -63
- package/theme/css/bootstrap.css +0 -5
- /package/theme/components/{NoteIndex → NoteCards}/styles.module.css +0 -0
- /package/theme/css/{tasks.css → tasks.module.css} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portosaur/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Theme components for Portosaur - React components, static assets etc.",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"author": "soymadip",
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
"@docusaurus/plugin-pwa": "^3.10.1",
|
|
32
32
|
"@docusaurus/preset-classic": "^3.10.1",
|
|
33
33
|
"@easyops-cn/docusaurus-search-local": "^0.55.1",
|
|
34
|
-
"@
|
|
34
|
+
"@fontsource-variable/lexend": "^5.2.11",
|
|
35
|
+
"@fontsource/alata": "^5.2.8",
|
|
36
|
+
"@portosaur/core": "^0.3.0",
|
|
35
37
|
"chalk": "^5.6.2",
|
|
36
38
|
"clsx": "^2.1.1",
|
|
37
39
|
"favicons": "^7.2.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import DocCategoryGeneratedIndexPage from "@theme-original/DocCategoryGeneratedIndexPage";
|
|
2
|
-
import NoteCards from "@portosaur/theme/theme/components/
|
|
2
|
+
import NoteCards from "@portosaur/theme/theme/components/NoteCards/index.jsx";
|
|
3
3
|
|
|
4
4
|
export default function DocCategoryGeneratedIndexPageWrapper(props) {
|
|
5
5
|
if (props.categoryGeneratedIndex?.title === "Notes") {
|
package/theme/MDXComponents.jsx
CHANGED
|
@@ -4,7 +4,8 @@ import Tabs from "@theme/Tabs";
|
|
|
4
4
|
import TabItem from "@theme/TabItem";
|
|
5
5
|
import { Pv, SrcPv } from "./components/Preview/index.js";
|
|
6
6
|
import Tooltip from "./components/Tooltip/index.js";
|
|
7
|
-
import NoteCards
|
|
7
|
+
import NoteCards from "./components/NoteCards/index.jsx";
|
|
8
|
+
import TopicList from "./components/TopicList/index.jsx";
|
|
8
9
|
const components = {
|
|
9
10
|
...MDXComponents,
|
|
10
11
|
Pv,
|
|
@@ -4,7 +4,6 @@ import Link from "@docusaurus/Link";
|
|
|
4
4
|
import { FaBook, FaChevronRight } from "react-icons/fa";
|
|
5
5
|
import Tooltip from "../Tooltip/index.jsx";
|
|
6
6
|
import { iconMap } from "../../config/iconMappings.jsx";
|
|
7
|
-
import DocCardList from "@theme/DocCardList";
|
|
8
7
|
import styles from "./styles.module.css";
|
|
9
8
|
|
|
10
9
|
function useNotes() {
|
|
@@ -13,7 +12,9 @@ function useNotes() {
|
|
|
13
12
|
return context
|
|
14
13
|
.keys()
|
|
15
14
|
.filter((path) => {
|
|
16
|
-
if (path === "./index.md" || path === "./index.mdx")
|
|
15
|
+
if (path === "./index.md" || path === "./index.mdx") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
17
18
|
const pathParts = path.split("/");
|
|
18
19
|
const isTopLevelFile =
|
|
19
20
|
pathParts.length === 2 && !path.match(/index\.mdx?$/);
|
|
@@ -87,14 +88,19 @@ export default function NoteCards() {
|
|
|
87
88
|
"docusaurus-plugin-content-docs",
|
|
88
89
|
);
|
|
89
90
|
|
|
90
|
-
if (!notes.length)
|
|
91
|
+
if (!notes.length) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
91
94
|
|
|
92
95
|
return (
|
|
93
96
|
<div className={styles.notesGrid} role="list">
|
|
94
97
|
{notes.map((note, index) => (
|
|
95
98
|
<NoteCard
|
|
96
99
|
key={note.slug}
|
|
97
|
-
{
|
|
100
|
+
title={note.title}
|
|
101
|
+
language={note.language}
|
|
102
|
+
slug={note.slug}
|
|
103
|
+
desc={note.desc}
|
|
98
104
|
index={index}
|
|
99
105
|
docsBasePath={docsBasePath}
|
|
100
106
|
/>
|
|
@@ -102,15 +108,3 @@ export default function NoteCards() {
|
|
|
102
108
|
</div>
|
|
103
109
|
);
|
|
104
110
|
}
|
|
105
|
-
|
|
106
|
-
export function TopicList({
|
|
107
|
-
desc = "Click on the links below to explore the topics.",
|
|
108
|
-
style = { marginTop: "-2.5rem", marginBottom: "2.5rem", textAlign: "center" },
|
|
109
|
-
}) {
|
|
110
|
-
return (
|
|
111
|
-
<div style={style}>
|
|
112
|
-
<p dangerouslySetInnerHTML={{ __html: desc }} />
|
|
113
|
-
<DocCardList />
|
|
114
|
-
</div>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
// Clear any existing timeout
|
|
13
|
+
if (timeoutRef.current) {
|
|
14
|
+
clearTimeout(timeoutRef.current);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Only start timer if not hovering
|
|
18
|
+
if (!isHovering) {
|
|
19
|
+
timeoutRef.current = setTimeout(() => {
|
|
20
|
+
setIsVisible(false);
|
|
21
|
+
}, hideDelay);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const handleScroll = () => {
|
|
27
|
+
const scrollTop =
|
|
28
|
+
window.pageYOffset || document.documentElement.scrollTop;
|
|
29
|
+
const isScrollingUp = scrollTop < lastScrollTopRef.current;
|
|
30
|
+
|
|
31
|
+
// Save the current scroll position
|
|
32
|
+
lastScrollTopRef.current = scrollTop;
|
|
33
|
+
|
|
34
|
+
// Show button when scrolling up past threshold
|
|
35
|
+
if (isScrollingUp && scrollTop > 300) {
|
|
36
|
+
setIsVisible(true);
|
|
37
|
+
startHideTimer();
|
|
38
|
+
} else {
|
|
39
|
+
setIsVisible(false);
|
|
40
|
+
if (timeoutRef.current) {
|
|
41
|
+
clearTimeout(timeoutRef.current);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Set up event listener
|
|
47
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
48
|
+
|
|
49
|
+
// Clean up
|
|
50
|
+
return () => {
|
|
51
|
+
window.removeEventListener("scroll", handleScroll);
|
|
52
|
+
if (timeoutRef.current) {
|
|
53
|
+
clearTimeout(timeoutRef.current);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}, [hideDelay, isHovering]);
|
|
57
|
+
|
|
58
|
+
const handleMouseEnter = () => {
|
|
59
|
+
setIsHovering(true);
|
|
60
|
+
if (timeoutRef.current) {
|
|
61
|
+
clearTimeout(timeoutRef.current);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const handleMouseLeave = () => {
|
|
66
|
+
setIsHovering(false);
|
|
67
|
+
startHideTimer();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const scrollToTop = () => {
|
|
71
|
+
const prefersReducedMotion = window.matchMedia(
|
|
72
|
+
"(prefers-reduced-motion: reduce)",
|
|
73
|
+
).matches;
|
|
74
|
+
|
|
75
|
+
window.scrollTo({
|
|
76
|
+
top: 0,
|
|
77
|
+
behavior: prefersReducedMotion ? "auto" : "smooth",
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<button
|
|
83
|
+
className={`${styles.scrollToTop} ${isVisible ? styles.visible : ""}`}
|
|
84
|
+
onClick={scrollToTop}
|
|
85
|
+
onMouseEnter={handleMouseEnter}
|
|
86
|
+
onMouseLeave={handleMouseLeave}
|
|
87
|
+
aria-label="Scroll to top"
|
|
88
|
+
title="Scroll to top"
|
|
89
|
+
>
|
|
90
|
+
<IoIosArrowUp />
|
|
91
|
+
</button>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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: var(--ifm-global-shadow-md);
|
|
15
|
+
border: none;
|
|
16
|
+
z-index: 100;
|
|
17
|
+
opacity: 0;
|
|
18
|
+
transform: translateY(20px) scale(0.9);
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
transition:
|
|
21
|
+
opacity 0.3s ease,
|
|
22
|
+
transform 0.3s ease,
|
|
23
|
+
background-color 0.3s ease;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.scrollToTop.visible {
|
|
27
|
+
opacity: 1;
|
|
28
|
+
transform: translateY(0) scale(1);
|
|
29
|
+
pointer-events: auto;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.scrollToTop:hover {
|
|
33
|
+
background-color: var(--ifm-color-primary-dark);
|
|
34
|
+
transform: translateY(-3px) scale(1.05);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.scrollToTop:active {
|
|
38
|
+
transform: translateY(-1px) scale(1.02);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.scrollToTop svg {
|
|
42
|
+
width: 28px;
|
|
43
|
+
height: 28px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.scrollToTop:hover svg {
|
|
47
|
+
transform: scale(1.1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Responsive adjustments */
|
|
51
|
+
@media (max-width: 768px) {
|
|
52
|
+
.scrollToTop {
|
|
53
|
+
width: 45px;
|
|
54
|
+
height: 45px;
|
|
55
|
+
bottom: 25px;
|
|
56
|
+
right: 25px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.scrollToTop svg {
|
|
60
|
+
width: 24px;
|
|
61
|
+
height: 24px;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@media (max-width: 480px) {
|
|
66
|
+
.scrollToTop {
|
|
67
|
+
width: 40px;
|
|
68
|
+
height: 40px;
|
|
69
|
+
bottom: 20px;
|
|
70
|
+
right: 50%;
|
|
71
|
+
transform: translateX(50%) translateY(20px) scale(0.9);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.scrollToTop.visible {
|
|
75
|
+
transform: translateX(50%) scale(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.scrollToTop:hover {
|
|
79
|
+
transform: translateX(50%) translateY(-3px) scale(1.05);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.scrollToTop:active {
|
|
83
|
+
transform: translateX(50%) translateY(-1px) scale(1.02);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.scrollToTop svg {
|
|
87
|
+
width: 22px;
|
|
88
|
+
height: 22px;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Accessibility preferences */
|
|
93
|
+
@media (prefers-reduced-motion: reduce) {
|
|
94
|
+
.scrollToTop {
|
|
95
|
+
transition: none;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
import styles from "./styles.module.css";
|
|
4
4
|
|
|
@@ -20,8 +20,10 @@ export default function Tooltip({
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const [isVisible, setIsVisible] = useState(false);
|
|
23
|
-
const [coords, setCoords] = useState({ top: 0, left: 0 });
|
|
23
|
+
const [coords, setCoords] = useState({ top: 0, left: 0, originalLeft: 0 });
|
|
24
|
+
const [arrowOffset, setArrowOffset] = useState(0);
|
|
24
25
|
const containerRef = useRef(null);
|
|
26
|
+
const tooltipRef = useRef(null);
|
|
25
27
|
|
|
26
28
|
const tooltipStyle = {
|
|
27
29
|
...(bg && { "--tooltip-color": bg }),
|
|
@@ -55,18 +57,68 @@ export default function Tooltip({
|
|
|
55
57
|
left = rect.left + rect.width / 2;
|
|
56
58
|
break;
|
|
57
59
|
}
|
|
58
|
-
setCoords({ top, left });
|
|
60
|
+
setCoords({ top, left, originalLeft: left });
|
|
61
|
+
setArrowOffset(0);
|
|
59
62
|
setIsVisible(true);
|
|
60
63
|
}, [position, gap]);
|
|
61
64
|
|
|
62
|
-
const hide = useCallback(() =>
|
|
65
|
+
const hide = useCallback(() => {
|
|
66
|
+
setIsVisible(false);
|
|
67
|
+
if (
|
|
68
|
+
typeof document !== "undefined" &&
|
|
69
|
+
containerRef.current &&
|
|
70
|
+
containerRef.current.contains(document.activeElement)
|
|
71
|
+
) {
|
|
72
|
+
document.activeElement.blur();
|
|
73
|
+
}
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (isVisible) {
|
|
78
|
+
window.addEventListener("scroll", hide, { passive: true });
|
|
79
|
+
return () => window.removeEventListener("scroll", hide);
|
|
80
|
+
}
|
|
81
|
+
}, [isVisible, hide]);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (
|
|
85
|
+
isVisible &&
|
|
86
|
+
tooltipRef.current &&
|
|
87
|
+
(position === "top" || position === "bottom")
|
|
88
|
+
) {
|
|
89
|
+
const rect = tooltipRef.current.getBoundingClientRect();
|
|
90
|
+
const padding = 12;
|
|
91
|
+
let newLeft = coords.originalLeft;
|
|
92
|
+
const halfWidth = rect.width / 2;
|
|
93
|
+
|
|
94
|
+
if (coords.originalLeft - halfWidth < padding) {
|
|
95
|
+
newLeft = halfWidth + padding;
|
|
96
|
+
} else if (
|
|
97
|
+
coords.originalLeft + halfWidth >
|
|
98
|
+
window.innerWidth - padding
|
|
99
|
+
) {
|
|
100
|
+
newLeft = window.innerWidth - halfWidth - padding;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (newLeft !== coords.left) {
|
|
104
|
+
setCoords((prev) => ({ ...prev, left: newLeft }));
|
|
105
|
+
setArrowOffset(newLeft - coords.originalLeft);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}, [isVisible, coords.originalLeft, coords.left, position]);
|
|
63
109
|
|
|
64
110
|
const tooltip =
|
|
65
111
|
isVisible && typeof document !== "undefined"
|
|
66
112
|
? createPortal(
|
|
67
113
|
<span
|
|
114
|
+
ref={tooltipRef}
|
|
68
115
|
className={`${styles.tooltip} ${styles[position]}`}
|
|
69
|
-
style={{
|
|
116
|
+
style={{
|
|
117
|
+
...tooltipStyle,
|
|
118
|
+
top: coords.top,
|
|
119
|
+
left: coords.left,
|
|
120
|
+
"--arrow-offset": `${arrowOffset}px`,
|
|
121
|
+
}}
|
|
70
122
|
role="tooltip"
|
|
71
123
|
>
|
|
72
124
|
{msg}
|
|
@@ -79,11 +131,12 @@ export default function Tooltip({
|
|
|
79
131
|
return (
|
|
80
132
|
<div
|
|
81
133
|
ref={containerRef}
|
|
82
|
-
className={`${styles.tooltipContainer} ${underline ? styles.hasUnderline : ""} ${className}`}
|
|
134
|
+
className={`${styles.tooltipContainer} ${underline ? styles.hasUnderline : ""} ${className} ${isVisible ? styles.isActive : ""}`}
|
|
83
135
|
onMouseEnter={show}
|
|
84
136
|
onMouseLeave={hide}
|
|
85
137
|
onFocus={show}
|
|
86
138
|
onBlur={hide}
|
|
139
|
+
onClick={() => (isVisible ? hide() : show())}
|
|
87
140
|
style={{ display: "contents" }}
|
|
88
141
|
>
|
|
89
142
|
{children}
|
|
@@ -21,7 +21,17 @@
|
|
|
21
21
|
transition: background-image 0.2s ease;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
@media (hover: hover) {
|
|
25
|
+
.hasUnderline:hover {
|
|
26
|
+
background-image: radial-gradient(
|
|
27
|
+
circle,
|
|
28
|
+
color-mix(in srgb, var(--ifm-color-primary-darker), transparent 10%) 1.2px,
|
|
29
|
+
transparent 1.5px
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.hasUnderline.isActive {
|
|
25
35
|
background-image: radial-gradient(
|
|
26
36
|
circle,
|
|
27
37
|
color-mix(in srgb, var(--ifm-color-primary-darker), transparent 10%) 1.2px,
|
|
@@ -83,7 +93,7 @@
|
|
|
83
93
|
|
|
84
94
|
.top .arrow {
|
|
85
95
|
top: 100%;
|
|
86
|
-
left: 50
|
|
96
|
+
left: calc(50% - var(--arrow-offset, 0px));
|
|
87
97
|
transform: translateX(-50%) rotate(45deg);
|
|
88
98
|
margin-top: -5.5px;
|
|
89
99
|
border-right: 1px solid var(--ifm-color-emphasis-300);
|
|
@@ -92,7 +102,7 @@
|
|
|
92
102
|
|
|
93
103
|
.bottom .arrow {
|
|
94
104
|
bottom: 100%;
|
|
95
|
-
left: 50
|
|
105
|
+
left: calc(50% - var(--arrow-offset, 0px));
|
|
96
106
|
transform: translateX(-50%) rotate(45deg);
|
|
97
107
|
margin-bottom: -5.5px;
|
|
98
108
|
border-left: 1px solid var(--ifm-color-emphasis-300);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import DocCardList from "@theme/DocCardList";
|
|
2
|
+
import { useCurrentSidebarCategory } from "@docusaurus/plugin-content-docs/client";
|
|
3
|
+
|
|
4
|
+
// List Topics inside Individual Notes
|
|
5
|
+
export default function TopicList({
|
|
6
|
+
children,
|
|
7
|
+
description = "Click on the links below to explore the topics.",
|
|
8
|
+
style = {
|
|
9
|
+
marginTop: "-2.5rem",
|
|
10
|
+
marginBottom: "2.5rem",
|
|
11
|
+
textAlign: "center",
|
|
12
|
+
},
|
|
13
|
+
}) {
|
|
14
|
+
let items;
|
|
15
|
+
try {
|
|
16
|
+
const category = useCurrentSidebarCategory();
|
|
17
|
+
items = category.items;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
// Fallback if not on a category page
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<br />
|
|
25
|
+
{(children || description) && (
|
|
26
|
+
<p style={style}>{children || description}</p>
|
|
27
|
+
)}
|
|
28
|
+
<DocCardList items={items} />
|
|
29
|
+
</>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* === Global Entrance Animations === */
|
|
2
|
+
|
|
3
|
+
@keyframes fadeIn {
|
|
4
|
+
from {
|
|
5
|
+
opacity: 0;
|
|
6
|
+
}
|
|
7
|
+
to {
|
|
8
|
+
opacity: 1;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@keyframes slideUp {
|
|
13
|
+
from {
|
|
14
|
+
opacity: 0;
|
|
15
|
+
transform: translateY(30px);
|
|
16
|
+
}
|
|
17
|
+
to {
|
|
18
|
+
opacity: 1;
|
|
19
|
+
transform: translateY(0);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@keyframes slideInLeft {
|
|
24
|
+
from {
|
|
25
|
+
opacity: 0;
|
|
26
|
+
transform: translateX(-40px);
|
|
27
|
+
}
|
|
28
|
+
to {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
transform: translateX(0);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@keyframes slideInRight {
|
|
35
|
+
from {
|
|
36
|
+
opacity: 0;
|
|
37
|
+
transform: translateX(40px);
|
|
38
|
+
}
|
|
39
|
+
to {
|
|
40
|
+
opacity: 1;
|
|
41
|
+
transform: translateX(0);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@keyframes scaleIn {
|
|
46
|
+
from {
|
|
47
|
+
opacity: 0;
|
|
48
|
+
transform: scale(0.95);
|
|
49
|
+
}
|
|
50
|
+
to {
|
|
51
|
+
opacity: 1;
|
|
52
|
+
transform: scale(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@keyframes skillAppear {
|
|
57
|
+
to {
|
|
58
|
+
opacity: 1;
|
|
59
|
+
transform: translateY(0);
|
|
60
|
+
}
|
|
61
|
+
}
|