-
+
+
-
- {props.title}
-
-
- {props.category}
+ {props.title}
+
+
+
+
+
+
+ {props.ProjectHeader.tags}
+
+
+
+
);
};
+
+
export default ProjectSingle;
diff --git a/components/projects/ProjectTabs.jsx b/components/projects/ProjectTabs.jsx
new file mode 100644
index 0000000..36e5108
--- /dev/null
+++ b/components/projects/ProjectTabs.jsx
@@ -0,0 +1,108 @@
+import { useState, useEffect } from "react";
+import NotebookViewer from "./NotebookViewer";
+import ReactMarkdown from "react-markdown";
+
+const DEFAULT_NOTEBOOK_TABS = [
+ "Overview",
+ "Key Impact",
+ "Challenge Highlights",
+ "Goal",
+ "Tools & Technologies",
+ "Implementation"
+];
+
+export default function ProjectTabs({ project }) {
+ const tabs = project.ProjectTabs?.length > 0
+ ? project.ProjectTabs
+ : project.type === "notebook"
+ ? DEFAULT_NOTEBOOK_TABS
+ : [];
+
+ const [activeTab, setActiveTab] = useState(tabs[0]);
+
+ useEffect(() => {
+ setActiveTab(tabs[0]);
+ }, [project]);
+
+ return (
+
+ {/* Tab bar */}
+
+
+ {tabs.map((tab) => (
+
+ ))}
+
+
+
+ {/* Tab content */}
+
+ {activeTab in project.ProjectInfo && (
+ Array.isArray(project.ProjectInfo[activeTab]) ? (
+ activeTab === "Tools & Technologies" ? (
+
{project.ProjectInfo[activeTab].join(", ")}
+ ) : (
+
+ {project.ProjectInfo[activeTab].map((item) => (
+ -
+
+ {item.title}
+ : {item.details}
+
+ ))}
+
+ )
+ ) : (
+
+
{children}
,
+ h1: ({ children }) => {children}
,
+ h2: ({ children }) => {children}
,
+ h3: ({ children }) => {children}
,
+ ul: ({ children }) => (
+
+ ),
+ li: ({ children }) => (
+
+ {children}
+
+ ),
+ ol: ({ children }) => {children}
,
+ a: ({ href, children }) => (
+
+ {children}
+
+ ),
+ strong: ({ children }) => {children},
+ em: ({ children }) => {children},
+ }}
+ >
+ {project.ProjectInfo[activeTab]}
+
+
+ )
+ )}
+
+ {activeTab === "Implementation" && project.Notebook?.file && (
+
+ )}
+
+
+ );
+}
diff --git a/components/projects/ProjectsFilter.jsx b/components/projects/ProjectsFilter.jsx
deleted file mode 100644
index 95bacf5..0000000
--- a/components/projects/ProjectsFilter.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-const selectOptions = [
- 'Web Application',
- 'Mobile Application',
- 'UI/UX Design',
- 'Branding',
-];
-
-function ProjectsFilter({ setSelectProject }) {
- return (
-
- );
-}
-
-export default ProjectsFilter;
diff --git a/components/projects/ProjectsGrid.jsx b/components/projects/ProjectsGrid.jsx
index 05f700b..6eda0ce 100644
--- a/components/projects/ProjectsGrid.jsx
+++ b/components/projects/ProjectsGrid.jsx
@@ -1,122 +1,68 @@
+// components/projects/ProjectsGrid.js
import { useState } from 'react';
-import { FiSearch } from 'react-icons/fi';
import ProjectSingle from './ProjectSingle';
import { projectsData } from '../../data/projectsData';
-import ProjectsFilter from './ProjectsFilter';
+import FilterDropdown from '../shared/FilterDropdown';
+import SearchInput from '../shared/SearchInput';
-function ProjectsGrid() {
- const [searchProject, setSearchProject] = useState();
- const [selectProject, setSelectProject] = useState();
+const selectOptions = [
+ 'Data Science',
+ 'Generative AI',
+ 'Data Engineering',
+ 'Software Engineering',
+ 'Web Application',
+];
- // @todo - To be fixed
- // const searchProjectsByTitle = projectsData.filter((item) => {
- // const result = item.title
- // .toLowerCase()
- // .includes(searchProject.toLowerCase())
- // ? item
- // : searchProject == ''
- // ? item
- // : '';
- // return result;
- // });
+function ProjectsGrid() {
+ const [searchProject, setSearchProject] = useState('');
+ const [selectProject, setSelectProject] = useState('all');
- const selectProjectsByCategory = projectsData.filter((item) => {
- let category =
- item.category.charAt(0).toUpperCase() + item.category.slice(1);
- return category.includes(selectProject);
- });
+ const filteredProjects = projectsData.filter((item) => {
+ const matchesCategory =
+ selectProject === 'all' || item.category.toLowerCase() === selectProject.toLowerCase();
+ const matchesSearch =
+ item.title.toLowerCase().includes(searchProject.toLowerCase());
+ return matchesCategory && matchesSearch;
+ });
- return (
-
-
-
- Projects portfolio
-
-
+ return (
+
+
+
+ Projects Portfolio
+
+
-
-
- Search projects by title or filter by category
-
-
-
-
-
-
- {
- setSearchProject(e.target.value);
- }}
- className="
- ont-general-medium
- pl-3
- pr-1
- sm:px-4
- py-2
- border
- border-gray-200
- dark:border-secondary-dark
- rounded-lg
- text-sm
- sm:text-md
- bg-secondary-light
- dark:bg-ternary-dark
- text-primary-dark
- dark:text-ternary-light
- "
- id="name"
- name="name"
- type="search"
- required=""
- placeholder="Search Projects"
- aria-label="Name"
- />
-
+
+
+ Search projects by title or filter by category
+
-
-
-
+
+
-
- {selectProject
- ? selectProjectsByCategory.map((project, index) => {
- return
;
- })
- : projectsData.map((project, index) => (
-
- ))}
-
-
- );
+
+ {filteredProjects.map((project, index) => (
+
+ ))}
+
+
+ );
}
export default ProjectsGrid;
diff --git a/components/projects/RelatedProjects.jsx b/components/projects/RelatedProjects.jsx
index f1a3c83..64ba77b 100644
--- a/components/projects/RelatedProjects.jsx
+++ b/components/projects/RelatedProjects.jsx
@@ -1,55 +1,58 @@
import Image from 'next/image';
-import { v4 as uuidv4 } from 'uuid';
+import Link from 'next/link';
+import { projectsData } from '../../data/projectsData';
+import { FiTag } from 'react-icons/fi';
-const RelatedProject = {
- title: 'Related Projects',
- Projects: [
- {
- id: uuidv4(),
- title: 'Mobile UI',
- img: '/images/ui-project-2.jpg',
- },
- {
- id: uuidv4(),
- title: 'Web Application',
- img: '/images/mobile-project-1.jpg',
- },
- {
- id: uuidv4(),
- title: 'UI Design',
- img: '/images/web-project-1.jpg',
- },
- {
- id: uuidv4(),
- title: 'Kabul Mobile App UI',
- img: '/images/mobile-project-2.jpg',
- },
- ],
-};
+function RelatedProjects({ currentProject, isBlog = false }) {
+ if (!currentProject) return null;
-function RelatedProjects() {
- return (
-
-
- {RelatedProject.title}
-
+ const relatedProjects = projectsData.filter(
+ (project) =>
+ project.category === currentProject.category &&
+ project.id !== currentProject.id
+ );
-
- {RelatedProject.Projects.map((project) => {
- return (
-
- );
- })}
-
-
- );
+ if (relatedProjects.length === 0) return null;
+
+ return (
+
+
+
+ Related Projects
+
+
+
+ {relatedProjects.map((project) => (
+
+
+
+
+
+
+
+
+ {project.title}
+
+
+
+
+ {project.ProjectHeader.tags}
+
+
+
+
+
+ ))}
+
+
+
+
+ );
}
export default RelatedProjects;
diff --git a/components/projects/layouts/DefaultProjectLayout.jsx b/components/projects/layouts/DefaultProjectLayout.jsx
new file mode 100644
index 0000000..25c3bcf
--- /dev/null
+++ b/components/projects/layouts/DefaultProjectLayout.jsx
@@ -0,0 +1,18 @@
+// components/projects/layouts/DefaultProjectLayout.jsx
+
+import ProjectHeader from "../ProjectHeader";
+import ProjectTabs from "../ProjectTabs";
+import BackButton from "../../reusable/BackButton";
+import RelatedProjects from "../RelatedProjects";
+import Container from "../../layout/Container";
+
+export default function DefaultProjectLayout({ project }) {
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/components/projects/layouts/NotebookProjectLayout.jsx b/components/projects/layouts/NotebookProjectLayout.jsx
new file mode 100644
index 0000000..0183116
--- /dev/null
+++ b/components/projects/layouts/NotebookProjectLayout.jsx
@@ -0,0 +1,36 @@
+import ProjectHeader from "../ProjectHeader";
+import ProjectTabs from "../ProjectTabs";
+import BackButton from "../../reusable/BackButton";
+import RelatedProjects from "../RelatedProjects";
+import Container from '../../layout/Container';
+import AppFooter from '../../shared/AppFooter';
+import PagesMetaHead from '../../PagesMetaHead';
+import AppHeader from '../../shared/AppHeader';
+
+
+export default function NotebookProjectLayout({ project, isBlog=false}) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+
+ );
+}
+
+
+
+
+
diff --git a/components/projects/layouts/WebAppProjectLayout.jsx b/components/projects/layouts/WebAppProjectLayout.jsx
new file mode 100644
index 0000000..a967139
--- /dev/null
+++ b/components/projects/layouts/WebAppProjectLayout.jsx
@@ -0,0 +1,26 @@
+import ProjectHeader from "../ProjectHeader";
+import ProjectTabs from "../ProjectTabs";
+import BackButton from "../../reusable/BackButton";
+import RelatedProjects from "../RelatedProjects";
+
+export default function WebAppProjectLayout({ project }) {
+ return (
+
+
+
+
+ {/* Web Apps : just a demo */}
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/reusable/BackButton.jsx b/components/reusable/BackButton.jsx
new file mode 100644
index 0000000..979c073
--- /dev/null
+++ b/components/reusable/BackButton.jsx
@@ -0,0 +1,36 @@
+// components/reusable/BackButton.jsx
+import { useEffect, useState } from 'react';
+import { useRouter } from 'next/router';
+import { ChevronLeft } from 'lucide-react';
+
+const BackButton = () => {
+ const router = useRouter();
+ const [visible, setVisible] = useState(true);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ setVisible(window.scrollY < 100); // Hide if user scrolls down
+ };
+ window.addEventListener('scroll', handleScroll);
+ return () => window.removeEventListener('scroll', handleScroll);
+ }, []);
+
+ return (
+
+ );
+};
+
+export default BackButton;
diff --git a/components/shared/AppBanner.jsx b/components/shared/AppBanner.jsx
index 6ecb47d..42039c7 100644
--- a/components/shared/AppBanner.jsx
+++ b/components/shared/AppBanner.jsx
@@ -2,18 +2,21 @@ import { motion } from 'framer-motion';
import Image from 'next/image';
import { FiArrowDownCircle } from 'react-icons/fi';
import useThemeSwitcher from '../../hooks/useThemeSwitcher';
+import Container from '../layout/Container';
function AppBanner() {
const [activeTheme] = useThemeSwitcher();
return (
+
+
+
-
+ className="flex flex-col sm:justify-between items-center sm:flex-row mt-5 md:mt-2">
+
- Hi, Iam Stoman
+ {"Hi, I'm Amadou"}
- A Full-Stack Developer & Design Enthusiast
+ A Data Scientist, AI Engineer, Data Engineer & Open Source Enthusiast
@@ -65,19 +68,23 @@ function AppBanner() {
initial={{ opacity: 0, y: -180 }}
animate={{ opacity: 1, y: 0 }}
transition={{ ease: 'easeInOut', duration: 0.9, delay: 0.2 }}
- className="w-full sm:w-2/3 text-right float-right mt-8 sm:mt-0"
+ className="w-full sm:w-2/3 text-right float-right mt-8 sm:mt-2"
>
-
+
+
+
);
}
diff --git a/components/shared/AppFooter.jsx b/components/shared/AppFooter.jsx
index 8662d2a..d8baa8d 100644
--- a/components/shared/AppFooter.jsx
+++ b/components/shared/AppFooter.jsx
@@ -4,66 +4,56 @@ import {
FiLinkedin,
FiGlobe,
FiYoutube,
-} from 'react-icons/fi';
-import AppFooterCopyright from './AppFooterCopyright';
-
-const socialLinks = [
- {
- id: 1,
- icon:
,
- url: 'https://www.stoman.me/',
- },
- {
- id: 2,
- icon:
,
- url: 'https://github.com/realstoman',
- },
- {
- id: 3,
- icon:
,
- url: 'https://twitter.com/realstoman',
- },
- {
- id: 4,
- icon:
,
- url: 'https://www.linkedin.com/in/realstoman',
- },
- {
- id: 5,
- icon:
,
- url: 'https://www.youtube.com/c/realstoman',
- },
-];
-
-function AppFooter() {
+ } from 'react-icons/fi';
+ import AppFooterCopyright from './AppFooterCopyright';
+
+ export default function AppFooter({ isBlog = false }) {
+ const maxWidth = isBlog ? 'max-w-[1800px]' : 'max-w-7xl';
+
return (
-
-
- {/* Footer social links */}
-
-
-
+
+
+
+
+
+
+
+ Follow me
+
+
+ {[
+
+ { id: 1, icon: , url: 'https://github.com/AmadouMamane' },
+ { id: 2, icon: , url: 'https://fr.linkedin.com/in/amadoumamane' },
+ { id: 3, icon: , url: 'https://x.com/AmadouMamaneX' },
+ { id: 6, icon: , url: '' },
+ { id: 7, icon: , url: '' },
+ ].map((link) => (
+
+ {link.icon}
+
+ ))}
+
+
+
+
+ {"I'm always learning, building, and sharing — come along for the journey"}
+
+
+
+
+
- );
-}
-export default AppFooter;
+
+
+
+ );
+ }
+
\ No newline at end of file
diff --git a/components/shared/AppFooterCopyright.jsx b/components/shared/AppFooterCopyright.jsx
index 5877571..aa3d8c4 100644
--- a/components/shared/AppFooterCopyright.jsx
+++ b/components/shared/AppFooterCopyright.jsx
@@ -1,10 +1,10 @@
function AppFooterCopyright() {
return (
-
+
+
+
+ This site uses Google Analytics to anonymously track visits to enhance your experience. By continuing to browse, you accept this usage.
+
+
);
}
diff --git a/components/shared/AppHeader.jsx b/components/shared/AppHeader.jsx
index 525f0d5..b2119ac 100644
--- a/components/shared/AppHeader.jsx
+++ b/components/shared/AppHeader.jsx
@@ -4,8 +4,8 @@ import Image from 'next/image';
import { motion } from 'framer-motion';
import { FiSun, FiMoon, FiX, FiMenu } from 'react-icons/fi';
import HireMeModal from '../HireMeModal';
-import logoLight from '../../public/images/logo-light.svg';
-import logoDark from '../../public/images/logo-dark.svg';
+import logoLight from '../../public/images/logo-light.png';
+import logoDark from '../../public/images/logo-dark.png';
import useThemeSwitcher from '../../hooks/useThemeSwitcher';
function AppHeader() {
@@ -40,29 +40,29 @@ function AppHeader() {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
id="nav"
- className="sm:container sm:mx-auto"
+ className="max-w-7xl mx-auto"
>
{/* Header */}
-
+
{/* Header menu links and small screen hamburger menu */}
-
+
{activeTheme === 'dark' ? (
) : (
)}
@@ -122,6 +122,11 @@ function AppHeader() {
About Me
+
+
+ Blog
+
+
Contact
@@ -139,7 +144,7 @@ function AppHeader() {
{/* Header links large screen */}
-
+
About Me
-
+
+ Blog
+
onChange(e.target.value)}
+ className={`
+ w-full h-full px-3 py-2
+ border border-gray-400 dark:border-secondary-dark
+ rounded-lg
+ text-sm bg-secondary-light dark:bg-ternary-dark
+ text-primary-dark dark:text-ternary-light
+ ${className}
+ `}
+ >
+
+ {options.map((option) => (
+
+ ))}
+
+ );
+ }
+
+ export default FilterDropdown;
+
\ No newline at end of file
diff --git a/components/shared/SearchInput.jsx b/components/shared/SearchInput.jsx
new file mode 100644
index 0000000..bd9a933
--- /dev/null
+++ b/components/shared/SearchInput.jsx
@@ -0,0 +1,21 @@
+// components/shared/SearchInput.js
+import { FiSearch } from 'react-icons/fi';
+
+function SearchInput({ value, onChange, placeholder = "Search", className = '' }) {
+ return (
+
+
+
+
+ onChange(e.target.value)}
+ className={`w-full border border-gray-400 dark:border-gray-600 rounded-lg sm:rounded-l-none px-3 py-2 text-sm bg-secondary-light dark:bg-ternary-dark text-primary-dark dark:text-ternary-light ${className}`}
+ />
+
+ );
+}
+
+export default SearchInput;
diff --git a/components/ui/AnimatedSection.jsx b/components/ui/AnimatedSection.jsx
new file mode 100644
index 0000000..599c110
--- /dev/null
+++ b/components/ui/AnimatedSection.jsx
@@ -0,0 +1,16 @@
+import { motion } from 'framer-motion'
+
+export const AnimatedSection = ({ children, className = '' }) => {
+ return (
+
+ {children}
+
+ )
+}
+
diff --git a/components/ui/AuthorBox.tsx b/components/ui/AuthorBox.tsx
new file mode 100644
index 0000000..0a4fc1e
--- /dev/null
+++ b/components/ui/AuthorBox.tsx
@@ -0,0 +1,15 @@
+import Image from "next/image";
+
+export default function AuthorBox({ name, bio, avatar }: { name: string; bio: string; avatar: string }) {
+ return (
+
+ );
+}
diff --git a/components/ui/Callout.jsx b/components/ui/Callout.jsx
new file mode 100644
index 0000000..7bc3a57
--- /dev/null
+++ b/components/ui/Callout.jsx
@@ -0,0 +1,13 @@
+const calloutStyles = {
+ tip: 'bg-green-100 dark:bg-green-900 border-green-300',
+ info: 'bg-blue-100 dark:bg-blue-900 border-blue-300',
+ warning: 'bg-yellow-100 dark:bg-yellow-900 border-yellow-300',
+};
+
+export const Callout = ({ type = 'info', children }) => (
+
+ {children}
+
+);
diff --git a/components/ui/ExplodedBookGallery.jsx b/components/ui/ExplodedBookGallery.jsx
new file mode 100644
index 0000000..faf263d
--- /dev/null
+++ b/components/ui/ExplodedBookGallery.jsx
@@ -0,0 +1,89 @@
+import Image from 'next/image'
+import { motion } from 'framer-motion'
+
+const slugify = (str) =>
+ str.toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
+
+export const ExplodedBookGallery = ({
+ authorImage,
+ authorName,
+ authorDescription,
+ books = [],
+ folder = '',
+ section = '',
+}) => {
+ return (
+
+ {/* Author Feature */}
+
+
+
+
+
+
+ {authorDescription}
+
+
+
+ {/* Books */}
+
+ {books.map((book, idx) => (
+
+ {/* Image */}
+
+
+ {/* Text */}
+
+ {/* h3 for TOC support */}
+
+ {book.title}
+
+
+
+ {book.description}
+
+
+ {book.quote && (
+
+ “{book.quote}”
+
+ )}
+
+
+
+
+ ))}
+
+
+ )
+}
diff --git a/components/ui/FadeInSection.jsx b/components/ui/FadeInSection.jsx
new file mode 100644
index 0000000..43baba7
--- /dev/null
+++ b/components/ui/FadeInSection.jsx
@@ -0,0 +1,13 @@
+import { motion } from 'framer-motion';
+
+export const FadeInSection = ({ children }) => (
+
+ {children}
+
+);
+
diff --git a/components/ui/GalleryLightbox.tsx b/components/ui/GalleryLightbox.tsx
new file mode 100644
index 0000000..6ed5ea0
--- /dev/null
+++ b/components/ui/GalleryLightbox.tsx
@@ -0,0 +1,78 @@
+'use client'
+
+import { useState } from 'react'
+import Lightbox from 'yet-another-react-lightbox'
+import Thumbnails from 'yet-another-react-lightbox/plugins/thumbnails'
+import 'yet-another-react-lightbox/styles.css'
+import 'yet-another-react-lightbox/plugins/thumbnails.css'
+
+interface Props {
+ children: React.ReactNode
+}
+
+export default function GalleryLightbox({ children }: Props) {
+ const [open, setOpen] = useState(false)
+ const [index, setIndex] = useState(0)
+
+ // Flatten children into array for mapping
+ const images = Array.isArray(children) ? children : [children]
+
+ // Extract image sources from
tags
+ const slides = images
+ .flatMap((child: any) => {
+ if (child?.props?.children) {
+ const nested = Array.isArray(child.props.children)
+ ? child.props.children
+ : [child.props.children]
+ return nested
+ }
+ return [child]
+ })
+ .map((child: any) => {
+ const src = child?.props?.src || child?.props?.children?.props?.src
+ return { src }
+ })
+ .filter((slide) => slide.src)
+
+ const handleOpen = (clickedIndex: number) => {
+ setIndex(clickedIndex)
+ setOpen(true)
+ }
+
+ return (
+ <>
+ e.stopPropagation()}>
+ {images.map((image: any, i: number) => (
+
handleOpen(i)} className="cursor-zoom-in">
+ {image}
+
+ ))}
+
+
+ setOpen(false)}
+ slides={slides}
+ index={index}
+ on={{ view: ({ index }) => setIndex(index) }}
+ plugins={[Thumbnails]}
+ carousel={{ finite: true }}
+ thumbnails={{
+ position: 'start',
+ showToggle: false,
+ vignette: false,
+ imageFit: 'cover',
+ border: 0,
+ padding: 4,
+ gap: 10,
+ width: 60,
+ height: 60
+ }}
+ />
+ >
+ )
+}
+
+
+
+
diff --git a/components/ui/GalleryLightbox_np.tsx b/components/ui/GalleryLightbox_np.tsx
new file mode 100644
index 0000000..29556d7
--- /dev/null
+++ b/components/ui/GalleryLightbox_np.tsx
@@ -0,0 +1,66 @@
+'use client'
+
+import { useEffect } from 'react'
+import basicLightbox from 'basiclightbox'
+import 'basiclightbox/dist/basicLightbox.min.css'
+
+export default function GalleryLightbox({ children }: { children: React.ReactNode }) {
+ useEffect(() => {
+ const images = Array.from(document.querySelectorAll('[data-gallery] img'))
+ images.forEach((img, index) => {
+ img.setAttribute('data-index', index.toString())
+ img.addEventListener('click', () => {
+ openGallery(index, images)
+ })
+ })
+ }, [])
+
+ const openGallery = (startIndex: number, images: Element[]) => {
+ let index = startIndex
+
+ const showImage = () => {
+ const src = (images[index] as HTMLImageElement).src
+ const instance = basicLightbox.create(
+ `
+
+

+
+
+
+ `,
+ {
+ onShow: (instance) => {
+ const nextBtn = instance.element().querySelector('#next') as HTMLElement
+ const prevBtn = instance.element().querySelector('#prev') as HTMLElement
+
+ nextBtn.onclick = () => {
+ index = (index + 1) % images.length
+ instance.close()
+ showImage()
+ }
+
+ prevBtn.onclick = () => {
+ index = (index - 1 + images.length) % images.length
+ instance.close()
+ showImage()
+ }
+
+ document.onkeydown = (e) => {
+ if (e.key === 'ArrowRight') nextBtn.click()
+ if (e.key === 'ArrowLeft') prevBtn.click()
+ if (e.key === 'Escape') instance.close()
+ }
+ },
+ onClose: () => {
+ document.onkeydown = null
+ },
+ }
+ )
+ instance.show()
+ }
+
+ showImage()
+ }
+
+ return {children}
+}
diff --git a/components/ui/ImageGallery.jsx b/components/ui/ImageGallery.jsx
new file mode 100644
index 0000000..e9330cc
--- /dev/null
+++ b/components/ui/ImageGallery.jsx
@@ -0,0 +1,70 @@
+import Image from 'next/image';
+import GalleryLightbox from '../../components/ui/GalleryLightbox';
+
+export const ImageGallery = ({
+ images = [],
+ folder = '',
+ section = '',
+ filenames = [],
+ highlightFirst = false,
+ galleryClassName = '',
+ classMap = {},
+}) => {
+ const DEFAULT_WIDTH = 600;
+ const DEFAULT_HEIGHT = 400;
+
+ const CLASS_STYLE_MAP = {
+ 'Berlin-marathon': 'rounded-xl border border-red-300 dark:border-red-500 shadow-lg',
+ 'books': 'rounded-2xl object-contain border-gray-300 dark:border-gray-600 transition-transform duration-500 hover:scale-105',
+ 'minimal': 'rounded-none border border-gray-200 dark:border-gray-600 shadow-sm',
+ 'vintage': 'rounded-xl sepia border border-yellow-500 shadow-xl',
+ default: 'rounded-2xl shadow-xl object-cover border border-gray-300 dark:border-gray-600 transition-transform duration-500 hover:scale-105',
+ ...classMap,
+ };
+
+ const resolvedImageClass = CLASS_STYLE_MAP[galleryClassName] || CLASS_STYLE_MAP['default'];
+
+ const combinedImages = [
+ ...images,
+ ...filenames.map((filename) => ({
+ src: `${folder}/${section}/${filename}`,
+ alt: filename.replace(/[-_]/g, ' ').replace(/\.\w+$/, ''),
+ })),
+ ];
+
+ return (
+
+
+ {combinedImages.map((img, idx) => {
+ const isFirst = idx === 0 && highlightFirst;
+ const width = img.width || DEFAULT_WIDTH;
+ const height = img.height || DEFAULT_HEIGHT;
+
+ return (
+
+
+
+ );
+ })}
+
+
+ );
+};
+
+
diff --git a/components/ui/NextPrev.tsx b/components/ui/NextPrev.tsx
new file mode 100644
index 0000000..a35488a
--- /dev/null
+++ b/components/ui/NextPrev.tsx
@@ -0,0 +1,66 @@
+import Link from 'next/link'
+import { motion } from 'framer-motion'
+import Container from '../layout/Container';
+
+export default function NextPrev({ prev, next, isBlog=true}) {
+ if (!prev && !next) return null
+
+ return (
+
+
+
+
+
+
+
+ {prev && (
+
+
+ Previous
+
+
+ ← {prev.title}
+
+
+ )}
+
+ {next && (
+
+
+ Next
+
+
+ {next.title} →
+
+
+ )}
+
+
+
+
+ )
+}
diff --git a/components/ui/Section.jsx b/components/ui/Section.jsx
new file mode 100644
index 0000000..96af105
--- /dev/null
+++ b/components/ui/Section.jsx
@@ -0,0 +1,34 @@
+import { motion } from 'framer-motion'
+
+export const slugify = (text) =>
+ text
+ .toLowerCase()
+ .replace(/[^\w\s-]/g, '')
+ .replace(/\s+/g, '-')
+
+export const Section = ({ title, children, marginTop = 'mt-16' }) => {
+ const id = title ? slugify(title) : null
+
+ return (
+
+ {title && (
+
+ {title}
+
+ )}
+
+
+ {children}
+
+
+ )
+}
diff --git a/components/ui/SectionDivider.jsx b/components/ui/SectionDivider.jsx
new file mode 100644
index 0000000..c9bb88b
--- /dev/null
+++ b/components/ui/SectionDivider.jsx
@@ -0,0 +1,3 @@
+export const SectionDivider = ({ className = 'max-w-7xl' }) => (
+
+ );
diff --git a/components/ui/ShareButtons.tsx b/components/ui/ShareButtons.tsx
new file mode 100644
index 0000000..79f1d58
--- /dev/null
+++ b/components/ui/ShareButtons.tsx
@@ -0,0 +1,46 @@
+// components/ui/ShareButtons.tsx
+import { FiLink, FiLinkedin, FiX } from "react-icons/fi";
+import { useRouter } from "next/router";
+
+export default function ShareButtons({ title }: { title: string }) {
+ const router = useRouter();
+ const url = `https://amadoumamane.fr${router.asPath}`;
+ const encodedTitle = encodeURIComponent(title);
+
+ const copyToClipboard = () => {
+ navigator.clipboard.writeText(url);
+ alert("Link copied to clipboard!");
+ };
+
+ return (
+
+ {/* Twitter (X) Share */}
+
+ Tweet
+
+
+ {/* LinkedIn Share */}
+
+ LinkedIn
+
+
+ {/* Copy Link Button */}
+
+
+ );
+}
diff --git a/components/ui/index.js b/components/ui/index.js
new file mode 100644
index 0000000..974fb3e
--- /dev/null
+++ b/components/ui/index.js
@@ -0,0 +1,13 @@
+export { Section } from './Section';
+export { ImageGallery } from './ImageGallery';
+export { Callout } from './Callout';
+export { FadeInSection } from './FadeInSection';
+export { AnimatedSection } from './AnimatedSection';
+export { ExplodedBookGallery } from './ExplodedBookGallery';
+export { SectionDivider } from './SectionDivider';
+
+
+
+
+export { default as AuthorBox } from '../../components/ui/AuthorBox';
+export { default as ShareButtons } from '../../components/ui/ShareButtons';
diff --git a/content/blog/1-berlin-marathon.mdx b/content/blog/1-berlin-marathon.mdx
new file mode 100644
index 0000000..fff131c
--- /dev/null
+++ b/content/blog/1-berlin-marathon.mdx
@@ -0,0 +1,464 @@
+---
+title: "Running Through Berlin - A Marathon Experience & City Adventure"
+date: "2023-09-24"
+image: "/images/blog/sports/berlin-marathon/berlin-marathon-2.png"
+category: "Sport"
+tags: ["Berlin", "Marathon", "Travel", "Running", "Race Recap"]
+readingTime: "6 min read"
+description: "A deep-dive into the Berlin Marathon experience — from race day adrenaline to post-run city joy."
+---
+
+import Image from 'next/image'
+import { motion } from 'framer-motion'
+import { Section, ImageGallery, AnimatedSection, FadeInSection, Callout, ShareButtons, AuthorBox, GalleryLightbox} from '@/components/ui'
+
+
+
+
+
+
+{/* Hero Section */}
+
+
+
+
+ From sunrise training to nightlife celebrations, this is the story of a city, a race, and everything in between. Berlin didn’t just host my marathon — it transformed it into a life highlight.
+
+
+ {"Whether you're chasing a personal best or a passport stamp, the Berlin Marathon delivers something unforgettable — a runner’s dream infused with culture, history, and heart."}
+
+
+
+
+
+
+
+
+
+
+
+
+ The Berlin Marathon is not just a race—it’s an experience that intertwines endurance, history, and the electrifying pulse of one of Europe’s most fascinating cities. From its flat, record-breaking course to the rich tapestry of Berlin’s culture, food, and nightlife, every step of this journey was unforgettable.
+ Whether you’re a seasoned marathoner or a curious traveler, Berlin offers something beyond the finish line.
+ Let me take you through my Berlin Marathon adventure, from the excitement of arrival to the thrill of running through the Brandenburg Gate.
+
+
+
+
+
+
+{/* Section: First Glimpses */}
+
+
+ Delayed flights couldn’t dull my excitement. Stepping into Berlin’s Tiergarten after a whirlwind arrival felt like unlocking a dream level.
+
+
+
+
+
+ - Flight chaos? - A Rocky Start : Travel is rarely smooth, and my journey was no exception. A delayed flight from Paris turned my well-planned afternoon arrival into an evening scramble. Still, stepping off the plane at Berlin Brandenburg Airport (BER), I felt the rush of excitement—I was finally in Berlin!
+ - Navigating to Tiergarten - RER 8: Berlin’s public transport is legendary, and rather than wasting money on a cab,
+ I took the RER 8 into the city. The efficiency was impressive, and before I knew it, I had arrived in Tiergarten, one of Berlin’s largest parks and the perfect backdrop for my marathon weekend.
+
+
+
+
+
+
🏨 My stay: Smartment Business Berlin City West
+
+ Finding the right accommodation was a challenge—I wanted a quiet, comfortable place to rest, but also one that gave me access to the city’s vibrant neighborhoods.
+
+
+
+ I opted for Smartment Business Berlin City West, a hybrid between an Airbnb and a hotel. It offered minimal staff interaction, but the digital check-in process was surprisingly seamless.
+
+
+
+
+
📌 What I Learned
+
+ Booking a traditional hotel might have been a better choice, especially for a marathon weekend. Though the room was spacious and well-equipped, not having a 24/7 reception was a slight inconvenience
+
+
+
+
+
+
+
+
+
+
+
+ {/* Intro */}
+
+
+ What started as a simple pre-race evening turned into a profound encounter with elite mindset, culture, and inspiration
+
+
+
+ {/* Hero Image */}
+
+
+
+
+ {/* Main Content */}
+
+
+ One of the most inspiring parts of my pre-race experience was attending the screening of “Behind the Team” at Babylon Cinema, a historic venue dating back to 1929. This wasn’t just a film—it was a cinematic portrait of greatness, culture, and discipline.
+
+
+ {/* Cards */}
+
+
+ {/* Card */}
+ {[
+ {
+ icon: "🧠",
+ title: "The Kenyan Training Philosophy",
+ desc: "More than physical effort — it’s a celebration of mental grit, community, and spiritual drive."
+ },
+ {
+ icon: "🎙️",
+ title: "Q&A with Patrick Sang",
+ desc: "Legendary coach of Kipchoge: “A great athlete is not just built in the body, but in the mind.”"
+ },
+ {
+ icon: "👟",
+ title: "Nike on Running Culture",
+ desc: "Running unites pros and amateurs alike. The spirit of the marathon is democratic, raw, and magical."
+ }
+ ].map((item, i) => (
+
+
{item.icon}
+
{item.title}
+
+ {item.desc}
+
+
+ ))}
+
+
+
+
+ {"Leaving the cinema, I wasn’t just inspired—I felt equipped. I wasn't only running a marathon the next day; I was running with the energy of legends."}
+
+
+ {/* Quote Box */}
+
+
+
📍 Babylon Cinema, Est. 1929
+
+ - Patrick Sang: "The mind is the foundation of greatness."
+ - Nike: “Run with legends. That’s the magic of a marathon.”
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{/* Section: Race Day */}
+
+
+ {/* Intro */}
+
+
+ From pre-dawn chaos to a personal record on the world’s fastest marathon course — this was Berlin, in all its raw magic.
+
+
+
+ {/* Hero Image */}
+
+
+
+
+
+ {/* Narrative Blocks */}
+
+ {/* Scene: Build-up */}
+
+
⏰ Before the Start
+
+ Woke up with a cramp in my right leg — not exactly ideal. My nerves were high. I left the hotel far too late and ended up sprinting through tens of thousands of runners to reach my corral. Chaos, anxiety... and adrenaline.
+
+
+
+ {/* Scene: The Marathon Experience */}
+
+
+
🔥 The Race – 42.195km of Emotion
+
+ - The Atmosphere: Berliners lined the streets with energy, humor, and music. It felt like the city itself was cheering me on.
+ - Mid-race Boost: At kilometer 30, volunteers handed me gummy bears — the weirdest, most perfect pick-me-up I never saw coming.
+ - The Pain: My leg cramped again just past halfway. I dug deep. Every cheer, every footstep helped me keep moving.
+ - The Finish: Running through the Brandenburg Gate... goosebumps. My time? 2h39m50s. A lifetime PB.
+
+
+ Bring your own fuel. Aid stations appear every 5K, but choices are limited and crowded.
+
+
+
+ {/* Visual Side Card */}
+
+
🏅 The Elites
+
+ - Eliud Kipchoge: Another title. 2h02m40s.
+ - Tigist Assefa: New women’s world record – 2h11m51s. A seismic moment.
+
+
+
+
+ {/* Recap Card */}
+
+
🏁 Race in 3 Highlights
+
+ - 🎯 Personal Best: 2h39m50s
+ - 🍬 Gummy bears at 30K? Yes. Berlin style.
+ - 🏛️ Through the Brandenburg Gate = goosebumps forever.
+
+
+
+
+
+
+
+{/* Section: Post-Race Berlin */}
+
+ {/* Header */}
+
+
+
+ The race may have ended, but Berlin had more to give. With sore legs and a full heart, I let the city move me in unexpected ways.
+
+
+
+ {/* Main Photo */}
+
+
+
+
+ {/* Highlights in 3 cards */}
+
+ {[
+ {
+ icon: "🖼️",
+ title: "East Side Gallery",
+ description: "Strolling along a 1.4km stretch of painted history. The Berlin Wall’s remnants now breathe with color and peace."
+ },
+ {
+ icon: "🕊️",
+ title: "Holocaust Memorial",
+ description: "A walk through silence. Among 2,700 concrete blocks, I found perspective, weight, and stillness."
+ },
+ {
+ icon: "💃",
+ title: "Bachata by the Spree",
+ description: "Berlin dances. I found myself moving with strangers, the Spree sparkling beside us, bachata in our bones. Joy, unexpected."
+ }
+ ].map(({ icon, title, description }, i) => (
+
+
+ {icon} {title}
+
+
+ {description}
+
+
+ ))}
+
+
+ {/* Gallery */}
+
+
+
+
+ {/* Food Memories */}
+
+
🍽️ My Final Bites in Berlin
+
+ {[
+ {
+ emoji: "🥙",
+ name: "Mustafa’s Kebab",
+ desc: "The myth is real is real. Juicy, crunchy, explosive - I’d queue again without hesitation."
+ },
+ {
+ emoji: "🐟",
+ name: "FunkyFisch",
+ desc: "Refined and radiant, one of Berlin’s top-rated seafood spots - with a hefty price tag but divine flavors."
+ },
+ {
+ emoji: "🍜",
+ name: "Ni’s Thai",
+ desc: " Comfort food, depth of flavor, and the best dumplings I’ve had."
+ }
+ ].map(({ emoji, name, desc }, i) => (
+
+
{emoji} {name}
+
{desc}
+
+ ))}
+
+
+
+
+
+
+
+ {/* Hero Visual Anchor */}
+
+
+
+
+ {/* Narrative Wrap-Up */}
+
+
+ The Berlin Marathon isn’t just 42.195 kilometers — it’s a moving masterpiece. A story told in cadence, cobblestone, and collective breath. It’s the kind of race that leaves footprints not only on the course, but on your character.
+
+
+
+ From the electrifying echoes of cheering crowds along Unter den Linden to the hushed reverence of the Holocaust Memorial, Berlin becomes more than a backdrop — it becomes your co-runner. Your rhythm. Your reward.
+
+
+
+ My race may have ended at the Brandenburg Gate, but Berlin’s impact didn’t stop there. The city unraveled itself layer by layer — through its food, its music, its contradictions. And somehow, in all that chaos and clarity, it gave something back.
+
+
+ {/* Reflective Pull Quote */}
+
+ Would I return? Without hesitation. Next time, I’ll come back stronger, stay longer — and maybe, just maybe, I’ll finally earn that Berghain wristband.
+
+
+
+ {/* Emotional Close */}
+
+ Danke, Berlin. Ich komme wieder. 🇩🇪❤️🔥
+
+
+
+
+
+
+ From iconic monuments to candid moments, here’s a visual love letter to Berlin — one frame at a time.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/content/blog/2-books-changed-my-life.mdx b/content/blog/2-books-changed-my-life.mdx
new file mode 100644
index 0000000..35e8373
--- /dev/null
+++ b/content/blog/2-books-changed-my-life.mdx
@@ -0,0 +1,400 @@
+---
+title: "My Personal Library of Transformation - The Books That Changed Everything"
+date: "2025-04-04"
+image: "/images/blog/readings/books-changed-my-life/books-changed-my-life.jpg"
+category: "Reading"
+tags: ["Reading", "Great Authors", "Books"]
+readingTime: "6 min read"
+description: "Books have the power to transform our perspectives and shape our lives. Here are some of my favorite books and authors who have profoundly impacted me:"
+
+defaultImageFolder: "/images/blog/readings/books-changed-my-life"
+---
+
+
+
+import Image from 'next/image'
+import { motion } from 'framer-motion'
+import { Section, ImageGallery, AnimatedSection, FadeInSection, Callout, ShareButtons, AuthorBox, GalleryLightbox, Gallery, ExplodedBookGallery, SectionDivider } from '../../components/ui'
+
+
+
+
+
+
+
+
+
+
+
+
+{/* Hero Section */}
+
+
+
+
+
+
+
+ There are books that entertain, books that educate—and then there are books that transform. These are the ones that found me at the right moment, cracked me open, and helped me evolve.
+ Below is a collection of the most powerful books I've ever read, accompanied by images of their authors and covers, along with reflections on their impact.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hill didn’t just write about success — he reverse-engineered it. His words weren’t formulas, they were frequencies. He helped me realize that wealth isn’t pursued — it’s magnetized by belief, clarity, and burning desire.
+
+
+
+
+ This was the first book that truly showed me the link between desire, belief, and achievement. It’s not just about money—Think and Grow Rich taught me how powerful thoughts are. It helped me develop a new relationship with intention, faith, and discipline. It planted the seed that mindset creates reality.
+ Think and Grow Rich didn’t teach me how to make money — it taught me how to think differently. It was about aligning thought with intention, and emotion with vision. Success, I learned, begins in the invisible.
+ >
+ ),
+ quote: "Whatever the mind can conceive and believe, it can achieve.",
+ }
+ ]}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dispenza didn't ask me to believe — he asked me to become. His books weren’t just ideas to read, they were experiments to live. Through his lens, the mind wasn’t a mirror — it was a map.
+
+
+
+
+ This book unraveled the invisible loops I was living in. Breaking the Habit of Being Yourself showed me that personality isn’t permanent — it’s programmed. And more importantly: it’s reprogrammable.
+
+
+ >
+ ),
+ quote: "To change is to think greater than your environment, greater than the conditions in your life, and greater than the emotional habits of the body.",
+ },
+ {
+ title: "You Are the Placebo",
+ image: "you-are-the-placebo.jpg",
+ description: (
+ <>
+ You Are the Placebo asked a radical question: what if healing didn’t come from the outside in, but the inside out? It blurred the line between mind and medicine.
+ This book deepened my belief in self-healing. It made me realize that the body listens to the mind — and that belief is a medicine in itself.
+ >
+ ),
+ quote: "The moment you start feeling abundant and worthy, you are generating wealth.",
+ }
+ ]}
+ />
+
+
+
+
+
+
+
+
+ His teachings didn’t shout — they whispered. They didn’t instruct — they revealed. Tolle didn’t just change what I knew, he changed how I *noticed*.
+
+
+
+
+ This wasn’t a book — it was a lifeline. In a world obsessed with productivity, The Power of Now reminded me that stillness is not emptiness — it’s everything. I began to notice space between thoughts. In that space, I found peace.
+ >
+ ),
+ quote: "Nothing ever happened in the past; it happened in the Now. Nothing will ever happen in the future; it will happen in the Now.",
+ },
+ {
+ title: "A New Earth",
+ image: "a-new-earth.jpg",
+ description: (
+ <>
+ A New Earth gave me language for the ego. It wasn’t just about personal growth — it was about collective awakening. The book cracked open the illusion of separateness and offered the possibility of conscious evolution.
+ >
+ ),
+ quote: "You are not the voice in your mind, but the one who is aware of it.",
+ }
+ ]}
+ />
+
+
+
+
+
+
+
+
+
+
+ Robbins has always been about peak performance — but Life Force felt different. It wasn’t just about pushing harder; it was about upgrading the system. The book wasn’t hype — it was hope, backed by science and urgency.
+
+
+
+
+ Life Force is part health guide, part call to action. Robbins brings the cutting edge of regenerative medicine, precision diagnostics, and energy optimization into one playbook. This book opened my eyes to what's possible in terms of vitality, longevity, and energy. It made me feel like I have agency over my health. It’s not just about living longer — it’s about living younger, with power and purpose.
+ >
+ ),
+ quote: "Your biography is not your destiny. You have the power to make new choices every day that can radically transform your health and your life.",
+ }
+ ]}
+ />
+
+
+
+
+
+
+
+
+
+
+ Hawkins didn’t teach in steps — he revealed in frequencies. His words had the stillness of truth. With him, I stopped striving to understand everything — and started calibrating what felt real.
+
+
+
+
+ Power vs Force reframed everything I thought I knew about influence, truth, and integrity. It introduced me to a world where energy speaks louder than logic — and where true power doesn’t push, it attracts.
+ >
+ ),
+ quote: "In the presence of truth, the false falls away by itself.",
+ },
+ {
+ title: "Letting Go",
+ image: "letting-go.jpeg",
+ description: (
+ <>
+ Letting Go wasn’t a manual for fixing — it was a method for releasing. Hawkins showed me that emotions weren’t problems to solve, but energy to surrender. And in that surrender, freedom.
+ >
+ ),
+ quote: "Surrender is the simple but profound wisdom of yielding to rather than opposing the flow of life.",
+ }
+ ]}
+ />
+
+
+
+
+
+
+
+
+
+ Adichie’s stories didn’t just move me — they relocated me. Her writing is memory and music, history and heartbeat. Through her characters, I remembered that the personal is always political, and love is never untouched by place.
+
+
+
+
+ Americanah is a love story stretched across time, distance, and diaspora. But beneath the romance, it’s a meditation on identity — how it shifts, performs, resists, and returns. Adichie gave voice to the in-betweenness that so many carry in silence.
+ >
+ ),
+ quote: "You can’t write an honest novel about race in this country without it being political.",
+ },
+ {
+ title: "Half of a Yellow Sun",
+ image: "half-of-a-yellow-sun.jpg",
+ description: (
+ <>
+ Half of a Yellow Sun broke me open. Set against the Biafran War, it’s both epic and intimate — weaving history into human texture. It reminded me that fiction can preserve what facts forget: the weight of a glance, the cost of silence, the hope of a meal shared.
+ >
+ ),
+ quote: "There are some things that are so unforgivable that they make other things easily forgivable.",
+ }
+ ]}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Chopra doesn’t just explore the mind — he dissolves the idea of a separate self entirely. Metahuman wasn’t about self-improvement. It was about self-transcendence. He didn’t hand me answers — he questioned the one who’s asking.
+
+
+
+
+ Metahuman isn't a book you finish — it's one you integrate. Chopra challenges every mental boundary we’ve accepted as truth, and opens a portal to the awareness that observes all experience. What if freedom isn’t something to achieve — but something we’ve simply forgotten?
+ >
+ ),
+ quote: "Waking up is not a mistake. It's the only way to truly live.",
+ }
+ ]}
+ />
+
+
+
+
+
+
+
+
+ Shetty translated silence into steps. In a world that rewards noise, Think Like a Monk was a quiet call to realign. He didn’t just share ancient teachings — he gave them a passport into everyday life.
+
+
+
+
+ Think Like a Monk was a guide back to stillness — not as escape, but as a foundation. Shetty weaves in mindfulness, service, and clarity like threads in a tapestry of purpose. He reminded me that success without peace isn’t success at all.
+ >
+ ),
+ quote: "You don’t have to live like a monk to think like one.",
+ }
+ ]}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/content/blog/3-greek-islands.mdx b/content/blog/3-greek-islands.mdx
new file mode 100644
index 0000000..ee6034e
--- /dev/null
+++ b/content/blog/3-greek-islands.mdx
@@ -0,0 +1,55 @@
+---
+title: "Syros, Naxos & Mykonos – A Journey Through Contrast and Charm"
+date: "2022-08-22"
+image: "/images/blog/travel/greek-islands/greek-islands.png.webp"
+category: "Travel"
+tags: ["❗🛠️ Work in Progress", "Greece", "Cycladic Islands"]
+readingTime: "6 min read"
+description: " The Greek islands aren’t just places — they’re moods. They unfold like chapters in a sunlit story, each with its own rhythm, flavor, and shade of blue. While the world flocks to Santorini for sunsets and Athens for ruins, there’s a quieter trilogy waiting in the Aegean: Mykonos, Syros, and Naxos. Each one offers something different — a vibe, a secret, a breath of salt air that lingers long after you leave.."
+---
+
+import Image from 'next/image'
+import { motion } from 'framer-motion'
+import { Section, ImageGallery, AnimatedSection, FadeInSection, Callout, ShareButtons, AuthorBox, GalleryLightbox} from '@/components/ui'
+
+
+
+
+
+
+
+
+
+
+📝 Article in progress.
+
+I'm currently writing up something I think you’ll love. Thanks for your patience — it’ll be live shortly!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/content/blog/4-ibiza.mdx b/content/blog/4-ibiza.mdx
new file mode 100644
index 0000000..64aee51
--- /dev/null
+++ b/content/blog/4-ibiza.mdx
@@ -0,0 +1,56 @@
+---
+title: "Hot Summer in Ibiza – Where the Sun Sets Late and the Soul Rises"
+date: "2022-08-22"
+image: "/images/blog/travel/ibiza/ibiza.jpg"
+category: "Travel"
+tags: ["❗🛠️ Work in Progress", "Summer", "Balearic Islands"]
+readingTime: "6 min read"
+description: " There’s summer... and then there’s Ibiza summer. It’s not just a season — it’s a feeling. One that pulses under your skin, thumps in the beat of the beach bars, and softens into silence at sunset. Ibiza isn’t just an island — it’s a state of mind that shows up in flip-flops and leaves barefoot."
+---
+
+import Image from 'next/image'
+import { motion } from 'framer-motion'
+import { Section, ImageGallery, AnimatedSection, FadeInSection, Callout, ShareButtons, AuthorBox, GalleryLightbox} from '@/components/ui'
+
+
+
+
+
+
+
+
+
+
+
+📝 Article in progress.
+
+I'm currently writing up something I think you’ll love. Thanks for your patience — it’ll be live shortly!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/content/blog/5-top-5-datacience-tools-2025.mdx b/content/blog/5-top-5-datacience-tools-2025.mdx
new file mode 100644
index 0000000..6e6750a
--- /dev/null
+++ b/content/blog/5-top-5-datacience-tools-2025.mdx
@@ -0,0 +1,55 @@
+---
+title: "Top 5 AI Engineeering Tools in 2025 – From Prototyping to Production"
+date: "2025-04-01"
+image: "/images/top-ai-tools.png"
+category: "AI Engineering"
+tags: ["❗🛠️ Work in Progress", "Data science", "AI Engineeering"]
+readingTime: "6 min read"
+description: "The Best Tools for AI Engineering - Let's explore the most efficient tools used by AI Engineers today..."
+---
+
+import Image from 'next/image'
+import { motion } from 'framer-motion'
+import { Section, ImageGallery, AnimatedSection, FadeInSection, Callout, ShareButtons, AuthorBox, GalleryLightbox} from '@/components/ui'
+
+
+
+
+
+
+
+
+
+
+📝 Article in progress.
+
+I'm currently writing up something I think you’ll love. Thanks for your patience — it’ll be live shortly!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/content/blog/6-Understanding-Transformers.mdx b/content/blog/6-Understanding-Transformers.mdx
new file mode 100644
index 0000000..26d3513
--- /dev/null
+++ b/content/blog/6-Understanding-Transformers.mdx
@@ -0,0 +1,56 @@
+---
+title: "Understanding Transformers – The Architecture Behind Modern AI"
+date: "2025-04-01"
+image: "/images/transformers.png"
+category: "AI Engineering"
+tags: ["❗🛠️ Work in Progress", "AI Engineering"]
+readingTime: "6 min read"
+description: " Introducing Transformers - a fundamental part of modern LLMs ..."
+---
+
+import Image from 'next/image'
+import { motion } from 'framer-motion'
+import { Section, ImageGallery, AnimatedSection, FadeInSection, Callout, ShareButtons, AuthorBox, GalleryLightbox} from '@/components/ui'
+
+
+
+
+
+
+
+
+
+
+
+📝 Article in progress.
+
+I'm currently writing up something I think you’ll love. Thanks for your patience — it’ll be live shortly!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/aboutMeData.js b/data/aboutMeData.js
index 132e69d..1512705 100644
--- a/data/aboutMeData.js
+++ b/data/aboutMeData.js
@@ -1,12 +1,25 @@
import { v4 as uuidv4 } from 'uuid';
export const aboutMeData = [
- {
- id: uuidv4(),
- bio: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
- },
- {
- id: uuidv4(),
- bio: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
+ {
+ id: uuidv4(),
+ bio: "Hey there! I'm thrilled to have you here — thanks for stopping by.",
+ },
+ {
+ id: uuidv4(),
+ bio: "I'm a passionate Data Scientist with over 9 years of experience navigating the world of Big Data. My journey began at Altran, where I spent 4 impactful years helping clients architect robust data ingestion pipelines and scalable analytical solutions.",
+ },
+ {
+ id: uuidv4(),
+ bio: "I then joined RCI Bank & Services, where for more than 4 years, I led initiatives ranging from building a Customer Master Data Management platform to designing segmentation and scoring systems. I also played a key role in the migration of our on-premise infrastructure to Google Cloud Platform (GCP).",
+ },
+ {
+ id: uuidv4(),
+ bio: "For the past 2 years, I've been working independently, diving deep into cutting-edge topics like fraud detection, computer vision, recommender systems, prompt engineering, Retrieval-Augmented Generation (RAG) systems, and LLM agents.",
+ },
+ {
+ id: uuidv4(),
+ bio: "I thrive on transforming complex data into actionable insights, and I'm always exploring new ways to push the boundaries of AI and machine learning.",
+ },
];
+
diff --git a/data/blogData.js b/data/blogData.js
new file mode 100644
index 0000000..01579fa
--- /dev/null
+++ b/data/blogData.js
@@ -0,0 +1,20 @@
+import fs from "fs";
+import path from "path";
+import matter from "gray-matter";
+
+const blogDirectory = path.join(process.cwd(), "content/blog");
+
+export function getAllBlogPosts() {
+ const files = fs.readdirSync(blogDirectory);
+ return files.map((filename) => {
+ const fileContent = fs.readFileSync(path.join(blogDirectory, filename), "utf-8");
+ const { data } = matter(fileContent);
+ return {
+ slug: filename.replace(".mdx", ""),
+ title: data.title,
+ category: data.tags[0],
+ date: data.date,
+ image: data.image,
+ };
+ });
+}
diff --git a/data/clientsData.js b/data/clientsData.js
index 98d2846..f64711a 100644
--- a/data/clientsData.js
+++ b/data/clientsData.js
@@ -1,56 +1,56 @@
import { v4 as uuidv4 } from 'uuid';
// Import images
-import AmazonImage from '../public/images/brands/amazon_gray.png';
-import SonyImage from '../public/images/brands/sony_gray.png';
-import AdidasImage from '../public/images/brands/adidas_gray.png';
-import FilaImage from '../public/images/brands/fila_gray.png';
-import NBImage from '../public/images/brands/nb_gray.png';
-import SamsungImage from '../public/images/brands/samsung_gray.png';
-import CanonImage from '../public/images/brands/canon_gray.png';
-import PumaImage from '../public/images/brands/puma_gray.png';
+import SocieteGeneraleImage from '../public/images/brands/societe_generale_gray.png';
+import OrangeImage from '../public/images/brands/orange_gray.png';
+import SncfImage from '../public/images/brands/sncf_gray.png';
+import OuiSncfImage from '../public/images/brands/oui_sncf_gray.png';
+import RenaultImage from '../public/images/brands/renault_gray.png';
+import RciImage from '../public/images/brands/rci_gray.png';
+import CartesBancairesImage from '../public/images/brands/cartes_bancaires_gray.png';
+import EdfImage from '../public/images/brands/edf_gray.png';
export const clientsHeading = 'Some of the brands I worked with';
export const clientsData = [
{
id: uuidv4(),
- title: 'Amazon',
- img: AmazonImage,
+ title: 'SocieteGenerale',
+ img: SocieteGeneraleImage,
},
{
id: uuidv4(),
- title: 'Sony',
- img: SonyImage,
+ title: 'Orange',
+ img: OrangeImage,
},
{
id: uuidv4(),
- title: 'Adidas',
- img: AdidasImage,
+ title: 'Sncf',
+ img: SncfImage,
},
{
id: uuidv4(),
- title: 'FILA',
- img: FilaImage,
+ title: 'OuiSncf',
+ img: OuiSncfImage,
},
{
id: uuidv4(),
- title: 'NB',
- img: NBImage,
+ title: 'Renault',
+ img: RenaultImage,
},
{
id: uuidv4(),
- title: 'SAMSUNG',
- img: SamsungImage,
+ title: 'Rci',
+ img: RciImage,
},
{
id: uuidv4(),
- title: 'CANON',
- img: CanonImage,
+ title: 'CartesBancaires',
+ img: CartesBancairesImage,
},
{
id: uuidv4(),
- title: 'PUMA',
- img: PumaImage,
+ title: 'Edf',
+ img: EdfImage,
},
];
diff --git a/data/projectsData.js b/data/projectsData.js
index 35d7951..c00f381 100644
--- a/data/projectsData.js
+++ b/data/projectsData.js
@@ -10,345 +10,74 @@ import {
export const projectsData = [
{
id: 1,
- title: 'Google Health Platform',
+ title: '🧬 AI Skin Cancer Detection Challenge',
url: 'google-health-platform',
- category: 'Web Application',
- img: '/images/web-project-2.jpg',
- ProjectHeader: {
- title: 'Google Health Platform',
- publishDate: 'Jul 26, 2021',
- tags: 'UI / Frontend',
- },
- ProjectImages: [
- {
- id: uuidv4(),
- title: 'Google Health Platform',
- img: '/images/ui-project-1.jpg',
- },
- {
- id: uuidv4(),
- title: 'Google Health Platform',
- img: '/images/web-project-2.jpg',
- },
- {
- id: uuidv4(),
- title: 'Google Health Platform',
- img: '/images/mobile-project-2.jpg',
- },
- ],
- ProjectInfo: {
- ClientHeading: 'About Client',
- CompanyInfo: [
- {
- id: uuidv4(),
- title: 'Name',
- details: 'Company Ltd',
- },
- {
- id: uuidv4(),
- title: 'Services',
- details: 'Web Development',
- },
- {
- id: uuidv4(),
- title: 'Website',
- details: 'https://company.com',
- },
- {
- id: uuidv4(),
- title: 'Phone',
- details: '555 8888 888',
- },
- ],
- ObjectivesHeading: 'Objective',
- ObjectivesDetails:
- 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Optio, natus! Quibusdam enim quod in esse, mollitia molestias incidunt quas ipsa accusamus veniam.',
- Technologies: [
- {
- title: 'Tools & Technologies',
- techs: [
- 'HTML',
- 'CSS',
- 'JavaScript',
- 'Vue.js',
- 'TailwindCSS',
- 'AdobeXD',
- ],
- },
- ],
- ProjectDetailsHeading: 'Challenge',
- ProjectDetails: [
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
- },
- ],
- SocialSharingHeading: 'Share This',
- // SocialSharing: [
- // {
- // id: uuidv4(),
- // name: 'Twitter',
- // icon: ,
- // url: 'https://twitter.com/realstoman',
- // },
- // {
- // id: uuidv4(),
- // name: 'Instagram',
- // icon: ,
- // url: 'https://instagram.com/realstoman',
- // },
- // {
- // id: uuidv4(),
- // name: 'Facebook',
- // icon: ,
- // url: 'https://facebook.com/',
- // },
- // {
- // id: uuidv4(),
- // name: 'LinkedIn',
- // icon: ,
- // url: 'https://linkedin.com/',
- // },
- // {
- // id: uuidv4(),
- // name: 'Youtube',
- // icon: ,
- // url: 'https://www.youtube.com/c/StomanStudio',
- // },
- // ],
- },
- },
- {
- id: 2,
- title: 'Phoenix Digital Agency',
- url: 'phoenix-digital-agency',
- category: 'Mobile Application',
- img: '/images/mobile-project-2.jpg',
- ProjectHeader: {
- title: 'Phoenix Digital Agency',
- publishDate: 'Jul 26, 2021',
- tags: 'Mobile Application',
- },
- ProjectImages: [
- {
- id: uuidv4(),
- title: 'Phoenix Digital Agency',
- img: '/images/ui-project-1.jpg',
- },
- {
- id: uuidv4(),
- title: 'Phoenix Digital Agency',
- img: '/images/web-project-2.jpg',
- },
- {
- id: uuidv4(),
- title: 'Phoenix Digital Agency',
- img: '/images/mobile-project-2.jpg',
- },
- ],
- ProjectInfo: {
- ClientHeading: 'About Client',
- CompanyInfo: [
- {
- id: uuidv4(),
- title: 'Name',
- details: 'Company Ltd',
- },
- {
- id: uuidv4(),
- title: 'Services',
- details: 'UI Design & Frontend Development',
- },
- {
- id: uuidv4(),
- title: 'Website',
- details: 'https://company.com',
- },
- {
- id: uuidv4(),
- title: 'Phone',
- details: '555 8888 888',
- },
- ],
- ObjectivesHeading: 'Objective',
- ObjectivesDetails:
- 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Optio, natus! Quibusdam enim quod in esse, mollitia molestias incidunt quas ipsa accusamus veniam.',
- Technologies: [
- {
- title: 'Tools & Technologies',
- techs: [
- 'HTML',
- 'CSS',
- 'JavaScript',
- 'Vue.js',
- 'TailwindCSS',
- 'AdobeXD',
- ],
- },
- ],
- ProjectDetailsHeading: 'Challenge',
- ProjectDetails: [
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
- },
- ],
- SocialSharingHeading: 'Share This',
- // SocialSharing: [
- // {
- // id: uuidv4(),
- // name: 'Twitter',
- // icon: ,
- // url: 'https://twitter.com/realstoman',
- // },
- // {
- // id: uuidv4(),
- // name: 'Instagram',
- // icon: ,
- // url: 'https://instagram.com/realstoman',
- // },
- // {
- // id: uuidv4(),
- // name: 'Facebook',
- // icon: ,
- // url: 'https://facebook.com/',
- // },
- // {
- // id: uuidv4(),
- // name: 'LinkedIn',
- // icon: ,
- // url: 'https://linkedin.com/',
- // },
- // {
- // id: uuidv4(),
- // name: 'Youtube',
- // icon: ,
- // url: 'https://www.youtube.com/c/StomanStudio',
- // },
- // ],
+ category: 'Data Science',
+ img: '/images/kaggle-isic-2024.png',
+ type: "notebook",
+ Notebook: {
+ file: '/notebooks/isic-2024_kaggle.html',
},
- },
- {
- id: 3,
- title: 'Project Management UI',
- url: 'project-management-ui',
- category: 'UI/UX Design',
- img: '/images/ui-project-1.jpg',
ProjectHeader: {
- title: 'Project Management UI',
- publishDate: 'Jul 26, 2021',
- tags: 'UI / Frontend',
+ title: '🧬 AI Skin Cancer Detection Challenge - ISIC 2024',
+ publishDate: 'March 24, 2025',
+ tags: 'Deep Learning',
},
ProjectImages: [
{
id: uuidv4(),
- title: 'Kabul Project Management UI',
+ title: 'Skin Cancer Detection',
img: '/images/ui-project-1.jpg',
},
{
id: uuidv4(),
- title: 'Kabul Project Management UI',
- img: '/images/web-project-2.jpg',
+ title: 'Skin Cancer Detection',
+ img: '/images/kaggle-isic-2024.png',
},
{
id: uuidv4(),
- title: 'Kabul Project Management UI',
+ title: 'Skin Cancer Detection',
img: '/images/mobile-project-2.jpg',
},
],
ProjectInfo: {
- ClientHeading: 'About Client',
- CompanyInfo: [
+ Overview: "In this competition, I developed a deep learning model to detect histologically confirmed skin cancer from smartphone-quality lesion images captured from 3D total body photography. The goal was to create a binary classification system capable of identifying malignant vs. benign lesions, even in low-resource or non-clinical settings where specialized dermatologic care is unavailable.",
+ "Challenge Highlights": [
{
id: uuidv4(),
- title: 'Name',
- details: 'Company Ltd',
+ title: '📅 Duration',
+ details: 'Jun 27 – Sep 7, 2024',
},
{
id: uuidv4(),
- title: 'Services',
- details: 'UI Design & Frontend Development',
+ title: '🌍 Dataset',
+ details: '3D total body photos from thousands of patients across 3 continents',
},
{
- id: uuidv4(),
- title: 'Website',
- details: 'https://company.com',
+
+ title: '🧠 Task',
+ details: 'Binary classification of malignant vs. benign skin lesions',
},
{
- id: uuidv4(),
- title: 'Phone',
- details: '555 8888 888',
+ title: '🛠️ Images',
+ details: 'Resemble close-up smartphone photos, simulating telehealth scenarios',
},
- ],
- ObjectivesHeading: 'Objective',
- ObjectivesDetails:
- 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Optio, natus! Quibusdam enim quod in esse, mollitia molestias incidunt quas ipsa accusamus veniam.',
- Technologies: [
{
- title: 'Tools & Technologies',
- techs: [
- 'HTML',
- 'CSS',
- 'JavaScript',
- 'Vue.js',
- 'TailwindCSS',
- 'AdobeXD',
- ],
+ title: '📈 Metric',
+ details: 'Partial AUC (pAUC) above 80% TPR, prioritizing high-sensitivity performance for early diagnosis',
},
],
- ProjectDetailsHeading: 'Challenge',
+ "Key Impact":
+ 'The model supports early detection and triage for skin cancer, with potential real-world use in primary care and underserved communities. By working with realistic, lower-resolution images, the system is designed to extend AI-driven diagnostic support beyond traditional clinical environments.',
+ Goal:
+ 'Improve early detection of skin cancers like melanoma, basal cell carcinoma, and squamous cell carcinoma through AI—especially in settings lacking access to dermatologists or dermatoscopic equipment.',
+
+ "Tools & Technologies": ['TensorFlow','Pandas','Python','Matplotlib','GPU','TPU'],
ProjectDetails: [
{
id: uuidv4(),
details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
+ 'Lorem ipsum dolor, sit amet conse.',
},
],
SocialSharingHeading: 'Share This',
@@ -385,383 +114,135 @@ export const projectsData = [
// },
// ],
},
- },
+ },
{
- id: 4,
- title: 'Cloud Storage Platform',
- url: 'cloud-storage-platform',
- category: 'UI/UX Design',
- img: '/images/ui-project-2.jpg',
- ProjectHeader: {
- title: 'Cloud Storage Platform',
- publishDate: 'Jul 26, 2021',
- tags: 'Web & Cloud',
- },
- ProjectImages: [
- {
- id: uuidv4(),
- title: 'Kabul Cloud Storage Platform',
- img: '/images/ui-project-1.jpg',
- },
- {
- id: uuidv4(),
- title: 'Kabul Cloud Storage Platform',
- img: '/images/web-project-2.jpg',
- },
- {
- id: uuidv4(),
- title: 'Kabul Cloud Storage Platform',
- img: '/images/mobile-project-2.jpg',
- },
- ],
- ProjectInfo: {
- ClientHeading: 'About Client',
- CompanyInfo: [
- {
- id: uuidv4(),
- title: 'Name',
- details: 'Company Ltd',
- },
- {
- id: uuidv4(),
- title: 'Services',
- details: 'UI Design & Frontend Development',
- },
- {
- id: uuidv4(),
- title: 'Website',
- details: 'https://company.com',
- },
- {
- id: uuidv4(),
- title: 'Phone',
- details: '555 8888 888',
- },
- ],
- ObjectivesHeading: 'Objective',
- ObjectivesDetails:
- 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Optio, natus! Quibusdam enim quod in esse, mollitia molestias incidunt quas ipsa accusamus veniam.',
- Technologies: [
- {
- title: 'Tools & Technologies',
- techs: [
- 'HTML',
- 'CSS',
- 'JavaScript',
- 'Vue.js',
- 'TailwindCSS',
- 'AdobeXD',
- ],
- },
- ],
- ProjectDetailsHeading: 'Challenge',
- ProjectDetails: [
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
- },
- ],
- SocialSharingHeading: 'Share This',
- // SocialSharing: [
- // {
- // id: uuidv4(),
- // name: 'Twitter',
- // icon: ,
- // url: 'https://twitter.com/realstoman',
- // },
- // {
- // id: uuidv4(),
- // name: 'Instagram',
- // icon: ,
- // url: 'https://instagram.com/realstoman',
- // },
- // {
- // id: uuidv4(),
- // name: 'Facebook',
- // icon: ,
- // url: 'https://facebook.com/',
- // },
- // {
- // id: uuidv4(),
- // name: 'LinkedIn',
- // icon: ,
- // url: 'https://linkedin.com/',
- // },
- // {
- // id: uuidv4(),
- // name: 'Youtube',
- // icon: ,
- // url: 'https://www.youtube.com/c/StomanStudio',
- // },
- // ],
- },
- },
+ id: 2, // Unique ID
+ title: '🎵 Music Recommendation System',
+ url: 'music-recommendation',
+ category: 'Data Science',
+ img: '/images/music-recommendation.webp', // Thumbnail image
+ type: 'notebook', // Mark as a notebook project
+ ProjectHeader: {
+ title: '🎵 AI-Powered Music Recommendation System',
+ publishDate: 'June 30, 2024',
+ tags: 'Machine Learning',
+ },
+ ProjectImages: [
+ {
+ id: uuidv4(),
+ title: 'Music Recommendation System',
+ img: '/images/music-recommendation.webp',
+ },
+ {
+ id: uuidv4(),
+ title: 'Algorithm Training',
+ img: '/images/music-recommendation.jpg',
+ },
+ ],
+ Notebook: {
+ file: '/notebooks/music-recommendation.html',
+ },
+ ProjectTabs: [
+ "Context",
+ "Goal",
+ "Key Questions",
+ "Data Dictionary",
+ "Problem Formulation",
+ "Tools & Technologies",
+ "Implementation"
+ ],
+ ProjectHeader: {
+ title: "🎵 AI-Powered Music Recommendation System",
+ publishDate: "June 30, 2024",
+ tags: "Machine Learning"
+ },
+ ProjectInfo: {
+ Context: `Nowadays, we are overwhelmed by information, making it increasingly challenging to select high-quality content from vast amounts of available data.
+Companies recognize this issue and have developed sophisticated techniques to assist users in finding the most relevant content, thereby enhancing user experience on their platforms.
+
+
+Spotify is an excellent example; it has become a leader in the music streaming industry, largely thanks to its advanced recommendation systems, enabling users to discover content tailored to their preferences. Therefore, recommendation systems are critical not only for enhancing user satisfaction but also for increasing user engagement and driving business success.
+
+This AI-powered music recommendation system leverages collaborative filtering and machine learning to suggest personalized playlists based on user behavior.`,
+
+ Goal: `The objective of this project is to develop a recommendation system capable of suggesting the top 10 songs most likely to be enjoyed by each user.
+ This system will dynamically learn and adapt to user preferences, continuously refining its recommendations.
+ Ultimately, this approach aims to enhance user engagement on music platforms by providing personalized, high-quality, and AI-powered song recommendations.`,
+
+ "Key Questions": `Throughout this project, we will explore and resolve the following essential questions:
+- What specific methodologies and modeling techniques are most effective for building this recommendation system?
+- Do we possess adequate data to develop a robust recommendation solution?
+- Is the dataset sufficiently comprehensive to yield accurate and meaningful recommendations?
+- What metrics should guide the evaluation of our recommendation system? Metrics such as precision, recall, F1-score, interpretability, responsiveness, and ethical considerations are particularly important.
+- How reliable and stable will our recommendation models be when deployed in real-world, production environments?
+- What additional refinements or future strategies should be implemented to further enhance the recommendation system?`,
+
+ "Data Dictionary": `Our primary dataset is the Taste Profile Subset, released by Echo Nest as part of the Million Song Dataset. It consists of two files:
+- Songs data:
+ - song_id: Unique identifier for each song
+ - title: Song title
+ - release: Name of the album
+ - artist_name: Artist’s name
+ - year: Year of the song's release
+- Play counts data
+ - user _id: A unique id given to the user
+ - song_id: A unique id given to the song
+ - play_cont: Number of times the song was played
+- Data source
+ - http://millionsongdataset.com/
+ `,
+
+"Problem Formulation": `Given the available song and play-count data, our task is to predict each user's likelihood of enjoying specific songs. To measure user preference, we will use predicted ratings. Songs with ratings above a defined threshold will be recommended. Ultimately, we aim to generate a ranked list of the top K songs for each user.
+We plan to experiment with various recommendation approaches, including:
+- Knowledge/Rank-Based Recommendations
+- Similarity-Based Recommendations:
+ - Clustering-based recommendations
+ - User-user collaborative filtering
+ - Item-item collaborative filtering
+- Matrix Factorization-Based Collaborative Filtering
+- Content-Based Recommendations"`,
+ "Tools & Technologies": ["Python", "Pandas", "Surprise", "Scikit-learn", "LightFM"]
+ }
+
+ },
{
- id: 5,
- title: 'WeTalk Social Application',
- url: 'wetalk-social-app',
- category: 'Mobile Application',
- img: '/images/mobile-project-1.jpg',
- ProjectHeader: {
- title: 'WeTalk Social Application',
- publishDate: 'Jul 26, 2021',
- tags: 'UI / Frontend',
- },
- ProjectImages: [
- {
- id: uuidv4(),
- title: 'WeTalk Social Application',
- img: '/images/ui-project-1.jpg',
- },
- {
- id: uuidv4(),
- title: 'WeTalk Social Application',
- img: '/images/web-project-2.jpg',
- },
- {
- id: uuidv4(),
- title: 'WeTalk Social Application',
- img: '/images/mobile-project-2.jpg',
- },
- ],
- ProjectInfo: {
- ClientHeading: 'About Client',
- CompanyInfo: [
- {
- id: uuidv4(),
- title: 'Name',
- details: 'Company Ltd',
- },
- {
- id: uuidv4(),
- title: 'Services',
- details: 'UI Design & Frontend Development',
- },
- {
- id: uuidv4(),
- title: 'Website',
- details: 'https://company.com',
- },
- {
- id: uuidv4(),
- title: 'Phone',
- details: '555 8888 888',
- },
- ],
- ObjectivesHeading: 'Objective',
- ObjectivesDetails:
- 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Optio, natus! Quibusdam enim quod in esse, mollitia molestias incidunt quas ipsa accusamus veniam.',
- Technologies: [
- {
- title: 'Tools & Technologies',
- techs: [
- 'HTML',
- 'CSS',
- 'JavaScript',
- 'Vue.js',
- 'TailwindCSS',
- 'AdobeXD',
- ],
- },
- ],
- ProjectDetailsHeading: 'Challenge',
- ProjectDetails: [
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
- },
- ],
- SocialSharingHeading: 'Share This',
- // SocialSharing: [
- // {
- // id: uuidv4(),
- // name: 'Twitter',
- // icon: ,
- // url: 'https://twitter.com/realstoman',
- // },
- // {
- // id: uuidv4(),
- // name: 'Instagram',
- // icon: ,
- // url: 'https://instagram.com/realstoman',
- // },
- // {
- // id: uuidv4(),
- // name: 'Facebook',
- // icon: ,
- // url: 'https://facebook.com/',
- // },
- // {
- // id: uuidv4(),
- // name: 'LinkedIn',
- // icon: ,
- // url: 'https://linkedin.com/',
- // },
- // {
- // id: uuidv4(),
- // name: 'Youtube',
- // icon: ,
- // url: 'https://www.youtube.com/c/realstoman',
- // },
- // ],
+ id: 3,
+ title: '💡 Building Autonomous Agentic AI',
+ url: 'google-health-platform',
+ category: 'Generative AI',
+ img: '/images/projects/ai-agents/agentic-ai.jpg',
+ type: "notebook",
+ Notebook: {
+ file: '',
},
- },
- {
- id: 6,
- title: 'Apple New Design System',
- url: 'apple-new-design-system',
- category: 'Web Application',
- img: '/images/web-project-1.jpg',
ProjectHeader: {
- title: 'Apple New Design System',
- publishDate: 'Jul 26, 2021',
- tags: 'UI / Frontend',
+ title: '💡 Building Agentic AI - Towards Autonomous and Goal-Directed Systems',
+ publishDate: 'April 06, 2025',
+ tags: '❗🛠️ Work in Progress, Deep Learning',
},
ProjectImages: [
{
id: uuidv4(),
- title: 'Kabul Apple New Design System',
- img: '/images/ui-project-1.jpg',
- },
- {
- id: uuidv4(),
- title: 'Kabul Apple New Design System',
- img: '/images/web-project-2.jpg',
- },
- {
- id: uuidv4(),
- title: 'Kabul Apple New Design System',
- img: '/images/mobile-project-2.jpg',
- },
+ title: 'Building Agentic AI Systems',
+ img: '/images/projects/ai-agents/agentic-ai.jpg',
+ }
],
ProjectInfo: {
- ClientHeading: 'About Client',
- CompanyInfo: [
- {
- id: uuidv4(),
- title: 'Name',
- details: 'Company Ltd',
- },
- {
- id: uuidv4(),
- title: 'Services',
- details: 'UI Design & Frontend Development',
- },
- {
- id: uuidv4(),
- title: 'Website',
- details: 'https://company.com',
- },
- {
- id: uuidv4(),
- title: 'Phone',
- details: '555 8888 888',
- },
- ],
- ObjectivesHeading: 'Objective',
- ObjectivesDetails:
- 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Optio, natus! Quibusdam enim quod in esse, mollitia molestias incidunt quas ipsa accusamus veniam.',
- Technologies: [
- {
- title: 'Tools & Technologies',
- techs: [
- 'HTML',
- 'CSS',
- 'JavaScript',
- 'Vue.js',
- 'TailwindCSS',
- 'AdobeXD',
- ],
- },
- ],
- ProjectDetailsHeading: 'Challenge',
+ Overview: `❗🛠️ 📝 Work in Progress
+
+I'm currently writing up something I think you’ll love. Thanks for your patience — it’ll be live shortly!`,
+ Goal:'',
+
+ "Tools & Technologies": [],
ProjectDetails: [
{
id: uuidv4(),
details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta?',
- },
- {
- id: uuidv4(),
- details:
- 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nihil vel illum asperiores dignissimos cumque quibusdam et fugiat voluptatem nobis suscipit explicabo, eaque consequatur nesciunt, fugit eligendi corporis laudantium adipisci soluta? Lorem ipsum, dolor sit amet consectetur adipisicing elit. Incidunt totam dolorum, ducimus obcaecati, voluptas facilis molestias nobis ut quam natus similique inventore excepturi optio ipsa deleniti fugit illo. Unde, amet! Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsum illo necessitatibus perspiciatis! Aperiam perferendis labore temporibus, eos culpa corporis recusandae quas, fuga voluptatibus nesciunt odit libero tenetur neque consequatur ea.',
+ 'Lorem ipsum dolor, sit amet conse.',
},
],
- SocialSharingHeading: 'Share This',
- // SocialSharing: [
- // {
- // id: uuidv4(),
- // name: 'Twitter',
- // icon: ,
- // url: 'https://twitter.com/realstoman',
- // },
- // {
- // id: uuidv4(),
- // name: 'Instagram',
- // icon: ,
- // url: 'https://instagram.com/realstoman',
- // },
- // {
- // id: uuidv4(),
- // name: 'Facebook',
- // icon: ,
- // url: 'https://facebook.com/',
- // },
- // {
- // id: uuidv4(),
- // name: 'LinkedIn',
- // icon: ,
- // url: 'https://linkedin.com/',
- // },
- // {
- // id: uuidv4(),
- // name: 'Youtube',
- // icon: ,
- // url: 'https://www.youtube.com/c/StomanStudio',
- // },
- // ],
+ SocialSharingHeading: 'Share This'
},
- },
+ },
];
+
diff --git a/hooks/useScrollToTop.jsx b/hooks/useScrollToTop.jsx
index 09d4be1..0ba7e0e 100644
--- a/hooks/useScrollToTop.jsx
+++ b/hooks/useScrollToTop.jsx
@@ -40,8 +40,8 @@ function useScrollToTop() {
width: 40,
padding: 7,
borderRadius: 50,
- right: 50,
- bottom: 50,
+ right: 35,
+ bottom: 35,
display: showScroll ? 'flex' : 'none',
}}
/>
diff --git a/lib/blogs.js b/lib/blogs.js
new file mode 100644
index 0000000..4eaea3c
--- /dev/null
+++ b/lib/blogs.js
@@ -0,0 +1,30 @@
+import fs from 'fs';
+import path from 'path';
+import matter from 'gray-matter';
+
+const blogDirectory = path.join(process.cwd(), 'content/blog');
+
+export function getAllBlogs() {
+ if (!fs.existsSync(blogDirectory)) {
+ console.error('Blog directory does not exist:', blogDirectory);
+ return [];
+ }
+
+ const files = fs.readdirSync(blogDirectory);
+
+ const blogs = files
+ .filter((filename) => filename.endsWith('.mdx')) // Only process MDX files
+ .map((filename) => {
+ const filePath = path.join(blogDirectory, filename);
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
+ const { data } = matter(fileContent);
+
+ return {
+ slug: filename.replace('.mdx', ''), // Extract slug from filename
+ ...data,
+ };
+ });
+
+ // Sort blogs by date (newest first)
+ return blogs.sort((a, b) => new Date(b.date) - new Date(a.date));
+}
diff --git a/next-env.d.ts b/next-env.d.ts
new file mode 100644
index 0000000..52e831b
--- /dev/null
+++ b/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
diff --git a/next.config.js b/next.config.js
index 0d60710..78de548 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,3 +1,19 @@
module.exports = {
reactStrictMode: true,
}
+
+
+const withMDX = require('@next/mdx')({
+ extension: /\.mdx?$/,
+ options: {
+ remarkPlugins: [
+ require('remark-gfm'), // Enables tables, strikethrough, and GitHub-style markdown
+ require('remark-mdx-frontmatter') // Ensures frontmatter parsing
+ ],
+ rehypePlugins: [require('rehype-slug')], // Generates IDs for headings
+ },
+});
+
+module.exports = withMDX({
+ pageExtensions: ['js', 'jsx', 'mdx'],
+});
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 21b2b41..0000000
--- a/package-lock.json
+++ /dev/null
@@ -1,6837 +0,0 @@
-{
- "name": "nextjs-tailwindcss-portfolio",
- "lockfileVersion": 2,
- "requires": true,
- "packages": {
- "": {
- "name": "nextjs-tailwindcss-portfolio",
- "dependencies": {
- "@tailwindcss/forms": "^0.4.0",
- "framer-motion": "^5.5.3",
- "next": "^13.0.5",
- "react": "^18.2.0",
- "react-countup": "^6.1.1",
- "react-dom": "^18.2.0",
- "react-icons": "^4.3.1",
- "uuid": "^8.3.2"
- },
- "devDependencies": {
- "autoprefixer": "^10.4.0",
- "eslint": "8.4.1",
- "eslint-config-next": "^13.0.5",
- "postcss": "^8.4.5",
- "tailwindcss": "^3.0.5"
- }
- },
- "node_modules/@babel/code-frame": {
- "version": "7.12.11",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
- "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
- "dependencies": {
- "@babel/highlight": "^7.10.4"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.15.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
- "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/highlight": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
- "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.15.7",
- "chalk": "^2.0.0",
- "js-tokens": "^4.0.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/runtime": {
- "version": "7.15.4",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz",
- "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==",
- "dev": true,
- "dependencies": {
- "regenerator-runtime": "^0.13.4"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/runtime-corejs3": {
- "version": "7.16.5",
- "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.16.5.tgz",
- "integrity": "sha512-F1pMwvTiUNSAM8mc45kccMQxj31x3y3P+tA/X8hKNWp3/hUsxdGxZ3D3H8JIkxtfA8qGkaBTKvcmvStaYseAFw==",
- "dev": true,
- "dependencies": {
- "core-js-pure": "^3.19.0",
- "regenerator-runtime": "^0.13.4"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@emotion/is-prop-valid": {
- "version": "0.8.8",
- "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
- "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
- "optional": true,
- "dependencies": {
- "@emotion/memoize": "0.7.4"
- }
- },
- "node_modules/@emotion/memoize": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
- "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
- "optional": true
- },
- "node_modules/@eslint/eslintrc": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz",
- "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==",
- "dev": true,
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^9.2.0",
- "globals": "^13.9.0",
- "ignore": "^4.0.6",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.0.4",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- }
- },
- "node_modules/@humanwhocodes/config-array": {
- "version": "0.9.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz",
- "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==",
- "dev": true,
- "dependencies": {
- "@humanwhocodes/object-schema": "^1.2.1",
- "debug": "^4.1.1",
- "minimatch": "^3.0.4"
- },
- "engines": {
- "node": ">=10.10.0"
- }
- },
- "node_modules/@humanwhocodes/object-schema": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
- "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
- "dev": true
- },
- "node_modules/@next/env": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.5.tgz",
- "integrity": "sha512-F3KLtiDrUslAZhTYTh8Zk5ZaavbYwLUn3NYPBnOjAXU8hWm0QVGVzKIOuURQ098ofRU4e9oglf3Sj9pFx5nI5w=="
- },
- "node_modules/@next/eslint-plugin-next": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.0.5.tgz",
- "integrity": "sha512-H9U9B1dFnCDmylDZ6/dYt95Ie1Iu+SLBMcO6rkIGIDcj5UK+DNyMiWm83xWBZ1gREM8cfp5Srv1g6wqf8pM4lw==",
- "dev": true,
- "dependencies": {
- "glob": "7.1.7"
- }
- },
- "node_modules/@next/eslint-plugin-next/node_modules/glob": {
- "version": "7.1.7",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
- "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
- "dev": true,
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@next/swc-android-arm-eabi": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.5.tgz",
- "integrity": "sha512-YO691dxHlviy6H0eghgwqn+5kU9J3iQnKERHTDSppqjjGDBl6ab4wz9XfI5AhljjkaTg3TknHoIEWFDoZ4Ve8g==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-android-arm64": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.0.5.tgz",
- "integrity": "sha512-ugbwffkUmp8cd2afehDC8LtQeFUxElRUBBngfB5UYSWBx18HW4OgzkPFIY8jUBH16zifvGZWXbICXJWDHrOLtw==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-darwin-arm64": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.0.5.tgz",
- "integrity": "sha512-mshlh8QOtOalfZbc17uNAftWgqHTKnrv6QUwBe+mpGz04eqsSUzVz1JGZEdIkmuDxOz00cK2NPoc+VHDXh99IQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-darwin-x64": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.5.tgz",
- "integrity": "sha512-SfigOKW4Z2UB3ruUPyvrlDIkcJq1hiw1wvYApWugD+tQsAkYZKEoz+/8emCmeYZ6Gwgi1WHV+z52Oj8u7bEHPg==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-freebsd-x64": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.0.5.tgz",
- "integrity": "sha512-0NJg8HZr4yG8ynmMGFXQf+Mahvq4ZgBmUwSlLXXymgxEQgH17erH/LoR69uITtW+KTsALgk9axEt5AAabM4ucg==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm-gnueabihf": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.0.5.tgz",
- "integrity": "sha512-Cye+h3oDT3NDWjACMlRaolL8fokpKie34FlPj9nfoW7bYKmoMBY1d4IO/GgBF+5xEl7HkH0Ny/qex63vQ0pN+A==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-gnu": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.0.5.tgz",
- "integrity": "sha512-5BfDS/VoRDR5QUGG9oedOCEZGmV2zxUVFYLUJVPMSMeIgqkjxWQBiG2BUHZI6/LGk9yvHmjx7BTvtBCLtRg6IQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-musl": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.0.5.tgz",
- "integrity": "sha512-xenvqlXz+KxVKAB1YR723gnVNszpsCvKZkiFFaAYqDGJ502YuqU2fwLsaSm/ASRizNcBYeo9HPLTyc3r/9cdMQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-gnu": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.0.5.tgz",
- "integrity": "sha512-9Ahi1bbdXwhrWQmOyoTod23/hhK05da/FzodiNqd6drrMl1y7+RujoEcU8Dtw3H1mGWB+yuTlWo8B4Iba8hqiQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-musl": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.0.5.tgz",
- "integrity": "sha512-V+1mnh49qmS9fOZxVRbzjhBEz9IUGJ7AQ80JPWAYQM5LI4TxfdiF4APLPvJ52rOmNeTqnVz1bbKtVOso+7EZ4w==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-arm64-msvc": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.0.5.tgz",
- "integrity": "sha512-wRE9rkp7I+/3Jf2T9PFIJOKq3adMWYEFkPOA7XAkUfYbQHlDJm/U5cVCWUsKByyQq5RThwufI91sgd19MfxRxg==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-ia32-msvc": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.0.5.tgz",
- "integrity": "sha512-Q1XQSLEhFuFhkKFdJIGt7cYQ4T3u6P5wrtUNreg5M+7P+fjSiC8+X+Vjcw+oebaacsdl0pWZlK+oACGafush1w==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-x64-msvc": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.0.5.tgz",
- "integrity": "sha512-t5gRblrwwiNZP6cT7NkxlgxrFgHWtv9ei5vUraCLgBqzvIsa7X+PnarZUeQCXqz6Jg9JSGGT9j8lvzD97UqeJQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@pkgr/utils": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz",
- "integrity": "sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==",
- "dev": true,
- "dependencies": {
- "cross-spawn": "^7.0.3",
- "is-glob": "^4.0.3",
- "open": "^8.4.0",
- "picocolors": "^1.0.0",
- "tiny-glob": "^0.2.9",
- "tslib": "^2.4.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/unts"
- }
- },
- "node_modules/@rushstack/eslint-patch": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
- "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==",
- "dev": true
- },
- "node_modules/@swc/helpers": {
- "version": "0.4.14",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz",
- "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==",
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@tailwindcss/forms": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.4.0.tgz",
- "integrity": "sha512-DeaQBx6EgEeuZPQACvC+mKneJsD8am1uiJugjgQK1+/Vt+Ai0GpFBC2T2fqnUad71WgOxyrZPE6BG1VaI6YqfQ==",
- "dependencies": {
- "mini-svg-data-uri": "^1.2.3"
- },
- "peerDependencies": {
- "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
- }
- },
- "node_modules/@types/json5": {
- "version": "0.0.29",
- "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
- "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
- "dev": true
- },
- "node_modules/@types/parse-json": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
- "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
- },
- "node_modules/@typescript-eslint/parser": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz",
- "integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/scope-manager": "5.44.0",
- "@typescript-eslint/types": "5.44.0",
- "@typescript-eslint/typescript-estree": "5.44.0",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz",
- "integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/types": "5.44.0",
- "@typescript-eslint/visitor-keys": "5.44.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/types": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz",
- "integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==",
- "dev": true,
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz",
- "integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/types": "5.44.0",
- "@typescript-eslint/visitor-keys": "5.44.0",
- "debug": "^4.3.4",
- "globby": "^11.1.0",
- "is-glob": "^4.0.3",
- "semver": "^7.3.7",
- "tsutils": "^3.21.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz",
- "integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/types": "5.44.0",
- "eslint-visitor-keys": "^3.3.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/acorn": {
- "version": "8.5.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
- "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
- "dev": true,
- "peer": true,
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/acorn-node": {
- "version": "1.8.2",
- "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
- "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
- "dependencies": {
- "acorn": "^7.0.0",
- "acorn-walk": "^7.0.0",
- "xtend": "^4.0.2"
- }
- },
- "node_modules/acorn-node/node_modules/acorn": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
- "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-walk": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
- "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ansi-colors": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
- "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dependencies": {
- "color-convert": "^1.9.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/anymatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
- "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/arg": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz",
- "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA=="
- },
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
- "node_modules/aria-query": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
- "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
- "dev": true,
- "dependencies": {
- "@babel/runtime": "^7.10.2",
- "@babel/runtime-corejs3": "^7.10.2"
- },
- "engines": {
- "node": ">=6.0"
- }
- },
- "node_modules/array-includes": {
- "version": "3.1.6",
- "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
- "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "get-intrinsic": "^1.1.3",
- "is-string": "^1.0.7"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array-union": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
- "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/array.prototype.flat": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz",
- "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "es-shim-unscopables": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.flatmap": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
- "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "es-shim-unscopables": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.tosorted": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz",
- "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "es-shim-unscopables": "^1.0.0",
- "get-intrinsic": "^1.1.3"
- }
- },
- "node_modules/ast-types-flow": {
- "version": "0.0.7",
- "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
- "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
- "dev": true
- },
- "node_modules/autoprefixer": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.0.tgz",
- "integrity": "sha512-7FdJ1ONtwzV1G43GDD0kpVMn/qbiNqyOPMFTX5nRffI+7vgWoFEc6DcXOxHJxrWNDXrZh18eDsZjvZGUljSRGA==",
- "dependencies": {
- "browserslist": "^4.17.5",
- "caniuse-lite": "^1.0.30001272",
- "fraction.js": "^4.1.1",
- "normalize-range": "^0.1.2",
- "picocolors": "^1.0.0",
- "postcss-value-parser": "^4.1.0"
- },
- "bin": {
- "autoprefixer": "bin/autoprefixer"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- "peerDependencies": {
- "postcss": "^8.1.0"
- }
- },
- "node_modules/autoprefixer/node_modules/browserslist": {
- "version": "4.19.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz",
- "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==",
- "dependencies": {
- "caniuse-lite": "^1.0.30001286",
- "electron-to-chromium": "^1.4.17",
- "escalade": "^3.1.1",
- "node-releases": "^2.0.1",
- "picocolors": "^1.0.0"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- }
- },
- "node_modules/autoprefixer/node_modules/node-releases": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
- "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA=="
- },
- "node_modules/axe-core": {
- "version": "4.3.5",
- "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.5.tgz",
- "integrity": "sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/axobject-query": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
- "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==",
- "dev": true
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
- },
- "node_modules/binary-extensions": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
- "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dependencies": {
- "fill-range": "^7.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/call-bind": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
- "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
- "dev": true,
- "dependencies": {
- "function-bind": "^1.1.1",
- "get-intrinsic": "^1.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/camelcase-css": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
- "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001434",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
- "integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- }
- ]
- },
- "node_modules/chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dependencies": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/client-only": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
- "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
- },
- "node_modules/color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
- },
- "node_modules/core-js-pure": {
- "version": "3.20.0",
- "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.20.0.tgz",
- "integrity": "sha512-qsrbIwWSEEYOM7z616jAVgwhuDDtPLwZSpUsU3vyUkHYqKTf/uwOJBZg2V7lMurYWkpVlaVOxBrfX0Q3ppvjfg==",
- "dev": true,
- "hasInstallScript": true,
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/core-js"
- }
- },
- "node_modules/cosmiconfig": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
- "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
- "dependencies": {
- "@types/parse-json": "^4.0.0",
- "import-fresh": "^3.2.1",
- "parse-json": "^5.0.0",
- "path-type": "^4.0.0",
- "yaml": "^1.10.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/countup.js": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.0.8.tgz",
- "integrity": "sha512-pW3xwwD+hB+xmtI16xFcuLS0D5hSQqPQWkZOdgpKQyzxCquDNo2VCFPkRw12vmvdpnicXVTcjmYiakG6biwINg=="
- },
- "node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
- "dev": true,
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/cssesc": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
- "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
- "bin": {
- "cssesc": "bin/cssesc"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/damerau-levenshtein": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz",
- "integrity": "sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw==",
- "dev": true
- },
- "node_modules/debounce": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
- "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
- },
- "node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true
- },
- "node_modules/define-lazy-prop": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
- "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/define-properties": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
- "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
- "dev": true,
- "dependencies": {
- "has-property-descriptors": "^1.0.0",
- "object-keys": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/defined": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
- "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM="
- },
- "node_modules/detective": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
- "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
- "dependencies": {
- "acorn-node": "^1.6.1",
- "defined": "^1.0.0",
- "minimist": "^1.1.1"
- },
- "bin": {
- "detective": "bin/detective.js"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/didyoumean": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
- "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
- },
- "node_modules/dir-glob": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
- "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
- "dev": true,
- "dependencies": {
- "path-type": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/dlv": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
- "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
- },
- "node_modules/doctrine": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
- "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
- "dev": true,
- "dependencies": {
- "esutils": "^2.0.2"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/electron-to-chromium": {
- "version": "1.4.20",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.20.tgz",
- "integrity": "sha512-N7ZVNrdzX8NE90OXEFBMsBf3fp8P/vVDUER3WCUZjzC7OkNTXHVoF6W9qVhq8+dA8tGnbDajzUpj2ISNVVyj+Q=="
- },
- "node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true
- },
- "node_modules/enhanced-resolve": {
- "version": "5.12.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
- "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==",
- "dev": true,
- "dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.2.0"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/enquirer": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
- "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
- "dev": true,
- "dependencies": {
- "ansi-colors": "^4.1.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/error-ex": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
- "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
- "dependencies": {
- "is-arrayish": "^0.2.1"
- }
- },
- "node_modules/es-abstract": {
- "version": "1.20.4",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz",
- "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "es-to-primitive": "^1.2.1",
- "function-bind": "^1.1.1",
- "function.prototype.name": "^1.1.5",
- "get-intrinsic": "^1.1.3",
- "get-symbol-description": "^1.0.0",
- "has": "^1.0.3",
- "has-property-descriptors": "^1.0.0",
- "has-symbols": "^1.0.3",
- "internal-slot": "^1.0.3",
- "is-callable": "^1.2.7",
- "is-negative-zero": "^2.0.2",
- "is-regex": "^1.1.4",
- "is-shared-array-buffer": "^1.0.2",
- "is-string": "^1.0.7",
- "is-weakref": "^1.0.2",
- "object-inspect": "^1.12.2",
- "object-keys": "^1.1.1",
- "object.assign": "^4.1.4",
- "regexp.prototype.flags": "^1.4.3",
- "safe-regex-test": "^1.0.0",
- "string.prototype.trimend": "^1.0.5",
- "string.prototype.trimstart": "^1.0.5",
- "unbox-primitive": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/es-shim-unscopables": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
- "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
- "dev": true,
- "dependencies": {
- "has": "^1.0.3"
- }
- },
- "node_modules/es-to-primitive": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
- "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
- "dev": true,
- "dependencies": {
- "is-callable": "^1.1.4",
- "is-date-object": "^1.0.1",
- "is-symbol": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/escalade": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/eslint": {
- "version": "8.4.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.4.1.tgz",
- "integrity": "sha512-TxU/p7LB1KxQ6+7aztTnO7K0i+h0tDi81YRY9VzB6Id71kNz+fFYnf5HD5UOQmxkzcoa0TlVZf9dpMtUv0GpWg==",
- "dev": true,
- "dependencies": {
- "@eslint/eslintrc": "^1.0.5",
- "@humanwhocodes/config-array": "^0.9.2",
- "ajv": "^6.10.0",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.2",
- "debug": "^4.3.2",
- "doctrine": "^3.0.0",
- "enquirer": "^2.3.5",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.1.0",
- "eslint-utils": "^3.0.0",
- "eslint-visitor-keys": "^3.1.0",
- "espree": "^9.2.0",
- "esquery": "^1.4.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^6.0.1",
- "functional-red-black-tree": "^1.0.1",
- "glob-parent": "^6.0.1",
- "globals": "^13.6.0",
- "ignore": "^4.0.6",
- "import-fresh": "^3.0.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "js-yaml": "^4.1.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "levn": "^0.4.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.0.4",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.1",
- "progress": "^2.0.0",
- "regexpp": "^3.2.0",
- "semver": "^7.2.1",
- "strip-ansi": "^6.0.1",
- "strip-json-comments": "^3.1.0",
- "text-table": "^0.2.0",
- "v8-compile-cache": "^2.0.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-config-next": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.0.5.tgz",
- "integrity": "sha512-lge94W7ME6kNCO96eCykq5GbKbllzmcDNDhh1/llMCRgNPl0+GIQ8dOoM0I7uRQVW56VmTXFybJFXgow11a5pg==",
- "dev": true,
- "dependencies": {
- "@next/eslint-plugin-next": "13.0.5",
- "@rushstack/eslint-patch": "^1.1.3",
- "@typescript-eslint/parser": "^5.42.0",
- "eslint-import-resolver-node": "^0.3.6",
- "eslint-import-resolver-typescript": "^3.5.2",
- "eslint-plugin-import": "^2.26.0",
- "eslint-plugin-jsx-a11y": "^6.5.1",
- "eslint-plugin-react": "^7.31.7",
- "eslint-plugin-react-hooks": "^4.5.0"
- },
- "peerDependencies": {
- "eslint": "^7.23.0 || ^8.0.0",
- "typescript": ">=3.3.1"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-import-resolver-node": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz",
- "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==",
- "dev": true,
- "dependencies": {
- "debug": "^3.2.7",
- "resolve": "^1.20.0"
- }
- },
- "node_modules/eslint-import-resolver-node/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-import-resolver-typescript": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.2.tgz",
- "integrity": "sha512-zX4ebnnyXiykjhcBvKIf5TNvt8K7yX6bllTRZ14MiurKPjDpCAZujlszTdB8pcNXhZcOf+god4s9SjQa5GnytQ==",
- "dev": true,
- "dependencies": {
- "debug": "^4.3.4",
- "enhanced-resolve": "^5.10.0",
- "get-tsconfig": "^4.2.0",
- "globby": "^13.1.2",
- "is-core-module": "^2.10.0",
- "is-glob": "^4.0.3",
- "synckit": "^0.8.4"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts"
- },
- "peerDependencies": {
- "eslint": "*",
- "eslint-plugin-import": "*"
- }
- },
- "node_modules/eslint-import-resolver-typescript/node_modules/globby": {
- "version": "13.1.2",
- "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz",
- "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==",
- "dev": true,
- "dependencies": {
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.2.11",
- "ignore": "^5.2.0",
- "merge2": "^1.4.1",
- "slash": "^4.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint-import-resolver-typescript/node_modules/ignore": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
- "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
- "dev": true,
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/eslint-import-resolver-typescript/node_modules/slash": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
- "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint-module-utils": {
- "version": "2.7.4",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz",
- "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==",
- "dev": true,
- "dependencies": {
- "debug": "^3.2.7"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependenciesMeta": {
- "eslint": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-module-utils/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-plugin-import": {
- "version": "2.26.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz",
- "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==",
- "dev": true,
- "dependencies": {
- "array-includes": "^3.1.4",
- "array.prototype.flat": "^1.2.5",
- "debug": "^2.6.9",
- "doctrine": "^2.1.0",
- "eslint-import-resolver-node": "^0.3.6",
- "eslint-module-utils": "^2.7.3",
- "has": "^1.0.3",
- "is-core-module": "^2.8.1",
- "is-glob": "^4.0.3",
- "minimatch": "^3.1.2",
- "object.values": "^1.1.5",
- "resolve": "^1.22.0",
- "tsconfig-paths": "^3.14.1"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependencies": {
- "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8"
- }
- },
- "node_modules/eslint-plugin-import/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/eslint-plugin-import/node_modules/doctrine": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
- "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
- "dev": true,
- "dependencies": {
- "esutils": "^2.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/eslint-plugin-import/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true
- },
- "node_modules/eslint-plugin-import/node_modules/resolve": {
- "version": "1.22.1",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
- "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
- "dev": true,
- "dependencies": {
- "is-core-module": "^2.9.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/eslint-plugin-jsx-a11y": {
- "version": "6.5.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz",
- "integrity": "sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g==",
- "dev": true,
- "dependencies": {
- "@babel/runtime": "^7.16.3",
- "aria-query": "^4.2.2",
- "array-includes": "^3.1.4",
- "ast-types-flow": "^0.0.7",
- "axe-core": "^4.3.5",
- "axobject-query": "^2.2.0",
- "damerau-levenshtein": "^1.0.7",
- "emoji-regex": "^9.2.2",
- "has": "^1.0.3",
- "jsx-ast-utils": "^3.2.1",
- "language-tags": "^1.0.5",
- "minimatch": "^3.0.4"
- },
- "engines": {
- "node": ">=4.0"
- },
- "peerDependencies": {
- "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
- }
- },
- "node_modules/eslint-plugin-jsx-a11y/node_modules/@babel/runtime": {
- "version": "7.16.5",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
- "integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
- "dev": true,
- "dependencies": {
- "regenerator-runtime": "^0.13.4"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/eslint-plugin-react": {
- "version": "7.31.11",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.11.tgz",
- "integrity": "sha512-TTvq5JsT5v56wPa9OYHzsrOlHzKZKjV+aLgS+55NJP/cuzdiQPC7PfYoUjMoxlffKtvijpk7vA/jmuqRb9nohw==",
- "dev": true,
- "dependencies": {
- "array-includes": "^3.1.6",
- "array.prototype.flatmap": "^1.3.1",
- "array.prototype.tosorted": "^1.1.1",
- "doctrine": "^2.1.0",
- "estraverse": "^5.3.0",
- "jsx-ast-utils": "^2.4.1 || ^3.0.0",
- "minimatch": "^3.1.2",
- "object.entries": "^1.1.6",
- "object.fromentries": "^2.0.6",
- "object.hasown": "^1.1.2",
- "object.values": "^1.1.6",
- "prop-types": "^15.8.1",
- "resolve": "^2.0.0-next.3",
- "semver": "^6.3.0",
- "string.prototype.matchall": "^4.0.8"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependencies": {
- "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
- }
- },
- "node_modules/eslint-plugin-react-hooks": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
- "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
- }
- },
- "node_modules/eslint-plugin-react/node_modules/doctrine": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
- "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
- "dev": true,
- "dependencies": {
- "esutils": "^2.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/eslint-plugin-react/node_modules/resolve": {
- "version": "2.0.0-next.4",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
- "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
- "dev": true,
- "dependencies": {
- "is-core-module": "^2.9.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/eslint-plugin-react/node_modules/semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true,
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/eslint-scope": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz",
- "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==",
- "dev": true,
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- }
- },
- "node_modules/eslint-utils": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
- "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
- "dev": true,
- "dependencies": {
- "eslint-visitor-keys": "^2.0.0"
- },
- "engines": {
- "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/mysticatea"
- },
- "peerDependencies": {
- "eslint": ">=5"
- }
- },
- "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
- "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
- "dev": true,
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
- "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
- "dev": true,
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- }
- },
- "node_modules/eslint/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/eslint/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/eslint/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/eslint/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "node_modules/eslint/node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint/node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/eslint/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/eslint/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/espree": {
- "version": "9.2.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.2.0.tgz",
- "integrity": "sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg==",
- "dev": true,
- "dependencies": {
- "acorn": "^8.6.0",
- "acorn-jsx": "^5.3.1",
- "eslint-visitor-keys": "^3.1.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- }
- },
- "node_modules/espree/node_modules/acorn": {
- "version": "8.6.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz",
- "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==",
- "dev": true,
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/esquery": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
- "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
- "dev": true,
- "dependencies": {
- "estraverse": "^5.1.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
- },
- "node_modules/fast-glob": {
- "version": "3.2.12",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
- "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.4"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
- "dev": true
- },
- "node_modules/fastq": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
- "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/file-entry-cache": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
- "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
- "dev": true,
- "dependencies": {
- "flat-cache": "^3.0.4"
- },
- "engines": {
- "node": "^10.12.0 || >=12.0.0"
- }
- },
- "node_modules/fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/flat-cache": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
- "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
- "dev": true,
- "dependencies": {
- "flatted": "^3.1.0",
- "rimraf": "^3.0.2"
- },
- "engines": {
- "node": "^10.12.0 || >=12.0.0"
- }
- },
- "node_modules/flatted": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz",
- "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
- "dev": true
- },
- "node_modules/fraction.js": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz",
- "integrity": "sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA==",
- "engines": {
- "node": "*"
- },
- "funding": {
- "type": "patreon",
- "url": "https://www.patreon.com/infusion"
- }
- },
- "node_modules/framer-motion": {
- "version": "5.5.3",
- "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-5.5.3.tgz",
- "integrity": "sha512-rS/eeTLdCsxg6F4Ikk+c+X9emawLSngwxdmXtgfT9qB8yiR2gi8/bdmfeSwyh3ngw63WcVVx3olIkwAY7x3p3A==",
- "dependencies": {
- "framesync": "6.0.1",
- "hey-listen": "^1.0.8",
- "popmotion": "11.0.3",
- "react-merge-refs": "^1.1.0",
- "react-use-measure": "^2.1.1",
- "style-value-types": "5.0.0",
- "tslib": "^2.1.0"
- },
- "optionalDependencies": {
- "@emotion/is-prop-valid": "^0.8.2"
- },
- "peerDependencies": {
- "@react-three/fiber": "*",
- "react": ">=16.8 || ^17.0.0",
- "react-dom": ">=16.8 || ^17.0.0",
- "three": "^0.135.0"
- },
- "peerDependenciesMeta": {
- "@react-three/fiber": {
- "optional": true
- },
- "three": {
- "optional": true
- }
- }
- },
- "node_modules/framesync": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz",
- "integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==",
- "dependencies": {
- "tslib": "^2.1.0"
- }
- },
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
- },
- "node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
- },
- "node_modules/function.prototype.name": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
- "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "es-abstract": "^1.19.0",
- "functions-have-names": "^1.2.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/functional-red-black-tree": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
- "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
- "dev": true
- },
- "node_modules/functions-have-names": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
- "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
- "dev": true,
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
- "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
- "dev": true,
- "dependencies": {
- "function-bind": "^1.1.1",
- "has": "^1.0.3",
- "has-symbols": "^1.0.3"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-symbol-description": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
- "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "get-intrinsic": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-tsconfig": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.2.0.tgz",
- "integrity": "sha512-X8u8fREiYOE6S8hLbq99PeykTDoLVnxvF4DjWKJmz9xy2nNRdUcV8ZN9tniJFeKyTU3qnC9lL8n4Chd6LmVKHg==",
- "dev": true,
- "funding": {
- "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
- }
- },
- "node_modules/glob": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
- "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/globals": {
- "version": "13.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz",
- "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==",
- "dev": true,
- "dependencies": {
- "type-fest": "^0.20.2"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/globals/node_modules/type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/globalyzer": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
- "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==",
- "dev": true
- },
- "node_modules/globby": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
- "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
- "dev": true,
- "dependencies": {
- "array-union": "^2.1.0",
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.2.9",
- "ignore": "^5.2.0",
- "merge2": "^1.4.1",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/globby/node_modules/ignore": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
- "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
- "dev": true,
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/globrex": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
- "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
- "dev": true
- },
- "node_modules/graceful-fs": {
- "version": "4.2.10",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
- "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
- "dev": true
- },
- "node_modules/has": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
- "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
- "dependencies": {
- "function-bind": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/has-bigints": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
- "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
- "dev": true,
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/has-property-descriptors": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
- "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
- "dev": true,
- "dependencies": {
- "get-intrinsic": "^1.1.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
- "dev": true,
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-tostringtag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
- "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
- "dev": true,
- "dependencies": {
- "has-symbols": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hey-listen": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
- "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="
- },
- "node_modules/ignore": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
- "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
- "dev": true,
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/import-cwd": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz",
- "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==",
- "dependencies": {
- "import-from": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/import-fresh": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
- "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/import-from": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz",
- "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==",
- "dependencies": {
- "resolve-from": "^5.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/import-from/node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
- "dev": true,
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
- },
- "node_modules/internal-slot": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
- "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
- "dev": true,
- "dependencies": {
- "get-intrinsic": "^1.1.0",
- "has": "^1.0.3",
- "side-channel": "^1.0.4"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/is-arrayish": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
- },
- "node_modules/is-bigint": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
- "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
- "dev": true,
- "dependencies": {
- "has-bigints": "^1.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-boolean-object": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
- "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-callable": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
- "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
- "dev": true,
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
- "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
- "dependencies": {
- "has": "^1.0.3"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-date-object": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
- "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
- "dev": true,
- "dependencies": {
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-docker": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
- "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
- "dev": true,
- "bin": {
- "is-docker": "cli.js"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-negative-zero": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
- "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
- "dev": true,
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/is-number-object": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
- "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
- "dev": true,
- "dependencies": {
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-regex": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
- "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-shared-array-buffer": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
- "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-string": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
- "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
- "dev": true,
- "dependencies": {
- "has-tostringtag": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-symbol": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
- "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
- "dev": true,
- "dependencies": {
- "has-symbols": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakref": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
- "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-wsl": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
- "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
- "dev": true,
- "dependencies": {
- "is-docker": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
- "dev": true
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
- },
- "node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/json-parse-even-better-errors": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true
- },
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
- "dev": true
- },
- "node_modules/json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
- "dev": true,
- "dependencies": {
- "minimist": "^1.2.0"
- },
- "bin": {
- "json5": "lib/cli.js"
- }
- },
- "node_modules/jsx-ast-utils": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz",
- "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==",
- "dev": true,
- "dependencies": {
- "array-includes": "^3.1.3",
- "object.assign": "^4.1.2"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/language-subtag-registry": {
- "version": "0.3.21",
- "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz",
- "integrity": "sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==",
- "dev": true
- },
- "node_modules/language-tags": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz",
- "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=",
- "dev": true,
- "dependencies": {
- "language-subtag-registry": "~0.3.2"
- }
- },
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/lilconfig": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz",
- "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/lines-and-columns": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
- },
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true
- },
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/micromatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
- "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
- "dependencies": {
- "braces": "^3.0.1",
- "picomatch": "^2.2.3"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/mini-svg-data-uri": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.3.tgz",
- "integrity": "sha512-gSfqpMRC8IxghvMcxzzmMnWpXAChSA+vy4cia33RgerMS8Fex95akUyQZPbxJJmeBGiGmK7n/1OpUX8ksRjIdA==",
- "bin": {
- "mini-svg-data-uri": "cli.js"
- }
- },
- "node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/minimist": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
- "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "node_modules/nanoid": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
- "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
- "dev": true
- },
- "node_modules/next": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/next/-/next-13.0.5.tgz",
- "integrity": "sha512-awpc3DkphyKydwCotcBnuKwh6hMqkT5xdiBK4OatJtOZurDPBYLP62jtM2be/4OunpmwIbsS0Eyv+ZGU97ciEg==",
- "dependencies": {
- "@next/env": "13.0.5",
- "@swc/helpers": "0.4.14",
- "caniuse-lite": "^1.0.30001406",
- "postcss": "8.4.14",
- "styled-jsx": "5.1.0"
- },
- "bin": {
- "next": "dist/bin/next"
- },
- "engines": {
- "node": ">=14.6.0"
- },
- "optionalDependencies": {
- "@next/swc-android-arm-eabi": "13.0.5",
- "@next/swc-android-arm64": "13.0.5",
- "@next/swc-darwin-arm64": "13.0.5",
- "@next/swc-darwin-x64": "13.0.5",
- "@next/swc-freebsd-x64": "13.0.5",
- "@next/swc-linux-arm-gnueabihf": "13.0.5",
- "@next/swc-linux-arm64-gnu": "13.0.5",
- "@next/swc-linux-arm64-musl": "13.0.5",
- "@next/swc-linux-x64-gnu": "13.0.5",
- "@next/swc-linux-x64-musl": "13.0.5",
- "@next/swc-win32-arm64-msvc": "13.0.5",
- "@next/swc-win32-ia32-msvc": "13.0.5",
- "@next/swc-win32-x64-msvc": "13.0.5"
- },
- "peerDependencies": {
- "fibers": ">= 3.1.0",
- "node-sass": "^6.0.0 || ^7.0.0",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "sass": "^1.3.0"
- },
- "peerDependenciesMeta": {
- "fibers": {
- "optional": true
- },
- "node-sass": {
- "optional": true
- },
- "sass": {
- "optional": true
- }
- }
- },
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/normalize-range": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
- "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-hash": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
- "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/object-inspect": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
- "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
- "dev": true,
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/object-keys": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
- "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
- "dev": true,
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/object.assign": {
- "version": "4.1.4",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
- "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "has-symbols": "^1.0.3",
- "object-keys": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/object.entries": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz",
- "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/object.fromentries": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz",
- "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/object.hasown": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz",
- "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==",
- "dev": true,
- "dependencies": {
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/object.values": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
- "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/open": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz",
- "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==",
- "dev": true,
- "dependencies": {
- "define-lazy-prop": "^2.0.0",
- "is-docker": "^2.1.1",
- "is-wsl": "^2.2.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/optionator": {
- "version": "0.9.1",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
- "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
- "dev": true,
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.3"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/parse-json": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
- "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
- "dependencies": {
- "@babel/code-frame": "^7.0.0",
- "error-ex": "^1.3.1",
- "json-parse-even-better-errors": "^2.3.0",
- "lines-and-columns": "^1.1.6"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
- },
- "node_modules/path-type": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
- "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/picocolors": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
- "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
- },
- "node_modules/picomatch": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
- "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/popmotion": {
- "version": "11.0.3",
- "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz",
- "integrity": "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==",
- "dependencies": {
- "framesync": "6.0.1",
- "hey-listen": "^1.0.8",
- "style-value-types": "5.0.0",
- "tslib": "^2.1.0"
- }
- },
- "node_modules/postcss": {
- "version": "8.4.14",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
- "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- }
- ],
- "dependencies": {
- "nanoid": "^3.3.4",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/postcss-js": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-3.0.3.tgz",
- "integrity": "sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==",
- "dependencies": {
- "camelcase-css": "^2.0.1",
- "postcss": "^8.1.6"
- },
- "engines": {
- "node": ">=10.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- }
- },
- "node_modules/postcss-load-config": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz",
- "integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==",
- "dependencies": {
- "import-cwd": "^3.0.0",
- "lilconfig": "^2.0.3",
- "yaml": "^1.10.2"
- },
- "engines": {
- "node": ">= 10"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- "peerDependencies": {
- "ts-node": ">=9.0.0"
- },
- "peerDependenciesMeta": {
- "ts-node": {
- "optional": true
- }
- }
- },
- "node_modules/postcss-nested": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
- "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
- "dependencies": {
- "postcss-selector-parser": "^6.0.6"
- },
- "engines": {
- "node": ">=12.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- "peerDependencies": {
- "postcss": "^8.2.14"
- }
- },
- "node_modules/postcss-selector-parser": {
- "version": "6.0.7",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.7.tgz",
- "integrity": "sha512-U+b/Deoi4I/UmE6KOVPpnhS7I7AYdKbhGcat+qTQ27gycvaACvNEw11ba6RrkwVmDVRW7sigWgLj4/KbbJjeDA==",
- "dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/postcss-value-parser": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
- },
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true,
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/progress": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
- "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
- "dev": true,
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
- "dependencies": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
- "node_modules/punycode": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
- "node_modules/quick-lru": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
- "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/react": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
- "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
- "dependencies": {
- "loose-envify": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-countup": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.1.1.tgz",
- "integrity": "sha512-9mYlnk/yCFg3g2/65pCTex6mr9h0eYdDMIL4T5nfLj/RzoiqY03wlPxZglzbJ6OzXvU37OmYApwQ4WMZNFr8Aw==",
- "dependencies": {
- "countup.js": "^2.0.8"
- },
- "peerDependencies": {
- "react": ">= 16.3.0"
- }
- },
- "node_modules/react-dom": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
- "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
- "dependencies": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.0"
- },
- "peerDependencies": {
- "react": "^18.2.0"
- }
- },
- "node_modules/react-icons": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
- "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==",
- "peerDependencies": {
- "react": "*"
- }
- },
- "node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
- },
- "node_modules/react-merge-refs": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz",
- "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/gregberge"
- }
- },
- "node_modules/react-use-measure": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz",
- "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==",
- "dependencies": {
- "debounce": "^1.2.1"
- },
- "peerDependencies": {
- "react": ">=16.13",
- "react-dom": ">=16.13"
- }
- },
- "node_modules/regenerator-runtime": {
- "version": "0.13.4",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz",
- "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==",
- "dev": true
- },
- "node_modules/regexp.prototype.flags": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
- "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "functions-have-names": "^1.2.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/regexpp": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
- "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
- "dev": true,
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/mysticatea"
- }
- },
- "node_modules/resolve": {
- "version": "1.20.0",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
- "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
- "dependencies": {
- "is-core-module": "^2.2.0",
- "path-parse": "^1.0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
- "node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "dependencies": {
- "queue-microtask": "^1.2.2"
- }
- },
- "node_modules/safe-regex-test": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
- "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "get-intrinsic": "^1.1.3",
- "is-regex": "^1.1.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/scheduler": {
- "version": "0.23.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
- "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
- "dependencies": {
- "loose-envify": "^1.1.0"
- }
- },
- "node_modules/semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
- "dev": true,
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/side-channel": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
- "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.0",
- "get-intrinsic": "^1.0.2",
- "object-inspect": "^1.9.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/slash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
- "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
- "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/string.prototype.matchall": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
- "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "get-intrinsic": "^1.1.3",
- "has-symbols": "^1.0.3",
- "internal-slot": "^1.0.3",
- "regexp.prototype.flags": "^1.4.3",
- "side-channel": "^1.0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/string.prototype.trimend": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
- "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/string.prototype.trimstart": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
- "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/style-value-types": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz",
- "integrity": "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==",
- "dependencies": {
- "hey-listen": "^1.0.8",
- "tslib": "^2.1.0"
- }
- },
- "node_modules/styled-jsx": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.0.tgz",
- "integrity": "sha512-/iHaRJt9U7T+5tp6TRelLnqBqiaIT0HsO0+vgyj8hK2KUk7aejFqRrumqPUlAqDwAj8IbS/1hk3IhBAAK/FCUQ==",
- "dependencies": {
- "client-only": "0.0.1"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "peerDependencies": {
- "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
- },
- "peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
- },
- "babel-plugin-macros": {
- "optional": true
- }
- }
- },
- "node_modules/supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/synckit": {
- "version": "0.8.4",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.4.tgz",
- "integrity": "sha512-Dn2ZkzMdSX827QbowGbU/4yjWuvNaCoScLLoMo/yKbu+P4GBR6cRGKZH27k6a9bRzdqcyd1DE96pQtQ6uNkmyw==",
- "dev": true,
- "dependencies": {
- "@pkgr/utils": "^2.3.1",
- "tslib": "^2.4.0"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/unts"
- }
- },
- "node_modules/tailwindcss": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.5.tgz",
- "integrity": "sha512-59pNgzx2o+wkAk7IZGIH7H9eNS53gzZGrO3+NPyOEWHDbquHgiLL/c993T5t1vPSAeBxox4X5OgZwNuRvXVf+g==",
- "dependencies": {
- "arg": "^5.0.1",
- "chalk": "^4.1.2",
- "chokidar": "^3.5.2",
- "color-name": "^1.1.4",
- "cosmiconfig": "^7.0.1",
- "detective": "^5.2.0",
- "didyoumean": "^1.2.2",
- "dlv": "^1.1.3",
- "fast-glob": "^3.2.7",
- "glob-parent": "^6.0.2",
- "is-glob": "^4.0.3",
- "normalize-path": "^3.0.0",
- "object-hash": "^2.2.0",
- "postcss-js": "^3.0.3",
- "postcss-load-config": "^3.1.0",
- "postcss-nested": "5.0.6",
- "postcss-selector-parser": "^6.0.6",
- "postcss-value-parser": "^4.2.0",
- "quick-lru": "^5.1.1",
- "resolve": "^1.20.0",
- "tmp": "^0.2.1"
- },
- "bin": {
- "tailwind": "lib/cli.js",
- "tailwindcss": "lib/cli.js"
- },
- "engines": {
- "node": ">=12.13.0"
- },
- "peerDependencies": {
- "autoprefixer": "^10.0.2",
- "postcss": "^8.0.9"
- }
- },
- "node_modules/tailwindcss/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/tailwindcss/node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/tailwindcss/node_modules/chokidar": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
- "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
- "dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
- },
- "engines": {
- "node": ">= 8.10.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/tailwindcss/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/tailwindcss/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
- },
- "node_modules/tailwindcss/node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/tailwindcss/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/tailwindcss/node_modules/readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dependencies": {
- "picomatch": "^2.2.1"
- },
- "engines": {
- "node": ">=8.10.0"
- }
- },
- "node_modules/tailwindcss/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/tapable": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
- "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/text-table": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
- "dev": true
- },
- "node_modules/tiny-glob": {
- "version": "0.2.9",
- "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
- "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==",
- "dev": true,
- "dependencies": {
- "globalyzer": "0.1.0",
- "globrex": "^0.1.2"
- }
- },
- "node_modules/tmp": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
- "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
- "dependencies": {
- "rimraf": "^3.0.0"
- },
- "engines": {
- "node": ">=8.17.0"
- }
- },
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/tsconfig-paths": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
- "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
- "dev": true,
- "dependencies": {
- "@types/json5": "^0.0.29",
- "json5": "^1.0.1",
- "minimist": "^1.2.6",
- "strip-bom": "^3.0.0"
- }
- },
- "node_modules/tslib": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
- "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
- },
- "node_modules/tsutils": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
- "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
- "dev": true,
- "dependencies": {
- "tslib": "^1.8.1"
- },
- "engines": {
- "node": ">= 6"
- },
- "peerDependencies": {
- "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
- }
- },
- "node_modules/tsutils/node_modules/tslib": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "dev": true
- },
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
- "dev": true,
- "dependencies": {
- "prelude-ls": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/typescript": {
- "version": "4.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
- "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
- "dev": true,
- "peer": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=4.2.0"
- }
- },
- "node_modules/unbox-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
- "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "has-bigints": "^1.0.2",
- "has-symbols": "^1.0.3",
- "which-boxed-primitive": "^1.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
- },
- "node_modules/uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
- "node_modules/v8-compile-cache": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
- "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
- "dev": true
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/which-boxed-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
- "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
- "dev": true,
- "dependencies": {
- "is-bigint": "^1.0.1",
- "is-boolean-object": "^1.1.0",
- "is-number-object": "^1.0.4",
- "is-string": "^1.0.5",
- "is-symbol": "^1.0.3"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
- },
- "node_modules/xtend": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
- "engines": {
- "node": ">=0.4"
- }
- },
- "node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
- },
- "node_modules/yaml": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
- "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
- "engines": {
- "node": ">= 6"
- }
- }
- },
- "dependencies": {
- "@babel/code-frame": {
- "version": "7.12.11",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
- "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
- "requires": {
- "@babel/highlight": "^7.10.4"
- }
- },
- "@babel/helper-validator-identifier": {
- "version": "7.15.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
- "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w=="
- },
- "@babel/highlight": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
- "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
- "requires": {
- "@babel/helper-validator-identifier": "^7.15.7",
- "chalk": "^2.0.0",
- "js-tokens": "^4.0.0"
- }
- },
- "@babel/runtime": {
- "version": "7.15.4",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz",
- "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==",
- "dev": true,
- "requires": {
- "regenerator-runtime": "^0.13.4"
- }
- },
- "@babel/runtime-corejs3": {
- "version": "7.16.5",
- "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.16.5.tgz",
- "integrity": "sha512-F1pMwvTiUNSAM8mc45kccMQxj31x3y3P+tA/X8hKNWp3/hUsxdGxZ3D3H8JIkxtfA8qGkaBTKvcmvStaYseAFw==",
- "dev": true,
- "requires": {
- "core-js-pure": "^3.19.0",
- "regenerator-runtime": "^0.13.4"
- }
- },
- "@emotion/is-prop-valid": {
- "version": "0.8.8",
- "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
- "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
- "optional": true,
- "requires": {
- "@emotion/memoize": "0.7.4"
- }
- },
- "@emotion/memoize": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
- "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
- "optional": true
- },
- "@eslint/eslintrc": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz",
- "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==",
- "dev": true,
- "requires": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^9.2.0",
- "globals": "^13.9.0",
- "ignore": "^4.0.6",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.0.4",
- "strip-json-comments": "^3.1.1"
- }
- },
- "@humanwhocodes/config-array": {
- "version": "0.9.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz",
- "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==",
- "dev": true,
- "requires": {
- "@humanwhocodes/object-schema": "^1.2.1",
- "debug": "^4.1.1",
- "minimatch": "^3.0.4"
- }
- },
- "@humanwhocodes/object-schema": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
- "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
- "dev": true
- },
- "@next/env": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.5.tgz",
- "integrity": "sha512-F3KLtiDrUslAZhTYTh8Zk5ZaavbYwLUn3NYPBnOjAXU8hWm0QVGVzKIOuURQ098ofRU4e9oglf3Sj9pFx5nI5w=="
- },
- "@next/eslint-plugin-next": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.0.5.tgz",
- "integrity": "sha512-H9U9B1dFnCDmylDZ6/dYt95Ie1Iu+SLBMcO6rkIGIDcj5UK+DNyMiWm83xWBZ1gREM8cfp5Srv1g6wqf8pM4lw==",
- "dev": true,
- "requires": {
- "glob": "7.1.7"
- },
- "dependencies": {
- "glob": {
- "version": "7.1.7",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
- "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
- "dev": true,
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- }
- }
- },
- "@next/swc-android-arm-eabi": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.5.tgz",
- "integrity": "sha512-YO691dxHlviy6H0eghgwqn+5kU9J3iQnKERHTDSppqjjGDBl6ab4wz9XfI5AhljjkaTg3TknHoIEWFDoZ4Ve8g==",
- "optional": true
- },
- "@next/swc-android-arm64": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.0.5.tgz",
- "integrity": "sha512-ugbwffkUmp8cd2afehDC8LtQeFUxElRUBBngfB5UYSWBx18HW4OgzkPFIY8jUBH16zifvGZWXbICXJWDHrOLtw==",
- "optional": true
- },
- "@next/swc-darwin-arm64": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.0.5.tgz",
- "integrity": "sha512-mshlh8QOtOalfZbc17uNAftWgqHTKnrv6QUwBe+mpGz04eqsSUzVz1JGZEdIkmuDxOz00cK2NPoc+VHDXh99IQ==",
- "optional": true
- },
- "@next/swc-darwin-x64": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.5.tgz",
- "integrity": "sha512-SfigOKW4Z2UB3ruUPyvrlDIkcJq1hiw1wvYApWugD+tQsAkYZKEoz+/8emCmeYZ6Gwgi1WHV+z52Oj8u7bEHPg==",
- "optional": true
- },
- "@next/swc-freebsd-x64": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.0.5.tgz",
- "integrity": "sha512-0NJg8HZr4yG8ynmMGFXQf+Mahvq4ZgBmUwSlLXXymgxEQgH17erH/LoR69uITtW+KTsALgk9axEt5AAabM4ucg==",
- "optional": true
- },
- "@next/swc-linux-arm-gnueabihf": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.0.5.tgz",
- "integrity": "sha512-Cye+h3oDT3NDWjACMlRaolL8fokpKie34FlPj9nfoW7bYKmoMBY1d4IO/GgBF+5xEl7HkH0Ny/qex63vQ0pN+A==",
- "optional": true
- },
- "@next/swc-linux-arm64-gnu": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.0.5.tgz",
- "integrity": "sha512-5BfDS/VoRDR5QUGG9oedOCEZGmV2zxUVFYLUJVPMSMeIgqkjxWQBiG2BUHZI6/LGk9yvHmjx7BTvtBCLtRg6IQ==",
- "optional": true
- },
- "@next/swc-linux-arm64-musl": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.0.5.tgz",
- "integrity": "sha512-xenvqlXz+KxVKAB1YR723gnVNszpsCvKZkiFFaAYqDGJ502YuqU2fwLsaSm/ASRizNcBYeo9HPLTyc3r/9cdMQ==",
- "optional": true
- },
- "@next/swc-linux-x64-gnu": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.0.5.tgz",
- "integrity": "sha512-9Ahi1bbdXwhrWQmOyoTod23/hhK05da/FzodiNqd6drrMl1y7+RujoEcU8Dtw3H1mGWB+yuTlWo8B4Iba8hqiQ==",
- "optional": true
- },
- "@next/swc-linux-x64-musl": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.0.5.tgz",
- "integrity": "sha512-V+1mnh49qmS9fOZxVRbzjhBEz9IUGJ7AQ80JPWAYQM5LI4TxfdiF4APLPvJ52rOmNeTqnVz1bbKtVOso+7EZ4w==",
- "optional": true
- },
- "@next/swc-win32-arm64-msvc": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.0.5.tgz",
- "integrity": "sha512-wRE9rkp7I+/3Jf2T9PFIJOKq3adMWYEFkPOA7XAkUfYbQHlDJm/U5cVCWUsKByyQq5RThwufI91sgd19MfxRxg==",
- "optional": true
- },
- "@next/swc-win32-ia32-msvc": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.0.5.tgz",
- "integrity": "sha512-Q1XQSLEhFuFhkKFdJIGt7cYQ4T3u6P5wrtUNreg5M+7P+fjSiC8+X+Vjcw+oebaacsdl0pWZlK+oACGafush1w==",
- "optional": true
- },
- "@next/swc-win32-x64-msvc": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.0.5.tgz",
- "integrity": "sha512-t5gRblrwwiNZP6cT7NkxlgxrFgHWtv9ei5vUraCLgBqzvIsa7X+PnarZUeQCXqz6Jg9JSGGT9j8lvzD97UqeJQ==",
- "optional": true
- },
- "@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "requires": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- }
- },
- "@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
- },
- "@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "requires": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- }
- },
- "@pkgr/utils": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz",
- "integrity": "sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==",
- "dev": true,
- "requires": {
- "cross-spawn": "^7.0.3",
- "is-glob": "^4.0.3",
- "open": "^8.4.0",
- "picocolors": "^1.0.0",
- "tiny-glob": "^0.2.9",
- "tslib": "^2.4.0"
- }
- },
- "@rushstack/eslint-patch": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
- "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==",
- "dev": true
- },
- "@swc/helpers": {
- "version": "0.4.14",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz",
- "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==",
- "requires": {
- "tslib": "^2.4.0"
- }
- },
- "@tailwindcss/forms": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.4.0.tgz",
- "integrity": "sha512-DeaQBx6EgEeuZPQACvC+mKneJsD8am1uiJugjgQK1+/Vt+Ai0GpFBC2T2fqnUad71WgOxyrZPE6BG1VaI6YqfQ==",
- "requires": {
- "mini-svg-data-uri": "^1.2.3"
- }
- },
- "@types/json5": {
- "version": "0.0.29",
- "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
- "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
- "dev": true
- },
- "@types/parse-json": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
- "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
- },
- "@typescript-eslint/parser": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz",
- "integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==",
- "dev": true,
- "requires": {
- "@typescript-eslint/scope-manager": "5.44.0",
- "@typescript-eslint/types": "5.44.0",
- "@typescript-eslint/typescript-estree": "5.44.0",
- "debug": "^4.3.4"
- }
- },
- "@typescript-eslint/scope-manager": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz",
- "integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==",
- "dev": true,
- "requires": {
- "@typescript-eslint/types": "5.44.0",
- "@typescript-eslint/visitor-keys": "5.44.0"
- }
- },
- "@typescript-eslint/types": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz",
- "integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==",
- "dev": true
- },
- "@typescript-eslint/typescript-estree": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz",
- "integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==",
- "dev": true,
- "requires": {
- "@typescript-eslint/types": "5.44.0",
- "@typescript-eslint/visitor-keys": "5.44.0",
- "debug": "^4.3.4",
- "globby": "^11.1.0",
- "is-glob": "^4.0.3",
- "semver": "^7.3.7",
- "tsutils": "^3.21.0"
- }
- },
- "@typescript-eslint/visitor-keys": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz",
- "integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==",
- "dev": true,
- "requires": {
- "@typescript-eslint/types": "5.44.0",
- "eslint-visitor-keys": "^3.3.0"
- }
- },
- "acorn": {
- "version": "8.5.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
- "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
- "dev": true,
- "peer": true
- },
- "acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "requires": {}
- },
- "acorn-node": {
- "version": "1.8.2",
- "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
- "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
- "requires": {
- "acorn": "^7.0.0",
- "acorn-walk": "^7.0.0",
- "xtend": "^4.0.2"
- },
- "dependencies": {
- "acorn": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
- "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
- }
- }
- },
- "acorn-walk": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
- "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA=="
- },
- "ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "requires": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- }
- },
- "ansi-colors": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
- "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
- "dev": true
- },
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true
- },
- "ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "requires": {
- "color-convert": "^1.9.0"
- }
- },
- "anymatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
- "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
- "requires": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- }
- },
- "arg": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz",
- "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA=="
- },
- "argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
- "aria-query": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
- "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
- "dev": true,
- "requires": {
- "@babel/runtime": "^7.10.2",
- "@babel/runtime-corejs3": "^7.10.2"
- }
- },
- "array-includes": {
- "version": "3.1.6",
- "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
- "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "get-intrinsic": "^1.1.3",
- "is-string": "^1.0.7"
- }
- },
- "array-union": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
- "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
- "dev": true
- },
- "array.prototype.flat": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz",
- "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "es-shim-unscopables": "^1.0.0"
- }
- },
- "array.prototype.flatmap": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
- "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "es-shim-unscopables": "^1.0.0"
- }
- },
- "array.prototype.tosorted": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz",
- "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "es-shim-unscopables": "^1.0.0",
- "get-intrinsic": "^1.1.3"
- }
- },
- "ast-types-flow": {
- "version": "0.0.7",
- "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
- "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
- "dev": true
- },
- "autoprefixer": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.0.tgz",
- "integrity": "sha512-7FdJ1ONtwzV1G43GDD0kpVMn/qbiNqyOPMFTX5nRffI+7vgWoFEc6DcXOxHJxrWNDXrZh18eDsZjvZGUljSRGA==",
- "requires": {
- "browserslist": "^4.17.5",
- "caniuse-lite": "^1.0.30001272",
- "fraction.js": "^4.1.1",
- "normalize-range": "^0.1.2",
- "picocolors": "^1.0.0",
- "postcss-value-parser": "^4.1.0"
- },
- "dependencies": {
- "browserslist": {
- "version": "4.19.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz",
- "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==",
- "requires": {
- "caniuse-lite": "^1.0.30001286",
- "electron-to-chromium": "^1.4.17",
- "escalade": "^3.1.1",
- "node-releases": "^2.0.1",
- "picocolors": "^1.0.0"
- }
- },
- "node-releases": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
- "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA=="
- }
- }
- },
- "axe-core": {
- "version": "4.3.5",
- "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.5.tgz",
- "integrity": "sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA==",
- "dev": true
- },
- "axobject-query": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
- "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==",
- "dev": true
- },
- "balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
- },
- "binary-extensions": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
- "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
- },
- "brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "requires": {
- "fill-range": "^7.0.1"
- }
- },
- "call-bind": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
- "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
- "dev": true,
- "requires": {
- "function-bind": "^1.1.1",
- "get-intrinsic": "^1.0.2"
- }
- },
- "callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
- },
- "camelcase-css": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
- "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
- },
- "caniuse-lite": {
- "version": "1.0.30001434",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
- "integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA=="
- },
- "chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "requires": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
- }
- },
- "client-only": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
- "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
- },
- "color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "requires": {
- "color-name": "1.1.3"
- }
- },
- "color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
- },
- "concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
- },
- "core-js-pure": {
- "version": "3.20.0",
- "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.20.0.tgz",
- "integrity": "sha512-qsrbIwWSEEYOM7z616jAVgwhuDDtPLwZSpUsU3vyUkHYqKTf/uwOJBZg2V7lMurYWkpVlaVOxBrfX0Q3ppvjfg==",
- "dev": true
- },
- "cosmiconfig": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
- "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
- "requires": {
- "@types/parse-json": "^4.0.0",
- "import-fresh": "^3.2.1",
- "parse-json": "^5.0.0",
- "path-type": "^4.0.0",
- "yaml": "^1.10.0"
- }
- },
- "countup.js": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.0.8.tgz",
- "integrity": "sha512-pW3xwwD+hB+xmtI16xFcuLS0D5hSQqPQWkZOdgpKQyzxCquDNo2VCFPkRw12vmvdpnicXVTcjmYiakG6biwINg=="
- },
- "cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
- "dev": true,
- "requires": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- }
- },
- "cssesc": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
- "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
- },
- "damerau-levenshtein": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz",
- "integrity": "sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw==",
- "dev": true
- },
- "debounce": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
- "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
- },
- "debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
- "requires": {
- "ms": "2.1.2"
- }
- },
- "deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true
- },
- "define-lazy-prop": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
- "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
- "dev": true
- },
- "define-properties": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
- "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
- "dev": true,
- "requires": {
- "has-property-descriptors": "^1.0.0",
- "object-keys": "^1.1.1"
- }
- },
- "defined": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
- "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM="
- },
- "detective": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
- "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
- "requires": {
- "acorn-node": "^1.6.1",
- "defined": "^1.0.0",
- "minimist": "^1.1.1"
- }
- },
- "didyoumean": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
- "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
- },
- "dir-glob": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
- "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
- "dev": true,
- "requires": {
- "path-type": "^4.0.0"
- }
- },
- "dlv": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
- "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
- },
- "doctrine": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
- "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2"
- }
- },
- "electron-to-chromium": {
- "version": "1.4.20",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.20.tgz",
- "integrity": "sha512-N7ZVNrdzX8NE90OXEFBMsBf3fp8P/vVDUER3WCUZjzC7OkNTXHVoF6W9qVhq8+dA8tGnbDajzUpj2ISNVVyj+Q=="
- },
- "emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true
- },
- "enhanced-resolve": {
- "version": "5.12.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
- "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.2.0"
- }
- },
- "enquirer": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
- "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
- "dev": true,
- "requires": {
- "ansi-colors": "^4.1.1"
- }
- },
- "error-ex": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
- "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
- "requires": {
- "is-arrayish": "^0.2.1"
- }
- },
- "es-abstract": {
- "version": "1.20.4",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz",
- "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "es-to-primitive": "^1.2.1",
- "function-bind": "^1.1.1",
- "function.prototype.name": "^1.1.5",
- "get-intrinsic": "^1.1.3",
- "get-symbol-description": "^1.0.0",
- "has": "^1.0.3",
- "has-property-descriptors": "^1.0.0",
- "has-symbols": "^1.0.3",
- "internal-slot": "^1.0.3",
- "is-callable": "^1.2.7",
- "is-negative-zero": "^2.0.2",
- "is-regex": "^1.1.4",
- "is-shared-array-buffer": "^1.0.2",
- "is-string": "^1.0.7",
- "is-weakref": "^1.0.2",
- "object-inspect": "^1.12.2",
- "object-keys": "^1.1.1",
- "object.assign": "^4.1.4",
- "regexp.prototype.flags": "^1.4.3",
- "safe-regex-test": "^1.0.0",
- "string.prototype.trimend": "^1.0.5",
- "string.prototype.trimstart": "^1.0.5",
- "unbox-primitive": "^1.0.2"
- }
- },
- "es-shim-unscopables": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
- "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
- "dev": true,
- "requires": {
- "has": "^1.0.3"
- }
- },
- "es-to-primitive": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
- "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
- "dev": true,
- "requires": {
- "is-callable": "^1.1.4",
- "is-date-object": "^1.0.1",
- "is-symbol": "^1.0.2"
- }
- },
- "escalade": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
- },
- "escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
- },
- "eslint": {
- "version": "8.4.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.4.1.tgz",
- "integrity": "sha512-TxU/p7LB1KxQ6+7aztTnO7K0i+h0tDi81YRY9VzB6Id71kNz+fFYnf5HD5UOQmxkzcoa0TlVZf9dpMtUv0GpWg==",
- "dev": true,
- "requires": {
- "@eslint/eslintrc": "^1.0.5",
- "@humanwhocodes/config-array": "^0.9.2",
- "ajv": "^6.10.0",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.2",
- "debug": "^4.3.2",
- "doctrine": "^3.0.0",
- "enquirer": "^2.3.5",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.1.0",
- "eslint-utils": "^3.0.0",
- "eslint-visitor-keys": "^3.1.0",
- "espree": "^9.2.0",
- "esquery": "^1.4.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^6.0.1",
- "functional-red-black-tree": "^1.0.1",
- "glob-parent": "^6.0.1",
- "globals": "^13.6.0",
- "ignore": "^4.0.6",
- "import-fresh": "^3.0.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "js-yaml": "^4.1.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "levn": "^0.4.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.0.4",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.1",
- "progress": "^2.0.0",
- "regexpp": "^3.2.0",
- "semver": "^7.2.1",
- "strip-ansi": "^6.0.1",
- "strip-json-comments": "^3.1.0",
- "text-table": "^0.2.0",
- "v8-compile-cache": "^2.0.3"
- },
- "dependencies": {
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "requires": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true
- },
- "glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "requires": {
- "is-glob": "^4.0.3"
- }
- },
- "has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true
- },
- "supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "requires": {
- "has-flag": "^4.0.0"
- }
- }
- }
- },
- "eslint-config-next": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.0.5.tgz",
- "integrity": "sha512-lge94W7ME6kNCO96eCykq5GbKbllzmcDNDhh1/llMCRgNPl0+GIQ8dOoM0I7uRQVW56VmTXFybJFXgow11a5pg==",
- "dev": true,
- "requires": {
- "@next/eslint-plugin-next": "13.0.5",
- "@rushstack/eslint-patch": "^1.1.3",
- "@typescript-eslint/parser": "^5.42.0",
- "eslint-import-resolver-node": "^0.3.6",
- "eslint-import-resolver-typescript": "^3.5.2",
- "eslint-plugin-import": "^2.26.0",
- "eslint-plugin-jsx-a11y": "^6.5.1",
- "eslint-plugin-react": "^7.31.7",
- "eslint-plugin-react-hooks": "^4.5.0"
- }
- },
- "eslint-import-resolver-node": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz",
- "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==",
- "dev": true,
- "requires": {
- "debug": "^3.2.7",
- "resolve": "^1.20.0"
- },
- "dependencies": {
- "debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "requires": {
- "ms": "^2.1.1"
- }
- }
- }
- },
- "eslint-import-resolver-typescript": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.2.tgz",
- "integrity": "sha512-zX4ebnnyXiykjhcBvKIf5TNvt8K7yX6bllTRZ14MiurKPjDpCAZujlszTdB8pcNXhZcOf+god4s9SjQa5GnytQ==",
- "dev": true,
- "requires": {
- "debug": "^4.3.4",
- "enhanced-resolve": "^5.10.0",
- "get-tsconfig": "^4.2.0",
- "globby": "^13.1.2",
- "is-core-module": "^2.10.0",
- "is-glob": "^4.0.3",
- "synckit": "^0.8.4"
- },
- "dependencies": {
- "globby": {
- "version": "13.1.2",
- "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz",
- "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==",
- "dev": true,
- "requires": {
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.2.11",
- "ignore": "^5.2.0",
- "merge2": "^1.4.1",
- "slash": "^4.0.0"
- }
- },
- "ignore": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
- "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
- "dev": true
- },
- "slash": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
- "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==",
- "dev": true
- }
- }
- },
- "eslint-module-utils": {
- "version": "2.7.4",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz",
- "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==",
- "dev": true,
- "requires": {
- "debug": "^3.2.7"
- },
- "dependencies": {
- "debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "requires": {
- "ms": "^2.1.1"
- }
- }
- }
- },
- "eslint-plugin-import": {
- "version": "2.26.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz",
- "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==",
- "dev": true,
- "requires": {
- "array-includes": "^3.1.4",
- "array.prototype.flat": "^1.2.5",
- "debug": "^2.6.9",
- "doctrine": "^2.1.0",
- "eslint-import-resolver-node": "^0.3.6",
- "eslint-module-utils": "^2.7.3",
- "has": "^1.0.3",
- "is-core-module": "^2.8.1",
- "is-glob": "^4.0.3",
- "minimatch": "^3.1.2",
- "object.values": "^1.1.5",
- "resolve": "^1.22.0",
- "tsconfig-paths": "^3.14.1"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "requires": {
- "ms": "2.0.0"
- }
- },
- "doctrine": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
- "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true
- },
- "resolve": {
- "version": "1.22.1",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
- "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
- "dev": true,
- "requires": {
- "is-core-module": "^2.9.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- }
- }
- }
- },
- "eslint-plugin-jsx-a11y": {
- "version": "6.5.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz",
- "integrity": "sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g==",
- "dev": true,
- "requires": {
- "@babel/runtime": "^7.16.3",
- "aria-query": "^4.2.2",
- "array-includes": "^3.1.4",
- "ast-types-flow": "^0.0.7",
- "axe-core": "^4.3.5",
- "axobject-query": "^2.2.0",
- "damerau-levenshtein": "^1.0.7",
- "emoji-regex": "^9.2.2",
- "has": "^1.0.3",
- "jsx-ast-utils": "^3.2.1",
- "language-tags": "^1.0.5",
- "minimatch": "^3.0.4"
- },
- "dependencies": {
- "@babel/runtime": {
- "version": "7.16.5",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
- "integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
- "dev": true,
- "requires": {
- "regenerator-runtime": "^0.13.4"
- }
- }
- }
- },
- "eslint-plugin-react": {
- "version": "7.31.11",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.11.tgz",
- "integrity": "sha512-TTvq5JsT5v56wPa9OYHzsrOlHzKZKjV+aLgS+55NJP/cuzdiQPC7PfYoUjMoxlffKtvijpk7vA/jmuqRb9nohw==",
- "dev": true,
- "requires": {
- "array-includes": "^3.1.6",
- "array.prototype.flatmap": "^1.3.1",
- "array.prototype.tosorted": "^1.1.1",
- "doctrine": "^2.1.0",
- "estraverse": "^5.3.0",
- "jsx-ast-utils": "^2.4.1 || ^3.0.0",
- "minimatch": "^3.1.2",
- "object.entries": "^1.1.6",
- "object.fromentries": "^2.0.6",
- "object.hasown": "^1.1.2",
- "object.values": "^1.1.6",
- "prop-types": "^15.8.1",
- "resolve": "^2.0.0-next.3",
- "semver": "^6.3.0",
- "string.prototype.matchall": "^4.0.8"
- },
- "dependencies": {
- "doctrine": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
- "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2"
- }
- },
- "resolve": {
- "version": "2.0.0-next.4",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
- "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
- "dev": true,
- "requires": {
- "is-core-module": "^2.9.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- }
- },
- "semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
- "dev": true
- }
- }
- },
- "eslint-plugin-react-hooks": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz",
- "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==",
- "dev": true,
- "requires": {}
- },
- "eslint-scope": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz",
- "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==",
- "dev": true,
- "requires": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- }
- },
- "eslint-utils": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
- "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
- "dev": true,
- "requires": {
- "eslint-visitor-keys": "^2.0.0"
- },
- "dependencies": {
- "eslint-visitor-keys": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
- "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
- "dev": true
- }
- }
- },
- "eslint-visitor-keys": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
- "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
- "dev": true
- },
- "espree": {
- "version": "9.2.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.2.0.tgz",
- "integrity": "sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg==",
- "dev": true,
- "requires": {
- "acorn": "^8.6.0",
- "acorn-jsx": "^5.3.1",
- "eslint-visitor-keys": "^3.1.0"
- },
- "dependencies": {
- "acorn": {
- "version": "8.6.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz",
- "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==",
- "dev": true
- }
- }
- },
- "esquery": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
- "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
- "dev": true,
- "requires": {
- "estraverse": "^5.1.0"
- }
- },
- "esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "requires": {
- "estraverse": "^5.2.0"
- }
- },
- "estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true
- },
- "esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true
- },
- "fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
- },
- "fast-glob": {
- "version": "3.2.12",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
- "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
- "requires": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.4"
- }
- },
- "fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true
- },
- "fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
- "dev": true
- },
- "fastq": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
- "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
- "requires": {
- "reusify": "^1.0.4"
- }
- },
- "file-entry-cache": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
- "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
- "dev": true,
- "requires": {
- "flat-cache": "^3.0.4"
- }
- },
- "fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
- "requires": {
- "to-regex-range": "^5.0.1"
- }
- },
- "flat-cache": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
- "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
- "dev": true,
- "requires": {
- "flatted": "^3.1.0",
- "rimraf": "^3.0.2"
- }
- },
- "flatted": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz",
- "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
- "dev": true
- },
- "fraction.js": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.1.2.tgz",
- "integrity": "sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA=="
- },
- "framer-motion": {
- "version": "5.5.3",
- "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-5.5.3.tgz",
- "integrity": "sha512-rS/eeTLdCsxg6F4Ikk+c+X9emawLSngwxdmXtgfT9qB8yiR2gi8/bdmfeSwyh3ngw63WcVVx3olIkwAY7x3p3A==",
- "requires": {
- "@emotion/is-prop-valid": "^0.8.2",
- "framesync": "6.0.1",
- "hey-listen": "^1.0.8",
- "popmotion": "11.0.3",
- "react-merge-refs": "^1.1.0",
- "react-use-measure": "^2.1.1",
- "style-value-types": "5.0.0",
- "tslib": "^2.1.0"
- }
- },
- "framesync": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.0.1.tgz",
- "integrity": "sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==",
- "requires": {
- "tslib": "^2.1.0"
- }
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
- },
- "fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "optional": true
- },
- "function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
- },
- "function.prototype.name": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
- "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "es-abstract": "^1.19.0",
- "functions-have-names": "^1.2.2"
- }
- },
- "functional-red-black-tree": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
- "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
- "dev": true
- },
- "functions-have-names": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
- "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
- "dev": true
- },
- "get-intrinsic": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
- "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
- "dev": true,
- "requires": {
- "function-bind": "^1.1.1",
- "has": "^1.0.3",
- "has-symbols": "^1.0.3"
- }
- },
- "get-symbol-description": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
- "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "get-intrinsic": "^1.1.1"
- }
- },
- "get-tsconfig": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.2.0.tgz",
- "integrity": "sha512-X8u8fREiYOE6S8hLbq99PeykTDoLVnxvF4DjWKJmz9xy2nNRdUcV8ZN9tniJFeKyTU3qnC9lL8n4Chd6LmVKHg==",
- "dev": true
- },
- "glob": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
- "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- },
- "glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "requires": {
- "is-glob": "^4.0.1"
- }
- },
- "globals": {
- "version": "13.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz",
- "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==",
- "dev": true,
- "requires": {
- "type-fest": "^0.20.2"
- },
- "dependencies": {
- "type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
- "dev": true
- }
- }
- },
- "globalyzer": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
- "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==",
- "dev": true
- },
- "globby": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
- "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
- "dev": true,
- "requires": {
- "array-union": "^2.1.0",
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.2.9",
- "ignore": "^5.2.0",
- "merge2": "^1.4.1",
- "slash": "^3.0.0"
- },
- "dependencies": {
- "ignore": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
- "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
- "dev": true
- }
- }
- },
- "globrex": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
- "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
- "dev": true
- },
- "graceful-fs": {
- "version": "4.2.10",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
- "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
- "dev": true
- },
- "has": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
- "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
- "requires": {
- "function-bind": "^1.1.1"
- }
- },
- "has-bigints": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
- "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
- "dev": true
- },
- "has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
- },
- "has-property-descriptors": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
- "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
- "dev": true,
- "requires": {
- "get-intrinsic": "^1.1.1"
- }
- },
- "has-symbols": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
- "dev": true
- },
- "has-tostringtag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
- "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
- "dev": true,
- "requires": {
- "has-symbols": "^1.0.2"
- }
- },
- "hey-listen": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
- "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="
- },
- "ignore": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
- "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
- "dev": true
- },
- "import-cwd": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz",
- "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==",
- "requires": {
- "import-from": "^3.0.0"
- }
- },
- "import-fresh": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
- "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
- "requires": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- }
- },
- "import-from": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz",
- "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==",
- "requires": {
- "resolve-from": "^5.0.0"
- },
- "dependencies": {
- "resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="
- }
- }
- },
- "imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
- "dev": true
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "requires": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
- },
- "internal-slot": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
- "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
- "dev": true,
- "requires": {
- "get-intrinsic": "^1.1.0",
- "has": "^1.0.3",
- "side-channel": "^1.0.4"
- }
- },
- "is-arrayish": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
- },
- "is-bigint": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
- "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
- "dev": true,
- "requires": {
- "has-bigints": "^1.0.1"
- }
- },
- "is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "requires": {
- "binary-extensions": "^2.0.0"
- }
- },
- "is-boolean-object": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
- "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
- }
- },
- "is-callable": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
- "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
- "dev": true
- },
- "is-core-module": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
- "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
- "requires": {
- "has": "^1.0.3"
- }
- },
- "is-date-object": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
- "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
- "dev": true,
- "requires": {
- "has-tostringtag": "^1.0.0"
- }
- },
- "is-docker": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
- "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
- "dev": true
- },
- "is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
- },
- "is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "requires": {
- "is-extglob": "^2.1.1"
- }
- },
- "is-negative-zero": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
- "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
- "dev": true
- },
- "is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
- },
- "is-number-object": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
- "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
- "dev": true,
- "requires": {
- "has-tostringtag": "^1.0.0"
- }
- },
- "is-regex": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
- "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "has-tostringtag": "^1.0.0"
- }
- },
- "is-shared-array-buffer": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
- "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2"
- }
- },
- "is-string": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
- "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
- "dev": true,
- "requires": {
- "has-tostringtag": "^1.0.0"
- }
- },
- "is-symbol": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
- "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
- "dev": true,
- "requires": {
- "has-symbols": "^1.0.2"
- }
- },
- "is-weakref": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
- "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2"
- }
- },
- "is-wsl": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
- "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
- "dev": true,
- "requires": {
- "is-docker": "^2.0.0"
- }
- },
- "isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
- "dev": true
- },
- "js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
- },
- "js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "requires": {
- "argparse": "^2.0.1"
- }
- },
- "json-parse-even-better-errors": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
- },
- "json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true
- },
- "json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
- "dev": true
- },
- "json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
- "dev": true,
- "requires": {
- "minimist": "^1.2.0"
- }
- },
- "jsx-ast-utils": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz",
- "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==",
- "dev": true,
- "requires": {
- "array-includes": "^3.1.3",
- "object.assign": "^4.1.2"
- }
- },
- "language-subtag-registry": {
- "version": "0.3.21",
- "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz",
- "integrity": "sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==",
- "dev": true
- },
- "language-tags": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz",
- "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=",
- "dev": true,
- "requires": {
- "language-subtag-registry": "~0.3.2"
- }
- },
- "levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "requires": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- }
- },
- "lilconfig": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz",
- "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA=="
- },
- "lines-and-columns": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
- },
- "lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true
- },
- "loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "requires": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- }
- },
- "lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "requires": {
- "yallist": "^4.0.0"
- }
- },
- "merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
- },
- "micromatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
- "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
- "requires": {
- "braces": "^3.0.1",
- "picomatch": "^2.2.3"
- }
- },
- "mini-svg-data-uri": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.3.tgz",
- "integrity": "sha512-gSfqpMRC8IxghvMcxzzmMnWpXAChSA+vy4cia33RgerMS8Fex95akUyQZPbxJJmeBGiGmK7n/1OpUX8ksRjIdA=="
- },
- "minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "minimist": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
- "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "nanoid": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
- "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
- },
- "natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
- "dev": true
- },
- "next": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/next/-/next-13.0.5.tgz",
- "integrity": "sha512-awpc3DkphyKydwCotcBnuKwh6hMqkT5xdiBK4OatJtOZurDPBYLP62jtM2be/4OunpmwIbsS0Eyv+ZGU97ciEg==",
- "requires": {
- "@next/env": "13.0.5",
- "@next/swc-android-arm-eabi": "13.0.5",
- "@next/swc-android-arm64": "13.0.5",
- "@next/swc-darwin-arm64": "13.0.5",
- "@next/swc-darwin-x64": "13.0.5",
- "@next/swc-freebsd-x64": "13.0.5",
- "@next/swc-linux-arm-gnueabihf": "13.0.5",
- "@next/swc-linux-arm64-gnu": "13.0.5",
- "@next/swc-linux-arm64-musl": "13.0.5",
- "@next/swc-linux-x64-gnu": "13.0.5",
- "@next/swc-linux-x64-musl": "13.0.5",
- "@next/swc-win32-arm64-msvc": "13.0.5",
- "@next/swc-win32-ia32-msvc": "13.0.5",
- "@next/swc-win32-x64-msvc": "13.0.5",
- "@swc/helpers": "0.4.14",
- "caniuse-lite": "^1.0.30001406",
- "postcss": "8.4.14",
- "styled-jsx": "5.1.0"
- }
- },
- "normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
- },
- "normalize-range": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
- "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI="
- },
- "object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true
- },
- "object-hash": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
- "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="
- },
- "object-inspect": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
- "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
- "dev": true
- },
- "object-keys": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
- "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
- "dev": true
- },
- "object.assign": {
- "version": "4.1.4",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
- "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "has-symbols": "^1.0.3",
- "object-keys": "^1.1.1"
- }
- },
- "object.entries": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz",
- "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- }
- },
- "object.fromentries": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz",
- "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- }
- },
- "object.hasown": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz",
- "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- }
- },
- "object.values": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
- "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- }
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "requires": {
- "wrappy": "1"
- }
- },
- "open": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz",
- "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==",
- "dev": true,
- "requires": {
- "define-lazy-prop": "^2.0.0",
- "is-docker": "^2.1.1",
- "is-wsl": "^2.2.0"
- }
- },
- "optionator": {
- "version": "0.9.1",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
- "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
- "dev": true,
- "requires": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.3"
- }
- },
- "parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "requires": {
- "callsites": "^3.0.0"
- }
- },
- "parse-json": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
- "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
- "requires": {
- "@babel/code-frame": "^7.0.0",
- "error-ex": "^1.3.1",
- "json-parse-even-better-errors": "^2.3.0",
- "lines-and-columns": "^1.1.6"
- }
- },
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
- },
- "path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true
- },
- "path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
- },
- "path-type": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
- "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
- },
- "picocolors": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
- "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
- },
- "picomatch": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
- "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw=="
- },
- "popmotion": {
- "version": "11.0.3",
- "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.3.tgz",
- "integrity": "sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==",
- "requires": {
- "framesync": "6.0.1",
- "hey-listen": "^1.0.8",
- "style-value-types": "5.0.0",
- "tslib": "^2.1.0"
- }
- },
- "postcss": {
- "version": "8.4.14",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
- "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
- "requires": {
- "nanoid": "^3.3.4",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- }
- },
- "postcss-js": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-3.0.3.tgz",
- "integrity": "sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==",
- "requires": {
- "camelcase-css": "^2.0.1",
- "postcss": "^8.1.6"
- }
- },
- "postcss-load-config": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz",
- "integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==",
- "requires": {
- "import-cwd": "^3.0.0",
- "lilconfig": "^2.0.3",
- "yaml": "^1.10.2"
- }
- },
- "postcss-nested": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
- "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
- "requires": {
- "postcss-selector-parser": "^6.0.6"
- }
- },
- "postcss-selector-parser": {
- "version": "6.0.7",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.7.tgz",
- "integrity": "sha512-U+b/Deoi4I/UmE6KOVPpnhS7I7AYdKbhGcat+qTQ27gycvaACvNEw11ba6RrkwVmDVRW7sigWgLj4/KbbJjeDA==",
- "requires": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- }
- },
- "postcss-value-parser": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
- },
- "prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true
- },
- "progress": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
- "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
- "dev": true
- },
- "prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
- "requires": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
- "punycode": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true
- },
- "queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
- },
- "quick-lru": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
- "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="
- },
- "react": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
- "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
- "requires": {
- "loose-envify": "^1.1.0"
- }
- },
- "react-countup": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.1.1.tgz",
- "integrity": "sha512-9mYlnk/yCFg3g2/65pCTex6mr9h0eYdDMIL4T5nfLj/RzoiqY03wlPxZglzbJ6OzXvU37OmYApwQ4WMZNFr8Aw==",
- "requires": {
- "countup.js": "^2.0.8"
- }
- },
- "react-dom": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
- "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
- "requires": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.0"
- }
- },
- "react-icons": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
- "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==",
- "requires": {}
- },
- "react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
- },
- "react-merge-refs": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz",
- "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ=="
- },
- "react-use-measure": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz",
- "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==",
- "requires": {
- "debounce": "^1.2.1"
- }
- },
- "regenerator-runtime": {
- "version": "0.13.4",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz",
- "integrity": "sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g==",
- "dev": true
- },
- "regexp.prototype.flags": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
- "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "functions-have-names": "^1.2.2"
- }
- },
- "regexpp": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
- "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
- "dev": true
- },
- "resolve": {
- "version": "1.20.0",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
- "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
- "requires": {
- "is-core-module": "^2.2.0",
- "path-parse": "^1.0.6"
- }
- },
- "resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
- },
- "reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
- },
- "rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "requires": {
- "glob": "^7.1.3"
- }
- },
- "run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "requires": {
- "queue-microtask": "^1.2.2"
- }
- },
- "safe-regex-test": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
- "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "get-intrinsic": "^1.1.3",
- "is-regex": "^1.1.4"
- }
- },
- "scheduler": {
- "version": "0.23.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
- "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
- "requires": {
- "loose-envify": "^1.1.0"
- }
- },
- "semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
- "dev": true,
- "requires": {
- "lru-cache": "^6.0.0"
- }
- },
- "shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "requires": {
- "shebang-regex": "^3.0.0"
- }
- },
- "shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true
- },
- "side-channel": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
- "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.0",
- "get-intrinsic": "^1.0.2",
- "object-inspect": "^1.9.0"
- }
- },
- "slash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
- "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
- "dev": true
- },
- "source-map-js": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
- "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
- },
- "string.prototype.matchall": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
- "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "get-intrinsic": "^1.1.3",
- "has-symbols": "^1.0.3",
- "internal-slot": "^1.0.3",
- "regexp.prototype.flags": "^1.4.3",
- "side-channel": "^1.0.4"
- }
- },
- "string.prototype.trimend": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
- "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- }
- },
- "string.prototype.trimstart": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
- "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- }
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- },
- "strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true
- },
- "strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true
- },
- "style-value-types": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.0.0.tgz",
- "integrity": "sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==",
- "requires": {
- "hey-listen": "^1.0.8",
- "tslib": "^2.1.0"
- }
- },
- "styled-jsx": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.0.tgz",
- "integrity": "sha512-/iHaRJt9U7T+5tp6TRelLnqBqiaIT0HsO0+vgyj8hK2KUk7aejFqRrumqPUlAqDwAj8IbS/1hk3IhBAAK/FCUQ==",
- "requires": {
- "client-only": "0.0.1"
- }
- },
- "supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "requires": {
- "has-flag": "^3.0.0"
- }
- },
- "supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true
- },
- "synckit": {
- "version": "0.8.4",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.4.tgz",
- "integrity": "sha512-Dn2ZkzMdSX827QbowGbU/4yjWuvNaCoScLLoMo/yKbu+P4GBR6cRGKZH27k6a9bRzdqcyd1DE96pQtQ6uNkmyw==",
- "dev": true,
- "requires": {
- "@pkgr/utils": "^2.3.1",
- "tslib": "^2.4.0"
- }
- },
- "tailwindcss": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.5.tgz",
- "integrity": "sha512-59pNgzx2o+wkAk7IZGIH7H9eNS53gzZGrO3+NPyOEWHDbquHgiLL/c993T5t1vPSAeBxox4X5OgZwNuRvXVf+g==",
- "requires": {
- "arg": "^5.0.1",
- "chalk": "^4.1.2",
- "chokidar": "^3.5.2",
- "color-name": "^1.1.4",
- "cosmiconfig": "^7.0.1",
- "detective": "^5.2.0",
- "didyoumean": "^1.2.2",
- "dlv": "^1.1.3",
- "fast-glob": "^3.2.7",
- "glob-parent": "^6.0.2",
- "is-glob": "^4.0.3",
- "normalize-path": "^3.0.0",
- "object-hash": "^2.2.0",
- "postcss-js": "^3.0.3",
- "postcss-load-config": "^3.1.0",
- "postcss-nested": "5.0.6",
- "postcss-selector-parser": "^6.0.6",
- "postcss-value-parser": "^4.2.0",
- "quick-lru": "^5.1.1",
- "resolve": "^1.20.0",
- "tmp": "^0.2.1"
- },
- "dependencies": {
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "requires": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- }
- },
- "chokidar": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
- "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
- "requires": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "fsevents": "~2.3.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
- },
- "dependencies": {
- "glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "requires": {
- "is-glob": "^4.0.1"
- }
- }
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
- },
- "glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "requires": {
- "is-glob": "^4.0.3"
- }
- },
- "has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
- },
- "readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "requires": {
- "picomatch": "^2.2.1"
- }
- },
- "supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "requires": {
- "has-flag": "^4.0.0"
- }
- }
- }
- },
- "tapable": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
- "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
- "dev": true
- },
- "text-table": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
- "dev": true
- },
- "tiny-glob": {
- "version": "0.2.9",
- "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
- "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==",
- "dev": true,
- "requires": {
- "globalyzer": "0.1.0",
- "globrex": "^0.1.2"
- }
- },
- "tmp": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
- "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
- "requires": {
- "rimraf": "^3.0.0"
- }
- },
- "to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "requires": {
- "is-number": "^7.0.0"
- }
- },
- "tsconfig-paths": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
- "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
- "dev": true,
- "requires": {
- "@types/json5": "^0.0.29",
- "json5": "^1.0.1",
- "minimist": "^1.2.6",
- "strip-bom": "^3.0.0"
- }
- },
- "tslib": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
- "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
- },
- "tsutils": {
- "version": "3.21.0",
- "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
- "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
- "dev": true,
- "requires": {
- "tslib": "^1.8.1"
- },
- "dependencies": {
- "tslib": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "dev": true
- }
- }
- },
- "type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
- "dev": true,
- "requires": {
- "prelude-ls": "^1.2.1"
- }
- },
- "typescript": {
- "version": "4.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
- "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
- "dev": true,
- "peer": true
- },
- "unbox-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
- "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "has-bigints": "^1.0.2",
- "has-symbols": "^1.0.3",
- "which-boxed-primitive": "^1.0.2"
- }
- },
- "uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "requires": {
- "punycode": "^2.1.0"
- }
- },
- "util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
- },
- "uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
- },
- "v8-compile-cache": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
- "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
- "dev": true
- },
- "which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "requires": {
- "isexe": "^2.0.0"
- }
- },
- "which-boxed-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
- "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
- "dev": true,
- "requires": {
- "is-bigint": "^1.0.1",
- "is-boolean-object": "^1.1.0",
- "is-number-object": "^1.0.4",
- "is-string": "^1.0.5",
- "is-symbol": "^1.0.3"
- }
- },
- "word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
- "dev": true
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
- },
- "xtend": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
- },
- "yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
- },
- "yaml": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
- "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
- }
- }
-}
diff --git a/package.json b/package.json
index 4efe3b6..ee07567 100644
--- a/package.json
+++ b/package.json
@@ -8,16 +8,35 @@
"lint": "next lint"
},
"dependencies": {
+ "@next/mdx": "^15.2.4",
"@tailwindcss/forms": "^0.4.0",
- "framer-motion": "^5.5.3",
- "next": "^13.0.5",
- "react": "^18.2.0",
+ "@tailwindcss/typography": "^0.5.16",
+ "basiclightbox": "^5.0.4",
+ "framer-motion": "^12.6.2",
+ "gray-matter": "^4.0.3",
+ "lucide-react": "^0.484.0",
+ "next": "^15.2.4",
+ "next-mdx-remote": "^5.0.0",
+ "next-themes": "^0.4.6",
+ "object-hash": "^3.0.0",
+ "react": "^19.0.0",
"react-countup": "^6.1.1",
- "react-dom": "^18.2.0",
+ "react-dom": "^19.0.0",
"react-icons": "^4.3.1",
- "uuid": "^8.3.2"
+ "react-markdown": "^10.1.0",
+ "reading-time": "^1.5.0",
+ "rehype-autolink-headings": "^7.1.0",
+ "rehype-highlight": "^7.0.2",
+ "rehype-slug": "^6.0.0",
+ "remark-gfm": "^4.0.1",
+ "remark-mdx-frontmatter": "^5.1.0",
+ "remark-slug": "^7.0.1",
+ "sharp": "^0.33.5",
+ "uuid": "^8.3.2",
+ "yet-another-react-lightbox": "^3.21.8"
},
"devDependencies": {
+ "@types/node": "22.13.14",
"autoprefixer": "^10.4.0",
"eslint": "8.4.1",
"eslint-config-next": "^13.0.5",
diff --git a/pages/_app.jsx b/pages/_app.jsx
index 4f5dd28..8e052bd 100644
--- a/pages/_app.jsx
+++ b/pages/_app.jsx
@@ -1,19 +1,54 @@
+import Script from 'next/script';
import '../styles/globals.css';
import { AnimatePresence } from 'framer-motion';
import DefaultLayout from '../components/layout/DefaultLayout';
import UseScrollToTop from '../hooks/useScrollToTop';
+import { ThemeProvider } from 'next-themes';
+import { Playfair_Display } from 'next/font/google';
+
+const playfair = Playfair_Display({
+ subsets: ['latin'],
+ weight: ['400', '500', '600', '700'],
+ variable: '--font-playfair',
+ display: 'swap',
+});
+
+const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_TRACKING_ID;
function MyApp({ Component, pageProps }) {
- return (
-
-
-
-
-
-
-
-
- );
+ const getLayout = Component.getLayout || ((page) => (
+ {page}
+ ));
+
+ return (
+
+
+
+ {getLayout()}
+
+
+
+
+ {/* Google Analytics */}
+ {GA_TRACKING_ID && (
+ <>
+
+
+ >
+ )}
+
+ );
}
+
export default MyApp;
diff --git a/pages/about.jsx b/pages/about.jsx
index e7fd4be..bc52ab5 100644
--- a/pages/about.jsx
+++ b/pages/about.jsx
@@ -1,42 +1,53 @@
+// pages/about.js
+
import { motion } from 'framer-motion';
import AboutClients from '../components/about/AboutClients';
import AboutCounter from '../components/about/AboutCounter';
import AboutMeBio from '../components/about/AboutMeBio';
import PagesMetaHead from '../components/PagesMetaHead';
+import Container from '../components/layout/Container';
+
+function About() {
+ return (
+
+
+
+ {/* Bio section in container */}
+
+
+
+
+
+
+ {/* Full-width counter (not wrapped in Container) */}
+
+
+
+
+
+
+
-function about() {
- return (
-
-
-
-
-
-
-
- {/** Counter without paddings */}
-
-
-
-
-
-
-
-
- );
+ {/* Clients section in container */}
+
+
+
+
+
+
+ );
}
-export default about;
+export default About;
diff --git a/pages/blog/[id].jsx b/pages/blog/[id].jsx
new file mode 100644
index 0000000..f83c73d
--- /dev/null
+++ b/pages/blog/[id].jsx
@@ -0,0 +1,258 @@
+// pages/blog/[id].jsx
+
+import fs from "fs";
+import path from "path";
+import matter from "gray-matter";
+import { serialize } from "next-mdx-remote/serialize";
+import { MDXRemote } from "next-mdx-remote";
+import remarkGfm from "remark-gfm";
+import remarkFrontmatter from "remark-mdx-frontmatter";
+import rehypeSlug from "rehype-slug";
+import rehypeHighlight from "rehype-highlight";
+import rehypeAutolinkHeadings from "rehype-autolink-headings";
+import remarkSlug from "remark-slug";
+
+import Image from "next/image";
+import { useEffect, useState, useRef } from "react";
+import { FiClock, FiTag } from "react-icons/fi";
+import { motion } from "framer-motion";
+import readingTime from "reading-time";
+
+import PagesMetaHead from "../../components/PagesMetaHead";
+import DefaultBlogLayout from "../../components/blog/layouts/DefaultBlogLayout";
+import Container from "../../components/layout/Container";
+
+import TableOfContents from "../../components/TableOfContents";
+import GalleryLightbox from "../../components/ui/GalleryLightbox";
+import NextPrev from "../../components/ui/NextPrev";
+
+
+import {
+ Section,
+ ImageGallery,
+ Callout,
+ FadeInSection,
+ AuthorBox,
+ ShareButtons,
+ AnimatedSection,
+ ExplodedBookGallery,
+ SectionDivider,
+} from "../../components/ui";
+
+const blogDirectory = path.join(process.cwd(), "content/blog");
+
+export async function getStaticPaths() {
+ const files = fs.readdirSync(blogDirectory);
+ const paths = files.map((filename) => ({
+ params: { id: filename.replace(/\.(mdx|qmd)$/, "") },
+ }));
+ return { paths, fallback: false };
+}
+
+export async function getStaticProps({ params }) {
+ const filePathMdx = path.join(blogDirectory, `${params.id}.mdx`);
+ const filePathQmd = path.join(blogDirectory, `${params.id}.qmd`);
+ const filePath = fs.existsSync(filePathMdx) ? filePathMdx : filePathQmd;
+
+ if (!fs.existsSync(filePath)) return { notFound: true };
+
+ const source = fs.readFileSync(filePath, "utf8");
+ const { content, data } = matter(source);
+ const readStats = readingTime(content);
+
+ const files = fs.readdirSync(blogDirectory).filter((file) =>
+ file.endsWith(".mdx") || file.endsWith(".qmd")
+ );
+ const slugs = files.map((file) => file.replace(/\.(mdx|qmd)$/, ""));
+ const currentIndex = slugs.indexOf(params.id);
+
+ const getPostMeta = (slug) => {
+ if (!slug) return null;
+ const fullPath = path.join(blogDirectory, `${slug}.mdx`);
+ const fileContent = fs.readFileSync(fullPath, "utf8");
+ const { data } = matter(fileContent);
+ return { slug, title: data.title || slug };
+ };
+
+ const prevPost = getPostMeta(slugs[currentIndex - 1]);
+ const nextPost = getPostMeta(slugs[currentIndex + 1]);
+
+ const mdxSource = await serialize(content, {
+ scope: data,
+ mdxOptions: {
+ remarkPlugins: [remarkGfm, remarkFrontmatter, remarkSlug],
+ rehypePlugins: [rehypeSlug, rehypeHighlight, rehypeAutolinkHeadings],
+ },
+ });
+
+ return {
+ props: {
+ frontMatter: {
+ ...data,
+ readingTime: readStats.text,
+ wordCount: readStats.words,
+ },
+ mdxSource,
+ prevPost,
+ nextPost,
+ isBlog: true,
+ },
+ };
+}
+
+function BlogSingle({ frontMatter, mdxSource, prevPost, nextPost }) {
+
+
+
+ const nextPrevRef = useRef(null);
+ const [scrollProgress, setScrollProgress] = useState(0);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ const scrollTop = window.scrollY;
+ const height = document.body.scrollHeight - window.innerHeight;
+ setScrollProgress((scrollTop / height) * 100);
+ };
+
+ window.addEventListener("scroll", handleScroll);
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, []);
+
+ const handleLightbox = (src) => {
+ import("basiclightbox").then((basicLightbox) => {
+ basicLightbox
+ .create(`
`)
+ .show();
+ });
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ {Math.round(scrollProgress)}%
+
+
+
+ {/* Scroll Indicator */}
+
+ {Math.round(scrollProgress)}%
+
+
+
+
+
+
+ {frontMatter.title}
+
+
+
+
+ {/* Meta Info */}
+
+
{frontMatter.date}
+ {frontMatter.readingTime && (
+
🕒 {frontMatter.readingTime}
+ )}
+ {frontMatter.wordCount && (
+
✍️ {frontMatter.wordCount} words
+ )}
+ {frontMatter.tags?.length > 0 && (
+
+
+ {frontMatter.tags.map((tag) => (
+
+ {tag}
+
+ ))}
+
+ )}
+
+
+
+
+ {/* Actual content */}
+
+ ,
+ h2: (props) => ,
+ h3: (props) => ,
+ p: (props) => ,
+ a: (props) => ,
+ blockquote: (props) => (
+
+ ),
+ img: (props) => (
+
handleLightbox(props.src)}
+ />
+ ),
+ Image,
+ motion,
+ AnimatedSection,
+ Callout,
+ FadeInSection,
+ AuthorBox,
+ ShareButtons,
+ TableOfContents,
+ GalleryLightbox,
+ NextPrev,
+ ImageGallery,
+ Section,
+ ExplodedBookGallery,
+ SectionDivider,
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+
+BlogSingle.getLayout = (page) => (
+ {page}
+
+);
+
+export default BlogSingle;
\ No newline at end of file
diff --git a/pages/blog/index.js b/pages/blog/index.js
new file mode 100644
index 0000000..6054a4c
--- /dev/null
+++ b/pages/blog/index.js
@@ -0,0 +1,47 @@
+import fs from "fs";
+import path from "path";
+import matter from "gray-matter";
+import BlogGrid from "../../components/blog/BlogGrid";
+import DefaultBlogLayout from "../../components/blog/layouts/DefaultBlogLayout";
+import Container from "../../components/layout/Container";
+
+export async function getStaticProps() {
+ const blogDir = path.join(process.cwd(), "content/blog");
+ const filenames = fs.readdirSync(blogDir);
+
+ const blogs = filenames.map((filename) => {
+ const slug = filename.replace(/\.(mdx|qmd)$/, "");
+ const filePath = path.join(blogDir, filename);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ const { data } = matter(fileContent);
+
+ return {
+ slug,
+ title: data.title,
+ publishDate: data.date,
+ img: data.image || "/default-thumbnail.jpg", // Ensures fallback image
+ tags: data.tags || [],
+ category: data.category || "General", // optional fallback
+ };
+ }).filter(blog => blog.title && blog.publishDate); // Ensures valid data
+
+ return {
+ props: {
+ blogs,
+ },
+ };
+}
+
+
+
+function BlogPage({ blogs }) {
+ return
+}
+
+BlogPage.getLayout = (page) => (
+
+ {page}
+
+);
+
+export default BlogPage;
\ No newline at end of file
diff --git a/pages/index.jsx b/pages/index.jsx
index 47ac0cf..ebf3d9e 100644
--- a/pages/index.jsx
+++ b/pages/index.jsx
@@ -3,23 +3,24 @@ import PagesMetaHead from '../components/PagesMetaHead';
import ProjectsGrid from '../components/projects/ProjectsGrid';
import Button from '../components/reusable/Button';
import AppBanner from '../components/shared/AppBanner';
+import Container from '../components/layout/Container';
export default function Home() {
- return (
-
-
+ return (
+
+
-
+
-
+
-
-
- );
+
+
+ );
}
diff --git a/pages/projects/[id].jsx b/pages/projects/[id].jsx
index 9ed8176..28402f2 100644
--- a/pages/projects/[id].jsx
+++ b/pages/projects/[id].jsx
@@ -1,170 +1,36 @@
-import Image from 'next/image';
-import { FiClock, FiTag } from 'react-icons/fi';
-import PagesMetaHead from '../../components/PagesMetaHead';
-import { projectsData } from '../../data/projectsData';
-import RelatedProjects from '../../components/projects/RelatedProjects';
+// pages/projects/[id].jsx
-function ProjectSingle(props) {
- return (
-
-
+import { projectsData } from "../../data/projectsData";
+import NotebookProjectLayout from "../../components/projects/layouts/NotebookProjectLayout";
+import DefaultProjectLayout from "../../components/projects/layouts/DefaultProjectLayout";
- {/* Header */}
-
-
- {props.project.ProjectHeader.title}
-
-
-
-
-
- {props.project.ProjectHeader.publishDate}
-
-
-
-
-
- {props.project.ProjectHeader.tags}
-
-
-
-
+// Mapping layouts based on project type
+const layoutMap = {
+ notebook: NotebookProjectLayout,
+ default: DefaultProjectLayout,
+};
- {/* Gallery */}
-
- {props.project.ProjectImages.map((project) => {
- return (
-
-
-
- );
- })}
-
+function ProjectSingle({ project }) {
+ if (!project) {
+ return
Project not found.
;
+ }
- {/* Info */}
-
-
- {/* Single project client details */}
-
-
- {props.project.ProjectInfo.ClientHeading}
-
-
- {props.project.ProjectInfo.CompanyInfo.map(
- (info) => {
- return (
- -
- {info.title}:
-
- {info.details}
-
-
- );
- }
- )}
-
-
-
- {/* Single project objectives */}
-
-
- {props.project.ProjectInfo.ObjectivesHeading}
-
-
- {props.project.ProjectInfo.ObjectivesDetails}
-
-
-
- {/* Single project technologies */}
-
-
- {props.project.ProjectInfo.Technologies[0].title}
-
-
- {props.project.ProjectInfo.Technologies[0].techs.join(
- ', '
- )}
-
-
-
- {/* Single project social sharing */}
-
-
- {props.project.ProjectInfo.SocialSharingHeading}
-
- {/*
- {props.project.ProjectInfo.SocialSharing.map(
- (social, index) => {
-
-
- {social.icon}
-
- ;
- }
- )}
-
*/}
-
-
-
- {/* Single project right section details */}
-
-
- {props.project.ProjectInfo.ProjectDetailsHeading}
-
- {props.project.ProjectInfo.ProjectDetails.map((details) => {
- return (
-
- {details.details}
-
- );
- })}
-
-
-
-
-
- );
+ return Content inside the project layout
;
}
export async function getServerSideProps({ query }) {
- const { id } = query;
- return {
- props: {
- project: projectsData.filter(
- (project) => project.id === parseInt(id)
- )[0],
- },
- };
+ const { id } = query;
+ const project = projectsData.find((p) => p.id === parseInt(id));
+
+ return { props: { project: project || null, isBlog: false } };
}
+// 🧠 Assign the layout dynamically based on the project type
+ProjectSingle.getLayout = function getLayout(page) {
+ const project = page.props?.project;
+ const Layout = layoutMap[project?.type] || DefaultProjectLayout;
+
+ return {page};
+};
+
export default ProjectSingle;
diff --git a/pages/projects/index.jsx b/pages/projects/index.jsx
index 3e4179b..15ca308 100644
--- a/pages/projects/index.jsx
+++ b/pages/projects/index.jsx
@@ -1,14 +1,21 @@
+// pages/projects/index.jsx
+
+import Container from '../../components/layout/Container';
import PagesMetaHead from '../../components/PagesMetaHead';
import ProjectsGrid from '../../components/projects/ProjectsGrid';
+import DefaultLayout from '../../components/layout/DefaultLayout';
-function index() {
- return (
-
- );
+function ProjectsIndex() {
+ return (
+
+
+
+
+ );
}
-export default index;
+ProjectsIndex.getLayout = function getLayout(page) {
+ return {page};
+};
+
+export default ProjectsIndex;
diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png
new file mode 100644
index 0000000..99b78dc
Binary files /dev/null and b/public/apple-touch-icon-precomposed.png differ
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
new file mode 100644
index 0000000..99b78dc
Binary files /dev/null and b/public/apple-touch-icon.png differ
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index 03d88ec..0000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/favicon.png b/public/favicon.png
new file mode 100644
index 0000000..70fa964
Binary files /dev/null and b/public/favicon.png differ
diff --git a/public/files/CV-Amadou-Mamane.pdf b/public/files/CV-Amadou-Mamane.pdf
new file mode 100644
index 0000000..a84c8df
Binary files /dev/null and b/public/files/CV-Amadou-Mamane.pdf differ
diff --git a/public/files/Stoman-Resume.pdf b/public/files/Stoman-Resume.pdf
deleted file mode 100644
index c09d0fe..0000000
Binary files a/public/files/Stoman-Resume.pdf and /dev/null differ
diff --git a/public/images/blog-data-tools.png b/public/images/blog-data-tools.png
new file mode 100644
index 0000000..56d2a21
Binary files /dev/null and b/public/images/blog-data-tools.png differ
diff --git a/public/images/blog-neural-networks.jpg b/public/images/blog-neural-networks.jpg
new file mode 100644
index 0000000..c91d6a1
Binary files /dev/null and b/public/images/blog-neural-networks.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/books-changed-my-life.jpg b/public/images/blog/readings/books-changed-my-life/books-changed-my-life.jpg
new file mode 100644
index 0000000..e1f2b26
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/books-changed-my-life.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/americanah.jpg b/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/americanah.jpg
new file mode 100644
index 0000000..120ab54
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/americanah.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/chimamanda-ngozi-adichie.jpg b/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/chimamanda-ngozi-adichie.jpg
new file mode 100644
index 0000000..5d7f3dc
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/chimamanda-ngozi-adichie.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/chimamanda-ngozi-adichie.webp b/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/chimamanda-ngozi-adichie.webp
new file mode 100644
index 0000000..5902c9e
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/chimamanda-ngozi-adichie.webp differ
diff --git a/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/half-of-a-yellow-sun.jpg b/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/half-of-a-yellow-sun.jpg
new file mode 100644
index 0000000..fc4c411
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/half-of-a-yellow-sun.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/schriftstellerin-chimamanda-ngozi-adichie.jpeg b/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/schriftstellerin-chimamanda-ngozi-adichie.jpeg
new file mode 100644
index 0000000..9291574
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/chimamanda-ngozi-adichie/schriftstellerin-chimamanda-ngozi-adichie.jpeg differ
diff --git a/public/images/blog/readings/books-changed-my-life/david-r-hawkins/dr-david-hawkins.jpg b/public/images/blog/readings/books-changed-my-life/david-r-hawkins/dr-david-hawkins.jpg
new file mode 100644
index 0000000..650c062
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/david-r-hawkins/dr-david-hawkins.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/david-r-hawkins/letting-go.jpeg b/public/images/blog/readings/books-changed-my-life/david-r-hawkins/letting-go.jpeg
new file mode 100644
index 0000000..dc2a07a
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/david-r-hawkins/letting-go.jpeg differ
diff --git a/public/images/blog/readings/books-changed-my-life/david-r-hawkins/power-vs-force.jpeg b/public/images/blog/readings/books-changed-my-life/david-r-hawkins/power-vs-force.jpeg
new file mode 100644
index 0000000..d446b66
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/david-r-hawkins/power-vs-force.jpeg differ
diff --git a/public/images/blog/readings/books-changed-my-life/deepak-chopra/deepak-chopra.jpg b/public/images/blog/readings/books-changed-my-life/deepak-chopra/deepak-chopra.jpg
new file mode 100644
index 0000000..37c0e7c
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/deepak-chopra/deepak-chopra.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/deepak-chopra/metahuman.jpg b/public/images/blog/readings/books-changed-my-life/deepak-chopra/metahuman.jpg
new file mode 100644
index 0000000..82189e7
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/deepak-chopra/metahuman.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/dr-joe-dispenza/becoming-supernatural.jpg b/public/images/blog/readings/books-changed-my-life/dr-joe-dispenza/becoming-supernatural.jpg
new file mode 100644
index 0000000..9c13fb7
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/dr-joe-dispenza/becoming-supernatural.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/dr-joe-dispenza/breaking-habit-being-yourself.jpg b/public/images/blog/readings/books-changed-my-life/dr-joe-dispenza/breaking-habit-being-yourself.jpg
new file mode 100644
index 0000000..64ca74d
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/dr-joe-dispenza/breaking-habit-being-yourself.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/dr-joe-dispenza/dr-joe-dispenza.jpg b/public/images/blog/readings/books-changed-my-life/dr-joe-dispenza/dr-joe-dispenza.jpg
new file mode 100644
index 0000000..83a9289
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/dr-joe-dispenza/dr-joe-dispenza.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/dr-joe-dispenza/you-are-the-placebo.jpg b/public/images/blog/readings/books-changed-my-life/dr-joe-dispenza/you-are-the-placebo.jpg
new file mode 100644
index 0000000..72bd423
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/dr-joe-dispenza/you-are-the-placebo.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/eckhart-tolle/1-eckhart-tolle.jpg b/public/images/blog/readings/books-changed-my-life/eckhart-tolle/1-eckhart-tolle.jpg
new file mode 100644
index 0000000..65af430
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/eckhart-tolle/1-eckhart-tolle.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/eckhart-tolle/a-new-earth.jpg b/public/images/blog/readings/books-changed-my-life/eckhart-tolle/a-new-earth.jpg
new file mode 100644
index 0000000..0f125a3
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/eckhart-tolle/a-new-earth.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/eckhart-tolle/the_power_of_now.jpg b/public/images/blog/readings/books-changed-my-life/eckhart-tolle/the_power_of_now.jpg
new file mode 100644
index 0000000..daf14bd
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/eckhart-tolle/the_power_of_now.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/jay-shetty/jay-shetty.jpg b/public/images/blog/readings/books-changed-my-life/jay-shetty/jay-shetty.jpg
new file mode 100644
index 0000000..0095bf6
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/jay-shetty/jay-shetty.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/jay-shetty/think-like-a-monk.png b/public/images/blog/readings/books-changed-my-life/jay-shetty/think-like-a-monk.png
new file mode 100644
index 0000000..7b7ad69
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/jay-shetty/think-like-a-monk.png differ
diff --git a/public/images/blog/readings/books-changed-my-life/napoleon-hill/napoleon-hill.jpeg b/public/images/blog/readings/books-changed-my-life/napoleon-hill/napoleon-hill.jpeg
new file mode 100644
index 0000000..851d0e3
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/napoleon-hill/napoleon-hill.jpeg differ
diff --git a/public/images/blog/readings/books-changed-my-life/napoleon-hill/think-and-grow-rich.jpeg b/public/images/blog/readings/books-changed-my-life/napoleon-hill/think-and-grow-rich.jpeg
new file mode 100644
index 0000000..dde5ecd
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/napoleon-hill/think-and-grow-rich.jpeg differ
diff --git a/public/images/blog/readings/books-changed-my-life/napoleon-hill/think-and-grow-rich.png b/public/images/blog/readings/books-changed-my-life/napoleon-hill/think-and-grow-rich.png
new file mode 100644
index 0000000..8320428
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/napoleon-hill/think-and-grow-rich.png differ
diff --git a/public/images/blog/readings/books-changed-my-life/tony-robbins/life-force.jpg b/public/images/blog/readings/books-changed-my-life/tony-robbins/life-force.jpg
new file mode 100644
index 0000000..c993244
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/tony-robbins/life-force.jpg differ
diff --git a/public/images/blog/readings/books-changed-my-life/tony-robbins/life-force.png b/public/images/blog/readings/books-changed-my-life/tony-robbins/life-force.png
new file mode 100644
index 0000000..fc8dc3f
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/tony-robbins/life-force.png differ
diff --git a/public/images/blog/readings/books-changed-my-life/tony-robbins/tony-robbins.webp b/public/images/blog/readings/books-changed-my-life/tony-robbins/tony-robbins.webp
new file mode 100644
index 0000000..d357959
Binary files /dev/null and b/public/images/blog/readings/books-changed-my-life/tony-robbins/tony-robbins.webp differ
diff --git a/public/images/blog/sports/berlin-marathon/alexandre.png b/public/images/blog/sports/berlin-marathon/alexandre.png
new file mode 100644
index 0000000..71e41b2
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/alexandre.png differ
diff --git a/public/images/blog/sports/berlin-marathon/altes-musueum-1.png b/public/images/blog/sports/berlin-marathon/altes-musueum-1.png
new file mode 100644
index 0000000..9bfafe5
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/altes-musueum-1.png differ
diff --git a/public/images/blog/sports/berlin-marathon/altes-musueum-2.png b/public/images/blog/sports/berlin-marathon/altes-musueum-2.png
new file mode 100644
index 0000000..5d13eef
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/altes-musueum-2.png differ
diff --git a/public/images/blog/sports/berlin-marathon/altes-musueum-3.png b/public/images/blog/sports/berlin-marathon/altes-musueum-3.png
new file mode 100644
index 0000000..b329153
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/altes-musueum-3.png differ
diff --git a/public/images/blog/sports/berlin-marathon/altes-musueum-4.png b/public/images/blog/sports/berlin-marathon/altes-musueum-4.png
new file mode 100644
index 0000000..765c7b9
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/altes-musueum-4.png differ
diff --git a/public/images/blog/sports/berlin-marathon/babylon-cinema-2.png b/public/images/blog/sports/berlin-marathon/babylon-cinema-2.png
new file mode 100644
index 0000000..ce56fa1
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/babylon-cinema-2.png differ
diff --git a/public/images/blog/sports/berlin-marathon/babylon-cinema.png b/public/images/blog/sports/berlin-marathon/babylon-cinema.png
new file mode 100644
index 0000000..67859b0
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/babylon-cinema.png differ
diff --git a/public/images/blog/sports/berlin-marathon/behind_the_team-questions.png b/public/images/blog/sports/berlin-marathon/behind_the_team-questions.png
new file mode 100644
index 0000000..355752e
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/behind_the_team-questions.png differ
diff --git a/public/images/blog/sports/berlin-marathon/behind_the_team.jpg b/public/images/blog/sports/berlin-marathon/behind_the_team.jpg
new file mode 100644
index 0000000..29a1f1b
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/behind_the_team.jpg differ
diff --git a/public/images/blog/sports/berlin-marathon/behind_the_team_large.jpg b/public/images/blog/sports/berlin-marathon/behind_the_team_large.jpg
new file mode 100644
index 0000000..c0de284
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/behind_the_team_large.jpg differ
diff --git a/public/images/blog/sports/berlin-marathon/berlin-marathon-1.png b/public/images/blog/sports/berlin-marathon/berlin-marathon-1.png
new file mode 100644
index 0000000..025584d
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/berlin-marathon-1.png differ
diff --git a/public/images/blog/sports/berlin-marathon/berlin-marathon-2.png b/public/images/blog/sports/berlin-marathon/berlin-marathon-2.png
new file mode 100644
index 0000000..673d222
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/berlin-marathon-2.png differ
diff --git a/public/images/blog/sports/berlin-marathon/berlin-marathon-arrival.png b/public/images/blog/sports/berlin-marathon/berlin-marathon-arrival.png
new file mode 100644
index 0000000..673d222
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/berlin-marathon-arrival.png differ
diff --git a/public/images/blog/sports/berlin-marathon/berlin-marathon-crowd.png b/public/images/blog/sports/berlin-marathon/berlin-marathon-crowd.png
new file mode 100644
index 0000000..025584d
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/berlin-marathon-crowd.png differ
diff --git a/public/images/blog/sports/berlin-marathon/berlin-marathon-hero.png b/public/images/blog/sports/berlin-marathon/berlin-marathon-hero.png
new file mode 100644
index 0000000..673d222
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/berlin-marathon-hero.png differ
diff --git a/public/images/blog/sports/berlin-marathon/berlin-marathon-streets.png b/public/images/blog/sports/berlin-marathon/berlin-marathon-streets.png
new file mode 100644
index 0000000..673d222
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/berlin-marathon-streets.png differ
diff --git a/public/images/blog/sports/berlin-marathon/brendenbourg-race-1.png b/public/images/blog/sports/berlin-marathon/brendenbourg-race-1.png
new file mode 100644
index 0000000..806b221
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/brendenbourg-race-1.png differ
diff --git a/public/images/blog/sports/berlin-marathon/east-side-galery-1.png b/public/images/blog/sports/berlin-marathon/east-side-galery-1.png
new file mode 100644
index 0000000..f5812aa
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/east-side-galery-1.png differ
diff --git a/public/images/blog/sports/berlin-marathon/east-side-galery-2.png b/public/images/blog/sports/berlin-marathon/east-side-galery-2.png
new file mode 100644
index 0000000..2be221a
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/east-side-galery-2.png differ
diff --git a/public/images/blog/sports/berlin-marathon/east-side-galery-3.png b/public/images/blog/sports/berlin-marathon/east-side-galery-3.png
new file mode 100644
index 0000000..b2b2290
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/east-side-galery-3.png differ
diff --git a/public/images/blog/sports/berlin-marathon/east-side-galery-4.png b/public/images/blog/sports/berlin-marathon/east-side-galery-4.png
new file mode 100644
index 0000000..7a05282
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/east-side-galery-4.png differ
diff --git a/public/images/blog/sports/berlin-marathon/fernsehturm-inside.png b/public/images/blog/sports/berlin-marathon/fernsehturm-inside.png
new file mode 100644
index 0000000..0152a10
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/fernsehturm-inside.png differ
diff --git a/public/images/blog/sports/berlin-marathon/fernsehturm.png b/public/images/blog/sports/berlin-marathon/fernsehturm.png
new file mode 100644
index 0000000..60ccda8
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/fernsehturm.png differ
diff --git a/public/images/blog/sports/berlin-marathon/hollocost-memeorial.png b/public/images/blog/sports/berlin-marathon/hollocost-memeorial.png
new file mode 100644
index 0000000..e40bc54
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/hollocost-memeorial.png differ
diff --git a/public/images/blog/sports/berlin-marathon/mustapha-kebab-1.png b/public/images/blog/sports/berlin-marathon/mustapha-kebab-1.png
new file mode 100644
index 0000000..921f46d
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/mustapha-kebab-1.png differ
diff --git a/public/images/blog/sports/berlin-marathon/mustapha-kebab-2.png b/public/images/blog/sports/berlin-marathon/mustapha-kebab-2.png
new file mode 100644
index 0000000..b81532d
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/mustapha-kebab-2.png differ
diff --git a/public/images/blog/sports/berlin-marathon/neues-museim-2.png b/public/images/blog/sports/berlin-marathon/neues-museim-2.png
new file mode 100644
index 0000000..201ec8d
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/neues-museim-2.png differ
diff --git a/public/images/blog/sports/berlin-marathon/neues-museim-3.png b/public/images/blog/sports/berlin-marathon/neues-museim-3.png
new file mode 100644
index 0000000..baca8be
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/neues-museim-3.png differ
diff --git a/public/images/blog/sports/berlin-marathon/neues-museim.png b/public/images/blog/sports/berlin-marathon/neues-museim.png
new file mode 100644
index 0000000..49cb639
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/neues-museim.png differ
diff --git a/public/images/blog/sports/berlin-marathon/pack-collection-2.png b/public/images/blog/sports/berlin-marathon/pack-collection-2.png
new file mode 100644
index 0000000..b4e5418
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/pack-collection-2.png differ
diff --git a/public/images/blog/sports/berlin-marathon/pack-collection-3.png b/public/images/blog/sports/berlin-marathon/pack-collection-3.png
new file mode 100644
index 0000000..394097a
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/pack-collection-3.png differ
diff --git a/public/images/blog/sports/berlin-marathon/pack-collection.png b/public/images/blog/sports/berlin-marathon/pack-collection.png
new file mode 100644
index 0000000..8ea26c3
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/pack-collection.png differ
diff --git a/public/images/blog/sports/berlin-marathon/patrick_sang.png b/public/images/blog/sports/berlin-marathon/patrick_sang.png
new file mode 100644
index 0000000..fe20f52
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/patrick_sang.png differ
diff --git a/public/images/blog/sports/berlin-marathon/patrick_sang_questions.png b/public/images/blog/sports/berlin-marathon/patrick_sang_questions.png
new file mode 100644
index 0000000..ec3de86
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/patrick_sang_questions.png differ
diff --git a/public/images/blog/sports/berlin-marathon/pergamon-museum-2.png b/public/images/blog/sports/berlin-marathon/pergamon-museum-2.png
new file mode 100644
index 0000000..f85c579
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/pergamon-museum-2.png differ
diff --git a/public/images/blog/sports/berlin-marathon/pergamum-meusum.png b/public/images/blog/sports/berlin-marathon/pergamum-meusum.png
new file mode 100644
index 0000000..569a7c0
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/pergamum-meusum.png differ
diff --git a/public/images/blog/sports/berlin-marathon/porte-brandebourg.jpg.webp b/public/images/blog/sports/berlin-marathon/porte-brandebourg.jpg.webp
new file mode 100644
index 0000000..712f47c
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/porte-brandebourg.jpg.webp differ
diff --git a/public/images/blog/sports/berlin-marathon/race-start-1.png b/public/images/blog/sports/berlin-marathon/race-start-1.png
new file mode 100644
index 0000000..84d22df
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/race-start-1.png differ
diff --git a/public/images/blog/sports/berlin-marathon/race-start-2.png b/public/images/blog/sports/berlin-marathon/race-start-2.png
new file mode 100644
index 0000000..ab82da9
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/race-start-2.png differ
diff --git a/public/images/blog/sports/berlin-marathon/race-start-3.png b/public/images/blog/sports/berlin-marathon/race-start-3.png
new file mode 100644
index 0000000..9992547
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/race-start-3.png differ
diff --git a/public/images/blog/sports/berlin-marathon/race_day_outfit.png b/public/images/blog/sports/berlin-marathon/race_day_outfit.png
new file mode 100644
index 0000000..866a6b5
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/race_day_outfit.png differ
diff --git a/public/images/blog/sports/berlin-marathon/tiergarten.webp b/public/images/blog/sports/berlin-marathon/tiergarten.webp
new file mode 100644
index 0000000..5decc5a
Binary files /dev/null and b/public/images/blog/sports/berlin-marathon/tiergarten.webp differ
diff --git a/public/images/blog/travel/greek-islands/greek-islands.png.webp b/public/images/blog/travel/greek-islands/greek-islands.png.webp
new file mode 100644
index 0000000..f42fb71
Binary files /dev/null and b/public/images/blog/travel/greek-islands/greek-islands.png.webp differ
diff --git a/public/images/blog/travel/ibiza/ibiza.jpg b/public/images/blog/travel/ibiza/ibiza.jpg
new file mode 100644
index 0000000..bda1bff
Binary files /dev/null and b/public/images/blog/travel/ibiza/ibiza.jpg differ
diff --git a/public/images/brands/cartes_bancaires_gray.png b/public/images/brands/cartes_bancaires_gray.png
new file mode 100644
index 0000000..a387e7f
Binary files /dev/null and b/public/images/brands/cartes_bancaires_gray.png differ
diff --git a/public/images/brands/edf_gray.png b/public/images/brands/edf_gray.png
new file mode 100644
index 0000000..2fa3c60
Binary files /dev/null and b/public/images/brands/edf_gray.png differ
diff --git a/public/images/brands/orange_gray.png b/public/images/brands/orange_gray.png
new file mode 100644
index 0000000..857b4ab
Binary files /dev/null and b/public/images/brands/orange_gray.png differ
diff --git a/public/images/brands/oui_sncf_gray.png b/public/images/brands/oui_sncf_gray.png
new file mode 100644
index 0000000..ba6276a
Binary files /dev/null and b/public/images/brands/oui_sncf_gray.png differ
diff --git a/public/images/brands/rci_gray.png b/public/images/brands/rci_gray.png
new file mode 100644
index 0000000..56b5af8
Binary files /dev/null and b/public/images/brands/rci_gray.png differ
diff --git a/public/images/brands/renault_gray.png b/public/images/brands/renault_gray.png
new file mode 100644
index 0000000..2704020
Binary files /dev/null and b/public/images/brands/renault_gray.png differ
diff --git a/public/images/brands/sncf_gray.png b/public/images/brands/sncf_gray.png
new file mode 100644
index 0000000..95f019e
Binary files /dev/null and b/public/images/brands/sncf_gray.png differ
diff --git a/public/images/brands/societe_generale_gray.png b/public/images/brands/societe_generale_gray.png
new file mode 100644
index 0000000..ff9fae9
Binary files /dev/null and b/public/images/brands/societe_generale_gray.png differ
diff --git a/public/images/images:blog-data-tools.png b/public/images/images:blog-data-tools.png
new file mode 100644
index 0000000..d895623
Binary files /dev/null and b/public/images/images:blog-data-tools.png differ
diff --git a/public/images/kaggle-isic-2024.png b/public/images/kaggle-isic-2024.png
new file mode 100644
index 0000000..03ac17e
Binary files /dev/null and b/public/images/kaggle-isic-2024.png differ
diff --git a/public/images/logo-dark.png b/public/images/logo-dark.png
new file mode 100644
index 0000000..6628bd4
Binary files /dev/null and b/public/images/logo-dark.png differ
diff --git a/public/images/logo-dark.svg b/public/images/logo-dark.svg
index 7f186f7..87664bf 100644
--- a/public/images/logo-dark.svg
+++ b/public/images/logo-dark.svg
@@ -6,7 +6,7 @@
.st0{fill:#F4F4F4;stroke:#F4F4F4;stroke-width:2;stroke-miterlimit:10;}
.st1{fill:#4E5CA6;}
.st2{enable-background:new ;}
- .st3{fill:#303031;}
+ .st3{fill:#FFFFFF;}
@@ -19,23 +19,23 @@
c4.7,9.1,7,19.4,7.1,31v9.9c0,19-3.3,33.5-9.9,43.5c-6.6,10-16,16.8-28.2,20.2C197,286,182.4,287.8,165.4,287.8z"/>
-
-
-
-
+
+
+
-
+
-
+ c30.1,0,47.6,17,47.6,46v78.2h-19.3V245z M1036.7,249.6c19.5,0,31.7-12.2,31.7-30.8v-9.9c-9-1.2-15.4-1.4-21.4-1.4
+ c-23.2,0-34.7,6.9-34.7,21.2C1012.3,241.3,1021.7,249.6,1036.7,249.6z"/>
+
diff --git a/public/images/logo-dark_letter.svg b/public/images/logo-dark_letter.svg
new file mode 100644
index 0000000..de64bfe
--- /dev/null
+++ b/public/images/logo-dark_letter.svg
@@ -0,0 +1,7 @@
+
diff --git a/public/images/logo-light.png b/public/images/logo-light.png
new file mode 100644
index 0000000..05285ea
Binary files /dev/null and b/public/images/logo-light.png differ
diff --git a/public/images/logo-light.svg b/public/images/logo-light.svg
deleted file mode 100644
index 87664bf..0000000
--- a/public/images/logo-light.svg
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
diff --git a/public/images/music-recomendation.jpg b/public/images/music-recomendation.jpg
new file mode 100644
index 0000000..cbff273
Binary files /dev/null and b/public/images/music-recomendation.jpg differ
diff --git a/public/images/music-recommendation.png b/public/images/music-recommendation.png
new file mode 100644
index 0000000..0d73822
Binary files /dev/null and b/public/images/music-recommendation.png differ
diff --git a/public/images/music-recommendation.webp b/public/images/music-recommendation.webp
new file mode 100644
index 0000000..923d1ce
Binary files /dev/null and b/public/images/music-recommendation.webp differ
diff --git a/public/images/profile.jpeg b/public/images/profile.jpeg
index 7cbe9d3..ecde453 100644
Binary files a/public/images/profile.jpeg and b/public/images/profile.jpeg differ
diff --git a/public/images/profile_gray.jpeg b/public/images/profile_gray.jpeg
new file mode 100644
index 0000000..3e9eef3
Binary files /dev/null and b/public/images/profile_gray.jpeg differ
diff --git a/public/images/projects/ai-agents/agentic-ai.jpg b/public/images/projects/ai-agents/agentic-ai.jpg
new file mode 100644
index 0000000..14342f5
Binary files /dev/null and b/public/images/projects/ai-agents/agentic-ai.jpg differ
diff --git a/public/images/projects/ai-agents/ai-agent.jpeg b/public/images/projects/ai-agents/ai-agent.jpeg
new file mode 100644
index 0000000..e9fcebd
Binary files /dev/null and b/public/images/projects/ai-agents/ai-agent.jpeg differ
diff --git a/public/images/projects/ai-agents/langchain.png b/public/images/projects/ai-agents/langchain.png
new file mode 100644
index 0000000..77effee
Binary files /dev/null and b/public/images/projects/ai-agents/langchain.png differ
diff --git a/public/images/top-ai-tools.png b/public/images/top-ai-tools.png
new file mode 100644
index 0000000..55ebea7
Binary files /dev/null and b/public/images/top-ai-tools.png differ
diff --git a/public/images/transformers.png b/public/images/transformers.png
new file mode 100644
index 0000000..1dda2ee
Binary files /dev/null and b/public/images/transformers.png differ
diff --git a/public/images/web-project-2-.jpg b/public/images/web-project-2-.jpg
new file mode 100644
index 0000000..f66d1ba
Binary files /dev/null and b/public/images/web-project-2-.jpg differ
diff --git a/public/images/web-project-2.jpg b/public/images/web-project-2.jpg
index 91ff591..8a09e8e 100644
Binary files a/public/images/web-project-2.jpg and b/public/images/web-project-2.jpg differ
diff --git a/public/notebooks/isic-2024_kaggle.html b/public/notebooks/isic-2024_kaggle.html
new file mode 100644
index 0000000..4bc3493
--- /dev/null
+++ b/public/notebooks/isic-2024_kaggle.html
@@ -0,0 +1,4333 @@
+
+
+
+
+
+
+
+
+
+isic-2024_kaggle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Initialization
+
+
import time
+# Unique indentifier specific to this execution
+run_id = int(time.time())
+
+
+
#! pip install tensorflow==2.15.0
+! pip install -q imblearn keras-tuner tensorflow-addons keras-cv memory_profiler
+
+
+ Global variables
+
+
# Database configuration: Choose between 'dagshub_storage', 'google_drive', 'local'
+database = 'local' # Current choice: local
+
+# Execution mode: Choose between 'CPU' or 'GPU'
+execution_mode = 'GPU' # Current choice: GPU
+
+# Execution environment: Choose between 'colab', 'mac'
+execution_env = 'kaggle' # Current choice: colab
+
+# Experimentation mode: Choose between 'test' or 'prod'
+experimentation_mode = 'prod' # Current choice: test
+
+# Training and Inference settings
+train_all_models = False # Train all models
+run_inference = True # Run inference
+run_inference_use_all_models = True # Run inference on all models
+model_type = 'pretrained_ensemble_model' # Type of model to train, choose between 'single_model', 'pretrained_ensemble_model', 'compact_ensemble_model'
+inference_model_name = model_type # Use the model_type as the inference model name
+
+# Dataset settings
+download_raw_datasets = False # Do not download raw datasets
+download_refined_training_datasets = True if execution_env == 'colab' else False # Download refined datasets if running on Colab
+dataset_size = 20000 # Set dataset size to 40,000 samples
+create_new_training_datasets = False # Do not create new training datasets
+
+
+
+ Execution strategy
+
+
import tensorflow as tf
+from tensorflow.keras import mixed_precision
+import warnings
+
+def limit_memory_growth():
+ gpu_devices = tf.config.experimental.list_physical_devices('GPU')
+ try:
+ for gpu in gpu_devices:
+ tf.config.experimental.set_memory_growth(gpu, True)
+ except Exception as e:
+ print('Can not set memory growth', e)
+
+def disable_eager_execution():
+ tf.compat.v1.disable_eager_execution()
+
+def enable_xla():
+ tf.config.optimizer.set_jit(True)
+
+def disable_gpu():
+ tf.config.set_visible_devices([], 'GPU')
+ visible_devices = tf.config.get_visible_devices()
+ for device in visible_devices:
+ assert device.device_type != 'GPU'
+
+def enable_mixed_precision():
+ policy = mixed_precision.Policy('mixed_float16')
+ mixed_precision.set_global_policy(policy)
+ print("Mixed precision enabled: ", mixed_precision.global_policy())
+
+# Suppress all warnings in the notebook
+warnings.filterwarnings('ignore')
+
+limit_memory_growth()
+#enable_mixed_precision()
+#tf.debugging.set_log_device_placement(True)
+#tf.config.set_visible_devices([], 'GPU')
+enable_xla()
+tf.config.optimizer.set_experimental_options({
+ "memory_optimization": True
+})
+
+
+
def check_tpu_initialized():
+ """Check if TPU is already initialized."""
+ tpu_devices = tf.config.list_logical_devices('TPU')
+ if len(tpu_devices) > 0:
+ print(f"TPU is already initialized with {len(tpu_devices)} logical devices.")
+ return True
+ else:
+ print("TPU is not initialized.")
+ return False
+
+def set_tpu_strategy(force=False):
+ """Set the TPU strategy if TPU is initialized."""
+ if check_tpu_initialized() and not force:
+ try:
+ resolver = tf.distribute.cluster_resolver.TPUClusterResolver() # TPU detection
+ strategy = tf.distribute.TPUStrategy(resolver)
+
+ print("Using TPU strategy.")
+ return strategy
+ except Exception as e:
+ print(f"Error while setting TPU strategy: {e}")
+ return None
+ else:
+ try:
+ print("Trying to initialize TPU.")
+ resolver = tf.distribute.cluster_resolver.TPUClusterResolver()
+ tf.config.experimental_connect_to_cluster(resolver)
+ tf.tpu.experimental.initialize_tpu_system(resolver)
+ strategy = tf.distribute.TPUStrategy(resolver)
+ print("TPU initialized and TPU strategy set.")
+ return strategy
+ except ValueError:
+ print("No TPU devices found.")
+ return None
+
+strategy = set_tpu_strategy(force = execution_env == 'colab')
+running_on_tpu = strategy is not None
+
+if strategy is None:
+ # For GPUs or CPU
+ if tf.config.list_physical_devices('GPU') and execution_mode == 'GPU':
+ limit_memory_growth()
+ strategy = tf.distribute.MirroredStrategy()
+ enable_mixed_precision() # Enable mixed precision policy
+ print("Running on GPU")
+
+ else:
+ strategy = tf.distribute.get_strategy()
+ print("Running on CPU")
+
+
+
+ Librairies import
+
+
# Core Libraries
+import os
+import gc
+import re
+import h5py
+import time
+import random
+import copy
+import psutil
+import hashlib
+import traceback
+import numpy as np
+import pandas as pd
+from glob import glob
+from collections import defaultdict
+from multiprocessing import Pool
+from concurrent.futures import ThreadPoolExecutor
+
+# TensorFlow and Keras Imports
+import tensorflow.keras.backend as K
+from tensorflow.keras import layers, Model, Input, regularizers, optimizers, callbacks
+from tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping, ModelCheckpoint
+from tensorflow.keras.layers.experimental import preprocessing
+from tensorflow.keras.utils import register_keras_serializable
+
+# TensorFlow Add-ons
+import tensorflow_addons as tfa
+
+# Keras Tuner for Hyperparameter Optimization
+import keras_tuner as kt
+from keras_tuner import HyperModel
+from keras_tuner.tuners import RandomSearch
+
+# TensorFlow Pretrained Models
+from tensorflow.keras.applications import (
+ EfficientNetB0, EfficientNetB4, EfficientNetB7,
+ DenseNet121, DenseNet169, DenseNet201,
+ InceptionV3, InceptionResNetV2,
+ ResNet50, ResNet101, ResNet152V2,
+ VGG16, VGG19, Xception
+)
+
+# Data Oversampling
+from imblearn.over_sampling import SMOTE
+
+# Scikit-Learn Utilities
+from sklearn.metrics import (
+ roc_curve, roc_auc_score, auc,
+ accuracy_score, classification_report, confusion_matrix
+)
+from sklearn.model_selection import StratifiedShuffleSplit
+
+# Memory Profiling
+from memory_profiler import memory_usage
+
+# Visualization Libraries
+import matplotlib.pyplot as plt
+import seaborn as sns
+sns.set()
+
+
+
+ Database setting
+
+
if download_refined_training_datasets:
+ if database == 'local':
+ %pip install -q dagshub
+ import dagshub.colab
+ DAGSHUB_REPO = dagshub.colab.login()
+ elif database == 'google_drive':
+ from google.colab import drive
+ drive.mount('/content/drive')
+ ROOT_DB_DIR='/content/drive/MyDrive/documents_travail'
+ else:
+ %pip install -q dagshub
+ import dagshub.colab
+ DAGSHUB_REPO = dagshub.colab.login()
+ mount_path = dagshub.storage.mount(DAGSHUB_REPO, cache=True)
+ print(f'Mount path: {mount_path}')
+ ROOT_DB_DIR=f'/content/{mount_path}'
+
+
+
+ Training variables
+
+
# Define root directories based on the execution environment
+if execution_env == 'mac':
+ ROOT_DB_DIR = '/Users/amadou/local/datalab'
+elif execution_env == 'kaggle':
+ ROOT_DB_DIR = '/kaggle/'
+else:
+ ROOT_DB_DIR = '/content'
+
+input_dir = f'{ROOT_DB_DIR}/input'
+output_dir = f'{ROOT_DB_DIR}/working'
+cache_dir = f'{output_dir}/tf_cache' # Temporary cache directory for TensorFlow
+project_dir = f"{output_dir}/isic_2024" # Project-specific directory
+tmp_dir = f'{output_dir}/tmp' # Temporary files directory
+models_dir = f'{output_dir}/models' # Directory to save model files
+pretrained_models_dir = f'{input_dir}/pretrained/tensorflow2/default/1/'
+
+# Define dataset directories
+datasets_dir = f'{input_dir}/datasets' # Root dataset directory
+training_dir = f'{datasets_dir}/training' # Training data directory
+training_metadata_dir = f'{training_dir}/metadata' # Metadata for training data
+
+# Define directories for training data (positives and negatives)
+train_dir = f'{datasets_dir}/training/train'
+train_dir_pos = f'{train_dir}/positives' # Positive training samples
+train_dir_negs = f'{train_dir}/negatives' # Negative training samples
+
+# Define validation and evaluation data directories (positives and negatives)
+val_dir = f'{datasets_dir}/training/val'
+val_dir_pos = f'{val_dir}/positives' # Positive validation samples
+val_dir_negs = f'{val_dir}/negatives' # Negative validation samples
+
+eval_dir = f'{datasets_dir}/training/eval'
+eval_dir_pos = f'{eval_dir}/positives' # Positive evaluation samples
+eval_dir_negs = f'{eval_dir}/negatives' # Negative evaluation samples
+
+# Define paths for inference
+inference_dir = f'{project_dir}/inference' # Inference-related directory
+inference_model_path = os.path.join(models_dir, 'inference_model.h5') # Model for inference
+inference_images_dir = f'{inference_dir}/images/hdf5' # Directory to source inference images
+inference_images_path = f'{inference_images_dir}/test-image.hdf5' # HDF5 file with test images
+inference_metadata_dir = f'{inference_dir}/metadata' # Directory for inference metadata
+inference_tabular_data_path = os.path.join(inference_metadata_dir, 'test-metadata.csv') # Test metadata for tabular data
+preprocessor_path = f'{inference_dir}/preprocessor_{run_id}.pkl' # Preprocessor for inference
+
+# Define TFRecord directories
+tf_records_dir = f'{input_dir}/tf_records/{dataset_size}' if experimentation_mode == 'test' else f'{input_dir}/sample-20k/sample_20k_kaggle'
+
+tf_records_train_val_dir = f'{tf_records_dir}/train' # TFRecords for training
+tf_records_val_dir = f'{tf_records_dir}/validation' # TFRecords for validation
+tf_records_eval_dir = f'{tf_records_dir}/evaluation' # TFRecords for evaluation
+oversample_persisted_data = False # Oversample persisted data when creating tf_records
+
+# Image and model settings
+img_height = 148 # Height of the input images
+img_width = 148 # Width of the input images
+img_channels = 3 # Number of color channels
+img_shape = (img_height, img_width, img_channels) # Shape of the input images (H x W x C)
+image_size = img_height # Image size, set to height for consistency
+
+# Training and evaluation settings
+use_cross_validation = False # Whether to use cross-validation
+augment_train_data = True # Augment training data
+use_tabular_data = False # Whether to include tabular data in the model
+force_cache = True # Force caching of data
+cache_in_memory = True if execution_env == 'colab' else False # Cache in memory only for Colab
+do_fine_tuning = True # Whether to fine-tune the model
+oversample_minority_class = True # Oversample the minority class in imbalanced datasets
+initial_epochs = 1 if experimentation_mode == 'test' else 5 # Initial epochs for training
+fine_tune_epochs = 1 if experimentation_mode == 'test' else 80 # Epochs for fine-tuning
+freeze_base_model = False # Whether to freeze the base model during training
+
+# Dataset buffering and shuffling
+buffer_size = 1000 if execution_env == 'colab' else 700 # Buffer size for data shuffling
+shuffle_train_val_at_each_call = True # Shuffle training/validation data at each call
+train_file_pattern = f'{tf_records_train_val_dir}/*.tfrecord' # Pattern to match TFRecord files
+
+# Model and training configurations
+model_name = 'res_net_50' # Name of the model to be used (ResNet50)
+train_individuals = False # Whether to train individual models of the pretrained ensemble
+run_evaluations = False
+
+# Batch size settings depending on environment and mode
+if experimentation_mode == 'test':
+ batch_size = 16 if execution_env == 'colab' else 16
+ val_batch_size = 16 if execution_env == 'colab' else 32
+ eval_batch_size = 4 if execution_env == 'colab' else 32
+else:
+ batch_size = 128 if execution_env == 'colab' else 128 # Batch size varies between Colab and local
+ val_batch_size = 128 if execution_env == 'colab' else 128 # Validation batch size
+ eval_batch_size = 128 if execution_env == 'colab' else 128 # Evaluation batch size
+
+# Model creation settings
+dropout_rate = 0.5
+l2_lambda = 0.02
+num_tabular_features = 14
+kernel_initializer = 'he_normal'
+activation = 'swish'
+
+
+
if create_new_training_datasets:
+ !rm -rf {tf_records_dir}
+
+!mkdir -p {tmp_dir}
+!mkdir -p {sample_dir}
+!mkdir -p {models_dir}
+!mkdir -p {tf_records_train_val_dir}
+!mkdir -p {tf_records_val_dir}
+!mkdir -p {tf_records_eval_dir}
+!mkdir -p {inference_dir}
+!mkdir -p {inference_images_dir}
+!mkdir -p {inference_metadata_dir}
+
+
+
+
+ Data preparation
+
+ Preparation utils
+
+
+ Encoding utils
+
+
# Fit the encoder for tabular data using dense format
+def fit_tabular_encoder(tabular_data):
+ num_cols = ['age_approx']
+ one_hot_cols = ['anatom_site_general', 'sex']
+
+ # Using dense format for OneHotEncoder
+ preprocessor = ColumnTransformer(
+ transformers=[
+ ('num', StandardScaler(), num_cols),
+ ('cat', OneHotEncoder(handle_unknown='ignore', sparse_output=False), one_hot_cols) # Dense output
+ ]
+ )
+ preprocessor.fit(tabular_data)
+ return preprocessor
+
+# Encode tabular data with dense format, called only after fit_tabular_encoder
+# or directely for val and eval data
+def encode_tabular_data(tabular_data, preprocessor):
+ # Use the preprocessor to transform the tabular data into dense format
+ return preprocessor.transform(tabular_data)
+
+# Save and load preprocessor for reuse between training and inference
+def save_preprocessor(preprocessor, file_path):
+ with open(file_path, 'wb') as f:
+ pickle.dump(preprocessor, f)
+ print('Saved preprocessor at', file_path)
+
+def load_preprocessor(file_path):
+ with open(file_path, 'rb') as f:
+ preprocessor = pickle.load(f)
+ print('Loaded preprocessor from', file_path)
+ return preprocessor
+
+
+
+ Oversampling utils
+
+
# Optionally apply smote on the whole dataset to balance minority and majority
+def smote_oversampling(image_paths, labels, tabular_data):
+ """Apply SMOTE to the labels and tabular data, with synchronized image resampling."""
+ tf.print("Applying SMOTE")
+
+ # Separate image paths, labels, and tabular data by class
+ class_0_indices = [i for i, label in enumerate(labels) if label == 0]
+ class_1_indices = [i for i, label in enumerate(labels) if label == 1]
+
+ class_0_image_paths = [image_paths[i] for i in class_0_indices]
+ class_1_image_paths = [image_paths[i] for i in class_1_indices]
+
+ if tabular_data is not None:
+ class_0_tabular = tabular_data.iloc[class_0_indices].reset_index(drop=True)
+ class_1_tabular = tabular_data.iloc[class_1_indices].reset_index(drop=True)
+ else:
+ class_0_tabular = class_1_tabular = None
+
+ # Apply SMOTE to the labels and tabular data
+ smote = SMOTE(sampling_strategy='minority')
+
+ if tabular_data is not None:
+ oversampled_tabular_data, oversampled_labels = smote.fit_resample(tabular_data, labels)
+ else:
+ dummy_data = np.zeros((len(labels), 1))
+ _, oversampled_labels = smote.fit_resample(dummy_data, labels)
+ oversampled_tabular_data = None
+
+ # Prepare for synchronized resampling of image paths and tabular data
+ synthetic_image_paths = []
+ synthetic_tabular_data = []
+
+ class_0_count = len(class_0_image_paths)
+ class_1_count = len(class_1_image_paths)
+
+ for idx, label in enumerate(oversampled_labels):
+ if label == 0:
+ synthetic_image_paths.append(class_0_image_paths[idx % class_0_count])
+ if tabular_data is not None:
+ synthetic_tabular_data.append(class_0_tabular.iloc[idx % class_0_count].values)
+ else:
+ synthetic_image_paths.append(class_1_image_paths[idx % class_1_count])
+ if tabular_data is not None:
+ synthetic_tabular_data.append(class_1_tabular.iloc[idx % class_1_count].values)
+
+ # Ensure coherence by converting synthetic_tabular_data to DataFrame if needed
+ if tabular_data is not None:
+ synthetic_tabular_data = pd.DataFrame(synthetic_tabular_data, columns=tabular_data.columns)
+ else:
+ synthetic_tabular_data = None
+
+ # Return synchronized image paths, labels, and tabular data
+ return synthetic_image_paths, oversampled_labels, synthetic_tabular_data
+
+
+
+ Subset sampling utils
+
+
def sample_data(image_paths, labels, tabular_data, sampling_fraction):
+ """Sample a subset of data for test purposes."""
+ tf.print("Sampling data...")
+ sampled_indices = np.random.choice(len(image_paths), int(len(image_paths) * sampling_fraction), replace=False)
+ image_paths = [image_paths[i] for i in sampled_indices]
+ labels = [labels[i] for i in sampled_indices]
+ if tabular_data is not None:
+ tabular_data = tabular_data[sampled_indices]
+ return image_paths, labels, tabular_data
+
+
+
+ Serialization utils
+
+
# Data writing to tf_records for later reuse if working in cloud
+def write_dataset_to_tfrecords(image_paths, labels, tabular_data, output_prefix, num_workers, data_type='train'):
+ tf.print(f"\nStart writting {data_type} tfrecords...")
+ chunk_size = len(image_paths) // num_workers
+ tf.print(f"Number of workers: {num_workers}")
+ tf.print(f"Dataset size: {len(image_paths)}, chunk size: {chunk_size}")
+
+ args = [
+ (
+ image_paths[i:i + chunk_size],
+ labels[i:i + chunk_size],
+ tabular_data[i:i + chunk_size] if tabular_data is not None else None,
+ f'{output_prefix}_{i}.tfrecord'
+ )
+ for i in range(0, len(image_paths), chunk_size)
+ ]
+
+ with ThreadPoolExecutor(max_workers=num_workers) as executor:
+ executor.map(write_tfrecord_single_batch, args)
+
+def write_tfrecord_single_batch(args):
+ """
+ Write a batch of data to a TFRecord file. This function processes images and optional tabular data,
+ serializes them into a TFRecord format, and saves the result to a file.
+
+ Args:
+ args: A tuple or list containing the following:
+ - image_paths: List of file paths to the images (batch).
+ - labels: List of corresponding labels for the images (batch).
+ - tabular_data: Optional tabular data associated with the images (batch) (could be None).
+ - output_file: Path to the output TFRecord file.
+ """
+ try:
+ image_paths, labels, tabular_data, output_file = args
+ # Print which file is being written
+ tf.print(f"Writing TFRecords to: {output_file}")
+
+ # Specify compression options for the TFRecord (GZIP in this case)
+ options = tf.io.TFRecordOptions(compression_type="GZIP")
+
+ # Open a TFRecordWriter for writing data to the specified output file
+ with tf.io.TFRecordWriter(output_file, options=options) as writer:
+ # Loop through each image and label in the batch
+ for i, (img_path, label) in enumerate(zip(image_paths, labels)):
+ img_bytes = process_image(img_path)
+
+ # Convert the raw image into a feature that can be stored in the TFRecord
+ img_feature = tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_bytes]))
+
+ # Convert the label into a feature (using int64 since it's a categorical label)
+ label_feature = tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
+
+ # Create a dictionary to store the image and label as features
+ features = {'image': img_feature, 'label': label_feature}
+
+ # If tabular data is used and provided, add it to the features dictionary
+ if tabular_data is not None:
+ # Convert the tabular data to a float list feature for this specific sample
+ tab_feature = tf.train.Feature(float_list=tf.train.FloatList(value=tabular_data[i]))
+ features['tabular_data'] = tab_feature
+
+ # Create a TFRecord example from the features dictionary
+ example = tf.train.Example(features=tf.train.Features(feature=features))
+
+ # Serialize the example to a string and write it to the TFRecord file
+ writer.write(example.SerializeToString())
+ except Exception as e:
+ print(f"Error in write_tfrecord: {e}")
+ traceback.print_exc()
+
+def process_image(img_path):
+ """Helper function to read and process a single image."""
+ try:
+ img_bytes = tf.io.read_file(img_path)
+ return img_bytes.numpy() # Return the image bytes
+ except Exception as e:
+ raise Exception(f"Error reading image {img_path}: {e}")
+
+
+
+ Data download utils
+
+
def download_folder_from_dagshub(boto_client, bucket_name, remote_folder, local_folder):
+ """
+ Downloads all files from a specified remote folder in DagsHub to a local folder.
+
+ Args:
+ boto_client: The boto client used to interact with DagsHub (S3-compatible).
+ bucket_name: Name of the DagsHub repository (S3 bucket).
+ remote_folder: The path to the folder in DagsHub (e.g., "kaggle/isic-2024/datasets/tf_records/sample_1k/validation/").
+ local_folder: The path to the local folder where files will be downloaded.
+ """
+
+ # Ensure local folder exists
+ os.makedirs(local_folder, exist_ok=True)
+
+ # List all files in the remote folder
+ response = boto_client.list_objects_v2(Bucket=bucket_name, Prefix=remote_folder)
+
+ # Check if there are contents in the folder
+ if 'Contents' in response:
+ for obj in response['Contents']:
+ remote_file_path = obj['Key']
+ local_file_path = os.path.join(local_folder, os.path.basename(remote_file_path))
+
+ # Download each file
+ print(f"Downloading {remote_file_path} to {local_file_path}...")
+ boto_client.download_file(
+ Bucket=bucket_name,
+ Key=remote_file_path,
+ Filename=local_file_path
+ )
+ print("Download completed!")
+ else:
+ print(f"No files found in the folder: {remote_folder}")
+
+def resolve_remote_folder(local_folder):
+ return local_folder.replace(f'{ROOT_DB_DIR}/', '')
+
+
+
+ Preparation pipeline
+
+
from sklearn.compose import ColumnTransformer
+from sklearn.preprocessing import OneHotEncoder, MinMaxScaler, StandardScaler, OrdinalEncoder
+import pickle
+from scipy.sparse import vstack, csr_matrix
+
+
+from sklearn.compose import ColumnTransformer
+from sklearn.preprocessing import OneHotEncoder, StandardScaler
+
+common_columns = ['isic_id', 'age_approx', 'sex', 'anatom_site_general']
+selected_2019_2020_columns = ['image_name', 'age_approx', 'sex', 'anatom_site_general_challenge']
+
+# Preparing data directories using dense tabular data format
+def prepare_data_directories(train_dir, tabular_data_paths, sampling_fraction=1.0, use_oversampling=False, data_type='train'):
+ """Load image paths, labels, and optionally tabular data"""
+
+ tf.print(f"\nStart {data_type} data preparation...")
+
+ train_image_paths = glob(os.path.join(train_dir, '**', '*.jpg'), recursive=True)
+ train_labels = [1 if os.path.dirname(path).endswith('positives') else 0 for path in train_image_paths]
+ train_images = [os.path.basename(path).split('.')[0] for path in train_image_paths]
+
+ data_type = data_type.capitalize()
+ tf.print(f"{data_type} labels length before preparing data", len(train_labels))
+
+ if use_tabular_data:
+ # Load the preprocessor for validation or fit it for training data
+ preprocessor = None if data_type == 'Train' else load_preprocessor(preprocessor_path)
+
+ train_tabular = format_and_encode_tabular_data(tabular_data_paths, train_images, preprocessor=preprocessor)
+ else:
+ train_tabular = None
+
+ # oversample only train data
+ if use_oversampling:
+ train_image_paths, train_labels, train_tabular = smote_oversampling(
+ train_image_paths, train_labels, train_tabular
+ )
+ tf.print(f"{data_type} data size after oversampling:", len(train_labels))
+
+ # sample all the datasets if sampling is needed
+ if sampling_fraction < 1.0:
+ train_image_paths, train_labels, train_tabular = sample_data(
+ train_image_paths, train_labels, train_tabular, sampling_fraction
+ )
+ tf.print(f"{data_type} data size after subsampling:", len(train_labels))
+
+ if train_tabular is not None:
+ tabular_data_length = train_tabular.shape[0]
+
+ print(f"Image data size: {len(train_image_paths)}")
+ print(f"Tabular data size: {tabular_data_length}")
+
+ # Ensure that the number of tabular rows matches the number of image paths
+ assert len(train_image_paths) == tabular_data_length, "Image paths and tabular data do not match"
+ return (train_image_paths, train_labels, train_tabular)
+
+
+
+
+ Run
+
+ Download raw datasets
+
+
if download_raw_datasets:
+ boto_client = get_repo_bucket_client("AmadouMamane/dagshub-drive", flavor="boto")
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(eval_dir_pos), eval_dir_pos)
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(eval_dir_negs), eval_dir_negs)
+
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(val_dir_pos), val_dir_pos)
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(val_dir_negs), val_dir_negs)
+
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(train_dir_pos), train_dir_pos)
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(train_dir_negs), train_dir_negs)
+
+
+
+ Download refined datasets
+
+
if download_refined_training_datasets:
+ boto_client = get_repo_bucket_client("AmadouMamane/dagshub-drive", flavor="boto")
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(tf_records_eval_dir), tf_records_eval_dir)
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(tf_records_val_dir), tf_records_val_dir)
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(tf_records_train_val_dir), tf_records_train_val_dir)
+
+
+
+ Data preparation
+
+
# Prepare and process data
+num_workers = 10
+#total_samples = #49403 + 12100 + 4389 =65892
+
+if create_new_training_datasets:
+ sampling_fraction = dataset_size / 65892
+ metadata_2024_path = os.path.join(f'{training_metadata_dir}', 'train_metadata_2024.csv')
+ metadata_2020_path = os.path.join(f'{training_metadata_dir}', 'train_metadata_2020.csv')
+ metadata_2019_path = os.path.join(f'{training_metadata_dir}', 'train_metadata_2019.csv')
+ tabular_data_paths = [metadata_2024_path, metadata_2020_path, metadata_2019_path]
+
+ (train_image_paths, train_labels, train_tabular) = prepare_data_directories(train_dir, tabular_data_paths, sampling_fraction=sampling_fraction, use_oversampling=oversample_persisted_data, data_type='train')
+ (eval_image_paths, eval_labels, eval_tabular) = prepare_data_directories(eval_dir, tabular_data_paths, sampling_fraction=sampling_fraction, use_oversampling=oversample_persisted_data, data_type='eval')
+ (val_image_paths, val_labels, val_tabular) = prepare_data_directories(val_dir, tabular_data_paths, sampling_fraction=sampling_fraction, use_oversampling=oversample_persisted_data, data_type='Val')
+
+
+
+ Data serialization
+
+
if create_new_training_datasets:
+ write_dataset_to_tfrecords(eval_image_paths, eval_labels, eval_tabular, f'{tf_records_eval_dir}/eval', num_workers=num_workers, data_type='train')
+ write_dataset_to_tfrecords(val_image_paths, val_labels, val_tabular, f'{tf_records_val_dir}/val', num_workers=num_workers, data_type='eval')
+ write_dataset_to_tfrecords(train_image_paths, train_labels, train_tabular, f'{tf_records_train_val_dir}/train', num_workers=num_workers, data_type='Val')
+ print('Number of train and val positives class samples', train_labels.count(1))
+ print('Number of val positives class samples', val_labels.count(1))
+ print('Number of eval positives class samples', eval_labels.count(1))
+
+
+
+
+ Data augmentation
+
+ Random hue saturation
+
+
sat_max_delta_hs=0.0005
+hue_max_delta_hs=0.0005
+val_max_delta_hs=0.0005
+@tf.function
+def random_hue_saturation(image, hue_max_delta=hue_max_delta_hs, sat_max_delta=sat_max_delta_hs, val_max_delta=val_max_delta_hs, probability=0.5, seed=None):
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ def apply_hue_sat_val(img):
+ img = tf.image.random_hue(img, max_delta=hue_max_delta)
+ img = tf.image.random_saturation(img, lower=1 - sat_max_delta, upper=1 + sat_max_delta)
+ img = tf.image.random_brightness(img, max_delta=val_max_delta)
+ img = tf.clip_by_value(img, 0.0, 1.0)
+ return img
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_hue_sat_val(image),
+ lambda: image
+ )
+
+
+
+ Random shift scale rotate
+
+
@tf.function
+def random_shift_scale_rotate(image, shift_limit=0.05, scale_limit=0.05, rotate_limit=5, threshold=0.5, image_size=image_size, seed=None):
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ def apply_shift_scale_rotate(img, seed):
+
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=4)
+ angle_seed = seeds[0]
+ scale_seed = seeds[1]
+ dx_seed = seeds[3]
+ dy_seed = seeds[4]
+
+ # Random rotation
+ angle = tf.random.stateless_uniform([], -rotate_limit, rotate_limit, seed=angle_seed) * 3.14159265 / 180
+ img = tf.image.rot90(img, k=tf.cast(angle / (3.14159265 / 2), tf.int32))
+
+ # Random scaling
+ scale = tf.random.stateless_uniform([], 1 - scale_limit, 1 + scale_limit, seed=scale_seed)
+ new_size = tf.cast(tf.cast(tf.shape(img)[0:2], tf.float32) * scale, tf.int32)
+ img = tf.image.resize(img, new_size)
+
+ # Random shifting
+ max_dx = tf.cast(shift_limit * tf.cast(tf.shape(img)[1], tf.float32), tf.int32)
+ max_dy = tf.cast(shift_limit * tf.cast(tf.shape(img)[0], tf.float32), tf.int32)
+
+
+ dx = tf.random.stateless_uniform([], -max_dx, max_dx, dtype=tf.int32, seed=dx_seed)
+ dy = tf.random.stateless_uniform([], -max_dy, max_dy, dtype=tf.int32, seed=dy_seed)
+
+ target_height = tf.minimum(image_size, new_size[0] - tf.abs(dy))
+ target_width = tf.minimum(image_size, new_size[1] - tf.abs(dx))
+
+ img = tf.image.crop_to_bounding_box(
+ img,
+ offset_height=tf.maximum(0, dy),
+ offset_width=tf.maximum(0, dx),
+ target_height=target_height,
+ target_width=target_width
+ )
+
+ # Resize back to original size
+ img = tf.image.resize_with_crop_or_pad(img, target_height=image_size, target_width=image_size)
+ return img
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),
+ lambda: apply_shift_scale_rotate(image, seed),
+ lambda: image
+ )
+
+
+
+ Random crop
+
+
@tf.function
+def random_crop(image, min_crop_size_ratio=0.7, max_crop_size_ratio=1.0, probability=0.5, seed=None):
+ def apply_random_crop_per_image(img):
+ img_shape = tf.shape(img)
+ height = img_shape[0]
+ width = img_shape[1]
+
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=4)
+ crop_seed = seeds[0]
+ size_seed = seeds[1]
+ y_seed = seeds[2]
+ x_seed = seeds[3]
+
+ # Randomly determine the crop size ratio within the given range using the seed
+ crop_size_ratio = tf.random.stateless_uniform([], minval=min_crop_size_ratio, maxval=max_crop_size_ratio, seed=size_seed)
+
+ # Calculate the crop size based on the randomly chosen ratio
+ crop_height = tf.cast(crop_size_ratio * tf.cast(height, tf.float32), tf.int32)
+ crop_width = tf.cast(crop_size_ratio * tf.cast(width, tf.float32), tf.int32)
+
+ y_maxval = tf.maximum(height - crop_height, 1) # Ensure maxval is > min_val
+ x_maxval = tf.maximum(width - crop_width, 1)
+
+ # Randomly select the top-left corner of the crop using the seed
+ y1 = tf.random.stateless_uniform([], minval=0, maxval=x_maxval, dtype=tf.int32, seed=y_seed)
+ x1 = tf.random.stateless_uniform([], minval=0, maxval=y_maxval, dtype=tf.int32, seed=x_seed)
+
+ # Define the bottom-right corner of the crop
+ y2 = y1 + crop_height
+ x2 = x1 + crop_width
+
+ # Crop the image
+ cropped_img = img[y1:y2, x1:x2, :]
+
+ # Set a static shape for cropped image if necessary before resizing (adjust as needed)
+ cropped_img.set_shape([None, None, img.shape[-1]])
+
+ # Resize the cropped image back to the original size
+ cropped_img = tf.image.resize(cropped_img, [height, width], method='bilinear')
+
+ return cropped_img
+
+ # Use stateless random to generate a seed if none is provided
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ # Apply random crop with the specified probability
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_random_crop_per_image(image),
+ lambda: image
+ )
+
+
+
+ Random cutout
+
+
@tf.function
+def random_cutout(image, max_cutout_size_ratio=0.3, probability=0.5, seed=None):
+ def apply_cutout(img):
+ img_shape = tf.shape(img)
+ height = img_shape[-3]
+ width = img_shape[-2]
+
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=3)
+ cutout_seed = seeds[0]
+ x_center_seed = seeds[1]
+ y_center_seed = seeds[2]
+
+ # Generate the cutout size using a stateless random function with a seed
+ cutout_size = tf.random.stateless_uniform([], minval=0, maxval=max_cutout_size_ratio, dtype=tf.float32, seed=cutout_seed) * tf.cast(tf.minimum(height, width), tf.float32)
+ cutout_size = tf.cast(cutout_size, tf.int32)
+
+ # Randomly select the center position of the cutout using the seed
+ x_center = tf.random.stateless_uniform([], minval=0, maxval=width, dtype=tf.int32, seed=x_center_seed)
+ y_center = tf.random.stateless_uniform([], minval=0, maxval=height, dtype=tf.int32, seed=y_center_seed)
+
+ # Define the top-left and bottom-right corners of the cutout
+ x1 = tf.clip_by_value(x_center - cutout_size // 2, 0, width)
+ y1 = tf.clip_by_value(y_center - cutout_size // 2, 0, height)
+ x2 = tf.clip_by_value(x_center + cutout_size // 2, 0, width)
+ y2 = tf.clip_by_value(y_center + cutout_size // 2, 0, height)
+
+ # Create the cutout mask
+ mask = tf.ones_like(img)
+ x_range = tf.range(x1, x2)
+ y_range = tf.range(y1, y2)
+
+ # Generate all the coordinates to update in the mask
+ y_grid, x_grid = tf.meshgrid(y_range, x_range)
+ coords = tf.stack([y_grid, x_grid], axis=-1)
+ coords = tf.reshape(coords, [-1, 2])
+
+ # Update the mask with zeros at the selected coordinates
+ mask = tf.tensor_scatter_nd_update(mask, coords, tf.zeros([tf.shape(coords)[0], img_shape[-1]], dtype=img.dtype))
+
+ # Apply the mask to the image
+ img = img * mask
+
+ return img
+
+ # Use stateless random to generate a seed if none is provided
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+ # Apply cutout with the specified probability
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_cutout(image),
+ lambda: image
+ )
+
+
+
+ Distorsions
+
+
+ Random grid distortion
+
+
@tf.function
+def random_grid_distortion(image, num_steps=10, distort_limit=0.05, probability=0.5, seed=None):
+ # Use stateless random to generate a seed if none is provided
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ def apply_grid_distortion(img):
+ img_shape = tf.shape(img)
+ height = tf.cast(img_shape[0], tf.float32)
+ width = tf.cast(img_shape[1], tf.float32)
+
+ # Define grid step size
+ x_step = tf.cast(width // num_steps, tf.float32)
+ y_step = tf.cast(height // num_steps, tf.float32)
+
+
+ # Verification for tf.random.stateless_uniform so that min_val and mx_val != 0
+ x_step = tf.cond(x_step > 0, lambda: x_step, lambda: tf.constant(1e-7))
+ y_step = tf.cond(y_step > 0, lambda: y_step, lambda: tf.constant(1e-7))
+
+
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=2)
+ x_offsets_seed = seeds[0]
+ y_offsets_seed = seeds[1]
+
+ # Generate random offsets
+ x_offsets = tf.random.stateless_uniform(
+ shape=[num_steps + 1, num_steps + 1],
+ minval=-distort_limit * x_step,
+ maxval=distort_limit * x_step,
+ dtype=tf.float32,
+ seed=x_offsets_seed
+ )
+ y_offsets = tf.random.stateless_uniform(
+ shape=[num_steps + 1, num_steps + 1],
+ minval=-distort_limit * y_step,
+ maxval=distort_limit * y_step,
+ dtype=tf.float32,
+ seed=y_offsets_seed
+ )
+
+ # Create grid of coordinates
+ x = tf.linspace(0.0, width, num_steps + 1)
+ y = tf.linspace(0.0, height, num_steps + 1)
+ x_t, y_t = tf.meshgrid(x, y)
+
+ # Apply offsets
+ x_t = x_t + x_offsets
+ y_t = y_t + y_offsets
+
+ # Interpolate to get dense flow field
+ x_interp = tf.image.resize(x_t[..., tf.newaxis], [height, width], method='bilinear')
+ y_interp = tf.image.resize(y_t[..., tf.newaxis], [height, width], method='bilinear')
+
+ # Stack and subtract identity grid
+ flow = tf.stack([y_interp[..., 0] - tf.range(height)[:, None],
+ x_interp[..., 0] - tf.range(width)[None, :]], axis=-1)
+
+ # Apply flow to image
+ distorted_image = tfa.image.dense_image_warp(img[tf.newaxis, ...], flow[tf.newaxis, ...])[0]
+
+ return distorted_image
+
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_grid_distortion(image),
+ lambda: image
+ )
+
+
+
+ Random optical distortion
+
+
# Define distortion parameters
+distort_limit_do = 0.0005
+shift_limit_do = 0.0005
+
+# Define augmentation layers outside the tf.function
+random_rotation = preprocessing.RandomRotation(factor=distort_limit_do)
+random_translation = preprocessing.RandomTranslation(height_factor=shift_limit_do,
+ width_factor=shift_limit_do)
+random_zoom = preprocessing.RandomZoom(height_factor=(-distort_limit_do, distort_limit_do),
+ width_factor=(-distort_limit_do, distort_limit_do))
+
+@tf.function
+def random_optical_distortion(image, probability=0.5, seed=None):
+ def apply_optical_distortion(img):
+ # Apply the augmentations
+ img = random_rotation(img, training=True)
+ img = random_translation(img, training=True)
+ img = random_zoom(img, training=True)
+ return img
+
+ # If seed is provided, split it for reproducible randomness
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_optical_distortion(image),
+ lambda: image
+ )
+
+
+
+ Random one of distorsion
+
+
distort_limit_do =0.0005 # defined oon function definition also
+shift_limit_do = 0.0005
+
+num_steps_dg=10
+distort_limit_dg=0.05
+
+alpha_range_de = (0.5,1.5)
+sigma_range_de =(0.5,1.0)
+padding_size_range_de = (5,10)
+
+@tf.function
+def random_one_of_distortion(image, probability=0.7, seed=None):
+ # Generate seed if not provided
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ def apply_random_transform(img, seed):
+
+ # Generate a random choice for distortion transformation
+ choice = tf.random.stateless_uniform([], minval=0, maxval=2, dtype=tf.int32, seed=seed)
+ # Apply random distortions based on the choice
+ #img = tf.cond(choice == 0, lambda: random_optical_distortion(img, probability=1.0, seed=None), lambda: img)
+ img = tf.cond(choice == 0, lambda: random_grid_distortion(img, num_steps=num_steps_dg, distort_limit=distort_limit_dg, probability=1.0, seed=None), lambda: img)
+ img = tf.cond(choice == 1, lambda: random_elastic_transform(img, alpha_range=alpha_range_de, sigma_range=sigma_range_de, padding_size_range=padding_size_range_de, probability=1.0, seed=None), lambda: img)
+ return img
+
+ # Apply transformation based on the probability
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_random_transform(image, seed),
+ lambda: image
+ )
+
+
+
+
+ Blurings
+
+
var_limit_gn=(0.0, 0.0001)
+sigma_range_gb=(0.0, 0.7)
+kernel_size_range_gb=(3, 4)
+angle_range_gm=(0, 30)
+kernel_size_range_gm=(1, 4)
+
+
+ Random gaussian blur
+
+
@tf.function
+def random_gaussian_blur(image, kernel_size_range=(3, 7), sigma_range=(0.1, 2.0), probability=0.5, seed=None):
+ def gaussian_kernel(size: int, mean: float, std: float):
+ """Creates a 2D Gaussian Kernel for convolution."""
+ coords = tf.range(-(size // 2), size // 2 + 1, dtype=tf.float32)
+ g = tf.exp(-tf.pow(coords - mean, 2.0) / (2.0 * tf.pow(std, 2.0)))
+ g /= tf.reduce_sum(g)
+ gauss_kernel = tf.tensordot(g, g, axes=0)
+ return gauss_kernel / tf.reduce_sum(gauss_kernel)
+
+ def apply_blur(img):
+ # Generate a seed if none is provided for consistent randomness
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=2)
+ kernel_size_seed = seeds[0]
+ sigma_seed = seeds[1]
+
+
+ # Randomize the kernel size and sigma within the given ranges
+ random_kernel_size = tf.random.stateless_uniform([], minval=kernel_size_range[0], maxval=kernel_size_range[1], dtype=tf.int32, seed=kernel_size_seed)
+ random_kernel_size = tf.where(random_kernel_size % 2 == 0, random_kernel_size + 1, random_kernel_size) # Ensure kernel size is odd
+ random_sigma = tf.random.stateless_uniform([], minval=sigma_range[0], maxval=sigma_range[1], seed=sigma_seed)
+
+ # Create the Gaussian kernel
+ kernel = gaussian_kernel(random_kernel_size, 0., random_sigma)
+ kernel = kernel[:, :, tf.newaxis, tf.newaxis]
+ kernel = tf.tile(kernel, [1, 1, 3, 1]) # Match kernel to the RGB channels
+
+ # Ensure image is 4D (batch size, height, width, channels)
+ img = tf.cond(tf.equal(tf.rank(img), 3), # Check if the image is rank 3 (without batch dimension)
+ lambda: tf.expand_dims(img, axis=0), # Add batch dimension
+ lambda: img) # If already 4D, pass as-is
+
+ # Apply depthwise convolution (motion blur)
+ img = tf.nn.depthwise_conv2d(img, kernel, [1, 1, 1, 1], padding='SAME')[0]
+
+ return img
+
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_blur(image),
+ lambda: image
+ )
+
+
+
+ Random motion blur
+
+
@tf.function
+def random_motion_blur(image, kernel_size_range=(3, 7), angle_range=(0, 360), probability=0.5, seed=None):
+ def motion_blur_kernel(img, kernel_size, angle):
+ center = tf.cast(kernel_size // 2, tf.float32)
+
+ # Calculate the angle in radians
+ angle_rad = angle * (3.14159265359 / 180.0)
+ cos_a = tf.cos(angle_rad)
+ sin_a = tf.sin(angle_rad)
+
+ # Create meshgrid for indices
+ x = tf.cast(tf.range(kernel_size), tf.float32) - center
+ y = tf.cast(tf.range(kernel_size), tf.float32) - center
+ xx, yy = tf.meshgrid(x, y)
+
+ # Compute the distance to simulate motion blur in the specified direction
+ distance = xx * cos_a + yy * sin_a
+ kernel = tf.cast(tf.abs(distance) < 0.5, tf.float32)
+
+ # Normalize the kernel
+ kernel = kernel / tf.reduce_sum(kernel)
+
+ # Expand dimensions to 4D for depthwise convolution
+ kernel = kernel[:, :, tf.newaxis, tf.newaxis]
+ kernel = tf.tile(kernel, [1, 1, tf.shape(img)[-1], 1]) # Match the number of channels
+
+ return kernel
+
+ def apply_motion_blur(img, seed):
+ # Generate a seed if none is provided for consistent randomness
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=2)
+ kernel_size_seed = seeds[0]
+ angle_seed = seeds[1]
+ # Randomly choose a kernel size and angle within the specified range
+ kernel_size = tf.random.stateless_uniform([], minval=kernel_size_range[0], maxval=kernel_size_range[1], dtype=tf.int32, seed=kernel_size_seed)
+ angle = tf.random.stateless_uniform([], minval=angle_range[0], maxval=angle_range[1], dtype=tf.float32, seed=angle_seed)
+
+ # Create the motion blur kernel
+ kernel = motion_blur_kernel(img, kernel_size, angle)
+
+ # Ensure image is 4D (batch size, height, width, channels)
+ img = tf.cond(tf.equal(tf.rank(img), 3), # Check if the image is rank 3 (without batch dimension)
+ lambda: tf.expand_dims(img, axis=0), # Add batch dimension
+ lambda: img) # If already 4D, pass as-is
+
+ # Apply depthwise convolution (motion blur)
+ img = tf.nn.depthwise_conv2d(img, kernel, [1, 1, 1, 1], padding='SAME')[0]
+
+ return img
+
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ # Apply motion blur with a certain probability
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_motion_blur(image, seed=None),
+ lambda: image)
+
+
+
+
+ Radom gaussian noise
+
+
@tf.function
+def random_gaussian_noise(image, var_limit=(0.01, 0.1), probability=0.5, seed=None):
+ def apply_noise(img, seed):
+ # Create a seed if none is provided to ensure consistent randomness
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=2)
+ stddev_seed = seeds[0]
+ noise_seed = seeds[1]
+ # Generate random standard deviation for the noise within the given limits
+ stddev = tf.random.stateless_uniform([], minval=var_limit[0], maxval=var_limit[1], seed=stddev_seed)
+
+ # Generate random Gaussian noise
+ noise = tf.random.stateless_normal(shape=tf.shape(img), mean=0.0, stddev=stddev, dtype=tf.float32, seed=noise_seed)
+
+ # Add noise to the image and clip values to ensure valid pixel values [0, 1]
+ return tf.clip_by_value(img + noise, 0.0, 1.0)
+
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+ # Apply noise with a certain probability
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_noise(image, seed=None),
+ lambda: image
+ )
+
+
+
+ Random one of blur
+
+
@tf.function
+def random_one_of_blur(image, probability=0.7, seed=None):
+ def apply_random_transform(img, seed):
+ # If no seed is provided, generate one
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ # Generate a random choice using stateless randomness for reproducibility
+ choice = tf.random.stateless_uniform([], minval=0, maxval=3, dtype=tf.int32, seed=seed)
+
+ # Apply different blur/noise transformations based on random choice
+ img = tf.cond(choice == 0,
+ lambda: random_motion_blur(img, kernel_size_range=kernel_size_range_gm, angle_range=angle_range_gm, probability=1.0, seed=None),
+ lambda: img)
+
+ img = tf.cond(choice == 1,
+ lambda: random_gaussian_blur(img, kernel_size_range=kernel_size_range_gb, sigma_range=sigma_range_gb, probability=1.0, seed=None),
+ lambda: img)
+
+ img = tf.cond(choice == 2,
+ lambda: random_gaussian_noise(img, var_limit=var_limit_gn, probability=1.0, seed=None),
+ lambda: img)
+ return img
+
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+ # Use the main condition to determine whether to apply a transformation or not
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_random_transform(image, seed=None),
+ lambda: image
+ )
+
+
+
@tf.function
+def random_transpose(image, threshold=0.5, seed=None):
+ # Ensure stateless randomness for reproducibility
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),
+ lambda: tf.image.transpose(image),
+ lambda: image
+ )
+
+
+@tf.function
+def random_vertical_flip(image, threshold=0.5, seed=None):
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),
+ lambda: tf.image.flip_up_down(image),
+ lambda: image
+ )
+
+@tf.function
+def random_horizontal_flip(image, threshold=0.5, seed=None):
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),
+ lambda: tf.image.flip_left_right(image),
+ lambda: image
+ )
+
+@tf.function
+def random_brightness(image, max_delta=0.05, threshold=0.5, seed=None):
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),
+ lambda: tf.image.random_brightness(image, max_delta=max_delta),
+ lambda: image
+ )
+
+@tf.function
+def random_contrast(image, lower=0.9, upper=1.1, threshold=0.5, seed=None):
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),
+ lambda: tf.image.random_contrast(image, lower=lower, upper=upper),
+ lambda: image
+ )
+
+
+
+
+ Apply augmentations
+
+
threshold_low=0.4
+threshold_medium=0.5
+threshold_high=0.7
+threshold_very_high=0.6
+
+
+
threshold_low=0.4
+threshold_medium=0.65
+threshold_high=0.7
+threshold_very_high=0.65
+
+@tf.function
+def augment_minority(image, threshold=threshold_very_high, seed=None):
+ # Apply a series of augmentations with appropriate thresholds
+ image = random_transpose(image, threshold_high, seed=seed)
+ image = random_vertical_flip(image, threshold=threshold_high, seed=seed)
+ image = random_horizontal_flip(image, threshold=threshold_high, seed=seed)
+ image = random_brightness(image, threshold=threshold, seed=seed)
+ image = random_contrast(image, threshold=threshold, seed=seed)
+
+ image = random_one_of_blur(image, probability=threshold_medium, seed=seed)
+ image = random_one_of_distortion(image, probability=threshold_medium, seed=seed)
+ image = random_hue_saturation(image, probability=threshold_medium, seed=seed)
+ image = random_crop(image, probability=threshold_medium, seed=seed)
+ image = random_cutout(image, probability=threshold_medium, seed=seed)
+ image = tf.clip_by_value(image, 0.0, 1.0)
+ return image
+
+@tf.function
+def augment_majority(image, threshold=threshold_low, seed=None):
+ # Apply augmentations with lower thresholds for majority class
+ image = random_transpose(image, threshold=threshold, seed=seed)
+ image = random_vertical_flip(image, threshold=threshold, seed=seed)
+ image = random_horizontal_flip(image, threshold=threshold, seed=seed)
+ image = random_brightness(image, threshold=threshold, seed=seed)
+ image = random_contrast(image, threshold=threshold, seed=seed)
+
+ image = random_one_of_blur(image, probability=threshold, seed=seed)
+ image = random_one_of_distortion(image, probability=threshold, seed=seed)
+ image = random_hue_saturation(image, probability=threshold, seed=seed)
+ image = random_crop(image, probability=threshold, seed=seed)
+ image = random_cutout(image, probability=threshold, seed=seed)
+ image = tf.clip_by_value(image, 0.0, 1.0)
+ return image
+
+@tf.function
+def augment_image_batch_old(images, labels):
+ """
+ This function applies augmentations to batches of images based on the label using vectorized operations.
+ It applies augment_minority if label == 1, otherwise augment_majority.
+ """
+ # Define how to augment each image based on the label
+ def augment_single(image, label):
+ if label == 1:
+ return augment_minority(image)
+ else:
+ return augment_majority(image)
+
+ # Apply augmentations to the batch using tf.map_fn
+ augmented_images = tf.map_fn(lambda x: augment_single(x[0], x[1]), (images, labels), fn_output_signature=tf.float32)
+ # Assuming augment_single takes image and label as input and returns augmented image
+ return augmented_images
+
+@tf.function
+def augment_image_batch(images, labels):
+ """
+ Apply augmentations based on the label (minority or majority) using tf.where.
+ Labels of 1 apply minority augmentations, labels of 0 apply majority augmentations.
+ """
+ # Create a mask where label == 1 (for minority)
+ mask = tf.equal(labels, 1)
+
+ # Apply augmentations conditionally based on the mask
+ augmented_images = tf.where(mask[:, None, None, None],
+ #augment_minority(images),
+ #augment_majority(images)
+ tf.map_fn(lambda x: augment_minority(x), images, fn_output_signature=tf.float32),
+ tf.map_fn(lambda x: augment_majority(x), images, fn_output_signature=tf.float32)
+ )
+ return augmented_images
+
+@tf.function
+def apply_augmentations(dataset, use_tabular_data=use_tabular_data):
+ tf.print("Started data augmentation...")
+ if use_tabular_data:
+ # Map using (image, tabular_data, label)
+ return dataset.map(lambda img, tab, lbl: (augment_image_batch(img, lbl), tab, lbl),
+ num_parallel_calls=tf.data.AUTOTUNE)
+ else:
+ # Map using only (image, label)
+ return dataset.map(lambda img, lbl: (augment_image_batch(img, lbl), lbl),
+ num_parallel_calls=tf.data.AUTOTUNE)
+
+
+
+
+ Data processing
+
+ Caching utils
+
+
def get_file_modification_time(file_path):
+ """Get the last modification time of a file."""
+ return os.path.getmtime(file_path)
+
+def get_file_checksum(file_path):
+ """Generate a checksum for a file's contents."""
+ md5_hash = hashlib.md5()
+ with tf.io.gfile.GFile(file_path, 'rb') as f:
+ while chunk := f.read(8192):
+ md5_hash.update(chunk)
+ return md5_hash.hexdigest()
+
+def get_dataset_checksum(file_pattern, check_modification_time=False):
+ """Generate a checksum for the dataset based on file contents or modification times."""
+ files = tf.io.gfile.glob(file_pattern)
+ if not files:
+ raise FileNotFoundError(f"No files match the pattern: {file_pattern}")
+
+ combined_checksum = hashlib.md5()
+
+ for file in files:
+ if check_modification_time:
+ # Include file modification times
+ file_mod_time = str(get_file_modification_time(file))
+ combined_checksum.update(file_mod_time.encode('utf-8'))
+ else:
+ # Include file contents checksum
+ file_checksum = get_file_checksum(file)
+ combined_checksum.update(file_checksum.encode('utf-8'))
+
+ return combined_checksum.hexdigest()
+
+def generate_cache_name(file_pattern, checksum, prefix="dataset_cache", run_id=None):
+ """Generate a unique cache name based on the file pattern and dataset checksum."""
+ if file_pattern:
+ file_pattern_hash = hashlib.md5(file_pattern.encode('utf-8')).hexdigest()
+ else:
+ file_pattern_hash = "not_provided"
+
+ cache_name = f"{prefix}_{file_pattern_hash}_checksum_{checksum}"
+
+ if run_id:
+ cache_name += f"_run_{run_id}"
+
+ return cache_name
+
+def remove_lockfile_if_exists(cache_path):
+ """Remove a lockfile if it exists."""
+ lockfile_path = f"{cache_path}.lockfile"
+ if os.path.exists(lockfile_path):
+ print(f"Removing stale lockfile: {lockfile_path}")
+ os.remove(lockfile_path)
+
+def check_cache_exists(cache_dir, cache_name):
+ """Check if the cache exists by looking for any cache files with the given prefix."""
+ cache_pattern = os.path.join(cache_dir, cache_name + "*")
+ return len(glob(cache_pattern)) > 0
+
+# Force full caching of the dataset by fully consuming it
+def fully_cache_dataset(dataset, train_val_dataset_steps):
+ """Fully cache the dataset by iterating over all elements."""
+ print("Triggering full dataset caching...")
+ for _ in dataset:
+ pass # Consume all elements to force caching
+ print("Caching complete.")
+ return dataset
+
+def persist_dataset(dataset, file_pattern, dataset_steps, cache_in_memory, cache_dir=cache_dir,
+ prefix="dataset_cache", run_id=None, check_modification_time=False, force_cache=True):
+ """
+ Efficiently caches a TensorFlow dataset to memory or disk.
+
+ Args:
+ dataset (tf.data.Dataset): The TensorFlow dataset to cache.
+ file_pattern (str): Pattern for the dataset files.
+ dataset_steps (int): Number of steps in the dataset.
+ cache_in_memory (bool): Whether to cache the dataset in memory.
+ cache_dir (str): Directory for storing the cached dataset (if not in memory).
+ prefix (str): Prefix for cache file names.
+ run_id (str): Optional identifier to distinguish cache versions.
+ check_modification_time (bool): Consider file modification times for caching.
+ force_cache (bool): If True, forces cache re-generation.
+
+ Returns:
+ tf.data.Dataset: The cached dataset.
+ """
+ # Generate the cache name and path
+ if file_pattern:
+ dataset_checksum = get_dataset_checksum(file_pattern, check_modification_time)
+ else:
+ dataset_checksum = 'not_provided'
+
+ cache_name = generate_cache_name(file_pattern, dataset_checksum, prefix, run_id)
+ cache_path = os.path.join(cache_dir, cache_name)
+
+ # If forcing cache or no cache exists, optionally clean the previous cache
+ if force_cache:
+ clean_cache_path(cache_path)
+ gc.collect()
+
+ # Use existing cache if available and not forced to recache
+ if not force_cache and check_cache_exists(cache_dir, cache_name):
+ print(f"Using cached dataset at {cache_path}")
+ #return dataset if cache_in_memory else dataset.cache(cache_path)
+ return dataset
+
+ # Create cache directory only if caching to disk
+ if not cache_in_memory:
+ os.makedirs(cache_dir, exist_ok=True)
+ print(f"Caching dataset to disk at {cache_path}")
+ else:
+ print("Caching dataset to memory")
+
+ # Cache in memory or disk and ensure dataset is fully cached
+ dataset = dataset.cache() if cache_in_memory else dataset.cache(cache_path)
+ fully_cache_dataset(dataset, dataset_steps)
+
+ return dataset
+
+def clean_cache_path(cache_path, cache_dir=cache_dir):
+ """
+ Removes all lockfiles from the cache directory to clean up stale caches.
+ Args:
+ cache_dir (str): The directory where the cache is stored.
+ """
+ try:
+ # List all files in the cache directory
+ for root, dirs, files in os.walk(cache_dir):
+ for file in files:
+ # Check if the file is a lockfile
+ if file.startswith(cache_path):
+ lockfile_path = os.path.join(root, file)
+ print(f"Removing lockfile: {lockfile_path}")
+ os.remove(lockfile_path)
+ print(f"Cache {cache_path} cleanup completed")
+ except Exception as e:
+ print(f"An error occurred while cleaning the cache path {cache_path}: {str(e)}")
+
+
+
+ Data processing utils
+
+
def calculate_samples_per_class(labels):
+ unique, counts = np.unique(labels, return_counts=True)
+ return dict(zip(unique, counts))
+
+def calculate_class_weights(samples_per_class):
+ total_samples = sum(samples_per_class.values())
+ return {cls: total_samples / (len(samples_per_class) * count) for cls, count in samples_per_class.items()}
+
+def calculate_steps_per_epoch(total_samples, batch_size=batch_size, strategy=strategy):
+ steps = total_samples // (batch_size * strategy.num_replicas_in_sync)
+ if steps == 0:
+ raise Exception(f"Calculated steps is 0 because the provided batch_size {batch_size} is too big !\n Total samples is {total_samples}")
+ return steps
+
+def stratified_split_indices(labels, n_splits=5, shuffle=True, random_state=42):
+ skf = StratifiedKFold(n_splits=n_splits, shuffle=shuffle, random_state=random_state)
+ return list(skf.split(np.zeros(len(labels)), labels))
+
+def count_samples_in_tfrecord(file_pattern):
+ """Count the total number of samples in the TFRecord files."""
+ print("Calculating tfrecords dataset size...")
+ dataset = tf.data.Dataset.list_files(file_pattern)
+ total_count = 0
+ for file in dataset:
+ count = sum(1 for _ in tf.data.TFRecordDataset(file, compression_type="GZIP"))
+ total_count += count
+ return total_count
+
+def split_dataset(dataset, indices):
+ tf.print('Splitting the dataset')
+ indices = tf.convert_to_tensor(indices, dtype=tf.int64)
+ indices_dataset = tf.data.Dataset.from_tensor_slices(indices)
+
+ # Use enumerate to pair each element with its index, then filter
+ filtered_dataset = dataset.enumerate().filter(
+ lambda idx, _: tf.reduce_any(tf.equal(idx, indices))
+ ).map(lambda idx, data: data) # Discard the index after filtering
+
+ #if use_tabular_data:
+ #return filtered_dataset.map(lambda img, tab, lbl: ((img, tab), lbl))
+ #else:
+ #return filtered_dataset.map(lambda img, lbl: (img, lbl))
+ return filtered_dataset
+
+def count_samples_in_training_dataset(dataset, batch_size=None, strategy=None):
+ """
+ Count the number of samples in a dataset, taking into account batched datasets.
+
+ Args:
+ dataset: A `tf.data.Dataset` object, possibly batched.
+ batch_size: If the dataset is batched, specify the batch size to correctly count samples.
+
+ Returns:
+ Integer count of the number of samples in the dataset.
+ """
+ print("Calculating the dataset size...")
+
+ # If the dataset is batched, multiply by batch size, or use the actual number of samples in each batch
+ if batch_size:
+ # Count the number of batches
+ batch_count = dataset.reduce(0, lambda x, _: x + 1).numpy()
+ # Multiply by batch size to get the total number of samples
+ total_samples = batch_count * batch_size * strategy.num_replicas_in_sync
+ print(f"Total samples: {total_samples} and number of batches: {batch_count}")
+ return total_samples
+ else:
+ # Unbatched case: Simply count the samples
+ total_samples = dataset.reduce(0, lambda x, _: x + 1).numpy()
+ print(f"Total samples: {total_samples}")
+ return total_samples
+
+def format_input_dataset(dataset, use_tabular_data=use_tabular_data):
+ if use_tabular_data:
+ return dataset.map(lambda img, tab, lbl: ((img, tab), lbl))
+ else:
+ return dataset.map(lambda img, lbl: (img, lbl))
+
+def preprocess_image(image, label):
+ image = tf.image.resize(image, [img_height, img_width])
+ return image, label
+
+def cast_labels(dataset, use_tabular_data=use_tabular_data):
+ if use_tabular_data:
+ return dataset.map(lambda img, tab, lbl: (img, tab, tf.cast(lbl, dtype=tf.float32)))
+ else:
+ return dataset.map(lambda img, lbl: (img, tf.cast(lbl, dtype=tf.float32)))
+
+# Helper to optimize dataset caching and prefetching
+def optimize_dataset(dataset, steps, batch_size=batch_size, file_pattern=None, shuffle=True, buffer_size=buffer_size,
+ drop_remainder=True, prefix='train', force_cache=False, cache_in_memory=False, run_id=run_id,
+ cache_dir=cache_dir, check_modification_time=False):
+ """Optimize dataset with caching, batching, shuffling, and prefetching."""
+ #dataset = persist_dataset(dataset, file_pattern=file_pattern, dataset_steps=steps, cache_in_memory=cache_in_memory,
+ #force_cache=force_cache, prefix=prefix, run_id=run_id,
+ #cache_dir=cache_dir, check_modification_time=check_modification_time)
+ if prefix == 'train':
+ print(f"Repeating, shuffling and batching {prefix} dataset")
+ else:
+ print(f"Repeating {prefix} dataset")
+ dataset = dataset.repeat()
+ dataset = dataset.shuffle(buffer_size=buffer_size) if shuffle else dataset
+ if prefix == 'train':
+ dataset = dataset.batch(batch_size * strategy.num_replicas_in_sync, drop_remainder=drop_remainder)
+ dataset = dataset.prefetch(tf.data.AUTOTUNE)
+ return dataset
+
+def clean_session():
+ # Reset the session and clear the graph before each fold
+ tf.keras.backend.clear_session()
+ tf.compat.v1.reset_default_graph()
+ gc.collect()
+
+def deletes_old_datasets():
+ try:
+ del train_dataset
+ del val_dataset
+ except:
+ pass
+ clean_session()
+
+
+
+ Oversampling utils
+
+
def oversample_minority_class_random(dataset, batch_size=batch_size, strategy=strategy, use_tabular_data=use_tabular_data):
+ """
+ Randomly oversample the minority class by duplicating its samples within the dataset.
+ """
+ tf.print("\nRandom oversampling the minority class...")
+ # Separate the dataset into majority and minority classes
+ minority_class = 1
+ majority_class = 0
+
+ if use_tabular_data:
+ # For datasets with both image and tabular data
+ minority_dataset = dataset.filter(lambda image, tab, label: tf.equal(label, minority_class))
+ majority_dataset = dataset.filter(lambda image, tab, label: tf.equal(label, majority_class))
+ else:
+ # For datasets with only image data
+ minority_dataset = dataset.filter(lambda image, label: tf.equal(label, minority_class))
+ majority_dataset = dataset.filter(lambda image, label: tf.equal(label, majority_class))
+
+ # Count the number of samples in each class
+ minority_count = count_samples_in_training_dataset(minority_dataset, batch_size=None)
+ majority_count = count_samples_in_training_dataset(majority_dataset, batch_size=None)
+
+ sample_per_class_input_ds = dict(majority_class=majority_count, minority_class=minority_count)
+
+ # Calculate how many samples need to be added to the minority class
+ additional_samples_needed = majority_count - minority_count
+
+ # Randomly sample from the minority dataset to add more samples
+ minority_dataset_repeated = minority_dataset.repeat()
+ minority_dataset_sampled = minority_dataset_repeated.shuffle(buffer_size=buffer_size).take(additional_samples_needed)
+
+ # Combine the sampled dataset with the original minority dataset
+ oversampled_minority_dataset = minority_dataset.concatenate(minority_dataset_sampled)
+
+ # Combine the oversampled minority class with the majority class
+ oversampled_dataset = majority_dataset.concatenate(oversampled_minority_dataset)
+
+ # Shuffle the combined dataset
+ oversampled_dataset = oversampled_dataset.shuffle(buffer_size=buffer_size)
+
+ # Recalculate steps per epoch after oversampling
+ oversampled_sample_count = majority_count + additional_samples_needed
+ training_steps = calculate_steps_per_epoch(oversampled_sample_count, batch_size)
+
+ sample_per_class_oversampled_ds = dict(majority_class=majority_count, minority_class=majority_count)
+
+ return oversampled_dataset, training_steps, oversampled_sample_count, sample_per_class_input_ds, sample_per_class_oversampled_ds
+
+
+def oversample_minority_with_tabular_smote_images_random(dataset, batch_size=batch_size, strategy=strategy, use_tabular_data=use_tabular_data):
+ """
+ Randomly oversample the minority class by duplicating its samples within the dataset for images.
+ Apply SMOTE for tabular data to oversample synthetic features. The function handles both cases: with and without tabular data.
+ """
+ # Separate the dataset into majority and minority classes
+ minority_class = 1
+ majority_class = 0
+
+ if use_tabular_data:
+ # If using tabular data, the dataset contains (image, tabular_data, label)
+ tabular_data = []
+ images = []
+ labels = []
+
+ for image, tabular, label in dataset:
+ images.append(image.numpy())
+ tabular_data.append(tabular.numpy())
+ labels.append(label.numpy())
+
+ # Convert to numpy arrays
+ tabular_data = np.array(tabular_data)
+ labels = np.array(labels)
+ images = np.array(images)
+
+ # Separate majority and minority classes
+ minority_images = images[labels == minority_class]
+ minority_tabular_data = tabular_data[labels == minority_class]
+ majority_images = images[labels == majority_class]
+ majority_tabular_data = tabular_data[labels == majority_class]
+
+ # Apply SMOTE on the tabular data
+ smote = SMOTE(sampling_strategy='auto')
+ tabular_data_resampled, labels_resampled = smote.fit_resample(tabular_data, labels)
+
+ # Find how many new samples were generated by SMOTE for the minority class
+ new_minority_count = np.sum(labels_resampled == minority_class) - len(minority_tabular_data)
+
+ # For each new synthetic tabular data point, randomly pick a corresponding minority image
+ random_indices = np.random.randint(0, len(minority_images), new_minority_count)
+ minority_images_resampled = minority_images[random_indices]
+ #minority_images_resampled = np.tile(minority_images, (new_minority_count // len(minority_images) + 1, 1, 1, 1))[:new_minority_count]
+
+
+ # Combine the original majority data with the resampled minority data
+ combined_images = np.concatenate([majority_images, minority_images, minority_images_resampled])
+ combined_tabular_data = np.concatenate([majority_tabular_data, minority_tabular_data, tabular_data_resampled[len(minority_tabular_data):]])
+ combined_labels = np.concatenate([np.full(len(majority_images), majority_class), np.full(len(minority_images), minority_class), np.full(len(minority_images_resampled), minority_class)])
+
+ assert len(combined_images) == len(combined_tabular_data) == len(combined_labels), "Mismatch in dataset length!"
+
+ # Convert the combined data back to TensorFlow tensors
+ combined_images = tf.convert_to_tensor(combined_images, dtype=tf.float32)
+ combined_tabular_data = tf.convert_to_tensor(combined_tabular_data, dtype=tf.float32)
+ combined_labels = tf.convert_to_tensor(combined_labels, dtype=tf.int64)
+
+ # Combine the resampled data into a dataset
+ oversampled_dataset = tf.data.Dataset.from_tensor_slices((combined_images, combined_tabular_data, combined_labels))
+
+ else:
+ # If not using tabular data, the dataset contains (image, label)
+ minority_dataset = dataset.filter(lambda image, label: tf.equal(label, minority_class))
+ majority_dataset = dataset.filter(lambda image, label: tf.equal(label, majority_class))
+
+ # Apply random oversampling for images only
+ minority_dataset_repeated = minority_dataset.repeat()
+ minority_dataset_sampled = minority_dataset_repeated.shuffle(buffer_size=buffer_size).take(majority_dataset.reduce(0, lambda x, _: x + 1).numpy())
+
+ oversampled_minority_dataset = minority_dataset.concatenate(minority_dataset_sampled)
+
+ # Combine the oversampled minority class with the majority class
+ oversampled_dataset = majority_dataset.concatenate(oversampled_minority_dataset)
+
+ # Shuffle the combined dataset (optional)
+ oversampled_dataset = oversampled_dataset.shuffle(buffer_size=buffer_size)
+
+ # Recalculate steps per epoch after oversampling
+ oversampled_sample_count = len(combined_labels) if use_tabular_data else count_samples_in_training_dataset(oversampled_dataset)
+ training_steps = calculate_steps_per_epoch(oversampled_sample_count, batch_size)
+
+ return oversampled_dataset, training_steps, oversampled_sample_count
+
+
+
+ Data loading utils
+
+
from sklearn.model_selection import StratifiedKFold
+from sklearn.metrics import roc_auc_score
+import numpy as np
+import math
+
+def parse_tfrecord_fn(example_proto):
+ """Parse a TFRecord into image and optional tabular data."""
+ feature_description = {'image': tf.io.FixedLenFeature([], tf.string), 'label': tf.io.FixedLenFeature([], tf.int64)}
+ if use_tabular_data:
+ feature_description['tabular_data'] = tf.io.FixedLenFeature([14], tf.float32)
+
+ example = tf.io.parse_single_example(example_proto, feature_description)
+ image = tf.image.decode_jpeg(example['image'], channels=3)
+ image = tf.image.resize(image, [img_height, img_width])
+ image = image / 255.0
+
+ #label = tf.cast(example['label'], tf.float32)
+ if use_tabular_data:
+ print('Parsing data including tabular metadata')
+ print(example['label'])
+ return image, example['tabular_data'], example['label']
+ else:
+ return image, example['label']
+
+# Wrap parsing function with error handling
+def safe_parse_fn(example):
+ try:
+ return parse_tfrecord_fn(example)
+ except Exception as e:
+ tf.print(f"Error parsing example: {e}")
+ return None
+
+def load_tfrecord_dataset(file_pattern,
+ use_tabular_data=False,
+ cache_in_memory=False,
+ is_training=False,
+ batch_size=batch_size,
+ num_parallel_reads=tf.data.AUTOTUNE,
+ data_type='Train'):
+ """Load TFRecord dataset with optional data augmentation and tabular data handling."""
+ tf.print(f"\nLoading {data_type} TFRecords...")
+
+ # Create a dataset of file paths
+ files = tf.data.Dataset.list_files(file_pattern, shuffle=is_training)
+ tf.print(f"Number of files: {files.cardinality().numpy()}")
+
+ # Interleave the files to read them in parallel
+ dataset = files.interleave(
+ lambda x: tf.data.TFRecordDataset(x, compression_type="GZIP"),
+ cycle_length=num_parallel_reads,
+ num_parallel_calls=tf.data.AUTOTUNE
+ )
+
+ # Parse the TFRecord files
+ dataset = dataset.map(parse_tfrecord_fn, num_parallel_calls=tf.data.AUTOTUNE)
+
+ dataset = dataset.cache()
+
+ #dataset = persist_dataset(dataset, file_pattern, cache_in_memory, '/tmp/tf_cache_2', prefix=data_type, run_id=run_id, check_modification_time=False)
+
+ if is_training:
+ dataset = dataset.prefetch(tf.data.AUTOTUNE)
+ else:
+ dataset = dataset.batch(batch_size * strategy.num_replicas_in_sync, drop_remainder=True)
+ dataset = dataset.prefetch(tf.data.AUTOTUNE)
+ print("Batched the dataset using batch size:", batch_size * strategy.num_replicas_in_sync)
+
+ rows_count = count_samples_in_tfrecord(file_pattern)
+ print(f"Loaded dataset at {file_pattern}, number of rows: {rows_count}")
+ if rows_count == 0:
+ raise Exception("The provided dataset is empty")
+
+ steps_per_epoch = calculate_steps_per_epoch(rows_count, batch_size)
+
+ print(f"Dataset number of batches: {steps_per_epoch}")
+
+ return dataset, steps_per_epoch
+
+
+
+
+
+ Modelling
+
+ Models utils
+
+ Base model
+
+
# Helper function to apply dense block
+def apply_dense_block(x, units, activation='swish', l2_lambda=None, dropout_rate=None, kernel_initializer='glorot_uniform'):
+ #print(f"Applying dense bloc with: kernel_initializer {kernel_initializer}, dropout_rate {dropout_rate}, activation {activation}, l2_lambda {l2_lambda}")
+ print("Applying dense bloc with kernel_initializer: {}, dropout_rate: {}, activation: {}, l2_lambda: {}".
+ format(kernel_initializer, dropout_rate, activation, l2_lambda))
+ kernel_reg = regularizers.l2(l2_lambda) if l2_lambda else None
+ x = layers.Dense(units, kernel_regularizer=kernel_reg, kernel_initializer=kernel_initializer, activation=activation)(x)
+ #x = layers.BatchNormalization()(x)
+ if dropout_rate:
+ x = layers.Dropout(dropout_rate)(x)
+ return x
+
+# Base model class for all models
+class BaseModel:
+ def __init__(self, model_name, dropout_rate=None, l2_lambda=None, freeze_base_model=False, img_shape=img_shape,
+ num_tabular_features=14, use_tabular_data=False, kernel_initializer='glorot_uniform', run_id=run_id, **kwargs):
+ """
+ Base model constructor. Supports flexible parameter passing through **kwargs.
+
+ Parameters:
+ - model_name: Name of the model to build (e.g., 'resnet50').
+ - dropout_rate: Dropout rate for dense layers.
+ - l2_lambda: L2 regularization.
+ - freeze_base_model: Whether to freeze the base model layers.
+ - img_shape: Shape of the input images.
+ - num_tabular_features: Number of tabular features (for hybrid models).
+ - use_tabular_data: Whether to include tabular data as input.
+ - kernel_initializer: Initializer for the kernel weights matrix.
+ - run_id: Unique identifier for the model run (useful for saving/loading).
+ - **kwargs: Additional parameters to pass through the model pipeline.
+ """
+ def get_param(value, default, kwargs, key):
+ if value is not None:
+ return value
+ return kwargs.get(key, default)
+ self.model_name = get_param(model_name, None, kwargs, 'model_name')
+ self.dropout_rate = get_param(dropout_rate, None, kwargs, 'dropout_rate')
+ self.l2_lambda = get_param(l2_lambda, None, kwargs, 'l2_lambda')
+ self.freeze_base_model = get_param(freeze_base_model, False, kwargs, 'freeze_base_model')
+ self.img_shape = get_param(img_shape, (224, 224, 3), kwargs, 'img_shape')
+ self.num_tabular_features = get_param(num_tabular_features, None, kwargs, 'num_tabular_features')
+ self.use_tabular_data = get_param(use_tabular_data, False, kwargs, 'use_tabular_data')
+ self.kernel_initializer = get_param(kernel_initializer, 'glorot_uniform', kwargs, 'kernel_initializer')
+ self.activation = get_param(activation, 'swish', kwargs, 'activation')
+ self.run_id = get_param(run_id, None, kwargs, 'run_id')
+ self.kwargs = kwargs
+
+ def _set_model_inputs_(self):
+ """Set input layers for the model, with optional tabular data."""
+ image_input = tf.keras.Input(shape=self.img_shape, name='image_input')
+ if self.use_tabular_data:
+ tabular_input = tf.keras.Input(shape=(self.num_tabular_features,), name='tabular_input')
+ return [image_input, tabular_input]
+ return image_input
+
+ def _set_model_inputs(self, image_input):
+ """Set input layers for the model, with optional tabular data."""
+ if self.use_tabular_data:
+ tabular_input = tf.keras.Input(shape=(self.num_tabular_features,), name='tabular_input')
+ return [image_input, tabular_input]
+ return image_input
+
+ def _get_base_model(self, model_name=None):
+ """Dynamically load the base model using the model name."""
+ if model_name is None:
+ model_name = self.model_name
+ try:
+ parts = model_name.split('_')
+ # Trick to get the class name from the provided string
+ # Example res_net_50 -> ResNet50, vgg_19 -> VGG19
+ if len(parts) > 2:
+ model_class_name = ''.join([part.capitalize() for part in parts])
+ else:
+ model_class_name = ''.join([part.upper() for part in parts])
+
+ base_model_cls = getattr(tf.keras.applications, model_class_name, None)
+
+ if base_model_cls is None:
+ raise ValueError(f"Unknown model name: {model_name}")
+ return base_model_cls(weights=None, include_top=False, input_shape=self.img_shape)
+ except Exception as e:
+ raise ValueError(f"Error loading model {model_name}: {str(e)}")
+
+ def _process_base_model(self, base_model):
+ """Freeze/unfreeze base model and apply global pooling."""
+ base_model.trainable = not self.freeze_base_model
+ return layers.GlobalAveragePooling2D()(base_model.output)
+
+ def _combine_image_and_tabular_features(self, image_features, inputs):
+ """Combine image features with tabular features, if applicable."""
+ if self.use_tabular_data:
+ tabular_input = inputs[1]
+ tabular_features = apply_dense_block(tabular_input, 1024, l2_lambda=self.l2_lambda, dropout_rate=self.dropout_rate,
+ kernel_initializer=self.kernel_initializer, activation=self.activation)
+ tabular_features = apply_dense_block(tabular_features, 812, l2_lambda=self.l2_lambda, dropout_rate=self.dropout_rate,
+ kernel_initializer=self.kernel_initializer, activation=self.activation)
+ return layers.Concatenate()([image_features, tabular_features])
+ return image_features
+
+ def set_model_architecture(self, model_name=None):
+ """Set the full model architecture."""
+ print(f'Setting model {model_name} architecture')
+ base_model = self._get_base_model(model_name)
+ image_input = base_model.input
+ inputs = self._set_model_inputs(image_input)
+ #inputs = self._set_model_inputs()
+ image_features = self._process_base_model(base_model)
+ combined_features = self._combine_image_and_tabular_features(image_features, inputs)
+
+ # Add Dense and BatchNorm layers
+ combined_features = apply_dense_block(combined_features, 512, l2_lambda=self.l2_lambda, dropout_rate=self.dropout_rate,
+ kernel_initializer=self.kernel_initializer, activation=self.activation)
+ #combined_features = apply_dense_block(combined_features, 256, l2_lambda=self.l2_lambda, dropout_rate=self.dropout_rate,
+ #kernel_initializer=self.kernel_initializer, activation=self.activation)
+
+ # Dropout Models (Multiple Dropouts for Ensembling)
+ combineds = [layers.Dropout(0.2)(combined_features) for _ in range(2)]
+
+ # Apply a Dense layer after each dropout
+ outputs = [layers.Dense(1, activation='sigmoid')(combined) for combined in combineds]
+
+ # Average the outputs for ensembling
+ final_output = layers.Average()(outputs)
+
+ model = tf.keras.Model(inputs=inputs, outputs=final_output)
+
+ model.base_model = base_model # Keep a reference to the base model for flexibility
+ return model
+
+ def create_model(self, model_name=None):
+ """
+ Create a base model using the architecture specified by model_name.
+ If model_name is None, use the instance's model_name.
+ """
+ if model_name is None:
+ model_name = self.model_name
+ print(f"Creating model {model_name}")
+ model = self.set_model_architecture(model_name)
+ model.model_name = model_name
+ return model
+
+
+
+ Ensemble model
+
+
# EnsembleModel class inherits from BaseModel
+class EnsembleModel(BaseModel):
+ def __init__(self, model_name, model_names, mode='weighted', run_id=run_id, num_ensemble=2, weights=None, **kwargs):
+ super().__init__(model_name=model_name, **kwargs)
+ self.model_name = model_name
+ self.model_names = model_names
+ self.mode = mode
+ self.num_ensemble = num_ensemble
+ self.weights = weights if weights else [1.0] * len(model_names)
+ self.run_id = run_id
+
+ def _load_pretrained_model(self, model_name):
+ """Load a pretrained model and return it."""
+ model = self.set_model_architecture(model_name)
+
+ def resolve_prm_name(model_name):
+ return ''.join(model_name.split('_'))
+
+ try:
+ model.load_weights(os.path.join(pretrained_models_dir, f'{resolve_prm_name(model_name)}_weights.h5'))
+ except Exception as e:
+ raise ValueError(f"Error loading weights for model {model_name}: {str(e)}")
+
+ return model
+
+ def create_ensemble(self, pretrained):
+ """
+ Create an ensemble model with the selected strategy (average, max, or weighted).
+ Can load pretrained models or create new ones based on the `pretrained` flag.
+ """
+ # Collect outputs from each model in the ensemble
+ inputs = self._set_model_inputs_() # Get the input layers
+ model_outputs = []
+
+ for model_name in self.model_names:
+
+ if pretrained:
+ ## Creates a pretrained model by loading the loads of the pretrained ensemble models
+ model = self._load_pretrained_model(model_name)
+ else:
+ ## Only set the architecture of the ensemble model from the individuals
+ model = self.create_model(model_name=model_name) # Create a new model from the architecture
+
+ model_outputs.append(model(inputs))
+
+ # Apply the selected ensembling strategy to combine the model outputs
+ ensemble_output = self._apply_ensemble_strategy(model_outputs)
+
+ # Create and return the ensemble model
+ ensemble_model = tf.keras.Model(inputs=inputs, outputs=ensemble_output)
+
+ ensemble_model.model_name = self.model_name # For later reuse in the training pipeline
+
+ return ensemble_model
+
+ def _apply_ensemble_strategy(self, outputs):
+ """Apply different ensemble strategies."""
+ if self.mode == 'average':
+ return layers.Average()(outputs)
+ elif self.mode == 'max':
+ return layers.Maximum()(outputs)
+ elif self.mode == 'weighted' and self.weights:
+ self.weights = [w / sum(self.weights) for w in self.weights]
+ return layers.Add()([output * weight for output, weight in zip(outputs, self.weights)])
+ elif self.mode == 'weighted_layer' and self.weights:
+ self.weights = [w / sum(self.weights) for w in self.weights]
+ weighted_outputs = [layers.Lambda(lambda x, w=weight: x * w)(output) for output, weight in zip(outputs, self.weights)]
+ return layers.Add()(weighted_outputs)
+ else:
+ raise ValueError(f"Unknown ensemble mode: {self.mode}")
+
+
+
+ Single model
+
+
class SingleModel(BaseModel):
+ def __init__(self, model_name, **kwargs):
+ super().__init__(model_name=model_name, **kwargs)
+
+
+
+ Factory utils
+
+
class ModelFactory:
+ @staticmethod
+ def create_model(model_type, model_names, pretrained=False, **kwargs):
+ """Factory method to create models based on type."""
+
+ strategy = kwargs.get('strategy')
+
+ with strategy.scope():
+ if model_type == 'ensemble':
+ weights = kwargs.get('weights')
+ ensemble_model_name = '_'.join(['_'.join((model_name, str(weight))) for (model_name, weight) in zip(model_names, weights)])
+ ensemble_model_name = f"pretrained_ensemble_{ensemble_model_name}" if pretrained else f"compact_ensemble_{ensemble_model_name}"
+ return EnsembleModel(model_name=ensemble_model_name, model_names=model_names, **kwargs).create_ensemble(pretrained=pretrained)
+
+ elif model_type == 'single':
+ return SingleModel(model_name=model_names[0], **kwargs).create_model()
+ else:
+ raise ValueError(f"Unknown model type: {model_type}")
+
+
+
+ Loss functions
+
+
@register_keras_serializable()
+@tf.function
+def focal_loss(y_true, y_pred, gamma=2.0, alpha=0.25):
+ """
+ Focal Loss for binary classification.
+
+ Args:
+ - y_true: Ground truth labels, shape = (batch_size, 1)
+ - y_pred: Predicted labels, shape = (batch_size, 1)
+ - gamma: Focusing parameter (default is 2.0)
+ - alpha: Balancing factor (default is 0.25)
+
+ Returns:
+ - Loss value
+ """
+ # Clip predictions to prevent log(0) and ensure stability
+ y_pred = tf.clip_by_value(y_pred, K.epsilon(), 1. - K.epsilon())
+
+ # Calculate cross-entropy loss
+ bce_loss = - (y_true * K.log(y_pred) + (1 - y_true) * K.log(1 - y_pred))
+
+ # Calculate p_t and modulating factor
+ p_t = y_true * y_pred + (1 - y_true) * (1 - y_pred)
+ modulating_factor = (1 - p_t) ** gamma
+
+ # Apply alpha balancing factor
+ alpha_factor = y_true * alpha + (1 - y_true) * (1 - alpha)
+
+ # Compute focal loss
+ loss = alpha_factor * modulating_factor * bce_loss
+
+ return tf.reduce_mean(loss)
+
+@register_keras_serializable()
+@tf.function
+def class_balanced_loss(y_true, y_pred, beta=0.99, samples_per_cls=None):
+ """
+ Class-Balanced Loss function for binary classification.
+
+ Parameters:
+ - y_true: Ground truth labels, shape = (batch_size,)
+ - y_pred: Predicted labels, shape = (batch_size,)
+ - samples_per_cls: List or array containing the number of samples for each class (class 0 and class 1).
+ - beta: Hyperparameter to adjust class balancing (default is 0.99)
+
+ Returns:
+ - Loss value
+ """
+ # Calculate the effective number of samples
+ effective_num = 1.0 - tf.pow(beta, samples_per_cls)
+ weights = (1.0 - beta) / tf.maximum(effective_num, tf.keras.backend.epsilon())
+ weights = weights / tf.reduce_sum(weights) # Normalize weights
+
+ # Extract the class weights for class 0 and class 1
+ weight_for_0 = weights[0]
+ weight_for_1 = weights[1]
+
+ # Apply weights to the true labels (binary classification)
+ weights_per_sample = y_true * weight_for_1 + (1.0 - y_true) * weight_for_0
+
+ # Compute the binary crossentropy loss
+ loss = tf.keras.losses.binary_crossentropy(y_true, y_pred)
+
+ # Apply class-balanced weights to the loss
+ loss = weights_per_sample * loss
+ return tf.reduce_mean(loss)
+
+@register_keras_serializable()
+def combined_loss(samples_per_cls, beta=0.99, gamma=2.0, alpha=0.8, focal_loss_percent=0.5):
+ """
+ Combined Class-Balanced Loss and Focal Loss.
+
+ Parameters:
+ - y_true: Ground truth labels, shape = (batch_size, num_classes)
+ - y_pred: Predicted labels, shape = (batch_size, num_classes)
+
+ Returns:
+ - Combined loss value
+ """
+ @tf.function
+ def loss(y_true, y_pred):
+ y_true = tf.cast(y_true, tf.float32)
+ y_true = tf.reshape(y_true, [-1, 1])
+ y_pred = tf.reshape(y_pred, [-1, 1])
+
+ cb_loss = class_balanced_loss(y_true, y_pred, beta=beta, samples_per_cls=samples_per_cls)
+ fl_loss = focal_loss(y_true, y_pred, gamma=gamma, alpha=alpha)
+ return ((1 - focal_loss_percent) * cb_loss) + (focal_loss_percent * fl_loss)
+ return loss
+
+##Binary class balance loss
+@register_keras_serializable()
+def get_class_balanced_weights(beta, samples_per_cls):
+ effective_num = 1.0 - np.power(beta, samples_per_cls)
+ effective_num = np.where(effective_num == 0, 1e-8, effective_num) # Avoid division by zero
+ weights = (1.0 - beta) / effective_num
+ weights = weights / np.sum(weights) * len(samples_per_cls)
+ return weights
+
+@tf.function
+@register_keras_serializable()
+def class_balanced_binary_cross_entropy(beta, samples_per_cls):
+ weights = get_class_balanced_weights(beta, samples_per_cls)
+ weight_for_0 = weights[0]
+ weight_for_1 = weights[1]
+
+ def loss(y_true, y_pred):
+ y_true = tf.cast(y_true, tf.float32)
+ bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)
+ weights_per_sample = y_true * weight_for_1 + (1 - y_true) * weight_for_0
+ weighted_bce = weights_per_sample * bce
+ return tf.reduce_mean(weighted_bce)
+
+ return loss
+
+@register_keras_serializable()
+@tf.function
+def focal_loss_keras_bce(y_true, y_pred, gamma, alpha):
+ """
+ Focal Loss function.
+ Parameters:
+ - y_true: Ground truth labels, shape = (batch_size, num_classes)
+ - y_pred: Predicted labels, shape = (batch_size, num_classes)
+ - gamma: Focusing parameter (default is 2.0)
+ - alpha: Balancing factor (default is 0.25)
+
+ Returns:
+ - Loss value
+ """
+ #y_true = tf.cast(y_true, tf.float32)
+ # Compute cross entropy
+ bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)
+
+ # Compute focal loss
+ p_t = y_true * y_pred + (1 - y_true) * (1 - y_pred)
+ alpha_factor = y_true * alpha + (1 - y_true) * (1 - alpha)
+ modulating_factor = tf.pow(1.0 - p_t, gamma)
+
+ loss = alpha_factor * modulating_factor * bce
+ return tf.reduce_mean(loss)
+
+@tf.function
+def weighted_binary_crossentropy(y_true, y_pred):
+ class_weight_0 = 0.1 # Weight for class 0 (negative)
+ class_weight_1 = 0.9 # Weight for class 1 (positive)
+
+ weights = tf.where(tf.equal(y_true, 1), class_weight_1, class_weight_0)
+ bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)
+ weighted_bce = bce * weights
+ return tf.reduce_mean(weighted_bce)
+
+
+@register_keras_serializable()
+def class_balanced_loss_vect(y_true, y_pred, samples_per_cls, beta):
+ """
+ Class-Balanced Loss function with samples_per_cls parameter.
+
+ Parameters:
+ - y_true: Ground truth labels, shape = (batch_size, num_classes)
+ - y_pred: Predicted labels, shape = (batch_size, num_classes)
+ - samples_per_cls: List or array containing the number of samples for each class.
+ - beta: Hyperparameter to adjust class balancing (usually close to 1.0, default is 0.9999)
+
+ Returns:
+ - Loss value
+ """
+ #y_true = tf.cast(y_true, tf.float32)
+ # Calculate the effective number of samples
+ effective_num = 1.0 - tf.pow(beta, samples_per_cls)
+ weights = (1.0 - beta) / tf.maximum(effective_num, tf.keras.backend.epsilon())
+ weights = weights / tf.reduce_sum(weights) # Normalize weights
+
+ # Apply weights to the true labels
+ weights = tf.reduce_sum(weights * y_true, axis=-1)
+
+ # Compute the binary crossentropy loss
+ loss = tf.keras.losses.binary_crossentropy(y_true, y_pred)
+
+ # Apply class-balanced weights to the loss
+ loss = weights * loss
+ return tf.reduce_mean(loss)
+
+@tf.function
+def weighted_binary_crossentropy(y_true, y_pred):
+ # Define class weights
+ class_weight_0 = 0.1 # Weight for class 0 (negative)
+ class_weight_1 = 0.9 # Weight for class 1 (positive)
+
+ # Create a tensor of weights based on the true labels
+ weights = tf.where(tf.equal(y_true, 1), class_weight_1, class_weight_0)
+
+ # Calculate binary crossentropy
+ bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)
+
+ # Apply the weights to the loss
+ weighted_bce = bce * weights
+
+ # Return the mean loss
+ return tf.reduce_mean(weighted_bce)
+
+def focal_loss_fix(gamma=2., alpha=0.25):
+ def focal_loss_fixed(y_true, y_pred):
+ epsilon = K.epsilon()
+ y_pred = K.clip(y_pred, epsilon, 1. - epsilon)
+ y_true = K.cast(y_true, K.floatx())
+
+ alpha_t = y_true * alpha + (K.ones_like(y_true) - y_true) * (1 - alpha)
+ p_t = y_true * y_pred + (K.ones_like(y_true) - y_true) * (1 - y_pred)
+ fl = - alpha_t * K.pow((K.ones_like(y_true) - p_t), gamma) * K.log(p_t)
+ return K.mean(fl)
+ return focal_loss_fixed
+
+def set_binary_crossentropy_weighted_loss(positive_weights=np.array([5.291666666666667]), negative_weights=np.array([0.5521739130434783]), epsilon=1e-7):
+ """
+ Note: Imported from the AI for Medicine Specialization course on Coursera: Assignment 1 Week 1.
+ Returns weighted binary cross entropy loss function given negative weights and positive weights.
+
+ Args:
+ positive_weights (np.array): array of positive weights for each class, size (num_classes)
+ negative_weights (np.array): array of negative weights for each class, size (num_classes)
+
+ Returns:
+ weighted_loss (function): weighted loss function
+ """
+ def binary_crossentropy_weighted_loss(y_true, y_pred):
+ """
+ Returns weighted binary cross entropy loss value.
+
+ Args:
+ y_true (Tensor): Tensor of true labels, size is (num_examples, num_classes)
+ y_pred (Tensor): Tensor of predicted labels, size is (num_examples, num_classes)
+
+ Returns:
+ loss (Tensor): overall scalar loss summed across all classes
+ """
+ y_true = tf.cast(y_true, tf.float32)
+ y_pred = tf.cast(y_pred, tf.float32)
+
+ # initialize loss to zero
+ loss = 0.0
+ #y_true = tf.cast(y_true, tf.float32)
+ for i in range(len(positive_weights)):
+ # for each class, add average weighted loss for that class
+ loss += -1 * K.mean((positive_weights[i] * y_true[:, i] * K.log(y_pred[:, i] + epsilon) +
+ negative_weights[i] * (1 - y_true[:, i]) * K.log(1 - y_pred[:, i] + epsilon)))
+ return loss
+
+ return binary_crossentropy_weighted_loss
+
+
+
+ Metrics
+
+
def score(y_true, y_pred, min_tpr: float = 0.80) -> float:
+ v_gt = abs(np.asarray(y_true) - 1)
+ v_pred = np.array([1.0 - x for x in y_pred])
+ max_fpr = abs(1 - min_tpr)
+ partial_auc_scaled = roc_auc_score(v_gt, v_pred, max_fpr=max_fpr)
+ partial_auc = 0.5 * max_fpr**2 + (max_fpr - 0.5 * max_fpr**2) / (1.0 - 0.5) * (partial_auc_scaled - 0.5)
+ return partial_auc
+
+@register_keras_serializable()
+class F1Score(tf.keras.metrics.Metric):
+ def __init__(self, name='f1_score', **kwargs):
+ super(F1Score, self).__init__(name=name, **kwargs)
+ self.precision = tf.keras.metrics.Precision()
+ self.recall = tf.keras.metrics.Recall()
+
+ def update_state(self, y_true, y_pred, sample_weight=None):
+ self.precision.update_state(y_true, y_pred, sample_weight)
+ self.recall.update_state(y_true, y_pred, sample_weight)
+
+ def result(self):
+ precision = self.precision.result()
+ recall = self.recall.result()
+ return 2 * ((precision * recall) / (precision + recall + tf.keras.backend.epsilon()))
+
+ def reset_state(self):
+ self.precision.reset_state()
+ self.recall.reset_state()
+
+def get_labels_and_predictions(model, dataset, steps):
+ """Return true labels and predictions from the model."""
+ y_true, y_pred = [], []
+ for x_batch, y_batch in dataset.take(steps):
+ y_pred_batch = model.predict_on_batch(x_batch).ravel()
+ y_true.append(y_batch)
+ y_pred.append(y_pred_batch)
+ return tf.concat(y_true, axis=0), tf.concat(y_pred, axis=0)
+
+def evaluate_model(model, eval_dataset, evaluation_steps, dataset_name='eval_dataset'):
+ print('Evaluating', dataset_name, 'with', evaluation_steps, 'steps')
+ y_val, y_pred = get_labels_and_predictions(model, eval_dataset, evaluation_steps)
+ partial_auc = score(y_val, y_pred)
+ print(f"Partial AUC for {dataset_name}: {partial_auc}")
+
+def get_memory_usage():
+ # memory_usage returns a list, so we get the first item
+ mem = memory_usage(-1, interval=0.1, timeout=1)[0] # Return the first value from the list in MB
+ mem = f"{mem:.2f} MB"
+ return mem
+
+def reset_session_and_get_memory():
+ """Cleans up the session and retrieves the initial memory usage."""
+ clean_session()
+ return get_memory_usage()
+
+def log_memory_usage(stage, memory_usage):
+ """Logs the memory usage with a specific stage identifier."""
+ print(f"{stage} memory usage: {memory_usage}")
+
+
+
+ Callbacks
+
+
class PartialAUCCallback(tf.keras.callbacks.Callback):
+ def __init__(self, validation_data, validation_steps, monitor, train_data = None, steps_per_epoch=None, min_tpr=0.8):
+ super(PartialAUCCallback, self).__init__()
+ self.validation_data = validation_data
+ self.validation_steps = validation_steps
+ self.monitor = monitor
+ self.train_data = train_data
+ self.steps_per_epoch = steps_per_epoch
+ self.min_tpr = min_tpr
+ self.max_fpr = 1.0 - min_tpr
+ self.best_partial_auc = -float('inf') # Initialize to a very low value
+ self.best_epoch = 0
+
+ def on_epoch_end(self, epoch, logs=None):
+ logs = logs or {}
+
+ # Compute Partial AUC on Training Data if available
+ if self.train_data:
+ train_labels, train_predictions = get_labels_and_predictions(self.model,
+ self.train_data,
+ self.steps_per_epoch)
+ train_partial_auc = self.compute_auc(train_labels, train_predictions)
+ self.log_partial_auc(logs, train_partial_auc, 'partial_auc', epoch)
+
+ PartialAUCCallback.log_classification_report(train_labels, train_predictions, epoch, 'Train')
+
+ #Validation computations
+ val_labels, val_predictions = get_labels_and_predictions(self.model,
+ self.validation_data,
+ self.validation_steps)
+ # Calculate the partial AUC
+ val_partial_auc = self.compute_auc(val_labels, val_predictions)
+ self.log_partial_auc(logs, val_partial_auc, 'val_partial_auc', epoch)
+ PartialAUCCallback.log_classification_report(val_labels, val_predictions, epoch)
+
+
+ def log_partial_auc(self, logs, partial_auc, pauc_metric_name, epoch):
+ logs[pauc_metric_name] = partial_auc
+ if self.monitor == pauc_metric_name:
+ # Store the best partial AUC
+ if partial_auc > self.best_partial_auc:
+ self.best_partial_auc = partial_auc
+ self.best_epoch = epoch
+
+ # Add the partial_auc to logs so it can be used in Keras Tuner
+ logs[pauc_metric_name] = partial_auc
+ tf.print(f'\nEpoch {epoch + 1}: Partial AUC = {partial_auc:.4f}')
+
+ @staticmethod
+ def log_classification_report(labels, predictions, epoch, name='Validation'):
+ # Convert probabilities to binary predictions
+ bin_preds = np.round(predictions).astype(int)
+ # Generate the classification report
+ report = classification_report(labels, bin_preds, labels= [1, 0],
+ target_names=['positives', 'negatives'])
+ # Print the report
+ tf.print(f"\n{name} classification report for epoch {epoch + 1}:\n{report}")
+
+ def compute_auc(self, y_true, y_pred, max_fpr=0.2):
+ v_gt = np.abs(y_true - 1)
+ v_pred = 1.0 - y_pred
+ partial_auc_scaled = roc_auc_score(v_gt, v_pred, max_fpr=max_fpr)
+ partial_auc = 0.5 * max_fpr**2 + (max_fpr - 0.5 * max_fpr**2) / (1.0 - 0.5) * (partial_auc_scaled - 0.5)
+ return partial_auc
+
+ def compute_auc_kaggle(self, y_true, y_pred):
+ y_true = tf.cast(y_true, tf.float32).numpy()
+ y_pred = tf.cast(y_pred, tf.float32).numpy()
+
+ # Invert classes as needed for minority focus
+ v_gt = 1 - y_true
+ v_pred = 1 - y_pred
+ fpr, tpr, _ = roc_curve(v_gt, v_pred)
+
+ # Find where FPR reaches max_fpr
+ stop = np.searchsorted(fpr, self.max_fpr, 'right')
+ if stop == 0: # If max_fpr is very low and FPR doesn't reach it
+ return 0.0
+
+ x_interp = [fpr[stop - 1], fpr[stop]]
+ y_interp = [tpr[stop - 1], tpr[stop]]
+ tpr_interp = np.interp(self.max_fpr, x_interp, y_interp)
+
+ # Append this interpolated point and calculate AUC
+ tpr = np.append(tpr[:stop], tpr_interp)
+ fpr = np.append(fpr[:stop], self.max_fpr)
+ return auc(fpr, tpr)
+
+ def get_best_partial_auc(self):
+ return self.best_partial_auc
+
+ def get_best_epoch(self):
+ return self.best_epoch
+
+ def __deepcopy__(self, memo):
+ # Skip deep copying the validation data to avoid issues
+ return PartialAUCCallback(self.validation_data, self.validation_steps,
+ self.monitor, self.train_data,
+ self.steps_per_epoch, self.min_tpr)
+
+class LearningRatePrinterCallback(tf.keras.callbacks.Callback):
+ def on_epoch_end(self, epoch, logs=None):
+ # Access the learning rate from the optimizer
+ lr = self.model.optimizer.learning_rate
+ # If the learning rate is a learning rate schedule (like CyclicalLearningRate), evaluate it
+ if isinstance(lr, tf.keras.optimizers.schedules.LearningRateSchedule):
+ lr = lr(self.model.optimizer.iterations)
+ tf.print(f"\nLearning rate at the end of epoch {epoch + 1}: {tf.keras.backend.get_value(lr)}")
+
+
+# Custom Callback to monitor memory after each epoch
+class MemoryUsageCallback(tf.keras.callbacks.Callback):
+ def on_epoch_end(self, epoch, logs=None):
+ process = psutil.Process(os.getpid())
+ mem = process.memory_info().rss / (1024 * 1024) # RSS memory in MB
+ print(f"Memory usage after epoch {epoch + 1}: {mem:.2f} MB")
+ #tf.keras.backend.clear_session()
+ #tf.compat.v1.reset_default_graph()
+ gc.collect()
+ #clean_session()
+
+
+
+ Optimizers
+
+
def clean_metric_name(metric_name):
+ # Use regular expression to check if the string ends with _ followed by digits
+ if re.search(r'_\d+$', metric_name):
+ # Remove the _ and the digits at the end
+ return re.sub(r'_\d+$', '', metric_name)
+ return metric_name
+
+# Define a custom learning rate scheduler for cyclical learning rates
+class CyclicalLearningRate(tf.keras.optimizers.schedules.LearningRateSchedule):
+ def __init__(self, initial_learning_rate, maximal_learning_rate, step_size):
+ self.initial_learning_rate = tf.cast(initial_learning_rate, tf.float32)
+ self.maximal_learning_rate = tf.cast(maximal_learning_rate, tf.float32)
+ self.step_size = tf.cast(step_size, tf.float32)
+
+ def __call__(self, step):
+ step = tf.cast(step, tf.float32)
+ cycle = tf.floor(1 + step / (2 * self.step_size))
+ x = tf.abs(step / self.step_size - 2 * cycle + 1)
+ lr = self.initial_learning_rate + (self.maximal_learning_rate - self.initial_learning_rate) * tf.maximum(0.0, (1 - x))
+ return lr
+
+ def get_config(self):
+ return {
+ "initial_learning_rate": self.initial_learning_rate.numpy(),
+ "maximal_learning_rate": self.maximal_learning_rate.numpy(),
+ "step_size": self.step_size.numpy(),
+ }
+
+# Set the parameters for the CLR
+initial_learning_rate = 1e-6 # Minimum LR
+maximal_learning_rate = 1e-4 # Maximum LR
+step_size = 50 # Number of steps in half a cycle
+def set_optimizer(optimizer, learning_rate=1e-4, use_nesterov_sgd=False, use_amsgrad_adam=False,
+ initial_learning_rate=1e-6,
+ maximal_learning_rate=1e-4,
+ step_size=50
+ ):
+ if optimizer == "sgd":
+ optimizer = optimizers.SGD(
+ learning_rate=learning_rate,
+ momentum=0.9,
+ nesterov=use_nesterov_sgd
+ )
+
+ elif optimizer == "adam":
+ optimizer = optimizers.Adam(
+ learning_rate=learning_rate,
+ beta_1=0.9,
+ beta_2=0.999,
+ epsilon=0.1,
+ amsgrad=use_amsgrad_adam
+ )
+
+ elif optimizer == "nadam":
+ optimizer = optimizers.Nadam(
+ learning_rate=learning_rate,
+ beta_1=0.9,
+ beta_2=0.999,
+ epsilon=0.1
+ )
+ elif optimizer == "adamw":
+ optimizer = optimizers.AdamW(
+ learning_rate=learning_rate,
+ beta_1=0.9,
+ beta_2=0.999,
+ epsilon=0.1
+ )
+ elif optimizer == "adamax":
+ optimizer = optimizers.Adamax(
+ learning_rate=learning_rate,
+ beta_1=0.9,
+ beta_2=0.999,
+ epsilon=0.1
+ )
+ elif optimizer == "radam":
+ optimizer = tfa.optimizers.RectifiedAdam(
+ learning_rate=learning_rate,
+ beta_1=0.9,
+ beta_2=0.999,
+ epsilon=0.1
+ )
+ elif optimizer == "rmsprop":
+ optimizer = optimizers.RMSprop(
+ learning_rate=learning_rate,
+ rho=0.9,
+ momentum=0.0,
+ epsilon=0.1,
+ centered=False
+ )
+ elif optimizer == "nadam_cyclical":
+ optimizer = optimizers.Nadam(
+ learning_rate=CyclicalLearningRate(
+ initial_learning_rate=initial_learning_rate,
+ maximal_learning_rate=maximal_learning_rate,
+ step_size=step_size
+ )
+ )
+ elif optimizer == "radam_cyclical":
+ optimizer = tfa.optimizers.RectifiedAdam(
+ learning_rate=CyclicalLearningRate(
+ initial_learning_rate=initial_learning_rate,
+ maximal_learning_rate=maximal_learning_rate,
+ step_size=step_size
+ )
+ )
+ elif optimizer == "adamw_cyclical":
+ optimizer = optimizers.AdamW(
+ learning_rate=CyclicalLearningRate(
+ initial_learning_rate=initial_learning_rate,
+ maximal_learning_rate=maximal_learning_rate,
+ step_size=step_size
+ )
+ )
+ return optimizer
+
+class OneCycleScheduler(tf.keras.callbacks.Callback):
+ def __init__(self, max_lr, steps_per_epoch, epochs, start_lr=None, last_epoch_lr=None):
+ super(OneCycleScheduler, self).__init__()
+ self.max_lr = max_lr
+ self.steps_per_epoch = steps_per_epoch
+ self.epochs = epochs
+ self.total_steps = steps_per_epoch * epochs
+ self.current_step = 0
+
+ if start_lr is None:
+ self.start_lr = max_lr / 25 # A commonly used starting ratio
+ else:
+ self.start_lr = start_lr
+
+ if last_epoch_lr is None:
+ self.last_epoch_lr = self.start_lr / 1e4 # A small value to anneal towards
+ else:
+ self.last_epoch_lr = last_epoch_lr
+
+ def on_batch_begin(self, batch, logs=None):
+ # Update learning rate based on current step
+ self.current_step += 1
+ lr = self.calc_lr()
+ #tf.keras.backend.set_value(self.model.optimizer[0].lr, lr)
+ tf.keras.backend.set_value(self.model.optimizer.lr, lr)
+
+ def calc_lr(self):
+ # 1Cycle Learning Rate Schedule: Increases then decreases
+ step_ratio = self.current_step / self.total_steps
+ if step_ratio < 0.6:
+ # Increase learning rate for first half of training
+ lr = self.start_lr + (self.max_lr - self.start_lr) * (step_ratio * 2)
+ else:
+ # Decrease learning rate for the second half of training
+ lr = self.max_lr - (self.max_lr - self.last_epoch_lr) * ((step_ratio - 0.5) * 2)
+ return lr
+
+class SGDRScheduler(tf.keras.callbacks.Callback):
+ '''Cosine annealing learning rate scheduler with periodic restarts.'''
+ def __init__(self, min_lr, max_lr, steps_per_epoch, lr_decay=1, cycle_length=10, mult_factor=2):
+ super(SGDRScheduler, self).__init__()
+ self.min_lr = min_lr
+ self.max_lr = max_lr
+ self.lr_decay = lr_decay
+ self.steps_per_epoch = steps_per_epoch
+ self.batch_since_restart = 0
+ self.next_restart = cycle_length
+ self.cycle_length = cycle_length
+ self.mult_factor = mult_factor
+
+ def on_batch_begin(self, batch, logs=None):
+ '''Adjust the learning rate at the start of each batch.'''
+ fraction_to_restart = self.batch_since_restart / (self.steps_per_epoch * self.cycle_length)
+ lr = self.min_lr + 0.5 * (self.max_lr - self.min_lr) * (1 + np.cos(fraction_to_restart * np.pi))
+ tf.keras.backend.set_value(self.model.optimizer.lr, lr)
+ self.batch_since_restart += 1
+
+ def on_epoch_end(self, epoch, logs=None):
+ '''Check for end of current cycle, apply restarts when necessary.'''
+ if epoch + 1 == self.next_restart:
+ self.batch_since_restart = 0
+ self.next_restart += self.cycle_length
+ self.cycle_length = int(self.cycle_length * self.mult_factor)
+ self.max_lr *= self.lr_decay
+ self.min_lr *= self.lr_decay
+
+
+
+
+ Training utils
+
+ Plotting metrics
+
+
def plot_training_history(history, model_name, metrics=['f1_score', 'precision', 'recall', 'loss', 'accuracy', 'partial_auc', 'auc']):
+ plt.figure(figsize=(16, 16))
+ for i, metric in enumerate(metrics):
+ plt.subplot(4, 2, i + 1)
+ plt.title(metric.capitalize())
+ if metric in history:
+ plt.plot(history[metric], label=metric)
+ if f'val_{metric}' in history:
+ plt.plot(history[f'val_{metric}'], label=f'val_{metric}')
+ plt.legend(loc='upper right')
+ plt.tight_layout()
+ plt.title(model_name, loc='center')
+ plt.savefig(f'{models_dir}/{model_name}_training_{run_id}')
+ plt.show()
+
+def normalize_history_keys(history):
+ """
+ Normalize the keys in the history dictionary using clean_metric_name.
+ """
+ return {clean_metric_name(key): value for key, value in history.items()}
+
+def print_infos(training_mode, fold_no, messages):
+ if training_mode == 'train-validation-split':
+ for message in messages:
+ print(message.capitalize())
+ else:
+ for message in messages:
+ print(f"Fold {fold_no + 1}", message)
+
+def merge_histories(history_initial, history_fine_tune):
+ # Merge histories
+ history = defaultdict(list)
+ if history_initial is not None:
+ # Normalize the keys in history_initial
+ history_initial_cleaned = normalize_history_keys(history_initial.history)
+ for key, value in history_initial_cleaned.items():
+ history[key] = value
+ else:
+ # Normalize the keys in history_fine_tune
+ history_fine_tune_cleaned = normalize_history_keys(history_fine_tune.history)
+ for key, value in history_fine_tune_cleaned.items():
+ history[key].extend(value)
+ history = dict(history)
+ return history
+
+
+
+ Data loading
+
+
def load_train_val_dataset(file_pattern, shuffle_dataset_at_each_call=True, cache_dataset=True, cache_in_memory=False,
+ cache_dir=cache_dir, prefix="train_val", run_id=run_id, check_modification_time=False,
+ is_training=True, force_cache=True, use_tabular_data=use_tabular_data, batch_size=batch_size):
+ print("Reloading and shuffling dataset...")
+ try:
+ del dataset
+ gc.collect()
+ except:
+ pass
+ gc.collect()
+
+ print('batch_size', batch_size)
+ print('file_pattern', file_pattern)
+ print('is_training', is_training)
+
+ print('cache_in_memory', cache_in_memory)
+
+ # Load dataset once and cache
+ dataset, dataset_steps = load_tfrecord_dataset(file_pattern=file_pattern, is_training=is_training,
+ batch_size=batch_size, cache_in_memory=cache_in_memory)
+
+ #dataset = cast_labels(dataset, use_tabular_data)
+
+
+ #dataset = persist_dataset(dataset, file_pattern=train_file_pattern, dataset_steps=dataset_steps, cache_in_memory=cache_in_memory,
+ #cache_dir=cache_dir, prefix=prefix, run_id=run_id, check_modification_time=check_modification_time, force_cache=force_cache)
+
+ # Extract labels for stratified splitting
+ use_cross_validation = False
+ if use_cross_validation :
+ labels = []
+ for *_, lbl in dataset:
+ labels.append(lbl.numpy())
+
+ labels = np.array(labels)
+
+ seed = random.randint(0, 1e9) if shuffle_dataset_at_each_call else 42
+
+ if use_cross_validation:
+ # Generate stratified split indices for cross-validation
+ split_indices = stratified_split_indices(labels, n_splits=5, shuffle=True, random_state=seed)
+ else:
+ # Create a stratified 80/20 train-test split
+ sss = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=seed)
+ train_index, val_index = next(sss.split(np.zeros(len(labels)), labels))
+ split_indices = [(train_index, val_index)] # Single split for train-test
+ return dataset, split_indices, dataset_steps
+ else:
+ return dataset, dataset_steps
+
+
+
+ Training pipeline common
+
+
def training_pipeline(model_name, model_names=None, model=None, train_file_pattern=None, **kwargs):
+ run_id = kwargs.get('run_id', None)
+ strategy = kwargs.get('strategy', None)
+ batch_size = kwargs.get('batch_size', None)
+ val_batch_size = kwargs.get('val_batch_size', None)
+ cache_in_memory = kwargs.get('cache_in_memory', None)
+ freeze_base_model = kwargs.get('freeze_base_model', None)
+ loss = kwargs.get('loss', None)
+ shuffle_dataset_at_each_call = kwargs.get('shuffle_dataset_at_each_call', None)
+ do_fine_tuning = kwargs.get('do_fine_tuning', None)
+ initial_epochs = kwargs.get('initial_epochs', None)
+ fine_tune_epochs = kwargs.get('fine_tune_epochs', None)
+ oversample_minority_class = kwargs.get('oversample_minority_class', None)
+ augment_train_data = kwargs.get('augment_train_data', None)
+ use_cross_validation = kwargs.get('use_cross_validation', None)
+ use_tabular_data = kwargs.get('use_tabular_data', None)
+ execution_env = kwargs.get('execution_env', None)
+ total_epochs = initial_epochs + fine_tune_epochs if freeze_base_model else fine_tune_epochs
+
+ # cleaning the session
+ deletes_old_datasets()
+
+ partial_aucs = []
+ training_mode = 'train-validation-split'
+ print("Kwargs from training_pipeline ", kwargs)
+
+ # Load train dataset once and cache
+ train_dataset, training_steps = load_tfrecord_dataset(file_pattern=train_file_pattern,
+ batch_size=batch_size,
+ is_training=True,
+ cache_in_memory=cache_in_memory)
+ # Validation dataset
+ val_dataset, validation_steps = load_tfrecord_dataset(f'{tf_records_eval_dir}/*.tfrecord',
+ batch_size=val_batch_size,
+ is_training=False,
+ cache_in_memory=cache_in_memory)
+
+ print_infos(training_mode, None, [f"start training model {model_name}...",
+ f"training size: {training_steps * batch_size * strategy.num_replicas_in_sync}",
+ f"validation size: {validation_steps * val_batch_size * strategy.num_replicas_in_sync}"])
+
+ # Oversample the minority class within the fold
+ if oversample_minority_class:
+ (train_dataset, training_steps, training_total_samples, sample_per_class_input_ds,
+ sample_per_class_oversampled_ds) = oversample_minority_class_random(train_dataset, batch_size, strategy)
+ print('Total samples in train dataset after oversampling', training_total_samples)
+
+ # Calculate class weights
+ class_weights_ovs = calculate_class_weights(sample_per_class_oversampled_ds)
+ class_weights_input_ds = calculate_class_weights(sample_per_class_input_ds)
+ print_infos(training_mode, None, [f"samples per class: {sample_per_class_input_ds}", f"class weights: {class_weights_input_ds}",
+ f"training steps: {training_steps}, evaluation steps: {validation_steps}"])
+
+ # Training data processing
+ train_dataset = optimize_dataset(train_dataset, file_pattern=None, steps=training_steps, cache_in_memory=cache_in_memory,
+ cache_dir=cache_dir, prefix='train', run_id=run_id, check_modification_time=False, force_cache=True)
+
+ if augment_train_data:
+ train_dataset = apply_augmentations(train_dataset)
+ train_dataset = format_input_dataset(train_dataset, use_tabular_data)
+ train_dataset = train_dataset.prefetch(tf.data.AUTOTUNE)
+
+ # Validation data processing
+ val_dataset = format_input_dataset(val_dataset, use_tabular_data)
+ val_dataset = optimize_dataset(val_dataset, file_pattern=None, steps=validation_steps, cache_in_memory=cache_in_memory,
+ cache_dir=cache_dir, prefix='val', run_id=run_id, check_modification_time=False,
+ force_cache=True)
+
+ # Callbacks
+ memory_callback = MemoryUsageCallback()
+ early_stopping_callback = EarlyStopping(monitor='val_loss', patience=20, restore_best_weights=True)
+ partial_auc_callback = PartialAUCCallback(validation_data=val_dataset, validation_steps=validation_steps,
+ monitor='val_partial_auc')
+ one_cycle_scheduler_callback = OneCycleScheduler(max_lr=1e-5, steps_per_epoch=training_steps, epochs=total_epochs,
+ last_epoch_lr=1e-7, start_lr=1e-7)
+ model_checkpoint_callback = ModelCheckpoint(filepath=f'{models_dir}/model_{model_name}_{run_id}_checkpoint.keras',
+ monitor='val_partial_auc', save_best_only=True)
+ callbacks=[one_cycle_scheduler_callback, partial_auc_callback, early_stopping_callback, model_checkpoint_callback, memory_callback]
+
+
+ with strategy.scope():
+ precision = tf.keras.metrics.Precision()
+ recall = tf.keras.metrics.Recall()
+ f1_score = F1Score()
+ auc = tf.keras.metrics.AUC(name="auc")
+
+ optimizer = tf.keras.optimizers.Nadam() if execution_env == 'colab' else tf.keras.optimizers.Adamax()
+ loss = set_binary_crossentropy_weighted_loss(positive_weights=np.array([class_weights_input_ds['minority_class']]),
+ negative_weights=np.array([class_weights_input_ds['majority_class']]),
+ epsilon=1e-7)
+ history_initial = None
+ class_weights=dict(zip([0, 1], class_weights_input_ds.values()))
+ # Initial training
+ if freeze_base_model:
+ with strategy.scope():
+ model.compile(optimizer=optimizer, loss=loss, metrics=[precision, recall, f1_score])
+ print("Started fitting the model with base model freezed...")
+ history_initial = model.fit(train_dataset,epochs=initial_epochs,steps_per_epoch=training_steps, validation_data=val_dataset,
+ validation_steps=validation_steps, callbacks=callbacks, class_weight=class_weights, verbose=1)
+
+ # Fine tuning
+ if do_fine_tuning:
+ initial_epoch = history_initial.epoch[-1] if freeze_base_model else 0
+
+ with strategy.scope():
+ model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy', precision, recall, f1_score, auc])
+
+ print("Started fine fitting the model with layers unfrozen...")
+ history_fine_tune = model.fit(train_dataset, epochs=total_epochs, initial_epoch=initial_epoch, steps_per_epoch=training_steps,
+ validation_data=val_dataset, validation_steps=validation_steps, class_weight=class_weights,
+ callbacks=callbacks, verbose=1)
+ # Merge histories
+ history = merge_histories(history_initial, history_fine_tune)
+ else:
+ history = history_initial.history
+
+ print("Computing the final validation score after training ended")
+ # Evaluate the model on the validation set
+ y_val, y_pred = get_labels_and_predictions(model, val_dataset, validation_steps)
+
+ # Calculate the Partial AUC
+ partial_auc = score(y_val, y_pred)
+ partial_aucs.append(partial_auc)
+
+ print("Plotting the training metrics...")
+ # Plot the training history
+ plot_training_history(history, model_name)
+
+ model.save_weights(os.path.join(models_dir, f'{model_name}_{run_id}_weights.h5'))
+
+ print(f"Train-validation-split partial AUC: {partial_auc}")
+
+ print(f'Finished training model {model_name}')
+ return model
+
+
+
+ Single model training pipeline
+
+
def train_single_model(model_name, **kwargs):
+ """Train a unique model from scratch."""
+
+ print(f"Training single model: {model_name}\n")
+
+ model = ModelFactory.create_model('single', pretrained=False, model_names=[model_name], **kwargs)
+ model = training_pipeline(model_name, model=model, **kwargs)
+
+ return model
+
+
+
+
+ Training
+
+ Utils
+
+
class TrainConfig:
+ def __init__(self, strategy, run_id, batch_size, val_batch_size, cache_in_memory,
+ freeze_base_model, shuffle_dataset_at_each_call, train_file_pattern,
+ do_fine_tuning, oversample_minority_class, initial_epochs, fine_tune_epochs,
+ img_shape, augment_train_data, use_cross_validation, dropout_rate, l2_lambda,
+ num_tabular_features, use_tabular_data, kernel_initializer, activation,
+ execution_env):
+ self.strategy = strategy
+ self.run_id = run_id
+ self.batch_size = batch_size
+ self.val_batch_size = val_batch_size
+ self.cache_in_memory = cache_in_memory
+ self.freeze_base_model = freeze_base_model
+ self.shuffle_dataset_at_each_call = shuffle_dataset_at_each_call
+ self.train_file_pattern = train_file_pattern
+ self.do_fine_tuning = do_fine_tuning
+ self.oversample_minority_class = oversample_minority_class
+ self.initial_epochs = initial_epochs
+ self.fine_tune_epochs = fine_tune_epochs
+ self.img_shape = img_shape
+ self.augment_train_data = augment_train_data
+ self.use_cross_validation = use_cross_validation
+ self.dropout_rate = dropout_rate
+ self.l2_lambda = l2_lambda
+ self.num_tabular_features = num_tabular_features
+ self.use_tabular_data = use_tabular_data
+ self.kernel_initializer = kernel_initializer
+ self.activation = activation
+ self.execution_env = execution_env
+
+
+def run_pipeline(pipeline_func, config: TrainConfig, **specific_params):
+ """General pipeline function for training models with a TrainConfig object."""
+
+ # Clean the session and retrieve initial memory
+ initial_memory_usage = reset_session_and_get_memory()
+ log_memory_usage("Initial", initial_memory_usage)
+
+ # Ensure strategy is initialized
+ if config.strategy is None:
+ raise ValueError("Strategy must be set in the configuration.")
+
+ # Train the model using the passed function with the TrainConfig object
+ #with config.strategy.scope():
+ model = pipeline_func(strategy=config.strategy, run_id=config.run_id, batch_size=config.batch_size,
+ cache_in_memory=config.cache_in_memory, freeze_base_model=config.freeze_base_model,
+ shuffle_dataset_at_each_call=config.shuffle_dataset_at_each_call,
+ do_fine_tuning=config.do_fine_tuning, train_file_pattern=config.train_file_pattern,
+ oversample_minority_class = config.oversample_minority_class,
+ initial_epochs = config.initial_epochs, val_batch_size = config.val_batch_size,
+ fine_tune_epochs = config.fine_tune_epochs,
+ img_shape = config.img_shape,
+ augment_train_data = config.augment_train_data,
+ use_cross_validation = config.use_cross_validation,
+ use_tabular_data = config.use_tabular_data,
+ activation = config.activation,
+ dropout_rate = config.dropout_rate,
+ l2_lambda = config.l2_lambda,
+ num_tabular_features = config.num_tabular_features,
+ kernel_initializer = config.kernel_initializer,
+ execution_env = config.execution_env,
+ **specific_params)
+ # Retrieve final memory usage
+ final_memory_usage = get_memory_usage()
+ log_memory_usage("Final", final_memory_usage)
+
+ return model
+
+def run_model_evaluation(model):
+ evaluate_model(model, val_dataset_small_formatted, validation_steps_small, dataset_name='small validation dataset')
+ evaluate_model(model, val_dataset_big_formatted, validation_steps_big, dataset_name='big validation dataset')
+ evaluate_model(model, train_val_dataset_formatted, train_val_dataset_steps, dataset_name='train_val dataset')
+
+
+
+ Initialization
+
+
# Instantiate TrainConfig with common parameters
+train_config = TrainConfig(
+ strategy=strategy,
+ run_id=run_id,
+ batch_size=batch_size,
+ val_batch_size=val_batch_size,
+ cache_in_memory=cache_in_memory,
+ freeze_base_model=freeze_base_model,
+ shuffle_dataset_at_each_call=True,
+ train_file_pattern=train_file_pattern,
+ do_fine_tuning = do_fine_tuning,
+ oversample_minority_class = oversample_minority_class,
+ initial_epochs = initial_epochs,
+ fine_tune_epochs = fine_tune_epochs,
+ img_shape = img_shape,
+ augment_train_data = augment_train_data,
+ use_cross_validation = use_cross_validation,
+ dropout_rate = dropout_rate,
+ l2_lambda = l2_lambda,
+ num_tabular_features = num_tabular_features,
+ use_tabular_data = use_tabular_data,
+ kernel_initializer = kernel_initializer,
+ activation = activation,
+ execution_env = execution_env
+)
+
+
+
+ Data loading
+
+
# Load dataset once and cache
+if run_evaluations:
+ train_val_dataset, train_val_dataset_steps = load_tfrecord_dataset(
+ train_file_pattern,
+ batch_size=batch_size,
+ is_training=False,
+ cache_in_memory=False)
+ train_val_dataset_formatted = format_input_dataset(train_val_dataset)
+ # Verify cache
+ #!ls -al {cache_dir}
+ # Load big validation dataset
+ val_dataset_small, validation_steps_small = load_tfrecord_dataset(
+ f'{tf_records_eval_dir}/*.tfrecord',
+ batch_size=eval_batch_size,
+ is_training=False,
+ cache_in_memory=False)
+ val_dataset_small_formatted = format_input_dataset(val_dataset_small)
+
+ # Load big validation dataset
+ val_dataset_big, validation_steps_big = load_tfrecord_dataset(
+ f'{tf_records_val_dir}/*.tfrecord',
+ batch_size=eval_batch_size * 2,
+ is_training=False,
+ cache_in_memory=False)
+ val_dataset_big_formatted = format_input_dataset(val_dataset_big)
+
+
+
+ Run
+
+
if model_type == 'single_model' or train_all_models:
+ #strategy = tf.distribute.OneDeviceStrategy(device="/gpu:0")
+ #strategy = tf.distribute.get_strategy()
+ #with strategy.scope():
+ single_model = run_pipeline(
+ pipeline_func=train_single_model, # Function to train the model
+ config=train_config, # Common configuration object
+ #model_name='res_net_50'
+ model_name=model_name
+ )
+ if inference_model_name == 'single_model':
+ inference_model = single_model
+
+
+
+ Evaluate on unseen and train_val datasets
+
+
if model_type == 'single_model' or train_all_models:
+ print('Evaluating single model:', model_name)
+ run_model_evaluation(single_model)
+
+
+
+
+
+ Ensembling models
+
+ Trained ensemble model
+
+ Trained ensemble training pipeline
+
+
def train_compact_ensemble_model(model_names=['efficientnetb0', 'resnet50', 'vgg16'], **kwargs):
+ """Train a unique ensmeble model from scratch."""
+
+ print(f"Training compact ensemble model")
+
+ compact_ensemble_model = ModelFactory.create_model('ensemble', model_names, pretrained=False, **kwargs)
+
+ compact_ensemble_model = training_pipeline(compact_ensemble_model.model_name, model=compact_ensemble_model, **kwargs)
+ return compact_ensemble_model
+
+
+
+ Run
+
+
if model_type == 'compact_ensemble_model' and train_all_models:
+ compact_ensemble_model = run_pipeline(
+ pipeline_func=train_compact_ensemble_model, # Function to train ensemble model
+ config=train_config, # Common configuration object
+ #model_names=['densenet201','resnet50', 'resnet152v2','vgg19'],
+ #weights=[0.41, 0.35, 0.14, 0.1],
+ #weights=[0.25, 0.25, 0.25, 0.25],
+ model_names=['res_net_152_v2', 'dense_net_201'],
+ weights=[0.55, 0.45]
+ )
+
+
+
+ Evaluation
+
+
if model_type == 'compact_ensemble_model' or train_all_models:
+ print('Evaluating compact ensemble model')
+ run_model_evaluation(compact_ensemble_model)
+
+ if inference_model_name == 'compact_ensemble_model':
+ inference_model = compact_ensemble_model
+
+
+
+
+ Pretrained ensemble model
+
+ Pretrained ensemble pipeline
+
+
def train_pretrained_ensemble(model_names, train_individuals=True, **kwargs):
+ """Train a unique model from scratch."""
+ print('Training pretrained ensemble model')
+
+ run_id = kwargs.get('run_id', None)
+
+ # Train individual models if needed
+ train_individual_models(model_names=model_names, train_individuals=train_individuals, **kwargs)
+
+ pretrained_ensemble = ModelFactory.create_model(model_type='ensemble', model_names=model_names, pretrained=True, **kwargs)
+
+ # Saving the weights as it is not trained via the commom pipeline
+ pretrained_ensemble.save_weights(os.path.join(models_dir, f'{pretrained_ensemble.model_name}_{run_id}_weights.h5'))
+
+ print('Finished creating the pretrained_ensemble', pretrained_ensemble.model_name)
+
+ return pretrained_ensemble
+
+def train_individual_models(model_names, train_individuals=True, **kwargs):
+ """Train individual models if their weights do not already exist."""
+ for model_name in model_names:
+ print('Training individual model', model_name)
+ weights_path = os.path.join('models_dir', f'{model_name}_{kwargs.get("run_id", "default")}_weights.h5')
+ if (not os.path.exists(weights_path)) and train_individuals:
+ print(f"Training {model_name} since weights are not found.")
+ train_single_model(model_name, **kwargs)
+ else:
+ print(f"Weights for {model_name} already exist at {weights_path}, skipping training.")
+
+
+
+
+ Run
+
+
if model_type == 'pretrained_ensemble_model' or train_all_models:
+ pretrained_ensemble_model = run_pipeline(
+ pipeline_func=train_pretrained_ensemble, # Function to train ensemble model
+ config=train_config, # Common configuration object
+ model_names=['dense_net_201','res_net_50', 'res_net_152_v2','vgg_19'],
+ weights=[0.41, 0.35, 0.14, 0.1],
+ train_individuals=train_individuals,
+ mode='weighted'
+ )
+ if inference_model_name == 'pretrained_ensemble_model':
+ inference_model = pretrained_ensemble_model
+
+
+
+ Evaluation
+
+
if (model_type == 'pretrained_ensemble_model' or train_all_models) and run_evaluations:
+ print('Evaluating pretrained ensemble model')
+ run_model_evaluation(pretrained_ensemble_model)
+
+
+
+
+
+ Inference
+
+ Data preparation
+
+
# Load and preprocess images using fused operations for I/O and preprocessing
+@tf.function
+def preprocess_image(image_bytes, img_height, img_width):
+ """Preprocess image loaded from HDF5."""
+ image = tf.io.decode_jpeg(image_bytes, channels=3)
+ image = tf.image.resize(image, [img_height, img_width])
+ image = image / 255.0 # Normalize to [0, 1]
+ return image
+
+def load_image_from_hdf5(key, h5_file):
+ """Function to load image bytes from HDF5 file."""
+ try:
+ image_bytes = h5_file[key][()]
+ if image_bytes is None or len(image_bytes) == 0:
+ raise ValueError(f"Empty image data for key: {key}")
+ return image_bytes
+ except Exception as e:
+ raise ValueError(f"Failed to load image for key {key}. Error: {str(e)}")
+
+def hdf5_image_generator(h5_file, keys):
+ """Python generator to load images from HDF5 file."""
+ for key in keys:
+ try:
+ image_bytes = load_image_from_hdf5(key, h5_file)
+ yield image_bytes
+ except Exception as e:
+ print(f"Error loading image for key {key}: {e}")
+ continue
+
+def prepare_inference_dataset(image_paths, data_source='hdf5', use_tabular_data = use_tabular_data, tabular_data_paths=None,
+ batch_size=batch_size, cache_data=False, preprocessor_path=preprocessor_path, target_img_shape=None):
+ """
+ Prepare the dataset for inference, either loading images from a file path or from an .hdf5 file.
+
+ Args:
+ image_source: Path to the images or .hdf5 file containing the images.
+ tabular_data_paths: Path to the tabular data, if any.
+ preprocessor: Preprocessing function for tabular data.
+ batch_size: Batch size for the dataset.
+ cache_data: Whether to cache the dataset.
+ data_source: 'path' if loading from image paths, 'hdf5' if loading from an .hdf5 file.
+
+ Returns:
+ A tf.data.Dataset ready for inference.
+ """
+
+ # Loading images from file paths
+ if data_source == 'path':
+ # Fused operations for loading and preprocessing images from file paths
+ image_paths = glob(image_paths)
+ image_dataset = tf.data.Dataset.from_tensor_slices(image_paths)
+
+ # Load and preprocess the images
+ image_dataset = image_dataset.map(preprocess_image, num_parallel_calls=tf.data.AUTOTUNE)
+
+ keys = [os.path.basename(path).split('.')[0] for path in image_paths]
+
+ # Loading images from an .hdf5 file
+ elif data_source == 'hdf5':
+ # Open the .hdf5 file and load the image dataset
+ # Open the HDF5 file
+ h5_file = h5py.File(image_paths, 'r')
+ keys = list(h5_file.keys())
+
+ # Create a generator for loading images from the HDF5 file
+ def image_gen():
+ return hdf5_image_generator(h5_file, keys)
+
+ # Create a TensorFlow Dataset from the generator
+ image_dataset = tf.data.Dataset.from_generator(
+ image_gen,
+ output_signature=tf.TensorSpec(shape=(), dtype=tf.string)
+ )
+
+ # Map preprocessing function over the dataset
+ image_dataset = image_dataset.map(
+ lambda image_bytes: preprocess_image(image_bytes, img_height, img_width),
+ num_parallel_calls=tf.data.AUTOTUNE
+ )
+
+ # Raise an error if the data_source is not recognized
+ else:
+ raise ValueError("data_source must be either 'path' or 'hdf5'")
+
+ # If tabular data is provided, load and zip it with the image dataset
+ if use_tabular_data:
+ # Extract filenames without extensions
+ inference_images = keys
+ preprocessor = load_preprocessor(preprocessor_path)
+ tabular_data = format_and_encode_tabular_data([tabular_data_paths], inference_images, preprocessor)
+
+ tabular_data = tf.data.Dataset.from_tensor_slices(tabular_data)
+
+ # Combine image and tabular data efficiently
+ combined_dataset = tf.data.Dataset.zip((image_dataset, tabular_data))
+ else:
+ combined_dataset = image_dataset
+
+ # Batch the dataset and prefetch to load data asynchronously
+ dataset_for_inference = combined_dataset.batch(batch_size * strategy.num_replicas_in_sync,
+ drop_remainder=False).prefetch(tf.data.AUTOTUNE)
+ if cache_data:
+ dataset_for_inference = dataset_for_inference.cache()
+
+ return dataset_for_inference, keys
+
+
+
+ Pipeline
+
+
# Run inference using fused operations, mixed precision, and optimized parallel data loading
+def inference_pipeline(model, image_paths, use_tabular_data, tabular_data_paths=None, batch_size=batch_size,
+ cache_data=False, use_mixed_precision=False, preprocessor_path=preprocessor_path,
+ submission_file_path=None, target_img_shape=None):
+ use_mixed_precision=False
+ if use_mixed_precision:
+ enable_mixed_precision()
+
+ # Prepare the dataset
+ dataset_for_inference, images_ids= prepare_inference_dataset(image_paths=image_paths, use_tabular_data=use_tabular_data,
+ cache_data=cache_data, tabular_data_paths=tabular_data_paths,
+ batch_size=batch_size, preprocessor_path=preprocessor_path,
+ target_img_shape=target_img_shape)
+ all_predictions = []
+ # Run inference and get predictions
+ for batch in dataset_for_inference:
+ if use_tabular_data:
+ image_batch, tabular_batch = batch
+ predictions = model.predict_on_batch([image_batch, tabular_batch]).ravel()
+ else:
+ image_batch = batch
+ predictions = model.predict_on_batch(image_batch).ravel()
+ all_predictions.append(predictions)
+
+ # Save predictions to CSV
+ predictions_df = pd.DataFrame(zip(images_ids, predictions), columns=['isic_id', 'target'])
+ predictions_df.to_csv(submission_file_path, index=False)
+
+ # Process predictions
+ return predictions_df, dataset_for_inference
+
+
+
+ Initialization
+
+
preprocessor_path = preprocessor_path
+inference_model_path = inference_model_path
+project_dir = f'{input_dir}/isic-2024-challenge'
+inference_images_path = f'{project_dir}/test-image.hdf5'
+inference_tabular_data_path = f'{project_dir}/test-metadata.csv'
+use_tabular_data = use_tabular_data
+cache_data=True
+submission_file_path = f'{output_dir}/submission.csv'
+target_img_shape=img_shape
+run_inference_use_all_models = False
+
+
+
+ Run
+
+
# Run inference with optimizations and mixed precision enabled
+if run_inference:
+ inference_models = [single_model, compact_ensemble_model, pretrained_ensemble_model] if run_inference_use_all_models else [inference_model]
+ predictions_dfs = []
+ for inf_model in inference_models:
+ print('Running inference for model...', inf_model.model_name)
+ predictions_df, dataset_for_inference = inference_pipeline(model=inf_model, image_paths=inference_images_path, use_tabular_data=use_tabular_data,
+ tabular_data_paths=inference_tabular_data_path, batch_size=batch_size, cache_data=cache_data,
+ use_mixed_precision=False, preprocessor_path=preprocessor_path, submission_file_path=submission_file_path,
+ target_img_shape=target_img_shape)
+ predictions_dfs.append(predictions_df)
+ print('Finished running inference for model', inf_model.model_name)
+
+
+
+
+
+ Annexe
+
+
# Limit intra-op parallelism (operations within a single layer)
+#export OMP_NUM_THREADS=100
+# Limit inter-op parallelism (parallel operations across layers)
+#export TF_NUM_INTEROP_THREADS=100
+# Limit intra-op parallelism (for operations like matrix multiplications)
+#tf.config.threading.set_intra_op_parallelism_threads(100)
+# Limit inter-op parallelism (for parallel operations across layers)
+#tf.config.threading.set_inter_op_parallelism_threads(100)
+#import os
+#os.environ["CUDA_VISIBLE_DEVICES"] = "-1" # Disables GPU by setting this environment variable
+#tf.config.list_physical_devices('GPU')
+
+
+
visualization = False
+if visualization:
+ eval_dataset_mapped = eval_dataset.map(lambda x, y, z: ((x, y), z))
+ augmented_dataset = apply_augmentations(eval_dataset_mapped)
+ imgs = augmented_dataset.as_numpy_iterator()
+ imgs_t = imgs.next()[0][0]
+ plt.figure(figsize=(50, 50))
+ #imgs_t = imgs.next()[0]
+ for i in range(0, 63):
+ plt.subplot(12, 12, i+1)
+ plt.xticks([])
+ plt.yticks([])
+ plt.imshow(imgs_t[i])
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/notebooks/isic-2024_kaggle.ipynb b/public/notebooks/isic-2024_kaggle.ipynb
new file mode 100644
index 0000000..db0fac5
--- /dev/null
+++ b/public/notebooks/isic-2024_kaggle.ipynb
@@ -0,0 +1,4857 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "gTvrxbQdeCRM"
+ },
+ "source": [
+ "# Initialization"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "es8kyxsCe2rj",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "import time\n",
+ "# Unique indentifier specific to this execution\n",
+ "run_id = int(time.time())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "G6DGt7m9Mdey"
+ },
+ "source": [
+ "## Installations"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "CLPR-TDUyM_1",
+ "outputId": "ec7aa160-b7c7-4053-a6d1-77a261747664",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "#! pip install tensorflow==2.15.0\n",
+ "! pip install -q imblearn keras-tuner tensorflow-addons keras-cv memory_profiler"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "qRXYfz1Fe2rj"
+ },
+ "source": [
+ "## Global variables"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "MRVQhcz0puop",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Database configuration: Choose between 'dagshub_storage', 'google_drive', 'local'\n",
+ "database = 'local' # Current choice: local\n",
+ "\n",
+ "# Execution mode: Choose between 'CPU' or 'GPU'\n",
+ "execution_mode = 'GPU' # Current choice: GPU\n",
+ "\n",
+ "# Execution environment: Choose between 'colab', 'mac'\n",
+ "execution_env = 'kaggle' # Current choice: colab\n",
+ "\n",
+ "# Experimentation mode: Choose between 'test' or 'prod'\n",
+ "experimentation_mode = 'prod' # Current choice: test\n",
+ "\n",
+ "# Training and Inference settings\n",
+ "train_all_models = False # Train all models\n",
+ "run_inference = True # Run inference\n",
+ "run_inference_use_all_models = True # Run inference on all models\n",
+ "model_type = 'pretrained_ensemble_model' # Type of model to train, choose between 'single_model', 'pretrained_ensemble_model', 'compact_ensemble_model'\n",
+ "inference_model_name = model_type # Use the model_type as the inference model name\n",
+ "\n",
+ "# Dataset settings\n",
+ "download_raw_datasets = False # Do not download raw datasets\n",
+ "download_refined_training_datasets = True if execution_env == 'colab' else False # Download refined datasets if running on Colab\n",
+ "dataset_size = 20000 # Set dataset size to 40,000 samples\n",
+ "create_new_training_datasets = False # Do not create new training datasets"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "6MvxojqUWoi5"
+ },
+ "source": [
+ "## Execution strategy"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "cU1NZXVce2rj",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "import tensorflow as tf\n",
+ "from tensorflow.keras import mixed_precision\n",
+ "import warnings\n",
+ "\n",
+ "def limit_memory_growth():\n",
+ " gpu_devices = tf.config.experimental.list_physical_devices('GPU')\n",
+ " try:\n",
+ " for gpu in gpu_devices:\n",
+ " tf.config.experimental.set_memory_growth(gpu, True)\n",
+ " except Exception as e:\n",
+ " print('Can not set memory growth', e)\n",
+ "\n",
+ "def disable_eager_execution():\n",
+ " tf.compat.v1.disable_eager_execution()\n",
+ "\n",
+ "def enable_xla():\n",
+ " tf.config.optimizer.set_jit(True)\n",
+ "\n",
+ "def disable_gpu():\n",
+ " tf.config.set_visible_devices([], 'GPU')\n",
+ " visible_devices = tf.config.get_visible_devices()\n",
+ " for device in visible_devices:\n",
+ " assert device.device_type != 'GPU'\n",
+ "\n",
+ "def enable_mixed_precision():\n",
+ " policy = mixed_precision.Policy('mixed_float16')\n",
+ " mixed_precision.set_global_policy(policy)\n",
+ " print(\"Mixed precision enabled: \", mixed_precision.global_policy())\n",
+ "\n",
+ "# Suppress all warnings in the notebook\n",
+ "warnings.filterwarnings('ignore')\n",
+ "\n",
+ "limit_memory_growth()\n",
+ "#enable_mixed_precision()\n",
+ "#tf.debugging.set_log_device_placement(True)\n",
+ "#tf.config.set_visible_devices([], 'GPU')\n",
+ "enable_xla()\n",
+ "tf.config.optimizer.set_experimental_options({\n",
+ " \"memory_optimization\": True\n",
+ "})"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "_naYJoLTdEID",
+ "outputId": "0b6da021-6eda-48f9-913f-e4ff4e13d564",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def check_tpu_initialized():\n",
+ " \"\"\"Check if TPU is already initialized.\"\"\"\n",
+ " tpu_devices = tf.config.list_logical_devices('TPU')\n",
+ " if len(tpu_devices) > 0:\n",
+ " print(f\"TPU is already initialized with {len(tpu_devices)} logical devices.\")\n",
+ " return True\n",
+ " else:\n",
+ " print(\"TPU is not initialized.\")\n",
+ " return False\n",
+ "\n",
+ "def set_tpu_strategy(force=False):\n",
+ " \"\"\"Set the TPU strategy if TPU is initialized.\"\"\"\n",
+ " if check_tpu_initialized() and not force:\n",
+ " try:\n",
+ " resolver = tf.distribute.cluster_resolver.TPUClusterResolver() # TPU detection\n",
+ " strategy = tf.distribute.TPUStrategy(resolver)\n",
+ "\n",
+ " print(\"Using TPU strategy.\")\n",
+ " return strategy\n",
+ " except Exception as e:\n",
+ " print(f\"Error while setting TPU strategy: {e}\")\n",
+ " return None\n",
+ " else:\n",
+ " try:\n",
+ " print(\"Trying to initialize TPU.\")\n",
+ " resolver = tf.distribute.cluster_resolver.TPUClusterResolver()\n",
+ " tf.config.experimental_connect_to_cluster(resolver)\n",
+ " tf.tpu.experimental.initialize_tpu_system(resolver)\n",
+ " strategy = tf.distribute.TPUStrategy(resolver)\n",
+ " print(\"TPU initialized and TPU strategy set.\")\n",
+ " return strategy\n",
+ " except ValueError:\n",
+ " print(\"No TPU devices found.\")\n",
+ " return None\n",
+ "\n",
+ "strategy = set_tpu_strategy(force = execution_env == 'colab')\n",
+ "running_on_tpu = strategy is not None\n",
+ "\n",
+ "if strategy is None:\n",
+ " # For GPUs or CPU\n",
+ " if tf.config.list_physical_devices('GPU') and execution_mode == 'GPU':\n",
+ " limit_memory_growth()\n",
+ " strategy = tf.distribute.MirroredStrategy()\n",
+ " enable_mixed_precision() # Enable mixed precision policy\n",
+ " print(\"Running on GPU\")\n",
+ "\n",
+ " else:\n",
+ " strategy = tf.distribute.get_strategy()\n",
+ " print(\"Running on CPU\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "HrpgCsE546QX"
+ },
+ "source": [
+ "## Librairies import"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "xVoeHCS02e2Y",
+ "outputId": "6d386b77-58c4-416c-a45b-00a070641588",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Core Libraries\n",
+ "import os\n",
+ "import gc\n",
+ "import re\n",
+ "import h5py\n",
+ "import time\n",
+ "import random\n",
+ "import copy\n",
+ "import psutil\n",
+ "import hashlib\n",
+ "import traceback\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "from glob import glob\n",
+ "from collections import defaultdict\n",
+ "from multiprocessing import Pool\n",
+ "from concurrent.futures import ThreadPoolExecutor\n",
+ "\n",
+ "# TensorFlow and Keras Imports\n",
+ "import tensorflow.keras.backend as K\n",
+ "from tensorflow.keras import layers, Model, Input, regularizers, optimizers, callbacks\n",
+ "from tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping, ModelCheckpoint\n",
+ "from tensorflow.keras.layers.experimental import preprocessing\n",
+ "from tensorflow.keras.utils import register_keras_serializable\n",
+ "\n",
+ "# TensorFlow Add-ons\n",
+ "import tensorflow_addons as tfa\n",
+ "\n",
+ "# Keras Tuner for Hyperparameter Optimization\n",
+ "import keras_tuner as kt\n",
+ "from keras_tuner import HyperModel\n",
+ "from keras_tuner.tuners import RandomSearch\n",
+ "\n",
+ "# TensorFlow Pretrained Models\n",
+ "from tensorflow.keras.applications import (\n",
+ " EfficientNetB0, EfficientNetB4, EfficientNetB7,\n",
+ " DenseNet121, DenseNet169, DenseNet201,\n",
+ " InceptionV3, InceptionResNetV2,\n",
+ " ResNet50, ResNet101, ResNet152V2,\n",
+ " VGG16, VGG19, Xception\n",
+ ")\n",
+ "\n",
+ "# Data Oversampling\n",
+ "from imblearn.over_sampling import SMOTE\n",
+ "\n",
+ "# Scikit-Learn Utilities\n",
+ "from sklearn.metrics import (\n",
+ " roc_curve, roc_auc_score, auc,\n",
+ " accuracy_score, classification_report, confusion_matrix\n",
+ ")\n",
+ "from sklearn.model_selection import StratifiedShuffleSplit\n",
+ "\n",
+ "# Memory Profiling\n",
+ "from memory_profiler import memory_usage\n",
+ "\n",
+ "# Visualization Libraries\n",
+ "import matplotlib.pyplot as plt\n",
+ "import seaborn as sns\n",
+ "sns.set()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "FsYks2fL5YFs"
+ },
+ "source": [
+ "## Database setting"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "YP8fe04oqHI1",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "if download_refined_training_datasets:\n",
+ " if database == 'local':\n",
+ " %pip install -q dagshub\n",
+ " import dagshub.colab\n",
+ " DAGSHUB_REPO = dagshub.colab.login()\n",
+ " elif database == 'google_drive':\n",
+ " from google.colab import drive\n",
+ " drive.mount('/content/drive')\n",
+ " ROOT_DB_DIR='/content/drive/MyDrive/documents_travail'\n",
+ " else:\n",
+ " %pip install -q dagshub\n",
+ " import dagshub.colab\n",
+ " DAGSHUB_REPO = dagshub.colab.login()\n",
+ " mount_path = dagshub.storage.mount(DAGSHUB_REPO, cache=True)\n",
+ " print(f'Mount path: {mount_path}')\n",
+ " ROOT_DB_DIR=f'/content/{mount_path}'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "HdkcJ8R-gQZ4"
+ },
+ "source": [
+ "## Training variables"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "7EJe_orxlYdY",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Define root directories based on the execution environment\n",
+ "if execution_env == 'mac': \n",
+ " ROOT_DB_DIR = '/Users/amadou/local/datalab'\n",
+ "elif execution_env == 'kaggle': \n",
+ " ROOT_DB_DIR = '/kaggle/'\n",
+ "else:\n",
+ " ROOT_DB_DIR = '/content'\n",
+ " \n",
+ "input_dir = f'{ROOT_DB_DIR}/input'\n",
+ "output_dir = f'{ROOT_DB_DIR}/working'\n",
+ "cache_dir = f'{output_dir}/tf_cache' # Temporary cache directory for TensorFlow\n",
+ "project_dir = f\"{output_dir}/isic_2024\" # Project-specific directory\n",
+ "tmp_dir = f'{output_dir}/tmp' # Temporary files directory\n",
+ "models_dir = f'{output_dir}/models' # Directory to save model files\n",
+ "pretrained_models_dir = f'{input_dir}/pretrained/tensorflow2/default/1/'\n",
+ "\n",
+ "# Define dataset directories\n",
+ "datasets_dir = f'{input_dir}/datasets' # Root dataset directory\n",
+ "training_dir = f'{datasets_dir}/training' # Training data directory\n",
+ "training_metadata_dir = f'{training_dir}/metadata' # Metadata for training data\n",
+ "\n",
+ "# Define directories for training data (positives and negatives)\n",
+ "train_dir = f'{datasets_dir}/training/train'\n",
+ "train_dir_pos = f'{train_dir}/positives' # Positive training samples\n",
+ "train_dir_negs = f'{train_dir}/negatives' # Negative training samples\n",
+ "\n",
+ "# Define validation and evaluation data directories (positives and negatives)\n",
+ "val_dir = f'{datasets_dir}/training/val'\n",
+ "val_dir_pos = f'{val_dir}/positives' # Positive validation samples\n",
+ "val_dir_negs = f'{val_dir}/negatives' # Negative validation samples\n",
+ "\n",
+ "eval_dir = f'{datasets_dir}/training/eval'\n",
+ "eval_dir_pos = f'{eval_dir}/positives' # Positive evaluation samples\n",
+ "eval_dir_negs = f'{eval_dir}/negatives' # Negative evaluation samples\n",
+ "\n",
+ "# Define paths for inference\n",
+ "inference_dir = f'{project_dir}/inference' # Inference-related directory\n",
+ "inference_model_path = os.path.join(models_dir, 'inference_model.h5') # Model for inference\n",
+ "inference_images_dir = f'{inference_dir}/images/hdf5' # Directory to source inference images\n",
+ "inference_images_path = f'{inference_images_dir}/test-image.hdf5' # HDF5 file with test images\n",
+ "inference_metadata_dir = f'{inference_dir}/metadata' # Directory for inference metadata\n",
+ "inference_tabular_data_path = os.path.join(inference_metadata_dir, 'test-metadata.csv') # Test metadata for tabular data\n",
+ "preprocessor_path = f'{inference_dir}/preprocessor_{run_id}.pkl' # Preprocessor for inference\n",
+ "\n",
+ "# Define TFRecord directories\n",
+ "tf_records_dir = f'{input_dir}/tf_records/{dataset_size}' if experimentation_mode == 'test' else f'{input_dir}/sample-20k/sample_20k_kaggle'\n",
+ "\n",
+ "tf_records_train_val_dir = f'{tf_records_dir}/train' # TFRecords for training\n",
+ "tf_records_val_dir = f'{tf_records_dir}/validation' # TFRecords for validation\n",
+ "tf_records_eval_dir = f'{tf_records_dir}/evaluation' # TFRecords for evaluation\n",
+ "oversample_persisted_data = False # Oversample persisted data when creating tf_records\n",
+ "\n",
+ "# Image and model settings\n",
+ "img_height = 148 # Height of the input images\n",
+ "img_width = 148 # Width of the input images\n",
+ "img_channels = 3 # Number of color channels\n",
+ "img_shape = (img_height, img_width, img_channels) # Shape of the input images (H x W x C)\n",
+ "image_size = img_height # Image size, set to height for consistency\n",
+ "\n",
+ "# Training and evaluation settings\n",
+ "use_cross_validation = False # Whether to use cross-validation\n",
+ "augment_train_data = True # Augment training data\n",
+ "use_tabular_data = False # Whether to include tabular data in the model\n",
+ "force_cache = True # Force caching of data\n",
+ "cache_in_memory = True if execution_env == 'colab' else False # Cache in memory only for Colab\n",
+ "do_fine_tuning = True # Whether to fine-tune the model\n",
+ "oversample_minority_class = True # Oversample the minority class in imbalanced datasets\n",
+ "initial_epochs = 1 if experimentation_mode == 'test' else 5 # Initial epochs for training\n",
+ "fine_tune_epochs = 1 if experimentation_mode == 'test' else 80 # Epochs for fine-tuning\n",
+ "freeze_base_model = False # Whether to freeze the base model during training\n",
+ "\n",
+ "# Dataset buffering and shuffling\n",
+ "buffer_size = 1000 if execution_env == 'colab' else 700 # Buffer size for data shuffling\n",
+ "shuffle_train_val_at_each_call = True # Shuffle training/validation data at each call\n",
+ "train_file_pattern = f'{tf_records_train_val_dir}/*.tfrecord' # Pattern to match TFRecord files\n",
+ "\n",
+ "# Model and training configurations\n",
+ "model_name = 'res_net_50' # Name of the model to be used (ResNet50)\n",
+ "train_individuals = False # Whether to train individual models of the pretrained ensemble\n",
+ "run_evaluations = False\n",
+ "\n",
+ "# Batch size settings depending on environment and mode\n",
+ "if experimentation_mode == 'test':\n",
+ " batch_size = 16 if execution_env == 'colab' else 16\n",
+ " val_batch_size = 16 if execution_env == 'colab' else 32\n",
+ " eval_batch_size = 4 if execution_env == 'colab' else 32\n",
+ "else:\n",
+ " batch_size = 128 if execution_env == 'colab' else 128 # Batch size varies between Colab and local\n",
+ " val_batch_size = 128 if execution_env == 'colab' else 128 # Validation batch size\n",
+ " eval_batch_size = 128 if execution_env == 'colab' else 128 # Evaluation batch size\n",
+ "\n",
+ "# Model creation settings\n",
+ "dropout_rate = 0.5\n",
+ "l2_lambda = 0.02\n",
+ "num_tabular_features = 14\n",
+ "kernel_initializer = 'he_normal'\n",
+ "activation = 'swish'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "cHzmyIyutgZI",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "if create_new_training_datasets:\n",
+ " !rm -rf {tf_records_dir}\n",
+ " \n",
+ "!mkdir -p {tmp_dir}\n",
+ "!mkdir -p {sample_dir}\n",
+ "!mkdir -p {models_dir}\n",
+ "!mkdir -p {tf_records_train_val_dir}\n",
+ "!mkdir -p {tf_records_val_dir}\n",
+ "!mkdir -p {tf_records_eval_dir}\n",
+ "!mkdir -p {inference_dir}\n",
+ "!mkdir -p {inference_images_dir}\n",
+ "!mkdir -p {inference_metadata_dir}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "EX8uhVi3g--9"
+ },
+ "source": [
+ "# Data preparation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "QtvwrF9uozL3"
+ },
+ "source": [
+ "## Preparation utils"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "rumQlSCcCHpX"
+ },
+ "source": [
+ "### Formatting utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "U0_SQA_GCMRt",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Format all the metadatas to have the same structure\n",
+ "def format_tabular_data(tabular_data_paths):\n",
+ " tabular_dfs = []\n",
+ " for path in tabular_data_paths:\n",
+ " file_name = os.path.basename(path)\n",
+ " if file_name == 'train_metadata_2024.csv' or file_name == 'test-metadata.csv':\n",
+ " metadata_2024 = pd.read_csv(path, low_memory=False)\n",
+ " metadata_2024.fillna({'age_approx': metadata_2024.age_approx.median(), 'sex': 'unknown', 'anatom_site_general': 'unknown'}, inplace=True)\n",
+ " tabular_dfs.append(metadata_2024[common_columns])\n",
+ " elif file_name == 'train_metadata_2020.csv':\n",
+ " metadata_2020 = pd.read_csv(path, low_memory=False).rename(columns=dict(zip(selected_2019_2020_columns, common_columns)))\n",
+ " metadata_2020.fillna({'age_approx': metadata_2020.age_approx.median(), 'sex': 'unknown', 'anatom_site_general': 'unknown'}, inplace=True)\n",
+ " tabular_dfs.append(metadata_2020[common_columns])\n",
+ " elif file_name == 'train_metadata_2019.csv':\n",
+ " metadata_2019 = pd.read_csv(path, low_memory=False).rename(columns=dict(zip(selected_2019_2020_columns, common_columns)))\n",
+ " metadata_2019.fillna({'age_approx': metadata_2019.age_approx.median(), 'anatom_site_general': 'unknown'}, inplace=True)\n",
+ " tabular_dfs.append(metadata_2019[common_columns])\n",
+ " return tabular_dfs\n",
+ "\n",
+ "\n",
+ "# Format and encode tabular data\n",
+ "def format_and_encode_tabular_data(tabular_data_paths, train_images, preprocessor=None):\n",
+ " tf.print('Formatting and encoding tabular data...')\n",
+ " tabular_data = pd.concat(format_tabular_data(tabular_data_paths), axis=0)\n",
+ "\n",
+ " # Reindex tabular data to align with image IDs\n",
+ " train_tabular = tabular_data.set_index('isic_id').reindex(train_images).reset_index(drop=True)\n",
+ "\n",
+ " if preprocessor is None:\n",
+ " # Create the preprocessor if not provided (for training)\n",
+ " print('Creating a new preprocessor to encode tabular data')\n",
+ " preprocessor = fit_tabular_encoder(train_tabular)\n",
+ " save_preprocessor(preprocessor, preprocessor_path)\n",
+ " encoded_tabular_data = encode_tabular_data(train_tabular, preprocessor)\n",
+ " else:\n",
+ " print('Using provided preprocessor to encode tabular data')\n",
+ " encoded_tabular_data = encode_tabular_data(train_tabular, preprocessor)\n",
+ "\n",
+ " return encoded_tabular_data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "YkHXdiUvCP5x"
+ },
+ "source": [
+ "### Encoding utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "7UTcn1mnCTTe",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Fit the encoder for tabular data using dense format\n",
+ "def fit_tabular_encoder(tabular_data):\n",
+ " num_cols = ['age_approx']\n",
+ " one_hot_cols = ['anatom_site_general', 'sex']\n",
+ "\n",
+ " # Using dense format for OneHotEncoder\n",
+ " preprocessor = ColumnTransformer(\n",
+ " transformers=[\n",
+ " ('num', StandardScaler(), num_cols),\n",
+ " ('cat', OneHotEncoder(handle_unknown='ignore', sparse_output=False), one_hot_cols) # Dense output\n",
+ " ]\n",
+ " )\n",
+ " preprocessor.fit(tabular_data)\n",
+ " return preprocessor\n",
+ "\n",
+ "# Encode tabular data with dense format, called only after fit_tabular_encoder\n",
+ "# or directely for val and eval data\n",
+ "def encode_tabular_data(tabular_data, preprocessor):\n",
+ " # Use the preprocessor to transform the tabular data into dense format\n",
+ " return preprocessor.transform(tabular_data)\n",
+ "\n",
+ "# Save and load preprocessor for reuse between training and inference\n",
+ "def save_preprocessor(preprocessor, file_path):\n",
+ " with open(file_path, 'wb') as f:\n",
+ " pickle.dump(preprocessor, f)\n",
+ " print('Saved preprocessor at', file_path)\n",
+ "\n",
+ "def load_preprocessor(file_path):\n",
+ " with open(file_path, 'rb') as f:\n",
+ " preprocessor = pickle.load(f)\n",
+ " print('Loaded preprocessor from', file_path)\n",
+ " return preprocessor\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "cHEP-sikCmTm"
+ },
+ "source": [
+ "### Oversampling utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "HD9kzgYPCqqQ",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Optionally apply smote on the whole dataset to balance minority and majority\n",
+ "def smote_oversampling(image_paths, labels, tabular_data):\n",
+ " \"\"\"Apply SMOTE to the labels and tabular data, with synchronized image resampling.\"\"\"\n",
+ " tf.print(\"Applying SMOTE\")\n",
+ "\n",
+ " # Separate image paths, labels, and tabular data by class\n",
+ " class_0_indices = [i for i, label in enumerate(labels) if label == 0]\n",
+ " class_1_indices = [i for i, label in enumerate(labels) if label == 1]\n",
+ "\n",
+ " class_0_image_paths = [image_paths[i] for i in class_0_indices]\n",
+ " class_1_image_paths = [image_paths[i] for i in class_1_indices]\n",
+ "\n",
+ " if tabular_data is not None:\n",
+ " class_0_tabular = tabular_data.iloc[class_0_indices].reset_index(drop=True)\n",
+ " class_1_tabular = tabular_data.iloc[class_1_indices].reset_index(drop=True)\n",
+ " else:\n",
+ " class_0_tabular = class_1_tabular = None\n",
+ "\n",
+ " # Apply SMOTE to the labels and tabular data\n",
+ " smote = SMOTE(sampling_strategy='minority')\n",
+ "\n",
+ " if tabular_data is not None:\n",
+ " oversampled_tabular_data, oversampled_labels = smote.fit_resample(tabular_data, labels)\n",
+ " else:\n",
+ " dummy_data = np.zeros((len(labels), 1))\n",
+ " _, oversampled_labels = smote.fit_resample(dummy_data, labels)\n",
+ " oversampled_tabular_data = None\n",
+ "\n",
+ " # Prepare for synchronized resampling of image paths and tabular data\n",
+ " synthetic_image_paths = []\n",
+ " synthetic_tabular_data = []\n",
+ "\n",
+ " class_0_count = len(class_0_image_paths)\n",
+ " class_1_count = len(class_1_image_paths)\n",
+ "\n",
+ " for idx, label in enumerate(oversampled_labels):\n",
+ " if label == 0:\n",
+ " synthetic_image_paths.append(class_0_image_paths[idx % class_0_count])\n",
+ " if tabular_data is not None:\n",
+ " synthetic_tabular_data.append(class_0_tabular.iloc[idx % class_0_count].values)\n",
+ " else:\n",
+ " synthetic_image_paths.append(class_1_image_paths[idx % class_1_count])\n",
+ " if tabular_data is not None:\n",
+ " synthetic_tabular_data.append(class_1_tabular.iloc[idx % class_1_count].values)\n",
+ "\n",
+ " # Ensure coherence by converting synthetic_tabular_data to DataFrame if needed\n",
+ " if tabular_data is not None:\n",
+ " synthetic_tabular_data = pd.DataFrame(synthetic_tabular_data, columns=tabular_data.columns)\n",
+ " else:\n",
+ " synthetic_tabular_data = None\n",
+ "\n",
+ " # Return synchronized image paths, labels, and tabular data\n",
+ " return synthetic_image_paths, oversampled_labels, synthetic_tabular_data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "s5UM7BRuCvdr"
+ },
+ "source": [
+ "### Subset sampling utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "1PG6BIF5C6c6",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def sample_data(image_paths, labels, tabular_data, sampling_fraction):\n",
+ " \"\"\"Sample a subset of data for test purposes.\"\"\"\n",
+ " tf.print(\"Sampling data...\")\n",
+ " sampled_indices = np.random.choice(len(image_paths), int(len(image_paths) * sampling_fraction), replace=False)\n",
+ " image_paths = [image_paths[i] for i in sampled_indices]\n",
+ " labels = [labels[i] for i in sampled_indices]\n",
+ " if tabular_data is not None:\n",
+ " tabular_data = tabular_data[sampled_indices]\n",
+ " return image_paths, labels, tabular_data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "skNdprmGfRQ8"
+ },
+ "source": [
+ "### Serialization utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "UYpXfYlJfPz1",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Data writing to tf_records for later reuse if working in cloud\n",
+ "def write_dataset_to_tfrecords(image_paths, labels, tabular_data, output_prefix, num_workers, data_type='train'):\n",
+ " tf.print(f\"\\nStart writting {data_type} tfrecords...\")\n",
+ " chunk_size = len(image_paths) // num_workers\n",
+ " tf.print(f\"Number of workers: {num_workers}\")\n",
+ " tf.print(f\"Dataset size: {len(image_paths)}, chunk size: {chunk_size}\")\n",
+ "\n",
+ " args = [\n",
+ " (\n",
+ " image_paths[i:i + chunk_size],\n",
+ " labels[i:i + chunk_size],\n",
+ " tabular_data[i:i + chunk_size] if tabular_data is not None else None,\n",
+ " f'{output_prefix}_{i}.tfrecord'\n",
+ " )\n",
+ " for i in range(0, len(image_paths), chunk_size)\n",
+ " ]\n",
+ "\n",
+ " with ThreadPoolExecutor(max_workers=num_workers) as executor:\n",
+ " executor.map(write_tfrecord_single_batch, args)\n",
+ "\n",
+ "def write_tfrecord_single_batch(args):\n",
+ " \"\"\"\n",
+ " Write a batch of data to a TFRecord file. This function processes images and optional tabular data,\n",
+ " serializes them into a TFRecord format, and saves the result to a file.\n",
+ "\n",
+ " Args:\n",
+ " args: A tuple or list containing the following:\n",
+ " - image_paths: List of file paths to the images (batch).\n",
+ " - labels: List of corresponding labels for the images (batch).\n",
+ " - tabular_data: Optional tabular data associated with the images (batch) (could be None).\n",
+ " - output_file: Path to the output TFRecord file.\n",
+ " \"\"\"\n",
+ " try:\n",
+ " image_paths, labels, tabular_data, output_file = args\n",
+ " # Print which file is being written\n",
+ " tf.print(f\"Writing TFRecords to: {output_file}\")\n",
+ "\n",
+ " # Specify compression options for the TFRecord (GZIP in this case)\n",
+ " options = tf.io.TFRecordOptions(compression_type=\"GZIP\")\n",
+ "\n",
+ " # Open a TFRecordWriter for writing data to the specified output file\n",
+ " with tf.io.TFRecordWriter(output_file, options=options) as writer:\n",
+ " # Loop through each image and label in the batch\n",
+ " for i, (img_path, label) in enumerate(zip(image_paths, labels)):\n",
+ " img_bytes = process_image(img_path)\n",
+ "\n",
+ " # Convert the raw image into a feature that can be stored in the TFRecord\n",
+ " img_feature = tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_bytes]))\n",
+ "\n",
+ " # Convert the label into a feature (using int64 since it's a categorical label)\n",
+ " label_feature = tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))\n",
+ "\n",
+ " # Create a dictionary to store the image and label as features\n",
+ " features = {'image': img_feature, 'label': label_feature}\n",
+ "\n",
+ " # If tabular data is used and provided, add it to the features dictionary\n",
+ " if tabular_data is not None:\n",
+ " # Convert the tabular data to a float list feature for this specific sample\n",
+ " tab_feature = tf.train.Feature(float_list=tf.train.FloatList(value=tabular_data[i]))\n",
+ " features['tabular_data'] = tab_feature\n",
+ "\n",
+ " # Create a TFRecord example from the features dictionary\n",
+ " example = tf.train.Example(features=tf.train.Features(feature=features))\n",
+ "\n",
+ " # Serialize the example to a string and write it to the TFRecord file\n",
+ " writer.write(example.SerializeToString())\n",
+ " except Exception as e:\n",
+ " print(f\"Error in write_tfrecord: {e}\")\n",
+ " traceback.print_exc()\n",
+ "\n",
+ "def process_image(img_path):\n",
+ " \"\"\"Helper function to read and process a single image.\"\"\"\n",
+ " try:\n",
+ " img_bytes = tf.io.read_file(img_path)\n",
+ " return img_bytes.numpy() # Return the image bytes\n",
+ " except Exception as e:\n",
+ " raise Exception(f\"Error reading image {img_path}: {e}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "LmnCG-oYNLuN"
+ },
+ "source": [
+ "### Data download utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "CcazDGZVh__P",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def download_folder_from_dagshub(boto_client, bucket_name, remote_folder, local_folder):\n",
+ " \"\"\"\n",
+ " Downloads all files from a specified remote folder in DagsHub to a local folder.\n",
+ "\n",
+ " Args:\n",
+ " boto_client: The boto client used to interact with DagsHub (S3-compatible).\n",
+ " bucket_name: Name of the DagsHub repository (S3 bucket).\n",
+ " remote_folder: The path to the folder in DagsHub (e.g., \"kaggle/isic-2024/datasets/tf_records/sample_1k/validation/\").\n",
+ " local_folder: The path to the local folder where files will be downloaded.\n",
+ " \"\"\"\n",
+ "\n",
+ " # Ensure local folder exists\n",
+ " os.makedirs(local_folder, exist_ok=True)\n",
+ "\n",
+ " # List all files in the remote folder\n",
+ " response = boto_client.list_objects_v2(Bucket=bucket_name, Prefix=remote_folder)\n",
+ "\n",
+ " # Check if there are contents in the folder\n",
+ " if 'Contents' in response:\n",
+ " for obj in response['Contents']:\n",
+ " remote_file_path = obj['Key']\n",
+ " local_file_path = os.path.join(local_folder, os.path.basename(remote_file_path))\n",
+ "\n",
+ " # Download each file\n",
+ " print(f\"Downloading {remote_file_path} to {local_file_path}...\")\n",
+ " boto_client.download_file(\n",
+ " Bucket=bucket_name,\n",
+ " Key=remote_file_path,\n",
+ " Filename=local_file_path\n",
+ " )\n",
+ " print(\"Download completed!\")\n",
+ " else:\n",
+ " print(f\"No files found in the folder: {remote_folder}\")\n",
+ "\n",
+ "def resolve_remote_folder(local_folder):\n",
+ " return local_folder.replace(f'{ROOT_DB_DIR}/', '')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "yu_Pj-0DDqju"
+ },
+ "source": [
+ "### Preparation pipeline"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "pgdFVV6vDsBs",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "from sklearn.compose import ColumnTransformer\n",
+ "from sklearn.preprocessing import OneHotEncoder, MinMaxScaler, StandardScaler, OrdinalEncoder\n",
+ "import pickle\n",
+ "from scipy.sparse import vstack, csr_matrix\n",
+ "\n",
+ "\n",
+ "from sklearn.compose import ColumnTransformer\n",
+ "from sklearn.preprocessing import OneHotEncoder, StandardScaler\n",
+ "\n",
+ "common_columns = ['isic_id', 'age_approx', 'sex', 'anatom_site_general']\n",
+ "selected_2019_2020_columns = ['image_name', 'age_approx', 'sex', 'anatom_site_general_challenge']\n",
+ "\n",
+ "# Preparing data directories using dense tabular data format\n",
+ "def prepare_data_directories(train_dir, tabular_data_paths, sampling_fraction=1.0, use_oversampling=False, data_type='train'):\n",
+ " \"\"\"Load image paths, labels, and optionally tabular data\"\"\"\n",
+ "\n",
+ " tf.print(f\"\\nStart {data_type} data preparation...\")\n",
+ "\n",
+ " train_image_paths = glob(os.path.join(train_dir, '**', '*.jpg'), recursive=True)\n",
+ " train_labels = [1 if os.path.dirname(path).endswith('positives') else 0 for path in train_image_paths]\n",
+ " train_images = [os.path.basename(path).split('.')[0] for path in train_image_paths]\n",
+ "\n",
+ " data_type = data_type.capitalize()\n",
+ " tf.print(f\"{data_type} labels length before preparing data\", len(train_labels))\n",
+ "\n",
+ " if use_tabular_data:\n",
+ " # Load the preprocessor for validation or fit it for training data\n",
+ " preprocessor = None if data_type == 'Train' else load_preprocessor(preprocessor_path)\n",
+ "\n",
+ " train_tabular = format_and_encode_tabular_data(tabular_data_paths, train_images, preprocessor=preprocessor)\n",
+ " else:\n",
+ " train_tabular = None\n",
+ "\n",
+ " # oversample only train data\n",
+ " if use_oversampling:\n",
+ " train_image_paths, train_labels, train_tabular = smote_oversampling(\n",
+ " train_image_paths, train_labels, train_tabular\n",
+ " )\n",
+ " tf.print(f\"{data_type} data size after oversampling:\", len(train_labels))\n",
+ "\n",
+ " # sample all the datasets if sampling is needed\n",
+ " if sampling_fraction < 1.0:\n",
+ " train_image_paths, train_labels, train_tabular = sample_data(\n",
+ " train_image_paths, train_labels, train_tabular, sampling_fraction\n",
+ " )\n",
+ " tf.print(f\"{data_type} data size after subsampling:\", len(train_labels))\n",
+ "\n",
+ " if train_tabular is not None:\n",
+ " tabular_data_length = train_tabular.shape[0]\n",
+ "\n",
+ " print(f\"Image data size: {len(train_image_paths)}\")\n",
+ " print(f\"Tabular data size: {tabular_data_length}\")\n",
+ "\n",
+ " # Ensure that the number of tabular rows matches the number of image paths\n",
+ " assert len(train_image_paths) == tabular_data_length, \"Image paths and tabular data do not match\"\n",
+ " return (train_image_paths, train_labels, train_tabular)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "NQN-Ofv4fr0n"
+ },
+ "source": [
+ "## Run"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "-dRINe0fg1x1"
+ },
+ "source": [
+ "### Download raw datasets"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "tA-VuKdAg2PP",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "if download_raw_datasets:\n",
+ " boto_client = get_repo_bucket_client(\"AmadouMamane/dagshub-drive\", flavor=\"boto\")\n",
+ " download_folder_from_dagshub(boto_client, \"dagshub-drive\", resolve_remote_folder(eval_dir_pos), eval_dir_pos)\n",
+ " download_folder_from_dagshub(boto_client, \"dagshub-drive\", resolve_remote_folder(eval_dir_negs), eval_dir_negs)\n",
+ "\n",
+ " download_folder_from_dagshub(boto_client, \"dagshub-drive\", resolve_remote_folder(val_dir_pos), val_dir_pos)\n",
+ " download_folder_from_dagshub(boto_client, \"dagshub-drive\", resolve_remote_folder(val_dir_negs), val_dir_negs)\n",
+ "\n",
+ " download_folder_from_dagshub(boto_client, \"dagshub-drive\", resolve_remote_folder(train_dir_pos), train_dir_pos)\n",
+ " download_folder_from_dagshub(boto_client, \"dagshub-drive\", resolve_remote_folder(train_dir_negs), train_dir_negs)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "7K1xS2pRhI9f"
+ },
+ "source": [
+ "### Download refined datasets"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "PsDlncmLhORm",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "if download_refined_training_datasets:\n",
+ " boto_client = get_repo_bucket_client(\"AmadouMamane/dagshub-drive\", flavor=\"boto\")\n",
+ " download_folder_from_dagshub(boto_client, \"dagshub-drive\", resolve_remote_folder(tf_records_eval_dir), tf_records_eval_dir)\n",
+ " download_folder_from_dagshub(boto_client, \"dagshub-drive\", resolve_remote_folder(tf_records_val_dir), tf_records_val_dir)\n",
+ " download_folder_from_dagshub(boto_client, \"dagshub-drive\", resolve_remote_folder(tf_records_train_val_dir), tf_records_train_val_dir)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "LjlJGFlRf_TT"
+ },
+ "source": [
+ "### Data preparation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Ft7LSZOfp_gW",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Prepare and process data\n",
+ "num_workers = 10\n",
+ "#total_samples = #49403 + 12100 + 4389 =65892\n",
+ "\n",
+ "if create_new_training_datasets:\n",
+ " sampling_fraction = dataset_size / 65892\n",
+ " metadata_2024_path = os.path.join(f'{training_metadata_dir}', 'train_metadata_2024.csv')\n",
+ " metadata_2020_path = os.path.join(f'{training_metadata_dir}', 'train_metadata_2020.csv')\n",
+ " metadata_2019_path = os.path.join(f'{training_metadata_dir}', 'train_metadata_2019.csv')\n",
+ " tabular_data_paths = [metadata_2024_path, metadata_2020_path, metadata_2019_path]\n",
+ "\n",
+ " (train_image_paths, train_labels, train_tabular) = prepare_data_directories(train_dir, tabular_data_paths, sampling_fraction=sampling_fraction, use_oversampling=oversample_persisted_data, data_type='train')\n",
+ " (eval_image_paths, eval_labels, eval_tabular) = prepare_data_directories(eval_dir, tabular_data_paths, sampling_fraction=sampling_fraction, use_oversampling=oversample_persisted_data, data_type='eval')\n",
+ " (val_image_paths, val_labels, val_tabular) = prepare_data_directories(val_dir, tabular_data_paths, sampling_fraction=sampling_fraction, use_oversampling=oversample_persisted_data, data_type='Val')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "sLd3G8dfgFIL"
+ },
+ "source": [
+ "### Data serialization"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "ZG140Mukf3iu",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "if create_new_training_datasets:\n",
+ " write_dataset_to_tfrecords(eval_image_paths, eval_labels, eval_tabular, f'{tf_records_eval_dir}/eval', num_workers=num_workers, data_type='train')\n",
+ " write_dataset_to_tfrecords(val_image_paths, val_labels, val_tabular, f'{tf_records_val_dir}/val', num_workers=num_workers, data_type='eval')\n",
+ " write_dataset_to_tfrecords(train_image_paths, train_labels, train_tabular, f'{tf_records_train_val_dir}/train', num_workers=num_workers, data_type='Val')\n",
+ " print('Number of train and val positives class samples', train_labels.count(1))\n",
+ " print('Number of val positives class samples', val_labels.count(1))\n",
+ " print('Number of eval positives class samples', eval_labels.count(1))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "X-zFAXEutWTT"
+ },
+ "source": [
+ "## Data augmentation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "jfYraWRv1LNN"
+ },
+ "source": [
+ "#### Random hue saturation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "iDrV3NQ81INK",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "sat_max_delta_hs=0.0005\n",
+ "hue_max_delta_hs=0.0005\n",
+ "val_max_delta_hs=0.0005\n",
+ "@tf.function\n",
+ "def random_hue_saturation(image, hue_max_delta=hue_max_delta_hs, sat_max_delta=sat_max_delta_hs, val_max_delta=val_max_delta_hs, probability=0.5, seed=None):\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " def apply_hue_sat_val(img):\n",
+ " img = tf.image.random_hue(img, max_delta=hue_max_delta)\n",
+ " img = tf.image.random_saturation(img, lower=1 - sat_max_delta, upper=1 + sat_max_delta)\n",
+ " img = tf.image.random_brightness(img, max_delta=val_max_delta)\n",
+ " img = tf.clip_by_value(img, 0.0, 1.0)\n",
+ " return img\n",
+ "\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),\n",
+ " lambda: apply_hue_sat_val(image),\n",
+ " lambda: image\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ASS4LwAj0jl_"
+ },
+ "source": [
+ "#### Random shift scale rotate"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "fM1u4CFb0kv6",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "@tf.function\n",
+ "def random_shift_scale_rotate(image, shift_limit=0.05, scale_limit=0.05, rotate_limit=5, threshold=0.5, image_size=image_size, seed=None):\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " def apply_shift_scale_rotate(img, seed):\n",
+ "\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " seeds = tf.random.experimental.stateless_split(seed, num=4)\n",
+ " angle_seed = seeds[0]\n",
+ " scale_seed = seeds[1]\n",
+ " dx_seed = seeds[3]\n",
+ " dy_seed = seeds[4]\n",
+ "\n",
+ " # Random rotation\n",
+ " angle = tf.random.stateless_uniform([], -rotate_limit, rotate_limit, seed=angle_seed) * 3.14159265 / 180\n",
+ " img = tf.image.rot90(img, k=tf.cast(angle / (3.14159265 / 2), tf.int32))\n",
+ "\n",
+ " # Random scaling\n",
+ " scale = tf.random.stateless_uniform([], 1 - scale_limit, 1 + scale_limit, seed=scale_seed)\n",
+ " new_size = tf.cast(tf.cast(tf.shape(img)[0:2], tf.float32) * scale, tf.int32)\n",
+ " img = tf.image.resize(img, new_size)\n",
+ "\n",
+ " # Random shifting\n",
+ " max_dx = tf.cast(shift_limit * tf.cast(tf.shape(img)[1], tf.float32), tf.int32)\n",
+ " max_dy = tf.cast(shift_limit * tf.cast(tf.shape(img)[0], tf.float32), tf.int32)\n",
+ "\n",
+ "\n",
+ " dx = tf.random.stateless_uniform([], -max_dx, max_dx, dtype=tf.int32, seed=dx_seed)\n",
+ " dy = tf.random.stateless_uniform([], -max_dy, max_dy, dtype=tf.int32, seed=dy_seed)\n",
+ "\n",
+ " target_height = tf.minimum(image_size, new_size[0] - tf.abs(dy))\n",
+ " target_width = tf.minimum(image_size, new_size[1] - tf.abs(dx))\n",
+ "\n",
+ " img = tf.image.crop_to_bounding_box(\n",
+ " img,\n",
+ " offset_height=tf.maximum(0, dy),\n",
+ " offset_width=tf.maximum(0, dx),\n",
+ " target_height=target_height,\n",
+ " target_width=target_width\n",
+ " )\n",
+ "\n",
+ " # Resize back to original size\n",
+ " img = tf.image.resize_with_crop_or_pad(img, target_height=image_size, target_width=image_size)\n",
+ " return img\n",
+ "\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),\n",
+ " lambda: apply_shift_scale_rotate(image, seed),\n",
+ " lambda: image\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "YRlY_69rQdrF"
+ },
+ "source": [
+ "#### Random crop"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "kXgy9gOU0GP9",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "@tf.function\n",
+ "def random_crop(image, min_crop_size_ratio=0.7, max_crop_size_ratio=1.0, probability=0.5, seed=None):\n",
+ " def apply_random_crop_per_image(img):\n",
+ " img_shape = tf.shape(img)\n",
+ " height = img_shape[0]\n",
+ " width = img_shape[1]\n",
+ "\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " seeds = tf.random.experimental.stateless_split(seed, num=4)\n",
+ " crop_seed = seeds[0]\n",
+ " size_seed = seeds[1]\n",
+ " y_seed = seeds[2]\n",
+ " x_seed = seeds[3]\n",
+ "\n",
+ " # Randomly determine the crop size ratio within the given range using the seed\n",
+ " crop_size_ratio = tf.random.stateless_uniform([], minval=min_crop_size_ratio, maxval=max_crop_size_ratio, seed=size_seed)\n",
+ "\n",
+ " # Calculate the crop size based on the randomly chosen ratio\n",
+ " crop_height = tf.cast(crop_size_ratio * tf.cast(height, tf.float32), tf.int32)\n",
+ " crop_width = tf.cast(crop_size_ratio * tf.cast(width, tf.float32), tf.int32)\n",
+ "\n",
+ " y_maxval = tf.maximum(height - crop_height, 1) # Ensure maxval is > min_val\n",
+ " x_maxval = tf.maximum(width - crop_width, 1)\n",
+ "\n",
+ " # Randomly select the top-left corner of the crop using the seed\n",
+ " y1 = tf.random.stateless_uniform([], minval=0, maxval=x_maxval, dtype=tf.int32, seed=y_seed)\n",
+ " x1 = tf.random.stateless_uniform([], minval=0, maxval=y_maxval, dtype=tf.int32, seed=x_seed)\n",
+ "\n",
+ " # Define the bottom-right corner of the crop\n",
+ " y2 = y1 + crop_height\n",
+ " x2 = x1 + crop_width\n",
+ "\n",
+ " # Crop the image\n",
+ " cropped_img = img[y1:y2, x1:x2, :]\n",
+ "\n",
+ " # Set a static shape for cropped image if necessary before resizing (adjust as needed)\n",
+ " cropped_img.set_shape([None, None, img.shape[-1]])\n",
+ "\n",
+ " # Resize the cropped image back to the original size\n",
+ " cropped_img = tf.image.resize(cropped_img, [height, width], method='bilinear')\n",
+ "\n",
+ " return cropped_img\n",
+ "\n",
+ " # Use stateless random to generate a seed if none is provided\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " # Apply random crop with the specified probability\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),\n",
+ " lambda: apply_random_crop_per_image(image),\n",
+ " lambda: image\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "d9jLIru7PnQA"
+ },
+ "source": [
+ "### Random cutout"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "pp9e5uxKvh95",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "@tf.function\n",
+ "def random_cutout(image, max_cutout_size_ratio=0.3, probability=0.5, seed=None):\n",
+ " def apply_cutout(img):\n",
+ " img_shape = tf.shape(img)\n",
+ " height = img_shape[-3]\n",
+ " width = img_shape[-2]\n",
+ "\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " seeds = tf.random.experimental.stateless_split(seed, num=3)\n",
+ " cutout_seed = seeds[0]\n",
+ " x_center_seed = seeds[1]\n",
+ " y_center_seed = seeds[2]\n",
+ "\n",
+ " # Generate the cutout size using a stateless random function with a seed\n",
+ " cutout_size = tf.random.stateless_uniform([], minval=0, maxval=max_cutout_size_ratio, dtype=tf.float32, seed=cutout_seed) * tf.cast(tf.minimum(height, width), tf.float32)\n",
+ " cutout_size = tf.cast(cutout_size, tf.int32)\n",
+ "\n",
+ " # Randomly select the center position of the cutout using the seed\n",
+ " x_center = tf.random.stateless_uniform([], minval=0, maxval=width, dtype=tf.int32, seed=x_center_seed)\n",
+ " y_center = tf.random.stateless_uniform([], minval=0, maxval=height, dtype=tf.int32, seed=y_center_seed)\n",
+ "\n",
+ " # Define the top-left and bottom-right corners of the cutout\n",
+ " x1 = tf.clip_by_value(x_center - cutout_size // 2, 0, width)\n",
+ " y1 = tf.clip_by_value(y_center - cutout_size // 2, 0, height)\n",
+ " x2 = tf.clip_by_value(x_center + cutout_size // 2, 0, width)\n",
+ " y2 = tf.clip_by_value(y_center + cutout_size // 2, 0, height)\n",
+ "\n",
+ " # Create the cutout mask\n",
+ " mask = tf.ones_like(img)\n",
+ " x_range = tf.range(x1, x2)\n",
+ " y_range = tf.range(y1, y2)\n",
+ "\n",
+ " # Generate all the coordinates to update in the mask\n",
+ " y_grid, x_grid = tf.meshgrid(y_range, x_range)\n",
+ " coords = tf.stack([y_grid, x_grid], axis=-1)\n",
+ " coords = tf.reshape(coords, [-1, 2])\n",
+ "\n",
+ " # Update the mask with zeros at the selected coordinates\n",
+ " mask = tf.tensor_scatter_nd_update(mask, coords, tf.zeros([tf.shape(coords)[0], img_shape[-1]], dtype=img.dtype))\n",
+ "\n",
+ " # Apply the mask to the image\n",
+ " img = img * mask\n",
+ "\n",
+ " return img\n",
+ "\n",
+ " # Use stateless random to generate a seed if none is provided\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ " # Apply cutout with the specified probability\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),\n",
+ " lambda: apply_cutout(image),\n",
+ " lambda: image\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Rw74RPsYU-ZU"
+ },
+ "source": [
+ "### Distorsions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "JWgZUXBdVDy3"
+ },
+ "source": [
+ "#### Random elastic transform"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "HrdEJ-Sck5ga",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "@tf.function\n",
+ "def random_elastic_transform(image, alpha_range=(1, 3), sigma_range=(0.8, 2), padding_size_range=(5, 15), probability=0.5, seed=None):\n",
+ " # Use stateless random to generate a seed if none is provided\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " # Generate random values for alpha, sigma, and padding_size from their respective ranges\n",
+ " alpha = tf.random.stateless_uniform([], minval=alpha_range[0], maxval=alpha_range[1], seed=seed)\n",
+ " sigma = tf.random.stateless_uniform([], minval=sigma_range[0], maxval=sigma_range[1], seed=seed)\n",
+ " padding_size = tf.random.stateless_uniform([], minval=padding_size_range[0], maxval=padding_size_range[1], seed=seed, dtype=tf.int32)\n",
+ "\n",
+ " def apply_elastic_transform(img):\n",
+ " img = tf.cast(img, tf.float32)\n",
+ " height, width, channels = tf.shape(img)[0], tf.shape(img)[1], tf.shape(img)[2]\n",
+ "\n",
+ " # Pad the image to reduce boundary artifacts\n",
+ " img_padded = tf.pad(img, [[padding_size, padding_size], [padding_size, padding_size], [0, 0]], mode='REFLECT')\n",
+ "\n",
+ " # Update dimensions after padding\n",
+ " padded_height, padded_width = tf.shape(img_padded)[0], tf.shape(img_padded)[1]\n",
+ "\n",
+ " # Split seed for reproducibility\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ " seeds = tf.random.experimental.stateless_split(seed, num=2)\n",
+ " dx_seed = seeds[0]\n",
+ " dy_seed = seeds[1]\n",
+ "\n",
+ " # Generate random displacement fields\n",
+ " dx = tf.random.stateless_normal([padded_height, padded_width], mean=0.0, stddev=sigma, seed=dx_seed)\n",
+ " dy = tf.random.stateless_normal([padded_height, padded_width], mean=0.0, stddev=sigma, seed=dy_seed)\n",
+ "\n",
+ " # Smooth the displacement fields\n",
+ " kernel_size = 13\n",
+ " kernel = tf.ones((kernel_size, kernel_size, 1, 1)) / (kernel_size * kernel_size)\n",
+ " dx = tf.nn.depthwise_conv2d(tf.expand_dims(tf.expand_dims(dx, axis=-1), axis=0), kernel, strides=[1, 1, 1, 1], padding=\"SAME\")[0, ..., 0]\n",
+ " dy = tf.nn.depthwise_conv2d(tf.expand_dims(tf.expand_dims(dy, axis=-1), axis=0), kernel, strides=[1, 1, 1, 1], padding=\"SAME\")[0, ..., 0]\n",
+ "\n",
+ " # Scale the displacement fields\n",
+ " dx *= alpha\n",
+ " dy *= alpha\n",
+ "\n",
+ " # Generate meshgrid and add the displacements\n",
+ " xs, ys = tf.meshgrid(tf.range(padded_width), tf.range(padded_height))\n",
+ " xs = tf.cast(xs, tf.float32) + dx\n",
+ " ys = tf.cast(ys, tf.float32) + dy\n",
+ "\n",
+ " # Clip the values to be within image dimensions\n",
+ " xs = tf.clip_by_value(xs, 0.0, tf.cast(padded_width - 1, tf.float32))\n",
+ " ys = tf.clip_by_value(ys, 0.0, tf.cast(padded_height - 1, tf.float32))\n",
+ "\n",
+ " # Resample using the distorted coordinates\n",
+ " distorted_indices = tf.stack([ys, xs], axis=-1)\n",
+ " distorted_image_padded = tf.gather_nd(img_padded, tf.cast(distorted_indices, tf.int32))\n",
+ "\n",
+ " # Remove padding by cropping back to the original image size\n",
+ " distorted_image = distorted_image_padded[padding_size:padding_size + height, padding_size:padding_size + width, :]\n",
+ "\n",
+ " return distorted_image\n",
+ "\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),\n",
+ " lambda: apply_elastic_transform(image),\n",
+ " lambda: image\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "fd8W-h6DVIvJ"
+ },
+ "source": [
+ "#### Random grid distortion"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "BIbXupt0kncg",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "@tf.function\n",
+ "def random_grid_distortion(image, num_steps=10, distort_limit=0.05, probability=0.5, seed=None):\n",
+ " # Use stateless random to generate a seed if none is provided\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " def apply_grid_distortion(img):\n",
+ " img_shape = tf.shape(img)\n",
+ " height = tf.cast(img_shape[0], tf.float32)\n",
+ " width = tf.cast(img_shape[1], tf.float32)\n",
+ "\n",
+ " # Define grid step size\n",
+ " x_step = tf.cast(width // num_steps, tf.float32)\n",
+ " y_step = tf.cast(height // num_steps, tf.float32)\n",
+ "\n",
+ "\n",
+ " # Verification for tf.random.stateless_uniform so that min_val and mx_val != 0\n",
+ " x_step = tf.cond(x_step > 0, lambda: x_step, lambda: tf.constant(1e-7))\n",
+ " y_step = tf.cond(y_step > 0, lambda: y_step, lambda: tf.constant(1e-7))\n",
+ "\n",
+ "\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " seeds = tf.random.experimental.stateless_split(seed, num=2)\n",
+ " x_offsets_seed = seeds[0]\n",
+ " y_offsets_seed = seeds[1]\n",
+ "\n",
+ " # Generate random offsets\n",
+ " x_offsets = tf.random.stateless_uniform(\n",
+ " shape=[num_steps + 1, num_steps + 1],\n",
+ " minval=-distort_limit * x_step,\n",
+ " maxval=distort_limit * x_step,\n",
+ " dtype=tf.float32,\n",
+ " seed=x_offsets_seed\n",
+ " )\n",
+ " y_offsets = tf.random.stateless_uniform(\n",
+ " shape=[num_steps + 1, num_steps + 1],\n",
+ " minval=-distort_limit * y_step,\n",
+ " maxval=distort_limit * y_step,\n",
+ " dtype=tf.float32,\n",
+ " seed=y_offsets_seed\n",
+ " )\n",
+ "\n",
+ " # Create grid of coordinates\n",
+ " x = tf.linspace(0.0, width, num_steps + 1)\n",
+ " y = tf.linspace(0.0, height, num_steps + 1)\n",
+ " x_t, y_t = tf.meshgrid(x, y)\n",
+ "\n",
+ " # Apply offsets\n",
+ " x_t = x_t + x_offsets\n",
+ " y_t = y_t + y_offsets\n",
+ "\n",
+ " # Interpolate to get dense flow field\n",
+ " x_interp = tf.image.resize(x_t[..., tf.newaxis], [height, width], method='bilinear')\n",
+ " y_interp = tf.image.resize(y_t[..., tf.newaxis], [height, width], method='bilinear')\n",
+ "\n",
+ " # Stack and subtract identity grid\n",
+ " flow = tf.stack([y_interp[..., 0] - tf.range(height)[:, None],\n",
+ " x_interp[..., 0] - tf.range(width)[None, :]], axis=-1)\n",
+ "\n",
+ " # Apply flow to image\n",
+ " distorted_image = tfa.image.dense_image_warp(img[tf.newaxis, ...], flow[tf.newaxis, ...])[0]\n",
+ "\n",
+ " return distorted_image\n",
+ "\n",
+ "\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),\n",
+ " lambda: apply_grid_distortion(image),\n",
+ " lambda: image\n",
+ " )\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "44p-jqlaskfR"
+ },
+ "source": [
+ "#### Random optical distortion"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "_Is47HQHdcKM",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Define distortion parameters\n",
+ "distort_limit_do = 0.0005\n",
+ "shift_limit_do = 0.0005\n",
+ "\n",
+ "# Define augmentation layers outside the tf.function\n",
+ "random_rotation = preprocessing.RandomRotation(factor=distort_limit_do)\n",
+ "random_translation = preprocessing.RandomTranslation(height_factor=shift_limit_do,\n",
+ " width_factor=shift_limit_do)\n",
+ "random_zoom = preprocessing.RandomZoom(height_factor=(-distort_limit_do, distort_limit_do),\n",
+ " width_factor=(-distort_limit_do, distort_limit_do))\n",
+ "\n",
+ "@tf.function\n",
+ "def random_optical_distortion(image, probability=0.5, seed=None):\n",
+ " def apply_optical_distortion(img):\n",
+ " # Apply the augmentations\n",
+ " img = random_rotation(img, training=True)\n",
+ " img = random_translation(img, training=True)\n",
+ " img = random_zoom(img, training=True)\n",
+ " return img\n",
+ "\n",
+ " # If seed is provided, split it for reproducible randomness\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),\n",
+ " lambda: apply_optical_distortion(image),\n",
+ " lambda: image\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "wN6HU68VvO1n"
+ },
+ "source": [
+ "#### Random one of distorsion"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "rvA7WvSoj0n8",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "distort_limit_do =0.0005 # defined oon function definition also\n",
+ "shift_limit_do = 0.0005\n",
+ "\n",
+ "num_steps_dg=10\n",
+ "distort_limit_dg=0.05\n",
+ "\n",
+ "alpha_range_de = (0.5,1.5)\n",
+ "sigma_range_de =(0.5,1.0)\n",
+ "padding_size_range_de = (5,10)\n",
+ "\n",
+ "@tf.function\n",
+ "def random_one_of_distortion(image, probability=0.7, seed=None):\n",
+ " # Generate seed if not provided\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " def apply_random_transform(img, seed):\n",
+ "\n",
+ " # Generate a random choice for distortion transformation\n",
+ " choice = tf.random.stateless_uniform([], minval=0, maxval=2, dtype=tf.int32, seed=seed)\n",
+ " # Apply random distortions based on the choice\n",
+ " #img = tf.cond(choice == 0, lambda: random_optical_distortion(img, probability=1.0, seed=None), lambda: img)\n",
+ " img = tf.cond(choice == 0, lambda: random_grid_distortion(img, num_steps=num_steps_dg, distort_limit=distort_limit_dg, probability=1.0, seed=None), lambda: img)\n",
+ " img = tf.cond(choice == 1, lambda: random_elastic_transform(img, alpha_range=alpha_range_de, sigma_range=sigma_range_de, padding_size_range=padding_size_range_de, probability=1.0, seed=None), lambda: img)\n",
+ " return img\n",
+ "\n",
+ " # Apply transformation based on the probability\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),\n",
+ " lambda: apply_random_transform(image, seed),\n",
+ " lambda: image\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "kxPVGK7c5ppq"
+ },
+ "source": [
+ "### Blurings"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "0dUBXJj8R4Nt",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "var_limit_gn=(0.0, 0.0001)\n",
+ "sigma_range_gb=(0.0, 0.7)\n",
+ "kernel_size_range_gb=(3, 4)\n",
+ "angle_range_gm=(0, 30)\n",
+ "kernel_size_range_gm=(1, 4)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "C9vouzC4QuBp"
+ },
+ "source": [
+ "#### Random gaussian blur"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "7GsexQzMQr0D",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "@tf.function\n",
+ "def random_gaussian_blur(image, kernel_size_range=(3, 7), sigma_range=(0.1, 2.0), probability=0.5, seed=None):\n",
+ " def gaussian_kernel(size: int, mean: float, std: float):\n",
+ " \"\"\"Creates a 2D Gaussian Kernel for convolution.\"\"\"\n",
+ " coords = tf.range(-(size // 2), size // 2 + 1, dtype=tf.float32)\n",
+ " g = tf.exp(-tf.pow(coords - mean, 2.0) / (2.0 * tf.pow(std, 2.0)))\n",
+ " g /= tf.reduce_sum(g)\n",
+ " gauss_kernel = tf.tensordot(g, g, axes=0)\n",
+ " return gauss_kernel / tf.reduce_sum(gauss_kernel)\n",
+ "\n",
+ " def apply_blur(img):\n",
+ " # Generate a seed if none is provided for consistent randomness\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " seeds = tf.random.experimental.stateless_split(seed, num=2)\n",
+ " kernel_size_seed = seeds[0]\n",
+ " sigma_seed = seeds[1]\n",
+ "\n",
+ "\n",
+ " # Randomize the kernel size and sigma within the given ranges\n",
+ " random_kernel_size = tf.random.stateless_uniform([], minval=kernel_size_range[0], maxval=kernel_size_range[1], dtype=tf.int32, seed=kernel_size_seed)\n",
+ " random_kernel_size = tf.where(random_kernel_size % 2 == 0, random_kernel_size + 1, random_kernel_size) # Ensure kernel size is odd\n",
+ " random_sigma = tf.random.stateless_uniform([], minval=sigma_range[0], maxval=sigma_range[1], seed=sigma_seed)\n",
+ "\n",
+ " # Create the Gaussian kernel\n",
+ " kernel = gaussian_kernel(random_kernel_size, 0., random_sigma)\n",
+ " kernel = kernel[:, :, tf.newaxis, tf.newaxis]\n",
+ " kernel = tf.tile(kernel, [1, 1, 3, 1]) # Match kernel to the RGB channels\n",
+ "\n",
+ " # Ensure image is 4D (batch size, height, width, channels)\n",
+ " img = tf.cond(tf.equal(tf.rank(img), 3), # Check if the image is rank 3 (without batch dimension)\n",
+ " lambda: tf.expand_dims(img, axis=0), # Add batch dimension\n",
+ " lambda: img) # If already 4D, pass as-is\n",
+ "\n",
+ " # Apply depthwise convolution (motion blur)\n",
+ " img = tf.nn.depthwise_conv2d(img, kernel, [1, 1, 1, 1], padding='SAME')[0]\n",
+ "\n",
+ " return img\n",
+ "\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),\n",
+ " lambda: apply_blur(image),\n",
+ " lambda: image\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "PwQ642z8SGOB"
+ },
+ "source": [
+ "#### Random motion blur"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "KcDm-kmDSJsD",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "@tf.function\n",
+ "def random_motion_blur(image, kernel_size_range=(3, 7), angle_range=(0, 360), probability=0.5, seed=None):\n",
+ " def motion_blur_kernel(img, kernel_size, angle):\n",
+ " center = tf.cast(kernel_size // 2, tf.float32)\n",
+ "\n",
+ " # Calculate the angle in radians\n",
+ " angle_rad = angle * (3.14159265359 / 180.0)\n",
+ " cos_a = tf.cos(angle_rad)\n",
+ " sin_a = tf.sin(angle_rad)\n",
+ "\n",
+ " # Create meshgrid for indices\n",
+ " x = tf.cast(tf.range(kernel_size), tf.float32) - center\n",
+ " y = tf.cast(tf.range(kernel_size), tf.float32) - center\n",
+ " xx, yy = tf.meshgrid(x, y)\n",
+ "\n",
+ " # Compute the distance to simulate motion blur in the specified direction\n",
+ " distance = xx * cos_a + yy * sin_a\n",
+ " kernel = tf.cast(tf.abs(distance) < 0.5, tf.float32)\n",
+ "\n",
+ " # Normalize the kernel\n",
+ " kernel = kernel / tf.reduce_sum(kernel)\n",
+ "\n",
+ " # Expand dimensions to 4D for depthwise convolution\n",
+ " kernel = kernel[:, :, tf.newaxis, tf.newaxis]\n",
+ " kernel = tf.tile(kernel, [1, 1, tf.shape(img)[-1], 1]) # Match the number of channels\n",
+ "\n",
+ " return kernel\n",
+ "\n",
+ " def apply_motion_blur(img, seed):\n",
+ " # Generate a seed if none is provided for consistent randomness\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " seeds = tf.random.experimental.stateless_split(seed, num=2)\n",
+ " kernel_size_seed = seeds[0]\n",
+ " angle_seed = seeds[1]\n",
+ " # Randomly choose a kernel size and angle within the specified range\n",
+ " kernel_size = tf.random.stateless_uniform([], minval=kernel_size_range[0], maxval=kernel_size_range[1], dtype=tf.int32, seed=kernel_size_seed)\n",
+ " angle = tf.random.stateless_uniform([], minval=angle_range[0], maxval=angle_range[1], dtype=tf.float32, seed=angle_seed)\n",
+ "\n",
+ " # Create the motion blur kernel\n",
+ " kernel = motion_blur_kernel(img, kernel_size, angle)\n",
+ "\n",
+ " # Ensure image is 4D (batch size, height, width, channels)\n",
+ " img = tf.cond(tf.equal(tf.rank(img), 3), # Check if the image is rank 3 (without batch dimension)\n",
+ " lambda: tf.expand_dims(img, axis=0), # Add batch dimension\n",
+ " lambda: img) # If already 4D, pass as-is\n",
+ "\n",
+ " # Apply depthwise convolution (motion blur)\n",
+ " img = tf.nn.depthwise_conv2d(img, kernel, [1, 1, 1, 1], padding='SAME')[0]\n",
+ "\n",
+ " return img\n",
+ "\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " # Apply motion blur with a certain probability\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),\n",
+ " lambda: apply_motion_blur(image, seed=None),\n",
+ " lambda: image)\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "TVVVuQW6SX7m"
+ },
+ "source": [
+ "#### Random median blur"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "7Evz2HsPSbGW",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "@tf.function\n",
+ "def random_median_blur(image, kernel_size=5, probability=0.5, seed=None):\n",
+ " def apply_median_blur(img, seed):\n",
+ " # If no seed is provided, generate one\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ " # Convert image to uint8 for applying the median filter\n",
+ " img = tf.cast(img * 255.0, tf.uint8)\n",
+ " img = tf.image.rgb_to_grayscale(img)\n",
+ " # Apply median blur\n",
+ " img = tf.image.median_filter2d(img, filter_shape=[kernel_size, kernel_size])\n",
+ " img = tf.image.grayscale_to_rgb(img)\n",
+ " # Convert back to float32 after blurring\n",
+ " return tf.cast(img, tf.float32) / 255.0\n",
+ "\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " # Apply the blur based on the given probability\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),\n",
+ " lambda: apply_median_blur(image, seed=None),\n",
+ " lambda: image\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "loASl3TOSe8I"
+ },
+ "source": [
+ "#### Radom gaussian noise"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "PrZq5YB0SihD",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "@tf.function\n",
+ "def random_gaussian_noise(image, var_limit=(0.01, 0.1), probability=0.5, seed=None):\n",
+ " def apply_noise(img, seed):\n",
+ " # Create a seed if none is provided to ensure consistent randomness\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " seeds = tf.random.experimental.stateless_split(seed, num=2)\n",
+ " stddev_seed = seeds[0]\n",
+ " noise_seed = seeds[1]\n",
+ " # Generate random standard deviation for the noise within the given limits\n",
+ " stddev = tf.random.stateless_uniform([], minval=var_limit[0], maxval=var_limit[1], seed=stddev_seed)\n",
+ "\n",
+ " # Generate random Gaussian noise\n",
+ " noise = tf.random.stateless_normal(shape=tf.shape(img), mean=0.0, stddev=stddev, dtype=tf.float32, seed=noise_seed)\n",
+ "\n",
+ " # Add noise to the image and clip values to ensure valid pixel values [0, 1]\n",
+ " return tf.clip_by_value(img + noise, 0.0, 1.0)\n",
+ "\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ " # Apply noise with a certain probability\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),\n",
+ " lambda: apply_noise(image, seed=None),\n",
+ " lambda: image\n",
+ " )\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "xVvyyhmiSnxz"
+ },
+ "source": [
+ "#### Random one of blur"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Ve6I6Ia4nlfE",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "@tf.function\n",
+ "def random_one_of_blur(image, probability=0.7, seed=None):\n",
+ " def apply_random_transform(img, seed):\n",
+ " # If no seed is provided, generate one\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " # Generate a random choice using stateless randomness for reproducibility\n",
+ " choice = tf.random.stateless_uniform([], minval=0, maxval=3, dtype=tf.int32, seed=seed)\n",
+ "\n",
+ " # Apply different blur/noise transformations based on random choice\n",
+ " img = tf.cond(choice == 0,\n",
+ " lambda: random_motion_blur(img, kernel_size_range=kernel_size_range_gm, angle_range=angle_range_gm, probability=1.0, seed=None),\n",
+ " lambda: img)\n",
+ "\n",
+ " img = tf.cond(choice == 1,\n",
+ " lambda: random_gaussian_blur(img, kernel_size_range=kernel_size_range_gb, sigma_range=sigma_range_gb, probability=1.0, seed=None),\n",
+ " lambda: img)\n",
+ "\n",
+ " img = tf.cond(choice == 2,\n",
+ " lambda: random_gaussian_noise(img, var_limit=var_limit_gn, probability=1.0, seed=None),\n",
+ " lambda: img)\n",
+ " return img\n",
+ "\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ " # Use the main condition to determine whether to apply a transformation or not\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),\n",
+ " lambda: apply_random_transform(image, seed=None),\n",
+ " lambda: image\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "PQ7HFQsuizgh",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "@tf.function\n",
+ "def random_transpose(image, threshold=0.5, seed=None):\n",
+ " # Ensure stateless randomness for reproducibility\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),\n",
+ " lambda: tf.image.transpose(image),\n",
+ " lambda: image\n",
+ " )\n",
+ "\n",
+ "\n",
+ "@tf.function\n",
+ "def random_vertical_flip(image, threshold=0.5, seed=None):\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),\n",
+ " lambda: tf.image.flip_up_down(image),\n",
+ " lambda: image\n",
+ " )\n",
+ "\n",
+ "@tf.function\n",
+ "def random_horizontal_flip(image, threshold=0.5, seed=None):\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),\n",
+ " lambda: tf.image.flip_left_right(image),\n",
+ " lambda: image\n",
+ " )\n",
+ "\n",
+ "@tf.function\n",
+ "def random_brightness(image, max_delta=0.05, threshold=0.5, seed=None):\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),\n",
+ " lambda: tf.image.random_brightness(image, max_delta=max_delta),\n",
+ " lambda: image\n",
+ " )\n",
+ "\n",
+ "@tf.function\n",
+ "def random_contrast(image, lower=0.9, upper=1.1, threshold=0.5, seed=None):\n",
+ " if seed is None:\n",
+ " seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)\n",
+ "\n",
+ " return tf.cond(\n",
+ " tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),\n",
+ " lambda: tf.image.random_contrast(image, lower=lower, upper=upper),\n",
+ " lambda: image\n",
+ " )\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "TKkCvkJHEvZW"
+ },
+ "source": [
+ "### Apply augmentations"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "smgAKxV_7N0x",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "threshold_low=0.4\n",
+ "threshold_medium=0.5\n",
+ "threshold_high=0.7\n",
+ "threshold_very_high=0.6"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "O0b9_Cdp19nS",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "threshold_low=0.4\n",
+ "threshold_medium=0.65\n",
+ "threshold_high=0.7\n",
+ "threshold_very_high=0.65\n",
+ "\n",
+ "@tf.function\n",
+ "def augment_minority(image, threshold=threshold_very_high, seed=None):\n",
+ " # Apply a series of augmentations with appropriate thresholds\n",
+ " image = random_transpose(image, threshold_high, seed=seed)\n",
+ " image = random_vertical_flip(image, threshold=threshold_high, seed=seed)\n",
+ " image = random_horizontal_flip(image, threshold=threshold_high, seed=seed)\n",
+ " image = random_brightness(image, threshold=threshold, seed=seed)\n",
+ " image = random_contrast(image, threshold=threshold, seed=seed)\n",
+ "\n",
+ " image = random_one_of_blur(image, probability=threshold_medium, seed=seed)\n",
+ " image = random_one_of_distortion(image, probability=threshold_medium, seed=seed)\n",
+ " image = random_hue_saturation(image, probability=threshold_medium, seed=seed)\n",
+ " image = random_crop(image, probability=threshold_medium, seed=seed)\n",
+ " image = random_cutout(image, probability=threshold_medium, seed=seed)\n",
+ " image = tf.clip_by_value(image, 0.0, 1.0)\n",
+ " return image\n",
+ "\n",
+ "@tf.function\n",
+ "def augment_majority(image, threshold=threshold_low, seed=None):\n",
+ " # Apply augmentations with lower thresholds for majority class\n",
+ " image = random_transpose(image, threshold=threshold, seed=seed)\n",
+ " image = random_vertical_flip(image, threshold=threshold, seed=seed)\n",
+ " image = random_horizontal_flip(image, threshold=threshold, seed=seed)\n",
+ " image = random_brightness(image, threshold=threshold, seed=seed)\n",
+ " image = random_contrast(image, threshold=threshold, seed=seed)\n",
+ "\n",
+ " image = random_one_of_blur(image, probability=threshold, seed=seed)\n",
+ " image = random_one_of_distortion(image, probability=threshold, seed=seed)\n",
+ " image = random_hue_saturation(image, probability=threshold, seed=seed)\n",
+ " image = random_crop(image, probability=threshold, seed=seed)\n",
+ " image = random_cutout(image, probability=threshold, seed=seed)\n",
+ " image = tf.clip_by_value(image, 0.0, 1.0)\n",
+ " return image\n",
+ "\n",
+ "@tf.function\n",
+ "def augment_image_batch_old(images, labels):\n",
+ " \"\"\"\n",
+ " This function applies augmentations to batches of images based on the label using vectorized operations.\n",
+ " It applies augment_minority if label == 1, otherwise augment_majority.\n",
+ " \"\"\"\n",
+ " # Define how to augment each image based on the label\n",
+ " def augment_single(image, label):\n",
+ " if label == 1:\n",
+ " return augment_minority(image)\n",
+ " else:\n",
+ " return augment_majority(image)\n",
+ "\n",
+ " # Apply augmentations to the batch using tf.map_fn\n",
+ " augmented_images = tf.map_fn(lambda x: augment_single(x[0], x[1]), (images, labels), fn_output_signature=tf.float32)\n",
+ " # Assuming augment_single takes image and label as input and returns augmented image\n",
+ " return augmented_images\n",
+ "\n",
+ "@tf.function\n",
+ "def augment_image_batch(images, labels):\n",
+ " \"\"\"\n",
+ " Apply augmentations based on the label (minority or majority) using tf.where.\n",
+ " Labels of 1 apply minority augmentations, labels of 0 apply majority augmentations.\n",
+ " \"\"\"\n",
+ " # Create a mask where label == 1 (for minority)\n",
+ " mask = tf.equal(labels, 1)\n",
+ "\n",
+ " # Apply augmentations conditionally based on the mask\n",
+ " augmented_images = tf.where(mask[:, None, None, None],\n",
+ " #augment_minority(images),\n",
+ " #augment_majority(images)\n",
+ " tf.map_fn(lambda x: augment_minority(x), images, fn_output_signature=tf.float32),\n",
+ " tf.map_fn(lambda x: augment_majority(x), images, fn_output_signature=tf.float32)\n",
+ " )\n",
+ " return augmented_images\n",
+ "\n",
+ "@tf.function\n",
+ "def apply_augmentations(dataset, use_tabular_data=use_tabular_data):\n",
+ " tf.print(\"Started data augmentation...\")\n",
+ " if use_tabular_data:\n",
+ " # Map using (image, tabular_data, label)\n",
+ " return dataset.map(lambda img, tab, lbl: (augment_image_batch(img, lbl), tab, lbl),\n",
+ " num_parallel_calls=tf.data.AUTOTUNE)\n",
+ " else:\n",
+ " # Map using only (image, label)\n",
+ " return dataset.map(lambda img, lbl: (augment_image_batch(img, lbl), lbl),\n",
+ " num_parallel_calls=tf.data.AUTOTUNE)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "IDupvFefwQyQ"
+ },
+ "source": [
+ "## Data processing"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "9Cf22aP_e2rr"
+ },
+ "source": [
+ "### Caching utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "RcFfFmxKe2rr",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def get_file_modification_time(file_path):\n",
+ " \"\"\"Get the last modification time of a file.\"\"\"\n",
+ " return os.path.getmtime(file_path)\n",
+ "\n",
+ "def get_file_checksum(file_path):\n",
+ " \"\"\"Generate a checksum for a file's contents.\"\"\"\n",
+ " md5_hash = hashlib.md5()\n",
+ " with tf.io.gfile.GFile(file_path, 'rb') as f:\n",
+ " while chunk := f.read(8192):\n",
+ " md5_hash.update(chunk)\n",
+ " return md5_hash.hexdigest()\n",
+ "\n",
+ "def get_dataset_checksum(file_pattern, check_modification_time=False):\n",
+ " \"\"\"Generate a checksum for the dataset based on file contents or modification times.\"\"\"\n",
+ " files = tf.io.gfile.glob(file_pattern)\n",
+ " if not files:\n",
+ " raise FileNotFoundError(f\"No files match the pattern: {file_pattern}\")\n",
+ "\n",
+ " combined_checksum = hashlib.md5()\n",
+ "\n",
+ " for file in files:\n",
+ " if check_modification_time:\n",
+ " # Include file modification times\n",
+ " file_mod_time = str(get_file_modification_time(file))\n",
+ " combined_checksum.update(file_mod_time.encode('utf-8'))\n",
+ " else:\n",
+ " # Include file contents checksum\n",
+ " file_checksum = get_file_checksum(file)\n",
+ " combined_checksum.update(file_checksum.encode('utf-8'))\n",
+ "\n",
+ " return combined_checksum.hexdigest()\n",
+ "\n",
+ "def generate_cache_name(file_pattern, checksum, prefix=\"dataset_cache\", run_id=None):\n",
+ " \"\"\"Generate a unique cache name based on the file pattern and dataset checksum.\"\"\"\n",
+ " if file_pattern:\n",
+ " file_pattern_hash = hashlib.md5(file_pattern.encode('utf-8')).hexdigest()\n",
+ " else:\n",
+ " file_pattern_hash = \"not_provided\"\n",
+ "\n",
+ " cache_name = f\"{prefix}_{file_pattern_hash}_checksum_{checksum}\"\n",
+ "\n",
+ " if run_id:\n",
+ " cache_name += f\"_run_{run_id}\"\n",
+ "\n",
+ " return cache_name\n",
+ "\n",
+ "def remove_lockfile_if_exists(cache_path):\n",
+ " \"\"\"Remove a lockfile if it exists.\"\"\"\n",
+ " lockfile_path = f\"{cache_path}.lockfile\"\n",
+ " if os.path.exists(lockfile_path):\n",
+ " print(f\"Removing stale lockfile: {lockfile_path}\")\n",
+ " os.remove(lockfile_path)\n",
+ "\n",
+ "def check_cache_exists(cache_dir, cache_name):\n",
+ " \"\"\"Check if the cache exists by looking for any cache files with the given prefix.\"\"\"\n",
+ " cache_pattern = os.path.join(cache_dir, cache_name + \"*\")\n",
+ " return len(glob(cache_pattern)) > 0\n",
+ "\n",
+ "# Force full caching of the dataset by fully consuming it\n",
+ "def fully_cache_dataset(dataset, train_val_dataset_steps):\n",
+ " \"\"\"Fully cache the dataset by iterating over all elements.\"\"\"\n",
+ " print(\"Triggering full dataset caching...\")\n",
+ " for _ in dataset:\n",
+ " pass # Consume all elements to force caching\n",
+ " print(\"Caching complete.\")\n",
+ " return dataset\n",
+ "\n",
+ "def persist_dataset(dataset, file_pattern, dataset_steps, cache_in_memory, cache_dir=cache_dir,\n",
+ " prefix=\"dataset_cache\", run_id=None, check_modification_time=False, force_cache=True):\n",
+ " \"\"\"\n",
+ " Efficiently caches a TensorFlow dataset to memory or disk.\n",
+ "\n",
+ " Args:\n",
+ " dataset (tf.data.Dataset): The TensorFlow dataset to cache.\n",
+ " file_pattern (str): Pattern for the dataset files.\n",
+ " dataset_steps (int): Number of steps in the dataset.\n",
+ " cache_in_memory (bool): Whether to cache the dataset in memory.\n",
+ " cache_dir (str): Directory for storing the cached dataset (if not in memory).\n",
+ " prefix (str): Prefix for cache file names.\n",
+ " run_id (str): Optional identifier to distinguish cache versions.\n",
+ " check_modification_time (bool): Consider file modification times for caching.\n",
+ " force_cache (bool): If True, forces cache re-generation.\n",
+ "\n",
+ " Returns:\n",
+ " tf.data.Dataset: The cached dataset.\n",
+ " \"\"\"\n",
+ " # Generate the cache name and path\n",
+ " if file_pattern:\n",
+ " dataset_checksum = get_dataset_checksum(file_pattern, check_modification_time)\n",
+ " else:\n",
+ " dataset_checksum = 'not_provided'\n",
+ "\n",
+ " cache_name = generate_cache_name(file_pattern, dataset_checksum, prefix, run_id)\n",
+ " cache_path = os.path.join(cache_dir, cache_name)\n",
+ "\n",
+ " # If forcing cache or no cache exists, optionally clean the previous cache\n",
+ " if force_cache:\n",
+ " clean_cache_path(cache_path)\n",
+ " gc.collect()\n",
+ "\n",
+ " # Use existing cache if available and not forced to recache\n",
+ " if not force_cache and check_cache_exists(cache_dir, cache_name):\n",
+ " print(f\"Using cached dataset at {cache_path}\")\n",
+ " #return dataset if cache_in_memory else dataset.cache(cache_path)\n",
+ " return dataset\n",
+ "\n",
+ " # Create cache directory only if caching to disk\n",
+ " if not cache_in_memory:\n",
+ " os.makedirs(cache_dir, exist_ok=True)\n",
+ " print(f\"Caching dataset to disk at {cache_path}\")\n",
+ " else:\n",
+ " print(\"Caching dataset to memory\")\n",
+ "\n",
+ " # Cache in memory or disk and ensure dataset is fully cached\n",
+ " dataset = dataset.cache() if cache_in_memory else dataset.cache(cache_path)\n",
+ " fully_cache_dataset(dataset, dataset_steps)\n",
+ "\n",
+ " return dataset\n",
+ "\n",
+ "def clean_cache_path(cache_path, cache_dir=cache_dir):\n",
+ " \"\"\"\n",
+ " Removes all lockfiles from the cache directory to clean up stale caches.\n",
+ " Args:\n",
+ " cache_dir (str): The directory where the cache is stored.\n",
+ " \"\"\"\n",
+ " try:\n",
+ " # List all files in the cache directory\n",
+ " for root, dirs, files in os.walk(cache_dir):\n",
+ " for file in files:\n",
+ " # Check if the file is a lockfile\n",
+ " if file.startswith(cache_path):\n",
+ " lockfile_path = os.path.join(root, file)\n",
+ " print(f\"Removing lockfile: {lockfile_path}\")\n",
+ " os.remove(lockfile_path)\n",
+ " print(f\"Cache {cache_path} cleanup completed\")\n",
+ " except Exception as e:\n",
+ " print(f\"An error occurred while cleaning the cache path {cache_path}: {str(e)}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "h4j8TAaVe2rr"
+ },
+ "source": [
+ "### Data processing utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Pw7rGEMre2rr",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def calculate_samples_per_class(labels):\n",
+ " unique, counts = np.unique(labels, return_counts=True)\n",
+ " return dict(zip(unique, counts))\n",
+ "\n",
+ "def calculate_class_weights(samples_per_class):\n",
+ " total_samples = sum(samples_per_class.values())\n",
+ " return {cls: total_samples / (len(samples_per_class) * count) for cls, count in samples_per_class.items()}\n",
+ "\n",
+ "def calculate_steps_per_epoch(total_samples, batch_size=batch_size, strategy=strategy):\n",
+ " steps = total_samples // (batch_size * strategy.num_replicas_in_sync)\n",
+ " if steps == 0:\n",
+ " raise Exception(f\"Calculated steps is 0 because the provided batch_size {batch_size} is too big !\\n Total samples is {total_samples}\")\n",
+ " return steps\n",
+ "\n",
+ "def stratified_split_indices(labels, n_splits=5, shuffle=True, random_state=42):\n",
+ " skf = StratifiedKFold(n_splits=n_splits, shuffle=shuffle, random_state=random_state)\n",
+ " return list(skf.split(np.zeros(len(labels)), labels))\n",
+ "\n",
+ "def count_samples_in_tfrecord(file_pattern):\n",
+ " \"\"\"Count the total number of samples in the TFRecord files.\"\"\"\n",
+ " print(\"Calculating tfrecords dataset size...\")\n",
+ " dataset = tf.data.Dataset.list_files(file_pattern)\n",
+ " total_count = 0\n",
+ " for file in dataset:\n",
+ " count = sum(1 for _ in tf.data.TFRecordDataset(file, compression_type=\"GZIP\"))\n",
+ " total_count += count\n",
+ " return total_count\n",
+ "\n",
+ "def split_dataset(dataset, indices):\n",
+ " tf.print('Splitting the dataset')\n",
+ " indices = tf.convert_to_tensor(indices, dtype=tf.int64)\n",
+ " indices_dataset = tf.data.Dataset.from_tensor_slices(indices)\n",
+ "\n",
+ " # Use enumerate to pair each element with its index, then filter\n",
+ " filtered_dataset = dataset.enumerate().filter(\n",
+ " lambda idx, _: tf.reduce_any(tf.equal(idx, indices))\n",
+ " ).map(lambda idx, data: data) # Discard the index after filtering\n",
+ "\n",
+ " #if use_tabular_data:\n",
+ " #return filtered_dataset.map(lambda img, tab, lbl: ((img, tab), lbl))\n",
+ " #else:\n",
+ " #return filtered_dataset.map(lambda img, lbl: (img, lbl))\n",
+ " return filtered_dataset\n",
+ "\n",
+ "def count_samples_in_training_dataset(dataset, batch_size=None, strategy=None):\n",
+ " \"\"\"\n",
+ " Count the number of samples in a dataset, taking into account batched datasets.\n",
+ "\n",
+ " Args:\n",
+ " dataset: A `tf.data.Dataset` object, possibly batched.\n",
+ " batch_size: If the dataset is batched, specify the batch size to correctly count samples.\n",
+ "\n",
+ " Returns:\n",
+ " Integer count of the number of samples in the dataset.\n",
+ " \"\"\"\n",
+ " print(\"Calculating the dataset size...\")\n",
+ "\n",
+ " # If the dataset is batched, multiply by batch size, or use the actual number of samples in each batch\n",
+ " if batch_size:\n",
+ " # Count the number of batches\n",
+ " batch_count = dataset.reduce(0, lambda x, _: x + 1).numpy()\n",
+ " # Multiply by batch size to get the total number of samples\n",
+ " total_samples = batch_count * batch_size * strategy.num_replicas_in_sync\n",
+ " print(f\"Total samples: {total_samples} and number of batches: {batch_count}\")\n",
+ " return total_samples\n",
+ " else:\n",
+ " # Unbatched case: Simply count the samples\n",
+ " total_samples = dataset.reduce(0, lambda x, _: x + 1).numpy()\n",
+ " print(f\"Total samples: {total_samples}\")\n",
+ " return total_samples\n",
+ "\n",
+ "def format_input_dataset(dataset, use_tabular_data=use_tabular_data):\n",
+ " if use_tabular_data:\n",
+ " return dataset.map(lambda img, tab, lbl: ((img, tab), lbl))\n",
+ " else:\n",
+ " return dataset.map(lambda img, lbl: (img, lbl))\n",
+ "\n",
+ "def preprocess_image(image, label):\n",
+ " image = tf.image.resize(image, [img_height, img_width])\n",
+ " return image, label\n",
+ "\n",
+ "def cast_labels(dataset, use_tabular_data=use_tabular_data):\n",
+ " if use_tabular_data:\n",
+ " return dataset.map(lambda img, tab, lbl: (img, tab, tf.cast(lbl, dtype=tf.float32)))\n",
+ " else:\n",
+ " return dataset.map(lambda img, lbl: (img, tf.cast(lbl, dtype=tf.float32)))\n",
+ "\n",
+ "# Helper to optimize dataset caching and prefetching\n",
+ "def optimize_dataset(dataset, steps, batch_size=batch_size, file_pattern=None, shuffle=True, buffer_size=buffer_size,\n",
+ " drop_remainder=True, prefix='train', force_cache=False, cache_in_memory=False, run_id=run_id,\n",
+ " cache_dir=cache_dir, check_modification_time=False):\n",
+ " \"\"\"Optimize dataset with caching, batching, shuffling, and prefetching.\"\"\"\n",
+ " #dataset = persist_dataset(dataset, file_pattern=file_pattern, dataset_steps=steps, cache_in_memory=cache_in_memory,\n",
+ " #force_cache=force_cache, prefix=prefix, run_id=run_id,\n",
+ " #cache_dir=cache_dir, check_modification_time=check_modification_time)\n",
+ " if prefix == 'train':\n",
+ " print(f\"Repeating, shuffling and batching {prefix} dataset\")\n",
+ " else:\n",
+ " print(f\"Repeating {prefix} dataset\")\n",
+ " dataset = dataset.repeat()\n",
+ " dataset = dataset.shuffle(buffer_size=buffer_size) if shuffle else dataset\n",
+ " if prefix == 'train':\n",
+ " dataset = dataset.batch(batch_size * strategy.num_replicas_in_sync, drop_remainder=drop_remainder)\n",
+ " dataset = dataset.prefetch(tf.data.AUTOTUNE)\n",
+ " return dataset\n",
+ "\n",
+ "def clean_session():\n",
+ " # Reset the session and clear the graph before each fold\n",
+ " tf.keras.backend.clear_session()\n",
+ " tf.compat.v1.reset_default_graph()\n",
+ " gc.collect()\n",
+ "\n",
+ "def deletes_old_datasets():\n",
+ " try:\n",
+ " del train_dataset\n",
+ " del val_dataset\n",
+ " except:\n",
+ " pass\n",
+ " clean_session()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "8QjZ-Gjhe2rr"
+ },
+ "source": [
+ "### Oversampling utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "R9ZvHMjbe2rr",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def oversample_minority_class_random(dataset, batch_size=batch_size, strategy=strategy, use_tabular_data=use_tabular_data):\n",
+ " \"\"\"\n",
+ " Randomly oversample the minority class by duplicating its samples within the dataset.\n",
+ " \"\"\"\n",
+ " tf.print(\"\\nRandom oversampling the minority class...\")\n",
+ " # Separate the dataset into majority and minority classes\n",
+ " minority_class = 1\n",
+ " majority_class = 0\n",
+ "\n",
+ " if use_tabular_data:\n",
+ " # For datasets with both image and tabular data\n",
+ " minority_dataset = dataset.filter(lambda image, tab, label: tf.equal(label, minority_class))\n",
+ " majority_dataset = dataset.filter(lambda image, tab, label: tf.equal(label, majority_class))\n",
+ " else:\n",
+ " # For datasets with only image data\n",
+ " minority_dataset = dataset.filter(lambda image, label: tf.equal(label, minority_class))\n",
+ " majority_dataset = dataset.filter(lambda image, label: tf.equal(label, majority_class))\n",
+ "\n",
+ " # Count the number of samples in each class\n",
+ " minority_count = count_samples_in_training_dataset(minority_dataset, batch_size=None)\n",
+ " majority_count = count_samples_in_training_dataset(majority_dataset, batch_size=None)\n",
+ "\n",
+ " sample_per_class_input_ds = dict(majority_class=majority_count, minority_class=minority_count)\n",
+ "\n",
+ " # Calculate how many samples need to be added to the minority class\n",
+ " additional_samples_needed = majority_count - minority_count\n",
+ "\n",
+ " # Randomly sample from the minority dataset to add more samples\n",
+ " minority_dataset_repeated = minority_dataset.repeat()\n",
+ " minority_dataset_sampled = minority_dataset_repeated.shuffle(buffer_size=buffer_size).take(additional_samples_needed)\n",
+ "\n",
+ " # Combine the sampled dataset with the original minority dataset\n",
+ " oversampled_minority_dataset = minority_dataset.concatenate(minority_dataset_sampled)\n",
+ "\n",
+ " # Combine the oversampled minority class with the majority class\n",
+ " oversampled_dataset = majority_dataset.concatenate(oversampled_minority_dataset)\n",
+ "\n",
+ " # Shuffle the combined dataset\n",
+ " oversampled_dataset = oversampled_dataset.shuffle(buffer_size=buffer_size)\n",
+ "\n",
+ " # Recalculate steps per epoch after oversampling\n",
+ " oversampled_sample_count = majority_count + additional_samples_needed\n",
+ " training_steps = calculate_steps_per_epoch(oversampled_sample_count, batch_size)\n",
+ "\n",
+ " sample_per_class_oversampled_ds = dict(majority_class=majority_count, minority_class=majority_count)\n",
+ "\n",
+ " return oversampled_dataset, training_steps, oversampled_sample_count, sample_per_class_input_ds, sample_per_class_oversampled_ds\n",
+ "\n",
+ "\n",
+ "def oversample_minority_with_tabular_smote_images_random(dataset, batch_size=batch_size, strategy=strategy, use_tabular_data=use_tabular_data):\n",
+ " \"\"\"\n",
+ " Randomly oversample the minority class by duplicating its samples within the dataset for images.\n",
+ " Apply SMOTE for tabular data to oversample synthetic features. The function handles both cases: with and without tabular data.\n",
+ " \"\"\"\n",
+ " # Separate the dataset into majority and minority classes\n",
+ " minority_class = 1\n",
+ " majority_class = 0\n",
+ "\n",
+ " if use_tabular_data:\n",
+ " # If using tabular data, the dataset contains (image, tabular_data, label)\n",
+ " tabular_data = []\n",
+ " images = []\n",
+ " labels = []\n",
+ "\n",
+ " for image, tabular, label in dataset:\n",
+ " images.append(image.numpy())\n",
+ " tabular_data.append(tabular.numpy())\n",
+ " labels.append(label.numpy())\n",
+ "\n",
+ " # Convert to numpy arrays\n",
+ " tabular_data = np.array(tabular_data)\n",
+ " labels = np.array(labels)\n",
+ " images = np.array(images)\n",
+ "\n",
+ " # Separate majority and minority classes\n",
+ " minority_images = images[labels == minority_class]\n",
+ " minority_tabular_data = tabular_data[labels == minority_class]\n",
+ " majority_images = images[labels == majority_class]\n",
+ " majority_tabular_data = tabular_data[labels == majority_class]\n",
+ "\n",
+ " # Apply SMOTE on the tabular data\n",
+ " smote = SMOTE(sampling_strategy='auto')\n",
+ " tabular_data_resampled, labels_resampled = smote.fit_resample(tabular_data, labels)\n",
+ "\n",
+ " # Find how many new samples were generated by SMOTE for the minority class\n",
+ " new_minority_count = np.sum(labels_resampled == minority_class) - len(minority_tabular_data)\n",
+ "\n",
+ " # For each new synthetic tabular data point, randomly pick a corresponding minority image\n",
+ " random_indices = np.random.randint(0, len(minority_images), new_minority_count)\n",
+ " minority_images_resampled = minority_images[random_indices]\n",
+ " #minority_images_resampled = np.tile(minority_images, (new_minority_count // len(minority_images) + 1, 1, 1, 1))[:new_minority_count]\n",
+ "\n",
+ "\n",
+ " # Combine the original majority data with the resampled minority data\n",
+ " combined_images = np.concatenate([majority_images, minority_images, minority_images_resampled])\n",
+ " combined_tabular_data = np.concatenate([majority_tabular_data, minority_tabular_data, tabular_data_resampled[len(minority_tabular_data):]])\n",
+ " combined_labels = np.concatenate([np.full(len(majority_images), majority_class), np.full(len(minority_images), minority_class), np.full(len(minority_images_resampled), minority_class)])\n",
+ "\n",
+ " assert len(combined_images) == len(combined_tabular_data) == len(combined_labels), \"Mismatch in dataset length!\"\n",
+ "\n",
+ " # Convert the combined data back to TensorFlow tensors\n",
+ " combined_images = tf.convert_to_tensor(combined_images, dtype=tf.float32)\n",
+ " combined_tabular_data = tf.convert_to_tensor(combined_tabular_data, dtype=tf.float32)\n",
+ " combined_labels = tf.convert_to_tensor(combined_labels, dtype=tf.int64)\n",
+ "\n",
+ " # Combine the resampled data into a dataset\n",
+ " oversampled_dataset = tf.data.Dataset.from_tensor_slices((combined_images, combined_tabular_data, combined_labels))\n",
+ "\n",
+ " else:\n",
+ " # If not using tabular data, the dataset contains (image, label)\n",
+ " minority_dataset = dataset.filter(lambda image, label: tf.equal(label, minority_class))\n",
+ " majority_dataset = dataset.filter(lambda image, label: tf.equal(label, majority_class))\n",
+ "\n",
+ " # Apply random oversampling for images only\n",
+ " minority_dataset_repeated = minority_dataset.repeat()\n",
+ " minority_dataset_sampled = minority_dataset_repeated.shuffle(buffer_size=buffer_size).take(majority_dataset.reduce(0, lambda x, _: x + 1).numpy())\n",
+ "\n",
+ " oversampled_minority_dataset = minority_dataset.concatenate(minority_dataset_sampled)\n",
+ "\n",
+ " # Combine the oversampled minority class with the majority class\n",
+ " oversampled_dataset = majority_dataset.concatenate(oversampled_minority_dataset)\n",
+ "\n",
+ " # Shuffle the combined dataset (optional)\n",
+ " oversampled_dataset = oversampled_dataset.shuffle(buffer_size=buffer_size)\n",
+ "\n",
+ " # Recalculate steps per epoch after oversampling\n",
+ " oversampled_sample_count = len(combined_labels) if use_tabular_data else count_samples_in_training_dataset(oversampled_dataset)\n",
+ " training_steps = calculate_steps_per_epoch(oversampled_sample_count, batch_size)\n",
+ "\n",
+ " return oversampled_dataset, training_steps, oversampled_sample_count"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "qHiFkU9ee2rr"
+ },
+ "source": [
+ "### Data loading utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "IsmWqZI5iAlY",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "from sklearn.model_selection import StratifiedKFold\n",
+ "from sklearn.metrics import roc_auc_score\n",
+ "import numpy as np\n",
+ "import math\n",
+ "\n",
+ "def parse_tfrecord_fn(example_proto):\n",
+ " \"\"\"Parse a TFRecord into image and optional tabular data.\"\"\"\n",
+ " feature_description = {'image': tf.io.FixedLenFeature([], tf.string), 'label': tf.io.FixedLenFeature([], tf.int64)}\n",
+ " if use_tabular_data:\n",
+ " feature_description['tabular_data'] = tf.io.FixedLenFeature([14], tf.float32)\n",
+ "\n",
+ " example = tf.io.parse_single_example(example_proto, feature_description)\n",
+ " image = tf.image.decode_jpeg(example['image'], channels=3)\n",
+ " image = tf.image.resize(image, [img_height, img_width])\n",
+ " image = image / 255.0\n",
+ "\n",
+ " #label = tf.cast(example['label'], tf.float32)\n",
+ " if use_tabular_data:\n",
+ " print('Parsing data including tabular metadata')\n",
+ " print(example['label'])\n",
+ " return image, example['tabular_data'], example['label']\n",
+ " else:\n",
+ " return image, example['label']\n",
+ "\n",
+ "# Wrap parsing function with error handling\n",
+ "def safe_parse_fn(example):\n",
+ " try:\n",
+ " return parse_tfrecord_fn(example)\n",
+ " except Exception as e:\n",
+ " tf.print(f\"Error parsing example: {e}\")\n",
+ " return None\n",
+ "\n",
+ "def load_tfrecord_dataset(file_pattern,\n",
+ " use_tabular_data=False,\n",
+ " cache_in_memory=False,\n",
+ " is_training=False,\n",
+ " batch_size=batch_size,\n",
+ " num_parallel_reads=tf.data.AUTOTUNE,\n",
+ " data_type='Train'):\n",
+ " \"\"\"Load TFRecord dataset with optional data augmentation and tabular data handling.\"\"\"\n",
+ " tf.print(f\"\\nLoading {data_type} TFRecords...\")\n",
+ "\n",
+ " # Create a dataset of file paths\n",
+ " files = tf.data.Dataset.list_files(file_pattern, shuffle=is_training)\n",
+ " tf.print(f\"Number of files: {files.cardinality().numpy()}\")\n",
+ "\n",
+ " # Interleave the files to read them in parallel\n",
+ " dataset = files.interleave(\n",
+ " lambda x: tf.data.TFRecordDataset(x, compression_type=\"GZIP\"),\n",
+ " cycle_length=num_parallel_reads,\n",
+ " num_parallel_calls=tf.data.AUTOTUNE\n",
+ " )\n",
+ "\n",
+ " # Parse the TFRecord files\n",
+ " dataset = dataset.map(parse_tfrecord_fn, num_parallel_calls=tf.data.AUTOTUNE)\n",
+ "\n",
+ " dataset = dataset.cache()\n",
+ "\n",
+ " #dataset = persist_dataset(dataset, file_pattern, cache_in_memory, '/tmp/tf_cache_2', prefix=data_type, run_id=run_id, check_modification_time=False)\n",
+ "\n",
+ " if is_training:\n",
+ " dataset = dataset.prefetch(tf.data.AUTOTUNE)\n",
+ " else:\n",
+ " dataset = dataset.batch(batch_size * strategy.num_replicas_in_sync, drop_remainder=True)\n",
+ " dataset = dataset.prefetch(tf.data.AUTOTUNE)\n",
+ " print(\"Batched the dataset using batch size:\", batch_size * strategy.num_replicas_in_sync)\n",
+ "\n",
+ " rows_count = count_samples_in_tfrecord(file_pattern)\n",
+ " print(f\"Loaded dataset at {file_pattern}, number of rows: {rows_count}\")\n",
+ " if rows_count == 0:\n",
+ " raise Exception(\"The provided dataset is empty\")\n",
+ "\n",
+ " steps_per_epoch = calculate_steps_per_epoch(rows_count, batch_size)\n",
+ "\n",
+ " print(f\"Dataset number of batches: {steps_per_epoch}\")\n",
+ "\n",
+ " return dataset, steps_per_epoch"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "18nhJxYZe2rr"
+ },
+ "source": [
+ "# Modelling"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Pk-hIsFvgVJf"
+ },
+ "source": [
+ "## Models utils"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "HabdJcwSyFts"
+ },
+ "source": [
+ "### Base model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "fo7RsAzFyFts",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Helper function to apply dense block\n",
+ "def apply_dense_block(x, units, activation='swish', l2_lambda=None, dropout_rate=None, kernel_initializer='glorot_uniform'):\n",
+ " #print(f\"Applying dense bloc with: kernel_initializer {kernel_initializer}, dropout_rate {dropout_rate}, activation {activation}, l2_lambda {l2_lambda}\")\n",
+ " print(\"Applying dense bloc with kernel_initializer: {}, dropout_rate: {}, activation: {}, l2_lambda: {}\".\n",
+ " format(kernel_initializer, dropout_rate, activation, l2_lambda))\n",
+ " kernel_reg = regularizers.l2(l2_lambda) if l2_lambda else None\n",
+ " x = layers.Dense(units, kernel_regularizer=kernel_reg, kernel_initializer=kernel_initializer, activation=activation)(x)\n",
+ " #x = layers.BatchNormalization()(x)\n",
+ " if dropout_rate:\n",
+ " x = layers.Dropout(dropout_rate)(x)\n",
+ " return x\n",
+ "\n",
+ "# Base model class for all models\n",
+ "class BaseModel:\n",
+ " def __init__(self, model_name, dropout_rate=None, l2_lambda=None, freeze_base_model=False, img_shape=img_shape,\n",
+ " num_tabular_features=14, use_tabular_data=False, kernel_initializer='glorot_uniform', run_id=run_id, **kwargs):\n",
+ " \"\"\"\n",
+ " Base model constructor. Supports flexible parameter passing through **kwargs.\n",
+ "\n",
+ " Parameters:\n",
+ " - model_name: Name of the model to build (e.g., 'resnet50').\n",
+ " - dropout_rate: Dropout rate for dense layers.\n",
+ " - l2_lambda: L2 regularization.\n",
+ " - freeze_base_model: Whether to freeze the base model layers.\n",
+ " - img_shape: Shape of the input images.\n",
+ " - num_tabular_features: Number of tabular features (for hybrid models).\n",
+ " - use_tabular_data: Whether to include tabular data as input.\n",
+ " - kernel_initializer: Initializer for the kernel weights matrix.\n",
+ " - run_id: Unique identifier for the model run (useful for saving/loading).\n",
+ " - **kwargs: Additional parameters to pass through the model pipeline.\n",
+ " \"\"\"\n",
+ " def get_param(value, default, kwargs, key):\n",
+ " if value is not None:\n",
+ " return value\n",
+ " return kwargs.get(key, default)\n",
+ " self.model_name = get_param(model_name, None, kwargs, 'model_name')\n",
+ " self.dropout_rate = get_param(dropout_rate, None, kwargs, 'dropout_rate')\n",
+ " self.l2_lambda = get_param(l2_lambda, None, kwargs, 'l2_lambda')\n",
+ " self.freeze_base_model = get_param(freeze_base_model, False, kwargs, 'freeze_base_model')\n",
+ " self.img_shape = get_param(img_shape, (224, 224, 3), kwargs, 'img_shape')\n",
+ " self.num_tabular_features = get_param(num_tabular_features, None, kwargs, 'num_tabular_features')\n",
+ " self.use_tabular_data = get_param(use_tabular_data, False, kwargs, 'use_tabular_data')\n",
+ " self.kernel_initializer = get_param(kernel_initializer, 'glorot_uniform', kwargs, 'kernel_initializer')\n",
+ " self.activation = get_param(activation, 'swish', kwargs, 'activation')\n",
+ " self.run_id = get_param(run_id, None, kwargs, 'run_id')\n",
+ " self.kwargs = kwargs\n",
+ "\n",
+ " def _set_model_inputs_(self):\n",
+ " \"\"\"Set input layers for the model, with optional tabular data.\"\"\"\n",
+ " image_input = tf.keras.Input(shape=self.img_shape, name='image_input')\n",
+ " if self.use_tabular_data:\n",
+ " tabular_input = tf.keras.Input(shape=(self.num_tabular_features,), name='tabular_input')\n",
+ " return [image_input, tabular_input]\n",
+ " return image_input\n",
+ "\n",
+ " def _set_model_inputs(self, image_input):\n",
+ " \"\"\"Set input layers for the model, with optional tabular data.\"\"\"\n",
+ " if self.use_tabular_data:\n",
+ " tabular_input = tf.keras.Input(shape=(self.num_tabular_features,), name='tabular_input')\n",
+ " return [image_input, tabular_input]\n",
+ " return image_input\n",
+ "\n",
+ " def _get_base_model(self, model_name=None):\n",
+ " \"\"\"Dynamically load the base model using the model name.\"\"\"\n",
+ " if model_name is None:\n",
+ " model_name = self.model_name\n",
+ " try:\n",
+ " parts = model_name.split('_')\n",
+ " # Trick to get the class name from the provided string \n",
+ " # Example res_net_50 -> ResNet50, vgg_19 -> VGG19\n",
+ " if len(parts) > 2:\n",
+ " model_class_name = ''.join([part.capitalize() for part in parts])\n",
+ " else:\n",
+ " model_class_name = ''.join([part.upper() for part in parts])\n",
+ " \n",
+ " base_model_cls = getattr(tf.keras.applications, model_class_name, None)\n",
+ " \n",
+ " if base_model_cls is None:\n",
+ " raise ValueError(f\"Unknown model name: {model_name}\")\n",
+ " return base_model_cls(weights=None, include_top=False, input_shape=self.img_shape)\n",
+ " except Exception as e:\n",
+ " raise ValueError(f\"Error loading model {model_name}: {str(e)}\")\n",
+ "\n",
+ " def _process_base_model(self, base_model):\n",
+ " \"\"\"Freeze/unfreeze base model and apply global pooling.\"\"\"\n",
+ " base_model.trainable = not self.freeze_base_model\n",
+ " return layers.GlobalAveragePooling2D()(base_model.output)\n",
+ "\n",
+ " def _combine_image_and_tabular_features(self, image_features, inputs):\n",
+ " \"\"\"Combine image features with tabular features, if applicable.\"\"\"\n",
+ " if self.use_tabular_data:\n",
+ " tabular_input = inputs[1]\n",
+ " tabular_features = apply_dense_block(tabular_input, 1024, l2_lambda=self.l2_lambda, dropout_rate=self.dropout_rate,\n",
+ " kernel_initializer=self.kernel_initializer, activation=self.activation)\n",
+ " tabular_features = apply_dense_block(tabular_features, 812, l2_lambda=self.l2_lambda, dropout_rate=self.dropout_rate,\n",
+ " kernel_initializer=self.kernel_initializer, activation=self.activation)\n",
+ " return layers.Concatenate()([image_features, tabular_features])\n",
+ " return image_features\n",
+ "\n",
+ " def set_model_architecture(self, model_name=None):\n",
+ " \"\"\"Set the full model architecture.\"\"\"\n",
+ " print(f'Setting model {model_name} architecture')\n",
+ " base_model = self._get_base_model(model_name)\n",
+ " image_input = base_model.input\n",
+ " inputs = self._set_model_inputs(image_input)\n",
+ " #inputs = self._set_model_inputs()\n",
+ " image_features = self._process_base_model(base_model)\n",
+ " combined_features = self._combine_image_and_tabular_features(image_features, inputs)\n",
+ "\n",
+ " # Add Dense and BatchNorm layers\n",
+ " combined_features = apply_dense_block(combined_features, 512, l2_lambda=self.l2_lambda, dropout_rate=self.dropout_rate,\n",
+ " kernel_initializer=self.kernel_initializer, activation=self.activation)\n",
+ " #combined_features = apply_dense_block(combined_features, 256, l2_lambda=self.l2_lambda, dropout_rate=self.dropout_rate,\n",
+ " #kernel_initializer=self.kernel_initializer, activation=self.activation)\n",
+ "\n",
+ " # Dropout Models (Multiple Dropouts for Ensembling)\n",
+ " combineds = [layers.Dropout(0.2)(combined_features) for _ in range(2)]\n",
+ "\n",
+ " # Apply a Dense layer after each dropout\n",
+ " outputs = [layers.Dense(1, activation='sigmoid')(combined) for combined in combineds]\n",
+ "\n",
+ " # Average the outputs for ensembling\n",
+ " final_output = layers.Average()(outputs)\n",
+ "\n",
+ " model = tf.keras.Model(inputs=inputs, outputs=final_output)\n",
+ "\n",
+ " model.base_model = base_model # Keep a reference to the base model for flexibility\n",
+ " return model\n",
+ "\n",
+ " def create_model(self, model_name=None):\n",
+ " \"\"\"\n",
+ " Create a base model using the architecture specified by model_name.\n",
+ " If model_name is None, use the instance's model_name.\n",
+ " \"\"\"\n",
+ " if model_name is None:\n",
+ " model_name = self.model_name\n",
+ " print(f\"Creating model {model_name}\")\n",
+ " model = self.set_model_architecture(model_name)\n",
+ " model.model_name = model_name\n",
+ " return model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "DP4qYDGXyFts"
+ },
+ "source": [
+ "### Ensemble model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "OFDbhcwAyFts",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# EnsembleModel class inherits from BaseModel\n",
+ "class EnsembleModel(BaseModel):\n",
+ " def __init__(self, model_name, model_names, mode='weighted', run_id=run_id, num_ensemble=2, weights=None, **kwargs):\n",
+ " super().__init__(model_name=model_name, **kwargs)\n",
+ " self.model_name = model_name\n",
+ " self.model_names = model_names\n",
+ " self.mode = mode\n",
+ " self.num_ensemble = num_ensemble\n",
+ " self.weights = weights if weights else [1.0] * len(model_names)\n",
+ " self.run_id = run_id\n",
+ "\n",
+ " def _load_pretrained_model(self, model_name):\n",
+ " \"\"\"Load a pretrained model and return it.\"\"\"\n",
+ " model = self.set_model_architecture(model_name)\n",
+ " \n",
+ " def resolve_prm_name(model_name):\n",
+ " return ''.join(model_name.split('_'))\n",
+ " \n",
+ " try:\n",
+ " model.load_weights(os.path.join(pretrained_models_dir, f'{resolve_prm_name(model_name)}_weights.h5'))\n",
+ " except Exception as e:\n",
+ " raise ValueError(f\"Error loading weights for model {model_name}: {str(e)}\")\n",
+ " \n",
+ " return model\n",
+ "\n",
+ " def create_ensemble(self, pretrained):\n",
+ " \"\"\"\n",
+ " Create an ensemble model with the selected strategy (average, max, or weighted).\n",
+ " Can load pretrained models or create new ones based on the `pretrained` flag.\n",
+ " \"\"\"\n",
+ " # Collect outputs from each model in the ensemble\n",
+ " inputs = self._set_model_inputs_() # Get the input layers\n",
+ " model_outputs = []\n",
+ "\n",
+ " for model_name in self.model_names:\n",
+ "\n",
+ " if pretrained:\n",
+ " ## Creates a pretrained model by loading the loads of the pretrained ensemble models\n",
+ " model = self._load_pretrained_model(model_name)\n",
+ " else:\n",
+ " ## Only set the architecture of the ensemble model from the individuals\n",
+ " model = self.create_model(model_name=model_name) # Create a new model from the architecture\n",
+ "\n",
+ " model_outputs.append(model(inputs))\n",
+ "\n",
+ " # Apply the selected ensembling strategy to combine the model outputs\n",
+ " ensemble_output = self._apply_ensemble_strategy(model_outputs)\n",
+ "\n",
+ " # Create and return the ensemble model\n",
+ " ensemble_model = tf.keras.Model(inputs=inputs, outputs=ensemble_output)\n",
+ "\n",
+ " ensemble_model.model_name = self.model_name # For later reuse in the training pipeline\n",
+ "\n",
+ " return ensemble_model\n",
+ "\n",
+ " def _apply_ensemble_strategy(self, outputs):\n",
+ " \"\"\"Apply different ensemble strategies.\"\"\"\n",
+ " if self.mode == 'average':\n",
+ " return layers.Average()(outputs)\n",
+ " elif self.mode == 'max':\n",
+ " return layers.Maximum()(outputs)\n",
+ " elif self.mode == 'weighted' and self.weights:\n",
+ " self.weights = [w / sum(self.weights) for w in self.weights]\n",
+ " return layers.Add()([output * weight for output, weight in zip(outputs, self.weights)])\n",
+ " elif self.mode == 'weighted_layer' and self.weights:\n",
+ " self.weights = [w / sum(self.weights) for w in self.weights]\n",
+ " weighted_outputs = [layers.Lambda(lambda x, w=weight: x * w)(output) for output, weight in zip(outputs, self.weights)]\n",
+ " return layers.Add()(weighted_outputs)\n",
+ " else:\n",
+ " raise ValueError(f\"Unknown ensemble mode: {self.mode}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "-u4Et-fIyFtt"
+ },
+ "source": [
+ "### Single model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "eAx1EBEEyFtt",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "class SingleModel(BaseModel):\n",
+ " def __init__(self, model_name, **kwargs):\n",
+ " super().__init__(model_name=model_name, **kwargs)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "JaWjxioyyFtt"
+ },
+ "source": [
+ "### Factory utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Kx-lXO7JyFtt",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "class ModelFactory:\n",
+ " @staticmethod\n",
+ " def create_model(model_type, model_names, pretrained=False, **kwargs):\n",
+ " \"\"\"Factory method to create models based on type.\"\"\"\n",
+ "\n",
+ " strategy = kwargs.get('strategy')\n",
+ "\n",
+ " with strategy.scope():\n",
+ " if model_type == 'ensemble':\n",
+ " weights = kwargs.get('weights')\n",
+ " ensemble_model_name = '_'.join(['_'.join((model_name, str(weight))) for (model_name, weight) in zip(model_names, weights)])\n",
+ " ensemble_model_name = f\"pretrained_ensemble_{ensemble_model_name}\" if pretrained else f\"compact_ensemble_{ensemble_model_name}\"\n",
+ " return EnsembleModel(model_name=ensemble_model_name, model_names=model_names, **kwargs).create_ensemble(pretrained=pretrained)\n",
+ "\n",
+ " elif model_type == 'single':\n",
+ " return SingleModel(model_name=model_names[0], **kwargs).create_model()\n",
+ " else:\n",
+ " raise ValueError(f\"Unknown model type: {model_type}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "CEKTa3dUe2rr"
+ },
+ "source": [
+ "### Loss functions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "D1qWsMHo5BZW",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "@register_keras_serializable()\n",
+ "@tf.function\n",
+ "def focal_loss(y_true, y_pred, gamma=2.0, alpha=0.25):\n",
+ " \"\"\"\n",
+ " Focal Loss for binary classification.\n",
+ "\n",
+ " Args:\n",
+ " - y_true: Ground truth labels, shape = (batch_size, 1)\n",
+ " - y_pred: Predicted labels, shape = (batch_size, 1)\n",
+ " - gamma: Focusing parameter (default is 2.0)\n",
+ " - alpha: Balancing factor (default is 0.25)\n",
+ "\n",
+ " Returns:\n",
+ " - Loss value\n",
+ " \"\"\"\n",
+ " # Clip predictions to prevent log(0) and ensure stability\n",
+ " y_pred = tf.clip_by_value(y_pred, K.epsilon(), 1. - K.epsilon())\n",
+ "\n",
+ " # Calculate cross-entropy loss\n",
+ " bce_loss = - (y_true * K.log(y_pred) + (1 - y_true) * K.log(1 - y_pred))\n",
+ "\n",
+ " # Calculate p_t and modulating factor\n",
+ " p_t = y_true * y_pred + (1 - y_true) * (1 - y_pred)\n",
+ " modulating_factor = (1 - p_t) ** gamma\n",
+ "\n",
+ " # Apply alpha balancing factor\n",
+ " alpha_factor = y_true * alpha + (1 - y_true) * (1 - alpha)\n",
+ "\n",
+ " # Compute focal loss\n",
+ " loss = alpha_factor * modulating_factor * bce_loss\n",
+ "\n",
+ " return tf.reduce_mean(loss)\n",
+ "\n",
+ "@register_keras_serializable()\n",
+ "@tf.function\n",
+ "def class_balanced_loss(y_true, y_pred, beta=0.99, samples_per_cls=None):\n",
+ " \"\"\"\n",
+ " Class-Balanced Loss function for binary classification.\n",
+ "\n",
+ " Parameters:\n",
+ " - y_true: Ground truth labels, shape = (batch_size,)\n",
+ " - y_pred: Predicted labels, shape = (batch_size,)\n",
+ " - samples_per_cls: List or array containing the number of samples for each class (class 0 and class 1).\n",
+ " - beta: Hyperparameter to adjust class balancing (default is 0.99)\n",
+ "\n",
+ " Returns:\n",
+ " - Loss value\n",
+ " \"\"\"\n",
+ " # Calculate the effective number of samples\n",
+ " effective_num = 1.0 - tf.pow(beta, samples_per_cls)\n",
+ " weights = (1.0 - beta) / tf.maximum(effective_num, tf.keras.backend.epsilon())\n",
+ " weights = weights / tf.reduce_sum(weights) # Normalize weights\n",
+ "\n",
+ " # Extract the class weights for class 0 and class 1\n",
+ " weight_for_0 = weights[0]\n",
+ " weight_for_1 = weights[1]\n",
+ "\n",
+ " # Apply weights to the true labels (binary classification)\n",
+ " weights_per_sample = y_true * weight_for_1 + (1.0 - y_true) * weight_for_0\n",
+ "\n",
+ " # Compute the binary crossentropy loss\n",
+ " loss = tf.keras.losses.binary_crossentropy(y_true, y_pred)\n",
+ "\n",
+ " # Apply class-balanced weights to the loss\n",
+ " loss = weights_per_sample * loss\n",
+ " return tf.reduce_mean(loss)\n",
+ "\n",
+ "@register_keras_serializable()\n",
+ "def combined_loss(samples_per_cls, beta=0.99, gamma=2.0, alpha=0.8, focal_loss_percent=0.5):\n",
+ " \"\"\"\n",
+ " Combined Class-Balanced Loss and Focal Loss.\n",
+ "\n",
+ " Parameters:\n",
+ " - y_true: Ground truth labels, shape = (batch_size, num_classes)\n",
+ " - y_pred: Predicted labels, shape = (batch_size, num_classes)\n",
+ "\n",
+ " Returns:\n",
+ " - Combined loss value\n",
+ " \"\"\"\n",
+ " @tf.function\n",
+ " def loss(y_true, y_pred):\n",
+ " y_true = tf.cast(y_true, tf.float32)\n",
+ " y_true = tf.reshape(y_true, [-1, 1])\n",
+ " y_pred = tf.reshape(y_pred, [-1, 1])\n",
+ "\n",
+ " cb_loss = class_balanced_loss(y_true, y_pred, beta=beta, samples_per_cls=samples_per_cls)\n",
+ " fl_loss = focal_loss(y_true, y_pred, gamma=gamma, alpha=alpha)\n",
+ " return ((1 - focal_loss_percent) * cb_loss) + (focal_loss_percent * fl_loss)\n",
+ " return loss\n",
+ "\n",
+ "##Binary class balance loss\n",
+ "@register_keras_serializable()\n",
+ "def get_class_balanced_weights(beta, samples_per_cls):\n",
+ " effective_num = 1.0 - np.power(beta, samples_per_cls)\n",
+ " effective_num = np.where(effective_num == 0, 1e-8, effective_num) # Avoid division by zero\n",
+ " weights = (1.0 - beta) / effective_num\n",
+ " weights = weights / np.sum(weights) * len(samples_per_cls)\n",
+ " return weights\n",
+ "\n",
+ "@tf.function\n",
+ "@register_keras_serializable()\n",
+ "def class_balanced_binary_cross_entropy(beta, samples_per_cls):\n",
+ " weights = get_class_balanced_weights(beta, samples_per_cls)\n",
+ " weight_for_0 = weights[0]\n",
+ " weight_for_1 = weights[1]\n",
+ "\n",
+ " def loss(y_true, y_pred):\n",
+ " y_true = tf.cast(y_true, tf.float32)\n",
+ " bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)\n",
+ " weights_per_sample = y_true * weight_for_1 + (1 - y_true) * weight_for_0\n",
+ " weighted_bce = weights_per_sample * bce\n",
+ " return tf.reduce_mean(weighted_bce)\n",
+ "\n",
+ " return loss\n",
+ "\n",
+ "@register_keras_serializable()\n",
+ "@tf.function\n",
+ "def focal_loss_keras_bce(y_true, y_pred, gamma, alpha):\n",
+ " \"\"\"\n",
+ " Focal Loss function.\n",
+ " Parameters:\n",
+ " - y_true: Ground truth labels, shape = (batch_size, num_classes)\n",
+ " - y_pred: Predicted labels, shape = (batch_size, num_classes)\n",
+ " - gamma: Focusing parameter (default is 2.0)\n",
+ " - alpha: Balancing factor (default is 0.25)\n",
+ "\n",
+ " Returns:\n",
+ " - Loss value\n",
+ " \"\"\"\n",
+ " #y_true = tf.cast(y_true, tf.float32)\n",
+ " # Compute cross entropy\n",
+ " bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)\n",
+ "\n",
+ " # Compute focal loss\n",
+ " p_t = y_true * y_pred + (1 - y_true) * (1 - y_pred)\n",
+ " alpha_factor = y_true * alpha + (1 - y_true) * (1 - alpha)\n",
+ " modulating_factor = tf.pow(1.0 - p_t, gamma)\n",
+ "\n",
+ " loss = alpha_factor * modulating_factor * bce\n",
+ " return tf.reduce_mean(loss)\n",
+ "\n",
+ "@tf.function\n",
+ "def weighted_binary_crossentropy(y_true, y_pred):\n",
+ " class_weight_0 = 0.1 # Weight for class 0 (negative)\n",
+ " class_weight_1 = 0.9 # Weight for class 1 (positive)\n",
+ "\n",
+ " weights = tf.where(tf.equal(y_true, 1), class_weight_1, class_weight_0)\n",
+ " bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)\n",
+ " weighted_bce = bce * weights\n",
+ " return tf.reduce_mean(weighted_bce)\n",
+ "\n",
+ "\n",
+ "@register_keras_serializable()\n",
+ "def class_balanced_loss_vect(y_true, y_pred, samples_per_cls, beta):\n",
+ " \"\"\"\n",
+ " Class-Balanced Loss function with samples_per_cls parameter.\n",
+ "\n",
+ " Parameters:\n",
+ " - y_true: Ground truth labels, shape = (batch_size, num_classes)\n",
+ " - y_pred: Predicted labels, shape = (batch_size, num_classes)\n",
+ " - samples_per_cls: List or array containing the number of samples for each class.\n",
+ " - beta: Hyperparameter to adjust class balancing (usually close to 1.0, default is 0.9999)\n",
+ "\n",
+ " Returns:\n",
+ " - Loss value\n",
+ " \"\"\"\n",
+ " #y_true = tf.cast(y_true, tf.float32)\n",
+ " # Calculate the effective number of samples\n",
+ " effective_num = 1.0 - tf.pow(beta, samples_per_cls)\n",
+ " weights = (1.0 - beta) / tf.maximum(effective_num, tf.keras.backend.epsilon())\n",
+ " weights = weights / tf.reduce_sum(weights) # Normalize weights\n",
+ "\n",
+ " # Apply weights to the true labels\n",
+ " weights = tf.reduce_sum(weights * y_true, axis=-1)\n",
+ "\n",
+ " # Compute the binary crossentropy loss\n",
+ " loss = tf.keras.losses.binary_crossentropy(y_true, y_pred)\n",
+ "\n",
+ " # Apply class-balanced weights to the loss\n",
+ " loss = weights * loss\n",
+ " return tf.reduce_mean(loss)\n",
+ "\n",
+ "@tf.function\n",
+ "def weighted_binary_crossentropy(y_true, y_pred):\n",
+ " # Define class weights\n",
+ " class_weight_0 = 0.1 # Weight for class 0 (negative)\n",
+ " class_weight_1 = 0.9 # Weight for class 1 (positive)\n",
+ "\n",
+ " # Create a tensor of weights based on the true labels\n",
+ " weights = tf.where(tf.equal(y_true, 1), class_weight_1, class_weight_0)\n",
+ "\n",
+ " # Calculate binary crossentropy\n",
+ " bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)\n",
+ "\n",
+ " # Apply the weights to the loss\n",
+ " weighted_bce = bce * weights\n",
+ "\n",
+ " # Return the mean loss\n",
+ " return tf.reduce_mean(weighted_bce)\n",
+ "\n",
+ "def focal_loss_fix(gamma=2., alpha=0.25):\n",
+ " def focal_loss_fixed(y_true, y_pred):\n",
+ " epsilon = K.epsilon()\n",
+ " y_pred = K.clip(y_pred, epsilon, 1. - epsilon)\n",
+ " y_true = K.cast(y_true, K.floatx())\n",
+ "\n",
+ " alpha_t = y_true * alpha + (K.ones_like(y_true) - y_true) * (1 - alpha)\n",
+ " p_t = y_true * y_pred + (K.ones_like(y_true) - y_true) * (1 - y_pred)\n",
+ " fl = - alpha_t * K.pow((K.ones_like(y_true) - p_t), gamma) * K.log(p_t)\n",
+ " return K.mean(fl)\n",
+ " return focal_loss_fixed\n",
+ "\n",
+ "def set_binary_crossentropy_weighted_loss(positive_weights=np.array([5.291666666666667]), negative_weights=np.array([0.5521739130434783]), epsilon=1e-7):\n",
+ " \"\"\"\n",
+ " Note: Imported from the AI for Medicine Specialization course on Coursera: Assignment 1 Week 1.\n",
+ " Returns weighted binary cross entropy loss function given negative weights and positive weights.\n",
+ "\n",
+ " Args:\n",
+ " positive_weights (np.array): array of positive weights for each class, size (num_classes)\n",
+ " negative_weights (np.array): array of negative weights for each class, size (num_classes)\n",
+ "\n",
+ " Returns:\n",
+ " weighted_loss (function): weighted loss function\n",
+ " \"\"\"\n",
+ " def binary_crossentropy_weighted_loss(y_true, y_pred):\n",
+ " \"\"\"\n",
+ " Returns weighted binary cross entropy loss value.\n",
+ "\n",
+ " Args:\n",
+ " y_true (Tensor): Tensor of true labels, size is (num_examples, num_classes)\n",
+ " y_pred (Tensor): Tensor of predicted labels, size is (num_examples, num_classes)\n",
+ "\n",
+ " Returns:\n",
+ " loss (Tensor): overall scalar loss summed across all classes\n",
+ " \"\"\"\n",
+ " y_true = tf.cast(y_true, tf.float32)\n",
+ " y_pred = tf.cast(y_pred, tf.float32)\n",
+ "\n",
+ " # initialize loss to zero\n",
+ " loss = 0.0\n",
+ " #y_true = tf.cast(y_true, tf.float32)\n",
+ " for i in range(len(positive_weights)):\n",
+ " # for each class, add average weighted loss for that class\n",
+ " loss += -1 * K.mean((positive_weights[i] * y_true[:, i] * K.log(y_pred[:, i] + epsilon) +\n",
+ " negative_weights[i] * (1 - y_true[:, i]) * K.log(1 - y_pred[:, i] + epsilon)))\n",
+ " return loss\n",
+ "\n",
+ " return binary_crossentropy_weighted_loss"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "XTzGXIf9e2rs"
+ },
+ "source": [
+ "### Metrics"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "BqlX0cuLgTrC",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def score(y_true, y_pred, min_tpr: float = 0.80) -> float:\n",
+ " v_gt = abs(np.asarray(y_true) - 1)\n",
+ " v_pred = np.array([1.0 - x for x in y_pred])\n",
+ " max_fpr = abs(1 - min_tpr)\n",
+ " partial_auc_scaled = roc_auc_score(v_gt, v_pred, max_fpr=max_fpr)\n",
+ " partial_auc = 0.5 * max_fpr**2 + (max_fpr - 0.5 * max_fpr**2) / (1.0 - 0.5) * (partial_auc_scaled - 0.5)\n",
+ " return partial_auc\n",
+ "\n",
+ "@register_keras_serializable()\n",
+ "class F1Score(tf.keras.metrics.Metric):\n",
+ " def __init__(self, name='f1_score', **kwargs):\n",
+ " super(F1Score, self).__init__(name=name, **kwargs)\n",
+ " self.precision = tf.keras.metrics.Precision()\n",
+ " self.recall = tf.keras.metrics.Recall()\n",
+ "\n",
+ " def update_state(self, y_true, y_pred, sample_weight=None):\n",
+ " self.precision.update_state(y_true, y_pred, sample_weight)\n",
+ " self.recall.update_state(y_true, y_pred, sample_weight)\n",
+ "\n",
+ " def result(self):\n",
+ " precision = self.precision.result()\n",
+ " recall = self.recall.result()\n",
+ " return 2 * ((precision * recall) / (precision + recall + tf.keras.backend.epsilon()))\n",
+ "\n",
+ " def reset_state(self):\n",
+ " self.precision.reset_state()\n",
+ " self.recall.reset_state()\n",
+ "\n",
+ "def get_labels_and_predictions(model, dataset, steps):\n",
+ " \"\"\"Return true labels and predictions from the model.\"\"\"\n",
+ " y_true, y_pred = [], []\n",
+ " for x_batch, y_batch in dataset.take(steps):\n",
+ " y_pred_batch = model.predict_on_batch(x_batch).ravel()\n",
+ " y_true.append(y_batch)\n",
+ " y_pred.append(y_pred_batch)\n",
+ " return tf.concat(y_true, axis=0), tf.concat(y_pred, axis=0)\n",
+ "\n",
+ "def evaluate_model(model, eval_dataset, evaluation_steps, dataset_name='eval_dataset'):\n",
+ " print('Evaluating', dataset_name, 'with', evaluation_steps, 'steps')\n",
+ " y_val, y_pred = get_labels_and_predictions(model, eval_dataset, evaluation_steps)\n",
+ " partial_auc = score(y_val, y_pred)\n",
+ " print(f\"Partial AUC for {dataset_name}: {partial_auc}\")\n",
+ "\n",
+ "def get_memory_usage():\n",
+ " # memory_usage returns a list, so we get the first item\n",
+ " mem = memory_usage(-1, interval=0.1, timeout=1)[0] # Return the first value from the list in MB\n",
+ " mem = f\"{mem:.2f} MB\"\n",
+ " return mem\n",
+ "\n",
+ "def reset_session_and_get_memory():\n",
+ " \"\"\"Cleans up the session and retrieves the initial memory usage.\"\"\"\n",
+ " clean_session()\n",
+ " return get_memory_usage()\n",
+ "\n",
+ "def log_memory_usage(stage, memory_usage):\n",
+ " \"\"\"Logs the memory usage with a specific stage identifier.\"\"\"\n",
+ " print(f\"{stage} memory usage: {memory_usage}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "fjddtd7ve2rt"
+ },
+ "source": [
+ "### Callbacks"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "GqAeIwVPgdqu",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "class PartialAUCCallback(tf.keras.callbacks.Callback):\n",
+ " def __init__(self, validation_data, validation_steps, monitor, train_data = None, steps_per_epoch=None, min_tpr=0.8):\n",
+ " super(PartialAUCCallback, self).__init__()\n",
+ " self.validation_data = validation_data\n",
+ " self.validation_steps = validation_steps\n",
+ " self.monitor = monitor\n",
+ " self.train_data = train_data\n",
+ " self.steps_per_epoch = steps_per_epoch\n",
+ " self.min_tpr = min_tpr\n",
+ " self.max_fpr = 1.0 - min_tpr\n",
+ " self.best_partial_auc = -float('inf') # Initialize to a very low value\n",
+ " self.best_epoch = 0\n",
+ "\n",
+ " def on_epoch_end(self, epoch, logs=None):\n",
+ " logs = logs or {}\n",
+ "\n",
+ " # Compute Partial AUC on Training Data if available\n",
+ " if self.train_data:\n",
+ " train_labels, train_predictions = get_labels_and_predictions(self.model,\n",
+ " self.train_data,\n",
+ " self.steps_per_epoch)\n",
+ " train_partial_auc = self.compute_auc(train_labels, train_predictions)\n",
+ " self.log_partial_auc(logs, train_partial_auc, 'partial_auc', epoch)\n",
+ "\n",
+ " PartialAUCCallback.log_classification_report(train_labels, train_predictions, epoch, 'Train')\n",
+ "\n",
+ " #Validation computations\n",
+ " val_labels, val_predictions = get_labels_and_predictions(self.model,\n",
+ " self.validation_data,\n",
+ " self.validation_steps)\n",
+ " # Calculate the partial AUC\n",
+ " val_partial_auc = self.compute_auc(val_labels, val_predictions)\n",
+ " self.log_partial_auc(logs, val_partial_auc, 'val_partial_auc', epoch)\n",
+ " PartialAUCCallback.log_classification_report(val_labels, val_predictions, epoch)\n",
+ "\n",
+ "\n",
+ " def log_partial_auc(self, logs, partial_auc, pauc_metric_name, epoch):\n",
+ " logs[pauc_metric_name] = partial_auc\n",
+ " if self.monitor == pauc_metric_name:\n",
+ " # Store the best partial AUC\n",
+ " if partial_auc > self.best_partial_auc:\n",
+ " self.best_partial_auc = partial_auc\n",
+ " self.best_epoch = epoch\n",
+ "\n",
+ " # Add the partial_auc to logs so it can be used in Keras Tuner\n",
+ " logs[pauc_metric_name] = partial_auc\n",
+ " tf.print(f'\\nEpoch {epoch + 1}: Partial AUC = {partial_auc:.4f}')\n",
+ "\n",
+ " @staticmethod\n",
+ " def log_classification_report(labels, predictions, epoch, name='Validation'):\n",
+ " # Convert probabilities to binary predictions\n",
+ " bin_preds = np.round(predictions).astype(int)\n",
+ " # Generate the classification report\n",
+ " report = classification_report(labels, bin_preds, labels= [1, 0],\n",
+ " target_names=['positives', 'negatives'])\n",
+ " # Print the report\n",
+ " tf.print(f\"\\n{name} classification report for epoch {epoch + 1}:\\n{report}\")\n",
+ "\n",
+ " def compute_auc(self, y_true, y_pred, max_fpr=0.2):\n",
+ " v_gt = np.abs(y_true - 1)\n",
+ " v_pred = 1.0 - y_pred\n",
+ " partial_auc_scaled = roc_auc_score(v_gt, v_pred, max_fpr=max_fpr)\n",
+ " partial_auc = 0.5 * max_fpr**2 + (max_fpr - 0.5 * max_fpr**2) / (1.0 - 0.5) * (partial_auc_scaled - 0.5)\n",
+ " return partial_auc\n",
+ "\n",
+ " def compute_auc_kaggle(self, y_true, y_pred):\n",
+ " y_true = tf.cast(y_true, tf.float32).numpy()\n",
+ " y_pred = tf.cast(y_pred, tf.float32).numpy()\n",
+ "\n",
+ " # Invert classes as needed for minority focus\n",
+ " v_gt = 1 - y_true\n",
+ " v_pred = 1 - y_pred\n",
+ " fpr, tpr, _ = roc_curve(v_gt, v_pred)\n",
+ "\n",
+ " # Find where FPR reaches max_fpr\n",
+ " stop = np.searchsorted(fpr, self.max_fpr, 'right')\n",
+ " if stop == 0: # If max_fpr is very low and FPR doesn't reach it\n",
+ " return 0.0\n",
+ "\n",
+ " x_interp = [fpr[stop - 1], fpr[stop]]\n",
+ " y_interp = [tpr[stop - 1], tpr[stop]]\n",
+ " tpr_interp = np.interp(self.max_fpr, x_interp, y_interp)\n",
+ "\n",
+ " # Append this interpolated point and calculate AUC\n",
+ " tpr = np.append(tpr[:stop], tpr_interp)\n",
+ " fpr = np.append(fpr[:stop], self.max_fpr)\n",
+ " return auc(fpr, tpr)\n",
+ "\n",
+ " def get_best_partial_auc(self):\n",
+ " return self.best_partial_auc\n",
+ "\n",
+ " def get_best_epoch(self):\n",
+ " return self.best_epoch\n",
+ "\n",
+ " def __deepcopy__(self, memo):\n",
+ " # Skip deep copying the validation data to avoid issues\n",
+ " return PartialAUCCallback(self.validation_data, self.validation_steps,\n",
+ " self.monitor, self.train_data,\n",
+ " self.steps_per_epoch, self.min_tpr)\n",
+ "\n",
+ "class LearningRatePrinterCallback(tf.keras.callbacks.Callback):\n",
+ " def on_epoch_end(self, epoch, logs=None):\n",
+ " # Access the learning rate from the optimizer\n",
+ " lr = self.model.optimizer.learning_rate\n",
+ " # If the learning rate is a learning rate schedule (like CyclicalLearningRate), evaluate it\n",
+ " if isinstance(lr, tf.keras.optimizers.schedules.LearningRateSchedule):\n",
+ " lr = lr(self.model.optimizer.iterations)\n",
+ " tf.print(f\"\\nLearning rate at the end of epoch {epoch + 1}: {tf.keras.backend.get_value(lr)}\")\n",
+ "\n",
+ "\n",
+ "# Custom Callback to monitor memory after each epoch\n",
+ "class MemoryUsageCallback(tf.keras.callbacks.Callback):\n",
+ " def on_epoch_end(self, epoch, logs=None):\n",
+ " process = psutil.Process(os.getpid())\n",
+ " mem = process.memory_info().rss / (1024 * 1024) # RSS memory in MB\n",
+ " print(f\"Memory usage after epoch {epoch + 1}: {mem:.2f} MB\")\n",
+ " #tf.keras.backend.clear_session()\n",
+ " #tf.compat.v1.reset_default_graph()\n",
+ " gc.collect()\n",
+ " #clean_session()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "9TNOAcVd6QWp"
+ },
+ "source": [
+ "### Optimizers"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Jer9dL9s0pIh",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def clean_metric_name(metric_name):\n",
+ " # Use regular expression to check if the string ends with _ followed by digits\n",
+ " if re.search(r'_\\d+$', metric_name):\n",
+ " # Remove the _ and the digits at the end\n",
+ " return re.sub(r'_\\d+$', '', metric_name)\n",
+ " return metric_name\n",
+ "\n",
+ "# Define a custom learning rate scheduler for cyclical learning rates\n",
+ "class CyclicalLearningRate(tf.keras.optimizers.schedules.LearningRateSchedule):\n",
+ " def __init__(self, initial_learning_rate, maximal_learning_rate, step_size):\n",
+ " self.initial_learning_rate = tf.cast(initial_learning_rate, tf.float32)\n",
+ " self.maximal_learning_rate = tf.cast(maximal_learning_rate, tf.float32)\n",
+ " self.step_size = tf.cast(step_size, tf.float32)\n",
+ "\n",
+ " def __call__(self, step):\n",
+ " step = tf.cast(step, tf.float32)\n",
+ " cycle = tf.floor(1 + step / (2 * self.step_size))\n",
+ " x = tf.abs(step / self.step_size - 2 * cycle + 1)\n",
+ " lr = self.initial_learning_rate + (self.maximal_learning_rate - self.initial_learning_rate) * tf.maximum(0.0, (1 - x))\n",
+ " return lr\n",
+ "\n",
+ " def get_config(self):\n",
+ " return {\n",
+ " \"initial_learning_rate\": self.initial_learning_rate.numpy(),\n",
+ " \"maximal_learning_rate\": self.maximal_learning_rate.numpy(),\n",
+ " \"step_size\": self.step_size.numpy(),\n",
+ " }\n",
+ "\n",
+ "# Set the parameters for the CLR\n",
+ "initial_learning_rate = 1e-6 # Minimum LR\n",
+ "maximal_learning_rate = 1e-4 # Maximum LR\n",
+ "step_size = 50 # Number of steps in half a cycle\n",
+ "def set_optimizer(optimizer, learning_rate=1e-4, use_nesterov_sgd=False, use_amsgrad_adam=False,\n",
+ " initial_learning_rate=1e-6,\n",
+ " maximal_learning_rate=1e-4,\n",
+ " step_size=50\n",
+ " ):\n",
+ " if optimizer == \"sgd\":\n",
+ " optimizer = optimizers.SGD(\n",
+ " learning_rate=learning_rate,\n",
+ " momentum=0.9,\n",
+ " nesterov=use_nesterov_sgd\n",
+ " )\n",
+ "\n",
+ " elif optimizer == \"adam\":\n",
+ " optimizer = optimizers.Adam(\n",
+ " learning_rate=learning_rate,\n",
+ " beta_1=0.9,\n",
+ " beta_2=0.999,\n",
+ " epsilon=0.1,\n",
+ " amsgrad=use_amsgrad_adam\n",
+ " )\n",
+ "\n",
+ " elif optimizer == \"nadam\":\n",
+ " optimizer = optimizers.Nadam(\n",
+ " learning_rate=learning_rate,\n",
+ " beta_1=0.9,\n",
+ " beta_2=0.999,\n",
+ " epsilon=0.1\n",
+ " )\n",
+ " elif optimizer == \"adamw\":\n",
+ " optimizer = optimizers.AdamW(\n",
+ " learning_rate=learning_rate,\n",
+ " beta_1=0.9,\n",
+ " beta_2=0.999,\n",
+ " epsilon=0.1\n",
+ " )\n",
+ " elif optimizer == \"adamax\":\n",
+ " optimizer = optimizers.Adamax(\n",
+ " learning_rate=learning_rate,\n",
+ " beta_1=0.9,\n",
+ " beta_2=0.999,\n",
+ " epsilon=0.1\n",
+ " )\n",
+ " elif optimizer == \"radam\":\n",
+ " optimizer = tfa.optimizers.RectifiedAdam(\n",
+ " learning_rate=learning_rate,\n",
+ " beta_1=0.9,\n",
+ " beta_2=0.999,\n",
+ " epsilon=0.1\n",
+ " )\n",
+ " elif optimizer == \"rmsprop\":\n",
+ " optimizer = optimizers.RMSprop(\n",
+ " learning_rate=learning_rate,\n",
+ " rho=0.9,\n",
+ " momentum=0.0,\n",
+ " epsilon=0.1,\n",
+ " centered=False\n",
+ " )\n",
+ " elif optimizer == \"nadam_cyclical\":\n",
+ " optimizer = optimizers.Nadam(\n",
+ " learning_rate=CyclicalLearningRate(\n",
+ " initial_learning_rate=initial_learning_rate,\n",
+ " maximal_learning_rate=maximal_learning_rate,\n",
+ " step_size=step_size\n",
+ " )\n",
+ " )\n",
+ " elif optimizer == \"radam_cyclical\":\n",
+ " optimizer = tfa.optimizers.RectifiedAdam(\n",
+ " learning_rate=CyclicalLearningRate(\n",
+ " initial_learning_rate=initial_learning_rate,\n",
+ " maximal_learning_rate=maximal_learning_rate,\n",
+ " step_size=step_size\n",
+ " )\n",
+ " )\n",
+ " elif optimizer == \"adamw_cyclical\":\n",
+ " optimizer = optimizers.AdamW(\n",
+ " learning_rate=CyclicalLearningRate(\n",
+ " initial_learning_rate=initial_learning_rate,\n",
+ " maximal_learning_rate=maximal_learning_rate,\n",
+ " step_size=step_size\n",
+ " )\n",
+ " )\n",
+ " return optimizer\n",
+ "\n",
+ "class OneCycleScheduler(tf.keras.callbacks.Callback):\n",
+ " def __init__(self, max_lr, steps_per_epoch, epochs, start_lr=None, last_epoch_lr=None):\n",
+ " super(OneCycleScheduler, self).__init__()\n",
+ " self.max_lr = max_lr\n",
+ " self.steps_per_epoch = steps_per_epoch\n",
+ " self.epochs = epochs\n",
+ " self.total_steps = steps_per_epoch * epochs\n",
+ " self.current_step = 0\n",
+ "\n",
+ " if start_lr is None:\n",
+ " self.start_lr = max_lr / 25 # A commonly used starting ratio\n",
+ " else:\n",
+ " self.start_lr = start_lr\n",
+ "\n",
+ " if last_epoch_lr is None:\n",
+ " self.last_epoch_lr = self.start_lr / 1e4 # A small value to anneal towards\n",
+ " else:\n",
+ " self.last_epoch_lr = last_epoch_lr\n",
+ "\n",
+ " def on_batch_begin(self, batch, logs=None):\n",
+ " # Update learning rate based on current step\n",
+ " self.current_step += 1\n",
+ " lr = self.calc_lr()\n",
+ " #tf.keras.backend.set_value(self.model.optimizer[0].lr, lr)\n",
+ " tf.keras.backend.set_value(self.model.optimizer.lr, lr)\n",
+ "\n",
+ " def calc_lr(self):\n",
+ " # 1Cycle Learning Rate Schedule: Increases then decreases\n",
+ " step_ratio = self.current_step / self.total_steps\n",
+ " if step_ratio < 0.6:\n",
+ " # Increase learning rate for first half of training\n",
+ " lr = self.start_lr + (self.max_lr - self.start_lr) * (step_ratio * 2)\n",
+ " else:\n",
+ " # Decrease learning rate for the second half of training\n",
+ " lr = self.max_lr - (self.max_lr - self.last_epoch_lr) * ((step_ratio - 0.5) * 2)\n",
+ " return lr\n",
+ "\n",
+ "class SGDRScheduler(tf.keras.callbacks.Callback):\n",
+ " '''Cosine annealing learning rate scheduler with periodic restarts.'''\n",
+ " def __init__(self, min_lr, max_lr, steps_per_epoch, lr_decay=1, cycle_length=10, mult_factor=2):\n",
+ " super(SGDRScheduler, self).__init__()\n",
+ " self.min_lr = min_lr\n",
+ " self.max_lr = max_lr\n",
+ " self.lr_decay = lr_decay\n",
+ " self.steps_per_epoch = steps_per_epoch\n",
+ " self.batch_since_restart = 0\n",
+ " self.next_restart = cycle_length\n",
+ " self.cycle_length = cycle_length\n",
+ " self.mult_factor = mult_factor\n",
+ "\n",
+ " def on_batch_begin(self, batch, logs=None):\n",
+ " '''Adjust the learning rate at the start of each batch.'''\n",
+ " fraction_to_restart = self.batch_since_restart / (self.steps_per_epoch * self.cycle_length)\n",
+ " lr = self.min_lr + 0.5 * (self.max_lr - self.min_lr) * (1 + np.cos(fraction_to_restart * np.pi))\n",
+ " tf.keras.backend.set_value(self.model.optimizer.lr, lr)\n",
+ " self.batch_since_restart += 1\n",
+ "\n",
+ " def on_epoch_end(self, epoch, logs=None):\n",
+ " '''Check for end of current cycle, apply restarts when necessary.'''\n",
+ " if epoch + 1 == self.next_restart:\n",
+ " self.batch_since_restart = 0\n",
+ " self.next_restart += self.cycle_length\n",
+ " self.cycle_length = int(self.cycle_length * self.mult_factor)\n",
+ " self.max_lr *= self.lr_decay\n",
+ " self.min_lr *= self.lr_decay"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "7RPt5to1e2ru"
+ },
+ "source": [
+ "## Training utils"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "GY3gVK7Ze2ru"
+ },
+ "source": [
+ "### Plotting metrics"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "x_sTrPhlblvI",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def plot_training_history(history, model_name, metrics=['f1_score', 'precision', 'recall', 'loss', 'accuracy', 'partial_auc', 'auc']):\n",
+ " plt.figure(figsize=(16, 16))\n",
+ " for i, metric in enumerate(metrics):\n",
+ " plt.subplot(4, 2, i + 1)\n",
+ " plt.title(metric.capitalize())\n",
+ " if metric in history:\n",
+ " plt.plot(history[metric], label=metric)\n",
+ " if f'val_{metric}' in history:\n",
+ " plt.plot(history[f'val_{metric}'], label=f'val_{metric}')\n",
+ " plt.legend(loc='upper right')\n",
+ " plt.tight_layout()\n",
+ " plt.title(model_name, loc='center')\n",
+ " plt.savefig(f'{models_dir}/{model_name}_training_{run_id}')\n",
+ " plt.show()\n",
+ "\n",
+ "def normalize_history_keys(history):\n",
+ " \"\"\"\n",
+ " Normalize the keys in the history dictionary using clean_metric_name.\n",
+ " \"\"\"\n",
+ " return {clean_metric_name(key): value for key, value in history.items()}\n",
+ "\n",
+ "def print_infos(training_mode, fold_no, messages):\n",
+ " if training_mode == 'train-validation-split':\n",
+ " for message in messages:\n",
+ " print(message.capitalize())\n",
+ " else:\n",
+ " for message in messages:\n",
+ " print(f\"Fold {fold_no + 1}\", message)\n",
+ "\n",
+ "def merge_histories(history_initial, history_fine_tune):\n",
+ " # Merge histories\n",
+ " history = defaultdict(list)\n",
+ " if history_initial is not None:\n",
+ " # Normalize the keys in history_initial\n",
+ " history_initial_cleaned = normalize_history_keys(history_initial.history)\n",
+ " for key, value in history_initial_cleaned.items():\n",
+ " history[key] = value\n",
+ " else:\n",
+ " # Normalize the keys in history_fine_tune\n",
+ " history_fine_tune_cleaned = normalize_history_keys(history_fine_tune.history)\n",
+ " for key, value in history_fine_tune_cleaned.items():\n",
+ " history[key].extend(value)\n",
+ " history = dict(history)\n",
+ " return history"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "eJivwO4ve2ru"
+ },
+ "source": [
+ "### Data loading"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "hYWdU0F-e2ru",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def load_train_val_dataset(file_pattern, shuffle_dataset_at_each_call=True, cache_dataset=True, cache_in_memory=False,\n",
+ " cache_dir=cache_dir, prefix=\"train_val\", run_id=run_id, check_modification_time=False,\n",
+ " is_training=True, force_cache=True, use_tabular_data=use_tabular_data, batch_size=batch_size):\n",
+ " print(\"Reloading and shuffling dataset...\")\n",
+ " try:\n",
+ " del dataset\n",
+ " gc.collect()\n",
+ " except:\n",
+ " pass\n",
+ " gc.collect()\n",
+ "\n",
+ " print('batch_size', batch_size)\n",
+ " print('file_pattern', file_pattern)\n",
+ " print('is_training', is_training)\n",
+ "\n",
+ " print('cache_in_memory', cache_in_memory)\n",
+ "\n",
+ " # Load dataset once and cache\n",
+ " dataset, dataset_steps = load_tfrecord_dataset(file_pattern=file_pattern, is_training=is_training,\n",
+ " batch_size=batch_size, cache_in_memory=cache_in_memory)\n",
+ "\n",
+ " #dataset = cast_labels(dataset, use_tabular_data)\n",
+ "\n",
+ "\n",
+ " #dataset = persist_dataset(dataset, file_pattern=train_file_pattern, dataset_steps=dataset_steps, cache_in_memory=cache_in_memory,\n",
+ " #cache_dir=cache_dir, prefix=prefix, run_id=run_id, check_modification_time=check_modification_time, force_cache=force_cache)\n",
+ "\n",
+ " # Extract labels for stratified splitting\n",
+ " use_cross_validation = False\n",
+ " if use_cross_validation :\n",
+ " labels = []\n",
+ " for *_, lbl in dataset:\n",
+ " labels.append(lbl.numpy())\n",
+ "\n",
+ " labels = np.array(labels)\n",
+ "\n",
+ " seed = random.randint(0, 1e9) if shuffle_dataset_at_each_call else 42\n",
+ "\n",
+ " if use_cross_validation:\n",
+ " # Generate stratified split indices for cross-validation\n",
+ " split_indices = stratified_split_indices(labels, n_splits=5, shuffle=True, random_state=seed)\n",
+ " else:\n",
+ " # Create a stratified 80/20 train-test split\n",
+ " sss = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=seed)\n",
+ " train_index, val_index = next(sss.split(np.zeros(len(labels)), labels))\n",
+ " split_indices = [(train_index, val_index)] # Single split for train-test\n",
+ " return dataset, split_indices, dataset_steps\n",
+ " else:\n",
+ " return dataset, dataset_steps\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "VNQ4pRa-yFtu"
+ },
+ "source": [
+ "### Training pipeline common"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "LZImUCJeTqk2",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def training_pipeline(model_name, model_names=None, model=None, train_file_pattern=None, **kwargs):\n",
+ " run_id = kwargs.get('run_id', None)\n",
+ " strategy = kwargs.get('strategy', None)\n",
+ " batch_size = kwargs.get('batch_size', None)\n",
+ " val_batch_size = kwargs.get('val_batch_size', None)\n",
+ " cache_in_memory = kwargs.get('cache_in_memory', None)\n",
+ " freeze_base_model = kwargs.get('freeze_base_model', None)\n",
+ " loss = kwargs.get('loss', None)\n",
+ " shuffle_dataset_at_each_call = kwargs.get('shuffle_dataset_at_each_call', None)\n",
+ " do_fine_tuning = kwargs.get('do_fine_tuning', None)\n",
+ " initial_epochs = kwargs.get('initial_epochs', None)\n",
+ " fine_tune_epochs = kwargs.get('fine_tune_epochs', None)\n",
+ " oversample_minority_class = kwargs.get('oversample_minority_class', None)\n",
+ " augment_train_data = kwargs.get('augment_train_data', None)\n",
+ " use_cross_validation = kwargs.get('use_cross_validation', None)\n",
+ " use_tabular_data = kwargs.get('use_tabular_data', None)\n",
+ " execution_env = kwargs.get('execution_env', None)\n",
+ " total_epochs = initial_epochs + fine_tune_epochs if freeze_base_model else fine_tune_epochs\n",
+ "\n",
+ " # cleaning the session\n",
+ " deletes_old_datasets()\n",
+ "\n",
+ " partial_aucs = []\n",
+ " training_mode = 'train-validation-split'\n",
+ " print(\"Kwargs from training_pipeline \", kwargs)\n",
+ "\n",
+ " # Load train dataset once and cache\n",
+ " train_dataset, training_steps = load_tfrecord_dataset(file_pattern=train_file_pattern,\n",
+ " batch_size=batch_size,\n",
+ " is_training=True,\n",
+ " cache_in_memory=cache_in_memory)\n",
+ " # Validation dataset\n",
+ " val_dataset, validation_steps = load_tfrecord_dataset(f'{tf_records_eval_dir}/*.tfrecord',\n",
+ " batch_size=val_batch_size,\n",
+ " is_training=False,\n",
+ " cache_in_memory=cache_in_memory)\n",
+ "\n",
+ " print_infos(training_mode, None, [f\"start training model {model_name}...\", \n",
+ " f\"training size: {training_steps * batch_size * strategy.num_replicas_in_sync}\",\n",
+ " f\"validation size: {validation_steps * val_batch_size * strategy.num_replicas_in_sync}\"])\n",
+ "\n",
+ " # Oversample the minority class within the fold\n",
+ " if oversample_minority_class:\n",
+ " (train_dataset, training_steps, training_total_samples, sample_per_class_input_ds, \n",
+ " sample_per_class_oversampled_ds) = oversample_minority_class_random(train_dataset, batch_size, strategy)\n",
+ " print('Total samples in train dataset after oversampling', training_total_samples)\n",
+ "\n",
+ " # Calculate class weights\n",
+ " class_weights_ovs = calculate_class_weights(sample_per_class_oversampled_ds)\n",
+ " class_weights_input_ds = calculate_class_weights(sample_per_class_input_ds)\n",
+ " print_infos(training_mode, None, [f\"samples per class: {sample_per_class_input_ds}\", f\"class weights: {class_weights_input_ds}\",\n",
+ " f\"training steps: {training_steps}, evaluation steps: {validation_steps}\"])\n",
+ "\n",
+ " # Training data processing\n",
+ " train_dataset = optimize_dataset(train_dataset, file_pattern=None, steps=training_steps, cache_in_memory=cache_in_memory,\n",
+ " cache_dir=cache_dir, prefix='train', run_id=run_id, check_modification_time=False, force_cache=True)\n",
+ "\n",
+ " if augment_train_data:\n",
+ " train_dataset = apply_augmentations(train_dataset)\n",
+ " train_dataset = format_input_dataset(train_dataset, use_tabular_data)\n",
+ " train_dataset = train_dataset.prefetch(tf.data.AUTOTUNE)\n",
+ "\n",
+ " # Validation data processing\n",
+ " val_dataset = format_input_dataset(val_dataset, use_tabular_data)\n",
+ " val_dataset = optimize_dataset(val_dataset, file_pattern=None, steps=validation_steps, cache_in_memory=cache_in_memory,\n",
+ " cache_dir=cache_dir, prefix='val', run_id=run_id, check_modification_time=False,\n",
+ " force_cache=True)\n",
+ "\n",
+ " # Callbacks\n",
+ " memory_callback = MemoryUsageCallback()\n",
+ " early_stopping_callback = EarlyStopping(monitor='val_loss', patience=20, restore_best_weights=True)\n",
+ " partial_auc_callback = PartialAUCCallback(validation_data=val_dataset, validation_steps=validation_steps,\n",
+ " monitor='val_partial_auc')\n",
+ " one_cycle_scheduler_callback = OneCycleScheduler(max_lr=1e-5, steps_per_epoch=training_steps, epochs=total_epochs,\n",
+ " last_epoch_lr=1e-7, start_lr=1e-7)\n",
+ " model_checkpoint_callback = ModelCheckpoint(filepath=f'{models_dir}/model_{model_name}_{run_id}_checkpoint.keras',\n",
+ " monitor='val_partial_auc', save_best_only=True)\n",
+ " callbacks=[one_cycle_scheduler_callback, partial_auc_callback, early_stopping_callback, model_checkpoint_callback, memory_callback]\n",
+ "\n",
+ "\n",
+ " with strategy.scope():\n",
+ " precision = tf.keras.metrics.Precision()\n",
+ " recall = tf.keras.metrics.Recall()\n",
+ " f1_score = F1Score()\n",
+ " auc = tf.keras.metrics.AUC(name=\"auc\")\n",
+ "\n",
+ " optimizer = tf.keras.optimizers.Nadam() if execution_env == 'colab' else tf.keras.optimizers.Adamax()\n",
+ " loss = set_binary_crossentropy_weighted_loss(positive_weights=np.array([class_weights_input_ds['minority_class']]),\n",
+ " negative_weights=np.array([class_weights_input_ds['majority_class']]),\n",
+ " epsilon=1e-7)\n",
+ " history_initial = None\n",
+ " class_weights=dict(zip([0, 1], class_weights_input_ds.values()))\n",
+ " # Initial training\n",
+ " if freeze_base_model:\n",
+ " with strategy.scope():\n",
+ " model.compile(optimizer=optimizer, loss=loss, metrics=[precision, recall, f1_score])\n",
+ " print(\"Started fitting the model with base model freezed...\")\n",
+ " history_initial = model.fit(train_dataset,epochs=initial_epochs,steps_per_epoch=training_steps, validation_data=val_dataset,\n",
+ " validation_steps=validation_steps, callbacks=callbacks, class_weight=class_weights, verbose=1)\n",
+ " \n",
+ " # Fine tuning\n",
+ " if do_fine_tuning:\n",
+ " initial_epoch = history_initial.epoch[-1] if freeze_base_model else 0\n",
+ "\n",
+ " with strategy.scope():\n",
+ " model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy', precision, recall, f1_score, auc])\n",
+ "\n",
+ " print(\"Started fine fitting the model with layers unfrozen...\")\n",
+ " history_fine_tune = model.fit(train_dataset, epochs=total_epochs, initial_epoch=initial_epoch, steps_per_epoch=training_steps,\n",
+ " validation_data=val_dataset, validation_steps=validation_steps, class_weight=class_weights,\n",
+ " callbacks=callbacks, verbose=1)\n",
+ " # Merge histories\n",
+ " history = merge_histories(history_initial, history_fine_tune)\n",
+ " else:\n",
+ " history = history_initial.history\n",
+ "\n",
+ " print(\"Computing the final validation score after training ended\")\n",
+ " # Evaluate the model on the validation set\n",
+ " y_val, y_pred = get_labels_and_predictions(model, val_dataset, validation_steps)\n",
+ "\n",
+ " # Calculate the Partial AUC\n",
+ " partial_auc = score(y_val, y_pred)\n",
+ " partial_aucs.append(partial_auc)\n",
+ "\n",
+ " print(\"Plotting the training metrics...\")\n",
+ " # Plot the training history\n",
+ " plot_training_history(history, model_name)\n",
+ "\n",
+ " model.save_weights(os.path.join(models_dir, f'{model_name}_{run_id}_weights.h5'))\n",
+ " \n",
+ " print(f\"Train-validation-split partial AUC: {partial_auc}\")\n",
+ "\n",
+ " print(f'Finished training model {model_name}')\n",
+ " return model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "-rb4sntpyFtu"
+ },
+ "source": [
+ "### Single model training pipeline"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "ra_VML37yFtu",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def train_single_model(model_name, **kwargs):\n",
+ " \"\"\"Train a unique model from scratch.\"\"\"\n",
+ "\n",
+ " print(f\"Training single model: {model_name}\\n\")\n",
+ "\n",
+ " model = ModelFactory.create_model('single', pretrained=False, model_names=[model_name], **kwargs)\n",
+ " model = training_pipeline(model_name, model=model, **kwargs)\n",
+ "\n",
+ " return model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "5TaVBBPYe2ru"
+ },
+ "source": [
+ "## Training"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "F8h9mp1EyFtu"
+ },
+ "source": [
+ "### Utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "ZT2DbKsMyFtu",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "class TrainConfig:\n",
+ " def __init__(self, strategy, run_id, batch_size, val_batch_size, cache_in_memory,\n",
+ " freeze_base_model, shuffle_dataset_at_each_call, train_file_pattern,\n",
+ " do_fine_tuning, oversample_minority_class, initial_epochs, fine_tune_epochs,\n",
+ " img_shape, augment_train_data, use_cross_validation, dropout_rate, l2_lambda,\n",
+ " num_tabular_features, use_tabular_data, kernel_initializer, activation,\n",
+ " execution_env):\n",
+ " self.strategy = strategy\n",
+ " self.run_id = run_id\n",
+ " self.batch_size = batch_size\n",
+ " self.val_batch_size = val_batch_size\n",
+ " self.cache_in_memory = cache_in_memory\n",
+ " self.freeze_base_model = freeze_base_model\n",
+ " self.shuffle_dataset_at_each_call = shuffle_dataset_at_each_call\n",
+ " self.train_file_pattern = train_file_pattern\n",
+ " self.do_fine_tuning = do_fine_tuning\n",
+ " self.oversample_minority_class = oversample_minority_class\n",
+ " self.initial_epochs = initial_epochs\n",
+ " self.fine_tune_epochs = fine_tune_epochs\n",
+ " self.img_shape = img_shape\n",
+ " self.augment_train_data = augment_train_data\n",
+ " self.use_cross_validation = use_cross_validation\n",
+ " self.dropout_rate = dropout_rate\n",
+ " self.l2_lambda = l2_lambda\n",
+ " self.num_tabular_features = num_tabular_features\n",
+ " self.use_tabular_data = use_tabular_data\n",
+ " self.kernel_initializer = kernel_initializer\n",
+ " self.activation = activation\n",
+ " self.execution_env = execution_env\n",
+ "\n",
+ "\n",
+ "def run_pipeline(pipeline_func, config: TrainConfig, **specific_params):\n",
+ " \"\"\"General pipeline function for training models with a TrainConfig object.\"\"\"\n",
+ "\n",
+ " # Clean the session and retrieve initial memory\n",
+ " initial_memory_usage = reset_session_and_get_memory()\n",
+ " log_memory_usage(\"Initial\", initial_memory_usage)\n",
+ "\n",
+ " # Ensure strategy is initialized\n",
+ " if config.strategy is None:\n",
+ " raise ValueError(\"Strategy must be set in the configuration.\")\n",
+ "\n",
+ " # Train the model using the passed function with the TrainConfig object\n",
+ " #with config.strategy.scope():\n",
+ " model = pipeline_func(strategy=config.strategy, run_id=config.run_id, batch_size=config.batch_size,\n",
+ " cache_in_memory=config.cache_in_memory, freeze_base_model=config.freeze_base_model,\n",
+ " shuffle_dataset_at_each_call=config.shuffle_dataset_at_each_call,\n",
+ " do_fine_tuning=config.do_fine_tuning, train_file_pattern=config.train_file_pattern,\n",
+ " oversample_minority_class = config.oversample_minority_class,\n",
+ " initial_epochs = config.initial_epochs, val_batch_size = config.val_batch_size,\n",
+ " fine_tune_epochs = config.fine_tune_epochs,\n",
+ " img_shape = config.img_shape,\n",
+ " augment_train_data = config.augment_train_data,\n",
+ " use_cross_validation = config.use_cross_validation,\n",
+ " use_tabular_data = config.use_tabular_data,\n",
+ " activation = config.activation,\n",
+ " dropout_rate = config.dropout_rate,\n",
+ " l2_lambda = config.l2_lambda,\n",
+ " num_tabular_features = config.num_tabular_features,\n",
+ " kernel_initializer = config.kernel_initializer,\n",
+ " execution_env = config.execution_env,\n",
+ " **specific_params)\n",
+ " # Retrieve final memory usage\n",
+ " final_memory_usage = get_memory_usage()\n",
+ " log_memory_usage(\"Final\", final_memory_usage)\n",
+ "\n",
+ " return model\n",
+ "\n",
+ "def run_model_evaluation(model):\n",
+ " evaluate_model(model, val_dataset_small_formatted, validation_steps_small, dataset_name='small validation dataset')\n",
+ " evaluate_model(model, val_dataset_big_formatted, validation_steps_big, dataset_name='big validation dataset')\n",
+ " evaluate_model(model, train_val_dataset_formatted, train_val_dataset_steps, dataset_name='train_val dataset')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "mQa1JmVSe2ru"
+ },
+ "source": [
+ "### Initialization"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "2TDM10k6yFtu",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Instantiate TrainConfig with common parameters\n",
+ "train_config = TrainConfig(\n",
+ " strategy=strategy,\n",
+ " run_id=run_id,\n",
+ " batch_size=batch_size,\n",
+ " val_batch_size=val_batch_size,\n",
+ " cache_in_memory=cache_in_memory,\n",
+ " freeze_base_model=freeze_base_model,\n",
+ " shuffle_dataset_at_each_call=True,\n",
+ " train_file_pattern=train_file_pattern,\n",
+ " do_fine_tuning = do_fine_tuning,\n",
+ " oversample_minority_class = oversample_minority_class,\n",
+ " initial_epochs = initial_epochs,\n",
+ " fine_tune_epochs = fine_tune_epochs,\n",
+ " img_shape = img_shape,\n",
+ " augment_train_data = augment_train_data,\n",
+ " use_cross_validation = use_cross_validation,\n",
+ " dropout_rate = dropout_rate,\n",
+ " l2_lambda = l2_lambda,\n",
+ " num_tabular_features = num_tabular_features,\n",
+ " use_tabular_data = use_tabular_data,\n",
+ " kernel_initializer = kernel_initializer,\n",
+ " activation = activation,\n",
+ " execution_env = execution_env\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "47Qz3n7oe2rv"
+ },
+ "source": [
+ "### Data loading"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "RCMr7pPWSdJ5",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Load dataset once and cache\n",
+ "if run_evaluations:\n",
+ " train_val_dataset, train_val_dataset_steps = load_tfrecord_dataset(\n",
+ " train_file_pattern,\n",
+ " batch_size=batch_size,\n",
+ " is_training=False,\n",
+ " cache_in_memory=False)\n",
+ " train_val_dataset_formatted = format_input_dataset(train_val_dataset)\n",
+ " # Verify cache\n",
+ " #!ls -al {cache_dir}\n",
+ " # Load big validation dataset\n",
+ " val_dataset_small, validation_steps_small = load_tfrecord_dataset(\n",
+ " f'{tf_records_eval_dir}/*.tfrecord',\n",
+ " batch_size=eval_batch_size,\n",
+ " is_training=False,\n",
+ " cache_in_memory=False)\n",
+ " val_dataset_small_formatted = format_input_dataset(val_dataset_small)\n",
+ "\n",
+ " # Load big validation dataset\n",
+ " val_dataset_big, validation_steps_big = load_tfrecord_dataset(\n",
+ " f'{tf_records_val_dir}/*.tfrecord',\n",
+ " batch_size=eval_batch_size * 2,\n",
+ " is_training=False,\n",
+ " cache_in_memory=False)\n",
+ " val_dataset_big_formatted = format_input_dataset(val_dataset_big)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "cSXVvmhpLkTY"
+ },
+ "source": [
+ "### Run"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "_V7fwzuxYKnn",
+ "scrolled": true,
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "if model_type == 'single_model' or train_all_models:\n",
+ " #strategy = tf.distribute.OneDeviceStrategy(device=\"/gpu:0\")\n",
+ " #strategy = tf.distribute.get_strategy()\n",
+ " #with strategy.scope():\n",
+ " single_model = run_pipeline(\n",
+ " pipeline_func=train_single_model, # Function to train the model\n",
+ " config=train_config, # Common configuration object\n",
+ " #model_name='res_net_50'\n",
+ " model_name=model_name\n",
+ " )\n",
+ " if inference_model_name == 'single_model':\n",
+ " inference_model = single_model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ferk6DB0e2rv"
+ },
+ "source": [
+ "### Evaluate on unseen and train_val datasets"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "16EUhZshe2rv",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "if model_type == 'single_model' or train_all_models:\n",
+ " print('Evaluating single model:', model_name)\n",
+ " run_model_evaluation(single_model)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "PUxNGwlOYZiB"
+ },
+ "source": [
+ "# Ensembling models"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Trained ensemble model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "C6LwSwXEe2rw"
+ },
+ "source": [
+ "#### Trained ensemble training pipeline"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "amV6RsxDe2rw",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def train_compact_ensemble_model(model_names=['efficientnetb0', 'resnet50', 'vgg16'], **kwargs):\n",
+ " \"\"\"Train a unique ensmeble model from scratch.\"\"\"\n",
+ "\n",
+ " print(f\"Training compact ensemble model\")\n",
+ "\n",
+ " compact_ensemble_model = ModelFactory.create_model('ensemble', model_names, pretrained=False, **kwargs)\n",
+ "\n",
+ " compact_ensemble_model = training_pipeline(compact_ensemble_model.model_name, model=compact_ensemble_model, **kwargs)\n",
+ " return compact_ensemble_model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "D_N4Ysa0fgX5"
+ },
+ "source": [
+ "#### Run"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "52ZYRcZae2rw",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "if model_type == 'compact_ensemble_model' and train_all_models:\n",
+ " compact_ensemble_model = run_pipeline(\n",
+ " pipeline_func=train_compact_ensemble_model, # Function to train ensemble model\n",
+ " config=train_config, # Common configuration object\n",
+ " #model_names=['densenet201','resnet50', 'resnet152v2','vgg19'],\n",
+ " #weights=[0.41, 0.35, 0.14, 0.1],\n",
+ " #weights=[0.25, 0.25, 0.25, 0.25],\n",
+ " model_names=['res_net_152_v2', 'dense_net_201'],\n",
+ " weights=[0.55, 0.45]\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "_I8Qh6vNe2rw"
+ },
+ "source": [
+ "#### Evaluation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "EMHz-8tNe2rw",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "if model_type == 'compact_ensemble_model' or train_all_models:\n",
+ " print('Evaluating compact ensemble model')\n",
+ " run_model_evaluation(compact_ensemble_model)\n",
+ "\n",
+ " if inference_model_name == 'compact_ensemble_model':\n",
+ " inference_model = compact_ensemble_model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "_ww-Q4P3VYom"
+ },
+ "source": [
+ "## Pretrained ensemble model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "lsk9qgwze2rw"
+ },
+ "source": [
+ "### Pretrained ensemble pipeline"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "CraB6SdSe2rw",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "def train_pretrained_ensemble(model_names, train_individuals=True, **kwargs):\n",
+ " \"\"\"Train a unique model from scratch.\"\"\"\n",
+ " print('Training pretrained ensemble model')\n",
+ "\n",
+ " run_id = kwargs.get('run_id', None)\n",
+ "\n",
+ " # Train individual models if needed\n",
+ " train_individual_models(model_names=model_names, train_individuals=train_individuals, **kwargs)\n",
+ "\n",
+ " pretrained_ensemble = ModelFactory.create_model(model_type='ensemble', model_names=model_names, pretrained=True, **kwargs)\n",
+ "\n",
+ " # Saving the weights as it is not trained via the commom pipeline\n",
+ " pretrained_ensemble.save_weights(os.path.join(models_dir, f'{pretrained_ensemble.model_name}_{run_id}_weights.h5'))\n",
+ "\n",
+ " print('Finished creating the pretrained_ensemble', pretrained_ensemble.model_name)\n",
+ "\n",
+ " return pretrained_ensemble\n",
+ "\n",
+ "def train_individual_models(model_names, train_individuals=True, **kwargs):\n",
+ " \"\"\"Train individual models if their weights do not already exist.\"\"\"\n",
+ " for model_name in model_names:\n",
+ " print('Training individual model', model_name)\n",
+ " weights_path = os.path.join('models_dir', f'{model_name}_{kwargs.get(\"run_id\", \"default\")}_weights.h5')\n",
+ " if (not os.path.exists(weights_path)) and train_individuals:\n",
+ " print(f\"Training {model_name} since weights are not found.\")\n",
+ " train_single_model(model_name, **kwargs)\n",
+ " else:\n",
+ " print(f\"Weights for {model_name} already exist at {weights_path}, skipping training.\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "uXGMYNGTe2rw"
+ },
+ "source": [
+ "### Training"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "G4dJ5JLAe2rw"
+ },
+ "source": [
+ "### Run"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "mi1rG5hGe2rw",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "if model_type == 'pretrained_ensemble_model' or train_all_models:\n",
+ " pretrained_ensemble_model = run_pipeline(\n",
+ " pipeline_func=train_pretrained_ensemble, # Function to train ensemble model\n",
+ " config=train_config, # Common configuration object\n",
+ " model_names=['dense_net_201','res_net_50', 'res_net_152_v2','vgg_19'],\n",
+ " weights=[0.41, 0.35, 0.14, 0.1],\n",
+ " train_individuals=train_individuals,\n",
+ " mode='weighted'\n",
+ " )\n",
+ " if inference_model_name == 'pretrained_ensemble_model':\n",
+ " inference_model = pretrained_ensemble_model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "WOJNkr42e2rw"
+ },
+ "source": [
+ "### Evaluation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "1wmxQDwAe2rw",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "if (model_type == 'pretrained_ensemble_model' or train_all_models) and run_evaluations:\n",
+ " print('Evaluating pretrained ensemble model')\n",
+ " run_model_evaluation(pretrained_ensemble_model)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "85VjJJ7uNHLk"
+ },
+ "source": [
+ "# Inference"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "imjhThNXyFtv"
+ },
+ "source": [
+ "### Data preparation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "rh4Se0aVOLQM",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Load and preprocess images using fused operations for I/O and preprocessing\n",
+ "@tf.function\n",
+ "def preprocess_image(image_bytes, img_height, img_width):\n",
+ " \"\"\"Preprocess image loaded from HDF5.\"\"\"\n",
+ " image = tf.io.decode_jpeg(image_bytes, channels=3)\n",
+ " image = tf.image.resize(image, [img_height, img_width])\n",
+ " image = image / 255.0 # Normalize to [0, 1]\n",
+ " return image\n",
+ "\n",
+ "def load_image_from_hdf5(key, h5_file):\n",
+ " \"\"\"Function to load image bytes from HDF5 file.\"\"\"\n",
+ " try:\n",
+ " image_bytes = h5_file[key][()]\n",
+ " if image_bytes is None or len(image_bytes) == 0:\n",
+ " raise ValueError(f\"Empty image data for key: {key}\")\n",
+ " return image_bytes\n",
+ " except Exception as e:\n",
+ " raise ValueError(f\"Failed to load image for key {key}. Error: {str(e)}\")\n",
+ "\n",
+ "def hdf5_image_generator(h5_file, keys):\n",
+ " \"\"\"Python generator to load images from HDF5 file.\"\"\"\n",
+ " for key in keys:\n",
+ " try:\n",
+ " image_bytes = load_image_from_hdf5(key, h5_file)\n",
+ " yield image_bytes\n",
+ " except Exception as e:\n",
+ " print(f\"Error loading image for key {key}: {e}\")\n",
+ " continue\n",
+ "\n",
+ "def prepare_inference_dataset(image_paths, data_source='hdf5', use_tabular_data = use_tabular_data, tabular_data_paths=None,\n",
+ " batch_size=batch_size, cache_data=False, preprocessor_path=preprocessor_path, target_img_shape=None):\n",
+ " \"\"\"\n",
+ " Prepare the dataset for inference, either loading images from a file path or from an .hdf5 file.\n",
+ "\n",
+ " Args:\n",
+ " image_source: Path to the images or .hdf5 file containing the images.\n",
+ " tabular_data_paths: Path to the tabular data, if any.\n",
+ " preprocessor: Preprocessing function for tabular data.\n",
+ " batch_size: Batch size for the dataset.\n",
+ " cache_data: Whether to cache the dataset.\n",
+ " data_source: 'path' if loading from image paths, 'hdf5' if loading from an .hdf5 file.\n",
+ "\n",
+ " Returns:\n",
+ " A tf.data.Dataset ready for inference.\n",
+ " \"\"\"\n",
+ "\n",
+ " # Loading images from file paths\n",
+ " if data_source == 'path':\n",
+ " # Fused operations for loading and preprocessing images from file paths\n",
+ " image_paths = glob(image_paths)\n",
+ " image_dataset = tf.data.Dataset.from_tensor_slices(image_paths)\n",
+ "\n",
+ " # Load and preprocess the images\n",
+ " image_dataset = image_dataset.map(preprocess_image, num_parallel_calls=tf.data.AUTOTUNE)\n",
+ "\n",
+ " keys = [os.path.basename(path).split('.')[0] for path in image_paths]\n",
+ "\n",
+ " # Loading images from an .hdf5 file\n",
+ " elif data_source == 'hdf5':\n",
+ " # Open the .hdf5 file and load the image dataset\n",
+ " # Open the HDF5 file\n",
+ " h5_file = h5py.File(image_paths, 'r')\n",
+ " keys = list(h5_file.keys())\n",
+ "\n",
+ " # Create a generator for loading images from the HDF5 file\n",
+ " def image_gen():\n",
+ " return hdf5_image_generator(h5_file, keys)\n",
+ "\n",
+ " # Create a TensorFlow Dataset from the generator\n",
+ " image_dataset = tf.data.Dataset.from_generator(\n",
+ " image_gen,\n",
+ " output_signature=tf.TensorSpec(shape=(), dtype=tf.string)\n",
+ " )\n",
+ "\n",
+ " # Map preprocessing function over the dataset\n",
+ " image_dataset = image_dataset.map(\n",
+ " lambda image_bytes: preprocess_image(image_bytes, img_height, img_width),\n",
+ " num_parallel_calls=tf.data.AUTOTUNE\n",
+ " )\n",
+ "\n",
+ " # Raise an error if the data_source is not recognized\n",
+ " else:\n",
+ " raise ValueError(\"data_source must be either 'path' or 'hdf5'\")\n",
+ "\n",
+ " # If tabular data is provided, load and zip it with the image dataset\n",
+ " if use_tabular_data:\n",
+ " # Extract filenames without extensions\n",
+ " inference_images = keys\n",
+ " preprocessor = load_preprocessor(preprocessor_path)\n",
+ " tabular_data = format_and_encode_tabular_data([tabular_data_paths], inference_images, preprocessor)\n",
+ "\n",
+ " tabular_data = tf.data.Dataset.from_tensor_slices(tabular_data)\n",
+ "\n",
+ " # Combine image and tabular data efficiently\n",
+ " combined_dataset = tf.data.Dataset.zip((image_dataset, tabular_data))\n",
+ " else:\n",
+ " combined_dataset = image_dataset\n",
+ "\n",
+ " # Batch the dataset and prefetch to load data asynchronously\n",
+ " dataset_for_inference = combined_dataset.batch(batch_size * strategy.num_replicas_in_sync,\n",
+ " drop_remainder=False).prefetch(tf.data.AUTOTUNE)\n",
+ " if cache_data:\n",
+ " dataset_for_inference = dataset_for_inference.cache()\n",
+ "\n",
+ " return dataset_for_inference, keys"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "_ylysfHoyFtw"
+ },
+ "source": [
+ "### Pipeline"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "JubaCa3ayFtw",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Run inference using fused operations, mixed precision, and optimized parallel data loading\n",
+ "def inference_pipeline(model, image_paths, use_tabular_data, tabular_data_paths=None, batch_size=batch_size,\n",
+ " cache_data=False, use_mixed_precision=False, preprocessor_path=preprocessor_path,\n",
+ " submission_file_path=None, target_img_shape=None):\n",
+ " use_mixed_precision=False\n",
+ " if use_mixed_precision:\n",
+ " enable_mixed_precision()\n",
+ "\n",
+ " # Prepare the dataset\n",
+ " dataset_for_inference, images_ids= prepare_inference_dataset(image_paths=image_paths, use_tabular_data=use_tabular_data,\n",
+ " cache_data=cache_data, tabular_data_paths=tabular_data_paths,\n",
+ " batch_size=batch_size, preprocessor_path=preprocessor_path,\n",
+ " target_img_shape=target_img_shape)\n",
+ " all_predictions = []\n",
+ " # Run inference and get predictions\n",
+ " for batch in dataset_for_inference:\n",
+ " if use_tabular_data:\n",
+ " image_batch, tabular_batch = batch\n",
+ " predictions = model.predict_on_batch([image_batch, tabular_batch]).ravel()\n",
+ " else:\n",
+ " image_batch = batch\n",
+ " predictions = model.predict_on_batch(image_batch).ravel()\n",
+ " all_predictions.append(predictions)\n",
+ "\n",
+ " # Save predictions to CSV\n",
+ " predictions_df = pd.DataFrame(zip(images_ids, predictions), columns=['isic_id', 'target'])\n",
+ " predictions_df.to_csv(submission_file_path, index=False)\n",
+ "\n",
+ " # Process predictions\n",
+ " return predictions_df, dataset_for_inference"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "VX1Pz5GmOaC8"
+ },
+ "source": [
+ "## Initialization"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "68W0wKs5Oe26",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "preprocessor_path = preprocessor_path\n",
+ "inference_model_path = inference_model_path\n",
+ "project_dir = f'{input_dir}/isic-2024-challenge'\n",
+ "inference_images_path = f'{project_dir}/test-image.hdf5'\n",
+ "inference_tabular_data_path = f'{project_dir}/test-metadata.csv'\n",
+ "use_tabular_data = use_tabular_data\n",
+ "cache_data=True\n",
+ "submission_file_path = f'{output_dir}/submission.csv'\n",
+ "target_img_shape=img_shape\n",
+ "run_inference_use_all_models = False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "xcvznEl-OkYP"
+ },
+ "source": [
+ "## Run"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "m4pqr3A2NJd-",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Run inference with optimizations and mixed precision enabled\n",
+ "if run_inference:\n",
+ " inference_models = [single_model, compact_ensemble_model, pretrained_ensemble_model] if run_inference_use_all_models else [inference_model]\n",
+ " predictions_dfs = []\n",
+ " for inf_model in inference_models:\n",
+ " print('Running inference for model...', inf_model.model_name)\n",
+ " predictions_df, dataset_for_inference = inference_pipeline(model=inf_model, image_paths=inference_images_path, use_tabular_data=use_tabular_data,\n",
+ " tabular_data_paths=inference_tabular_data_path, batch_size=batch_size, cache_data=cache_data,\n",
+ " use_mixed_precision=False, preprocessor_path=preprocessor_path, submission_file_path=submission_file_path,\n",
+ " target_img_shape=target_img_shape)\n",
+ " predictions_dfs.append(predictions_df)\n",
+ " print('Finished running inference for model', inf_model.model_name)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "CzavW04_RbHv",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "predictions_dfs[-1]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ao5HqGmRe2rw"
+ },
+ "source": [
+ "# Annexe"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "nsXlSSMSe2rw",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "# Limit intra-op parallelism (operations within a single layer)\n",
+ "#export OMP_NUM_THREADS=100\n",
+ "# Limit inter-op parallelism (parallel operations across layers)\n",
+ "#export TF_NUM_INTEROP_THREADS=100\n",
+ "# Limit intra-op parallelism (for operations like matrix multiplications)\n",
+ "#tf.config.threading.set_intra_op_parallelism_threads(100)\n",
+ "# Limit inter-op parallelism (for parallel operations across layers)\n",
+ "#tf.config.threading.set_inter_op_parallelism_threads(100)\n",
+ "#import os\n",
+ "#os.environ[\"CUDA_VISIBLE_DEVICES\"] = \"-1\" # Disables GPU by setting this environment variable\n",
+ "#tf.config.list_physical_devices('GPU')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "QqMgKK62e2rw",
+ "trusted": true
+ },
+ "outputs": [],
+ "source": [
+ "visualization = False\n",
+ "if visualization:\n",
+ " eval_dataset_mapped = eval_dataset.map(lambda x, y, z: ((x, y), z))\n",
+ " augmented_dataset = apply_augmentations(eval_dataset_mapped)\n",
+ " imgs = augmented_dataset.as_numpy_iterator()\n",
+ " imgs_t = imgs.next()[0][0]\n",
+ " plt.figure(figsize=(50, 50))\n",
+ " #imgs_t = imgs.next()[0]\n",
+ " for i in range(0, 63):\n",
+ " plt.subplot(12, 12, i+1)\n",
+ " plt.xticks([])\n",
+ " plt.yticks([])\n",
+ " plt.imshow(imgs_t[i])"
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "machine_shape": "hm",
+ "provenance": [],
+ "toc_visible": true
+ },
+ "kaggle": {
+ "accelerator": "nvidiaTeslaT4",
+ "dataSources": [
+ {
+ "databundleVersionId": 9094797,
+ "sourceId": 63056,
+ "sourceType": "competition"
+ },
+ {
+ "datasetId": 5790004,
+ "sourceId": 9511835,
+ "sourceType": "datasetVersion"
+ },
+ {
+ "modelId": 101,
+ "modelInstanceId": 846,
+ "sourceId": 992,
+ "sourceType": "modelInstanceVersion"
+ },
+ {
+ "modelId": 2796,
+ "modelInstanceId": 4592,
+ "sourceId": 6080,
+ "sourceType": "modelInstanceVersion"
+ },
+ {
+ "modelId": 73369,
+ "modelInstanceId": 53489,
+ "sourceId": 64146,
+ "sourceType": "modelInstanceVersion"
+ },
+ {
+ "modelId": 128647,
+ "modelInstanceId": 104448,
+ "sourceId": 124095,
+ "sourceType": "modelInstanceVersion"
+ }
+ ],
+ "dockerImageVersionId": 30761,
+ "isGpuEnabled": true,
+ "isInternetEnabled": false,
+ "language": "python",
+ "sourceType": "notebook"
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.14"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/public/notebooks/isic-2024_kaggle.qmd b/public/notebooks/isic-2024_kaggle.qmd
new file mode 100644
index 0000000..2f60c1a
--- /dev/null
+++ b/public/notebooks/isic-2024_kaggle.qmd
@@ -0,0 +1,3752 @@
+---
+format:
+ html:
+ title: ''
+ toc: true
+ toc-title: "Table of contents"
+ toc-location: right
+ toc-depth: 5
+ toc-expand: true
+ toc-float:
+ collapsed: true
+ smooth-scroll: true
+ width: 300px
+ theme: cosmo
+ code-fold: false
+ code-fold-show: true
+ page-layout: full
+ number-sections: true
+ code-tools: false
+ code-line-numbers: false
+ code-summary: "Show Code"
+ code-block-bg: true
+ include-in-header: styles/toc-scrollbar-hide.html
+execute:
+ enabled: false
+---
+
+# Initialization
+
+```{python}
+#| trusted: true
+import time
+# Unique indentifier specific to this execution
+run_id = int(time.time())
+```
+
+## Installations
+
+```{python}
+#| trusted: true
+#! pip install tensorflow==2.15.0
+! pip install -q imblearn keras-tuner tensorflow-addons keras-cv memory_profiler
+```
+
+## Global variables
+
+```{python}
+#| trusted: true
+# Database configuration: Choose between 'dagshub_storage', 'google_drive', 'local'
+database = 'local' # Current choice: local
+
+# Execution mode: Choose between 'CPU' or 'GPU'
+execution_mode = 'GPU' # Current choice: GPU
+
+# Execution environment: Choose between 'colab', 'mac'
+execution_env = 'kaggle' # Current choice: colab
+
+# Experimentation mode: Choose between 'test' or 'prod'
+experimentation_mode = 'prod' # Current choice: test
+
+# Training and Inference settings
+train_all_models = False # Train all models
+run_inference = True # Run inference
+run_inference_use_all_models = True # Run inference on all models
+model_type = 'pretrained_ensemble_model' # Type of model to train, choose between 'single_model', 'pretrained_ensemble_model', 'compact_ensemble_model'
+inference_model_name = model_type # Use the model_type as the inference model name
+
+# Dataset settings
+download_raw_datasets = False # Do not download raw datasets
+download_refined_training_datasets = True if execution_env == 'colab' else False # Download refined datasets if running on Colab
+dataset_size = 20000 # Set dataset size to 40,000 samples
+create_new_training_datasets = False # Do not create new training datasets
+```
+
+## Execution strategy
+
+```{python}
+#| trusted: true
+import tensorflow as tf
+from tensorflow.keras import mixed_precision
+import warnings
+
+def limit_memory_growth():
+ gpu_devices = tf.config.experimental.list_physical_devices('GPU')
+ try:
+ for gpu in gpu_devices:
+ tf.config.experimental.set_memory_growth(gpu, True)
+ except Exception as e:
+ print('Can not set memory growth', e)
+
+def disable_eager_execution():
+ tf.compat.v1.disable_eager_execution()
+
+def enable_xla():
+ tf.config.optimizer.set_jit(True)
+
+def disable_gpu():
+ tf.config.set_visible_devices([], 'GPU')
+ visible_devices = tf.config.get_visible_devices()
+ for device in visible_devices:
+ assert device.device_type != 'GPU'
+
+def enable_mixed_precision():
+ policy = mixed_precision.Policy('mixed_float16')
+ mixed_precision.set_global_policy(policy)
+ print("Mixed precision enabled: ", mixed_precision.global_policy())
+
+# Suppress all warnings in the notebook
+warnings.filterwarnings('ignore')
+
+limit_memory_growth()
+#enable_mixed_precision()
+#tf.debugging.set_log_device_placement(True)
+#tf.config.set_visible_devices([], 'GPU')
+enable_xla()
+tf.config.optimizer.set_experimental_options({
+ "memory_optimization": True
+})
+```
+
+```{python}
+#| trusted: true
+def check_tpu_initialized():
+ """Check if TPU is already initialized."""
+ tpu_devices = tf.config.list_logical_devices('TPU')
+ if len(tpu_devices) > 0:
+ print(f"TPU is already initialized with {len(tpu_devices)} logical devices.")
+ return True
+ else:
+ print("TPU is not initialized.")
+ return False
+
+def set_tpu_strategy(force=False):
+ """Set the TPU strategy if TPU is initialized."""
+ if check_tpu_initialized() and not force:
+ try:
+ resolver = tf.distribute.cluster_resolver.TPUClusterResolver() # TPU detection
+ strategy = tf.distribute.TPUStrategy(resolver)
+
+ print("Using TPU strategy.")
+ return strategy
+ except Exception as e:
+ print(f"Error while setting TPU strategy: {e}")
+ return None
+ else:
+ try:
+ print("Trying to initialize TPU.")
+ resolver = tf.distribute.cluster_resolver.TPUClusterResolver()
+ tf.config.experimental_connect_to_cluster(resolver)
+ tf.tpu.experimental.initialize_tpu_system(resolver)
+ strategy = tf.distribute.TPUStrategy(resolver)
+ print("TPU initialized and TPU strategy set.")
+ return strategy
+ except ValueError:
+ print("No TPU devices found.")
+ return None
+
+strategy = set_tpu_strategy(force = execution_env == 'colab')
+running_on_tpu = strategy is not None
+
+if strategy is None:
+ # For GPUs or CPU
+ if tf.config.list_physical_devices('GPU') and execution_mode == 'GPU':
+ limit_memory_growth()
+ strategy = tf.distribute.MirroredStrategy()
+ enable_mixed_precision() # Enable mixed precision policy
+ print("Running on GPU")
+
+ else:
+ strategy = tf.distribute.get_strategy()
+ print("Running on CPU")
+```
+
+## Librairies import
+
+```{python}
+#| trusted: true
+# Core Libraries
+import os
+import gc
+import re
+import h5py
+import time
+import random
+import copy
+import psutil
+import hashlib
+import traceback
+import numpy as np
+import pandas as pd
+from glob import glob
+from collections import defaultdict
+from multiprocessing import Pool
+from concurrent.futures import ThreadPoolExecutor
+
+# TensorFlow and Keras Imports
+import tensorflow.keras.backend as K
+from tensorflow.keras import layers, Model, Input, regularizers, optimizers, callbacks
+from tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping, ModelCheckpoint
+from tensorflow.keras.layers.experimental import preprocessing
+from tensorflow.keras.utils import register_keras_serializable
+
+# TensorFlow Add-ons
+import tensorflow_addons as tfa
+
+# Keras Tuner for Hyperparameter Optimization
+import keras_tuner as kt
+from keras_tuner import HyperModel
+from keras_tuner.tuners import RandomSearch
+
+# TensorFlow Pretrained Models
+from tensorflow.keras.applications import (
+ EfficientNetB0, EfficientNetB4, EfficientNetB7,
+ DenseNet121, DenseNet169, DenseNet201,
+ InceptionV3, InceptionResNetV2,
+ ResNet50, ResNet101, ResNet152V2,
+ VGG16, VGG19, Xception
+)
+
+# Data Oversampling
+from imblearn.over_sampling import SMOTE
+
+# Scikit-Learn Utilities
+from sklearn.metrics import (
+ roc_curve, roc_auc_score, auc,
+ accuracy_score, classification_report, confusion_matrix
+)
+from sklearn.model_selection import StratifiedShuffleSplit
+
+# Memory Profiling
+from memory_profiler import memory_usage
+
+# Visualization Libraries
+import matplotlib.pyplot as plt
+import seaborn as sns
+sns.set()
+```
+
+## Database setting
+
+```{python}
+#| trusted: true
+if download_refined_training_datasets:
+ if database == 'local':
+ %pip install -q dagshub
+ import dagshub.colab
+ DAGSHUB_REPO = dagshub.colab.login()
+ elif database == 'google_drive':
+ from google.colab import drive
+ drive.mount('/content/drive')
+ ROOT_DB_DIR='/content/drive/MyDrive/documents_travail'
+ else:
+ %pip install -q dagshub
+ import dagshub.colab
+ DAGSHUB_REPO = dagshub.colab.login()
+ mount_path = dagshub.storage.mount(DAGSHUB_REPO, cache=True)
+ print(f'Mount path: {mount_path}')
+ ROOT_DB_DIR=f'/content/{mount_path}'
+```
+
+## Training variables
+
+```{python}
+#| trusted: true
+# Define root directories based on the execution environment
+if execution_env == 'mac':
+ ROOT_DB_DIR = '/Users/amadou/local/datalab'
+elif execution_env == 'kaggle':
+ ROOT_DB_DIR = '/kaggle/'
+else:
+ ROOT_DB_DIR = '/content'
+
+input_dir = f'{ROOT_DB_DIR}/input'
+output_dir = f'{ROOT_DB_DIR}/working'
+cache_dir = f'{output_dir}/tf_cache' # Temporary cache directory for TensorFlow
+project_dir = f"{output_dir}/isic_2024" # Project-specific directory
+tmp_dir = f'{output_dir}/tmp' # Temporary files directory
+models_dir = f'{output_dir}/models' # Directory to save model files
+pretrained_models_dir = f'{input_dir}/pretrained/tensorflow2/default/1/'
+
+# Define dataset directories
+datasets_dir = f'{input_dir}/datasets' # Root dataset directory
+training_dir = f'{datasets_dir}/training' # Training data directory
+training_metadata_dir = f'{training_dir}/metadata' # Metadata for training data
+
+# Define directories for training data (positives and negatives)
+train_dir = f'{datasets_dir}/training/train'
+train_dir_pos = f'{train_dir}/positives' # Positive training samples
+train_dir_negs = f'{train_dir}/negatives' # Negative training samples
+
+# Define validation and evaluation data directories (positives and negatives)
+val_dir = f'{datasets_dir}/training/val'
+val_dir_pos = f'{val_dir}/positives' # Positive validation samples
+val_dir_negs = f'{val_dir}/negatives' # Negative validation samples
+
+eval_dir = f'{datasets_dir}/training/eval'
+eval_dir_pos = f'{eval_dir}/positives' # Positive evaluation samples
+eval_dir_negs = f'{eval_dir}/negatives' # Negative evaluation samples
+
+# Define paths for inference
+inference_dir = f'{project_dir}/inference' # Inference-related directory
+inference_model_path = os.path.join(models_dir, 'inference_model.h5') # Model for inference
+inference_images_dir = f'{inference_dir}/images/hdf5' # Directory to source inference images
+inference_images_path = f'{inference_images_dir}/test-image.hdf5' # HDF5 file with test images
+inference_metadata_dir = f'{inference_dir}/metadata' # Directory for inference metadata
+inference_tabular_data_path = os.path.join(inference_metadata_dir, 'test-metadata.csv') # Test metadata for tabular data
+preprocessor_path = f'{inference_dir}/preprocessor_{run_id}.pkl' # Preprocessor for inference
+
+# Define TFRecord directories
+tf_records_dir = f'{input_dir}/tf_records/{dataset_size}' if experimentation_mode == 'test' else f'{input_dir}/sample-20k/sample_20k_kaggle'
+
+tf_records_train_val_dir = f'{tf_records_dir}/train' # TFRecords for training
+tf_records_val_dir = f'{tf_records_dir}/validation' # TFRecords for validation
+tf_records_eval_dir = f'{tf_records_dir}/evaluation' # TFRecords for evaluation
+oversample_persisted_data = False # Oversample persisted data when creating tf_records
+
+# Image and model settings
+img_height = 148 # Height of the input images
+img_width = 148 # Width of the input images
+img_channels = 3 # Number of color channels
+img_shape = (img_height, img_width, img_channels) # Shape of the input images (H x W x C)
+image_size = img_height # Image size, set to height for consistency
+
+# Training and evaluation settings
+use_cross_validation = False # Whether to use cross-validation
+augment_train_data = True # Augment training data
+use_tabular_data = False # Whether to include tabular data in the model
+force_cache = True # Force caching of data
+cache_in_memory = True if execution_env == 'colab' else False # Cache in memory only for Colab
+do_fine_tuning = True # Whether to fine-tune the model
+oversample_minority_class = True # Oversample the minority class in imbalanced datasets
+initial_epochs = 1 if experimentation_mode == 'test' else 5 # Initial epochs for training
+fine_tune_epochs = 1 if experimentation_mode == 'test' else 80 # Epochs for fine-tuning
+freeze_base_model = False # Whether to freeze the base model during training
+
+# Dataset buffering and shuffling
+buffer_size = 1000 if execution_env == 'colab' else 700 # Buffer size for data shuffling
+shuffle_train_val_at_each_call = True # Shuffle training/validation data at each call
+train_file_pattern = f'{tf_records_train_val_dir}/*.tfrecord' # Pattern to match TFRecord files
+
+# Model and training configurations
+model_name = 'res_net_50' # Name of the model to be used (ResNet50)
+train_individuals = False # Whether to train individual models of the pretrained ensemble
+run_evaluations = False
+
+# Batch size settings depending on environment and mode
+if experimentation_mode == 'test':
+ batch_size = 16 if execution_env == 'colab' else 16
+ val_batch_size = 16 if execution_env == 'colab' else 32
+ eval_batch_size = 4 if execution_env == 'colab' else 32
+else:
+ batch_size = 128 if execution_env == 'colab' else 128 # Batch size varies between Colab and local
+ val_batch_size = 128 if execution_env == 'colab' else 128 # Validation batch size
+ eval_batch_size = 128 if execution_env == 'colab' else 128 # Evaluation batch size
+
+# Model creation settings
+dropout_rate = 0.5
+l2_lambda = 0.02
+num_tabular_features = 14
+kernel_initializer = 'he_normal'
+activation = 'swish'
+```
+
+```{python}
+#| trusted: true
+if create_new_training_datasets:
+ !rm -rf {tf_records_dir}
+
+!mkdir -p {tmp_dir}
+!mkdir -p {sample_dir}
+!mkdir -p {models_dir}
+!mkdir -p {tf_records_train_val_dir}
+!mkdir -p {tf_records_val_dir}
+!mkdir -p {tf_records_eval_dir}
+!mkdir -p {inference_dir}
+!mkdir -p {inference_images_dir}
+!mkdir -p {inference_metadata_dir}
+```
+
+# Data preparation
+
+## Preparation utils
+
+### Formatting utils
+
+```{python}
+#| trusted: true
+# Format all the metadatas to have the same structure
+def format_tabular_data(tabular_data_paths):
+ tabular_dfs = []
+ for path in tabular_data_paths:
+ file_name = os.path.basename(path)
+ if file_name == 'train_metadata_2024.csv' or file_name == 'test-metadata.csv':
+ metadata_2024 = pd.read_csv(path, low_memory=False)
+ metadata_2024.fillna({'age_approx': metadata_2024.age_approx.median(), 'sex': 'unknown', 'anatom_site_general': 'unknown'}, inplace=True)
+ tabular_dfs.append(metadata_2024[common_columns])
+ elif file_name == 'train_metadata_2020.csv':
+ metadata_2020 = pd.read_csv(path, low_memory=False).rename(columns=dict(zip(selected_2019_2020_columns, common_columns)))
+ metadata_2020.fillna({'age_approx': metadata_2020.age_approx.median(), 'sex': 'unknown', 'anatom_site_general': 'unknown'}, inplace=True)
+ tabular_dfs.append(metadata_2020[common_columns])
+ elif file_name == 'train_metadata_2019.csv':
+ metadata_2019 = pd.read_csv(path, low_memory=False).rename(columns=dict(zip(selected_2019_2020_columns, common_columns)))
+ metadata_2019.fillna({'age_approx': metadata_2019.age_approx.median(), 'anatom_site_general': 'unknown'}, inplace=True)
+ tabular_dfs.append(metadata_2019[common_columns])
+ return tabular_dfs
+
+
+# Format and encode tabular data
+def format_and_encode_tabular_data(tabular_data_paths, train_images, preprocessor=None):
+ tf.print('Formatting and encoding tabular data...')
+ tabular_data = pd.concat(format_tabular_data(tabular_data_paths), axis=0)
+
+ # Reindex tabular data to align with image IDs
+ train_tabular = tabular_data.set_index('isic_id').reindex(train_images).reset_index(drop=True)
+
+ if preprocessor is None:
+ # Create the preprocessor if not provided (for training)
+ print('Creating a new preprocessor to encode tabular data')
+ preprocessor = fit_tabular_encoder(train_tabular)
+ save_preprocessor(preprocessor, preprocessor_path)
+ encoded_tabular_data = encode_tabular_data(train_tabular, preprocessor)
+ else:
+ print('Using provided preprocessor to encode tabular data')
+ encoded_tabular_data = encode_tabular_data(train_tabular, preprocessor)
+
+ return encoded_tabular_data
+```
+
+### Encoding utils
+
+```{python}
+#| trusted: true
+# Fit the encoder for tabular data using dense format
+def fit_tabular_encoder(tabular_data):
+ num_cols = ['age_approx']
+ one_hot_cols = ['anatom_site_general', 'sex']
+
+ # Using dense format for OneHotEncoder
+ preprocessor = ColumnTransformer(
+ transformers=[
+ ('num', StandardScaler(), num_cols),
+ ('cat', OneHotEncoder(handle_unknown='ignore', sparse_output=False), one_hot_cols) # Dense output
+ ]
+ )
+ preprocessor.fit(tabular_data)
+ return preprocessor
+
+# Encode tabular data with dense format, called only after fit_tabular_encoder
+# or directely for val and eval data
+def encode_tabular_data(tabular_data, preprocessor):
+ # Use the preprocessor to transform the tabular data into dense format
+ return preprocessor.transform(tabular_data)
+
+# Save and load preprocessor for reuse between training and inference
+def save_preprocessor(preprocessor, file_path):
+ with open(file_path, 'wb') as f:
+ pickle.dump(preprocessor, f)
+ print('Saved preprocessor at', file_path)
+
+def load_preprocessor(file_path):
+ with open(file_path, 'rb') as f:
+ preprocessor = pickle.load(f)
+ print('Loaded preprocessor from', file_path)
+ return preprocessor
+```
+
+### Oversampling utils
+
+```{python}
+#| trusted: true
+# Optionally apply smote on the whole dataset to balance minority and majority
+def smote_oversampling(image_paths, labels, tabular_data):
+ """Apply SMOTE to the labels and tabular data, with synchronized image resampling."""
+ tf.print("Applying SMOTE")
+
+ # Separate image paths, labels, and tabular data by class
+ class_0_indices = [i for i, label in enumerate(labels) if label == 0]
+ class_1_indices = [i for i, label in enumerate(labels) if label == 1]
+
+ class_0_image_paths = [image_paths[i] for i in class_0_indices]
+ class_1_image_paths = [image_paths[i] for i in class_1_indices]
+
+ if tabular_data is not None:
+ class_0_tabular = tabular_data.iloc[class_0_indices].reset_index(drop=True)
+ class_1_tabular = tabular_data.iloc[class_1_indices].reset_index(drop=True)
+ else:
+ class_0_tabular = class_1_tabular = None
+
+ # Apply SMOTE to the labels and tabular data
+ smote = SMOTE(sampling_strategy='minority')
+
+ if tabular_data is not None:
+ oversampled_tabular_data, oversampled_labels = smote.fit_resample(tabular_data, labels)
+ else:
+ dummy_data = np.zeros((len(labels), 1))
+ _, oversampled_labels = smote.fit_resample(dummy_data, labels)
+ oversampled_tabular_data = None
+
+ # Prepare for synchronized resampling of image paths and tabular data
+ synthetic_image_paths = []
+ synthetic_tabular_data = []
+
+ class_0_count = len(class_0_image_paths)
+ class_1_count = len(class_1_image_paths)
+
+ for idx, label in enumerate(oversampled_labels):
+ if label == 0:
+ synthetic_image_paths.append(class_0_image_paths[idx % class_0_count])
+ if tabular_data is not None:
+ synthetic_tabular_data.append(class_0_tabular.iloc[idx % class_0_count].values)
+ else:
+ synthetic_image_paths.append(class_1_image_paths[idx % class_1_count])
+ if tabular_data is not None:
+ synthetic_tabular_data.append(class_1_tabular.iloc[idx % class_1_count].values)
+
+ # Ensure coherence by converting synthetic_tabular_data to DataFrame if needed
+ if tabular_data is not None:
+ synthetic_tabular_data = pd.DataFrame(synthetic_tabular_data, columns=tabular_data.columns)
+ else:
+ synthetic_tabular_data = None
+
+ # Return synchronized image paths, labels, and tabular data
+ return synthetic_image_paths, oversampled_labels, synthetic_tabular_data
+```
+
+### Subset sampling utils
+
+```{python}
+#| trusted: true
+def sample_data(image_paths, labels, tabular_data, sampling_fraction):
+ """Sample a subset of data for test purposes."""
+ tf.print("Sampling data...")
+ sampled_indices = np.random.choice(len(image_paths), int(len(image_paths) * sampling_fraction), replace=False)
+ image_paths = [image_paths[i] for i in sampled_indices]
+ labels = [labels[i] for i in sampled_indices]
+ if tabular_data is not None:
+ tabular_data = tabular_data[sampled_indices]
+ return image_paths, labels, tabular_data
+```
+
+### Serialization utils
+
+```{python}
+#| trusted: true
+# Data writing to tf_records for later reuse if working in cloud
+def write_dataset_to_tfrecords(image_paths, labels, tabular_data, output_prefix, num_workers, data_type='train'):
+ tf.print(f"\nStart writting {data_type} tfrecords...")
+ chunk_size = len(image_paths) // num_workers
+ tf.print(f"Number of workers: {num_workers}")
+ tf.print(f"Dataset size: {len(image_paths)}, chunk size: {chunk_size}")
+
+ args = [
+ (
+ image_paths[i:i + chunk_size],
+ labels[i:i + chunk_size],
+ tabular_data[i:i + chunk_size] if tabular_data is not None else None,
+ f'{output_prefix}_{i}.tfrecord'
+ )
+ for i in range(0, len(image_paths), chunk_size)
+ ]
+
+ with ThreadPoolExecutor(max_workers=num_workers) as executor:
+ executor.map(write_tfrecord_single_batch, args)
+
+def write_tfrecord_single_batch(args):
+ """
+ Write a batch of data to a TFRecord file. This function processes images and optional tabular data,
+ serializes them into a TFRecord format, and saves the result to a file.
+
+ Args:
+ args: A tuple or list containing the following:
+ - image_paths: List of file paths to the images (batch).
+ - labels: List of corresponding labels for the images (batch).
+ - tabular_data: Optional tabular data associated with the images (batch) (could be None).
+ - output_file: Path to the output TFRecord file.
+ """
+ try:
+ image_paths, labels, tabular_data, output_file = args
+ # Print which file is being written
+ tf.print(f"Writing TFRecords to: {output_file}")
+
+ # Specify compression options for the TFRecord (GZIP in this case)
+ options = tf.io.TFRecordOptions(compression_type="GZIP")
+
+ # Open a TFRecordWriter for writing data to the specified output file
+ with tf.io.TFRecordWriter(output_file, options=options) as writer:
+ # Loop through each image and label in the batch
+ for i, (img_path, label) in enumerate(zip(image_paths, labels)):
+ img_bytes = process_image(img_path)
+
+ # Convert the raw image into a feature that can be stored in the TFRecord
+ img_feature = tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_bytes]))
+
+ # Convert the label into a feature (using int64 since it's a categorical label)
+ label_feature = tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
+
+ # Create a dictionary to store the image and label as features
+ features = {'image': img_feature, 'label': label_feature}
+
+ # If tabular data is used and provided, add it to the features dictionary
+ if tabular_data is not None:
+ # Convert the tabular data to a float list feature for this specific sample
+ tab_feature = tf.train.Feature(float_list=tf.train.FloatList(value=tabular_data[i]))
+ features['tabular_data'] = tab_feature
+
+ # Create a TFRecord example from the features dictionary
+ example = tf.train.Example(features=tf.train.Features(feature=features))
+
+ # Serialize the example to a string and write it to the TFRecord file
+ writer.write(example.SerializeToString())
+ except Exception as e:
+ print(f"Error in write_tfrecord: {e}")
+ traceback.print_exc()
+
+def process_image(img_path):
+ """Helper function to read and process a single image."""
+ try:
+ img_bytes = tf.io.read_file(img_path)
+ return img_bytes.numpy() # Return the image bytes
+ except Exception as e:
+ raise Exception(f"Error reading image {img_path}: {e}")
+```
+
+### Data download utils
+
+```{python}
+#| trusted: true
+def download_folder_from_dagshub(boto_client, bucket_name, remote_folder, local_folder):
+ """
+ Downloads all files from a specified remote folder in DagsHub to a local folder.
+
+ Args:
+ boto_client: The boto client used to interact with DagsHub (S3-compatible).
+ bucket_name: Name of the DagsHub repository (S3 bucket).
+ remote_folder: The path to the folder in DagsHub (e.g., "kaggle/isic-2024/datasets/tf_records/sample_1k/validation/").
+ local_folder: The path to the local folder where files will be downloaded.
+ """
+
+ # Ensure local folder exists
+ os.makedirs(local_folder, exist_ok=True)
+
+ # List all files in the remote folder
+ response = boto_client.list_objects_v2(Bucket=bucket_name, Prefix=remote_folder)
+
+ # Check if there are contents in the folder
+ if 'Contents' in response:
+ for obj in response['Contents']:
+ remote_file_path = obj['Key']
+ local_file_path = os.path.join(local_folder, os.path.basename(remote_file_path))
+
+ # Download each file
+ print(f"Downloading {remote_file_path} to {local_file_path}...")
+ boto_client.download_file(
+ Bucket=bucket_name,
+ Key=remote_file_path,
+ Filename=local_file_path
+ )
+ print("Download completed!")
+ else:
+ print(f"No files found in the folder: {remote_folder}")
+
+def resolve_remote_folder(local_folder):
+ return local_folder.replace(f'{ROOT_DB_DIR}/', '')
+```
+
+### Preparation pipeline
+
+```{python}
+#| trusted: true
+from sklearn.compose import ColumnTransformer
+from sklearn.preprocessing import OneHotEncoder, MinMaxScaler, StandardScaler, OrdinalEncoder
+import pickle
+from scipy.sparse import vstack, csr_matrix
+
+
+from sklearn.compose import ColumnTransformer
+from sklearn.preprocessing import OneHotEncoder, StandardScaler
+
+common_columns = ['isic_id', 'age_approx', 'sex', 'anatom_site_general']
+selected_2019_2020_columns = ['image_name', 'age_approx', 'sex', 'anatom_site_general_challenge']
+
+# Preparing data directories using dense tabular data format
+def prepare_data_directories(train_dir, tabular_data_paths, sampling_fraction=1.0, use_oversampling=False, data_type='train'):
+ """Load image paths, labels, and optionally tabular data"""
+
+ tf.print(f"\nStart {data_type} data preparation...")
+
+ train_image_paths = glob(os.path.join(train_dir, '**', '*.jpg'), recursive=True)
+ train_labels = [1 if os.path.dirname(path).endswith('positives') else 0 for path in train_image_paths]
+ train_images = [os.path.basename(path).split('.')[0] for path in train_image_paths]
+
+ data_type = data_type.capitalize()
+ tf.print(f"{data_type} labels length before preparing data", len(train_labels))
+
+ if use_tabular_data:
+ # Load the preprocessor for validation or fit it for training data
+ preprocessor = None if data_type == 'Train' else load_preprocessor(preprocessor_path)
+
+ train_tabular = format_and_encode_tabular_data(tabular_data_paths, train_images, preprocessor=preprocessor)
+ else:
+ train_tabular = None
+
+ # oversample only train data
+ if use_oversampling:
+ train_image_paths, train_labels, train_tabular = smote_oversampling(
+ train_image_paths, train_labels, train_tabular
+ )
+ tf.print(f"{data_type} data size after oversampling:", len(train_labels))
+
+ # sample all the datasets if sampling is needed
+ if sampling_fraction < 1.0:
+ train_image_paths, train_labels, train_tabular = sample_data(
+ train_image_paths, train_labels, train_tabular, sampling_fraction
+ )
+ tf.print(f"{data_type} data size after subsampling:", len(train_labels))
+
+ if train_tabular is not None:
+ tabular_data_length = train_tabular.shape[0]
+
+ print(f"Image data size: {len(train_image_paths)}")
+ print(f"Tabular data size: {tabular_data_length}")
+
+ # Ensure that the number of tabular rows matches the number of image paths
+ assert len(train_image_paths) == tabular_data_length, "Image paths and tabular data do not match"
+ return (train_image_paths, train_labels, train_tabular)
+```
+
+## Run
+
+### Download raw datasets
+
+```{python}
+#| trusted: true
+if download_raw_datasets:
+ boto_client = get_repo_bucket_client("AmadouMamane/dagshub-drive", flavor="boto")
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(eval_dir_pos), eval_dir_pos)
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(eval_dir_negs), eval_dir_negs)
+
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(val_dir_pos), val_dir_pos)
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(val_dir_negs), val_dir_negs)
+
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(train_dir_pos), train_dir_pos)
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(train_dir_negs), train_dir_negs)
+```
+
+### Download refined datasets
+
+```{python}
+#| trusted: true
+if download_refined_training_datasets:
+ boto_client = get_repo_bucket_client("AmadouMamane/dagshub-drive", flavor="boto")
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(tf_records_eval_dir), tf_records_eval_dir)
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(tf_records_val_dir), tf_records_val_dir)
+ download_folder_from_dagshub(boto_client, "dagshub-drive", resolve_remote_folder(tf_records_train_val_dir), tf_records_train_val_dir)
+```
+
+### Data preparation
+
+```{python}
+#| trusted: true
+# Prepare and process data
+num_workers = 10
+#total_samples = #49403 + 12100 + 4389 =65892
+
+if create_new_training_datasets:
+ sampling_fraction = dataset_size / 65892
+ metadata_2024_path = os.path.join(f'{training_metadata_dir}', 'train_metadata_2024.csv')
+ metadata_2020_path = os.path.join(f'{training_metadata_dir}', 'train_metadata_2020.csv')
+ metadata_2019_path = os.path.join(f'{training_metadata_dir}', 'train_metadata_2019.csv')
+ tabular_data_paths = [metadata_2024_path, metadata_2020_path, metadata_2019_path]
+
+ (train_image_paths, train_labels, train_tabular) = prepare_data_directories(train_dir, tabular_data_paths, sampling_fraction=sampling_fraction, use_oversampling=oversample_persisted_data, data_type='train')
+ (eval_image_paths, eval_labels, eval_tabular) = prepare_data_directories(eval_dir, tabular_data_paths, sampling_fraction=sampling_fraction, use_oversampling=oversample_persisted_data, data_type='eval')
+ (val_image_paths, val_labels, val_tabular) = prepare_data_directories(val_dir, tabular_data_paths, sampling_fraction=sampling_fraction, use_oversampling=oversample_persisted_data, data_type='Val')
+```
+
+### Data serialization
+
+```{python}
+#| trusted: true
+if create_new_training_datasets:
+ write_dataset_to_tfrecords(eval_image_paths, eval_labels, eval_tabular, f'{tf_records_eval_dir}/eval', num_workers=num_workers, data_type='train')
+ write_dataset_to_tfrecords(val_image_paths, val_labels, val_tabular, f'{tf_records_val_dir}/val', num_workers=num_workers, data_type='eval')
+ write_dataset_to_tfrecords(train_image_paths, train_labels, train_tabular, f'{tf_records_train_val_dir}/train', num_workers=num_workers, data_type='Val')
+ print('Number of train and val positives class samples', train_labels.count(1))
+ print('Number of val positives class samples', val_labels.count(1))
+ print('Number of eval positives class samples', eval_labels.count(1))
+```
+
+## Data augmentation
+
+#### Random hue saturation
+
+```{python}
+#| trusted: true
+sat_max_delta_hs=0.0005
+hue_max_delta_hs=0.0005
+val_max_delta_hs=0.0005
+@tf.function
+def random_hue_saturation(image, hue_max_delta=hue_max_delta_hs, sat_max_delta=sat_max_delta_hs, val_max_delta=val_max_delta_hs, probability=0.5, seed=None):
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ def apply_hue_sat_val(img):
+ img = tf.image.random_hue(img, max_delta=hue_max_delta)
+ img = tf.image.random_saturation(img, lower=1 - sat_max_delta, upper=1 + sat_max_delta)
+ img = tf.image.random_brightness(img, max_delta=val_max_delta)
+ img = tf.clip_by_value(img, 0.0, 1.0)
+ return img
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_hue_sat_val(image),
+ lambda: image
+ )
+```
+
+#### Random shift scale rotate
+
+```{python}
+#| trusted: true
+@tf.function
+def random_shift_scale_rotate(image, shift_limit=0.05, scale_limit=0.05, rotate_limit=5, threshold=0.5, image_size=image_size, seed=None):
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ def apply_shift_scale_rotate(img, seed):
+
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=4)
+ angle_seed = seeds[0]
+ scale_seed = seeds[1]
+ dx_seed = seeds[3]
+ dy_seed = seeds[4]
+
+ # Random rotation
+ angle = tf.random.stateless_uniform([], -rotate_limit, rotate_limit, seed=angle_seed) * 3.14159265 / 180
+ img = tf.image.rot90(img, k=tf.cast(angle / (3.14159265 / 2), tf.int32))
+
+ # Random scaling
+ scale = tf.random.stateless_uniform([], 1 - scale_limit, 1 + scale_limit, seed=scale_seed)
+ new_size = tf.cast(tf.cast(tf.shape(img)[0:2], tf.float32) * scale, tf.int32)
+ img = tf.image.resize(img, new_size)
+
+ # Random shifting
+ max_dx = tf.cast(shift_limit * tf.cast(tf.shape(img)[1], tf.float32), tf.int32)
+ max_dy = tf.cast(shift_limit * tf.cast(tf.shape(img)[0], tf.float32), tf.int32)
+
+
+ dx = tf.random.stateless_uniform([], -max_dx, max_dx, dtype=tf.int32, seed=dx_seed)
+ dy = tf.random.stateless_uniform([], -max_dy, max_dy, dtype=tf.int32, seed=dy_seed)
+
+ target_height = tf.minimum(image_size, new_size[0] - tf.abs(dy))
+ target_width = tf.minimum(image_size, new_size[1] - tf.abs(dx))
+
+ img = tf.image.crop_to_bounding_box(
+ img,
+ offset_height=tf.maximum(0, dy),
+ offset_width=tf.maximum(0, dx),
+ target_height=target_height,
+ target_width=target_width
+ )
+
+ # Resize back to original size
+ img = tf.image.resize_with_crop_or_pad(img, target_height=image_size, target_width=image_size)
+ return img
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),
+ lambda: apply_shift_scale_rotate(image, seed),
+ lambda: image
+ )
+```
+
+#### Random crop
+
+```{python}
+#| trusted: true
+@tf.function
+def random_crop(image, min_crop_size_ratio=0.7, max_crop_size_ratio=1.0, probability=0.5, seed=None):
+ def apply_random_crop_per_image(img):
+ img_shape = tf.shape(img)
+ height = img_shape[0]
+ width = img_shape[1]
+
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=4)
+ crop_seed = seeds[0]
+ size_seed = seeds[1]
+ y_seed = seeds[2]
+ x_seed = seeds[3]
+
+ # Randomly determine the crop size ratio within the given range using the seed
+ crop_size_ratio = tf.random.stateless_uniform([], minval=min_crop_size_ratio, maxval=max_crop_size_ratio, seed=size_seed)
+
+ # Calculate the crop size based on the randomly chosen ratio
+ crop_height = tf.cast(crop_size_ratio * tf.cast(height, tf.float32), tf.int32)
+ crop_width = tf.cast(crop_size_ratio * tf.cast(width, tf.float32), tf.int32)
+
+ y_maxval = tf.maximum(height - crop_height, 1) # Ensure maxval is > min_val
+ x_maxval = tf.maximum(width - crop_width, 1)
+
+ # Randomly select the top-left corner of the crop using the seed
+ y1 = tf.random.stateless_uniform([], minval=0, maxval=x_maxval, dtype=tf.int32, seed=y_seed)
+ x1 = tf.random.stateless_uniform([], minval=0, maxval=y_maxval, dtype=tf.int32, seed=x_seed)
+
+ # Define the bottom-right corner of the crop
+ y2 = y1 + crop_height
+ x2 = x1 + crop_width
+
+ # Crop the image
+ cropped_img = img[y1:y2, x1:x2, :]
+
+ # Set a static shape for cropped image if necessary before resizing (adjust as needed)
+ cropped_img.set_shape([None, None, img.shape[-1]])
+
+ # Resize the cropped image back to the original size
+ cropped_img = tf.image.resize(cropped_img, [height, width], method='bilinear')
+
+ return cropped_img
+
+ # Use stateless random to generate a seed if none is provided
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ # Apply random crop with the specified probability
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_random_crop_per_image(image),
+ lambda: image
+ )
+```
+
+### Random cutout
+
+```{python}
+#| trusted: true
+@tf.function
+def random_cutout(image, max_cutout_size_ratio=0.3, probability=0.5, seed=None):
+ def apply_cutout(img):
+ img_shape = tf.shape(img)
+ height = img_shape[-3]
+ width = img_shape[-2]
+
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=3)
+ cutout_seed = seeds[0]
+ x_center_seed = seeds[1]
+ y_center_seed = seeds[2]
+
+ # Generate the cutout size using a stateless random function with a seed
+ cutout_size = tf.random.stateless_uniform([], minval=0, maxval=max_cutout_size_ratio, dtype=tf.float32, seed=cutout_seed) * tf.cast(tf.minimum(height, width), tf.float32)
+ cutout_size = tf.cast(cutout_size, tf.int32)
+
+ # Randomly select the center position of the cutout using the seed
+ x_center = tf.random.stateless_uniform([], minval=0, maxval=width, dtype=tf.int32, seed=x_center_seed)
+ y_center = tf.random.stateless_uniform([], minval=0, maxval=height, dtype=tf.int32, seed=y_center_seed)
+
+ # Define the top-left and bottom-right corners of the cutout
+ x1 = tf.clip_by_value(x_center - cutout_size // 2, 0, width)
+ y1 = tf.clip_by_value(y_center - cutout_size // 2, 0, height)
+ x2 = tf.clip_by_value(x_center + cutout_size // 2, 0, width)
+ y2 = tf.clip_by_value(y_center + cutout_size // 2, 0, height)
+
+ # Create the cutout mask
+ mask = tf.ones_like(img)
+ x_range = tf.range(x1, x2)
+ y_range = tf.range(y1, y2)
+
+ # Generate all the coordinates to update in the mask
+ y_grid, x_grid = tf.meshgrid(y_range, x_range)
+ coords = tf.stack([y_grid, x_grid], axis=-1)
+ coords = tf.reshape(coords, [-1, 2])
+
+ # Update the mask with zeros at the selected coordinates
+ mask = tf.tensor_scatter_nd_update(mask, coords, tf.zeros([tf.shape(coords)[0], img_shape[-1]], dtype=img.dtype))
+
+ # Apply the mask to the image
+ img = img * mask
+
+ return img
+
+ # Use stateless random to generate a seed if none is provided
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+ # Apply cutout with the specified probability
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_cutout(image),
+ lambda: image
+ )
+```
+
+### Distorsions
+
+#### Random elastic transform
+
+```{python}
+#| trusted: true
+@tf.function
+def random_elastic_transform(image, alpha_range=(1, 3), sigma_range=(0.8, 2), padding_size_range=(5, 15), probability=0.5, seed=None):
+ # Use stateless random to generate a seed if none is provided
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ # Generate random values for alpha, sigma, and padding_size from their respective ranges
+ alpha = tf.random.stateless_uniform([], minval=alpha_range[0], maxval=alpha_range[1], seed=seed)
+ sigma = tf.random.stateless_uniform([], minval=sigma_range[0], maxval=sigma_range[1], seed=seed)
+ padding_size = tf.random.stateless_uniform([], minval=padding_size_range[0], maxval=padding_size_range[1], seed=seed, dtype=tf.int32)
+
+ def apply_elastic_transform(img):
+ img = tf.cast(img, tf.float32)
+ height, width, channels = tf.shape(img)[0], tf.shape(img)[1], tf.shape(img)[2]
+
+ # Pad the image to reduce boundary artifacts
+ img_padded = tf.pad(img, [[padding_size, padding_size], [padding_size, padding_size], [0, 0]], mode='REFLECT')
+
+ # Update dimensions after padding
+ padded_height, padded_width = tf.shape(img_padded)[0], tf.shape(img_padded)[1]
+
+ # Split seed for reproducibility
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+ seeds = tf.random.experimental.stateless_split(seed, num=2)
+ dx_seed = seeds[0]
+ dy_seed = seeds[1]
+
+ # Generate random displacement fields
+ dx = tf.random.stateless_normal([padded_height, padded_width], mean=0.0, stddev=sigma, seed=dx_seed)
+ dy = tf.random.stateless_normal([padded_height, padded_width], mean=0.0, stddev=sigma, seed=dy_seed)
+
+ # Smooth the displacement fields
+ kernel_size = 13
+ kernel = tf.ones((kernel_size, kernel_size, 1, 1)) / (kernel_size * kernel_size)
+ dx = tf.nn.depthwise_conv2d(tf.expand_dims(tf.expand_dims(dx, axis=-1), axis=0), kernel, strides=[1, 1, 1, 1], padding="SAME")[0, ..., 0]
+ dy = tf.nn.depthwise_conv2d(tf.expand_dims(tf.expand_dims(dy, axis=-1), axis=0), kernel, strides=[1, 1, 1, 1], padding="SAME")[0, ..., 0]
+
+ # Scale the displacement fields
+ dx *= alpha
+ dy *= alpha
+
+ # Generate meshgrid and add the displacements
+ xs, ys = tf.meshgrid(tf.range(padded_width), tf.range(padded_height))
+ xs = tf.cast(xs, tf.float32) + dx
+ ys = tf.cast(ys, tf.float32) + dy
+
+ # Clip the values to be within image dimensions
+ xs = tf.clip_by_value(xs, 0.0, tf.cast(padded_width - 1, tf.float32))
+ ys = tf.clip_by_value(ys, 0.0, tf.cast(padded_height - 1, tf.float32))
+
+ # Resample using the distorted coordinates
+ distorted_indices = tf.stack([ys, xs], axis=-1)
+ distorted_image_padded = tf.gather_nd(img_padded, tf.cast(distorted_indices, tf.int32))
+
+ # Remove padding by cropping back to the original image size
+ distorted_image = distorted_image_padded[padding_size:padding_size + height, padding_size:padding_size + width, :]
+
+ return distorted_image
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_elastic_transform(image),
+ lambda: image
+ )
+```
+
+#### Random grid distortion
+
+```{python}
+#| trusted: true
+@tf.function
+def random_grid_distortion(image, num_steps=10, distort_limit=0.05, probability=0.5, seed=None):
+ # Use stateless random to generate a seed if none is provided
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ def apply_grid_distortion(img):
+ img_shape = tf.shape(img)
+ height = tf.cast(img_shape[0], tf.float32)
+ width = tf.cast(img_shape[1], tf.float32)
+
+ # Define grid step size
+ x_step = tf.cast(width // num_steps, tf.float32)
+ y_step = tf.cast(height // num_steps, tf.float32)
+
+
+ # Verification for tf.random.stateless_uniform so that min_val and mx_val != 0
+ x_step = tf.cond(x_step > 0, lambda: x_step, lambda: tf.constant(1e-7))
+ y_step = tf.cond(y_step > 0, lambda: y_step, lambda: tf.constant(1e-7))
+
+
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=2)
+ x_offsets_seed = seeds[0]
+ y_offsets_seed = seeds[1]
+
+ # Generate random offsets
+ x_offsets = tf.random.stateless_uniform(
+ shape=[num_steps + 1, num_steps + 1],
+ minval=-distort_limit * x_step,
+ maxval=distort_limit * x_step,
+ dtype=tf.float32,
+ seed=x_offsets_seed
+ )
+ y_offsets = tf.random.stateless_uniform(
+ shape=[num_steps + 1, num_steps + 1],
+ minval=-distort_limit * y_step,
+ maxval=distort_limit * y_step,
+ dtype=tf.float32,
+ seed=y_offsets_seed
+ )
+
+ # Create grid of coordinates
+ x = tf.linspace(0.0, width, num_steps + 1)
+ y = tf.linspace(0.0, height, num_steps + 1)
+ x_t, y_t = tf.meshgrid(x, y)
+
+ # Apply offsets
+ x_t = x_t + x_offsets
+ y_t = y_t + y_offsets
+
+ # Interpolate to get dense flow field
+ x_interp = tf.image.resize(x_t[..., tf.newaxis], [height, width], method='bilinear')
+ y_interp = tf.image.resize(y_t[..., tf.newaxis], [height, width], method='bilinear')
+
+ # Stack and subtract identity grid
+ flow = tf.stack([y_interp[..., 0] - tf.range(height)[:, None],
+ x_interp[..., 0] - tf.range(width)[None, :]], axis=-1)
+
+ # Apply flow to image
+ distorted_image = tfa.image.dense_image_warp(img[tf.newaxis, ...], flow[tf.newaxis, ...])[0]
+
+ return distorted_image
+
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_grid_distortion(image),
+ lambda: image
+ )
+```
+
+#### Random optical distortion
+
+```{python}
+#| trusted: true
+# Define distortion parameters
+distort_limit_do = 0.0005
+shift_limit_do = 0.0005
+
+# Define augmentation layers outside the tf.function
+random_rotation = preprocessing.RandomRotation(factor=distort_limit_do)
+random_translation = preprocessing.RandomTranslation(height_factor=shift_limit_do,
+ width_factor=shift_limit_do)
+random_zoom = preprocessing.RandomZoom(height_factor=(-distort_limit_do, distort_limit_do),
+ width_factor=(-distort_limit_do, distort_limit_do))
+
+@tf.function
+def random_optical_distortion(image, probability=0.5, seed=None):
+ def apply_optical_distortion(img):
+ # Apply the augmentations
+ img = random_rotation(img, training=True)
+ img = random_translation(img, training=True)
+ img = random_zoom(img, training=True)
+ return img
+
+ # If seed is provided, split it for reproducible randomness
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_optical_distortion(image),
+ lambda: image
+ )
+```
+
+#### Random one of distorsion
+
+```{python}
+#| trusted: true
+distort_limit_do =0.0005 # defined oon function definition also
+shift_limit_do = 0.0005
+
+num_steps_dg=10
+distort_limit_dg=0.05
+
+alpha_range_de = (0.5,1.5)
+sigma_range_de =(0.5,1.0)
+padding_size_range_de = (5,10)
+
+@tf.function
+def random_one_of_distortion(image, probability=0.7, seed=None):
+ # Generate seed if not provided
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ def apply_random_transform(img, seed):
+
+ # Generate a random choice for distortion transformation
+ choice = tf.random.stateless_uniform([], minval=0, maxval=2, dtype=tf.int32, seed=seed)
+ # Apply random distortions based on the choice
+ #img = tf.cond(choice == 0, lambda: random_optical_distortion(img, probability=1.0, seed=None), lambda: img)
+ img = tf.cond(choice == 0, lambda: random_grid_distortion(img, num_steps=num_steps_dg, distort_limit=distort_limit_dg, probability=1.0, seed=None), lambda: img)
+ img = tf.cond(choice == 1, lambda: random_elastic_transform(img, alpha_range=alpha_range_de, sigma_range=sigma_range_de, padding_size_range=padding_size_range_de, probability=1.0, seed=None), lambda: img)
+ return img
+
+ # Apply transformation based on the probability
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_random_transform(image, seed),
+ lambda: image
+ )
+```
+
+### Blurings
+
+```{python}
+#| trusted: true
+var_limit_gn=(0.0, 0.0001)
+sigma_range_gb=(0.0, 0.7)
+kernel_size_range_gb=(3, 4)
+angle_range_gm=(0, 30)
+kernel_size_range_gm=(1, 4)
+```
+
+#### Random gaussian blur
+
+```{python}
+#| trusted: true
+@tf.function
+def random_gaussian_blur(image, kernel_size_range=(3, 7), sigma_range=(0.1, 2.0), probability=0.5, seed=None):
+ def gaussian_kernel(size: int, mean: float, std: float):
+ """Creates a 2D Gaussian Kernel for convolution."""
+ coords = tf.range(-(size // 2), size // 2 + 1, dtype=tf.float32)
+ g = tf.exp(-tf.pow(coords - mean, 2.0) / (2.0 * tf.pow(std, 2.0)))
+ g /= tf.reduce_sum(g)
+ gauss_kernel = tf.tensordot(g, g, axes=0)
+ return gauss_kernel / tf.reduce_sum(gauss_kernel)
+
+ def apply_blur(img):
+ # Generate a seed if none is provided for consistent randomness
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=2)
+ kernel_size_seed = seeds[0]
+ sigma_seed = seeds[1]
+
+
+ # Randomize the kernel size and sigma within the given ranges
+ random_kernel_size = tf.random.stateless_uniform([], minval=kernel_size_range[0], maxval=kernel_size_range[1], dtype=tf.int32, seed=kernel_size_seed)
+ random_kernel_size = tf.where(random_kernel_size % 2 == 0, random_kernel_size + 1, random_kernel_size) # Ensure kernel size is odd
+ random_sigma = tf.random.stateless_uniform([], minval=sigma_range[0], maxval=sigma_range[1], seed=sigma_seed)
+
+ # Create the Gaussian kernel
+ kernel = gaussian_kernel(random_kernel_size, 0., random_sigma)
+ kernel = kernel[:, :, tf.newaxis, tf.newaxis]
+ kernel = tf.tile(kernel, [1, 1, 3, 1]) # Match kernel to the RGB channels
+
+ # Ensure image is 4D (batch size, height, width, channels)
+ img = tf.cond(tf.equal(tf.rank(img), 3), # Check if the image is rank 3 (without batch dimension)
+ lambda: tf.expand_dims(img, axis=0), # Add batch dimension
+ lambda: img) # If already 4D, pass as-is
+
+ # Apply depthwise convolution (motion blur)
+ img = tf.nn.depthwise_conv2d(img, kernel, [1, 1, 1, 1], padding='SAME')[0]
+
+ return img
+
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_blur(image),
+ lambda: image
+ )
+```
+
+#### Random motion blur
+
+```{python}
+#| trusted: true
+@tf.function
+def random_motion_blur(image, kernel_size_range=(3, 7), angle_range=(0, 360), probability=0.5, seed=None):
+ def motion_blur_kernel(img, kernel_size, angle):
+ center = tf.cast(kernel_size // 2, tf.float32)
+
+ # Calculate the angle in radians
+ angle_rad = angle * (3.14159265359 / 180.0)
+ cos_a = tf.cos(angle_rad)
+ sin_a = tf.sin(angle_rad)
+
+ # Create meshgrid for indices
+ x = tf.cast(tf.range(kernel_size), tf.float32) - center
+ y = tf.cast(tf.range(kernel_size), tf.float32) - center
+ xx, yy = tf.meshgrid(x, y)
+
+ # Compute the distance to simulate motion blur in the specified direction
+ distance = xx * cos_a + yy * sin_a
+ kernel = tf.cast(tf.abs(distance) < 0.5, tf.float32)
+
+ # Normalize the kernel
+ kernel = kernel / tf.reduce_sum(kernel)
+
+ # Expand dimensions to 4D for depthwise convolution
+ kernel = kernel[:, :, tf.newaxis, tf.newaxis]
+ kernel = tf.tile(kernel, [1, 1, tf.shape(img)[-1], 1]) # Match the number of channels
+
+ return kernel
+
+ def apply_motion_blur(img, seed):
+ # Generate a seed if none is provided for consistent randomness
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=2)
+ kernel_size_seed = seeds[0]
+ angle_seed = seeds[1]
+ # Randomly choose a kernel size and angle within the specified range
+ kernel_size = tf.random.stateless_uniform([], minval=kernel_size_range[0], maxval=kernel_size_range[1], dtype=tf.int32, seed=kernel_size_seed)
+ angle = tf.random.stateless_uniform([], minval=angle_range[0], maxval=angle_range[1], dtype=tf.float32, seed=angle_seed)
+
+ # Create the motion blur kernel
+ kernel = motion_blur_kernel(img, kernel_size, angle)
+
+ # Ensure image is 4D (batch size, height, width, channels)
+ img = tf.cond(tf.equal(tf.rank(img), 3), # Check if the image is rank 3 (without batch dimension)
+ lambda: tf.expand_dims(img, axis=0), # Add batch dimension
+ lambda: img) # If already 4D, pass as-is
+
+ # Apply depthwise convolution (motion blur)
+ img = tf.nn.depthwise_conv2d(img, kernel, [1, 1, 1, 1], padding='SAME')[0]
+
+ return img
+
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ # Apply motion blur with a certain probability
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_motion_blur(image, seed=None),
+ lambda: image)
+```
+
+#### Random median blur
+
+```{python}
+#| trusted: true
+@tf.function
+def random_median_blur(image, kernel_size=5, probability=0.5, seed=None):
+ def apply_median_blur(img, seed):
+ # If no seed is provided, generate one
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+ # Convert image to uint8 for applying the median filter
+ img = tf.cast(img * 255.0, tf.uint8)
+ img = tf.image.rgb_to_grayscale(img)
+ # Apply median blur
+ img = tf.image.median_filter2d(img, filter_shape=[kernel_size, kernel_size])
+ img = tf.image.grayscale_to_rgb(img)
+ # Convert back to float32 after blurring
+ return tf.cast(img, tf.float32) / 255.0
+
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ # Apply the blur based on the given probability
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_median_blur(image, seed=None),
+ lambda: image
+ )
+```
+
+#### Radom gaussian noise
+
+```{python}
+#| trusted: true
+@tf.function
+def random_gaussian_noise(image, var_limit=(0.01, 0.1), probability=0.5, seed=None):
+ def apply_noise(img, seed):
+ # Create a seed if none is provided to ensure consistent randomness
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ seeds = tf.random.experimental.stateless_split(seed, num=2)
+ stddev_seed = seeds[0]
+ noise_seed = seeds[1]
+ # Generate random standard deviation for the noise within the given limits
+ stddev = tf.random.stateless_uniform([], minval=var_limit[0], maxval=var_limit[1], seed=stddev_seed)
+
+ # Generate random Gaussian noise
+ noise = tf.random.stateless_normal(shape=tf.shape(img), mean=0.0, stddev=stddev, dtype=tf.float32, seed=noise_seed)
+
+ # Add noise to the image and clip values to ensure valid pixel values [0, 1]
+ return tf.clip_by_value(img + noise, 0.0, 1.0)
+
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+ # Apply noise with a certain probability
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_noise(image, seed=None),
+ lambda: image
+ )
+```
+
+#### Random one of blur
+
+```{python}
+#| trusted: true
+@tf.function
+def random_one_of_blur(image, probability=0.7, seed=None):
+ def apply_random_transform(img, seed):
+ # If no seed is provided, generate one
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ # Generate a random choice using stateless randomness for reproducibility
+ choice = tf.random.stateless_uniform([], minval=0, maxval=3, dtype=tf.int32, seed=seed)
+
+ # Apply different blur/noise transformations based on random choice
+ img = tf.cond(choice == 0,
+ lambda: random_motion_blur(img, kernel_size_range=kernel_size_range_gm, angle_range=angle_range_gm, probability=1.0, seed=None),
+ lambda: img)
+
+ img = tf.cond(choice == 1,
+ lambda: random_gaussian_blur(img, kernel_size_range=kernel_size_range_gb, sigma_range=sigma_range_gb, probability=1.0, seed=None),
+ lambda: img)
+
+ img = tf.cond(choice == 2,
+ lambda: random_gaussian_noise(img, var_limit=var_limit_gn, probability=1.0, seed=None),
+ lambda: img)
+ return img
+
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+ # Use the main condition to determine whether to apply a transformation or not
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), probability),
+ lambda: apply_random_transform(image, seed=None),
+ lambda: image
+ )
+```
+
+```{python}
+#| trusted: true
+@tf.function
+def random_transpose(image, threshold=0.5, seed=None):
+ # Ensure stateless randomness for reproducibility
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),
+ lambda: tf.image.transpose(image),
+ lambda: image
+ )
+
+
+@tf.function
+def random_vertical_flip(image, threshold=0.5, seed=None):
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),
+ lambda: tf.image.flip_up_down(image),
+ lambda: image
+ )
+
+@tf.function
+def random_horizontal_flip(image, threshold=0.5, seed=None):
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),
+ lambda: tf.image.flip_left_right(image),
+ lambda: image
+ )
+
+@tf.function
+def random_brightness(image, max_delta=0.05, threshold=0.5, seed=None):
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),
+ lambda: tf.image.random_brightness(image, max_delta=max_delta),
+ lambda: image
+ )
+
+@tf.function
+def random_contrast(image, lower=0.9, upper=1.1, threshold=0.5, seed=None):
+ if seed is None:
+ seed = tf.random.uniform([2], maxval=2**31 - 1, dtype=tf.int32)
+
+ return tf.cond(
+ tf.less(tf.random.stateless_uniform([], minval=0, maxval=1, seed=seed), threshold),
+ lambda: tf.image.random_contrast(image, lower=lower, upper=upper),
+ lambda: image
+ )
+```
+
+### Apply augmentations
+
+```{python}
+#| trusted: true
+threshold_low=0.4
+threshold_medium=0.5
+threshold_high=0.7
+threshold_very_high=0.6
+```
+
+```{python}
+#| trusted: true
+threshold_low=0.4
+threshold_medium=0.65
+threshold_high=0.7
+threshold_very_high=0.65
+
+@tf.function
+def augment_minority(image, threshold=threshold_very_high, seed=None):
+ # Apply a series of augmentations with appropriate thresholds
+ image = random_transpose(image, threshold_high, seed=seed)
+ image = random_vertical_flip(image, threshold=threshold_high, seed=seed)
+ image = random_horizontal_flip(image, threshold=threshold_high, seed=seed)
+ image = random_brightness(image, threshold=threshold, seed=seed)
+ image = random_contrast(image, threshold=threshold, seed=seed)
+
+ image = random_one_of_blur(image, probability=threshold_medium, seed=seed)
+ image = random_one_of_distortion(image, probability=threshold_medium, seed=seed)
+ image = random_hue_saturation(image, probability=threshold_medium, seed=seed)
+ image = random_crop(image, probability=threshold_medium, seed=seed)
+ image = random_cutout(image, probability=threshold_medium, seed=seed)
+ image = tf.clip_by_value(image, 0.0, 1.0)
+ return image
+
+@tf.function
+def augment_majority(image, threshold=threshold_low, seed=None):
+ # Apply augmentations with lower thresholds for majority class
+ image = random_transpose(image, threshold=threshold, seed=seed)
+ image = random_vertical_flip(image, threshold=threshold, seed=seed)
+ image = random_horizontal_flip(image, threshold=threshold, seed=seed)
+ image = random_brightness(image, threshold=threshold, seed=seed)
+ image = random_contrast(image, threshold=threshold, seed=seed)
+
+ image = random_one_of_blur(image, probability=threshold, seed=seed)
+ image = random_one_of_distortion(image, probability=threshold, seed=seed)
+ image = random_hue_saturation(image, probability=threshold, seed=seed)
+ image = random_crop(image, probability=threshold, seed=seed)
+ image = random_cutout(image, probability=threshold, seed=seed)
+ image = tf.clip_by_value(image, 0.0, 1.0)
+ return image
+
+@tf.function
+def augment_image_batch_old(images, labels):
+ """
+ This function applies augmentations to batches of images based on the label using vectorized operations.
+ It applies augment_minority if label == 1, otherwise augment_majority.
+ """
+ # Define how to augment each image based on the label
+ def augment_single(image, label):
+ if label == 1:
+ return augment_minority(image)
+ else:
+ return augment_majority(image)
+
+ # Apply augmentations to the batch using tf.map_fn
+ augmented_images = tf.map_fn(lambda x: augment_single(x[0], x[1]), (images, labels), fn_output_signature=tf.float32)
+ # Assuming augment_single takes image and label as input and returns augmented image
+ return augmented_images
+
+@tf.function
+def augment_image_batch(images, labels):
+ """
+ Apply augmentations based on the label (minority or majority) using tf.where.
+ Labels of 1 apply minority augmentations, labels of 0 apply majority augmentations.
+ """
+ # Create a mask where label == 1 (for minority)
+ mask = tf.equal(labels, 1)
+
+ # Apply augmentations conditionally based on the mask
+ augmented_images = tf.where(mask[:, None, None, None],
+ #augment_minority(images),
+ #augment_majority(images)
+ tf.map_fn(lambda x: augment_minority(x), images, fn_output_signature=tf.float32),
+ tf.map_fn(lambda x: augment_majority(x), images, fn_output_signature=tf.float32)
+ )
+ return augmented_images
+
+@tf.function
+def apply_augmentations(dataset, use_tabular_data=use_tabular_data):
+ tf.print("Started data augmentation...")
+ if use_tabular_data:
+ # Map using (image, tabular_data, label)
+ return dataset.map(lambda img, tab, lbl: (augment_image_batch(img, lbl), tab, lbl),
+ num_parallel_calls=tf.data.AUTOTUNE)
+ else:
+ # Map using only (image, label)
+ return dataset.map(lambda img, lbl: (augment_image_batch(img, lbl), lbl),
+ num_parallel_calls=tf.data.AUTOTUNE)
+```
+
+## Data processing
+
+### Caching utils
+
+```{python}
+#| trusted: true
+def get_file_modification_time(file_path):
+ """Get the last modification time of a file."""
+ return os.path.getmtime(file_path)
+
+def get_file_checksum(file_path):
+ """Generate a checksum for a file's contents."""
+ md5_hash = hashlib.md5()
+ with tf.io.gfile.GFile(file_path, 'rb') as f:
+ while chunk := f.read(8192):
+ md5_hash.update(chunk)
+ return md5_hash.hexdigest()
+
+def get_dataset_checksum(file_pattern, check_modification_time=False):
+ """Generate a checksum for the dataset based on file contents or modification times."""
+ files = tf.io.gfile.glob(file_pattern)
+ if not files:
+ raise FileNotFoundError(f"No files match the pattern: {file_pattern}")
+
+ combined_checksum = hashlib.md5()
+
+ for file in files:
+ if check_modification_time:
+ # Include file modification times
+ file_mod_time = str(get_file_modification_time(file))
+ combined_checksum.update(file_mod_time.encode('utf-8'))
+ else:
+ # Include file contents checksum
+ file_checksum = get_file_checksum(file)
+ combined_checksum.update(file_checksum.encode('utf-8'))
+
+ return combined_checksum.hexdigest()
+
+def generate_cache_name(file_pattern, checksum, prefix="dataset_cache", run_id=None):
+ """Generate a unique cache name based on the file pattern and dataset checksum."""
+ if file_pattern:
+ file_pattern_hash = hashlib.md5(file_pattern.encode('utf-8')).hexdigest()
+ else:
+ file_pattern_hash = "not_provided"
+
+ cache_name = f"{prefix}_{file_pattern_hash}_checksum_{checksum}"
+
+ if run_id:
+ cache_name += f"_run_{run_id}"
+
+ return cache_name
+
+def remove_lockfile_if_exists(cache_path):
+ """Remove a lockfile if it exists."""
+ lockfile_path = f"{cache_path}.lockfile"
+ if os.path.exists(lockfile_path):
+ print(f"Removing stale lockfile: {lockfile_path}")
+ os.remove(lockfile_path)
+
+def check_cache_exists(cache_dir, cache_name):
+ """Check if the cache exists by looking for any cache files with the given prefix."""
+ cache_pattern = os.path.join(cache_dir, cache_name + "*")
+ return len(glob(cache_pattern)) > 0
+
+# Force full caching of the dataset by fully consuming it
+def fully_cache_dataset(dataset, train_val_dataset_steps):
+ """Fully cache the dataset by iterating over all elements."""
+ print("Triggering full dataset caching...")
+ for _ in dataset:
+ pass # Consume all elements to force caching
+ print("Caching complete.")
+ return dataset
+
+def persist_dataset(dataset, file_pattern, dataset_steps, cache_in_memory, cache_dir=cache_dir,
+ prefix="dataset_cache", run_id=None, check_modification_time=False, force_cache=True):
+ """
+ Efficiently caches a TensorFlow dataset to memory or disk.
+
+ Args:
+ dataset (tf.data.Dataset): The TensorFlow dataset to cache.
+ file_pattern (str): Pattern for the dataset files.
+ dataset_steps (int): Number of steps in the dataset.
+ cache_in_memory (bool): Whether to cache the dataset in memory.
+ cache_dir (str): Directory for storing the cached dataset (if not in memory).
+ prefix (str): Prefix for cache file names.
+ run_id (str): Optional identifier to distinguish cache versions.
+ check_modification_time (bool): Consider file modification times for caching.
+ force_cache (bool): If True, forces cache re-generation.
+
+ Returns:
+ tf.data.Dataset: The cached dataset.
+ """
+ # Generate the cache name and path
+ if file_pattern:
+ dataset_checksum = get_dataset_checksum(file_pattern, check_modification_time)
+ else:
+ dataset_checksum = 'not_provided'
+
+ cache_name = generate_cache_name(file_pattern, dataset_checksum, prefix, run_id)
+ cache_path = os.path.join(cache_dir, cache_name)
+
+ # If forcing cache or no cache exists, optionally clean the previous cache
+ if force_cache:
+ clean_cache_path(cache_path)
+ gc.collect()
+
+ # Use existing cache if available and not forced to recache
+ if not force_cache and check_cache_exists(cache_dir, cache_name):
+ print(f"Using cached dataset at {cache_path}")
+ #return dataset if cache_in_memory else dataset.cache(cache_path)
+ return dataset
+
+ # Create cache directory only if caching to disk
+ if not cache_in_memory:
+ os.makedirs(cache_dir, exist_ok=True)
+ print(f"Caching dataset to disk at {cache_path}")
+ else:
+ print("Caching dataset to memory")
+
+ # Cache in memory or disk and ensure dataset is fully cached
+ dataset = dataset.cache() if cache_in_memory else dataset.cache(cache_path)
+ fully_cache_dataset(dataset, dataset_steps)
+
+ return dataset
+
+def clean_cache_path(cache_path, cache_dir=cache_dir):
+ """
+ Removes all lockfiles from the cache directory to clean up stale caches.
+ Args:
+ cache_dir (str): The directory where the cache is stored.
+ """
+ try:
+ # List all files in the cache directory
+ for root, dirs, files in os.walk(cache_dir):
+ for file in files:
+ # Check if the file is a lockfile
+ if file.startswith(cache_path):
+ lockfile_path = os.path.join(root, file)
+ print(f"Removing lockfile: {lockfile_path}")
+ os.remove(lockfile_path)
+ print(f"Cache {cache_path} cleanup completed")
+ except Exception as e:
+ print(f"An error occurred while cleaning the cache path {cache_path}: {str(e)}")
+```
+
+### Data processing utils
+
+```{python}
+#| trusted: true
+def calculate_samples_per_class(labels):
+ unique, counts = np.unique(labels, return_counts=True)
+ return dict(zip(unique, counts))
+
+def calculate_class_weights(samples_per_class):
+ total_samples = sum(samples_per_class.values())
+ return {cls: total_samples / (len(samples_per_class) * count) for cls, count in samples_per_class.items()}
+
+def calculate_steps_per_epoch(total_samples, batch_size=batch_size, strategy=strategy):
+ steps = total_samples // (batch_size * strategy.num_replicas_in_sync)
+ if steps == 0:
+ raise Exception(f"Calculated steps is 0 because the provided batch_size {batch_size} is too big !\n Total samples is {total_samples}")
+ return steps
+
+def stratified_split_indices(labels, n_splits=5, shuffle=True, random_state=42):
+ skf = StratifiedKFold(n_splits=n_splits, shuffle=shuffle, random_state=random_state)
+ return list(skf.split(np.zeros(len(labels)), labels))
+
+def count_samples_in_tfrecord(file_pattern):
+ """Count the total number of samples in the TFRecord files."""
+ print("Calculating tfrecords dataset size...")
+ dataset = tf.data.Dataset.list_files(file_pattern)
+ total_count = 0
+ for file in dataset:
+ count = sum(1 for _ in tf.data.TFRecordDataset(file, compression_type="GZIP"))
+ total_count += count
+ return total_count
+
+def split_dataset(dataset, indices):
+ tf.print('Splitting the dataset')
+ indices = tf.convert_to_tensor(indices, dtype=tf.int64)
+ indices_dataset = tf.data.Dataset.from_tensor_slices(indices)
+
+ # Use enumerate to pair each element with its index, then filter
+ filtered_dataset = dataset.enumerate().filter(
+ lambda idx, _: tf.reduce_any(tf.equal(idx, indices))
+ ).map(lambda idx, data: data) # Discard the index after filtering
+
+ #if use_tabular_data:
+ #return filtered_dataset.map(lambda img, tab, lbl: ((img, tab), lbl))
+ #else:
+ #return filtered_dataset.map(lambda img, lbl: (img, lbl))
+ return filtered_dataset
+
+def count_samples_in_training_dataset(dataset, batch_size=None, strategy=None):
+ """
+ Count the number of samples in a dataset, taking into account batched datasets.
+
+ Args:
+ dataset: A `tf.data.Dataset` object, possibly batched.
+ batch_size: If the dataset is batched, specify the batch size to correctly count samples.
+
+ Returns:
+ Integer count of the number of samples in the dataset.
+ """
+ print("Calculating the dataset size...")
+
+ # If the dataset is batched, multiply by batch size, or use the actual number of samples in each batch
+ if batch_size:
+ # Count the number of batches
+ batch_count = dataset.reduce(0, lambda x, _: x + 1).numpy()
+ # Multiply by batch size to get the total number of samples
+ total_samples = batch_count * batch_size * strategy.num_replicas_in_sync
+ print(f"Total samples: {total_samples} and number of batches: {batch_count}")
+ return total_samples
+ else:
+ # Unbatched case: Simply count the samples
+ total_samples = dataset.reduce(0, lambda x, _: x + 1).numpy()
+ print(f"Total samples: {total_samples}")
+ return total_samples
+
+def format_input_dataset(dataset, use_tabular_data=use_tabular_data):
+ if use_tabular_data:
+ return dataset.map(lambda img, tab, lbl: ((img, tab), lbl))
+ else:
+ return dataset.map(lambda img, lbl: (img, lbl))
+
+def preprocess_image(image, label):
+ image = tf.image.resize(image, [img_height, img_width])
+ return image, label
+
+def cast_labels(dataset, use_tabular_data=use_tabular_data):
+ if use_tabular_data:
+ return dataset.map(lambda img, tab, lbl: (img, tab, tf.cast(lbl, dtype=tf.float32)))
+ else:
+ return dataset.map(lambda img, lbl: (img, tf.cast(lbl, dtype=tf.float32)))
+
+# Helper to optimize dataset caching and prefetching
+def optimize_dataset(dataset, steps, batch_size=batch_size, file_pattern=None, shuffle=True, buffer_size=buffer_size,
+ drop_remainder=True, prefix='train', force_cache=False, cache_in_memory=False, run_id=run_id,
+ cache_dir=cache_dir, check_modification_time=False):
+ """Optimize dataset with caching, batching, shuffling, and prefetching."""
+ #dataset = persist_dataset(dataset, file_pattern=file_pattern, dataset_steps=steps, cache_in_memory=cache_in_memory,
+ #force_cache=force_cache, prefix=prefix, run_id=run_id,
+ #cache_dir=cache_dir, check_modification_time=check_modification_time)
+ if prefix == 'train':
+ print(f"Repeating, shuffling and batching {prefix} dataset")
+ else:
+ print(f"Repeating {prefix} dataset")
+ dataset = dataset.repeat()
+ dataset = dataset.shuffle(buffer_size=buffer_size) if shuffle else dataset
+ if prefix == 'train':
+ dataset = dataset.batch(batch_size * strategy.num_replicas_in_sync, drop_remainder=drop_remainder)
+ dataset = dataset.prefetch(tf.data.AUTOTUNE)
+ return dataset
+
+def clean_session():
+ # Reset the session and clear the graph before each fold
+ tf.keras.backend.clear_session()
+ tf.compat.v1.reset_default_graph()
+ gc.collect()
+
+def deletes_old_datasets():
+ try:
+ del train_dataset
+ del val_dataset
+ except:
+ pass
+ clean_session()
+```
+
+### Oversampling utils
+
+```{python}
+#| trusted: true
+def oversample_minority_class_random(dataset, batch_size=batch_size, strategy=strategy, use_tabular_data=use_tabular_data):
+ """
+ Randomly oversample the minority class by duplicating its samples within the dataset.
+ """
+ tf.print("\nRandom oversampling the minority class...")
+ # Separate the dataset into majority and minority classes
+ minority_class = 1
+ majority_class = 0
+
+ if use_tabular_data:
+ # For datasets with both image and tabular data
+ minority_dataset = dataset.filter(lambda image, tab, label: tf.equal(label, minority_class))
+ majority_dataset = dataset.filter(lambda image, tab, label: tf.equal(label, majority_class))
+ else:
+ # For datasets with only image data
+ minority_dataset = dataset.filter(lambda image, label: tf.equal(label, minority_class))
+ majority_dataset = dataset.filter(lambda image, label: tf.equal(label, majority_class))
+
+ # Count the number of samples in each class
+ minority_count = count_samples_in_training_dataset(minority_dataset, batch_size=None)
+ majority_count = count_samples_in_training_dataset(majority_dataset, batch_size=None)
+
+ sample_per_class_input_ds = dict(majority_class=majority_count, minority_class=minority_count)
+
+ # Calculate how many samples need to be added to the minority class
+ additional_samples_needed = majority_count - minority_count
+
+ # Randomly sample from the minority dataset to add more samples
+ minority_dataset_repeated = minority_dataset.repeat()
+ minority_dataset_sampled = minority_dataset_repeated.shuffle(buffer_size=buffer_size).take(additional_samples_needed)
+
+ # Combine the sampled dataset with the original minority dataset
+ oversampled_minority_dataset = minority_dataset.concatenate(minority_dataset_sampled)
+
+ # Combine the oversampled minority class with the majority class
+ oversampled_dataset = majority_dataset.concatenate(oversampled_minority_dataset)
+
+ # Shuffle the combined dataset
+ oversampled_dataset = oversampled_dataset.shuffle(buffer_size=buffer_size)
+
+ # Recalculate steps per epoch after oversampling
+ oversampled_sample_count = majority_count + additional_samples_needed
+ training_steps = calculate_steps_per_epoch(oversampled_sample_count, batch_size)
+
+ sample_per_class_oversampled_ds = dict(majority_class=majority_count, minority_class=majority_count)
+
+ return oversampled_dataset, training_steps, oversampled_sample_count, sample_per_class_input_ds, sample_per_class_oversampled_ds
+
+
+def oversample_minority_with_tabular_smote_images_random(dataset, batch_size=batch_size, strategy=strategy, use_tabular_data=use_tabular_data):
+ """
+ Randomly oversample the minority class by duplicating its samples within the dataset for images.
+ Apply SMOTE for tabular data to oversample synthetic features. The function handles both cases: with and without tabular data.
+ """
+ # Separate the dataset into majority and minority classes
+ minority_class = 1
+ majority_class = 0
+
+ if use_tabular_data:
+ # If using tabular data, the dataset contains (image, tabular_data, label)
+ tabular_data = []
+ images = []
+ labels = []
+
+ for image, tabular, label in dataset:
+ images.append(image.numpy())
+ tabular_data.append(tabular.numpy())
+ labels.append(label.numpy())
+
+ # Convert to numpy arrays
+ tabular_data = np.array(tabular_data)
+ labels = np.array(labels)
+ images = np.array(images)
+
+ # Separate majority and minority classes
+ minority_images = images[labels == minority_class]
+ minority_tabular_data = tabular_data[labels == minority_class]
+ majority_images = images[labels == majority_class]
+ majority_tabular_data = tabular_data[labels == majority_class]
+
+ # Apply SMOTE on the tabular data
+ smote = SMOTE(sampling_strategy='auto')
+ tabular_data_resampled, labels_resampled = smote.fit_resample(tabular_data, labels)
+
+ # Find how many new samples were generated by SMOTE for the minority class
+ new_minority_count = np.sum(labels_resampled == minority_class) - len(minority_tabular_data)
+
+ # For each new synthetic tabular data point, randomly pick a corresponding minority image
+ random_indices = np.random.randint(0, len(minority_images), new_minority_count)
+ minority_images_resampled = minority_images[random_indices]
+ #minority_images_resampled = np.tile(minority_images, (new_minority_count // len(minority_images) + 1, 1, 1, 1))[:new_minority_count]
+
+
+ # Combine the original majority data with the resampled minority data
+ combined_images = np.concatenate([majority_images, minority_images, minority_images_resampled])
+ combined_tabular_data = np.concatenate([majority_tabular_data, minority_tabular_data, tabular_data_resampled[len(minority_tabular_data):]])
+ combined_labels = np.concatenate([np.full(len(majority_images), majority_class), np.full(len(minority_images), minority_class), np.full(len(minority_images_resampled), minority_class)])
+
+ assert len(combined_images) == len(combined_tabular_data) == len(combined_labels), "Mismatch in dataset length!"
+
+ # Convert the combined data back to TensorFlow tensors
+ combined_images = tf.convert_to_tensor(combined_images, dtype=tf.float32)
+ combined_tabular_data = tf.convert_to_tensor(combined_tabular_data, dtype=tf.float32)
+ combined_labels = tf.convert_to_tensor(combined_labels, dtype=tf.int64)
+
+ # Combine the resampled data into a dataset
+ oversampled_dataset = tf.data.Dataset.from_tensor_slices((combined_images, combined_tabular_data, combined_labels))
+
+ else:
+ # If not using tabular data, the dataset contains (image, label)
+ minority_dataset = dataset.filter(lambda image, label: tf.equal(label, minority_class))
+ majority_dataset = dataset.filter(lambda image, label: tf.equal(label, majority_class))
+
+ # Apply random oversampling for images only
+ minority_dataset_repeated = minority_dataset.repeat()
+ minority_dataset_sampled = minority_dataset_repeated.shuffle(buffer_size=buffer_size).take(majority_dataset.reduce(0, lambda x, _: x + 1).numpy())
+
+ oversampled_minority_dataset = minority_dataset.concatenate(minority_dataset_sampled)
+
+ # Combine the oversampled minority class with the majority class
+ oversampled_dataset = majority_dataset.concatenate(oversampled_minority_dataset)
+
+ # Shuffle the combined dataset (optional)
+ oversampled_dataset = oversampled_dataset.shuffle(buffer_size=buffer_size)
+
+ # Recalculate steps per epoch after oversampling
+ oversampled_sample_count = len(combined_labels) if use_tabular_data else count_samples_in_training_dataset(oversampled_dataset)
+ training_steps = calculate_steps_per_epoch(oversampled_sample_count, batch_size)
+
+ return oversampled_dataset, training_steps, oversampled_sample_count
+```
+
+### Data loading utils
+
+```{python}
+#| trusted: true
+from sklearn.model_selection import StratifiedKFold
+from sklearn.metrics import roc_auc_score
+import numpy as np
+import math
+
+def parse_tfrecord_fn(example_proto):
+ """Parse a TFRecord into image and optional tabular data."""
+ feature_description = {'image': tf.io.FixedLenFeature([], tf.string), 'label': tf.io.FixedLenFeature([], tf.int64)}
+ if use_tabular_data:
+ feature_description['tabular_data'] = tf.io.FixedLenFeature([14], tf.float32)
+
+ example = tf.io.parse_single_example(example_proto, feature_description)
+ image = tf.image.decode_jpeg(example['image'], channels=3)
+ image = tf.image.resize(image, [img_height, img_width])
+ image = image / 255.0
+
+ #label = tf.cast(example['label'], tf.float32)
+ if use_tabular_data:
+ print('Parsing data including tabular metadata')
+ print(example['label'])
+ return image, example['tabular_data'], example['label']
+ else:
+ return image, example['label']
+
+# Wrap parsing function with error handling
+def safe_parse_fn(example):
+ try:
+ return parse_tfrecord_fn(example)
+ except Exception as e:
+ tf.print(f"Error parsing example: {e}")
+ return None
+
+def load_tfrecord_dataset(file_pattern,
+ use_tabular_data=False,
+ cache_in_memory=False,
+ is_training=False,
+ batch_size=batch_size,
+ num_parallel_reads=tf.data.AUTOTUNE,
+ data_type='Train'):
+ """Load TFRecord dataset with optional data augmentation and tabular data handling."""
+ tf.print(f"\nLoading {data_type} TFRecords...")
+
+ # Create a dataset of file paths
+ files = tf.data.Dataset.list_files(file_pattern, shuffle=is_training)
+ tf.print(f"Number of files: {files.cardinality().numpy()}")
+
+ # Interleave the files to read them in parallel
+ dataset = files.interleave(
+ lambda x: tf.data.TFRecordDataset(x, compression_type="GZIP"),
+ cycle_length=num_parallel_reads,
+ num_parallel_calls=tf.data.AUTOTUNE
+ )
+
+ # Parse the TFRecord files
+ dataset = dataset.map(parse_tfrecord_fn, num_parallel_calls=tf.data.AUTOTUNE)
+
+ dataset = dataset.cache()
+
+ #dataset = persist_dataset(dataset, file_pattern, cache_in_memory, '/tmp/tf_cache_2', prefix=data_type, run_id=run_id, check_modification_time=False)
+
+ if is_training:
+ dataset = dataset.prefetch(tf.data.AUTOTUNE)
+ else:
+ dataset = dataset.batch(batch_size * strategy.num_replicas_in_sync, drop_remainder=True)
+ dataset = dataset.prefetch(tf.data.AUTOTUNE)
+ print("Batched the dataset using batch size:", batch_size * strategy.num_replicas_in_sync)
+
+ rows_count = count_samples_in_tfrecord(file_pattern)
+ print(f"Loaded dataset at {file_pattern}, number of rows: {rows_count}")
+ if rows_count == 0:
+ raise Exception("The provided dataset is empty")
+
+ steps_per_epoch = calculate_steps_per_epoch(rows_count, batch_size)
+
+ print(f"Dataset number of batches: {steps_per_epoch}")
+
+ return dataset, steps_per_epoch
+```
+
+# Modelling
+
+## Models utils
+
+### Base model
+
+```{python}
+#| trusted: true
+# Helper function to apply dense block
+def apply_dense_block(x, units, activation='swish', l2_lambda=None, dropout_rate=None, kernel_initializer='glorot_uniform'):
+ #print(f"Applying dense bloc with: kernel_initializer {kernel_initializer}, dropout_rate {dropout_rate}, activation {activation}, l2_lambda {l2_lambda}")
+ print("Applying dense bloc with kernel_initializer: {}, dropout_rate: {}, activation: {}, l2_lambda: {}".
+ format(kernel_initializer, dropout_rate, activation, l2_lambda))
+ kernel_reg = regularizers.l2(l2_lambda) if l2_lambda else None
+ x = layers.Dense(units, kernel_regularizer=kernel_reg, kernel_initializer=kernel_initializer, activation=activation)(x)
+ #x = layers.BatchNormalization()(x)
+ if dropout_rate:
+ x = layers.Dropout(dropout_rate)(x)
+ return x
+
+# Base model class for all models
+class BaseModel:
+ def __init__(self, model_name, dropout_rate=None, l2_lambda=None, freeze_base_model=False, img_shape=img_shape,
+ num_tabular_features=14, use_tabular_data=False, kernel_initializer='glorot_uniform', run_id=run_id, **kwargs):
+ """
+ Base model constructor. Supports flexible parameter passing through **kwargs.
+
+ Parameters:
+ - model_name: Name of the model to build (e.g., 'resnet50').
+ - dropout_rate: Dropout rate for dense layers.
+ - l2_lambda: L2 regularization.
+ - freeze_base_model: Whether to freeze the base model layers.
+ - img_shape: Shape of the input images.
+ - num_tabular_features: Number of tabular features (for hybrid models).
+ - use_tabular_data: Whether to include tabular data as input.
+ - kernel_initializer: Initializer for the kernel weights matrix.
+ - run_id: Unique identifier for the model run (useful for saving/loading).
+ - **kwargs: Additional parameters to pass through the model pipeline.
+ """
+ def get_param(value, default, kwargs, key):
+ if value is not None:
+ return value
+ return kwargs.get(key, default)
+ self.model_name = get_param(model_name, None, kwargs, 'model_name')
+ self.dropout_rate = get_param(dropout_rate, None, kwargs, 'dropout_rate')
+ self.l2_lambda = get_param(l2_lambda, None, kwargs, 'l2_lambda')
+ self.freeze_base_model = get_param(freeze_base_model, False, kwargs, 'freeze_base_model')
+ self.img_shape = get_param(img_shape, (224, 224, 3), kwargs, 'img_shape')
+ self.num_tabular_features = get_param(num_tabular_features, None, kwargs, 'num_tabular_features')
+ self.use_tabular_data = get_param(use_tabular_data, False, kwargs, 'use_tabular_data')
+ self.kernel_initializer = get_param(kernel_initializer, 'glorot_uniform', kwargs, 'kernel_initializer')
+ self.activation = get_param(activation, 'swish', kwargs, 'activation')
+ self.run_id = get_param(run_id, None, kwargs, 'run_id')
+ self.kwargs = kwargs
+
+ def _set_model_inputs_(self):
+ """Set input layers for the model, with optional tabular data."""
+ image_input = tf.keras.Input(shape=self.img_shape, name='image_input')
+ if self.use_tabular_data:
+ tabular_input = tf.keras.Input(shape=(self.num_tabular_features,), name='tabular_input')
+ return [image_input, tabular_input]
+ return image_input
+
+ def _set_model_inputs(self, image_input):
+ """Set input layers for the model, with optional tabular data."""
+ if self.use_tabular_data:
+ tabular_input = tf.keras.Input(shape=(self.num_tabular_features,), name='tabular_input')
+ return [image_input, tabular_input]
+ return image_input
+
+ def _get_base_model(self, model_name=None):
+ """Dynamically load the base model using the model name."""
+ if model_name is None:
+ model_name = self.model_name
+ try:
+ parts = model_name.split('_')
+ # Trick to get the class name from the provided string
+ # Example res_net_50 -> ResNet50, vgg_19 -> VGG19
+ if len(parts) > 2:
+ model_class_name = ''.join([part.capitalize() for part in parts])
+ else:
+ model_class_name = ''.join([part.upper() for part in parts])
+
+ base_model_cls = getattr(tf.keras.applications, model_class_name, None)
+
+ if base_model_cls is None:
+ raise ValueError(f"Unknown model name: {model_name}")
+ return base_model_cls(weights=None, include_top=False, input_shape=self.img_shape)
+ except Exception as e:
+ raise ValueError(f"Error loading model {model_name}: {str(e)}")
+
+ def _process_base_model(self, base_model):
+ """Freeze/unfreeze base model and apply global pooling."""
+ base_model.trainable = not self.freeze_base_model
+ return layers.GlobalAveragePooling2D()(base_model.output)
+
+ def _combine_image_and_tabular_features(self, image_features, inputs):
+ """Combine image features with tabular features, if applicable."""
+ if self.use_tabular_data:
+ tabular_input = inputs[1]
+ tabular_features = apply_dense_block(tabular_input, 1024, l2_lambda=self.l2_lambda, dropout_rate=self.dropout_rate,
+ kernel_initializer=self.kernel_initializer, activation=self.activation)
+ tabular_features = apply_dense_block(tabular_features, 812, l2_lambda=self.l2_lambda, dropout_rate=self.dropout_rate,
+ kernel_initializer=self.kernel_initializer, activation=self.activation)
+ return layers.Concatenate()([image_features, tabular_features])
+ return image_features
+
+ def set_model_architecture(self, model_name=None):
+ """Set the full model architecture."""
+ print(f'Setting model {model_name} architecture')
+ base_model = self._get_base_model(model_name)
+ image_input = base_model.input
+ inputs = self._set_model_inputs(image_input)
+ #inputs = self._set_model_inputs()
+ image_features = self._process_base_model(base_model)
+ combined_features = self._combine_image_and_tabular_features(image_features, inputs)
+
+ # Add Dense and BatchNorm layers
+ combined_features = apply_dense_block(combined_features, 512, l2_lambda=self.l2_lambda, dropout_rate=self.dropout_rate,
+ kernel_initializer=self.kernel_initializer, activation=self.activation)
+ #combined_features = apply_dense_block(combined_features, 256, l2_lambda=self.l2_lambda, dropout_rate=self.dropout_rate,
+ #kernel_initializer=self.kernel_initializer, activation=self.activation)
+
+ # Dropout Models (Multiple Dropouts for Ensembling)
+ combineds = [layers.Dropout(0.2)(combined_features) for _ in range(2)]
+
+ # Apply a Dense layer after each dropout
+ outputs = [layers.Dense(1, activation='sigmoid')(combined) for combined in combineds]
+
+ # Average the outputs for ensembling
+ final_output = layers.Average()(outputs)
+
+ model = tf.keras.Model(inputs=inputs, outputs=final_output)
+
+ model.base_model = base_model # Keep a reference to the base model for flexibility
+ return model
+
+ def create_model(self, model_name=None):
+ """
+ Create a base model using the architecture specified by model_name.
+ If model_name is None, use the instance's model_name.
+ """
+ if model_name is None:
+ model_name = self.model_name
+ print(f"Creating model {model_name}")
+ model = self.set_model_architecture(model_name)
+ model.model_name = model_name
+ return model
+```
+
+### Ensemble model
+
+```{python}
+#| trusted: true
+# EnsembleModel class inherits from BaseModel
+class EnsembleModel(BaseModel):
+ def __init__(self, model_name, model_names, mode='weighted', run_id=run_id, num_ensemble=2, weights=None, **kwargs):
+ super().__init__(model_name=model_name, **kwargs)
+ self.model_name = model_name
+ self.model_names = model_names
+ self.mode = mode
+ self.num_ensemble = num_ensemble
+ self.weights = weights if weights else [1.0] * len(model_names)
+ self.run_id = run_id
+
+ def _load_pretrained_model(self, model_name):
+ """Load a pretrained model and return it."""
+ model = self.set_model_architecture(model_name)
+
+ def resolve_prm_name(model_name):
+ return ''.join(model_name.split('_'))
+
+ try:
+ model.load_weights(os.path.join(pretrained_models_dir, f'{resolve_prm_name(model_name)}_weights.h5'))
+ except Exception as e:
+ raise ValueError(f"Error loading weights for model {model_name}: {str(e)}")
+
+ return model
+
+ def create_ensemble(self, pretrained):
+ """
+ Create an ensemble model with the selected strategy (average, max, or weighted).
+ Can load pretrained models or create new ones based on the `pretrained` flag.
+ """
+ # Collect outputs from each model in the ensemble
+ inputs = self._set_model_inputs_() # Get the input layers
+ model_outputs = []
+
+ for model_name in self.model_names:
+
+ if pretrained:
+ ## Creates a pretrained model by loading the loads of the pretrained ensemble models
+ model = self._load_pretrained_model(model_name)
+ else:
+ ## Only set the architecture of the ensemble model from the individuals
+ model = self.create_model(model_name=model_name) # Create a new model from the architecture
+
+ model_outputs.append(model(inputs))
+
+ # Apply the selected ensembling strategy to combine the model outputs
+ ensemble_output = self._apply_ensemble_strategy(model_outputs)
+
+ # Create and return the ensemble model
+ ensemble_model = tf.keras.Model(inputs=inputs, outputs=ensemble_output)
+
+ ensemble_model.model_name = self.model_name # For later reuse in the training pipeline
+
+ return ensemble_model
+
+ def _apply_ensemble_strategy(self, outputs):
+ """Apply different ensemble strategies."""
+ if self.mode == 'average':
+ return layers.Average()(outputs)
+ elif self.mode == 'max':
+ return layers.Maximum()(outputs)
+ elif self.mode == 'weighted' and self.weights:
+ self.weights = [w / sum(self.weights) for w in self.weights]
+ return layers.Add()([output * weight for output, weight in zip(outputs, self.weights)])
+ elif self.mode == 'weighted_layer' and self.weights:
+ self.weights = [w / sum(self.weights) for w in self.weights]
+ weighted_outputs = [layers.Lambda(lambda x, w=weight: x * w)(output) for output, weight in zip(outputs, self.weights)]
+ return layers.Add()(weighted_outputs)
+ else:
+ raise ValueError(f"Unknown ensemble mode: {self.mode}")
+```
+
+### Single model
+
+```{python}
+#| trusted: true
+class SingleModel(BaseModel):
+ def __init__(self, model_name, **kwargs):
+ super().__init__(model_name=model_name, **kwargs)
+```
+
+### Factory utils
+
+```{python}
+#| trusted: true
+class ModelFactory:
+ @staticmethod
+ def create_model(model_type, model_names, pretrained=False, **kwargs):
+ """Factory method to create models based on type."""
+
+ strategy = kwargs.get('strategy')
+
+ with strategy.scope():
+ if model_type == 'ensemble':
+ weights = kwargs.get('weights')
+ ensemble_model_name = '_'.join(['_'.join((model_name, str(weight))) for (model_name, weight) in zip(model_names, weights)])
+ ensemble_model_name = f"pretrained_ensemble_{ensemble_model_name}" if pretrained else f"compact_ensemble_{ensemble_model_name}"
+ return EnsembleModel(model_name=ensemble_model_name, model_names=model_names, **kwargs).create_ensemble(pretrained=pretrained)
+
+ elif model_type == 'single':
+ return SingleModel(model_name=model_names[0], **kwargs).create_model()
+ else:
+ raise ValueError(f"Unknown model type: {model_type}")
+```
+
+### Loss functions
+
+```{python}
+#| trusted: true
+@register_keras_serializable()
+@tf.function
+def focal_loss(y_true, y_pred, gamma=2.0, alpha=0.25):
+ """
+ Focal Loss for binary classification.
+
+ Args:
+ - y_true: Ground truth labels, shape = (batch_size, 1)
+ - y_pred: Predicted labels, shape = (batch_size, 1)
+ - gamma: Focusing parameter (default is 2.0)
+ - alpha: Balancing factor (default is 0.25)
+
+ Returns:
+ - Loss value
+ """
+ # Clip predictions to prevent log(0) and ensure stability
+ y_pred = tf.clip_by_value(y_pred, K.epsilon(), 1. - K.epsilon())
+
+ # Calculate cross-entropy loss
+ bce_loss = - (y_true * K.log(y_pred) + (1 - y_true) * K.log(1 - y_pred))
+
+ # Calculate p_t and modulating factor
+ p_t = y_true * y_pred + (1 - y_true) * (1 - y_pred)
+ modulating_factor = (1 - p_t) ** gamma
+
+ # Apply alpha balancing factor
+ alpha_factor = y_true * alpha + (1 - y_true) * (1 - alpha)
+
+ # Compute focal loss
+ loss = alpha_factor * modulating_factor * bce_loss
+
+ return tf.reduce_mean(loss)
+
+@register_keras_serializable()
+@tf.function
+def class_balanced_loss(y_true, y_pred, beta=0.99, samples_per_cls=None):
+ """
+ Class-Balanced Loss function for binary classification.
+
+ Parameters:
+ - y_true: Ground truth labels, shape = (batch_size,)
+ - y_pred: Predicted labels, shape = (batch_size,)
+ - samples_per_cls: List or array containing the number of samples for each class (class 0 and class 1).
+ - beta: Hyperparameter to adjust class balancing (default is 0.99)
+
+ Returns:
+ - Loss value
+ """
+ # Calculate the effective number of samples
+ effective_num = 1.0 - tf.pow(beta, samples_per_cls)
+ weights = (1.0 - beta) / tf.maximum(effective_num, tf.keras.backend.epsilon())
+ weights = weights / tf.reduce_sum(weights) # Normalize weights
+
+ # Extract the class weights for class 0 and class 1
+ weight_for_0 = weights[0]
+ weight_for_1 = weights[1]
+
+ # Apply weights to the true labels (binary classification)
+ weights_per_sample = y_true * weight_for_1 + (1.0 - y_true) * weight_for_0
+
+ # Compute the binary crossentropy loss
+ loss = tf.keras.losses.binary_crossentropy(y_true, y_pred)
+
+ # Apply class-balanced weights to the loss
+ loss = weights_per_sample * loss
+ return tf.reduce_mean(loss)
+
+@register_keras_serializable()
+def combined_loss(samples_per_cls, beta=0.99, gamma=2.0, alpha=0.8, focal_loss_percent=0.5):
+ """
+ Combined Class-Balanced Loss and Focal Loss.
+
+ Parameters:
+ - y_true: Ground truth labels, shape = (batch_size, num_classes)
+ - y_pred: Predicted labels, shape = (batch_size, num_classes)
+
+ Returns:
+ - Combined loss value
+ """
+ @tf.function
+ def loss(y_true, y_pred):
+ y_true = tf.cast(y_true, tf.float32)
+ y_true = tf.reshape(y_true, [-1, 1])
+ y_pred = tf.reshape(y_pred, [-1, 1])
+
+ cb_loss = class_balanced_loss(y_true, y_pred, beta=beta, samples_per_cls=samples_per_cls)
+ fl_loss = focal_loss(y_true, y_pred, gamma=gamma, alpha=alpha)
+ return ((1 - focal_loss_percent) * cb_loss) + (focal_loss_percent * fl_loss)
+ return loss
+
+##Binary class balance loss
+@register_keras_serializable()
+def get_class_balanced_weights(beta, samples_per_cls):
+ effective_num = 1.0 - np.power(beta, samples_per_cls)
+ effective_num = np.where(effective_num == 0, 1e-8, effective_num) # Avoid division by zero
+ weights = (1.0 - beta) / effective_num
+ weights = weights / np.sum(weights) * len(samples_per_cls)
+ return weights
+
+@tf.function
+@register_keras_serializable()
+def class_balanced_binary_cross_entropy(beta, samples_per_cls):
+ weights = get_class_balanced_weights(beta, samples_per_cls)
+ weight_for_0 = weights[0]
+ weight_for_1 = weights[1]
+
+ def loss(y_true, y_pred):
+ y_true = tf.cast(y_true, tf.float32)
+ bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)
+ weights_per_sample = y_true * weight_for_1 + (1 - y_true) * weight_for_0
+ weighted_bce = weights_per_sample * bce
+ return tf.reduce_mean(weighted_bce)
+
+ return loss
+
+@register_keras_serializable()
+@tf.function
+def focal_loss_keras_bce(y_true, y_pred, gamma, alpha):
+ """
+ Focal Loss function.
+ Parameters:
+ - y_true: Ground truth labels, shape = (batch_size, num_classes)
+ - y_pred: Predicted labels, shape = (batch_size, num_classes)
+ - gamma: Focusing parameter (default is 2.0)
+ - alpha: Balancing factor (default is 0.25)
+
+ Returns:
+ - Loss value
+ """
+ #y_true = tf.cast(y_true, tf.float32)
+ # Compute cross entropy
+ bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)
+
+ # Compute focal loss
+ p_t = y_true * y_pred + (1 - y_true) * (1 - y_pred)
+ alpha_factor = y_true * alpha + (1 - y_true) * (1 - alpha)
+ modulating_factor = tf.pow(1.0 - p_t, gamma)
+
+ loss = alpha_factor * modulating_factor * bce
+ return tf.reduce_mean(loss)
+
+@tf.function
+def weighted_binary_crossentropy(y_true, y_pred):
+ class_weight_0 = 0.1 # Weight for class 0 (negative)
+ class_weight_1 = 0.9 # Weight for class 1 (positive)
+
+ weights = tf.where(tf.equal(y_true, 1), class_weight_1, class_weight_0)
+ bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)
+ weighted_bce = bce * weights
+ return tf.reduce_mean(weighted_bce)
+
+
+@register_keras_serializable()
+def class_balanced_loss_vect(y_true, y_pred, samples_per_cls, beta):
+ """
+ Class-Balanced Loss function with samples_per_cls parameter.
+
+ Parameters:
+ - y_true: Ground truth labels, shape = (batch_size, num_classes)
+ - y_pred: Predicted labels, shape = (batch_size, num_classes)
+ - samples_per_cls: List or array containing the number of samples for each class.
+ - beta: Hyperparameter to adjust class balancing (usually close to 1.0, default is 0.9999)
+
+ Returns:
+ - Loss value
+ """
+ #y_true = tf.cast(y_true, tf.float32)
+ # Calculate the effective number of samples
+ effective_num = 1.0 - tf.pow(beta, samples_per_cls)
+ weights = (1.0 - beta) / tf.maximum(effective_num, tf.keras.backend.epsilon())
+ weights = weights / tf.reduce_sum(weights) # Normalize weights
+
+ # Apply weights to the true labels
+ weights = tf.reduce_sum(weights * y_true, axis=-1)
+
+ # Compute the binary crossentropy loss
+ loss = tf.keras.losses.binary_crossentropy(y_true, y_pred)
+
+ # Apply class-balanced weights to the loss
+ loss = weights * loss
+ return tf.reduce_mean(loss)
+
+@tf.function
+def weighted_binary_crossentropy(y_true, y_pred):
+ # Define class weights
+ class_weight_0 = 0.1 # Weight for class 0 (negative)
+ class_weight_1 = 0.9 # Weight for class 1 (positive)
+
+ # Create a tensor of weights based on the true labels
+ weights = tf.where(tf.equal(y_true, 1), class_weight_1, class_weight_0)
+
+ # Calculate binary crossentropy
+ bce = tf.keras.losses.binary_crossentropy(y_true, y_pred)
+
+ # Apply the weights to the loss
+ weighted_bce = bce * weights
+
+ # Return the mean loss
+ return tf.reduce_mean(weighted_bce)
+
+def focal_loss_fix(gamma=2., alpha=0.25):
+ def focal_loss_fixed(y_true, y_pred):
+ epsilon = K.epsilon()
+ y_pred = K.clip(y_pred, epsilon, 1. - epsilon)
+ y_true = K.cast(y_true, K.floatx())
+
+ alpha_t = y_true * alpha + (K.ones_like(y_true) - y_true) * (1 - alpha)
+ p_t = y_true * y_pred + (K.ones_like(y_true) - y_true) * (1 - y_pred)
+ fl = - alpha_t * K.pow((K.ones_like(y_true) - p_t), gamma) * K.log(p_t)
+ return K.mean(fl)
+ return focal_loss_fixed
+
+def set_binary_crossentropy_weighted_loss(positive_weights=np.array([5.291666666666667]), negative_weights=np.array([0.5521739130434783]), epsilon=1e-7):
+ """
+ Note: Imported from the AI for Medicine Specialization course on Coursera: Assignment 1 Week 1.
+ Returns weighted binary cross entropy loss function given negative weights and positive weights.
+
+ Args:
+ positive_weights (np.array): array of positive weights for each class, size (num_classes)
+ negative_weights (np.array): array of negative weights for each class, size (num_classes)
+
+ Returns:
+ weighted_loss (function): weighted loss function
+ """
+ def binary_crossentropy_weighted_loss(y_true, y_pred):
+ """
+ Returns weighted binary cross entropy loss value.
+
+ Args:
+ y_true (Tensor): Tensor of true labels, size is (num_examples, num_classes)
+ y_pred (Tensor): Tensor of predicted labels, size is (num_examples, num_classes)
+
+ Returns:
+ loss (Tensor): overall scalar loss summed across all classes
+ """
+ y_true = tf.cast(y_true, tf.float32)
+ y_pred = tf.cast(y_pred, tf.float32)
+
+ # initialize loss to zero
+ loss = 0.0
+ #y_true = tf.cast(y_true, tf.float32)
+ for i in range(len(positive_weights)):
+ # for each class, add average weighted loss for that class
+ loss += -1 * K.mean((positive_weights[i] * y_true[:, i] * K.log(y_pred[:, i] + epsilon) +
+ negative_weights[i] * (1 - y_true[:, i]) * K.log(1 - y_pred[:, i] + epsilon)))
+ return loss
+
+ return binary_crossentropy_weighted_loss
+```
+
+### Metrics
+
+```{python}
+#| trusted: true
+def score(y_true, y_pred, min_tpr: float = 0.80) -> float:
+ v_gt = abs(np.asarray(y_true) - 1)
+ v_pred = np.array([1.0 - x for x in y_pred])
+ max_fpr = abs(1 - min_tpr)
+ partial_auc_scaled = roc_auc_score(v_gt, v_pred, max_fpr=max_fpr)
+ partial_auc = 0.5 * max_fpr**2 + (max_fpr - 0.5 * max_fpr**2) / (1.0 - 0.5) * (partial_auc_scaled - 0.5)
+ return partial_auc
+
+@register_keras_serializable()
+class F1Score(tf.keras.metrics.Metric):
+ def __init__(self, name='f1_score', **kwargs):
+ super(F1Score, self).__init__(name=name, **kwargs)
+ self.precision = tf.keras.metrics.Precision()
+ self.recall = tf.keras.metrics.Recall()
+
+ def update_state(self, y_true, y_pred, sample_weight=None):
+ self.precision.update_state(y_true, y_pred, sample_weight)
+ self.recall.update_state(y_true, y_pred, sample_weight)
+
+ def result(self):
+ precision = self.precision.result()
+ recall = self.recall.result()
+ return 2 * ((precision * recall) / (precision + recall + tf.keras.backend.epsilon()))
+
+ def reset_state(self):
+ self.precision.reset_state()
+ self.recall.reset_state()
+
+def get_labels_and_predictions(model, dataset, steps):
+ """Return true labels and predictions from the model."""
+ y_true, y_pred = [], []
+ for x_batch, y_batch in dataset.take(steps):
+ y_pred_batch = model.predict_on_batch(x_batch).ravel()
+ y_true.append(y_batch)
+ y_pred.append(y_pred_batch)
+ return tf.concat(y_true, axis=0), tf.concat(y_pred, axis=0)
+
+def evaluate_model(model, eval_dataset, evaluation_steps, dataset_name='eval_dataset'):
+ print('Evaluating', dataset_name, 'with', evaluation_steps, 'steps')
+ y_val, y_pred = get_labels_and_predictions(model, eval_dataset, evaluation_steps)
+ partial_auc = score(y_val, y_pred)
+ print(f"Partial AUC for {dataset_name}: {partial_auc}")
+
+def get_memory_usage():
+ # memory_usage returns a list, so we get the first item
+ mem = memory_usage(-1, interval=0.1, timeout=1)[0] # Return the first value from the list in MB
+ mem = f"{mem:.2f} MB"
+ return mem
+
+def reset_session_and_get_memory():
+ """Cleans up the session and retrieves the initial memory usage."""
+ clean_session()
+ return get_memory_usage()
+
+def log_memory_usage(stage, memory_usage):
+ """Logs the memory usage with a specific stage identifier."""
+ print(f"{stage} memory usage: {memory_usage}")
+```
+
+### Callbacks
+
+```{python}
+#| trusted: true
+class PartialAUCCallback(tf.keras.callbacks.Callback):
+ def __init__(self, validation_data, validation_steps, monitor, train_data = None, steps_per_epoch=None, min_tpr=0.8):
+ super(PartialAUCCallback, self).__init__()
+ self.validation_data = validation_data
+ self.validation_steps = validation_steps
+ self.monitor = monitor
+ self.train_data = train_data
+ self.steps_per_epoch = steps_per_epoch
+ self.min_tpr = min_tpr
+ self.max_fpr = 1.0 - min_tpr
+ self.best_partial_auc = -float('inf') # Initialize to a very low value
+ self.best_epoch = 0
+
+ def on_epoch_end(self, epoch, logs=None):
+ logs = logs or {}
+
+ # Compute Partial AUC on Training Data if available
+ if self.train_data:
+ train_labels, train_predictions = get_labels_and_predictions(self.model,
+ self.train_data,
+ self.steps_per_epoch)
+ train_partial_auc = self.compute_auc(train_labels, train_predictions)
+ self.log_partial_auc(logs, train_partial_auc, 'partial_auc', epoch)
+
+ PartialAUCCallback.log_classification_report(train_labels, train_predictions, epoch, 'Train')
+
+ #Validation computations
+ val_labels, val_predictions = get_labels_and_predictions(self.model,
+ self.validation_data,
+ self.validation_steps)
+ # Calculate the partial AUC
+ val_partial_auc = self.compute_auc(val_labels, val_predictions)
+ self.log_partial_auc(logs, val_partial_auc, 'val_partial_auc', epoch)
+ PartialAUCCallback.log_classification_report(val_labels, val_predictions, epoch)
+
+
+ def log_partial_auc(self, logs, partial_auc, pauc_metric_name, epoch):
+ logs[pauc_metric_name] = partial_auc
+ if self.monitor == pauc_metric_name:
+ # Store the best partial AUC
+ if partial_auc > self.best_partial_auc:
+ self.best_partial_auc = partial_auc
+ self.best_epoch = epoch
+
+ # Add the partial_auc to logs so it can be used in Keras Tuner
+ logs[pauc_metric_name] = partial_auc
+ tf.print(f'\nEpoch {epoch + 1}: Partial AUC = {partial_auc:.4f}')
+
+ @staticmethod
+ def log_classification_report(labels, predictions, epoch, name='Validation'):
+ # Convert probabilities to binary predictions
+ bin_preds = np.round(predictions).astype(int)
+ # Generate the classification report
+ report = classification_report(labels, bin_preds, labels= [1, 0],
+ target_names=['positives', 'negatives'])
+ # Print the report
+ tf.print(f"\n{name} classification report for epoch {epoch + 1}:\n{report}")
+
+ def compute_auc(self, y_true, y_pred, max_fpr=0.2):
+ v_gt = np.abs(y_true - 1)
+ v_pred = 1.0 - y_pred
+ partial_auc_scaled = roc_auc_score(v_gt, v_pred, max_fpr=max_fpr)
+ partial_auc = 0.5 * max_fpr**2 + (max_fpr - 0.5 * max_fpr**2) / (1.0 - 0.5) * (partial_auc_scaled - 0.5)
+ return partial_auc
+
+ def compute_auc_kaggle(self, y_true, y_pred):
+ y_true = tf.cast(y_true, tf.float32).numpy()
+ y_pred = tf.cast(y_pred, tf.float32).numpy()
+
+ # Invert classes as needed for minority focus
+ v_gt = 1 - y_true
+ v_pred = 1 - y_pred
+ fpr, tpr, _ = roc_curve(v_gt, v_pred)
+
+ # Find where FPR reaches max_fpr
+ stop = np.searchsorted(fpr, self.max_fpr, 'right')
+ if stop == 0: # If max_fpr is very low and FPR doesn't reach it
+ return 0.0
+
+ x_interp = [fpr[stop - 1], fpr[stop]]
+ y_interp = [tpr[stop - 1], tpr[stop]]
+ tpr_interp = np.interp(self.max_fpr, x_interp, y_interp)
+
+ # Append this interpolated point and calculate AUC
+ tpr = np.append(tpr[:stop], tpr_interp)
+ fpr = np.append(fpr[:stop], self.max_fpr)
+ return auc(fpr, tpr)
+
+ def get_best_partial_auc(self):
+ return self.best_partial_auc
+
+ def get_best_epoch(self):
+ return self.best_epoch
+
+ def __deepcopy__(self, memo):
+ # Skip deep copying the validation data to avoid issues
+ return PartialAUCCallback(self.validation_data, self.validation_steps,
+ self.monitor, self.train_data,
+ self.steps_per_epoch, self.min_tpr)
+
+class LearningRatePrinterCallback(tf.keras.callbacks.Callback):
+ def on_epoch_end(self, epoch, logs=None):
+ # Access the learning rate from the optimizer
+ lr = self.model.optimizer.learning_rate
+ # If the learning rate is a learning rate schedule (like CyclicalLearningRate), evaluate it
+ if isinstance(lr, tf.keras.optimizers.schedules.LearningRateSchedule):
+ lr = lr(self.model.optimizer.iterations)
+ tf.print(f"\nLearning rate at the end of epoch {epoch + 1}: {tf.keras.backend.get_value(lr)}")
+
+
+# Custom Callback to monitor memory after each epoch
+class MemoryUsageCallback(tf.keras.callbacks.Callback):
+ def on_epoch_end(self, epoch, logs=None):
+ process = psutil.Process(os.getpid())
+ mem = process.memory_info().rss / (1024 * 1024) # RSS memory in MB
+ print(f"Memory usage after epoch {epoch + 1}: {mem:.2f} MB")
+ #tf.keras.backend.clear_session()
+ #tf.compat.v1.reset_default_graph()
+ gc.collect()
+ #clean_session()
+```
+
+### Optimizers
+
+```{python}
+#| trusted: true
+def clean_metric_name(metric_name):
+ # Use regular expression to check if the string ends with _ followed by digits
+ if re.search(r'_\d+$', metric_name):
+ # Remove the _ and the digits at the end
+ return re.sub(r'_\d+$', '', metric_name)
+ return metric_name
+
+# Define a custom learning rate scheduler for cyclical learning rates
+class CyclicalLearningRate(tf.keras.optimizers.schedules.LearningRateSchedule):
+ def __init__(self, initial_learning_rate, maximal_learning_rate, step_size):
+ self.initial_learning_rate = tf.cast(initial_learning_rate, tf.float32)
+ self.maximal_learning_rate = tf.cast(maximal_learning_rate, tf.float32)
+ self.step_size = tf.cast(step_size, tf.float32)
+
+ def __call__(self, step):
+ step = tf.cast(step, tf.float32)
+ cycle = tf.floor(1 + step / (2 * self.step_size))
+ x = tf.abs(step / self.step_size - 2 * cycle + 1)
+ lr = self.initial_learning_rate + (self.maximal_learning_rate - self.initial_learning_rate) * tf.maximum(0.0, (1 - x))
+ return lr
+
+ def get_config(self):
+ return {
+ "initial_learning_rate": self.initial_learning_rate.numpy(),
+ "maximal_learning_rate": self.maximal_learning_rate.numpy(),
+ "step_size": self.step_size.numpy(),
+ }
+
+# Set the parameters for the CLR
+initial_learning_rate = 1e-6 # Minimum LR
+maximal_learning_rate = 1e-4 # Maximum LR
+step_size = 50 # Number of steps in half a cycle
+def set_optimizer(optimizer, learning_rate=1e-4, use_nesterov_sgd=False, use_amsgrad_adam=False,
+ initial_learning_rate=1e-6,
+ maximal_learning_rate=1e-4,
+ step_size=50
+ ):
+ if optimizer == "sgd":
+ optimizer = optimizers.SGD(
+ learning_rate=learning_rate,
+ momentum=0.9,
+ nesterov=use_nesterov_sgd
+ )
+
+ elif optimizer == "adam":
+ optimizer = optimizers.Adam(
+ learning_rate=learning_rate,
+ beta_1=0.9,
+ beta_2=0.999,
+ epsilon=0.1,
+ amsgrad=use_amsgrad_adam
+ )
+
+ elif optimizer == "nadam":
+ optimizer = optimizers.Nadam(
+ learning_rate=learning_rate,
+ beta_1=0.9,
+ beta_2=0.999,
+ epsilon=0.1
+ )
+ elif optimizer == "adamw":
+ optimizer = optimizers.AdamW(
+ learning_rate=learning_rate,
+ beta_1=0.9,
+ beta_2=0.999,
+ epsilon=0.1
+ )
+ elif optimizer == "adamax":
+ optimizer = optimizers.Adamax(
+ learning_rate=learning_rate,
+ beta_1=0.9,
+ beta_2=0.999,
+ epsilon=0.1
+ )
+ elif optimizer == "radam":
+ optimizer = tfa.optimizers.RectifiedAdam(
+ learning_rate=learning_rate,
+ beta_1=0.9,
+ beta_2=0.999,
+ epsilon=0.1
+ )
+ elif optimizer == "rmsprop":
+ optimizer = optimizers.RMSprop(
+ learning_rate=learning_rate,
+ rho=0.9,
+ momentum=0.0,
+ epsilon=0.1,
+ centered=False
+ )
+ elif optimizer == "nadam_cyclical":
+ optimizer = optimizers.Nadam(
+ learning_rate=CyclicalLearningRate(
+ initial_learning_rate=initial_learning_rate,
+ maximal_learning_rate=maximal_learning_rate,
+ step_size=step_size
+ )
+ )
+ elif optimizer == "radam_cyclical":
+ optimizer = tfa.optimizers.RectifiedAdam(
+ learning_rate=CyclicalLearningRate(
+ initial_learning_rate=initial_learning_rate,
+ maximal_learning_rate=maximal_learning_rate,
+ step_size=step_size
+ )
+ )
+ elif optimizer == "adamw_cyclical":
+ optimizer = optimizers.AdamW(
+ learning_rate=CyclicalLearningRate(
+ initial_learning_rate=initial_learning_rate,
+ maximal_learning_rate=maximal_learning_rate,
+ step_size=step_size
+ )
+ )
+ return optimizer
+
+class OneCycleScheduler(tf.keras.callbacks.Callback):
+ def __init__(self, max_lr, steps_per_epoch, epochs, start_lr=None, last_epoch_lr=None):
+ super(OneCycleScheduler, self).__init__()
+ self.max_lr = max_lr
+ self.steps_per_epoch = steps_per_epoch
+ self.epochs = epochs
+ self.total_steps = steps_per_epoch * epochs
+ self.current_step = 0
+
+ if start_lr is None:
+ self.start_lr = max_lr / 25 # A commonly used starting ratio
+ else:
+ self.start_lr = start_lr
+
+ if last_epoch_lr is None:
+ self.last_epoch_lr = self.start_lr / 1e4 # A small value to anneal towards
+ else:
+ self.last_epoch_lr = last_epoch_lr
+
+ def on_batch_begin(self, batch, logs=None):
+ # Update learning rate based on current step
+ self.current_step += 1
+ lr = self.calc_lr()
+ #tf.keras.backend.set_value(self.model.optimizer[0].lr, lr)
+ tf.keras.backend.set_value(self.model.optimizer.lr, lr)
+
+ def calc_lr(self):
+ # 1Cycle Learning Rate Schedule: Increases then decreases
+ step_ratio = self.current_step / self.total_steps
+ if step_ratio < 0.6:
+ # Increase learning rate for first half of training
+ lr = self.start_lr + (self.max_lr - self.start_lr) * (step_ratio * 2)
+ else:
+ # Decrease learning rate for the second half of training
+ lr = self.max_lr - (self.max_lr - self.last_epoch_lr) * ((step_ratio - 0.5) * 2)
+ return lr
+
+class SGDRScheduler(tf.keras.callbacks.Callback):
+ '''Cosine annealing learning rate scheduler with periodic restarts.'''
+ def __init__(self, min_lr, max_lr, steps_per_epoch, lr_decay=1, cycle_length=10, mult_factor=2):
+ super(SGDRScheduler, self).__init__()
+ self.min_lr = min_lr
+ self.max_lr = max_lr
+ self.lr_decay = lr_decay
+ self.steps_per_epoch = steps_per_epoch
+ self.batch_since_restart = 0
+ self.next_restart = cycle_length
+ self.cycle_length = cycle_length
+ self.mult_factor = mult_factor
+
+ def on_batch_begin(self, batch, logs=None):
+ '''Adjust the learning rate at the start of each batch.'''
+ fraction_to_restart = self.batch_since_restart / (self.steps_per_epoch * self.cycle_length)
+ lr = self.min_lr + 0.5 * (self.max_lr - self.min_lr) * (1 + np.cos(fraction_to_restart * np.pi))
+ tf.keras.backend.set_value(self.model.optimizer.lr, lr)
+ self.batch_since_restart += 1
+
+ def on_epoch_end(self, epoch, logs=None):
+ '''Check for end of current cycle, apply restarts when necessary.'''
+ if epoch + 1 == self.next_restart:
+ self.batch_since_restart = 0
+ self.next_restart += self.cycle_length
+ self.cycle_length = int(self.cycle_length * self.mult_factor)
+ self.max_lr *= self.lr_decay
+ self.min_lr *= self.lr_decay
+```
+
+## Training utils
+
+### Plotting metrics
+
+```{python}
+#| trusted: true
+def plot_training_history(history, model_name, metrics=['f1_score', 'precision', 'recall', 'loss', 'accuracy', 'partial_auc', 'auc']):
+ plt.figure(figsize=(16, 16))
+ for i, metric in enumerate(metrics):
+ plt.subplot(4, 2, i + 1)
+ plt.title(metric.capitalize())
+ if metric in history:
+ plt.plot(history[metric], label=metric)
+ if f'val_{metric}' in history:
+ plt.plot(history[f'val_{metric}'], label=f'val_{metric}')
+ plt.legend(loc='upper right')
+ plt.tight_layout()
+ plt.title(model_name, loc='center')
+ plt.savefig(f'{models_dir}/{model_name}_training_{run_id}')
+ plt.show()
+
+def normalize_history_keys(history):
+ """
+ Normalize the keys in the history dictionary using clean_metric_name.
+ """
+ return {clean_metric_name(key): value for key, value in history.items()}
+
+def print_infos(training_mode, fold_no, messages):
+ if training_mode == 'train-validation-split':
+ for message in messages:
+ print(message.capitalize())
+ else:
+ for message in messages:
+ print(f"Fold {fold_no + 1}", message)
+
+def merge_histories(history_initial, history_fine_tune):
+ # Merge histories
+ history = defaultdict(list)
+ if history_initial is not None:
+ # Normalize the keys in history_initial
+ history_initial_cleaned = normalize_history_keys(history_initial.history)
+ for key, value in history_initial_cleaned.items():
+ history[key] = value
+ else:
+ # Normalize the keys in history_fine_tune
+ history_fine_tune_cleaned = normalize_history_keys(history_fine_tune.history)
+ for key, value in history_fine_tune_cleaned.items():
+ history[key].extend(value)
+ history = dict(history)
+ return history
+```
+
+### Data loading
+
+```{python}
+#| trusted: true
+def load_train_val_dataset(file_pattern, shuffle_dataset_at_each_call=True, cache_dataset=True, cache_in_memory=False,
+ cache_dir=cache_dir, prefix="train_val", run_id=run_id, check_modification_time=False,
+ is_training=True, force_cache=True, use_tabular_data=use_tabular_data, batch_size=batch_size):
+ print("Reloading and shuffling dataset...")
+ try:
+ del dataset
+ gc.collect()
+ except:
+ pass
+ gc.collect()
+
+ print('batch_size', batch_size)
+ print('file_pattern', file_pattern)
+ print('is_training', is_training)
+
+ print('cache_in_memory', cache_in_memory)
+
+ # Load dataset once and cache
+ dataset, dataset_steps = load_tfrecord_dataset(file_pattern=file_pattern, is_training=is_training,
+ batch_size=batch_size, cache_in_memory=cache_in_memory)
+
+ #dataset = cast_labels(dataset, use_tabular_data)
+
+
+ #dataset = persist_dataset(dataset, file_pattern=train_file_pattern, dataset_steps=dataset_steps, cache_in_memory=cache_in_memory,
+ #cache_dir=cache_dir, prefix=prefix, run_id=run_id, check_modification_time=check_modification_time, force_cache=force_cache)
+
+ # Extract labels for stratified splitting
+ use_cross_validation = False
+ if use_cross_validation :
+ labels = []
+ for *_, lbl in dataset:
+ labels.append(lbl.numpy())
+
+ labels = np.array(labels)
+
+ seed = random.randint(0, 1e9) if shuffle_dataset_at_each_call else 42
+
+ if use_cross_validation:
+ # Generate stratified split indices for cross-validation
+ split_indices = stratified_split_indices(labels, n_splits=5, shuffle=True, random_state=seed)
+ else:
+ # Create a stratified 80/20 train-test split
+ sss = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=seed)
+ train_index, val_index = next(sss.split(np.zeros(len(labels)), labels))
+ split_indices = [(train_index, val_index)] # Single split for train-test
+ return dataset, split_indices, dataset_steps
+ else:
+ return dataset, dataset_steps
+```
+
+### Training pipeline common
+
+```{python}
+#| trusted: true
+def training_pipeline(model_name, model_names=None, model=None, train_file_pattern=None, **kwargs):
+ run_id = kwargs.get('run_id', None)
+ strategy = kwargs.get('strategy', None)
+ batch_size = kwargs.get('batch_size', None)
+ val_batch_size = kwargs.get('val_batch_size', None)
+ cache_in_memory = kwargs.get('cache_in_memory', None)
+ freeze_base_model = kwargs.get('freeze_base_model', None)
+ loss = kwargs.get('loss', None)
+ shuffle_dataset_at_each_call = kwargs.get('shuffle_dataset_at_each_call', None)
+ do_fine_tuning = kwargs.get('do_fine_tuning', None)
+ initial_epochs = kwargs.get('initial_epochs', None)
+ fine_tune_epochs = kwargs.get('fine_tune_epochs', None)
+ oversample_minority_class = kwargs.get('oversample_minority_class', None)
+ augment_train_data = kwargs.get('augment_train_data', None)
+ use_cross_validation = kwargs.get('use_cross_validation', None)
+ use_tabular_data = kwargs.get('use_tabular_data', None)
+ execution_env = kwargs.get('execution_env', None)
+ total_epochs = initial_epochs + fine_tune_epochs if freeze_base_model else fine_tune_epochs
+
+ # cleaning the session
+ deletes_old_datasets()
+
+ partial_aucs = []
+ training_mode = 'train-validation-split'
+ print("Kwargs from training_pipeline ", kwargs)
+
+ # Load train dataset once and cache
+ train_dataset, training_steps = load_tfrecord_dataset(file_pattern=train_file_pattern,
+ batch_size=batch_size,
+ is_training=True,
+ cache_in_memory=cache_in_memory)
+ # Validation dataset
+ val_dataset, validation_steps = load_tfrecord_dataset(f'{tf_records_eval_dir}/*.tfrecord',
+ batch_size=val_batch_size,
+ is_training=False,
+ cache_in_memory=cache_in_memory)
+
+ print_infos(training_mode, None, [f"start training model {model_name}...",
+ f"training size: {training_steps * batch_size * strategy.num_replicas_in_sync}",
+ f"validation size: {validation_steps * val_batch_size * strategy.num_replicas_in_sync}"])
+
+ # Oversample the minority class within the fold
+ if oversample_minority_class:
+ (train_dataset, training_steps, training_total_samples, sample_per_class_input_ds,
+ sample_per_class_oversampled_ds) = oversample_minority_class_random(train_dataset, batch_size, strategy)
+ print('Total samples in train dataset after oversampling', training_total_samples)
+
+ # Calculate class weights
+ class_weights_ovs = calculate_class_weights(sample_per_class_oversampled_ds)
+ class_weights_input_ds = calculate_class_weights(sample_per_class_input_ds)
+ print_infos(training_mode, None, [f"samples per class: {sample_per_class_input_ds}", f"class weights: {class_weights_input_ds}",
+ f"training steps: {training_steps}, evaluation steps: {validation_steps}"])
+
+ # Training data processing
+ train_dataset = optimize_dataset(train_dataset, file_pattern=None, steps=training_steps, cache_in_memory=cache_in_memory,
+ cache_dir=cache_dir, prefix='train', run_id=run_id, check_modification_time=False, force_cache=True)
+
+ if augment_train_data:
+ train_dataset = apply_augmentations(train_dataset)
+ train_dataset = format_input_dataset(train_dataset, use_tabular_data)
+ train_dataset = train_dataset.prefetch(tf.data.AUTOTUNE)
+
+ # Validation data processing
+ val_dataset = format_input_dataset(val_dataset, use_tabular_data)
+ val_dataset = optimize_dataset(val_dataset, file_pattern=None, steps=validation_steps, cache_in_memory=cache_in_memory,
+ cache_dir=cache_dir, prefix='val', run_id=run_id, check_modification_time=False,
+ force_cache=True)
+
+ # Callbacks
+ memory_callback = MemoryUsageCallback()
+ early_stopping_callback = EarlyStopping(monitor='val_loss', patience=20, restore_best_weights=True)
+ partial_auc_callback = PartialAUCCallback(validation_data=val_dataset, validation_steps=validation_steps,
+ monitor='val_partial_auc')
+ one_cycle_scheduler_callback = OneCycleScheduler(max_lr=1e-5, steps_per_epoch=training_steps, epochs=total_epochs,
+ last_epoch_lr=1e-7, start_lr=1e-7)
+ model_checkpoint_callback = ModelCheckpoint(filepath=f'{models_dir}/model_{model_name}_{run_id}_checkpoint.keras',
+ monitor='val_partial_auc', save_best_only=True)
+ callbacks=[one_cycle_scheduler_callback, partial_auc_callback, early_stopping_callback, model_checkpoint_callback, memory_callback]
+
+
+ with strategy.scope():
+ precision = tf.keras.metrics.Precision()
+ recall = tf.keras.metrics.Recall()
+ f1_score = F1Score()
+ auc = tf.keras.metrics.AUC(name="auc")
+
+ optimizer = tf.keras.optimizers.Nadam() if execution_env == 'colab' else tf.keras.optimizers.Adamax()
+ loss = set_binary_crossentropy_weighted_loss(positive_weights=np.array([class_weights_input_ds['minority_class']]),
+ negative_weights=np.array([class_weights_input_ds['majority_class']]),
+ epsilon=1e-7)
+ history_initial = None
+ class_weights=dict(zip([0, 1], class_weights_input_ds.values()))
+ # Initial training
+ if freeze_base_model:
+ with strategy.scope():
+ model.compile(optimizer=optimizer, loss=loss, metrics=[precision, recall, f1_score])
+ print("Started fitting the model with base model freezed...")
+ history_initial = model.fit(train_dataset,epochs=initial_epochs,steps_per_epoch=training_steps, validation_data=val_dataset,
+ validation_steps=validation_steps, callbacks=callbacks, class_weight=class_weights, verbose=1)
+
+ # Fine tuning
+ if do_fine_tuning:
+ initial_epoch = history_initial.epoch[-1] if freeze_base_model else 0
+
+ with strategy.scope():
+ model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy', precision, recall, f1_score, auc])
+
+ print("Started fine fitting the model with layers unfrozen...")
+ history_fine_tune = model.fit(train_dataset, epochs=total_epochs, initial_epoch=initial_epoch, steps_per_epoch=training_steps,
+ validation_data=val_dataset, validation_steps=validation_steps, class_weight=class_weights,
+ callbacks=callbacks, verbose=1)
+ # Merge histories
+ history = merge_histories(history_initial, history_fine_tune)
+ else:
+ history = history_initial.history
+
+ print("Computing the final validation score after training ended")
+ # Evaluate the model on the validation set
+ y_val, y_pred = get_labels_and_predictions(model, val_dataset, validation_steps)
+
+ # Calculate the Partial AUC
+ partial_auc = score(y_val, y_pred)
+ partial_aucs.append(partial_auc)
+
+ print("Plotting the training metrics...")
+ # Plot the training history
+ plot_training_history(history, model_name)
+
+ model.save_weights(os.path.join(models_dir, f'{model_name}_{run_id}_weights.h5'))
+
+ print(f"Train-validation-split partial AUC: {partial_auc}")
+
+ print(f'Finished training model {model_name}')
+ return model
+```
+
+### Single model training pipeline
+
+```{python}
+#| trusted: true
+def train_single_model(model_name, **kwargs):
+ """Train a unique model from scratch."""
+
+ print(f"Training single model: {model_name}\n")
+
+ model = ModelFactory.create_model('single', pretrained=False, model_names=[model_name], **kwargs)
+ model = training_pipeline(model_name, model=model, **kwargs)
+
+ return model
+```
+
+## Training
+
+### Utils
+
+```{python}
+#| trusted: true
+class TrainConfig:
+ def __init__(self, strategy, run_id, batch_size, val_batch_size, cache_in_memory,
+ freeze_base_model, shuffle_dataset_at_each_call, train_file_pattern,
+ do_fine_tuning, oversample_minority_class, initial_epochs, fine_tune_epochs,
+ img_shape, augment_train_data, use_cross_validation, dropout_rate, l2_lambda,
+ num_tabular_features, use_tabular_data, kernel_initializer, activation,
+ execution_env):
+ self.strategy = strategy
+ self.run_id = run_id
+ self.batch_size = batch_size
+ self.val_batch_size = val_batch_size
+ self.cache_in_memory = cache_in_memory
+ self.freeze_base_model = freeze_base_model
+ self.shuffle_dataset_at_each_call = shuffle_dataset_at_each_call
+ self.train_file_pattern = train_file_pattern
+ self.do_fine_tuning = do_fine_tuning
+ self.oversample_minority_class = oversample_minority_class
+ self.initial_epochs = initial_epochs
+ self.fine_tune_epochs = fine_tune_epochs
+ self.img_shape = img_shape
+ self.augment_train_data = augment_train_data
+ self.use_cross_validation = use_cross_validation
+ self.dropout_rate = dropout_rate
+ self.l2_lambda = l2_lambda
+ self.num_tabular_features = num_tabular_features
+ self.use_tabular_data = use_tabular_data
+ self.kernel_initializer = kernel_initializer
+ self.activation = activation
+ self.execution_env = execution_env
+
+
+def run_pipeline(pipeline_func, config: TrainConfig, **specific_params):
+ """General pipeline function for training models with a TrainConfig object."""
+
+ # Clean the session and retrieve initial memory
+ initial_memory_usage = reset_session_and_get_memory()
+ log_memory_usage("Initial", initial_memory_usage)
+
+ # Ensure strategy is initialized
+ if config.strategy is None:
+ raise ValueError("Strategy must be set in the configuration.")
+
+ # Train the model using the passed function with the TrainConfig object
+ #with config.strategy.scope():
+ model = pipeline_func(strategy=config.strategy, run_id=config.run_id, batch_size=config.batch_size,
+ cache_in_memory=config.cache_in_memory, freeze_base_model=config.freeze_base_model,
+ shuffle_dataset_at_each_call=config.shuffle_dataset_at_each_call,
+ do_fine_tuning=config.do_fine_tuning, train_file_pattern=config.train_file_pattern,
+ oversample_minority_class = config.oversample_minority_class,
+ initial_epochs = config.initial_epochs, val_batch_size = config.val_batch_size,
+ fine_tune_epochs = config.fine_tune_epochs,
+ img_shape = config.img_shape,
+ augment_train_data = config.augment_train_data,
+ use_cross_validation = config.use_cross_validation,
+ use_tabular_data = config.use_tabular_data,
+ activation = config.activation,
+ dropout_rate = config.dropout_rate,
+ l2_lambda = config.l2_lambda,
+ num_tabular_features = config.num_tabular_features,
+ kernel_initializer = config.kernel_initializer,
+ execution_env = config.execution_env,
+ **specific_params)
+ # Retrieve final memory usage
+ final_memory_usage = get_memory_usage()
+ log_memory_usage("Final", final_memory_usage)
+
+ return model
+
+def run_model_evaluation(model):
+ evaluate_model(model, val_dataset_small_formatted, validation_steps_small, dataset_name='small validation dataset')
+ evaluate_model(model, val_dataset_big_formatted, validation_steps_big, dataset_name='big validation dataset')
+ evaluate_model(model, train_val_dataset_formatted, train_val_dataset_steps, dataset_name='train_val dataset')
+```
+
+### Initialization
+
+```{python}
+#| trusted: true
+# Instantiate TrainConfig with common parameters
+train_config = TrainConfig(
+ strategy=strategy,
+ run_id=run_id,
+ batch_size=batch_size,
+ val_batch_size=val_batch_size,
+ cache_in_memory=cache_in_memory,
+ freeze_base_model=freeze_base_model,
+ shuffle_dataset_at_each_call=True,
+ train_file_pattern=train_file_pattern,
+ do_fine_tuning = do_fine_tuning,
+ oversample_minority_class = oversample_minority_class,
+ initial_epochs = initial_epochs,
+ fine_tune_epochs = fine_tune_epochs,
+ img_shape = img_shape,
+ augment_train_data = augment_train_data,
+ use_cross_validation = use_cross_validation,
+ dropout_rate = dropout_rate,
+ l2_lambda = l2_lambda,
+ num_tabular_features = num_tabular_features,
+ use_tabular_data = use_tabular_data,
+ kernel_initializer = kernel_initializer,
+ activation = activation,
+ execution_env = execution_env
+)
+```
+
+### Data loading
+
+```{python}
+#| trusted: true
+# Load dataset once and cache
+if run_evaluations:
+ train_val_dataset, train_val_dataset_steps = load_tfrecord_dataset(
+ train_file_pattern,
+ batch_size=batch_size,
+ is_training=False,
+ cache_in_memory=False)
+ train_val_dataset_formatted = format_input_dataset(train_val_dataset)
+ # Verify cache
+ #!ls -al {cache_dir}
+ # Load big validation dataset
+ val_dataset_small, validation_steps_small = load_tfrecord_dataset(
+ f'{tf_records_eval_dir}/*.tfrecord',
+ batch_size=eval_batch_size,
+ is_training=False,
+ cache_in_memory=False)
+ val_dataset_small_formatted = format_input_dataset(val_dataset_small)
+
+ # Load big validation dataset
+ val_dataset_big, validation_steps_big = load_tfrecord_dataset(
+ f'{tf_records_val_dir}/*.tfrecord',
+ batch_size=eval_batch_size * 2,
+ is_training=False,
+ cache_in_memory=False)
+ val_dataset_big_formatted = format_input_dataset(val_dataset_big)
+```
+
+### Run
+
+```{python}
+#| scrolled: true
+#| trusted: true
+if model_type == 'single_model' or train_all_models:
+ #strategy = tf.distribute.OneDeviceStrategy(device="/gpu:0")
+ #strategy = tf.distribute.get_strategy()
+ #with strategy.scope():
+ single_model = run_pipeline(
+ pipeline_func=train_single_model, # Function to train the model
+ config=train_config, # Common configuration object
+ #model_name='res_net_50'
+ model_name=model_name
+ )
+ if inference_model_name == 'single_model':
+ inference_model = single_model
+```
+
+### Evaluate on unseen and train_val datasets
+
+```{python}
+#| trusted: true
+if model_type == 'single_model' or train_all_models:
+ print('Evaluating single model:', model_name)
+ run_model_evaluation(single_model)
+```
+
+# Ensembling models
+
+## Trained ensemble model
+
+#### Trained ensemble training pipeline
+
+```{python}
+#| trusted: true
+def train_compact_ensemble_model(model_names=['efficientnetb0', 'resnet50', 'vgg16'], **kwargs):
+ """Train a unique ensmeble model from scratch."""
+
+ print(f"Training compact ensemble model")
+
+ compact_ensemble_model = ModelFactory.create_model('ensemble', model_names, pretrained=False, **kwargs)
+
+ compact_ensemble_model = training_pipeline(compact_ensemble_model.model_name, model=compact_ensemble_model, **kwargs)
+ return compact_ensemble_model
+```
+
+#### Run
+
+```{python}
+#| trusted: true
+if model_type == 'compact_ensemble_model' and train_all_models:
+ compact_ensemble_model = run_pipeline(
+ pipeline_func=train_compact_ensemble_model, # Function to train ensemble model
+ config=train_config, # Common configuration object
+ #model_names=['densenet201','resnet50', 'resnet152v2','vgg19'],
+ #weights=[0.41, 0.35, 0.14, 0.1],
+ #weights=[0.25, 0.25, 0.25, 0.25],
+ model_names=['res_net_152_v2', 'dense_net_201'],
+ weights=[0.55, 0.45]
+ )
+```
+
+#### Evaluation
+
+```{python}
+#| trusted: true
+if model_type == 'compact_ensemble_model' or train_all_models:
+ print('Evaluating compact ensemble model')
+ run_model_evaluation(compact_ensemble_model)
+
+ if inference_model_name == 'compact_ensemble_model':
+ inference_model = compact_ensemble_model
+```
+
+## Pretrained ensemble model
+
+### Pretrained ensemble pipeline
+
+```{python}
+#| trusted: true
+def train_pretrained_ensemble(model_names, train_individuals=True, **kwargs):
+ """Train a unique model from scratch."""
+ print('Training pretrained ensemble model')
+
+ run_id = kwargs.get('run_id', None)
+
+ # Train individual models if needed
+ train_individual_models(model_names=model_names, train_individuals=train_individuals, **kwargs)
+
+ pretrained_ensemble = ModelFactory.create_model(model_type='ensemble', model_names=model_names, pretrained=True, **kwargs)
+
+ # Saving the weights as it is not trained via the commom pipeline
+ pretrained_ensemble.save_weights(os.path.join(models_dir, f'{pretrained_ensemble.model_name}_{run_id}_weights.h5'))
+
+ print('Finished creating the pretrained_ensemble', pretrained_ensemble.model_name)
+
+ return pretrained_ensemble
+
+def train_individual_models(model_names, train_individuals=True, **kwargs):
+ """Train individual models if their weights do not already exist."""
+ for model_name in model_names:
+ print('Training individual model', model_name)
+ weights_path = os.path.join('models_dir', f'{model_name}_{kwargs.get("run_id", "default")}_weights.h5')
+ if (not os.path.exists(weights_path)) and train_individuals:
+ print(f"Training {model_name} since weights are not found.")
+ train_single_model(model_name, **kwargs)
+ else:
+ print(f"Weights for {model_name} already exist at {weights_path}, skipping training.")
+```
+
+### Training
+
+### Run
+
+```{python}
+#| trusted: true
+if model_type == 'pretrained_ensemble_model' or train_all_models:
+ pretrained_ensemble_model = run_pipeline(
+ pipeline_func=train_pretrained_ensemble, # Function to train ensemble model
+ config=train_config, # Common configuration object
+ model_names=['dense_net_201','res_net_50', 'res_net_152_v2','vgg_19'],
+ weights=[0.41, 0.35, 0.14, 0.1],
+ train_individuals=train_individuals,
+ mode='weighted'
+ )
+ if inference_model_name == 'pretrained_ensemble_model':
+ inference_model = pretrained_ensemble_model
+```
+
+### Evaluation
+
+```{python}
+#| trusted: true
+if (model_type == 'pretrained_ensemble_model' or train_all_models) and run_evaluations:
+ print('Evaluating pretrained ensemble model')
+ run_model_evaluation(pretrained_ensemble_model)
+```
+
+# Inference
+
+### Data preparation
+
+```{python}
+#| trusted: true
+# Load and preprocess images using fused operations for I/O and preprocessing
+@tf.function
+def preprocess_image(image_bytes, img_height, img_width):
+ """Preprocess image loaded from HDF5."""
+ image = tf.io.decode_jpeg(image_bytes, channels=3)
+ image = tf.image.resize(image, [img_height, img_width])
+ image = image / 255.0 # Normalize to [0, 1]
+ return image
+
+def load_image_from_hdf5(key, h5_file):
+ """Function to load image bytes from HDF5 file."""
+ try:
+ image_bytes = h5_file[key][()]
+ if image_bytes is None or len(image_bytes) == 0:
+ raise ValueError(f"Empty image data for key: {key}")
+ return image_bytes
+ except Exception as e:
+ raise ValueError(f"Failed to load image for key {key}. Error: {str(e)}")
+
+def hdf5_image_generator(h5_file, keys):
+ """Python generator to load images from HDF5 file."""
+ for key in keys:
+ try:
+ image_bytes = load_image_from_hdf5(key, h5_file)
+ yield image_bytes
+ except Exception as e:
+ print(f"Error loading image for key {key}: {e}")
+ continue
+
+def prepare_inference_dataset(image_paths, data_source='hdf5', use_tabular_data = use_tabular_data, tabular_data_paths=None,
+ batch_size=batch_size, cache_data=False, preprocessor_path=preprocessor_path, target_img_shape=None):
+ """
+ Prepare the dataset for inference, either loading images from a file path or from an .hdf5 file.
+
+ Args:
+ image_source: Path to the images or .hdf5 file containing the images.
+ tabular_data_paths: Path to the tabular data, if any.
+ preprocessor: Preprocessing function for tabular data.
+ batch_size: Batch size for the dataset.
+ cache_data: Whether to cache the dataset.
+ data_source: 'path' if loading from image paths, 'hdf5' if loading from an .hdf5 file.
+
+ Returns:
+ A tf.data.Dataset ready for inference.
+ """
+
+ # Loading images from file paths
+ if data_source == 'path':
+ # Fused operations for loading and preprocessing images from file paths
+ image_paths = glob(image_paths)
+ image_dataset = tf.data.Dataset.from_tensor_slices(image_paths)
+
+ # Load and preprocess the images
+ image_dataset = image_dataset.map(preprocess_image, num_parallel_calls=tf.data.AUTOTUNE)
+
+ keys = [os.path.basename(path).split('.')[0] for path in image_paths]
+
+ # Loading images from an .hdf5 file
+ elif data_source == 'hdf5':
+ # Open the .hdf5 file and load the image dataset
+ # Open the HDF5 file
+ h5_file = h5py.File(image_paths, 'r')
+ keys = list(h5_file.keys())
+
+ # Create a generator for loading images from the HDF5 file
+ def image_gen():
+ return hdf5_image_generator(h5_file, keys)
+
+ # Create a TensorFlow Dataset from the generator
+ image_dataset = tf.data.Dataset.from_generator(
+ image_gen,
+ output_signature=tf.TensorSpec(shape=(), dtype=tf.string)
+ )
+
+ # Map preprocessing function over the dataset
+ image_dataset = image_dataset.map(
+ lambda image_bytes: preprocess_image(image_bytes, img_height, img_width),
+ num_parallel_calls=tf.data.AUTOTUNE
+ )
+
+ # Raise an error if the data_source is not recognized
+ else:
+ raise ValueError("data_source must be either 'path' or 'hdf5'")
+
+ # If tabular data is provided, load and zip it with the image dataset
+ if use_tabular_data:
+ # Extract filenames without extensions
+ inference_images = keys
+ preprocessor = load_preprocessor(preprocessor_path)
+ tabular_data = format_and_encode_tabular_data([tabular_data_paths], inference_images, preprocessor)
+
+ tabular_data = tf.data.Dataset.from_tensor_slices(tabular_data)
+
+ # Combine image and tabular data efficiently
+ combined_dataset = tf.data.Dataset.zip((image_dataset, tabular_data))
+ else:
+ combined_dataset = image_dataset
+
+ # Batch the dataset and prefetch to load data asynchronously
+ dataset_for_inference = combined_dataset.batch(batch_size * strategy.num_replicas_in_sync,
+ drop_remainder=False).prefetch(tf.data.AUTOTUNE)
+ if cache_data:
+ dataset_for_inference = dataset_for_inference.cache()
+
+ return dataset_for_inference, keys
+```
+
+### Pipeline
+
+```{python}
+#| trusted: true
+# Run inference using fused operations, mixed precision, and optimized parallel data loading
+def inference_pipeline(model, image_paths, use_tabular_data, tabular_data_paths=None, batch_size=batch_size,
+ cache_data=False, use_mixed_precision=False, preprocessor_path=preprocessor_path,
+ submission_file_path=None, target_img_shape=None):
+ use_mixed_precision=False
+ if use_mixed_precision:
+ enable_mixed_precision()
+
+ # Prepare the dataset
+ dataset_for_inference, images_ids= prepare_inference_dataset(image_paths=image_paths, use_tabular_data=use_tabular_data,
+ cache_data=cache_data, tabular_data_paths=tabular_data_paths,
+ batch_size=batch_size, preprocessor_path=preprocessor_path,
+ target_img_shape=target_img_shape)
+ all_predictions = []
+ # Run inference and get predictions
+ for batch in dataset_for_inference:
+ if use_tabular_data:
+ image_batch, tabular_batch = batch
+ predictions = model.predict_on_batch([image_batch, tabular_batch]).ravel()
+ else:
+ image_batch = batch
+ predictions = model.predict_on_batch(image_batch).ravel()
+ all_predictions.append(predictions)
+
+ # Save predictions to CSV
+ predictions_df = pd.DataFrame(zip(images_ids, predictions), columns=['isic_id', 'target'])
+ predictions_df.to_csv(submission_file_path, index=False)
+
+ # Process predictions
+ return predictions_df, dataset_for_inference
+```
+
+## Initialization
+
+```{python}
+#| trusted: true
+preprocessor_path = preprocessor_path
+inference_model_path = inference_model_path
+project_dir = f'{input_dir}/isic-2024-challenge'
+inference_images_path = f'{project_dir}/test-image.hdf5'
+inference_tabular_data_path = f'{project_dir}/test-metadata.csv'
+use_tabular_data = use_tabular_data
+cache_data=True
+submission_file_path = f'{output_dir}/submission.csv'
+target_img_shape=img_shape
+run_inference_use_all_models = False
+```
+
+## Run
+
+```{python}
+#| trusted: true
+# Run inference with optimizations and mixed precision enabled
+if run_inference:
+ inference_models = [single_model, compact_ensemble_model, pretrained_ensemble_model] if run_inference_use_all_models else [inference_model]
+ predictions_dfs = []
+ for inf_model in inference_models:
+ print('Running inference for model...', inf_model.model_name)
+ predictions_df, dataset_for_inference = inference_pipeline(model=inf_model, image_paths=inference_images_path, use_tabular_data=use_tabular_data,
+ tabular_data_paths=inference_tabular_data_path, batch_size=batch_size, cache_data=cache_data,
+ use_mixed_precision=False, preprocessor_path=preprocessor_path, submission_file_path=submission_file_path,
+ target_img_shape=target_img_shape)
+ predictions_dfs.append(predictions_df)
+ print('Finished running inference for model', inf_model.model_name)
+```
+
+```{python}
+#| trusted: true
+predictions_dfs[-1]
+```
+
+# Annexe
+
+```{python}
+#| trusted: true
+# Limit intra-op parallelism (operations within a single layer)
+#export OMP_NUM_THREADS=100
+# Limit inter-op parallelism (parallel operations across layers)
+#export TF_NUM_INTEROP_THREADS=100
+# Limit intra-op parallelism (for operations like matrix multiplications)
+#tf.config.threading.set_intra_op_parallelism_threads(100)
+# Limit inter-op parallelism (for parallel operations across layers)
+#tf.config.threading.set_inter_op_parallelism_threads(100)
+#import os
+#os.environ["CUDA_VISIBLE_DEVICES"] = "-1" # Disables GPU by setting this environment variable
+#tf.config.list_physical_devices('GPU')
+```
+
+```{python}
+#| trusted: true
+visualization = False
+if visualization:
+ eval_dataset_mapped = eval_dataset.map(lambda x, y, z: ((x, y), z))
+ augmented_dataset = apply_augmentations(eval_dataset_mapped)
+ imgs = augmented_dataset.as_numpy_iterator()
+ imgs_t = imgs.next()[0][0]
+ plt.figure(figsize=(50, 50))
+ #imgs_t = imgs.next()[0]
+ for i in range(0, 63):
+ plt.subplot(12, 12, i+1)
+ plt.xticks([])
+ plt.yticks([])
+ plt.imshow(imgs_t[i])
+```
+
diff --git a/public/notebooks/isic-2024_kaggle_files/libs/bootstrap/bootstrap-10daa034703793678e481cc8cee6d76f.min.css b/public/notebooks/isic-2024_kaggle_files/libs/bootstrap/bootstrap-10daa034703793678e481cc8cee6d76f.min.css
new file mode 100644
index 0000000..1226b46
--- /dev/null
+++ b/public/notebooks/isic-2024_kaggle_files/libs/bootstrap/bootstrap-10daa034703793678e481cc8cee6d76f.min.css
@@ -0,0 +1,12 @@
+/*!
+ * Bootstrap v5.3.1 (https://getbootstrap.com/)
+ * Copyright 2011-2023 The Bootstrap Authors
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */@import"https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;700&display=swap";:root,[data-bs-theme=light]{--bs-blue: #2780e3;--bs-indigo: #6610f2;--bs-purple: #613d7c;--bs-pink: #e83e8c;--bs-red: #ff0039;--bs-orange: #f0ad4e;--bs-yellow: #ff7518;--bs-green: #3fb618;--bs-teal: #20c997;--bs-cyan: #9954bb;--bs-black: #000;--bs-white: #fff;--bs-gray: #868e96;--bs-gray-dark: #373a3c;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #868e96;--bs-gray-700: #495057;--bs-gray-800: #373a3c;--bs-gray-900: #212529;--bs-default: #373a3c;--bs-primary: #2780e3;--bs-secondary: #373a3c;--bs-success: #3fb618;--bs-info: #9954bb;--bs-warning: #ff7518;--bs-danger: #ff0039;--bs-light: #f8f9fa;--bs-dark: #373a3c;--bs-default-rgb: 55, 58, 60;--bs-primary-rgb: 39, 128, 227;--bs-secondary-rgb: 55, 58, 60;--bs-success-rgb: 63, 182, 24;--bs-info-rgb: 153, 84, 187;--bs-warning-rgb: 255, 117, 24;--bs-danger-rgb: 255, 0, 57;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 55, 58, 60;--bs-primary-text-emphasis: #10335b;--bs-secondary-text-emphasis: #161718;--bs-success-text-emphasis: #19490a;--bs-info-text-emphasis: #3d224b;--bs-warning-text-emphasis: #662f0a;--bs-danger-text-emphasis: #660017;--bs-light-text-emphasis: #495057;--bs-dark-text-emphasis: #495057;--bs-primary-bg-subtle: #d4e6f9;--bs-secondary-bg-subtle: #d7d8d8;--bs-success-bg-subtle: #d9f0d1;--bs-info-bg-subtle: #ebddf1;--bs-warning-bg-subtle: #ffe3d1;--bs-danger-bg-subtle: #ffccd7;--bs-light-bg-subtle: #fcfcfd;--bs-dark-bg-subtle: #ced4da;--bs-primary-border-subtle: #a9ccf4;--bs-secondary-border-subtle: #afb0b1;--bs-success-border-subtle: #b2e2a3;--bs-info-border-subtle: #d6bbe4;--bs-warning-border-subtle: #ffc8a3;--bs-danger-border-subtle: #ff99b0;--bs-light-border-subtle: #e9ecef;--bs-dark-border-subtle: #adb5bd;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-font-sans-serif: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-root-font-size: 17px;--bs-body-font-family: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #373a3c;--bs-body-color-rgb: 55, 58, 60;--bs-body-bg: #fff;--bs-body-bg-rgb: 255, 255, 255;--bs-emphasis-color: #000;--bs-emphasis-color-rgb: 0, 0, 0;--bs-secondary-color: rgba(55, 58, 60, 0.75);--bs-secondary-color-rgb: 55, 58, 60;--bs-secondary-bg: #e9ecef;--bs-secondary-bg-rgb: 233, 236, 239;--bs-tertiary-color: rgba(55, 58, 60, 0.5);--bs-tertiary-color-rgb: 55, 58, 60;--bs-tertiary-bg: #f8f9fa;--bs-tertiary-bg-rgb: 248, 249, 250;--bs-heading-color: inherit;--bs-link-color: #2761e3;--bs-link-color-rgb: 39, 97, 227;--bs-link-decoration: underline;--bs-link-hover-color: #1f4eb6;--bs-link-hover-color-rgb: 31, 78, 182;--bs-code-color: #7d12ba;--bs-highlight-bg: #ffe3d1;--bs-border-width: 1px;--bs-border-style: solid;--bs-border-color: #e1e1e2;--bs-border-color-translucent: rgba(0, 0, 0, 0.175);--bs-border-radius: 0.375rem;--bs-border-radius-sm: 0.25rem;--bs-border-radius-lg: 0.5rem;--bs-border-radius-xl: 1rem;--bs-border-radius-xxl: 2rem;--bs-border-radius-2xl: var(--bs-border-radius-xxl);--bs-border-radius-pill: 50rem;--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width: 0.25rem;--bs-focus-ring-opacity: 0.25;--bs-focus-ring-color: rgba(39, 128, 227, 0.25);--bs-form-valid-color: #3fb618;--bs-form-valid-border-color: #3fb618;--bs-form-invalid-color: #ff0039;--bs-form-invalid-border-color: #ff0039}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color: #dee2e6;--bs-body-color-rgb: 222, 226, 230;--bs-body-bg: #212529;--bs-body-bg-rgb: 33, 37, 41;--bs-emphasis-color: #fff;--bs-emphasis-color-rgb: 255, 255, 255;--bs-secondary-color: rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb: 222, 226, 230;--bs-secondary-bg: #373a3c;--bs-secondary-bg-rgb: 55, 58, 60;--bs-tertiary-color: rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb: 222, 226, 230;--bs-tertiary-bg: #2c3033;--bs-tertiary-bg-rgb: 44, 48, 51;--bs-primary-text-emphasis: #7db3ee;--bs-secondary-text-emphasis: #87898a;--bs-success-text-emphasis: #8cd374;--bs-info-text-emphasis: #c298d6;--bs-warning-text-emphasis: #ffac74;--bs-danger-text-emphasis: #ff6688;--bs-light-text-emphasis: #f8f9fa;--bs-dark-text-emphasis: #dee2e6;--bs-primary-bg-subtle: #081a2d;--bs-secondary-bg-subtle: #0b0c0c;--bs-success-bg-subtle: #0d2405;--bs-info-bg-subtle: #1f1125;--bs-warning-bg-subtle: #331705;--bs-danger-bg-subtle: #33000b;--bs-light-bg-subtle: #373a3c;--bs-dark-bg-subtle: #1c1d1e;--bs-primary-border-subtle: #174d88;--bs-secondary-border-subtle: #212324;--bs-success-border-subtle: #266d0e;--bs-info-border-subtle: #5c3270;--bs-warning-border-subtle: #99460e;--bs-danger-border-subtle: #990022;--bs-light-border-subtle: #495057;--bs-dark-border-subtle: #373a3c;--bs-heading-color: inherit;--bs-link-color: #7db3ee;--bs-link-hover-color: #97c2f1;--bs-link-color-rgb: 125, 179, 238;--bs-link-hover-color-rgb: 151, 194, 241;--bs-code-color: white;--bs-border-color: #495057;--bs-border-color-translucent: rgba(255, 255, 255, 0.15);--bs-form-valid-color: #8cd374;--bs-form-valid-border-color: #8cd374;--bs-form-invalid-color: #ff6688;--bs-form-invalid-border-color: #ff6688}*,*::before,*::after{box-sizing:border-box}:root{font-size:var(--bs-root-font-size)}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;border:0;border-top:1px solid;opacity:.25}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2;color:var(--bs-heading-color)}h1,.h1{font-size:calc(1.325rem + 0.9vw)}@media(min-width: 1200px){h1,.h1{font-size:2rem}}h2,.h2{font-size:calc(1.29rem + 0.48vw)}@media(min-width: 1200px){h2,.h2{font-size:1.65rem}}h3,.h3{font-size:calc(1.27rem + 0.24vw)}@media(min-width: 1200px){h3,.h3{font-size:1.45rem}}h4,.h4{font-size:1.25rem}h5,.h5{font-size:1.1rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{text-decoration:underline dotted;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;-ms-text-decoration:underline dotted;-o-text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem;padding:.625rem 1.25rem;border-left:.25rem solid #e9ecef}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}b,strong{font-weight:bolder}small,.small{font-size:0.875em}mark,.mark{padding:.1875em;background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}a:hover{--bs-link-color-rgb: var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f8f9fa;line-height:1.5;padding:.5rem;border:1px solid var(--bs-border-color, #e1e1e2)}pre code{background-color:rgba(0,0,0,0);font-size:inherit;color:inherit;word-break:normal}code{font-size:0.875em;color:var(--bs-code-color);background-color:#f8f9fa;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#373a3c}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:rgba(55,58,60,.75);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none !important}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + 0.3vw);line-height:inherit}@media(min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:0.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:0.875em;color:#868e96}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #e1e1e2;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:rgba(55,58,60,.75)}.container,.container-fluid,.container-xxl,.container-xl,.container-lg,.container-md,.container-sm{--bs-gutter-x: 1.5rem;--bs-gutter-y: 0;width:100%;padding-right:calc(var(--bs-gutter-x)*.5);padding-left:calc(var(--bs-gutter-x)*.5);margin-right:auto;margin-left:auto}@media(min-width: 576px){.container-sm,.container{max-width:540px}}@media(min-width: 768px){.container-md,.container-sm,.container{max-width:720px}}@media(min-width: 992px){.container-lg,.container-md,.container-sm,.container{max-width:960px}}@media(min-width: 1200px){.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1140px}}@media(min-width: 1400px){.container-xxl,.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1320px}}:root{--bs-breakpoint-xs: 0;--bs-breakpoint-sm: 576px;--bs-breakpoint-md: 768px;--bs-breakpoint-lg: 992px;--bs-breakpoint-xl: 1200px;--bs-breakpoint-xxl: 1400px}.grid{display:grid;grid-template-rows:repeat(var(--bs-rows, 1), 1fr);grid-template-columns:repeat(var(--bs-columns, 12), 1fr);gap:var(--bs-gap, 1.5rem)}.grid .g-col-1{grid-column:auto/span 1}.grid .g-col-2{grid-column:auto/span 2}.grid .g-col-3{grid-column:auto/span 3}.grid .g-col-4{grid-column:auto/span 4}.grid .g-col-5{grid-column:auto/span 5}.grid .g-col-6{grid-column:auto/span 6}.grid .g-col-7{grid-column:auto/span 7}.grid .g-col-8{grid-column:auto/span 8}.grid .g-col-9{grid-column:auto/span 9}.grid .g-col-10{grid-column:auto/span 10}.grid .g-col-11{grid-column:auto/span 11}.grid .g-col-12{grid-column:auto/span 12}.grid .g-start-1{grid-column-start:1}.grid .g-start-2{grid-column-start:2}.grid .g-start-3{grid-column-start:3}.grid .g-start-4{grid-column-start:4}.grid .g-start-5{grid-column-start:5}.grid .g-start-6{grid-column-start:6}.grid .g-start-7{grid-column-start:7}.grid .g-start-8{grid-column-start:8}.grid .g-start-9{grid-column-start:9}.grid .g-start-10{grid-column-start:10}.grid .g-start-11{grid-column-start:11}@media(min-width: 576px){.grid .g-col-sm-1{grid-column:auto/span 1}.grid .g-col-sm-2{grid-column:auto/span 2}.grid .g-col-sm-3{grid-column:auto/span 3}.grid .g-col-sm-4{grid-column:auto/span 4}.grid .g-col-sm-5{grid-column:auto/span 5}.grid .g-col-sm-6{grid-column:auto/span 6}.grid .g-col-sm-7{grid-column:auto/span 7}.grid .g-col-sm-8{grid-column:auto/span 8}.grid .g-col-sm-9{grid-column:auto/span 9}.grid .g-col-sm-10{grid-column:auto/span 10}.grid .g-col-sm-11{grid-column:auto/span 11}.grid .g-col-sm-12{grid-column:auto/span 12}.grid .g-start-sm-1{grid-column-start:1}.grid .g-start-sm-2{grid-column-start:2}.grid .g-start-sm-3{grid-column-start:3}.grid .g-start-sm-4{grid-column-start:4}.grid .g-start-sm-5{grid-column-start:5}.grid .g-start-sm-6{grid-column-start:6}.grid .g-start-sm-7{grid-column-start:7}.grid .g-start-sm-8{grid-column-start:8}.grid .g-start-sm-9{grid-column-start:9}.grid .g-start-sm-10{grid-column-start:10}.grid .g-start-sm-11{grid-column-start:11}}@media(min-width: 768px){.grid .g-col-md-1{grid-column:auto/span 1}.grid .g-col-md-2{grid-column:auto/span 2}.grid .g-col-md-3{grid-column:auto/span 3}.grid .g-col-md-4{grid-column:auto/span 4}.grid .g-col-md-5{grid-column:auto/span 5}.grid .g-col-md-6{grid-column:auto/span 6}.grid .g-col-md-7{grid-column:auto/span 7}.grid .g-col-md-8{grid-column:auto/span 8}.grid .g-col-md-9{grid-column:auto/span 9}.grid .g-col-md-10{grid-column:auto/span 10}.grid .g-col-md-11{grid-column:auto/span 11}.grid .g-col-md-12{grid-column:auto/span 12}.grid .g-start-md-1{grid-column-start:1}.grid .g-start-md-2{grid-column-start:2}.grid .g-start-md-3{grid-column-start:3}.grid .g-start-md-4{grid-column-start:4}.grid .g-start-md-5{grid-column-start:5}.grid .g-start-md-6{grid-column-start:6}.grid .g-start-md-7{grid-column-start:7}.grid .g-start-md-8{grid-column-start:8}.grid .g-start-md-9{grid-column-start:9}.grid .g-start-md-10{grid-column-start:10}.grid .g-start-md-11{grid-column-start:11}}@media(min-width: 992px){.grid .g-col-lg-1{grid-column:auto/span 1}.grid .g-col-lg-2{grid-column:auto/span 2}.grid .g-col-lg-3{grid-column:auto/span 3}.grid .g-col-lg-4{grid-column:auto/span 4}.grid .g-col-lg-5{grid-column:auto/span 5}.grid .g-col-lg-6{grid-column:auto/span 6}.grid .g-col-lg-7{grid-column:auto/span 7}.grid .g-col-lg-8{grid-column:auto/span 8}.grid .g-col-lg-9{grid-column:auto/span 9}.grid .g-col-lg-10{grid-column:auto/span 10}.grid .g-col-lg-11{grid-column:auto/span 11}.grid .g-col-lg-12{grid-column:auto/span 12}.grid .g-start-lg-1{grid-column-start:1}.grid .g-start-lg-2{grid-column-start:2}.grid .g-start-lg-3{grid-column-start:3}.grid .g-start-lg-4{grid-column-start:4}.grid .g-start-lg-5{grid-column-start:5}.grid .g-start-lg-6{grid-column-start:6}.grid .g-start-lg-7{grid-column-start:7}.grid .g-start-lg-8{grid-column-start:8}.grid .g-start-lg-9{grid-column-start:9}.grid .g-start-lg-10{grid-column-start:10}.grid .g-start-lg-11{grid-column-start:11}}@media(min-width: 1200px){.grid .g-col-xl-1{grid-column:auto/span 1}.grid .g-col-xl-2{grid-column:auto/span 2}.grid .g-col-xl-3{grid-column:auto/span 3}.grid .g-col-xl-4{grid-column:auto/span 4}.grid .g-col-xl-5{grid-column:auto/span 5}.grid .g-col-xl-6{grid-column:auto/span 6}.grid .g-col-xl-7{grid-column:auto/span 7}.grid .g-col-xl-8{grid-column:auto/span 8}.grid .g-col-xl-9{grid-column:auto/span 9}.grid .g-col-xl-10{grid-column:auto/span 10}.grid .g-col-xl-11{grid-column:auto/span 11}.grid .g-col-xl-12{grid-column:auto/span 12}.grid .g-start-xl-1{grid-column-start:1}.grid .g-start-xl-2{grid-column-start:2}.grid .g-start-xl-3{grid-column-start:3}.grid .g-start-xl-4{grid-column-start:4}.grid .g-start-xl-5{grid-column-start:5}.grid .g-start-xl-6{grid-column-start:6}.grid .g-start-xl-7{grid-column-start:7}.grid .g-start-xl-8{grid-column-start:8}.grid .g-start-xl-9{grid-column-start:9}.grid .g-start-xl-10{grid-column-start:10}.grid .g-start-xl-11{grid-column-start:11}}@media(min-width: 1400px){.grid .g-col-xxl-1{grid-column:auto/span 1}.grid .g-col-xxl-2{grid-column:auto/span 2}.grid .g-col-xxl-3{grid-column:auto/span 3}.grid .g-col-xxl-4{grid-column:auto/span 4}.grid .g-col-xxl-5{grid-column:auto/span 5}.grid .g-col-xxl-6{grid-column:auto/span 6}.grid .g-col-xxl-7{grid-column:auto/span 7}.grid .g-col-xxl-8{grid-column:auto/span 8}.grid .g-col-xxl-9{grid-column:auto/span 9}.grid .g-col-xxl-10{grid-column:auto/span 10}.grid .g-col-xxl-11{grid-column:auto/span 11}.grid .g-col-xxl-12{grid-column:auto/span 12}.grid .g-start-xxl-1{grid-column-start:1}.grid .g-start-xxl-2{grid-column-start:2}.grid .g-start-xxl-3{grid-column-start:3}.grid .g-start-xxl-4{grid-column-start:4}.grid .g-start-xxl-5{grid-column-start:5}.grid .g-start-xxl-6{grid-column-start:6}.grid .g-start-xxl-7{grid-column-start:7}.grid .g-start-xxl-8{grid-column-start:8}.grid .g-start-xxl-9{grid-column-start:9}.grid .g-start-xxl-10{grid-column-start:10}.grid .g-start-xxl-11{grid-column-start:11}}.table{--bs-table-color-type: initial;--bs-table-bg-type: initial;--bs-table-color-state: initial;--bs-table-bg-state: initial;--bs-table-color: #373a3c;--bs-table-bg: #fff;--bs-table-border-color: #e1e1e2;--bs-table-accent-bg: transparent;--bs-table-striped-color: #373a3c;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #373a3c;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #373a3c;--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(1px*2) solid #9b9d9e}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type: var(--bs-table-striped-color);--bs-table-bg-type: var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(even){--bs-table-color-type: var(--bs-table-striped-color);--bs-table-bg-type: var(--bs-table-striped-bg)}.table-active{--bs-table-color-state: var(--bs-table-active-color);--bs-table-bg-state: var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state: var(--bs-table-hover-color);--bs-table-bg-state: var(--bs-table-hover-bg)}.table-primary{--bs-table-color: #000;--bs-table-bg: #d4e6f9;--bs-table-border-color: #bfcfe0;--bs-table-striped-bg: #c9dbed;--bs-table-striped-color: #000;--bs-table-active-bg: #bfcfe0;--bs-table-active-color: #000;--bs-table-hover-bg: #c4d5e6;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color: #000;--bs-table-bg: #d7d8d8;--bs-table-border-color: #c2c2c2;--bs-table-striped-bg: #cccdcd;--bs-table-striped-color: #000;--bs-table-active-bg: #c2c2c2;--bs-table-active-color: #000;--bs-table-hover-bg: #c7c8c8;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color: #000;--bs-table-bg: #d9f0d1;--bs-table-border-color: #c3d8bc;--bs-table-striped-bg: #cee4c7;--bs-table-striped-color: #000;--bs-table-active-bg: #c3d8bc;--bs-table-active-color: #000;--bs-table-hover-bg: #c9dec1;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color: #000;--bs-table-bg: #ebddf1;--bs-table-border-color: #d4c7d9;--bs-table-striped-bg: #dfd2e5;--bs-table-striped-color: #000;--bs-table-active-bg: #d4c7d9;--bs-table-active-color: #000;--bs-table-hover-bg: #d9ccdf;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color: #000;--bs-table-bg: #ffe3d1;--bs-table-border-color: #e6ccbc;--bs-table-striped-bg: #f2d8c7;--bs-table-striped-color: #000;--bs-table-active-bg: #e6ccbc;--bs-table-active-color: #000;--bs-table-hover-bg: #ecd2c1;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color: #000;--bs-table-bg: #ffccd7;--bs-table-border-color: #e6b8c2;--bs-table-striped-bg: #f2c2cc;--bs-table-striped-color: #000;--bs-table-active-bg: #e6b8c2;--bs-table-active-color: #000;--bs-table-hover-bg: #ecbdc7;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color: #000;--bs-table-bg: #f8f9fa;--bs-table-border-color: #dfe0e1;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color: #fff;--bs-table-bg: #373a3c;--bs-table-border-color: #4b4e50;--bs-table-striped-bg: #414446;--bs-table-striped-color: #fff;--bs-table-active-bg: #4b4e50;--bs-table-active-color: #fff;--bs-table-hover-bg: #46494b;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label,.shiny-input-container .control-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.875rem}.form-text{margin-top:.25rem;font-size:0.875em;color:rgba(55,58,60,.75)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#fff;background-clip:padding-box;border:1px solid #e1e1e2;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#373a3c;background-color:#fff;border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::placeholder{color:rgba(55,58,60,.75);opacity:1}.form-control:disabled{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#373a3c;background-color:#f8f9fa;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#e9ecef}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#373a3c;background-color:rgba(0,0,0,0);border:solid rgba(0,0,0,0);border-width:1px 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + calc(1px * 2));padding:.25rem .5rem;font-size:0.875rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(1px * 2));padding:.5rem 1rem;font-size:1.25rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + calc(1px * 2))}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + calc(1px * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(1px * 2))}.form-control-color{width:3rem;height:calc(1.5em + 0.75rem + calc(1px * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0 !important}.form-control-color::-webkit-color-swatch{border:0 !important}.form-control-color.form-control-sm{height:calc(1.5em + 0.5rem + calc(1px * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(1px * 2))}.form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23373a3c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#fff;background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon, none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #e1e1e2;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 #373a3c}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check,.shiny-input-container .checkbox,.shiny-input-container .radio{display:block;min-height:1.5rem;padding-left:0;margin-bottom:.125rem}.form-check .form-check-input,.form-check .shiny-input-container .checkbox input,.form-check .shiny-input-container .radio input,.shiny-input-container .checkbox .form-check-input,.shiny-input-container .checkbox .shiny-input-container .checkbox input,.shiny-input-container .checkbox .shiny-input-container .radio input,.shiny-input-container .radio .form-check-input,.shiny-input-container .radio .shiny-input-container .checkbox input,.shiny-input-container .radio .shiny-input-container .radio input{float:left;margin-left:0}.form-check-reverse{padding-right:0;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:0;margin-left:0}.form-check-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{--bs-form-check-bg: #fff;width:1em;height:1em;margin-top:.25em;vertical-align:top;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid #e1e1e2;print-color-adjust:exact}.form-check-input[type=radio],.shiny-input-container .checkbox input[type=radio],.shiny-input-container .checkbox-inline input[type=radio],.shiny-input-container .radio input[type=radio],.shiny-input-container .radio-inline input[type=radio]{border-radius:50%}.form-check-input:active,.shiny-input-container .checkbox input:active,.shiny-input-container .checkbox-inline input:active,.shiny-input-container .radio input:active,.shiny-input-container .radio-inline input:active{filter:brightness(90%)}.form-check-input:focus,.shiny-input-container .checkbox input:focus,.shiny-input-container .checkbox-inline input:focus,.shiny-input-container .radio input:focus,.shiny-input-container .radio-inline input:focus{border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-check-input:checked,.shiny-input-container .checkbox input:checked,.shiny-input-container .checkbox-inline input:checked,.shiny-input-container .radio input:checked,.shiny-input-container .radio-inline input:checked{background-color:#2780e3;border-color:#2780e3}.form-check-input:checked[type=checkbox],.shiny-input-container .checkbox input:checked[type=checkbox],.shiny-input-container .checkbox-inline input:checked[type=checkbox],.shiny-input-container .radio input:checked[type=checkbox],.shiny-input-container .radio-inline input:checked[type=checkbox]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio],.shiny-input-container .checkbox input:checked[type=radio],.shiny-input-container .checkbox-inline input:checked[type=radio],.shiny-input-container .radio input:checked[type=radio],.shiny-input-container .radio-inline input:checked[type=radio]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate,.shiny-input-container .checkbox input[type=checkbox]:indeterminate,.shiny-input-container .checkbox-inline input[type=checkbox]:indeterminate,.shiny-input-container .radio input[type=checkbox]:indeterminate,.shiny-input-container .radio-inline input[type=checkbox]:indeterminate{background-color:#2780e3;border-color:#2780e3;--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled,.shiny-input-container .checkbox input:disabled,.shiny-input-container .checkbox-inline input:disabled,.shiny-input-container .radio input:disabled,.shiny-input-container .radio-inline input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input[disabled]~span,.form-check-input:disabled~.form-check-label,.form-check-input:disabled~span,.shiny-input-container .checkbox input[disabled]~.form-check-label,.shiny-input-container .checkbox input[disabled]~span,.shiny-input-container .checkbox input:disabled~.form-check-label,.shiny-input-container .checkbox input:disabled~span,.shiny-input-container .checkbox-inline input[disabled]~.form-check-label,.shiny-input-container .checkbox-inline input[disabled]~span,.shiny-input-container .checkbox-inline input:disabled~.form-check-label,.shiny-input-container .checkbox-inline input:disabled~span,.shiny-input-container .radio input[disabled]~.form-check-label,.shiny-input-container .radio input[disabled]~span,.shiny-input-container .radio input:disabled~.form-check-label,.shiny-input-container .radio input:disabled~span,.shiny-input-container .radio-inline input[disabled]~.form-check-label,.shiny-input-container .radio-inline input[disabled]~span,.shiny-input-container .radio-inline input:disabled~.form-check-label,.shiny-input-container .radio-inline input:disabled~span{cursor:default;opacity:.5}.form-check-label,.shiny-input-container .checkbox label,.shiny-input-container .checkbox-inline label,.shiny-input-container .radio label,.shiny-input-container .radio-inline label{cursor:pointer}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2393c0f1'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:rgba(0,0,0,0)}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#2780e3;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#bed9f7}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#f8f9fa;border-color:rgba(0,0,0,0)}.form-range::-moz-range-thumb{width:1rem;height:1rem;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#2780e3;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#bed9f7}.form-range::-moz-range-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#f8f9fa;border-color:rgba(0,0,0,0)}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:rgba(55,58,60,.75)}.form-range:disabled::-moz-range-thumb{background-color:rgba(55,58,60,.75)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(1px * 2));min-height:calc(3.5rem + calc(1px * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control::placeholder,.form-floating>.form-control-plaintext::placeholder{color:rgba(0,0,0,0)}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill,.form-floating>.form-control-plaintext:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-control-plaintext~label,.form-floating>.form-select~label{color:rgba(var(--bs-body-color-rgb), 0.65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:focus~label::after,.form-floating>.form-control:not(:placeholder-shown)~label::after,.form-floating>.form-control-plaintext~label::after,.form-floating>.form-select~label::after{position:absolute;inset:1rem .375rem;z-index:-1;height:1.5em;content:"";background-color:#fff}.form-floating>.form-control:-webkit-autofill~label{color:rgba(var(--bs-body-color-rgb), 0.65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control-plaintext~label{border-width:1px 0}.form-floating>:disabled~label,.form-floating>.form-control:disabled~label{color:#868e96}.form-floating>:disabled~label::after,.form-floating>.form-control:disabled~label::after{background-color:#e9ecef}.input-group{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:stretch;-webkit-align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select,.input-group>.form-floating{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus,.input-group>.form-floating:focus-within{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;text-align:center;white-space:nowrap;background-color:#f8f9fa;border:1px solid #e1e1e2}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(1px*-1)}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#3fb618}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:#3fb618}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#3fb618;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233fb618' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#3fb618}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233fb618' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-control-color:valid,.form-control-color.is-valid{width:calc(3rem + calc(1.5em + 0.75rem))}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#3fb618}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#3fb618}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#3fb618}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):valid,.input-group>.form-control:not(:focus).is-valid,.was-validated .input-group>.form-select:not(:focus):valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.input-group>.form-floating:not(:focus-within).is-valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#ff0039}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:#ff0039}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#ff0039;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff0039'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff0039' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#ff0039}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff0039'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff0039' stroke='none'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-control-color:invalid,.form-control-color.is-invalid{width:calc(3rem + calc(1.5em + 0.75rem))}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#ff0039}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#ff0039}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#ff0039}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):invalid,.input-group>.form-control:not(:focus).is-invalid,.was-validated .input-group>.form-select:not(:focus):invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.input-group>.form-floating:not(:focus-within).is-invalid{z-index:4}.btn{--bs-btn-padding-x: 0.75rem;--bs-btn-padding-y: 0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight: 400;--bs-btn-line-height: 1.5;--bs-btn-color: #373a3c;--bs-btn-bg: transparent;--bs-btn-border-width: 1px;--bs-btn-border-color: transparent;--bs-btn-border-radius: 0.375rem;--bs-btn-hover-border-color: transparent;--bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity: 0.65;--bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,:not(.btn-check)+.btn:active,.btn:first-child:active,.btn.active,.btn.show{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,:not(.btn-check)+.btn:active:focus-visible,.btn:first-child:active:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-default{--bs-btn-color: #fff;--bs-btn-bg: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2f3133;--bs-btn-hover-border-color: #2c2e30;--bs-btn-focus-shadow-rgb: 85, 88, 89;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2c2e30;--bs-btn-active-border-color: #292c2d;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #373a3c;--bs-btn-disabled-border-color: #373a3c}.btn-primary{--bs-btn-color: #fff;--bs-btn-bg: #2780e3;--bs-btn-border-color: #2780e3;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #216dc1;--bs-btn-hover-border-color: #1f66b6;--bs-btn-focus-shadow-rgb: 71, 147, 231;--bs-btn-active-color: #fff;--bs-btn-active-bg: #1f66b6;--bs-btn-active-border-color: #1d60aa;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #2780e3;--bs-btn-disabled-border-color: #2780e3}.btn-secondary{--bs-btn-color: #fff;--bs-btn-bg: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2f3133;--bs-btn-hover-border-color: #2c2e30;--bs-btn-focus-shadow-rgb: 85, 88, 89;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2c2e30;--bs-btn-active-border-color: #292c2d;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #373a3c;--bs-btn-disabled-border-color: #373a3c}.btn-success{--bs-btn-color: #fff;--bs-btn-bg: #3fb618;--bs-btn-border-color: #3fb618;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #369b14;--bs-btn-hover-border-color: #329213;--bs-btn-focus-shadow-rgb: 92, 193, 59;--bs-btn-active-color: #fff;--bs-btn-active-bg: #329213;--bs-btn-active-border-color: #2f8912;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #3fb618;--bs-btn-disabled-border-color: #3fb618}.btn-info{--bs-btn-color: #fff;--bs-btn-bg: #9954bb;--bs-btn-border-color: #9954bb;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #82479f;--bs-btn-hover-border-color: #7a4396;--bs-btn-focus-shadow-rgb: 168, 110, 197;--bs-btn-active-color: #fff;--bs-btn-active-bg: #7a4396;--bs-btn-active-border-color: #733f8c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #9954bb;--bs-btn-disabled-border-color: #9954bb}.btn-warning{--bs-btn-color: #fff;--bs-btn-bg: #ff7518;--bs-btn-border-color: #ff7518;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #d96314;--bs-btn-hover-border-color: #cc5e13;--bs-btn-focus-shadow-rgb: 255, 138, 59;--bs-btn-active-color: #fff;--bs-btn-active-bg: #cc5e13;--bs-btn-active-border-color: #bf5812;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #ff7518;--bs-btn-disabled-border-color: #ff7518}.btn-danger{--bs-btn-color: #fff;--bs-btn-bg: #ff0039;--bs-btn-border-color: #ff0039;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #d90030;--bs-btn-hover-border-color: #cc002e;--bs-btn-focus-shadow-rgb: 255, 38, 87;--bs-btn-active-color: #fff;--bs-btn-active-bg: #cc002e;--bs-btn-active-border-color: #bf002b;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #ff0039;--bs-btn-disabled-border-color: #ff0039}.btn-light{--bs-btn-color: #000;--bs-btn-bg: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #d3d4d5;--bs-btn-hover-border-color: #c6c7c8;--bs-btn-focus-shadow-rgb: 211, 212, 213;--bs-btn-active-color: #000;--bs-btn-active-bg: #c6c7c8;--bs-btn-active-border-color: #babbbc;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #f8f9fa;--bs-btn-disabled-border-color: #f8f9fa}.btn-dark{--bs-btn-color: #fff;--bs-btn-bg: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #555859;--bs-btn-hover-border-color: #4b4e50;--bs-btn-focus-shadow-rgb: 85, 88, 89;--bs-btn-active-color: #fff;--bs-btn-active-bg: #5f6163;--bs-btn-active-border-color: #4b4e50;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #373a3c;--bs-btn-disabled-border-color: #373a3c}.btn-outline-default{--bs-btn-color: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #373a3c;--bs-btn-hover-border-color: #373a3c;--bs-btn-focus-shadow-rgb: 55, 58, 60;--bs-btn-active-color: #fff;--bs-btn-active-bg: #373a3c;--bs-btn-active-border-color: #373a3c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #373a3c;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #373a3c;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-primary{--bs-btn-color: #2780e3;--bs-btn-border-color: #2780e3;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2780e3;--bs-btn-hover-border-color: #2780e3;--bs-btn-focus-shadow-rgb: 39, 128, 227;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2780e3;--bs-btn-active-border-color: #2780e3;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #2780e3;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #2780e3;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-secondary{--bs-btn-color: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #373a3c;--bs-btn-hover-border-color: #373a3c;--bs-btn-focus-shadow-rgb: 55, 58, 60;--bs-btn-active-color: #fff;--bs-btn-active-bg: #373a3c;--bs-btn-active-border-color: #373a3c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #373a3c;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #373a3c;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-success{--bs-btn-color: #3fb618;--bs-btn-border-color: #3fb618;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #3fb618;--bs-btn-hover-border-color: #3fb618;--bs-btn-focus-shadow-rgb: 63, 182, 24;--bs-btn-active-color: #fff;--bs-btn-active-bg: #3fb618;--bs-btn-active-border-color: #3fb618;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #3fb618;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #3fb618;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-info{--bs-btn-color: #9954bb;--bs-btn-border-color: #9954bb;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #9954bb;--bs-btn-hover-border-color: #9954bb;--bs-btn-focus-shadow-rgb: 153, 84, 187;--bs-btn-active-color: #fff;--bs-btn-active-bg: #9954bb;--bs-btn-active-border-color: #9954bb;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #9954bb;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #9954bb;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-warning{--bs-btn-color: #ff7518;--bs-btn-border-color: #ff7518;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #ff7518;--bs-btn-hover-border-color: #ff7518;--bs-btn-focus-shadow-rgb: 255, 117, 24;--bs-btn-active-color: #fff;--bs-btn-active-bg: #ff7518;--bs-btn-active-border-color: #ff7518;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ff7518;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ff7518;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-danger{--bs-btn-color: #ff0039;--bs-btn-border-color: #ff0039;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #ff0039;--bs-btn-hover-border-color: #ff0039;--bs-btn-focus-shadow-rgb: 255, 0, 57;--bs-btn-active-color: #fff;--bs-btn-active-bg: #ff0039;--bs-btn-active-border-color: #ff0039;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ff0039;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ff0039;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-light{--bs-btn-color: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #f8f9fa;--bs-btn-hover-border-color: #f8f9fa;--bs-btn-focus-shadow-rgb: 248, 249, 250;--bs-btn-active-color: #000;--bs-btn-active-bg: #f8f9fa;--bs-btn-active-border-color: #f8f9fa;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #f8f9fa;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #f8f9fa;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-dark{--bs-btn-color: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #373a3c;--bs-btn-hover-border-color: #373a3c;--bs-btn-focus-shadow-rgb: 55, 58, 60;--bs-btn-active-color: #fff;--bs-btn-active-bg: #373a3c;--bs-btn-active-border-color: #373a3c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #373a3c;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #373a3c;--bs-btn-bg: transparent;--bs-gradient: none}.btn-link{--bs-btn-font-weight: 400;--bs-btn-color: #2761e3;--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: #1f4eb6;--bs-btn-hover-border-color: transparent;--bs-btn-active-color: #1f4eb6;--bs-btn-active-border-color: transparent;--bs-btn-disabled-color: #868e96;--bs-btn-disabled-border-color: transparent;--bs-btn-box-shadow: 0 0 0 #000;--bs-btn-focus-shadow-rgb: 71, 121, 231;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-lg,.btn-group-lg>.btn{--bs-btn-padding-y: 0.5rem;--bs-btn-padding-x: 1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius: 0.5rem}.btn-sm,.btn-group-sm>.btn{--bs-btn-padding-y: 0.25rem;--bs-btn-padding-x: 0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius: 0.25rem}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .2s ease}@media(prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart,.dropup-center,.dropdown-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid rgba(0,0,0,0);border-bottom:0;border-left:.3em solid rgba(0,0,0,0)}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex: 1000;--bs-dropdown-min-width: 10rem;--bs-dropdown-padding-x: 0;--bs-dropdown-padding-y: 0.5rem;--bs-dropdown-spacer: 0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color: #373a3c;--bs-dropdown-bg: #fff;--bs-dropdown-border-color: rgba(0, 0, 0, 0.175);--bs-dropdown-border-radius: 0.375rem;--bs-dropdown-border-width: 1px;--bs-dropdown-inner-border-radius: calc(0.375rem - 1px);--bs-dropdown-divider-bg: rgba(0, 0, 0, 0.175);--bs-dropdown-divider-margin-y: 0.5rem;--bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-dropdown-link-color: #373a3c;--bs-dropdown-link-hover-color: #373a3c;--bs-dropdown-link-hover-bg: #f8f9fa;--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #2780e3;--bs-dropdown-link-disabled-color: rgba(55, 58, 60, 0.5);--bs-dropdown-item-padding-x: 1rem;--bs-dropdown-item-padding-y: 0.25rem;--bs-dropdown-header-color: #868e96;--bs-dropdown-header-padding-x: 1rem;--bs-dropdown-header-padding-y: 0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid rgba(0,0,0,0);border-bottom:.3em solid;border-left:.3em solid rgba(0,0,0,0)}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:0;border-bottom:.3em solid rgba(0,0,0,0);border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:.3em solid;border-bottom:.3em solid rgba(0,0,0,0)}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap;background-color:rgba(0,0,0,0);border:0}.dropdown-item:hover,.dropdown-item:focus{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:rgba(0,0,0,0)}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:0.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color: #dee2e6;--bs-dropdown-bg: #373a3c;--bs-dropdown-border-color: rgba(0, 0, 0, 0.175);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color: #dee2e6;--bs-dropdown-link-hover-color: #fff;--bs-dropdown-divider-bg: rgba(0, 0, 0, 0.175);--bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #2780e3;--bs-dropdown-link-disabled-color: #adb5bd;--bs-dropdown-header-color: #adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;justify-content:flex-start;-webkit-justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(1px*-1)}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;-webkit-flex-direction:column;align-items:flex-start;-webkit-align-items:flex-start;justify-content:center;-webkit-justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:calc(1px*-1)}.nav{--bs-nav-link-padding-x: 1rem;--bs-nav-link-padding-y: 0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: #2761e3;--bs-nav-link-hover-color: #1f4eb6;--bs-nav-link-disabled-color: rgba(55, 58, 60, 0.75);display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background:none;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width: 1px;--bs-nav-tabs-border-color: #e1e1e2;--bs-nav-tabs-border-radius: 0.375rem;--bs-nav-tabs-link-hover-border-color: #e9ecef #e9ecef #e1e1e2;--bs-nav-tabs-link-active-color: #000;--bs-nav-tabs-link-active-bg: #fff;--bs-nav-tabs-link-active-border-color: #e1e1e2 #e1e1e2 #fff;border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1*var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid rgba(0,0,0,0)}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1*var(--bs-nav-tabs-border-width))}.nav-pills{--bs-nav-pills-border-radius: 0.375rem;--bs-nav-pills-link-active-color: #fff;--bs-nav-pills-link-active-bg: #2780e3}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap: 1rem;--bs-nav-underline-border-width: 0.125rem;--bs-nav-underline-link-active-color: #000;gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid rgba(0,0,0,0)}.nav-underline .nav-link:hover,.nav-underline .nav-link:focus{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;-webkit-flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;-webkit-flex-basis:0;flex-grow:1;-webkit-flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x: 0;--bs-navbar-padding-y: 0.5rem;--bs-navbar-color: #545555;--bs-navbar-hover-color: rgba(31, 78, 182, 0.8);--bs-navbar-disabled-color: rgba(84, 85, 85, 0.75);--bs-navbar-active-color: #1f4eb6;--bs-navbar-brand-padding-y: 0.3125rem;--bs-navbar-brand-margin-end: 1rem;--bs-navbar-brand-font-size: 1.25rem;--bs-navbar-brand-color: #545555;--bs-navbar-brand-hover-color: #1f4eb6;--bs-navbar-nav-link-padding-x: 0.5rem;--bs-navbar-toggler-padding-y: 0.25;--bs-navbar-toggler-padding-x: 0;--bs-navbar-toggler-font-size: 1.25rem;--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23545555' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color: rgba(84, 85, 85, 0);--bs-navbar-toggler-border-radius: 0.375rem;--bs-navbar-toggler-focus-width: 0.25rem;--bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out;position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-sm,.navbar>.container-md,.navbar>.container-lg,.navbar>.container-xl,.navbar>.container-xxl{display:flex;display:-webkit-flex;flex-wrap:inherit;-webkit-flex-wrap:inherit;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-brand:hover,.navbar-brand:focus{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x: 0;--bs-nav-link-padding-y: 0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: var(--bs-navbar-color);--bs-nav-link-hover-color: var(--bs-navbar-hover-color);--bs-nav-link-disabled-color: var(--bs-navbar-disabled-color);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:hover,.navbar-text a:focus{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;-webkit-flex-basis:100%;flex-grow:1;-webkit-flex-grow:1;align-items:center;-webkit-align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:rgba(0,0,0,0);border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);transition:var(--bs-navbar-toggler-transition)}@media(prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media(min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color: #545555;--bs-navbar-hover-color: rgba(31, 78, 182, 0.8);--bs-navbar-disabled-color: rgba(84, 85, 85, 0.75);--bs-navbar-active-color: #1f4eb6;--bs-navbar-brand-color: #545555;--bs-navbar-brand-hover-color: #1f4eb6;--bs-navbar-toggler-border-color: rgba(84, 85, 85, 0);--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23545555' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23545555' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y: 1rem;--bs-card-spacer-x: 1rem;--bs-card-title-spacer-y: 0.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width: 1px;--bs-card-border-color: rgba(0, 0, 0, 0.175);--bs-card-border-radius: 0.375rem;--bs-card-box-shadow: ;--bs-card-inner-border-radius: calc(0.375rem - 1px);--bs-card-cap-padding-y: 0.5rem;--bs-card-cap-padding-x: 1rem;--bs-card-cap-bg: rgba(55, 58, 60, 0.03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg: #fff;--bs-card-img-overlay-padding: 1rem;--bs-card-group-margin: 0.75rem;position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0}.card>.list-group:last-child{border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;-webkit-flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-0.5*var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header-tabs{margin-right:calc(-0.5*var(--bs-card-cap-padding-x));margin-bottom:calc(-1*var(--bs-card-cap-padding-y));margin-left:calc(-0.5*var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-0.5*var(--bs-card-cap-padding-x));margin-left:calc(-0.5*var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media(min-width: 576px){.card-group{display:flex;display:-webkit-flex;flex-flow:row wrap;-webkit-flex-flow:row wrap}.card-group>.card{flex:1 0 0%;-webkit-flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}}.accordion{--bs-accordion-color: #373a3c;--bs-accordion-bg: #fff;--bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease;--bs-accordion-border-color: #e1e1e2;--bs-accordion-border-width: 1px;--bs-accordion-border-radius: 0.375rem;--bs-accordion-inner-border-radius: calc(0.375rem - 1px);--bs-accordion-btn-padding-x: 1.25rem;--bs-accordion-btn-padding-y: 1rem;--bs-accordion-btn-color: #373a3c;--bs-accordion-btn-bg: #fff;--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23373a3c'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width: 1.25rem;--bs-accordion-btn-icon-transform: rotate(-180deg);--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2310335b'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-focus-border-color: #93c0f1;--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-accordion-body-padding-x: 1.25rem;--bs-accordion-body-padding-y: 1rem;--bs-accordion-active-color: #10335b;--bs-accordion-active-bg: #d4e6f9}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1*var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:var(--bs-accordion-btn-focus-border-color);outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:not(:first-of-type){border-top:0}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%237db3ee'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%237db3ee'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x: 0;--bs-breadcrumb-padding-y: 0;--bs-breadcrumb-margin-bottom: 1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color: rgba(55, 58, 60, 0.75);--bs-breadcrumb-item-padding-x: 0.5rem;--bs-breadcrumb-item-active-color: rgba(55, 58, 60, 0.75);display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, ">") /* rtl: var(--bs-breadcrumb-divider, ">") */}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x: 0.75rem;--bs-pagination-padding-y: 0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color: #2761e3;--bs-pagination-bg: #fff;--bs-pagination-border-width: 1px;--bs-pagination-border-color: #e1e1e2;--bs-pagination-border-radius: 0.375rem;--bs-pagination-hover-color: #1f4eb6;--bs-pagination-hover-bg: #f8f9fa;--bs-pagination-hover-border-color: #e1e1e2;--bs-pagination-focus-color: #1f4eb6;--bs-pagination-focus-bg: #e9ecef;--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-pagination-active-color: #fff;--bs-pagination-active-bg: #2780e3;--bs-pagination-active-border-color: #2780e3;--bs-pagination-disabled-color: rgba(55, 58, 60, 0.75);--bs-pagination-disabled-bg: #e9ecef;--bs-pagination-disabled-border-color: #e1e1e2;display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.page-link.active,.active>.page-link{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.page-link.disabled,.disabled>.page-link{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(1px*-1)}.pagination-lg{--bs-pagination-padding-x: 1.5rem;--bs-pagination-padding-y: 0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius: 0.5rem}.pagination-sm{--bs-pagination-padding-x: 0.5rem;--bs-pagination-padding-y: 0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius: 0.25rem}.badge{--bs-badge-padding-x: 0.65em;--bs-badge-padding-y: 0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight: 700;--bs-badge-color: #fff;--bs-badge-border-radius: 0.375rem;display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg: transparent;--bs-alert-padding-x: 1rem;--bs-alert-padding-y: 1rem;--bs-alert-margin-bottom: 1rem;--bs-alert-color: inherit;--bs-alert-border-color: transparent;--bs-alert-border: 0 solid var(--bs-alert-border-color);--bs-alert-border-radius: 0.375rem;--bs-alert-link-color: inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{--bs-alert-color: var(--bs-default-text-emphasis);--bs-alert-bg: var(--bs-default-bg-subtle);--bs-alert-border-color: var(--bs-default-border-subtle);--bs-alert-link-color: var(--bs-default-text-emphasis)}.alert-primary{--bs-alert-color: var(--bs-primary-text-emphasis);--bs-alert-bg: var(--bs-primary-bg-subtle);--bs-alert-border-color: var(--bs-primary-border-subtle);--bs-alert-link-color: var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color: var(--bs-secondary-text-emphasis);--bs-alert-bg: var(--bs-secondary-bg-subtle);--bs-alert-border-color: var(--bs-secondary-border-subtle);--bs-alert-link-color: var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color: var(--bs-success-text-emphasis);--bs-alert-bg: var(--bs-success-bg-subtle);--bs-alert-border-color: var(--bs-success-border-subtle);--bs-alert-link-color: var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color: var(--bs-info-text-emphasis);--bs-alert-bg: var(--bs-info-bg-subtle);--bs-alert-border-color: var(--bs-info-border-subtle);--bs-alert-link-color: var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color: var(--bs-warning-text-emphasis);--bs-alert-bg: var(--bs-warning-bg-subtle);--bs-alert-border-color: var(--bs-warning-border-subtle);--bs-alert-link-color: var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color: var(--bs-danger-text-emphasis);--bs-alert-bg: var(--bs-danger-bg-subtle);--bs-alert-border-color: var(--bs-danger-border-subtle);--bs-alert-link-color: var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color: var(--bs-light-text-emphasis);--bs-alert-bg: var(--bs-light-bg-subtle);--bs-alert-border-color: var(--bs-light-border-subtle);--bs-alert-link-color: var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color: var(--bs-dark-text-emphasis);--bs-alert-bg: var(--bs-dark-bg-subtle);--bs-alert-border-color: var(--bs-dark-border-subtle);--bs-alert-link-color: var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:.5rem}}.progress,.progress-stacked{--bs-progress-height: 0.5rem;--bs-progress-font-size:0.75rem;--bs-progress-bg: #e9ecef;--bs-progress-border-radius: 0.375rem;--bs-progress-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-progress-bar-color: #fff;--bs-progress-bar-bg: #2780e3;--bs-progress-bar-transition: width 0.6s ease;display:flex;display:-webkit-flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg)}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media(prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color: #373a3c;--bs-list-group-bg: #fff;--bs-list-group-border-color: #e1e1e2;--bs-list-group-border-width: 1px;--bs-list-group-border-radius: 0.375rem;--bs-list-group-item-padding-x: 1rem;--bs-list-group-item-padding-y: 0.5rem;--bs-list-group-action-color: rgba(55, 58, 60, 0.75);--bs-list-group-action-hover-color: #000;--bs-list-group-action-hover-bg: #f8f9fa;--bs-list-group-action-active-color: #373a3c;--bs-list-group-action-active-bg: #e9ecef;--bs-list-group-disabled-color: rgba(55, 58, 60, 0.75);--bs-list-group-disabled-bg: #fff;--bs-list-group-active-color: #fff;--bs-list-group-active-bg: #2780e3;--bs-list-group-active-border-color: #2780e3;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1*var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{--bs-list-group-color: var(--bs-default-text-emphasis);--bs-list-group-bg: var(--bs-default-bg-subtle);--bs-list-group-border-color: var(--bs-default-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-default-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-default-border-subtle);--bs-list-group-active-color: var(--bs-default-bg-subtle);--bs-list-group-active-bg: var(--bs-default-text-emphasis);--bs-list-group-active-border-color: var(--bs-default-text-emphasis)}.list-group-item-primary{--bs-list-group-color: var(--bs-primary-text-emphasis);--bs-list-group-bg: var(--bs-primary-bg-subtle);--bs-list-group-border-color: var(--bs-primary-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-primary-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-primary-border-subtle);--bs-list-group-active-color: var(--bs-primary-bg-subtle);--bs-list-group-active-bg: var(--bs-primary-text-emphasis);--bs-list-group-active-border-color: var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color: var(--bs-secondary-text-emphasis);--bs-list-group-bg: var(--bs-secondary-bg-subtle);--bs-list-group-border-color: var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-secondary-border-subtle);--bs-list-group-active-color: var(--bs-secondary-bg-subtle);--bs-list-group-active-bg: var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color: var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color: var(--bs-success-text-emphasis);--bs-list-group-bg: var(--bs-success-bg-subtle);--bs-list-group-border-color: var(--bs-success-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-success-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-success-border-subtle);--bs-list-group-active-color: var(--bs-success-bg-subtle);--bs-list-group-active-bg: var(--bs-success-text-emphasis);--bs-list-group-active-border-color: var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color: var(--bs-info-text-emphasis);--bs-list-group-bg: var(--bs-info-bg-subtle);--bs-list-group-border-color: var(--bs-info-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-info-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-info-border-subtle);--bs-list-group-active-color: var(--bs-info-bg-subtle);--bs-list-group-active-bg: var(--bs-info-text-emphasis);--bs-list-group-active-border-color: var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color: var(--bs-warning-text-emphasis);--bs-list-group-bg: var(--bs-warning-bg-subtle);--bs-list-group-border-color: var(--bs-warning-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-warning-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-warning-border-subtle);--bs-list-group-active-color: var(--bs-warning-bg-subtle);--bs-list-group-active-bg: var(--bs-warning-text-emphasis);--bs-list-group-active-border-color: var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color: var(--bs-danger-text-emphasis);--bs-list-group-bg: var(--bs-danger-bg-subtle);--bs-list-group-border-color: var(--bs-danger-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-danger-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-danger-border-subtle);--bs-list-group-active-color: var(--bs-danger-bg-subtle);--bs-list-group-active-bg: var(--bs-danger-text-emphasis);--bs-list-group-active-border-color: var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color: var(--bs-light-text-emphasis);--bs-list-group-bg: var(--bs-light-bg-subtle);--bs-list-group-border-color: var(--bs-light-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-light-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-light-border-subtle);--bs-list-group-active-color: var(--bs-light-bg-subtle);--bs-list-group-active-bg: var(--bs-light-text-emphasis);--bs-list-group-active-border-color: var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color: var(--bs-dark-text-emphasis);--bs-list-group-bg: var(--bs-dark-bg-subtle);--bs-list-group-border-color: var(--bs-dark-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-dark-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-dark-border-subtle);--bs-list-group-active-color: var(--bs-dark-bg-subtle);--bs-list-group-active-bg: var(--bs-dark-text-emphasis);--bs-list-group-active-border-color: var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color: #000;--bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");--bs-btn-close-opacity: 0.5;--bs-btn-close-hover-opacity: 0.75;--bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-btn-close-focus-opacity: 1;--bs-btn-close-disabled-opacity: 0.25;--bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%);box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:rgba(0,0,0,0) var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{filter:var(--bs-btn-close-white-filter)}[data-bs-theme=dark] .btn-close{filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex: 1090;--bs-toast-padding-x: 0.75rem;--bs-toast-padding-y: 0.5rem;--bs-toast-spacing: 1.5rem;--bs-toast-max-width: 350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg: rgba(255, 255, 255, 0.85);--bs-toast-border-width: 1px;--bs-toast-border-color: rgba(0, 0, 0, 0.175);--bs-toast-border-radius: 0.375rem;--bs-toast-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-toast-header-color: rgba(55, 58, 60, 0.75);--bs-toast-header-bg: rgba(255, 255, 255, 0.85);--bs-toast-header-border-color: rgba(0, 0, 0, 0.175);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex: 1090;position:absolute;z-index:var(--bs-toast-zindex);width:max-content;width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:-o-max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color)}.toast-header .btn-close{margin-right:calc(-0.5*var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex: 1055;--bs-modal-width: 500px;--bs-modal-padding: 1rem;--bs-modal-margin: 0.5rem;--bs-modal-color: ;--bs-modal-bg: #fff;--bs-modal-border-color: rgba(0, 0, 0, 0.175);--bs-modal-border-width: 1px;--bs-modal-border-radius: 0.5rem;--bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-modal-inner-border-radius: calc(0.5rem - 1px);--bs-modal-header-padding-x: 1rem;--bs-modal-header-padding-y: 1rem;--bs-modal-header-padding: 1rem 1rem;--bs-modal-header-border-color: #e1e1e2;--bs-modal-header-border-width: 1px;--bs-modal-title-line-height: 1.5;--bs-modal-footer-gap: 0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color: #e1e1e2;--bs-modal-footer-border-width: 1px;position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media(prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin)*2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;min-height:calc(100% - var(--bs-modal-margin)*2)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);outline:0}.modal-backdrop{--bs-backdrop-zindex: 1050;--bs-backdrop-bg: #000;--bs-backdrop-opacity: 0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y)*.5) calc(var(--bs-modal-header-padding-x)*.5);margin:calc(-0.5*var(--bs-modal-header-padding-y)) calc(-0.5*var(--bs-modal-header-padding-x)) calc(-0.5*var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap)*.5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap)*.5)}@media(min-width: 576px){.modal{--bs-modal-margin: 1.75rem;--bs-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width: 300px}}@media(min-width: 992px){.modal-lg,.modal-xl{--bs-modal-width: 800px}}@media(min-width: 1200px){.modal-xl{--bs-modal-width: 1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0}.modal-fullscreen .modal-body{overflow-y:auto}@media(max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media(max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media(max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media(max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media(max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex: 1080;--bs-tooltip-max-width: 200px;--bs-tooltip-padding-x: 0.5rem;--bs-tooltip-padding-y: 0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color: #fff;--bs-tooltip-bg: #000;--bs-tooltip-border-radius: 0.375rem;--bs-tooltip-opacity: 0.9;--bs-tooltip-arrow-width: 0.8rem;--bs-tooltip-arrow-height: 0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:calc(-1*var(--bs-tooltip-arrow-height))}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width)*.5) 0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:calc(-1*var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width)*.5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width)*.5) 0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:calc(-1*var(--bs-tooltip-arrow-height))}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width)*.5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:calc(-1*var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width)*.5) 0 calc(var(--bs-tooltip-arrow-width)*.5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg)}.popover{--bs-popover-zindex: 1070;--bs-popover-max-width: 276px;--bs-popover-font-size:0.875rem;--bs-popover-bg: #fff;--bs-popover-border-width: 1px;--bs-popover-border-color: rgba(0, 0, 0, 0.175);--bs-popover-border-radius: 0.5rem;--bs-popover-inner-border-radius: calc(0.5rem - 1px);--bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-popover-header-padding-x: 1rem;--bs-popover-header-padding-y: 0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color: inherit;--bs-popover-header-bg: #e9ecef;--bs-popover-body-padding-x: 1rem;--bs-popover-body-padding-y: 1rem;--bs-popover-body-color: #373a3c;--bs-popover-arrow-width: 1rem;--bs-popover-arrow-height: 0.5rem;--bs-popover-arrow-border: var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:rgba(0,0,0,0);border-style:solid;border-width:0}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width)*.5) 0}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{border-width:calc(var(--bs-popover-arrow-width)*.5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width)*.5) 0}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{border-width:0 calc(var(--bs-popover-arrow-width)*.5) var(--bs-popover-arrow-height)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-0.5*var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{border-width:calc(var(--bs-popover-arrow-width)*.5) 0 calc(var(--bs-popover-arrow-width)*.5) var(--bs-popover-arrow-height)}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y;-webkit-touch-action:pan-y;-moz-touch-action:pan-y;-ms-touch-action:pan-y;-o-touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:center;-webkit-justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;-webkit-flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid rgba(0,0,0,0);border-bottom:10px solid rgba(0,0,0,0);opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme=dark] .carousel .carousel-control-prev-icon,[data-bs-theme=dark] .carousel .carousel-control-next-icon,[data-bs-theme=dark].carousel .carousel-control-prev-icon,[data-bs-theme=dark].carousel .carousel-control-next-icon{filter:invert(1) grayscale(100)}[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target],[data-bs-theme=dark].carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme=dark] .carousel .carousel-caption,[data-bs-theme=dark].carousel .carousel-caption{color:#000}.spinner-grow,.spinner-border{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{--bs-spinner-width: 2rem;--bs-spinner-height: 2rem;--bs-spinner-vertical-align: -0.125em;--bs-spinner-border-width: 0.25em;--bs-spinner-animation-speed: 0.75s;--bs-spinner-animation-name: spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:rgba(0,0,0,0)}.spinner-border-sm{--bs-spinner-width: 1rem;--bs-spinner-height: 1rem;--bs-spinner-border-width: 0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width: 2rem;--bs-spinner-height: 2rem;--bs-spinner-vertical-align: -0.125em;--bs-spinner-animation-speed: 0.75s;--bs-spinner-animation-name: spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width: 1rem;--bs-spinner-height: 1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed: 1.5s}}.offcanvas,.offcanvas-xxl,.offcanvas-xl,.offcanvas-lg,.offcanvas-md,.offcanvas-sm{--bs-offcanvas-zindex: 1045;--bs-offcanvas-width: 400px;--bs-offcanvas-height: 30vh;--bs-offcanvas-padding-x: 1rem;--bs-offcanvas-padding-y: 1rem;--bs-offcanvas-color: #373a3c;--bs-offcanvas-bg: #fff;--bs-offcanvas-border-width: 1px;--bs-offcanvas-border-color: rgba(0, 0, 0, 0.175);--bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-offcanvas-transition: transform 0.3s ease-in-out;--bs-offcanvas-title-line-height: 1.5}@media(max-width: 575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 575.98px)and (prefers-reduced-motion: reduce){.offcanvas-sm{transition:none}}@media(max-width: 575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.showing,.offcanvas-sm.show:not(.hiding){transform:none}.offcanvas-sm.showing,.offcanvas-sm.hiding,.offcanvas-sm.show{visibility:visible}}@media(min-width: 576px){.offcanvas-sm{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 767.98px)and (prefers-reduced-motion: reduce){.offcanvas-md{transition:none}}@media(max-width: 767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.showing,.offcanvas-md.show:not(.hiding){transform:none}.offcanvas-md.showing,.offcanvas-md.hiding,.offcanvas-md.show{visibility:visible}}@media(min-width: 768px){.offcanvas-md{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 991.98px)and (prefers-reduced-motion: reduce){.offcanvas-lg{transition:none}}@media(max-width: 991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.showing,.offcanvas-lg.show:not(.hiding){transform:none}.offcanvas-lg.showing,.offcanvas-lg.hiding,.offcanvas-lg.show{visibility:visible}}@media(min-width: 992px){.offcanvas-lg{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 1199.98px)and (prefers-reduced-motion: reduce){.offcanvas-xl{transition:none}}@media(max-width: 1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.showing,.offcanvas-xl.show:not(.hiding){transform:none}.offcanvas-xl.showing,.offcanvas-xl.hiding,.offcanvas-xl.show{visibility:visible}}@media(min-width: 1200px){.offcanvas-xl{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 1399.98px)and (prefers-reduced-motion: reduce){.offcanvas-xxl{transition:none}}@media(max-width: 1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.showing,.offcanvas-xxl.show:not(.hiding){transform:none}.offcanvas-xxl.showing,.offcanvas-xxl.hiding,.offcanvas-xxl.show{visibility:visible}}@media(min-width: 1400px){.offcanvas-xxl{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.showing,.offcanvas.show:not(.hiding){transform:none}.offcanvas.showing,.offcanvas.hiding,.offcanvas.show{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y)*.5) calc(var(--bs-offcanvas-padding-x)*.5);margin-top:calc(-0.5*var(--bs-offcanvas-padding-y));margin-right:calc(-0.5*var(--bs-offcanvas-padding-x));margin-bottom:calc(-0.5*var(--bs-offcanvas-padding-y))}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);-webkit-mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);mask-size:200% 100%;-webkit-mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{mask-position:-200% 0%;-webkit-mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-default{color:#fff !important;background-color:RGBA(var(--bs-default-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-primary{color:#fff !important;background-color:RGBA(var(--bs-primary-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-secondary{color:#fff !important;background-color:RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-success{color:#fff !important;background-color:RGBA(var(--bs-success-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-info{color:#fff !important;background-color:RGBA(var(--bs-info-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-warning{color:#fff !important;background-color:RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-danger{color:#fff !important;background-color:RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-light{color:#000 !important;background-color:RGBA(var(--bs-light-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-dark{color:#fff !important;background-color:RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity, 1)) !important}.link-default{color:RGBA(var(--bs-default-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-default-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-default:hover,.link-default:focus{color:RGBA(44, 46, 48, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(44, 46, 48, var(--bs-link-underline-opacity, 1)) !important}.link-primary{color:RGBA(var(--bs-primary-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-primary:hover,.link-primary:focus{color:RGBA(31, 102, 182, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(31, 102, 182, var(--bs-link-underline-opacity, 1)) !important}.link-secondary{color:RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-secondary:hover,.link-secondary:focus{color:RGBA(44, 46, 48, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(44, 46, 48, var(--bs-link-underline-opacity, 1)) !important}.link-success{color:RGBA(var(--bs-success-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-success:hover,.link-success:focus{color:RGBA(50, 146, 19, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(50, 146, 19, var(--bs-link-underline-opacity, 1)) !important}.link-info{color:RGBA(var(--bs-info-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-info:hover,.link-info:focus{color:RGBA(122, 67, 150, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(122, 67, 150, var(--bs-link-underline-opacity, 1)) !important}.link-warning{color:RGBA(var(--bs-warning-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-warning:hover,.link-warning:focus{color:RGBA(204, 94, 19, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(204, 94, 19, var(--bs-link-underline-opacity, 1)) !important}.link-danger{color:RGBA(var(--bs-danger-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-danger:hover,.link-danger:focus{color:RGBA(204, 0, 46, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(204, 0, 46, var(--bs-link-underline-opacity, 1)) !important}.link-light{color:RGBA(var(--bs-light-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-light:hover,.link-light:focus{color:RGBA(249, 250, 251, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(249, 250, 251, var(--bs-link-underline-opacity, 1)) !important}.link-dark{color:RGBA(var(--bs-dark-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-dark:hover,.link-dark:focus{color:RGBA(44, 46, 48, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(44, 46, 48, var(--bs-link-underline-opacity, 1)) !important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-body-emphasis:hover,.link-body-emphasis:focus{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 0.75)) !important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-align-items:center;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5));text-underline-offset:.25em;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;-webkit-flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media(prefers-reduced-motion: reduce){.icon-link>.bi{transition:none}}.icon-link-hover:hover>.bi,.icon-link-hover:focus-visible>.bi{transform:var(--bs-icon-link-transform, translate3d(0.25em, 0, 0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: 75%}.ratio-16x9{--bs-aspect-ratio: 56.25%}.ratio-21x9{--bs-aspect-ratio: 42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}.sticky-bottom{position:sticky;bottom:0;z-index:1020}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;align-items:center;-webkit-align-items:center;align-self:stretch;-webkit-align-self:stretch}.vstack{display:flex;display:-webkit-flex;flex:1 1 auto;-webkit-flex:1 1 auto;flex-direction:column;-webkit-flex-direction:column;align-self:stretch;-webkit-align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.visually-hidden:not(caption),.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption){position:absolute !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;-webkit-align-self:stretch;width:1px;min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.object-fit-contain{object-fit:contain !important}.object-fit-cover{object-fit:cover !important}.object-fit-fill{object-fit:fill !important}.object-fit-scale{object-fit:scale-down !important}.object-fit-none{object-fit:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.overflow-x-auto{overflow-x:auto !important}.overflow-x-hidden{overflow-x:hidden !important}.overflow-x-visible{overflow-x:visible !important}.overflow-x-scroll{overflow-x:scroll !important}.overflow-y-auto{overflow-y:auto !important}.overflow-y-hidden{overflow-y:hidden !important}.overflow-y-visible{overflow-y:visible !important}.overflow-y-scroll{overflow-y:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-inline-grid{display:inline-grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.focus-ring-default{--bs-focus-ring-color: rgba(var(--bs-default-rgb), var(--bs-focus-ring-opacity))}.focus-ring-primary{--bs-focus-ring-color: rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color: rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color: rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color: rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color: rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color: rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color: rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color: rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-0{border:0 !important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-top-0{border-top:0 !important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-start-0{border-left:0 !important}.border-default{--bs-border-opacity: 1;border-color:rgba(var(--bs-default-rgb), var(--bs-border-opacity)) !important}.border-primary{--bs-border-opacity: 1;border-color:rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important}.border-secondary{--bs-border-opacity: 1;border-color:rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important}.border-success{--bs-border-opacity: 1;border-color:rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important}.border-info{--bs-border-opacity: 1;border-color:rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important}.border-warning{--bs-border-opacity: 1;border-color:rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important}.border-danger{--bs-border-opacity: 1;border-color:rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important}.border-light{--bs-border-opacity: 1;border-color:rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important}.border-dark{--bs-border-opacity: 1;border-color:rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important}.border-black{--bs-border-opacity: 1;border-color:rgba(var(--bs-black-rgb), var(--bs-border-opacity)) !important}.border-white{--bs-border-opacity: 1;border-color:rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle) !important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle) !important}.border-success-subtle{border-color:var(--bs-success-border-subtle) !important}.border-info-subtle{border-color:var(--bs-info-border-subtle) !important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle) !important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle) !important}.border-light-subtle{border-color:var(--bs-light-border-subtle) !important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle) !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.border-opacity-10{--bs-border-opacity: 0.1}.border-opacity-25{--bs-border-opacity: 0.25}.border-opacity-50{--bs-border-opacity: 0.5}.border-opacity-75{--bs-border-opacity: 0.75}.border-opacity-100{--bs-border-opacity: 1}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.row-gap-0{row-gap:0 !important}.row-gap-1{row-gap:.25rem !important}.row-gap-2{row-gap:.5rem !important}.row-gap-3{row-gap:1rem !important}.row-gap-4{row-gap:1.5rem !important}.row-gap-5{row-gap:3rem !important}.column-gap-0{column-gap:0 !important}.column-gap-1{column-gap:.25rem !important}.column-gap-2{column-gap:.5rem !important}.column-gap-3{column-gap:1rem !important}.column-gap-4{column-gap:1.5rem !important}.column-gap-5{column-gap:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.325rem + 0.9vw) !important}.fs-2{font-size:calc(1.29rem + 0.48vw) !important}.fs-3{font-size:calc(1.27rem + 0.24vw) !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1.1rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-lighter{font-weight:lighter !important}.fw-light{font-weight:300 !important}.fw-normal{font-weight:400 !important}.fw-medium{font-weight:500 !important}.fw-semibold{font-weight:600 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-default{--bs-text-opacity: 1;color:rgba(var(--bs-default-rgb), var(--bs-text-opacity)) !important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:var(--bs-secondary-color) !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,.5) !important}.text-body-secondary{--bs-text-opacity: 1;color:var(--bs-secondary-color) !important}.text-body-tertiary{--bs-text-opacity: 1;color:var(--bs-tertiary-color) !important}.text-body-emphasis{--bs-text-opacity: 1;color:var(--bs-emphasis-color) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: 0.25}.text-opacity-50{--bs-text-opacity: 0.5}.text-opacity-75{--bs-text-opacity: 0.75}.text-opacity-100{--bs-text-opacity: 1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis) !important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis) !important}.text-success-emphasis{color:var(--bs-success-text-emphasis) !important}.text-info-emphasis{color:var(--bs-info-text-emphasis) !important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis) !important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis) !important}.text-light-emphasis{color:var(--bs-light-text-emphasis) !important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis) !important}.link-opacity-10{--bs-link-opacity: 0.1}.link-opacity-10-hover:hover{--bs-link-opacity: 0.1}.link-opacity-25{--bs-link-opacity: 0.25}.link-opacity-25-hover:hover{--bs-link-opacity: 0.25}.link-opacity-50{--bs-link-opacity: 0.5}.link-opacity-50-hover:hover{--bs-link-opacity: 0.5}.link-opacity-75{--bs-link-opacity: 0.75}.link-opacity-75-hover:hover{--bs-link-opacity: 0.75}.link-opacity-100{--bs-link-opacity: 1}.link-opacity-100-hover:hover{--bs-link-opacity: 1}.link-offset-1{text-underline-offset:.125em !important}.link-offset-1-hover:hover{text-underline-offset:.125em !important}.link-offset-2{text-underline-offset:.25em !important}.link-offset-2-hover:hover{text-underline-offset:.25em !important}.link-offset-3{text-underline-offset:.375em !important}.link-offset-3-hover:hover{text-underline-offset:.375em !important}.link-underline-default{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-default-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-primary{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-secondary{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-success{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-info{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-warning{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-danger{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-light{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-dark{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important}.link-underline{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-underline-opacity-0{--bs-link-underline-opacity: 0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity: 0}.link-underline-opacity-10{--bs-link-underline-opacity: 0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity: 0.1}.link-underline-opacity-25{--bs-link-underline-opacity: 0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity: 0.25}.link-underline-opacity-50{--bs-link-underline-opacity: 0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity: 0.5}.link-underline-opacity-75{--bs-link-underline-opacity: 0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity: 0.75}.link-underline-opacity-100{--bs-link-underline-opacity: 1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity: 1}.bg-default{--bs-bg-opacity: 1;background-color:rgba(var(--bs-default-rgb), var(--bs-bg-opacity)) !important}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:rgba(0,0,0,0) !important}.bg-body-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity)) !important}.bg-body-tertiary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity)) !important}.bg-opacity-10{--bs-bg-opacity: 0.1}.bg-opacity-25{--bs-bg-opacity: 0.25}.bg-opacity-50{--bs-bg-opacity: 0.5}.bg-opacity-75{--bs-bg-opacity: 0.75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle) !important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle) !important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle) !important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle) !important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle) !important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle) !important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle) !important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle) !important}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:var(--bs-border-radius) !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:var(--bs-border-radius-sm) !important}.rounded-2{border-radius:var(--bs-border-radius) !important}.rounded-3{border-radius:var(--bs-border-radius-lg) !important}.rounded-4{border-radius:var(--bs-border-radius-xl) !important}.rounded-5{border-radius:var(--bs-border-radius-xxl) !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:var(--bs-border-radius-pill) !important}.rounded-top{border-top-left-radius:var(--bs-border-radius) !important;border-top-right-radius:var(--bs-border-radius) !important}.rounded-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm) !important;border-top-right-radius:var(--bs-border-radius-sm) !important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius) !important;border-top-right-radius:var(--bs-border-radius) !important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg) !important;border-top-right-radius:var(--bs-border-radius-lg) !important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl) !important;border-top-right-radius:var(--bs-border-radius-xl) !important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl) !important;border-top-right-radius:var(--bs-border-radius-xxl) !important}.rounded-top-circle{border-top-left-radius:50% !important;border-top-right-radius:50% !important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill) !important;border-top-right-radius:var(--bs-border-radius-pill) !important}.rounded-end{border-top-right-radius:var(--bs-border-radius) !important;border-bottom-right-radius:var(--bs-border-radius) !important}.rounded-end-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm) !important;border-bottom-right-radius:var(--bs-border-radius-sm) !important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius) !important;border-bottom-right-radius:var(--bs-border-radius) !important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg) !important;border-bottom-right-radius:var(--bs-border-radius-lg) !important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl) !important;border-bottom-right-radius:var(--bs-border-radius-xl) !important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl) !important;border-bottom-right-radius:var(--bs-border-radius-xxl) !important}.rounded-end-circle{border-top-right-radius:50% !important;border-bottom-right-radius:50% !important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill) !important;border-bottom-right-radius:var(--bs-border-radius-pill) !important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius) !important;border-bottom-left-radius:var(--bs-border-radius) !important}.rounded-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm) !important;border-bottom-left-radius:var(--bs-border-radius-sm) !important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius) !important;border-bottom-left-radius:var(--bs-border-radius) !important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg) !important;border-bottom-left-radius:var(--bs-border-radius-lg) !important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl) !important;border-bottom-left-radius:var(--bs-border-radius-xl) !important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl) !important;border-bottom-left-radius:var(--bs-border-radius-xxl) !important}.rounded-bottom-circle{border-bottom-right-radius:50% !important;border-bottom-left-radius:50% !important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill) !important;border-bottom-left-radius:var(--bs-border-radius-pill) !important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius) !important;border-top-left-radius:var(--bs-border-radius) !important}.rounded-start-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm) !important;border-top-left-radius:var(--bs-border-radius-sm) !important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius) !important;border-top-left-radius:var(--bs-border-radius) !important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg) !important;border-top-left-radius:var(--bs-border-radius-lg) !important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl) !important;border-top-left-radius:var(--bs-border-radius-xl) !important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl) !important;border-top-left-radius:var(--bs-border-radius-xxl) !important}.rounded-start-circle{border-bottom-left-radius:50% !important;border-top-left-radius:50% !important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill) !important;border-top-left-radius:var(--bs-border-radius-pill) !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}.z-n1{z-index:-1 !important}.z-0{z-index:0 !important}.z-1{z-index:1 !important}.z-2{z-index:2 !important}.z-3{z-index:3 !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.object-fit-sm-contain{object-fit:contain !important}.object-fit-sm-cover{object-fit:cover !important}.object-fit-sm-fill{object-fit:fill !important}.object-fit-sm-scale{object-fit:scale-down !important}.object-fit-sm-none{object-fit:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-inline-grid{display:inline-grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.row-gap-sm-0{row-gap:0 !important}.row-gap-sm-1{row-gap:.25rem !important}.row-gap-sm-2{row-gap:.5rem !important}.row-gap-sm-3{row-gap:1rem !important}.row-gap-sm-4{row-gap:1.5rem !important}.row-gap-sm-5{row-gap:3rem !important}.column-gap-sm-0{column-gap:0 !important}.column-gap-sm-1{column-gap:.25rem !important}.column-gap-sm-2{column-gap:.5rem !important}.column-gap-sm-3{column-gap:1rem !important}.column-gap-sm-4{column-gap:1.5rem !important}.column-gap-sm-5{column-gap:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.object-fit-md-contain{object-fit:contain !important}.object-fit-md-cover{object-fit:cover !important}.object-fit-md-fill{object-fit:fill !important}.object-fit-md-scale{object-fit:scale-down !important}.object-fit-md-none{object-fit:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-inline-grid{display:inline-grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.row-gap-md-0{row-gap:0 !important}.row-gap-md-1{row-gap:.25rem !important}.row-gap-md-2{row-gap:.5rem !important}.row-gap-md-3{row-gap:1rem !important}.row-gap-md-4{row-gap:1.5rem !important}.row-gap-md-5{row-gap:3rem !important}.column-gap-md-0{column-gap:0 !important}.column-gap-md-1{column-gap:.25rem !important}.column-gap-md-2{column-gap:.5rem !important}.column-gap-md-3{column-gap:1rem !important}.column-gap-md-4{column-gap:1.5rem !important}.column-gap-md-5{column-gap:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media(min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.object-fit-lg-contain{object-fit:contain !important}.object-fit-lg-cover{object-fit:cover !important}.object-fit-lg-fill{object-fit:fill !important}.object-fit-lg-scale{object-fit:scale-down !important}.object-fit-lg-none{object-fit:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-inline-grid{display:inline-grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.row-gap-lg-0{row-gap:0 !important}.row-gap-lg-1{row-gap:.25rem !important}.row-gap-lg-2{row-gap:.5rem !important}.row-gap-lg-3{row-gap:1rem !important}.row-gap-lg-4{row-gap:1.5rem !important}.row-gap-lg-5{row-gap:3rem !important}.column-gap-lg-0{column-gap:0 !important}.column-gap-lg-1{column-gap:.25rem !important}.column-gap-lg-2{column-gap:.5rem !important}.column-gap-lg-3{column-gap:1rem !important}.column-gap-lg-4{column-gap:1.5rem !important}.column-gap-lg-5{column-gap:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media(min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.object-fit-xl-contain{object-fit:contain !important}.object-fit-xl-cover{object-fit:cover !important}.object-fit-xl-fill{object-fit:fill !important}.object-fit-xl-scale{object-fit:scale-down !important}.object-fit-xl-none{object-fit:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-inline-grid{display:inline-grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.row-gap-xl-0{row-gap:0 !important}.row-gap-xl-1{row-gap:.25rem !important}.row-gap-xl-2{row-gap:.5rem !important}.row-gap-xl-3{row-gap:1rem !important}.row-gap-xl-4{row-gap:1.5rem !important}.row-gap-xl-5{row-gap:3rem !important}.column-gap-xl-0{column-gap:0 !important}.column-gap-xl-1{column-gap:.25rem !important}.column-gap-xl-2{column-gap:.5rem !important}.column-gap-xl-3{column-gap:1rem !important}.column-gap-xl-4{column-gap:1.5rem !important}.column-gap-xl-5{column-gap:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media(min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.object-fit-xxl-contain{object-fit:contain !important}.object-fit-xxl-cover{object-fit:cover !important}.object-fit-xxl-fill{object-fit:fill !important}.object-fit-xxl-scale{object-fit:scale-down !important}.object-fit-xxl-none{object-fit:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-inline-grid{display:inline-grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.row-gap-xxl-0{row-gap:0 !important}.row-gap-xxl-1{row-gap:.25rem !important}.row-gap-xxl-2{row-gap:.5rem !important}.row-gap-xxl-3{row-gap:1rem !important}.row-gap-xxl-4{row-gap:1.5rem !important}.row-gap-xxl-5{row-gap:3rem !important}.column-gap-xxl-0{column-gap:0 !important}.column-gap-xxl-1{column-gap:.25rem !important}.column-gap-xxl-2{column-gap:.5rem !important}.column-gap-xxl-3{column-gap:1rem !important}.column-gap-xxl-4{column-gap:1.5rem !important}.column-gap-xxl-5{column-gap:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#fff}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#fff}.bg-warning{color:#fff}.bg-danger{color:#fff}.bg-light{color:#000}.bg-dark{color:#fff}@media(min-width: 1200px){.fs-1{font-size:2rem !important}.fs-2{font-size:1.65rem !important}.fs-3{font-size:1.45rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-inline-grid{display:inline-grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}:root{--bslib-spacer: 1rem;--bslib-mb-spacer: var(--bslib-spacer, 1rem)}.bslib-mb-spacing{margin-bottom:var(--bslib-mb-spacer)}.bslib-gap-spacing{gap:var(--bslib-mb-spacer)}.bslib-gap-spacing>.bslib-mb-spacing,.bslib-gap-spacing>.form-group,.bslib-gap-spacing>p,.bslib-gap-spacing>pre{margin-bottom:0}.html-fill-container>.html-fill-item.bslib-mb-spacing{margin-bottom:0}.tab-content>.tab-pane.html-fill-container{display:none}.tab-content>.active.html-fill-container{display:flex}.tab-content.html-fill-container{padding:0}.bg-blue{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #2780e3;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-indigo{--bslib-color-fg: #6610f2;color:var(--bslib-color-fg)}.bg-purple{--bslib-color-bg: #613d7c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-purple{--bslib-color-fg: #613d7c;color:var(--bslib-color-fg)}.bg-pink{--bslib-color-bg: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #ff0039;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #f0ad4e;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-orange{--bslib-color-fg: #f0ad4e;color:var(--bslib-color-fg)}.bg-yellow{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #ff7518;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #3fb618;color:var(--bslib-color-fg)}.bg-teal{--bslib-color-bg: #20c997;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-teal{--bslib-color-fg: #20c997;color:var(--bslib-color-fg)}.bg-cyan{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #9954bb;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #373a3c}.bg-default{--bslib-color-bg: #373a3c;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #2780e3}.bg-primary{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #373a3c}.bg-secondary{--bslib-color-bg: #373a3c;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #3fb618}.bg-success{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #9954bb}.bg-info{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #ff7518}.bg-warning{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #ff0039}.bg-danger{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #f8f9fa}.bg-light{--bslib-color-bg: #f8f9fa;--bslib-color-fg: #000}.text-dark{--bslib-color-fg: #373a3c}.bg-dark{--bslib-color-bg: #373a3c;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4053e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4053e9;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3e65ba;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3e65ba;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7466c0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7466c0;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d4d9f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #7d4d9f;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #7792a7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #7792a7;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #7d7c92;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #7d7c92;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #319692;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #319692;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #249dc5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #249dc5;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #556ed3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #556ed3;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4d3dec;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4d3dec;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--bslib-color-bg: #6422c3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #6422c3;color:#fff}.bg-gradient-indigo-pink{--bslib-color-fg: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #a30aa8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a30aa8;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9d4fb0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9d4fb0;color:#fff}.bg-gradient-indigo-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a3389b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a3389b;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #56529b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #56529b;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--bslib-color-bg: #4a5ace;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #4a5ace;color:#fff}.bg-gradient-indigo-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #7a2bdc;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #7a2bdc;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4a58a5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4a58a5;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #632bab;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #632bab;color:#fff}.bg-gradient-purple-pink{--bslib-color-fg: #fff;--bslib-color-bg: #973d82;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #973d82;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #a02561;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a02561;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9a6a6a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9a6a6a;color:#fff}.bg-gradient-purple-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a05354;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a05354;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #536d54;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #536d54;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--bslib-color-bg: #477587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #477587;color:#fff}.bg-gradient-purple-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #774695;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #774695;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #9b58af;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #9b58af;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b23e86;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b23e86;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #f1256b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f1256b;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #eb6a73;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #eb6a73;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f1545e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f1545e;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #a46e5e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a46e5e;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #c8479f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #c8479f;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a9337d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a9337d;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c20683;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c20683;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c01854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c01854;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f6195a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f6195a;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f94541;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f94541;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ff2f2c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #ff2f2c;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #b2492c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b2492c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6505f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6505f;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d6226d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d6226d;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a09b8a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a09b8a;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b96e90;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b96e90;color:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b78060;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b78060;color:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ed8167;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ed8167;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f66846;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f66846;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #000;--bslib-color-bg: #f69738;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f69738;color:#000}.bg-gradient-orange-green{--bslib-color-fg: #000;--bslib-color-bg: #a9b138;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a9b138;color:#000}.bg-gradient-orange-teal{--bslib-color-fg: #000;--bslib-color-bg: #9db86b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9db86b;color:#000}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #cd897a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #cd897a;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a97969;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a97969;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c24d6f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c24d6f;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c05f40;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c05f40;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f65f46;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f65f46;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ff4625;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #ff4625;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #000;--bslib-color-bg: #f98b2e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f98b2e;color:#000}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #b28f18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b28f18;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6974b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6974b;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d66859;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d66859;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #35a069;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #35a069;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4f746f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4f746f;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d8640;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #4d8640;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #838646;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #838646;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #8c6d25;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #8c6d25;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #000;--bslib-color-bg: #86b22e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #86b22e;color:#000}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #8c9c18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #8c9c18;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #000;--bslib-color-bg: #33be4b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #33be4b;color:#000}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #638f59;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #638f59;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #23acb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #23acb5;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #3c7fbb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #3c7fbb;color:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3a918c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3a918c;color:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #797971;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #797971;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #000;--bslib-color-bg: #73be7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #73be7a;color:#000}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #79a764;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #79a764;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #000;--bslib-color-bg: #2cc164;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #2cc164;color:#000}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #509aa5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #509aa5;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #6b66cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #6b66cb;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #8539d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #8539d1;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #834ba2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #834ba2;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #b94ba8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #b94ba8;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #c23287;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #c23287;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #bc788f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #bc788f;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #c2617a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #c2617a;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #757b7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #757b7a;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #6983ad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #6983ad;color:#fff}.tab-content>.tab-pane.html-fill-container{display:none}.tab-content>.active.html-fill-container{display:flex}.tab-content.html-fill-container{padding:0}:root{--bslib-spacer: 1rem;--bslib-mb-spacer: var(--bslib-spacer, 1rem)}.bslib-mb-spacing{margin-bottom:var(--bslib-mb-spacer)}.bslib-gap-spacing{gap:var(--bslib-mb-spacer)}.bslib-gap-spacing>.bslib-mb-spacing,.bslib-gap-spacing>.form-group,.bslib-gap-spacing>p,.bslib-gap-spacing>pre{margin-bottom:0}.html-fill-container>.html-fill-item.bslib-mb-spacing{margin-bottom:0}.bg-blue{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #2780e3;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-indigo{--bslib-color-fg: #6610f2;color:var(--bslib-color-fg)}.bg-purple{--bslib-color-bg: #613d7c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-purple{--bslib-color-fg: #613d7c;color:var(--bslib-color-fg)}.bg-pink{--bslib-color-bg: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #ff0039;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #f0ad4e;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-orange{--bslib-color-fg: #f0ad4e;color:var(--bslib-color-fg)}.bg-yellow{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #ff7518;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #3fb618;color:var(--bslib-color-fg)}.bg-teal{--bslib-color-bg: #20c997;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-teal{--bslib-color-fg: #20c997;color:var(--bslib-color-fg)}.bg-cyan{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #9954bb;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #373a3c}.bg-default{--bslib-color-bg: #373a3c;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #2780e3}.bg-primary{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #373a3c}.bg-secondary{--bslib-color-bg: #373a3c;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #3fb618}.bg-success{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #9954bb}.bg-info{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #ff7518}.bg-warning{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #ff0039}.bg-danger{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #f8f9fa}.bg-light{--bslib-color-bg: #f8f9fa;--bslib-color-fg: #000}.text-dark{--bslib-color-fg: #373a3c}.bg-dark{--bslib-color-bg: #373a3c;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4053e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4053e9;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3e65ba;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3e65ba;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7466c0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7466c0;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d4d9f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #7d4d9f;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #7792a7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #7792a7;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #7d7c92;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #7d7c92;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #319692;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #319692;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #249dc5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #249dc5;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #556ed3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #556ed3;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4d3dec;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4d3dec;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--bslib-color-bg: #6422c3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #6422c3;color:#fff}.bg-gradient-indigo-pink{--bslib-color-fg: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #a30aa8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a30aa8;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9d4fb0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9d4fb0;color:#fff}.bg-gradient-indigo-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a3389b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a3389b;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #56529b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #56529b;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--bslib-color-bg: #4a5ace;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #4a5ace;color:#fff}.bg-gradient-indigo-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #7a2bdc;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #7a2bdc;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4a58a5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4a58a5;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #632bab;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #632bab;color:#fff}.bg-gradient-purple-pink{--bslib-color-fg: #fff;--bslib-color-bg: #973d82;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #973d82;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #a02561;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a02561;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9a6a6a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9a6a6a;color:#fff}.bg-gradient-purple-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a05354;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a05354;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #536d54;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #536d54;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--bslib-color-bg: #477587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #477587;color:#fff}.bg-gradient-purple-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #774695;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #774695;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #9b58af;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #9b58af;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b23e86;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b23e86;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #f1256b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f1256b;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #eb6a73;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #eb6a73;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f1545e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f1545e;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #a46e5e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a46e5e;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #c8479f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #c8479f;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a9337d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a9337d;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c20683;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c20683;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c01854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c01854;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f6195a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f6195a;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f94541;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f94541;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ff2f2c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #ff2f2c;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #b2492c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b2492c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6505f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6505f;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d6226d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d6226d;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a09b8a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a09b8a;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b96e90;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b96e90;color:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b78060;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b78060;color:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ed8167;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ed8167;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f66846;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f66846;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #000;--bslib-color-bg: #f69738;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f69738;color:#000}.bg-gradient-orange-green{--bslib-color-fg: #000;--bslib-color-bg: #a9b138;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a9b138;color:#000}.bg-gradient-orange-teal{--bslib-color-fg: #000;--bslib-color-bg: #9db86b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9db86b;color:#000}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #cd897a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #cd897a;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a97969;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a97969;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c24d6f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c24d6f;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c05f40;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c05f40;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f65f46;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f65f46;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ff4625;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #ff4625;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #000;--bslib-color-bg: #f98b2e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f98b2e;color:#000}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #b28f18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b28f18;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6974b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6974b;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d66859;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d66859;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #35a069;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #35a069;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4f746f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4f746f;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d8640;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #4d8640;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #838646;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #838646;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #8c6d25;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #8c6d25;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #000;--bslib-color-bg: #86b22e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #86b22e;color:#000}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #8c9c18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #8c9c18;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #000;--bslib-color-bg: #33be4b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #33be4b;color:#000}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #638f59;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #638f59;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #23acb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #23acb5;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #3c7fbb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #3c7fbb;color:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3a918c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3a918c;color:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #797971;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #797971;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #000;--bslib-color-bg: #73be7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #73be7a;color:#000}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #79a764;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #79a764;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #000;--bslib-color-bg: #2cc164;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #2cc164;color:#000}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #509aa5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #509aa5;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #6b66cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #6b66cb;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #8539d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #8539d1;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #834ba2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #834ba2;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #b94ba8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #b94ba8;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #c23287;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #c23287;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #bc788f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #bc788f;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #c2617a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #c2617a;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #757b7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #757b7a;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #6983ad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #6983ad;color:#fff}:root{--bslib-value-box-shadow: none;--bslib-value-box-border-width-auto-yes: var(--bslib-value-box-border-width-baseline);--bslib-value-box-border-width-auto-no: 0;--bslib-value-box-border-width-baseline: 1px}.bslib-value-box{border-width:var(--bslib-value-box-border-width-auto-no, var(--bslib-value-box-border-width-baseline));container-name:bslib-value-box;container-type:inline-size}.bslib-value-box.card{box-shadow:var(--bslib-value-box-shadow)}.bslib-value-box.border-auto{border-width:var(--bslib-value-box-border-width-auto-yes, var(--bslib-value-box-border-width-baseline))}.bslib-value-box.default{--bslib-value-box-bg-default: var(--bs-card-bg, #fff);--bslib-value-box-border-color-default: var(--bs-card-border-color, rgba(0, 0, 0, 0.175));color:var(--bslib-value-box-color);background-color:var(--bslib-value-box-bg, var(--bslib-value-box-bg-default));border-color:var(--bslib-value-box-border-color, var(--bslib-value-box-border-color-default))}.bslib-value-box .value-box-grid{display:grid;grid-template-areas:"left right";align-items:center;overflow:hidden}.bslib-value-box .value-box-showcase{height:100%;max-height:var(---bslib-value-box-showcase-max-h, 100%)}.bslib-value-box .value-box-showcase,.bslib-value-box .value-box-showcase>.html-fill-item{width:100%}.bslib-value-box[data-full-screen=true] .value-box-showcase{max-height:var(---bslib-value-box-showcase-max-h-fs, 100%)}@media screen and (min-width: 575.98px){@container bslib-value-box (max-width: 300px){.bslib-value-box:not(.showcase-bottom) .value-box-grid{grid-template-columns:1fr !important;grid-template-rows:auto auto;grid-template-areas:"top" "bottom"}.bslib-value-box:not(.showcase-bottom) .value-box-grid .value-box-showcase{grid-area:top !important}.bslib-value-box:not(.showcase-bottom) .value-box-grid .value-box-area{grid-area:bottom !important;justify-content:end}}}.bslib-value-box .value-box-area{justify-content:center;padding:1.5rem 1rem;font-size:.9rem;font-weight:500}.bslib-value-box .value-box-area *{margin-bottom:0;margin-top:0}.bslib-value-box .value-box-title{font-size:1rem;margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2}.bslib-value-box .value-box-title:empty::after{content:" "}.bslib-value-box .value-box-value{font-size:calc(1.29rem + 0.48vw);margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2}@media(min-width: 1200px){.bslib-value-box .value-box-value{font-size:1.65rem}}.bslib-value-box .value-box-value:empty::after{content:" "}.bslib-value-box .value-box-showcase{align-items:center;justify-content:center;margin-top:auto;margin-bottom:auto;padding:1rem}.bslib-value-box .value-box-showcase .bi,.bslib-value-box .value-box-showcase .fa,.bslib-value-box .value-box-showcase .fab,.bslib-value-box .value-box-showcase .fas,.bslib-value-box .value-box-showcase .far{opacity:.85;min-width:50px;max-width:125%}.bslib-value-box .value-box-showcase .bi,.bslib-value-box .value-box-showcase .fa,.bslib-value-box .value-box-showcase .fab,.bslib-value-box .value-box-showcase .fas,.bslib-value-box .value-box-showcase .far{font-size:4rem}.bslib-value-box.showcase-top-right .value-box-grid{grid-template-columns:1fr var(---bslib-value-box-showcase-w, 50%)}.bslib-value-box.showcase-top-right .value-box-grid .value-box-showcase{grid-area:right;margin-left:auto;align-self:start;align-items:end;padding-left:0;padding-bottom:0}.bslib-value-box.showcase-top-right .value-box-grid .value-box-area{grid-area:left;align-self:end}.bslib-value-box.showcase-top-right[data-full-screen=true] .value-box-grid{grid-template-columns:auto var(---bslib-value-box-showcase-w-fs, 1fr)}.bslib-value-box.showcase-top-right[data-full-screen=true] .value-box-grid>div{align-self:center}.bslib-value-box.showcase-top-right:not([data-full-screen=true]) .value-box-showcase{margin-top:0}@container bslib-value-box (max-width: 300px){.bslib-value-box.showcase-top-right:not([data-full-screen=true]) .value-box-grid .value-box-showcase{padding-left:1rem}}.bslib-value-box.showcase-left-center .value-box-grid{grid-template-columns:var(---bslib-value-box-showcase-w, 30%) auto}.bslib-value-box.showcase-left-center[data-full-screen=true] .value-box-grid{grid-template-columns:var(---bslib-value-box-showcase-w-fs, 1fr) auto}.bslib-value-box.showcase-left-center:not([data-fill-screen=true]) .value-box-grid .value-box-showcase{grid-area:left}.bslib-value-box.showcase-left-center:not([data-fill-screen=true]) .value-box-grid .value-box-area{grid-area:right}.bslib-value-box.showcase-bottom .value-box-grid{grid-template-columns:1fr;grid-template-rows:1fr var(---bslib-value-box-showcase-h, auto);grid-template-areas:"top" "bottom";overflow:hidden}.bslib-value-box.showcase-bottom .value-box-grid .value-box-showcase{grid-area:bottom;padding:0;margin:0}.bslib-value-box.showcase-bottom .value-box-grid .value-box-area{grid-area:top}.bslib-value-box.showcase-bottom[data-full-screen=true] .value-box-grid{grid-template-rows:1fr var(---bslib-value-box-showcase-h-fs, 2fr)}.bslib-value-box.showcase-bottom[data-full-screen=true] .value-box-grid .value-box-showcase{padding:1rem}[data-bs-theme=dark] .bslib-value-box{--bslib-value-box-shadow: 0 0.5rem 1rem rgb(0 0 0 / 50%)}@media(min-width: 576px){.nav:not(.nav-hidden){display:flex !important;display:-webkit-flex !important}.nav:not(.nav-hidden):not(.nav-stacked):not(.flex-column){float:none !important}.nav:not(.nav-hidden):not(.nav-stacked):not(.flex-column)>.bslib-nav-spacer{margin-left:auto !important}.nav:not(.nav-hidden):not(.nav-stacked):not(.flex-column)>.form-inline{margin-top:auto;margin-bottom:auto}.nav:not(.nav-hidden).nav-stacked{flex-direction:column;-webkit-flex-direction:column;height:100%}.nav:not(.nav-hidden).nav-stacked>.bslib-nav-spacer{margin-top:auto !important}}.bslib-card{overflow:auto}.bslib-card .card-body+.card-body{padding-top:0}.bslib-card .card-body{overflow:auto}.bslib-card .card-body p{margin-top:0}.bslib-card .card-body p:last-child{margin-bottom:0}.bslib-card .card-body{max-height:var(--bslib-card-body-max-height, none)}.bslib-card[data-full-screen=true]>.card-body{max-height:var(--bslib-card-body-max-height-full-screen, none)}.bslib-card .card-header .form-group{margin-bottom:0}.bslib-card .card-header .selectize-control{margin-bottom:0}.bslib-card .card-header .selectize-control .item{margin-right:1.15rem}.bslib-card .card-footer{margin-top:auto}.bslib-card .bslib-navs-card-title{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center}.bslib-card .bslib-navs-card-title .nav{margin-left:auto}.bslib-card .bslib-sidebar-layout:not([data-bslib-sidebar-border=true]){border:none}.bslib-card .bslib-sidebar-layout:not([data-bslib-sidebar-border-radius=true]){border-top-left-radius:0;border-top-right-radius:0}[data-full-screen=true]{position:fixed;inset:3.5rem 1rem 1rem;height:auto !important;max-height:none !important;width:auto !important;z-index:1070}.bslib-full-screen-enter{display:none;position:absolute;bottom:var(--bslib-full-screen-enter-bottom, 0.2rem);right:var(--bslib-full-screen-enter-right, 0);top:var(--bslib-full-screen-enter-top);left:var(--bslib-full-screen-enter-left);color:var(--bslib-color-fg, var(--bs-card-color));background-color:var(--bslib-color-bg, var(--bs-card-bg, var(--bs-body-bg)));border:var(--bs-card-border-width) solid var(--bslib-color-fg, var(--bs-card-border-color));box-shadow:0 2px 4px rgba(0,0,0,.15);margin:.2rem .4rem;padding:.55rem !important;font-size:.8rem;cursor:pointer;opacity:.7;z-index:1070}.bslib-full-screen-enter:hover{opacity:1}.card[data-full-screen=false]:hover>*>.bslib-full-screen-enter{display:block}.bslib-has-full-screen .card:hover>*>.bslib-full-screen-enter{display:none}@media(max-width: 575.98px){.bslib-full-screen-enter{display:none !important}}.bslib-full-screen-exit{position:relative;top:1.35rem;font-size:.9rem;cursor:pointer;text-decoration:none;display:flex;float:right;margin-right:2.15rem;align-items:center;color:rgba(var(--bs-body-bg-rgb), 0.8)}.bslib-full-screen-exit:hover{color:rgba(var(--bs-body-bg-rgb), 1)}.bslib-full-screen-exit svg{margin-left:.5rem;font-size:1.5rem}#bslib-full-screen-overlay{position:fixed;inset:0;background-color:rgba(var(--bs-body-color-rgb), 0.6);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);z-index:1069;animation:bslib-full-screen-overlay-enter 400ms cubic-bezier(0.6, 0.02, 0.65, 1) forwards}@keyframes bslib-full-screen-overlay-enter{0%{opacity:0}100%{opacity:1}}.bslib-grid{display:grid !important;gap:var(--bslib-spacer, 1rem);height:var(--bslib-grid-height)}.bslib-grid.grid{grid-template-columns:repeat(var(--bs-columns, 12), minmax(0, 1fr));grid-template-rows:unset;grid-auto-rows:var(--bslib-grid--row-heights);--bslib-grid--row-heights--xs: unset;--bslib-grid--row-heights--sm: unset;--bslib-grid--row-heights--md: unset;--bslib-grid--row-heights--lg: unset;--bslib-grid--row-heights--xl: unset;--bslib-grid--row-heights--xxl: unset}.bslib-grid.grid.bslib-grid--row-heights--xs{--bslib-grid--row-heights: var(--bslib-grid--row-heights--xs)}@media(min-width: 576px){.bslib-grid.grid.bslib-grid--row-heights--sm{--bslib-grid--row-heights: var(--bslib-grid--row-heights--sm)}}@media(min-width: 768px){.bslib-grid.grid.bslib-grid--row-heights--md{--bslib-grid--row-heights: var(--bslib-grid--row-heights--md)}}@media(min-width: 992px){.bslib-grid.grid.bslib-grid--row-heights--lg{--bslib-grid--row-heights: var(--bslib-grid--row-heights--lg)}}@media(min-width: 1200px){.bslib-grid.grid.bslib-grid--row-heights--xl{--bslib-grid--row-heights: var(--bslib-grid--row-heights--xl)}}@media(min-width: 1400px){.bslib-grid.grid.bslib-grid--row-heights--xxl{--bslib-grid--row-heights: var(--bslib-grid--row-heights--xxl)}}.bslib-grid>*>.shiny-input-container{width:100%}.bslib-grid-item{grid-column:auto/span 1}@media(max-width: 767.98px){.bslib-grid-item{grid-column:1/-1}}@media(max-width: 575.98px){.bslib-grid{grid-template-columns:1fr !important;height:var(--bslib-grid-height-mobile)}.bslib-grid.grid{height:unset !important;grid-auto-rows:var(--bslib-grid--row-heights--xs, auto)}}.accordion .accordion-header{font-size:calc(1.29rem + 0.48vw);margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2;color:var(--bs-heading-color);margin-bottom:0}@media(min-width: 1200px){.accordion .accordion-header{font-size:1.65rem}}.accordion .accordion-icon:not(:empty){margin-right:.75rem;display:flex}.accordion .accordion-button:not(.collapsed){box-shadow:none}.accordion .accordion-button:not(.collapsed):focus{box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.navbar+.container-fluid:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-sm:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-md:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-lg:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-xl:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-xxl:has(>.tab-content>.tab-pane.active.html-fill-container){padding-left:0;padding-right:0}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container{padding:var(--bslib-spacer, 1rem);gap:var(--bslib-spacer, 1rem)}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child){padding:0}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]){border-left:none;border-right:none;border-bottom:none}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]){border-radius:0}.navbar+div>.bslib-sidebar-layout{border-top:var(--bslib-sidebar-border)}html{height:100%}.bslib-page-fill{width:100%;height:100%;margin:0;padding:var(--bslib-spacer, 1rem);gap:var(--bslib-spacer, 1rem)}@media(max-width: 575.98px){.bslib-page-fill{height:var(--bslib-page-fill-mobile-height, auto)}}:root{--bslib-page-sidebar-title-bg: #f8f9fa;--bslib-page-sidebar-title-color: #000}.bslib-page-title{background-color:var(--bslib-page-sidebar-title-bg);color:var(--bslib-page-sidebar-title-color);font-size:1.25rem;font-weight:300;padding:var(--bslib-spacer, 1rem);padding-left:1.5rem;margin-bottom:0;border-bottom:1px solid #e1e1e2}.bslib-sidebar-layout{--bslib-sidebar-transition-duration: 500ms;--bslib-sidebar-transition-easing-x: cubic-bezier(0.8, 0.78, 0.22, 1.07);--bslib-sidebar-border: var(--bs-card-border-width, 1px) solid var(--bs-card-border-color, rgba(0, 0, 0, 0.175));--bslib-sidebar-border-radius: var(--bs-border-radius);--bslib-sidebar-vert-border: var(--bs-card-border-width, 1px) solid var(--bs-card-border-color, rgba(0, 0, 0, 0.175));--bslib-sidebar-bg: rgba(var(--bs-emphasis-color-rgb, 0, 0, 0), 0.05);--bslib-sidebar-fg: var(--bs-emphasis-color, black);--bslib-sidebar-main-fg: var(--bs-card-color, var(--bs-body-color));--bslib-sidebar-main-bg: var(--bs-card-bg, var(--bs-body-bg));--bslib-sidebar-toggle-bg: rgba(var(--bs-emphasis-color-rgb, 0, 0, 0), 0.1);--bslib-sidebar-padding: calc(var(--bslib-spacer) * 1.5);--bslib-sidebar-icon-size: var(--bslib-spacer, 1rem);--bslib-sidebar-icon-button-size: calc(var(--bslib-sidebar-icon-size, 1rem) * 2);--bslib-sidebar-padding-icon: calc(var(--bslib-sidebar-icon-button-size, 2rem) * 1.5);--bslib-collapse-toggle-border-radius: var(--bs-border-radius, 0.375rem);--bslib-collapse-toggle-transform: 0deg;--bslib-sidebar-toggle-transition-easing: cubic-bezier(1, 0, 0, 1);--bslib-collapse-toggle-right-transform: 180deg;--bslib-sidebar-column-main: minmax(0, 1fr);display:grid !important;grid-template-columns:min(100% - var(--bslib-sidebar-icon-size),var(--bslib-sidebar-width, 250px)) var(--bslib-sidebar-column-main);position:relative;transition:grid-template-columns ease-in-out var(--bslib-sidebar-transition-duration);border:var(--bslib-sidebar-border);border-radius:var(--bslib-sidebar-border-radius)}@media(prefers-reduced-motion: reduce){.bslib-sidebar-layout{transition:none}}.bslib-sidebar-layout[data-bslib-sidebar-border=false]{border:none}.bslib-sidebar-layout[data-bslib-sidebar-border-radius=false]{border-radius:initial}.bslib-sidebar-layout>.main,.bslib-sidebar-layout>.sidebar{grid-row:1/2;border-radius:inherit;overflow:auto}.bslib-sidebar-layout>.main{grid-column:2/3;border-top-left-radius:0;border-bottom-left-radius:0;padding:var(--bslib-sidebar-padding);transition:padding var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration);color:var(--bslib-sidebar-main-fg);background-color:var(--bslib-sidebar-main-bg)}.bslib-sidebar-layout>.sidebar{grid-column:1/2;width:100%;height:100%;border-right:var(--bslib-sidebar-vert-border);border-top-right-radius:0;border-bottom-right-radius:0;color:var(--bslib-sidebar-fg);background-color:var(--bslib-sidebar-bg);backdrop-filter:blur(5px)}.bslib-sidebar-layout>.sidebar>.sidebar-content{display:flex;flex-direction:column;gap:var(--bslib-spacer, 1rem);padding:var(--bslib-sidebar-padding);padding-top:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout>.sidebar>.sidebar-content>:last-child:not(.sidebar-title){margin-bottom:0}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion{margin-left:calc(-1*var(--bslib-sidebar-padding));margin-right:calc(-1*var(--bslib-sidebar-padding))}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:last-child{margin-bottom:calc(-1*var(--bslib-sidebar-padding))}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:not(:last-child){margin-bottom:1rem}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion .accordion-body{display:flex;flex-direction:column}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:not(:first-child) .accordion-item:first-child{border-top:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:not(:last-child) .accordion-item:last-child{border-bottom:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.bslib-sidebar-layout>.sidebar>.sidebar-content.has-accordion>.sidebar-title{border-bottom:none;padding-bottom:0}.bslib-sidebar-layout>.sidebar .shiny-input-container{width:100%}.bslib-sidebar-layout[data-bslib-sidebar-open=always]>.sidebar>.sidebar-content{padding-top:var(--bslib-sidebar-padding)}.bslib-sidebar-layout>.collapse-toggle{grid-row:1/2;grid-column:1/2;display:inline-flex;align-items:center;position:absolute;right:calc(var(--bslib-sidebar-icon-size));top:calc(var(--bslib-sidebar-icon-size, 1rem)/2);border:none;border-radius:var(--bslib-collapse-toggle-border-radius);height:var(--bslib-sidebar-icon-button-size, 2rem);width:var(--bslib-sidebar-icon-button-size, 2rem);display:flex;align-items:center;justify-content:center;padding:0;color:var(--bslib-sidebar-fg);background-color:unset;transition:color var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),top var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),right var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),left var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration)}.bslib-sidebar-layout>.collapse-toggle:hover{background-color:var(--bslib-sidebar-toggle-bg)}.bslib-sidebar-layout>.collapse-toggle>.collapse-icon{opacity:.8;width:var(--bslib-sidebar-icon-size);height:var(--bslib-sidebar-icon-size);transform:rotateY(var(--bslib-collapse-toggle-transform));transition:transform var(--bslib-sidebar-toggle-transition-easing) var(--bslib-sidebar-transition-duration)}.bslib-sidebar-layout>.collapse-toggle:hover>.collapse-icon{opacity:1}.bslib-sidebar-layout .sidebar-title{font-size:1.25rem;line-height:1.25;margin-top:0;margin-bottom:1rem;padding-bottom:1rem;border-bottom:var(--bslib-sidebar-border)}.bslib-sidebar-layout.sidebar-right{grid-template-columns:var(--bslib-sidebar-column-main) min(100% - var(--bslib-sidebar-icon-size),var(--bslib-sidebar-width, 250px))}.bslib-sidebar-layout.sidebar-right>.main{grid-column:1/2;border-top-right-radius:0;border-bottom-right-radius:0;border-top-left-radius:inherit;border-bottom-left-radius:inherit}.bslib-sidebar-layout.sidebar-right>.sidebar{grid-column:2/3;border-right:none;border-left:var(--bslib-sidebar-vert-border);border-top-left-radius:0;border-bottom-left-radius:0}.bslib-sidebar-layout.sidebar-right>.collapse-toggle{grid-column:2/3;left:var(--bslib-sidebar-icon-size);right:unset;border:var(--bslib-collapse-toggle-border)}.bslib-sidebar-layout.sidebar-right>.collapse-toggle>.collapse-icon{transform:rotateY(var(--bslib-collapse-toggle-right-transform))}.bslib-sidebar-layout.sidebar-collapsed{--bslib-collapse-toggle-transform: 180deg;--bslib-collapse-toggle-right-transform: 0deg;--bslib-sidebar-vert-border: none;grid-template-columns:0 minmax(0, 1fr)}.bslib-sidebar-layout.sidebar-collapsed.sidebar-right{grid-template-columns:minmax(0, 1fr) 0}.bslib-sidebar-layout.sidebar-collapsed:not(.transitioning)>.sidebar>*{display:none}.bslib-sidebar-layout.sidebar-collapsed>.main{border-radius:inherit}.bslib-sidebar-layout.sidebar-collapsed:not(.sidebar-right)>.main{padding-left:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout.sidebar-collapsed.sidebar-right>.main{padding-right:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout.sidebar-collapsed>.collapse-toggle{color:var(--bslib-sidebar-main-fg);top:calc(var(--bslib-sidebar-overlap-counter, 0)*(var(--bslib-sidebar-icon-size) + var(--bslib-sidebar-padding)) + var(--bslib-sidebar-icon-size, 1rem)/2);right:calc(-2.5*var(--bslib-sidebar-icon-size) - var(--bs-card-border-width, 1px))}.bslib-sidebar-layout.sidebar-collapsed.sidebar-right>.collapse-toggle{left:calc(-2.5*var(--bslib-sidebar-icon-size) - var(--bs-card-border-width, 1px));right:unset}@media(min-width: 576px){.bslib-sidebar-layout.transitioning>.sidebar>.sidebar-content{display:none}}@media(max-width: 575.98px){.bslib-sidebar-layout[data-bslib-sidebar-open=desktop]{--bslib-sidebar-js-init-collapsed: true}.bslib-sidebar-layout>.sidebar,.bslib-sidebar-layout.sidebar-right>.sidebar{border:none}.bslib-sidebar-layout>.main,.bslib-sidebar-layout.sidebar-right>.main{grid-column:1/3}.bslib-sidebar-layout[data-bslib-sidebar-open=always]{display:block !important}.bslib-sidebar-layout[data-bslib-sidebar-open=always]>.sidebar{max-height:var(--bslib-sidebar-max-height-mobile);overflow-y:auto;border-top:var(--bslib-sidebar-vert-border)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]){grid-template-columns:100% 0}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]):not(.sidebar-collapsed)>.sidebar{z-index:1}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]):not(.sidebar-collapsed)>.collapse-toggle{z-index:1}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-right{grid-template-columns:0 100%}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-collapsed{grid-template-columns:0 100%}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-collapsed.sidebar-right{grid-template-columns:100% 0}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]):not(.sidebar-right)>.main{padding-left:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-right>.main{padding-right:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always])>.main{opacity:0;transition:opacity var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-collapsed>.main{opacity:1}}.html-fill-container{display:flex;flex-direction:column;min-height:0;min-width:0}.html-fill-container>.html-fill-item{flex:1 1 auto;min-height:0;min-width:0}.html-fill-container>:not(.html-fill-item){flex:0 0 auto}.tippy-box[data-theme~=quarto]{background-color:#fff;border:solid 1px #e1e1e2;border-radius:.375rem;color:#373a3c;font-size:.875rem}.tippy-box[data-theme~=quarto]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=quarto]>.tippy-arrow:after,.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=quarto]>.tippy-arrow:after{border-color:rgba(0,0,0,0);border-style:solid}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-6px}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-6px}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-6px}.tippy-box[data-placement^=left]>.tippy-arrow:before{right:-6px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:after{border-top-color:#e1e1e2;border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff;bottom:16px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:#e1e1e2;border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:15px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:after{border-left-color:#e1e1e2;border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff;right:16px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:#e1e1e2}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow{fill:#373a3c}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px}.top-right{position:absolute;top:1em;right:1em}.visually-hidden{border:0;clip:rect(0 0 0 0);height:auto;margin:0;overflow:hidden;padding:0;position:absolute;width:1px;white-space:nowrap}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}figure.figure{display:block}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p,.quarto-figure-left>figure>div{text-align:left}.quarto-figure-center>figure>p,.quarto-figure-center>figure>div{text-align:center}.quarto-figure-right>figure>p,.quarto-figure-right>figure>div{text-align:right}.quarto-figure>figure>div.cell-annotation,.quarto-figure>figure>div code{text-align:left}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption.quarto-float-caption-bottom{margin-bottom:.5em}figure>figcaption.quarto-float-caption-top{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link{position:absolute;top:.6em;right:.5em}div[id^=tbl-]>.anchorjs-link{position:absolute;top:.7em;right:.3em}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,.h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,.h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,.h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,.h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1,#title-block-header .quarto-title-block>div>.h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}@media(min-width: 992px){#title-block-header .quarto-title-block>div>button{margin-top:5px}}tr.header>th>p:last-of-type{margin-bottom:0px}table,table.table{margin-top:.5rem;margin-bottom:.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}figure.quarto-float-tbl figcaption.quarto-float-caption-top{margin-top:.5rem;margin-bottom:.25rem;text-align:center}figure.quarto-float-tbl figcaption.quarto-float-caption-bottom{padding-top:.25rem;margin-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:rgba(55,58,60,.75)}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}dd code:not(.sourceCode),p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.footnote-back{margin-left:.2em}.tippy-content{overflow-x:auto}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x),.knitsql-table:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}a{text-underline-offset:3px}.callout pre.sourceCode{padding-left:0}div.ansi-escaped-output{font-family:monospace;display:block}/*!
+*
+* ansi colors from IPython notebook's
+*
+* we also add `bright-[color]-` synonyms for the `-[color]-intense` classes since
+* that seems to be what ansi_up emits
+*
+*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-black,.ansi-bright-black-fg{color:#282c36}.ansi-black-intense-black,.ansi-bright-black-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-red,.ansi-bright-red-fg{color:#b22b31}.ansi-red-intense-red,.ansi-bright-red-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-green,.ansi-bright-green-fg{color:#007427}.ansi-green-intense-green,.ansi-bright-green-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-yellow,.ansi-bright-yellow-fg{color:#b27d12}.ansi-yellow-intense-yellow,.ansi-bright-yellow-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-blue,.ansi-bright-blue-fg{color:#0065ca}.ansi-blue-intense-blue,.ansi-bright-blue-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-magenta,.ansi-bright-magenta-fg{color:#a03196}.ansi-magenta-intense-magenta,.ansi-bright-magenta-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-cyan,.ansi-bright-cyan-fg{color:#258f8f}.ansi-cyan-intense-cyan,.ansi-bright-cyan-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-white,.ansi-bright-white-fg{color:#a1a6b2}.ansi-white-intense-white,.ansi-bright-white-bg{background-color:#a1a6b2}.ansi-default-inverse-fg{color:#fff}.ansi-default-inverse-bg{background-color:#000}.ansi-bold{font-weight:bold}.ansi-underline{text-decoration:underline}:root{--quarto-body-bg: #fff;--quarto-body-color: #373a3c;--quarto-text-muted: rgba(55, 58, 60, 0.75);--quarto-border-color: #e1e1e2;--quarto-border-width: 1px}table.gt_table{color:var(--quarto-body-color);font-size:1em;width:100%;background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_column_spanner_outer{color:var(--quarto-body-color);background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_col_heading{color:var(--quarto-body-color);font-weight:bold;background-color:rgba(0,0,0,0)}table.gt_table thead.gt_col_headings{border-bottom:1px solid currentColor;border-top-width:inherit;border-top-color:var(--quarto-border-color)}table.gt_table thead.gt_col_headings:not(:first-child){border-top-width:1px;border-top-color:var(--quarto-border-color)}table.gt_table td.gt_row{border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-width:0px}table.gt_table tbody.gt_table_body{border-top-width:1px;border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-color:currentColor}div.columns{display:initial;gap:initial}div.column{display:inline-block;overflow-x:initial;vertical-align:top;width:50%}.code-annotation-tip-content{word-wrap:break-word}.code-annotation-container-hidden{display:none !important}dl.code-annotation-container-grid{display:grid;grid-template-columns:min-content auto}dl.code-annotation-container-grid dt{grid-column:1}dl.code-annotation-container-grid dd{grid-column:2}pre.sourceCode.code-annotation-code{padding-right:0}code.sourceCode .code-annotation-anchor{z-index:100;position:relative;float:right;background-color:rgba(0,0,0,0)}input[type=checkbox]{margin-right:.5ch}:root{--mermaid-bg-color: #fff;--mermaid-edge-color: #373a3c;--mermaid-node-fg-color: #373a3c;--mermaid-fg-color: #373a3c;--mermaid-fg-color--lighter: #4f5457;--mermaid-fg-color--lightest: #686d71;--mermaid-font-family: Source Sans Pro, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;--mermaid-label-bg-color: #fff;--mermaid-label-fg-color: #2780e3;--mermaid-node-bg-color: rgba(39, 128, 227, 0.1);--mermaid-node-fg-color: #373a3c}@media print{:root{font-size:11pt}#quarto-sidebar,#TOC,.nav-page{display:none}.page-columns .content{grid-column-start:page-start}.fixed-top{position:relative}.panel-caption,.figure-caption,figcaption{color:#666}}.code-copy-button{position:absolute;top:0;right:0;border:0;margin-top:5px;margin-right:5px;background-color:rgba(0,0,0,0);z-index:3}.code-copy-button:focus{outline:none}.code-copy-button-tooltip{font-size:.75em}pre.sourceCode:hover>.code-copy-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}pre.sourceCode:hover>.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button-checked:hover>.bi::before{background-image:url('data:image/svg+xml,')}main ol ol,main ul ul,main ol ul,main ul ol{margin-bottom:1em}ul>li:not(:has(>p))>ul,ol>li:not(:has(>p))>ul,ul>li:not(:has(>p))>ol,ol>li:not(:has(>p))>ol{margin-bottom:0}ul>li:not(:has(>p))>ul>li:has(>p),ol>li:not(:has(>p))>ul>li:has(>p),ul>li:not(:has(>p))>ol>li:has(>p),ol>li:not(:has(>p))>ol>li:has(>p){margin-top:1rem}body{margin:0}main.page-columns>header>h1.title,main.page-columns>header>.title.h1{margin-bottom:0}@media(min-width: 992px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] 35px [page-end-inset page-end] 5fr [screen-end-inset] 1.5em}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] minmax(50px, 100px) [page-end] 1fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1000px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1000px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1000px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 150px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 991.98px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(1250px - 3em)) [body-content-end body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(1000px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 767.98px){body .page-columns,body.fullcontent:not(.floating):not(.docked) .page-columns,body.slimcontent:not(.floating):not(.docked) .page-columns,body.docked .page-columns,body.docked.slimcontent .page-columns,body.docked.fullcontent .page-columns,body.floating .page-columns,body.floating.slimcontent .page-columns,body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}nav[role=doc-toc]{display:none}}body,.page-row-navigation{grid-template-rows:[page-top] max-content [contents-top] max-content [contents-bottom] max-content [page-bottom]}.page-rows-contents{grid-template-rows:[content-top] minmax(max-content, 1fr) [content-bottom] minmax(60px, max-content) [page-bottom]}.page-full{grid-column:screen-start/screen-end !important}.page-columns>*{grid-column:body-content-start/body-content-end}.page-columns.column-page>*{grid-column:page-start/page-end}.page-columns.column-page-left .page-columns.page-full>*,.page-columns.column-page-left>*{grid-column:page-start/body-content-end}.page-columns.column-page-right .page-columns.page-full>*,.page-columns.column-page-right>*{grid-column:body-content-start/page-end}.page-rows{grid-auto-rows:auto}.header{grid-column:screen-start/screen-end;grid-row:page-top/contents-top}#quarto-content{padding:0;grid-column:screen-start/screen-end;grid-row:contents-top/contents-bottom}body.floating .sidebar.sidebar-navigation{grid-column:page-start/body-start;grid-row:content-top/page-bottom}body.docked .sidebar.sidebar-navigation{grid-column:screen-start/body-start;grid-row:content-top/page-bottom}.sidebar.toc-left{grid-column:page-start/body-start;grid-row:content-top/page-bottom}.sidebar.margin-sidebar{grid-column:body-end/page-end;grid-row:content-top/page-bottom}.page-columns .content{grid-column:body-content-start/body-content-end;grid-row:content-top/content-bottom;align-content:flex-start}.page-columns .page-navigation{grid-column:body-content-start/body-content-end;grid-row:content-bottom/page-bottom}.page-columns .footer{grid-column:screen-start/screen-end;grid-row:contents-bottom/page-bottom}.page-columns .column-body{grid-column:body-content-start/body-content-end}.page-columns .column-body-fullbleed{grid-column:body-start/body-end}.page-columns .column-body-outset{grid-column:body-start-outset/body-end-outset;z-index:998;opacity:.999}.page-columns .column-body-outset table{background:#fff}.page-columns .column-body-outset-left{grid-column:body-start-outset/body-content-end;z-index:998;opacity:.999}.page-columns .column-body-outset-left table{background:#fff}.page-columns .column-body-outset-right{grid-column:body-content-start/body-end-outset;z-index:998;opacity:.999}.page-columns .column-body-outset-right table{background:#fff}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;opacity:.999}.page-columns .column-page table{background:#fff}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;opacity:.999}.page-columns .column-page-inset table{background:#fff}.page-columns .column-page-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;opacity:.999}.page-columns .column-page-inset-left table{background:#fff}.page-columns .column-page-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;opacity:.999}.page-columns .column-page-inset-right figcaption table{background:#fff}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-page-left table{background:#fff}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;opacity:.999}.page-columns .column-page-right figcaption table{background:#fff}#quarto-content.page-columns #quarto-margin-sidebar,#quarto-content.page-columns #quarto-sidebar{z-index:1}@media(max-width: 991.98px){#quarto-content.page-columns #quarto-margin-sidebar.collapse,#quarto-content.page-columns #quarto-sidebar.collapse,#quarto-content.page-columns #quarto-margin-sidebar.collapsing,#quarto-content.page-columns #quarto-sidebar.collapsing{z-index:1055}}#quarto-content.page-columns main.column-page,#quarto-content.page-columns main.column-page-right,#quarto-content.page-columns main.column-page-left{z-index:0}.page-columns .column-screen-inset{grid-column:screen-start-inset/screen-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:screen-start-inset/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/screen-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;opacity:.999}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;opacity:.999}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#f8f9fa;z-index:998;opacity:.999;margin-bottom:1em}.zindex-content{z-index:998;opacity:.999}.zindex-modal{z-index:1055;opacity:.999}.zindex-over-content{z-index:999;opacity:.999}img.img-fluid.column-screen,img.img-fluid.column-screen-inset-shaded,img.img-fluid.column-screen-inset,img.img-fluid.column-screen-inset-left,img.img-fluid.column-screen-inset-right,img.img-fluid.column-screen-left,img.img-fluid.column-screen-right{width:100%}@media(min-width: 992px){.margin-caption,div.aside,aside:not(.footnotes):not(.sidebar),.column-margin{grid-column:body-end/page-end !important;z-index:998}.column-sidebar{grid-column:page-start/body-start !important;z-index:998}.column-leftmargin{grid-column:screen-start-inset/body-start !important;z-index:998}.no-row-height{height:1em;overflow:visible}}@media(max-width: 991.98px){.margin-caption,div.aside,aside:not(.footnotes):not(.sidebar),.column-margin{grid-column:body-end/page-end !important;z-index:998}.no-row-height{height:1em;overflow:visible}.page-columns.page-full{overflow:visible}.page-columns.toc-left .margin-caption,.page-columns.toc-left div.aside,.page-columns.toc-left aside:not(.footnotes):not(.sidebar),.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;opacity:.999}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside:not(.footnotes):not(.sidebar),.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;opacity:.999}.no-row-height{height:initial;overflow:initial}#quarto-margin-sidebar{display:none}#quarto-sidebar-toc-left{display:none}.hidden-sm{display:none}}.panel-grid{display:grid;grid-template-rows:repeat(1, 1fr);grid-template-columns:repeat(24, 1fr);gap:1em}.panel-grid .g-col-1{grid-column:auto/span 1}.panel-grid .g-col-2{grid-column:auto/span 2}.panel-grid .g-col-3{grid-column:auto/span 3}.panel-grid .g-col-4{grid-column:auto/span 4}.panel-grid .g-col-5{grid-column:auto/span 5}.panel-grid .g-col-6{grid-column:auto/span 6}.panel-grid .g-col-7{grid-column:auto/span 7}.panel-grid .g-col-8{grid-column:auto/span 8}.panel-grid .g-col-9{grid-column:auto/span 9}.panel-grid .g-col-10{grid-column:auto/span 10}.panel-grid .g-col-11{grid-column:auto/span 11}.panel-grid .g-col-12{grid-column:auto/span 12}.panel-grid .g-col-13{grid-column:auto/span 13}.panel-grid .g-col-14{grid-column:auto/span 14}.panel-grid .g-col-15{grid-column:auto/span 15}.panel-grid .g-col-16{grid-column:auto/span 16}.panel-grid .g-col-17{grid-column:auto/span 17}.panel-grid .g-col-18{grid-column:auto/span 18}.panel-grid .g-col-19{grid-column:auto/span 19}.panel-grid .g-col-20{grid-column:auto/span 20}.panel-grid .g-col-21{grid-column:auto/span 21}.panel-grid .g-col-22{grid-column:auto/span 22}.panel-grid .g-col-23{grid-column:auto/span 23}.panel-grid .g-col-24{grid-column:auto/span 24}.panel-grid .g-start-1{grid-column-start:1}.panel-grid .g-start-2{grid-column-start:2}.panel-grid .g-start-3{grid-column-start:3}.panel-grid .g-start-4{grid-column-start:4}.panel-grid .g-start-5{grid-column-start:5}.panel-grid .g-start-6{grid-column-start:6}.panel-grid .g-start-7{grid-column-start:7}.panel-grid .g-start-8{grid-column-start:8}.panel-grid .g-start-9{grid-column-start:9}.panel-grid .g-start-10{grid-column-start:10}.panel-grid .g-start-11{grid-column-start:11}.panel-grid .g-start-12{grid-column-start:12}.panel-grid .g-start-13{grid-column-start:13}.panel-grid .g-start-14{grid-column-start:14}.panel-grid .g-start-15{grid-column-start:15}.panel-grid .g-start-16{grid-column-start:16}.panel-grid .g-start-17{grid-column-start:17}.panel-grid .g-start-18{grid-column-start:18}.panel-grid .g-start-19{grid-column-start:19}.panel-grid .g-start-20{grid-column-start:20}.panel-grid .g-start-21{grid-column-start:21}.panel-grid .g-start-22{grid-column-start:22}.panel-grid .g-start-23{grid-column-start:23}@media(min-width: 576px){.panel-grid .g-col-sm-1{grid-column:auto/span 1}.panel-grid .g-col-sm-2{grid-column:auto/span 2}.panel-grid .g-col-sm-3{grid-column:auto/span 3}.panel-grid .g-col-sm-4{grid-column:auto/span 4}.panel-grid .g-col-sm-5{grid-column:auto/span 5}.panel-grid .g-col-sm-6{grid-column:auto/span 6}.panel-grid .g-col-sm-7{grid-column:auto/span 7}.panel-grid .g-col-sm-8{grid-column:auto/span 8}.panel-grid .g-col-sm-9{grid-column:auto/span 9}.panel-grid .g-col-sm-10{grid-column:auto/span 10}.panel-grid .g-col-sm-11{grid-column:auto/span 11}.panel-grid .g-col-sm-12{grid-column:auto/span 12}.panel-grid .g-col-sm-13{grid-column:auto/span 13}.panel-grid .g-col-sm-14{grid-column:auto/span 14}.panel-grid .g-col-sm-15{grid-column:auto/span 15}.panel-grid .g-col-sm-16{grid-column:auto/span 16}.panel-grid .g-col-sm-17{grid-column:auto/span 17}.panel-grid .g-col-sm-18{grid-column:auto/span 18}.panel-grid .g-col-sm-19{grid-column:auto/span 19}.panel-grid .g-col-sm-20{grid-column:auto/span 20}.panel-grid .g-col-sm-21{grid-column:auto/span 21}.panel-grid .g-col-sm-22{grid-column:auto/span 22}.panel-grid .g-col-sm-23{grid-column:auto/span 23}.panel-grid .g-col-sm-24{grid-column:auto/span 24}.panel-grid .g-start-sm-1{grid-column-start:1}.panel-grid .g-start-sm-2{grid-column-start:2}.panel-grid .g-start-sm-3{grid-column-start:3}.panel-grid .g-start-sm-4{grid-column-start:4}.panel-grid .g-start-sm-5{grid-column-start:5}.panel-grid .g-start-sm-6{grid-column-start:6}.panel-grid .g-start-sm-7{grid-column-start:7}.panel-grid .g-start-sm-8{grid-column-start:8}.panel-grid .g-start-sm-9{grid-column-start:9}.panel-grid .g-start-sm-10{grid-column-start:10}.panel-grid .g-start-sm-11{grid-column-start:11}.panel-grid .g-start-sm-12{grid-column-start:12}.panel-grid .g-start-sm-13{grid-column-start:13}.panel-grid .g-start-sm-14{grid-column-start:14}.panel-grid .g-start-sm-15{grid-column-start:15}.panel-grid .g-start-sm-16{grid-column-start:16}.panel-grid .g-start-sm-17{grid-column-start:17}.panel-grid .g-start-sm-18{grid-column-start:18}.panel-grid .g-start-sm-19{grid-column-start:19}.panel-grid .g-start-sm-20{grid-column-start:20}.panel-grid .g-start-sm-21{grid-column-start:21}.panel-grid .g-start-sm-22{grid-column-start:22}.panel-grid .g-start-sm-23{grid-column-start:23}}@media(min-width: 768px){.panel-grid .g-col-md-1{grid-column:auto/span 1}.panel-grid .g-col-md-2{grid-column:auto/span 2}.panel-grid .g-col-md-3{grid-column:auto/span 3}.panel-grid .g-col-md-4{grid-column:auto/span 4}.panel-grid .g-col-md-5{grid-column:auto/span 5}.panel-grid .g-col-md-6{grid-column:auto/span 6}.panel-grid .g-col-md-7{grid-column:auto/span 7}.panel-grid .g-col-md-8{grid-column:auto/span 8}.panel-grid .g-col-md-9{grid-column:auto/span 9}.panel-grid .g-col-md-10{grid-column:auto/span 10}.panel-grid .g-col-md-11{grid-column:auto/span 11}.panel-grid .g-col-md-12{grid-column:auto/span 12}.panel-grid .g-col-md-13{grid-column:auto/span 13}.panel-grid .g-col-md-14{grid-column:auto/span 14}.panel-grid .g-col-md-15{grid-column:auto/span 15}.panel-grid .g-col-md-16{grid-column:auto/span 16}.panel-grid .g-col-md-17{grid-column:auto/span 17}.panel-grid .g-col-md-18{grid-column:auto/span 18}.panel-grid .g-col-md-19{grid-column:auto/span 19}.panel-grid .g-col-md-20{grid-column:auto/span 20}.panel-grid .g-col-md-21{grid-column:auto/span 21}.panel-grid .g-col-md-22{grid-column:auto/span 22}.panel-grid .g-col-md-23{grid-column:auto/span 23}.panel-grid .g-col-md-24{grid-column:auto/span 24}.panel-grid .g-start-md-1{grid-column-start:1}.panel-grid .g-start-md-2{grid-column-start:2}.panel-grid .g-start-md-3{grid-column-start:3}.panel-grid .g-start-md-4{grid-column-start:4}.panel-grid .g-start-md-5{grid-column-start:5}.panel-grid .g-start-md-6{grid-column-start:6}.panel-grid .g-start-md-7{grid-column-start:7}.panel-grid .g-start-md-8{grid-column-start:8}.panel-grid .g-start-md-9{grid-column-start:9}.panel-grid .g-start-md-10{grid-column-start:10}.panel-grid .g-start-md-11{grid-column-start:11}.panel-grid .g-start-md-12{grid-column-start:12}.panel-grid .g-start-md-13{grid-column-start:13}.panel-grid .g-start-md-14{grid-column-start:14}.panel-grid .g-start-md-15{grid-column-start:15}.panel-grid .g-start-md-16{grid-column-start:16}.panel-grid .g-start-md-17{grid-column-start:17}.panel-grid .g-start-md-18{grid-column-start:18}.panel-grid .g-start-md-19{grid-column-start:19}.panel-grid .g-start-md-20{grid-column-start:20}.panel-grid .g-start-md-21{grid-column-start:21}.panel-grid .g-start-md-22{grid-column-start:22}.panel-grid .g-start-md-23{grid-column-start:23}}@media(min-width: 992px){.panel-grid .g-col-lg-1{grid-column:auto/span 1}.panel-grid .g-col-lg-2{grid-column:auto/span 2}.panel-grid .g-col-lg-3{grid-column:auto/span 3}.panel-grid .g-col-lg-4{grid-column:auto/span 4}.panel-grid .g-col-lg-5{grid-column:auto/span 5}.panel-grid .g-col-lg-6{grid-column:auto/span 6}.panel-grid .g-col-lg-7{grid-column:auto/span 7}.panel-grid .g-col-lg-8{grid-column:auto/span 8}.panel-grid .g-col-lg-9{grid-column:auto/span 9}.panel-grid .g-col-lg-10{grid-column:auto/span 10}.panel-grid .g-col-lg-11{grid-column:auto/span 11}.panel-grid .g-col-lg-12{grid-column:auto/span 12}.panel-grid .g-col-lg-13{grid-column:auto/span 13}.panel-grid .g-col-lg-14{grid-column:auto/span 14}.panel-grid .g-col-lg-15{grid-column:auto/span 15}.panel-grid .g-col-lg-16{grid-column:auto/span 16}.panel-grid .g-col-lg-17{grid-column:auto/span 17}.panel-grid .g-col-lg-18{grid-column:auto/span 18}.panel-grid .g-col-lg-19{grid-column:auto/span 19}.panel-grid .g-col-lg-20{grid-column:auto/span 20}.panel-grid .g-col-lg-21{grid-column:auto/span 21}.panel-grid .g-col-lg-22{grid-column:auto/span 22}.panel-grid .g-col-lg-23{grid-column:auto/span 23}.panel-grid .g-col-lg-24{grid-column:auto/span 24}.panel-grid .g-start-lg-1{grid-column-start:1}.panel-grid .g-start-lg-2{grid-column-start:2}.panel-grid .g-start-lg-3{grid-column-start:3}.panel-grid .g-start-lg-4{grid-column-start:4}.panel-grid .g-start-lg-5{grid-column-start:5}.panel-grid .g-start-lg-6{grid-column-start:6}.panel-grid .g-start-lg-7{grid-column-start:7}.panel-grid .g-start-lg-8{grid-column-start:8}.panel-grid .g-start-lg-9{grid-column-start:9}.panel-grid .g-start-lg-10{grid-column-start:10}.panel-grid .g-start-lg-11{grid-column-start:11}.panel-grid .g-start-lg-12{grid-column-start:12}.panel-grid .g-start-lg-13{grid-column-start:13}.panel-grid .g-start-lg-14{grid-column-start:14}.panel-grid .g-start-lg-15{grid-column-start:15}.panel-grid .g-start-lg-16{grid-column-start:16}.panel-grid .g-start-lg-17{grid-column-start:17}.panel-grid .g-start-lg-18{grid-column-start:18}.panel-grid .g-start-lg-19{grid-column-start:19}.panel-grid .g-start-lg-20{grid-column-start:20}.panel-grid .g-start-lg-21{grid-column-start:21}.panel-grid .g-start-lg-22{grid-column-start:22}.panel-grid .g-start-lg-23{grid-column-start:23}}@media(min-width: 1200px){.panel-grid .g-col-xl-1{grid-column:auto/span 1}.panel-grid .g-col-xl-2{grid-column:auto/span 2}.panel-grid .g-col-xl-3{grid-column:auto/span 3}.panel-grid .g-col-xl-4{grid-column:auto/span 4}.panel-grid .g-col-xl-5{grid-column:auto/span 5}.panel-grid .g-col-xl-6{grid-column:auto/span 6}.panel-grid .g-col-xl-7{grid-column:auto/span 7}.panel-grid .g-col-xl-8{grid-column:auto/span 8}.panel-grid .g-col-xl-9{grid-column:auto/span 9}.panel-grid .g-col-xl-10{grid-column:auto/span 10}.panel-grid .g-col-xl-11{grid-column:auto/span 11}.panel-grid .g-col-xl-12{grid-column:auto/span 12}.panel-grid .g-col-xl-13{grid-column:auto/span 13}.panel-grid .g-col-xl-14{grid-column:auto/span 14}.panel-grid .g-col-xl-15{grid-column:auto/span 15}.panel-grid .g-col-xl-16{grid-column:auto/span 16}.panel-grid .g-col-xl-17{grid-column:auto/span 17}.panel-grid .g-col-xl-18{grid-column:auto/span 18}.panel-grid .g-col-xl-19{grid-column:auto/span 19}.panel-grid .g-col-xl-20{grid-column:auto/span 20}.panel-grid .g-col-xl-21{grid-column:auto/span 21}.panel-grid .g-col-xl-22{grid-column:auto/span 22}.panel-grid .g-col-xl-23{grid-column:auto/span 23}.panel-grid .g-col-xl-24{grid-column:auto/span 24}.panel-grid .g-start-xl-1{grid-column-start:1}.panel-grid .g-start-xl-2{grid-column-start:2}.panel-grid .g-start-xl-3{grid-column-start:3}.panel-grid .g-start-xl-4{grid-column-start:4}.panel-grid .g-start-xl-5{grid-column-start:5}.panel-grid .g-start-xl-6{grid-column-start:6}.panel-grid .g-start-xl-7{grid-column-start:7}.panel-grid .g-start-xl-8{grid-column-start:8}.panel-grid .g-start-xl-9{grid-column-start:9}.panel-grid .g-start-xl-10{grid-column-start:10}.panel-grid .g-start-xl-11{grid-column-start:11}.panel-grid .g-start-xl-12{grid-column-start:12}.panel-grid .g-start-xl-13{grid-column-start:13}.panel-grid .g-start-xl-14{grid-column-start:14}.panel-grid .g-start-xl-15{grid-column-start:15}.panel-grid .g-start-xl-16{grid-column-start:16}.panel-grid .g-start-xl-17{grid-column-start:17}.panel-grid .g-start-xl-18{grid-column-start:18}.panel-grid .g-start-xl-19{grid-column-start:19}.panel-grid .g-start-xl-20{grid-column-start:20}.panel-grid .g-start-xl-21{grid-column-start:21}.panel-grid .g-start-xl-22{grid-column-start:22}.panel-grid .g-start-xl-23{grid-column-start:23}}@media(min-width: 1400px){.panel-grid .g-col-xxl-1{grid-column:auto/span 1}.panel-grid .g-col-xxl-2{grid-column:auto/span 2}.panel-grid .g-col-xxl-3{grid-column:auto/span 3}.panel-grid .g-col-xxl-4{grid-column:auto/span 4}.panel-grid .g-col-xxl-5{grid-column:auto/span 5}.panel-grid .g-col-xxl-6{grid-column:auto/span 6}.panel-grid .g-col-xxl-7{grid-column:auto/span 7}.panel-grid .g-col-xxl-8{grid-column:auto/span 8}.panel-grid .g-col-xxl-9{grid-column:auto/span 9}.panel-grid .g-col-xxl-10{grid-column:auto/span 10}.panel-grid .g-col-xxl-11{grid-column:auto/span 11}.panel-grid .g-col-xxl-12{grid-column:auto/span 12}.panel-grid .g-col-xxl-13{grid-column:auto/span 13}.panel-grid .g-col-xxl-14{grid-column:auto/span 14}.panel-grid .g-col-xxl-15{grid-column:auto/span 15}.panel-grid .g-col-xxl-16{grid-column:auto/span 16}.panel-grid .g-col-xxl-17{grid-column:auto/span 17}.panel-grid .g-col-xxl-18{grid-column:auto/span 18}.panel-grid .g-col-xxl-19{grid-column:auto/span 19}.panel-grid .g-col-xxl-20{grid-column:auto/span 20}.panel-grid .g-col-xxl-21{grid-column:auto/span 21}.panel-grid .g-col-xxl-22{grid-column:auto/span 22}.panel-grid .g-col-xxl-23{grid-column:auto/span 23}.panel-grid .g-col-xxl-24{grid-column:auto/span 24}.panel-grid .g-start-xxl-1{grid-column-start:1}.panel-grid .g-start-xxl-2{grid-column-start:2}.panel-grid .g-start-xxl-3{grid-column-start:3}.panel-grid .g-start-xxl-4{grid-column-start:4}.panel-grid .g-start-xxl-5{grid-column-start:5}.panel-grid .g-start-xxl-6{grid-column-start:6}.panel-grid .g-start-xxl-7{grid-column-start:7}.panel-grid .g-start-xxl-8{grid-column-start:8}.panel-grid .g-start-xxl-9{grid-column-start:9}.panel-grid .g-start-xxl-10{grid-column-start:10}.panel-grid .g-start-xxl-11{grid-column-start:11}.panel-grid .g-start-xxl-12{grid-column-start:12}.panel-grid .g-start-xxl-13{grid-column-start:13}.panel-grid .g-start-xxl-14{grid-column-start:14}.panel-grid .g-start-xxl-15{grid-column-start:15}.panel-grid .g-start-xxl-16{grid-column-start:16}.panel-grid .g-start-xxl-17{grid-column-start:17}.panel-grid .g-start-xxl-18{grid-column-start:18}.panel-grid .g-start-xxl-19{grid-column-start:19}.panel-grid .g-start-xxl-20{grid-column-start:20}.panel-grid .g-start-xxl-21{grid-column-start:21}.panel-grid .g-start-xxl-22{grid-column-start:22}.panel-grid .g-start-xxl-23{grid-column-start:23}}main{margin-top:1em;margin-bottom:1em}h1,.h1,h2,.h2{color:inherit;margin-top:2rem;margin-bottom:1rem;font-weight:600}h1.title,.title.h1{margin-top:0}main.content>section:first-of-type>h2:first-child,main.content>section:first-of-type>.h2:first-child{margin-top:0}h2,.h2{border-bottom:1px solid #e1e1e2;padding-bottom:.5rem}h3,.h3{font-weight:600}h3,.h3,h4,.h4{opacity:.9;margin-top:1.5rem}h5,.h5,h6,.h6{opacity:.9}.header-section-number{color:#747a7f}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,.figure-caption,.subfigure-caption,.table-caption,figcaption,caption{font-size:.9rem;color:#747a7f}.quarto-layout-cell[data-ref-parent] caption{color:#747a7f}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#747a7f;font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse):first-child{padding-bottom:.5em;display:block}.column-margin.column-container>*:not(.collapse):not(:first-child){padding-top:.5em;padding-bottom:.5em;display:block}.column-margin.column-container>*.collapse:not(.show){display:none}@media(min-width: 768px){.column-margin.column-container .callout-margin-content:first-child{margin-top:4.5em}.column-margin.column-container .callout-margin-content-simple:first-child{margin-top:3.5em}}.margin-caption>*{padding-top:.5em;padding-bottom:.5em}@media(max-width: 767.98px){.quarto-layout-row{flex-direction:column}}.nav-tabs .nav-item{margin-top:1px;cursor:pointer}.tab-content{margin-top:0px;border-left:#e1e1e2 1px solid;border-right:#e1e1e2 1px solid;border-bottom:#e1e1e2 1px solid;margin-left:0;padding:1em;margin-bottom:1em}@media(max-width: 767.98px){.layout-sidebar{margin-left:0;margin-right:0}}.panel-sidebar,.panel-sidebar .form-control,.panel-input,.panel-input .form-control,.selectize-dropdown{font-size:.9rem}.panel-sidebar .form-control,.panel-input .form-control{padding-top:.1rem}.tab-pane div.sourceCode{margin-top:0px}.tab-pane>p{padding-top:0}.tab-pane>p:nth-child(1){padding-top:0}.tab-pane>p:last-child{margin-bottom:0}.tab-pane>pre:last-child{margin-bottom:0}.tab-content>.tab-pane:not(.active){display:none !important}div.sourceCode{background-color:rgba(233,236,239,.65);border:1px solid rgba(233,236,239,.65)}pre.sourceCode{background-color:rgba(0,0,0,0)}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:#747a7f}.blockquote h1:first-child,.blockquote .h1:first-child,.blockquote h2:first-child,.blockquote .h2:first-child,.blockquote h3:first-child,.blockquote .h3:first-child,.blockquote h4:first-child,.blockquote .h4:first-child,.blockquote h5:first-child,.blockquote .h5:first-child{margin-top:0}pre{background-color:initial;padding:initial;border:initial}p pre code:not(.sourceCode),li pre code:not(.sourceCode),pre code:not(.sourceCode){background-color:initial}p code:not(.sourceCode),li code:not(.sourceCode),td code:not(.sourceCode){background-color:#f8f9fa;padding:.2em}nav p code:not(.sourceCode),nav li code:not(.sourceCode),nav td code:not(.sourceCode){background-color:rgba(0,0,0,0);padding:0}td code:not(.sourceCode){white-space:pre-wrap}#quarto-embedded-source-code-modal>.modal-dialog{max-width:1000px;padding-left:1.75rem;padding-right:1.75rem}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body{padding:0}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body div.sourceCode{margin:0;padding:.2rem .2rem;border-radius:0px;border:none}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-header{padding:.7rem}.code-tools-button{font-size:1rem;padding:.15rem .15rem;margin-left:5px;color:rgba(55,58,60,.75);background-color:rgba(0,0,0,0);transition:initial;cursor:pointer}.code-tools-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.sidebar{will-change:top;transition:top 200ms linear;position:sticky;overflow-y:auto;padding-top:1.2em;max-height:100vh}.sidebar.toc-left,.sidebar.margin-sidebar{top:0px;padding-top:1em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}figure .quarto-notebook-link{margin-top:.5em}.quarto-notebook-link{font-size:.75em;color:rgba(55,58,60,.75);margin-bottom:1em;text-decoration:none;display:block}.quarto-notebook-link:hover{text-decoration:underline;color:#2761e3}.quarto-notebook-link::before{display:inline-block;height:.75rem;width:.75rem;margin-bottom:0em;margin-right:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}.toc-actions i.bi,.quarto-code-links i.bi,.quarto-other-links i.bi,.quarto-alternate-notebooks i.bi,.quarto-alternate-formats i.bi{margin-right:.4em;font-size:.8rem}.quarto-other-links-text-target .quarto-code-links i.bi,.quarto-other-links-text-target .quarto-other-links i.bi{margin-right:.2em}.quarto-other-formats-text-target .quarto-alternate-formats i.bi{margin-right:.1em}.toc-actions i.bi.empty,.quarto-code-links i.bi.empty,.quarto-other-links i.bi.empty,.quarto-alternate-notebooks i.bi.empty,.quarto-alternate-formats i.bi.empty{padding-left:1em}.quarto-notebook h2,.quarto-notebook .h2{border-bottom:none}.quarto-notebook .cell-container{display:flex}.quarto-notebook .cell-container .cell{flex-grow:4}.quarto-notebook .cell-container .cell-decorator{padding-top:1.5em;padding-right:1em;text-align:right}.quarto-notebook .cell-container.code-fold .cell-decorator{padding-top:3em}.quarto-notebook .cell-code code{white-space:pre-wrap}.quarto-notebook .cell .cell-output-stderr pre code,.quarto-notebook .cell .cell-output-stdout pre code{white-space:pre-wrap;overflow-wrap:anywhere}.toc-actions,.quarto-alternate-formats,.quarto-other-links,.quarto-code-links,.quarto-alternate-notebooks{padding-left:0em}.sidebar .toc-actions a,.sidebar .quarto-alternate-formats a,.sidebar .quarto-other-links a,.sidebar .quarto-code-links a,.sidebar .quarto-alternate-notebooks a,.sidebar nav[role=doc-toc] a{text-decoration:none}.sidebar .toc-actions a:hover,.sidebar .quarto-other-links a:hover,.sidebar .quarto-code-links a:hover,.sidebar .quarto-alternate-formats a:hover,.sidebar .quarto-alternate-notebooks a:hover{color:#2761e3}.sidebar .toc-actions h2,.sidebar .toc-actions .h2,.sidebar .quarto-code-links h2,.sidebar .quarto-code-links .h2,.sidebar .quarto-other-links h2,.sidebar .quarto-other-links .h2,.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2,.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-weight:500;margin-bottom:.2rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar .toc-actions>h2,.sidebar .toc-actions>.h2,.sidebar .quarto-code-links>h2,.sidebar .quarto-code-links>.h2,.sidebar .quarto-other-links>h2,.sidebar .quarto-other-links>.h2,.sidebar .quarto-alternate-notebooks>h2,.sidebar .quarto-alternate-notebooks>.h2,.sidebar .quarto-alternate-formats>h2,.sidebar .quarto-alternate-formats>.h2{font-size:.8rem}.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-size:.875rem}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #e9ecef;padding-left:.6rem}.sidebar .toc-actions h2>ul a,.sidebar .toc-actions .h2>ul a,.sidebar .quarto-code-links h2>ul a,.sidebar .quarto-code-links .h2>ul a,.sidebar .quarto-other-links h2>ul a,.sidebar .quarto-other-links .h2>ul a,.sidebar .quarto-alternate-notebooks h2>ul a,.sidebar .quarto-alternate-notebooks .h2>ul a,.sidebar .quarto-alternate-formats h2>ul a,.sidebar .quarto-alternate-formats .h2>ul a{border-left:none;padding-left:.6rem}.sidebar .toc-actions ul a:empty,.sidebar .quarto-code-links ul a:empty,.sidebar .quarto-other-links ul a:empty,.sidebar .quarto-alternate-notebooks ul a:empty,.sidebar .quarto-alternate-formats ul a:empty,.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar .toc-actions ul,.sidebar .quarto-code-links ul,.sidebar .quarto-other-links ul,.sidebar .quarto-alternate-notebooks ul,.sidebar .quarto-alternate-formats ul{padding-left:0;list-style:none}.sidebar nav[role=doc-toc] ul{list-style:none;padding-left:0;list-style:none}.sidebar nav[role=doc-toc]>ul{margin-left:.45em}.quarto-margin-sidebar nav[role=doc-toc]{padding-left:.5em}.sidebar .toc-actions>ul,.sidebar .quarto-code-links>ul,.sidebar .quarto-other-links>ul,.sidebar .quarto-alternate-notebooks>ul,.sidebar .quarto-alternate-formats>ul{font-size:.8rem}.sidebar nav[role=doc-toc]>ul{font-size:.875rem}.sidebar .toc-actions ul li a,.sidebar .quarto-code-links ul li a,.sidebar .quarto-other-links ul li a,.sidebar .quarto-alternate-notebooks ul li a,.sidebar .quarto-alternate-formats ul li a,.sidebar nav[role=doc-toc]>ul li a{line-height:1.1rem;padding-bottom:.2rem;padding-top:.2rem;color:inherit}.sidebar nav[role=doc-toc] ul>li>ul>li>a{padding-left:1.2em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>a{padding-left:2.4em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>a{padding-left:3.6em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:4.8em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:6em}.sidebar nav[role=doc-toc] ul>li>a.active,.sidebar nav[role=doc-toc] ul>li>ul>li>a.active{border-left:1px solid #2761e3;color:#2761e3 !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#2761e3 !important}kbd,.kbd{color:#373a3c;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#e1e1e2}.quarto-appendix-contents div.hanging-indent{margin-left:0em}.quarto-appendix-contents div.hanging-indent div.csl-entry{margin-left:1em;text-indent:-1em}.citation a,.footnote-ref{text-decoration:none}.footnotes ol{padding-left:1em}.tippy-content>*{margin-bottom:.7em}.tippy-content>*:last-child{margin-bottom:0}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.375rem;overflow-wrap:break-word}.callout .callout-title-container{overflow-wrap:anywhere}.callout.callout-style-simple{padding:.4em .7em;border-left:5px solid;border-right:1px solid #e1e1e2;border-top:1px solid #e1e1e2;border-bottom:1px solid #e1e1e2}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #e1e1e2;border-top:1px solid #e1e1e2;border-bottom:1px solid #e1e1e2}.callout .callout-body-container{flex-grow:1}.callout.callout-style-simple .callout-body{font-size:.9rem;font-weight:400}.callout.callout-style-default .callout-body{font-size:.9rem;font-weight:400}.callout:not(.no-icon).callout-titled.callout-style-simple .callout-body{padding-left:1.6em}.callout.callout-titled>.callout-header{padding-top:.2em;margin-bottom:-0.2em}.callout.callout-style-simple>div.callout-header{border-bottom:none;font-size:.9rem;font-weight:600;opacity:75%}.callout.callout-style-default>div.callout-header{border-bottom:none;font-weight:600;opacity:85%;font-size:.9rem;padding-left:.5em;padding-right:.5em}.callout.callout-style-default .callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default .callout-body>:first-child{padding-top:.5rem;margin-top:0}.callout>div.callout-header[data-bs-toggle=collapse]{cursor:pointer}.callout.callout-style-default .callout-header[aria-expanded=false],.callout.callout-style-default .callout-header[aria-expanded=true]{padding-top:0px;margin-bottom:0px;align-items:center}.callout.callout-titled .callout-body>:last-child:not(.sourceCode),.callout.callout-titled .callout-body>div>:last-child:not(.sourceCode){padding-bottom:.5rem;margin-bottom:0}.callout:not(.callout-titled) .callout-body>:first-child,.callout:not(.callout-titled) .callout-body>div>:first-child{margin-top:.25rem}.callout:not(.callout-titled) .callout-body>:last-child,.callout:not(.callout-titled) .callout-body>div>:last-child{margin-bottom:.2rem}.callout.callout-style-simple .callout-icon::before,.callout.callout-style-simple .callout-toggle::before{height:1rem;width:1rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.callout.callout-style-default .callout-icon::before,.callout.callout-style-default .callout-toggle::before{height:.9rem;width:.9rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:.9rem .9rem}.callout.callout-style-default .callout-toggle::before{margin-top:5px}.callout .callout-btn-toggle .callout-toggle::before{transition:transform .2s linear}.callout .callout-header[aria-expanded=false] .callout-toggle::before{transform:rotate(-90deg)}.callout .callout-header[aria-expanded=true] .callout-toggle::before{transform:none}.callout.callout-style-simple:not(.no-icon) div.callout-icon-container{padding-top:.2em;padding-right:.55em}.callout.callout-style-default:not(.no-icon) div.callout-icon-container{padding-top:.1em;padding-right:.35em}.callout.callout-style-default:not(.no-icon) div.callout-title-container{margin-top:-1px}.callout.callout-style-default.callout-caution:not(.no-icon) div.callout-icon-container{padding-top:.3em;padding-right:.35em}.callout>.callout-body>.callout-icon-container>.no-icon,.callout>.callout-header>.callout-icon-container>.no-icon{display:none}div.callout.callout{border-left-color:rgba(55,58,60,.75)}div.callout.callout-style-default>.callout-header{background-color:rgba(55,58,60,.75)}div.callout-note.callout{border-left-color:#2780e3}div.callout-note.callout-style-default>.callout-header{background-color:#e9f2fc}div.callout-note:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-tip.callout{border-left-color:#3fb618}div.callout-tip.callout-style-default>.callout-header{background-color:#ecf8e8}div.callout-tip:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-warning.callout{border-left-color:#ff7518}div.callout-warning.callout-style-default>.callout-header{background-color:#fff1e8}div.callout-warning:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-caution.callout{border-left-color:#f0ad4e}div.callout-caution.callout-style-default>.callout-header{background-color:#fef7ed}div.callout-caution:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-important.callout{border-left-color:#ff0039}div.callout-important.callout-style-default>.callout-header{background-color:#ffe6eb}div.callout-important:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important .callout-toggle::before{background-image:url('data:image/svg+xml,')}.quarto-toggle-container{display:flex;align-items:center}.quarto-reader-toggle .bi::before,.quarto-color-scheme-toggle .bi::before{display:inline-block;height:1rem;width:1rem;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.sidebar-navigation{padding-left:20px}.navbar{background-color:#f8f9fa;color:#545555}.navbar .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.quarto-sidebar-toggle{border-color:#e1e1e2;border-bottom-left-radius:.375rem;border-bottom-right-radius:.375rem;border-style:solid;border-width:1px;overflow:hidden;border-top-width:0px;padding-top:0px !important}.quarto-sidebar-toggle-title{cursor:pointer;padding-bottom:2px;margin-left:.25em;text-align:center;font-weight:400;font-size:.775em}#quarto-content .quarto-sidebar-toggle{background:#fafafa}#quarto-content .quarto-sidebar-toggle-title{color:#373a3c}.quarto-sidebar-toggle-icon{color:#e1e1e2;margin-right:.5em;float:right;transition:transform .2s ease}.quarto-sidebar-toggle-icon::before{padding-top:5px}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-icon{transform:rotate(-180deg)}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-title{border-bottom:solid #e1e1e2 1px}.quarto-sidebar-toggle-contents{background-color:#fff;padding-right:10px;padding-left:10px;margin-top:0px !important;transition:max-height .5s ease}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-contents{padding-top:1em;padding-bottom:10px}@media(max-width: 767.98px){.sidebar-menu-container{padding-bottom:5em}}.quarto-sidebar-toggle:not(.expanded) .quarto-sidebar-toggle-contents{padding-top:0px !important;padding-bottom:0px}nav[role=doc-toc]{z-index:1020}#quarto-sidebar>*,nav[role=doc-toc]>*{transition:opacity .1s ease,border .1s ease}#quarto-sidebar.slow>*,nav[role=doc-toc].slow>*{transition:opacity .4s ease,border .4s ease}.quarto-color-scheme-toggle:not(.alternate).top-right .bi::before{background-image:url('data:image/svg+xml,')}.quarto-color-scheme-toggle.alternate.top-right .bi::before{background-image:url('data:image/svg+xml,')}#quarto-appendix.default{border-top:1px solid #e1e1e2}#quarto-appendix.default{background-color:#fff;padding-top:1.5em;margin-top:2em;z-index:998}#quarto-appendix.default .quarto-appendix-heading{margin-top:0;line-height:1.4em;font-weight:600;opacity:.9;border-bottom:none;margin-bottom:0}#quarto-appendix.default .footnotes ol,#quarto-appendix.default .footnotes ol li>p:last-of-type,#quarto-appendix.default .quarto-appendix-contents>p:last-of-type{margin-bottom:0}#quarto-appendix.default .footnotes ol{margin-left:.5em}#quarto-appendix.default .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #e1e1e2;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-bibtex code.sourceCode{white-space:pre-wrap}#quarto-appendix.default .quarto-appendix-citeas{font-size:.9em;padding:1em;border:solid 1px #e1e1e2;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-heading{font-size:1em !important}#quarto-appendix.default *[role=doc-endnotes]>ol,#quarto-appendix.default .quarto-appendix-contents>*:not(h2):not(.h2){font-size:.9em}#quarto-appendix.default section{padding-bottom:1.5em}#quarto-appendix.default section *[role=doc-endnotes],#quarto-appendix.default section>*:not(a){opacity:.9;word-wrap:break-word}.btn.btn-quarto,div.cell-output-display .btn-quarto{--bs-btn-color: #cbcccc;--bs-btn-bg: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #cbcccc;--bs-btn-hover-bg: #555859;--bs-btn-hover-border-color: #4b4e50;--bs-btn-focus-shadow-rgb: 77, 80, 82;--bs-btn-active-color: #fff;--bs-btn-active-bg: #5f6163;--bs-btn-active-border-color: #4b4e50;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #373a3c;--bs-btn-disabled-border-color: #373a3c}nav.quarto-secondary-nav.color-navbar{background-color:#f8f9fa;color:#545555}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#545555}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner{margin-bottom:0;padding-bottom:1em}body.nav-sidebar #title-block-header{margin-block-end:0}}p.subtitle{margin-top:.25em;margin-bottom:.5em}code a:any-link{color:inherit;text-decoration-color:#868e96}/*! light */div.observablehq table thead tr th{background-color:var(--bs-body-bg)}input,button,select,optgroup,textarea{background-color:var(--bs-body-bg)}.code-annotated .code-copy-button{margin-right:1.25em;margin-top:0;padding-bottom:0;padding-top:3px}.code-annotation-gutter-bg{background-color:#fff}.code-annotation-gutter{background-color:rgba(233,236,239,.65)}.code-annotation-gutter,.code-annotation-gutter-bg{height:100%;width:calc(20px + .5em);position:absolute;top:0;right:0}dl.code-annotation-container-grid dt{margin-right:1em;margin-top:.25rem}dl.code-annotation-container-grid dt{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;color:#4f5457;border:solid #4f5457 1px;border-radius:50%;height:22px;width:22px;line-height:22px;font-size:11px;text-align:center;vertical-align:middle;text-decoration:none}dl.code-annotation-container-grid dt[data-target-cell]{cursor:pointer}dl.code-annotation-container-grid dt[data-target-cell].code-annotation-active{color:#fff;border:solid #aaa 1px;background-color:#aaa}pre.code-annotation-code{padding-top:0;padding-bottom:0}pre.code-annotation-code code{z-index:3}#code-annotation-line-highlight-gutter{width:100%;border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}#code-annotation-line-highlight{margin-left:-4em;width:calc(100% + 4em);border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}code.sourceCode .code-annotation-anchor.code-annotation-active{background-color:var(--quarto-hl-normal-color, #aaaaaa);border:solid var(--quarto-hl-normal-color, #aaaaaa) 1px;color:#e9ecef;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;color:var(--quarto-hl-co-color);border:solid var(--quarto-hl-co-color) 1px;border-radius:50%;height:18px;width:18px;font-size:9px;margin-top:2px}code.sourceCode button.code-annotation-anchor{padding:2px;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none}code.sourceCode a.code-annotation-anchor{line-height:18px;text-align:center;vertical-align:middle;cursor:default;text-decoration:none}@media print{.page-columns .column-screen-inset{grid-column:page-start-inset/page-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;opacity:.999}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;opacity:.999}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#f8f9fa;z-index:998;opacity:.999;margin-bottom:1em}}.quarto-video{margin-bottom:1em}.table{border-top:1px solid #d7d8d8;border-bottom:1px solid #d7d8d8}.table>thead{border-top-width:0;border-bottom:1px solid #9b9d9e}.table a{word-break:break-word}.table>:not(caption)>*>*{background-color:unset;color:unset}#quarto-document-content .crosstalk-input .checkbox input[type=checkbox],#quarto-document-content .crosstalk-input .checkbox-inline input[type=checkbox]{position:unset;margin-top:unset;margin-left:unset}#quarto-document-content .row{margin-left:unset;margin-right:unset}.quarto-xref{white-space:nowrap}#quarto-draft-alert{margin-top:0px;margin-bottom:0px;padding:.3em;text-align:center;font-size:.9em}#quarto-draft-alert i{margin-right:.3em}#quarto-back-to-top{z-index:1000}pre{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:0.875em;font-weight:400}pre code{font-family:inherit;font-size:inherit;font-weight:inherit}code{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:0.875em;font-weight:400}a{background-color:rgba(0,0,0,0);font-weight:400;text-decoration:underline}a.external:after{content:"";background-image:url('data:image/svg+xml,');background-size:contain;background-repeat:no-repeat;background-position:center center;margin-left:.2em;padding-right:.75em}div.sourceCode code a.external:after{content:none}a.external:after:hover{cursor:pointer}.quarto-ext-icon{display:inline-block;font-size:.75em;padding-left:.3em}.code-with-filename .code-with-filename-file{margin-bottom:0;padding-bottom:2px;padding-top:2px;padding-left:.7em;border:var(--quarto-border-width) solid var(--quarto-border-color);border-radius:var(--quarto-border-radius);border-bottom:0;border-bottom-left-radius:0%;border-bottom-right-radius:0%}.code-with-filename div.sourceCode,.reveal .code-with-filename div.sourceCode{margin-top:0;border-top-left-radius:0%;border-top-right-radius:0%}.code-with-filename .code-with-filename-file pre{margin-bottom:0}.code-with-filename .code-with-filename-file{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.quarto-title-banner{margin-bottom:1em;color:#545555;background:#f8f9fa}.quarto-title-banner a{color:#545555}.quarto-title-banner h1,.quarto-title-banner .h1,.quarto-title-banner h2,.quarto-title-banner .h2{color:#545555}.quarto-title-banner .code-tools-button{color:#878888}.quarto-title-banner .code-tools-button:hover{color:#545555}.quarto-title-banner .code-tools-button>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .quarto-title .title{font-weight:600}.quarto-title-banner .quarto-categories{margin-top:.75em}@media(min-width: 992px){.quarto-title-banner{padding-top:2.5em;padding-bottom:2.5em}}@media(max-width: 991.98px){.quarto-title-banner{padding-top:1em;padding-bottom:1em}}@media(max-width: 767.98px){body.hypothesis-enabled #title-block-header>*{padding-right:20px}}main.quarto-banner-title-block>section:first-child>h2,main.quarto-banner-title-block>section:first-child>.h2,main.quarto-banner-title-block>section:first-child>h3,main.quarto-banner-title-block>section:first-child>.h3,main.quarto-banner-title-block>section:first-child>h4,main.quarto-banner-title-block>section:first-child>.h4{margin-top:0}.quarto-title .quarto-categories{display:flex;flex-wrap:wrap;row-gap:.5em;column-gap:.4em;padding-bottom:.5em;margin-top:.75em}.quarto-title .quarto-categories .quarto-category{padding:.25em .75em;font-size:.65em;text-transform:uppercase;border:solid 1px;border-radius:.375rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}.quarto-title-meta-container{display:grid;grid-template-columns:1fr auto}.quarto-title-meta-column-end{display:flex;flex-direction:column;padding-left:1em}.quarto-title-meta-column-end a .bi{margin-right:.3em}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:repeat(2, 1fr);grid-column-gap:1em}#title-block-header.quarto-title-block.default .quarto-title .title{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-author-orcid img{margin-top:-0.2em;height:.8em;width:.8em}#title-block-header.quarto-title-block.default .quarto-title-author-email{opacity:.7}#title-block-header.quarto-title-block.default .quarto-description p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p,#title-block-header.quarto-title-block.default .quarto-title-authors p,#title-block-header.quarto-title-block.default .quarto-title-affiliations p{margin-bottom:.1em}#title-block-header.quarto-title-block.default .quarto-title-meta-heading{text-transform:uppercase;margin-top:1em;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-contents{font-size:.9em}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.1em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#title-block-header.quarto-title-block.default .keywords,#title-block-header.quarto-title-block.default .description,#title-block-header.quarto-title-block.default .abstract{margin-top:0}#title-block-header.quarto-title-block.default .keywords>p,#title-block-header.quarto-title-block.default .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#title-block-header.quarto-title-block.default .keywords>p:last-of-type,#title-block-header.quarto-title-block.default .description>p:last-of-type,#title-block-header.quarto-title-block.default .abstract>p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .keywords .block-title,#title-block-header.quarto-title-block.default .description .block-title,#title-block-header.quarto-title-block.default .abstract .block-title{margin-top:1em;text-transform:uppercase;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-author{display:grid;grid-template-columns:minmax(max-content, 1fr) 1fr;grid-column-gap:1em}.quarto-title-tools-only{display:flex;justify-content:right}body{-webkit-font-smoothing:antialiased}.badge.bg-light{color:#373a3c}.progress .progress-bar{font-size:8px;line-height:8px}:root{--quarto-scss-export-white: #fff;--quarto-scss-export-gray-100: #f8f9fa;--quarto-scss-export-gray-200: #e9ecef;--quarto-scss-export-gray-300: #dee2e6;--quarto-scss-export-gray-400: #ced4da;--quarto-scss-export-gray-500: #adb5bd;--quarto-scss-export-gray-600: #868e96;--quarto-scss-export-gray-700: #495057;--quarto-scss-export-gray-800: #373a3c;--quarto-scss-export-gray-900: #212529;--quarto-scss-export-black: #000;--quarto-scss-export-blue: #2780e3;--quarto-scss-export-indigo: #6610f2;--quarto-scss-export-purple: #613d7c;--quarto-scss-export-pink: #e83e8c;--quarto-scss-export-red: #ff0039;--quarto-scss-export-orange: #f0ad4e;--quarto-scss-export-yellow: #ff7518;--quarto-scss-export-green: #3fb618;--quarto-scss-export-teal: #20c997;--quarto-scss-export-cyan: #9954bb;--quarto-scss-export-primary: #2780e3;--quarto-scss-export-secondary: #373a3c;--quarto-scss-export-success: #3fb618;--quarto-scss-export-info: #9954bb;--quarto-scss-export-warning: #ff7518;--quarto-scss-export-danger: #ff0039;--quarto-scss-export-light: #f8f9fa;--quarto-scss-export-dark: #373a3c;--quarto-scss-export-body-color: #373a3c;--quarto-scss-export-title-banner-color: ;--quarto-scss-export-title-banner-bg: ;--quarto-scss-export-btn-code-copy-color: #5E5E5E;--quarto-scss-export-btn-code-copy-color-active: #4758AB;--quarto-scss-export-sidebar-bg: #fff;--quarto-scss-export-link-color: #2761e3;--quarto-scss-export-link-color-bg: transparent;--quarto-scss-export-code-color: #7d12ba;--quarto-scss-export-code-bg: #f8f9fa;--quarto-scss-export-toc-color: #2761e3;--quarto-scss-export-toc-active-border: #2761e3;--quarto-scss-export-toc-inactive-border: #e9ecef;--quarto-scss-export-navbar-default: #2780e3;--quarto-scss-export-navbar-hl-override: false;--quarto-scss-export-navbar-bg: #f8f9fa;--quarto-scss-export-btn-bg: #373a3c;--quarto-scss-export-btn-fg: #cbcccc;--quarto-scss-export-body-contrast-bg: #fff;--quarto-scss-export-body-contrast-color: #373a3c;--quarto-scss-export-navbar-fg: #545555;--quarto-scss-export-navbar-hl: #1f4eb6;--quarto-scss-export-navbar-brand: #545555;--quarto-scss-export-navbar-brand-hl: #1f4eb6;--quarto-scss-export-navbar-toggler-border-color: rgba(84, 85, 85, 0);--quarto-scss-export-navbar-hover-color: rgba(31, 78, 182, 0.8);--quarto-scss-export-navbar-disabled-color: rgba(84, 85, 85, 0.75);--quarto-scss-export-sidebar-fg: #595959;--quarto-scss-export-sidebar-hl: ;--quarto-scss-export-title-block-color: #373a3c;--quarto-scss-export-title-block-contast-color: #fff;--quarto-scss-export-footer-bg: #fff;--quarto-scss-export-footer-fg: #757575;--quarto-scss-export-popover-bg: #fff;--quarto-scss-export-input-bg: #fff;--quarto-scss-export-input-border-color: #e1e1e2;--quarto-scss-export-code-annotation-higlight-color: rgba(170, 170, 170, 0.2666666667);--quarto-scss-export-code-annotation-higlight-bg: rgba(170, 170, 170, 0.1333333333);--quarto-scss-export-table-group-separator-color: #9b9d9e;--quarto-scss-export-table-group-separator-color-lighter: #d7d8d8;--quarto-scss-export-link-decoration: underline;--quarto-scss-export-border-color: #e1e1e2;--quarto-scss-export-table-border-color: #e1e1e2;--quarto-scss-export-color-contrast-dark: #000;--quarto-scss-export-color-contrast-light: #fff;--quarto-scss-export-blue-100: #d4e6f9;--quarto-scss-export-blue-200: #a9ccf4;--quarto-scss-export-blue-300: #7db3ee;--quarto-scss-export-blue-400: #5299e9;--quarto-scss-export-blue-500: #2780e3;--quarto-scss-export-blue-600: #1f66b6;--quarto-scss-export-blue-700: #174d88;--quarto-scss-export-blue-800: #10335b;--quarto-scss-export-blue-900: #081a2d;--quarto-scss-export-indigo-100: #e0cffc;--quarto-scss-export-indigo-200: #c29ffa;--quarto-scss-export-indigo-300: #a370f7;--quarto-scss-export-indigo-400: #8540f5;--quarto-scss-export-indigo-500: #6610f2;--quarto-scss-export-indigo-600: #520dc2;--quarto-scss-export-indigo-700: #3d0a91;--quarto-scss-export-indigo-800: #290661;--quarto-scss-export-indigo-900: #140330;--quarto-scss-export-purple-100: #dfd8e5;--quarto-scss-export-purple-200: #c0b1cb;--quarto-scss-export-purple-300: #a08bb0;--quarto-scss-export-purple-400: #816496;--quarto-scss-export-purple-500: #613d7c;--quarto-scss-export-purple-600: #4e3163;--quarto-scss-export-purple-700: #3a254a;--quarto-scss-export-purple-800: #271832;--quarto-scss-export-purple-900: #130c19;--quarto-scss-export-pink-100: #fad8e8;--quarto-scss-export-pink-200: #f6b2d1;--quarto-scss-export-pink-300: #f18bba;--quarto-scss-export-pink-400: #ed65a3;--quarto-scss-export-pink-500: #e83e8c;--quarto-scss-export-pink-600: #ba3270;--quarto-scss-export-pink-700: #8b2554;--quarto-scss-export-pink-800: #5d1938;--quarto-scss-export-pink-900: #2e0c1c;--quarto-scss-export-red-100: #ffccd7;--quarto-scss-export-red-200: #ff99b0;--quarto-scss-export-red-300: #ff6688;--quarto-scss-export-red-400: #ff3361;--quarto-scss-export-red-500: #ff0039;--quarto-scss-export-red-600: #cc002e;--quarto-scss-export-red-700: #990022;--quarto-scss-export-red-800: #660017;--quarto-scss-export-red-900: #33000b;--quarto-scss-export-orange-100: #fcefdc;--quarto-scss-export-orange-200: #f9deb8;--quarto-scss-export-orange-300: #f6ce95;--quarto-scss-export-orange-400: #f3bd71;--quarto-scss-export-orange-500: #f0ad4e;--quarto-scss-export-orange-600: #c08a3e;--quarto-scss-export-orange-700: #90682f;--quarto-scss-export-orange-800: #60451f;--quarto-scss-export-orange-900: #302310;--quarto-scss-export-yellow-100: #ffe3d1;--quarto-scss-export-yellow-200: #ffc8a3;--quarto-scss-export-yellow-300: #ffac74;--quarto-scss-export-yellow-400: #ff9146;--quarto-scss-export-yellow-500: #ff7518;--quarto-scss-export-yellow-600: #cc5e13;--quarto-scss-export-yellow-700: #99460e;--quarto-scss-export-yellow-800: #662f0a;--quarto-scss-export-yellow-900: #331705;--quarto-scss-export-green-100: #d9f0d1;--quarto-scss-export-green-200: #b2e2a3;--quarto-scss-export-green-300: #8cd374;--quarto-scss-export-green-400: #65c546;--quarto-scss-export-green-500: #3fb618;--quarto-scss-export-green-600: #329213;--quarto-scss-export-green-700: #266d0e;--quarto-scss-export-green-800: #19490a;--quarto-scss-export-green-900: #0d2405;--quarto-scss-export-teal-100: #d2f4ea;--quarto-scss-export-teal-200: #a6e9d5;--quarto-scss-export-teal-300: #79dfc1;--quarto-scss-export-teal-400: #4dd4ac;--quarto-scss-export-teal-500: #20c997;--quarto-scss-export-teal-600: #1aa179;--quarto-scss-export-teal-700: #13795b;--quarto-scss-export-teal-800: #0d503c;--quarto-scss-export-teal-900: #06281e;--quarto-scss-export-cyan-100: #ebddf1;--quarto-scss-export-cyan-200: #d6bbe4;--quarto-scss-export-cyan-300: #c298d6;--quarto-scss-export-cyan-400: #ad76c9;--quarto-scss-export-cyan-500: #9954bb;--quarto-scss-export-cyan-600: #7a4396;--quarto-scss-export-cyan-700: #5c3270;--quarto-scss-export-cyan-800: #3d224b;--quarto-scss-export-cyan-900: #1f1125;--quarto-scss-export-default: #373a3c;--quarto-scss-export-primary-text-emphasis: #10335b;--quarto-scss-export-secondary-text-emphasis: #161718;--quarto-scss-export-success-text-emphasis: #19490a;--quarto-scss-export-info-text-emphasis: #3d224b;--quarto-scss-export-warning-text-emphasis: #662f0a;--quarto-scss-export-danger-text-emphasis: #660017;--quarto-scss-export-light-text-emphasis: #495057;--quarto-scss-export-dark-text-emphasis: #495057;--quarto-scss-export-primary-bg-subtle: #d4e6f9;--quarto-scss-export-secondary-bg-subtle: #d7d8d8;--quarto-scss-export-success-bg-subtle: #d9f0d1;--quarto-scss-export-info-bg-subtle: #ebddf1;--quarto-scss-export-warning-bg-subtle: #ffe3d1;--quarto-scss-export-danger-bg-subtle: #ffccd7;--quarto-scss-export-light-bg-subtle: #fcfcfd;--quarto-scss-export-dark-bg-subtle: #ced4da;--quarto-scss-export-primary-border-subtle: #a9ccf4;--quarto-scss-export-secondary-border-subtle: #afb0b1;--quarto-scss-export-success-border-subtle: #b2e2a3;--quarto-scss-export-info-border-subtle: #d6bbe4;--quarto-scss-export-warning-border-subtle: #ffc8a3;--quarto-scss-export-danger-border-subtle: #ff99b0;--quarto-scss-export-light-border-subtle: #e9ecef;--quarto-scss-export-dark-border-subtle: #adb5bd;--quarto-scss-export-body-text-align: ;--quarto-scss-export-body-bg: #fff;--quarto-scss-export-body-secondary-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-body-secondary-bg: #e9ecef;--quarto-scss-export-body-tertiary-color: rgba(55, 58, 60, 0.5);--quarto-scss-export-body-tertiary-bg: #f8f9fa;--quarto-scss-export-body-emphasis-color: #000;--quarto-scss-export-link-hover-color: #1f4eb6;--quarto-scss-export-link-hover-decoration: ;--quarto-scss-export-border-color-translucent: rgba(0, 0, 0, 0.175);--quarto-scss-export-component-active-bg: #2780e3;--quarto-scss-export-component-active-color: #fff;--quarto-scss-export-focus-ring-color: rgba(39, 128, 227, 0.25);--quarto-scss-export-headings-font-family: ;--quarto-scss-export-headings-font-style: ;--quarto-scss-export-display-font-family: ;--quarto-scss-export-display-font-style: ;--quarto-scss-export-text-muted: rgba(55, 58, 60, 0.75);--quarto-scss-export-blockquote-footer-color: #868e96;--quarto-scss-export-blockquote-border-color: #e9ecef;--quarto-scss-export-hr-bg-color: ;--quarto-scss-export-hr-height: ;--quarto-scss-export-hr-border-color: ;--quarto-scss-export-legend-font-weight: ;--quarto-scss-export-mark-bg: #ffe3d1;--quarto-scss-export-table-color: #373a3c;--quarto-scss-export-table-bg: #fff;--quarto-scss-export-table-accent-bg: transparent;--quarto-scss-export-table-th-font-weight: ;--quarto-scss-export-table-striped-color: #373a3c;--quarto-scss-export-table-striped-bg: rgba(0, 0, 0, 0.05);--quarto-scss-export-table-active-color: #373a3c;--quarto-scss-export-table-active-bg: rgba(0, 0, 0, 0.1);--quarto-scss-export-table-hover-color: #373a3c;--quarto-scss-export-table-hover-bg: rgba(0, 0, 0, 0.075);--quarto-scss-export-table-caption-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-input-btn-font-family: ;--quarto-scss-export-input-btn-focus-color: rgba(39, 128, 227, 0.25);--quarto-scss-export-btn-color: #373a3c;--quarto-scss-export-btn-font-family: ;--quarto-scss-export-btn-white-space: ;--quarto-scss-export-btn-link-color: #2761e3;--quarto-scss-export-btn-link-hover-color: #1f4eb6;--quarto-scss-export-btn-link-disabled-color: #868e96;--quarto-scss-export-form-text-font-style: ;--quarto-scss-export-form-text-font-weight: ;--quarto-scss-export-form-text-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-form-label-font-size: ;--quarto-scss-export-form-label-font-style: ;--quarto-scss-export-form-label-font-weight: ;--quarto-scss-export-form-label-color: ;--quarto-scss-export-input-font-family: ;--quarto-scss-export-input-disabled-color: ;--quarto-scss-export-input-disabled-bg: #e9ecef;--quarto-scss-export-input-disabled-border-color: ;--quarto-scss-export-input-color: #373a3c;--quarto-scss-export-input-focus-bg: #fff;--quarto-scss-export-input-focus-border-color: #93c0f1;--quarto-scss-export-input-focus-color: #373a3c;--quarto-scss-export-input-placeholder-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-input-plaintext-color: #373a3c;--quarto-scss-export-form-check-label-color: ;--quarto-scss-export-form-check-transition: ;--quarto-scss-export-form-check-input-bg: #fff;--quarto-scss-export-form-check-input-focus-border: #93c0f1;--quarto-scss-export-form-check-input-checked-color: #fff;--quarto-scss-export-form-check-input-checked-bg-color: #2780e3;--quarto-scss-export-form-check-input-checked-border-color: #2780e3;--quarto-scss-export-form-check-input-indeterminate-color: #fff;--quarto-scss-export-form-check-input-indeterminate-bg-color: #2780e3;--quarto-scss-export-form-check-input-indeterminate-border-color: #2780e3;--quarto-scss-export-form-switch-color: rgba(0, 0, 0, 0.25);--quarto-scss-export-form-switch-focus-color: #93c0f1;--quarto-scss-export-form-switch-checked-color: #fff;--quarto-scss-export-input-group-addon-color: #373a3c;--quarto-scss-export-input-group-addon-bg: #f8f9fa;--quarto-scss-export-input-group-addon-border-color: #e1e1e2;--quarto-scss-export-form-select-font-family: ;--quarto-scss-export-form-select-color: #373a3c;--quarto-scss-export-form-select-bg: #fff;--quarto-scss-export-form-select-disabled-color: ;--quarto-scss-export-form-select-disabled-bg: #e9ecef;--quarto-scss-export-form-select-disabled-border-color: ;--quarto-scss-export-form-select-indicator-color: #373a3c;--quarto-scss-export-form-select-border-color: #e1e1e2;--quarto-scss-export-form-select-focus-border-color: #93c0f1;--quarto-scss-export-form-range-track-bg: #f8f9fa;--quarto-scss-export-form-range-thumb-bg: #2780e3;--quarto-scss-export-form-range-thumb-active-bg: #bed9f7;--quarto-scss-export-form-range-thumb-disabled-bg: rgba(55, 58, 60, 0.75);--quarto-scss-export-form-file-button-color: #373a3c;--quarto-scss-export-form-file-button-bg: #f8f9fa;--quarto-scss-export-form-file-button-hover-bg: #e9ecef;--quarto-scss-export-form-floating-label-disabled-color: #868e96;--quarto-scss-export-form-feedback-font-style: ;--quarto-scss-export-form-feedback-valid-color: #3fb618;--quarto-scss-export-form-feedback-invalid-color: #ff0039;--quarto-scss-export-form-feedback-icon-valid-color: #3fb618;--quarto-scss-export-form-feedback-icon-invalid-color: #ff0039;--quarto-scss-export-form-valid-color: #3fb618;--quarto-scss-export-form-valid-border-color: #3fb618;--quarto-scss-export-form-invalid-color: #ff0039;--quarto-scss-export-form-invalid-border-color: #ff0039;--quarto-scss-export-nav-link-font-size: ;--quarto-scss-export-nav-link-font-weight: ;--quarto-scss-export-nav-link-color: #2761e3;--quarto-scss-export-nav-link-hover-color: #1f4eb6;--quarto-scss-export-nav-link-disabled-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-nav-tabs-border-color: #e1e1e2;--quarto-scss-export-nav-tabs-link-hover-border-color: #e9ecef #e9ecef #e1e1e2;--quarto-scss-export-nav-tabs-link-active-color: #000;--quarto-scss-export-nav-tabs-link-active-bg: #fff;--quarto-scss-export-nav-pills-link-active-bg: #2780e3;--quarto-scss-export-nav-pills-link-active-color: #fff;--quarto-scss-export-nav-underline-link-active-color: #000;--quarto-scss-export-navbar-padding-x: ;--quarto-scss-export-navbar-light-contrast: #000;--quarto-scss-export-navbar-dark-contrast: #000;--quarto-scss-export-navbar-light-icon-color: rgba(0, 0, 0, 0.75);--quarto-scss-export-navbar-dark-icon-color: rgba(0, 0, 0, 0.75);--quarto-scss-export-dropdown-color: #373a3c;--quarto-scss-export-dropdown-bg: #fff;--quarto-scss-export-dropdown-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-dropdown-divider-bg: rgba(0, 0, 0, 0.175);--quarto-scss-export-dropdown-link-color: #373a3c;--quarto-scss-export-dropdown-link-hover-color: #373a3c;--quarto-scss-export-dropdown-link-hover-bg: #f8f9fa;--quarto-scss-export-dropdown-link-active-bg: #2780e3;--quarto-scss-export-dropdown-link-active-color: #fff;--quarto-scss-export-dropdown-link-disabled-color: rgba(55, 58, 60, 0.5);--quarto-scss-export-dropdown-header-color: #868e96;--quarto-scss-export-dropdown-dark-color: #dee2e6;--quarto-scss-export-dropdown-dark-bg: #373a3c;--quarto-scss-export-dropdown-dark-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-dropdown-dark-divider-bg: rgba(0, 0, 0, 0.175);--quarto-scss-export-dropdown-dark-box-shadow: ;--quarto-scss-export-dropdown-dark-link-color: #dee2e6;--quarto-scss-export-dropdown-dark-link-hover-color: #fff;--quarto-scss-export-dropdown-dark-link-hover-bg: rgba(255, 255, 255, 0.15);--quarto-scss-export-dropdown-dark-link-active-color: #fff;--quarto-scss-export-dropdown-dark-link-active-bg: #2780e3;--quarto-scss-export-dropdown-dark-link-disabled-color: #adb5bd;--quarto-scss-export-dropdown-dark-header-color: #adb5bd;--quarto-scss-export-pagination-color: #2761e3;--quarto-scss-export-pagination-bg: #fff;--quarto-scss-export-pagination-border-color: #e1e1e2;--quarto-scss-export-pagination-focus-color: #1f4eb6;--quarto-scss-export-pagination-focus-bg: #e9ecef;--quarto-scss-export-pagination-hover-color: #1f4eb6;--quarto-scss-export-pagination-hover-bg: #f8f9fa;--quarto-scss-export-pagination-hover-border-color: #e1e1e2;--quarto-scss-export-pagination-active-color: #fff;--quarto-scss-export-pagination-active-bg: #2780e3;--quarto-scss-export-pagination-active-border-color: #2780e3;--quarto-scss-export-pagination-disabled-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-pagination-disabled-bg: #e9ecef;--quarto-scss-export-pagination-disabled-border-color: #e1e1e2;--quarto-scss-export-card-title-color: ;--quarto-scss-export-card-subtitle-color: ;--quarto-scss-export-card-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-card-box-shadow: ;--quarto-scss-export-card-cap-bg: rgba(55, 58, 60, 0.03);--quarto-scss-export-card-cap-color: ;--quarto-scss-export-card-height: ;--quarto-scss-export-card-color: ;--quarto-scss-export-card-bg: #fff;--quarto-scss-export-accordion-color: #373a3c;--quarto-scss-export-accordion-bg: #fff;--quarto-scss-export-accordion-border-color: #e1e1e2;--quarto-scss-export-accordion-button-color: #373a3c;--quarto-scss-export-accordion-button-bg: #fff;--quarto-scss-export-accordion-button-active-bg: #d4e6f9;--quarto-scss-export-accordion-button-active-color: #10335b;--quarto-scss-export-accordion-button-focus-border-color: #93c0f1;--quarto-scss-export-accordion-icon-color: #373a3c;--quarto-scss-export-accordion-icon-active-color: #10335b;--quarto-scss-export-tooltip-color: #fff;--quarto-scss-export-tooltip-bg: #000;--quarto-scss-export-tooltip-margin: ;--quarto-scss-export-tooltip-arrow-color: ;--quarto-scss-export-form-feedback-tooltip-line-height: ;--quarto-scss-export-popover-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-popover-header-bg: #e9ecef;--quarto-scss-export-popover-body-color: #373a3c;--quarto-scss-export-popover-arrow-color: #fff;--quarto-scss-export-popover-arrow-outer-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-toast-color: ;--quarto-scss-export-toast-background-color: rgba(255, 255, 255, 0.85);--quarto-scss-export-toast-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-toast-header-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-toast-header-background-color: rgba(255, 255, 255, 0.85);--quarto-scss-export-toast-header-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-badge-color: #fff;--quarto-scss-export-modal-content-color: ;--quarto-scss-export-modal-content-bg: #fff;--quarto-scss-export-modal-content-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-modal-backdrop-bg: #000;--quarto-scss-export-modal-header-border-color: #e1e1e2;--quarto-scss-export-modal-footer-bg: ;--quarto-scss-export-modal-footer-border-color: #e1e1e2;--quarto-scss-export-progress-bg: #e9ecef;--quarto-scss-export-progress-bar-color: #fff;--quarto-scss-export-progress-bar-bg: #2780e3;--quarto-scss-export-list-group-color: #373a3c;--quarto-scss-export-list-group-bg: #fff;--quarto-scss-export-list-group-border-color: #e1e1e2;--quarto-scss-export-list-group-hover-bg: #f8f9fa;--quarto-scss-export-list-group-active-bg: #2780e3;--quarto-scss-export-list-group-active-color: #fff;--quarto-scss-export-list-group-active-border-color: #2780e3;--quarto-scss-export-list-group-disabled-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-list-group-disabled-bg: #fff;--quarto-scss-export-list-group-action-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-list-group-action-hover-color: #000;--quarto-scss-export-list-group-action-active-color: #373a3c;--quarto-scss-export-list-group-action-active-bg: #e9ecef;--quarto-scss-export-thumbnail-bg: #fff;--quarto-scss-export-thumbnail-border-color: #e1e1e2;--quarto-scss-export-figure-caption-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-breadcrumb-font-size: ;--quarto-scss-export-breadcrumb-bg: ;--quarto-scss-export-breadcrumb-divider-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-breadcrumb-active-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-breadcrumb-border-radius: ;--quarto-scss-export-carousel-control-color: #fff;--quarto-scss-export-carousel-indicator-active-bg: #fff;--quarto-scss-export-carousel-caption-color: #fff;--quarto-scss-export-carousel-dark-indicator-active-bg: #000;--quarto-scss-export-carousel-dark-caption-color: #000;--quarto-scss-export-btn-close-color: #000;--quarto-scss-export-offcanvas-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-offcanvas-bg-color: #fff;--quarto-scss-export-offcanvas-color: #373a3c;--quarto-scss-export-offcanvas-backdrop-bg: #000;--quarto-scss-export-code-color-dark: white;--quarto-scss-export-kbd-color: #fff;--quarto-scss-export-kbd-bg: #373a3c;--quarto-scss-export-nested-kbd-font-weight: ;--quarto-scss-export-pre-bg: #f8f9fa;--quarto-scss-export-pre-color: #000;--quarto-scss-export-bslib-page-sidebar-title-bg: #f8f9fa;--quarto-scss-export-bslib-page-sidebar-title-color: #000;--quarto-scss-export-bslib-sidebar-bg: rgba(var(--bs-emphasis-color-rgb, 0, 0, 0), 0.05);--quarto-scss-export-bslib-sidebar-toggle-bg: rgba(var(--bs-emphasis-color-rgb, 0, 0, 0), 0.1);--quarto-scss-export-mermaid-bg-color: #fff;--quarto-scss-export-mermaid-edge-color: #373a3c;--quarto-scss-export-mermaid-node-fg-color: #373a3c;--quarto-scss-export-mermaid-fg-color: #373a3c;--quarto-scss-export-mermaid-fg-color--lighter: #4f5457;--quarto-scss-export-mermaid-fg-color--lightest: #686d71;--quarto-scss-export-mermaid-label-bg-color: #fff;--quarto-scss-export-mermaid-label-fg-color: #2780e3;--quarto-scss-export-mermaid-node-bg-color: rgba(39, 128, 227, 0.1);--quarto-scss-export-code-block-border-left-color: #e1e1e2;--quarto-scss-export-callout-color-note: #2780e3;--quarto-scss-export-callout-color-tip: #3fb618;--quarto-scss-export-callout-color-important: #ff0039;--quarto-scss-export-callout-color-caution: #f0ad4e;--quarto-scss-export-callout-color-warning: #ff7518}
\ No newline at end of file
diff --git a/public/notebooks/isic-2024_kaggle_files/libs/bootstrap/bootstrap-icons.css b/public/notebooks/isic-2024_kaggle_files/libs/bootstrap/bootstrap-icons.css
new file mode 100644
index 0000000..285e444
--- /dev/null
+++ b/public/notebooks/isic-2024_kaggle_files/libs/bootstrap/bootstrap-icons.css
@@ -0,0 +1,2078 @@
+/*!
+ * Bootstrap Icons v1.11.1 (https://icons.getbootstrap.com/)
+ * Copyright 2019-2023 The Bootstrap Authors
+ * Licensed under MIT (https://github.com/twbs/icons/blob/main/LICENSE)
+ */
+
+@font-face {
+ font-display: block;
+ font-family: "bootstrap-icons";
+ src:
+url("./bootstrap-icons.woff?2820a3852bdb9a5832199cc61cec4e65") format("woff");
+}
+
+.bi::before,
+[class^="bi-"]::before,
+[class*=" bi-"]::before {
+ display: inline-block;
+ font-family: bootstrap-icons !important;
+ font-style: normal;
+ font-weight: normal !important;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+ vertical-align: -.125em;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.bi-123::before { content: "\f67f"; }
+.bi-alarm-fill::before { content: "\f101"; }
+.bi-alarm::before { content: "\f102"; }
+.bi-align-bottom::before { content: "\f103"; }
+.bi-align-center::before { content: "\f104"; }
+.bi-align-end::before { content: "\f105"; }
+.bi-align-middle::before { content: "\f106"; }
+.bi-align-start::before { content: "\f107"; }
+.bi-align-top::before { content: "\f108"; }
+.bi-alt::before { content: "\f109"; }
+.bi-app-indicator::before { content: "\f10a"; }
+.bi-app::before { content: "\f10b"; }
+.bi-archive-fill::before { content: "\f10c"; }
+.bi-archive::before { content: "\f10d"; }
+.bi-arrow-90deg-down::before { content: "\f10e"; }
+.bi-arrow-90deg-left::before { content: "\f10f"; }
+.bi-arrow-90deg-right::before { content: "\f110"; }
+.bi-arrow-90deg-up::before { content: "\f111"; }
+.bi-arrow-bar-down::before { content: "\f112"; }
+.bi-arrow-bar-left::before { content: "\f113"; }
+.bi-arrow-bar-right::before { content: "\f114"; }
+.bi-arrow-bar-up::before { content: "\f115"; }
+.bi-arrow-clockwise::before { content: "\f116"; }
+.bi-arrow-counterclockwise::before { content: "\f117"; }
+.bi-arrow-down-circle-fill::before { content: "\f118"; }
+.bi-arrow-down-circle::before { content: "\f119"; }
+.bi-arrow-down-left-circle-fill::before { content: "\f11a"; }
+.bi-arrow-down-left-circle::before { content: "\f11b"; }
+.bi-arrow-down-left-square-fill::before { content: "\f11c"; }
+.bi-arrow-down-left-square::before { content: "\f11d"; }
+.bi-arrow-down-left::before { content: "\f11e"; }
+.bi-arrow-down-right-circle-fill::before { content: "\f11f"; }
+.bi-arrow-down-right-circle::before { content: "\f120"; }
+.bi-arrow-down-right-square-fill::before { content: "\f121"; }
+.bi-arrow-down-right-square::before { content: "\f122"; }
+.bi-arrow-down-right::before { content: "\f123"; }
+.bi-arrow-down-short::before { content: "\f124"; }
+.bi-arrow-down-square-fill::before { content: "\f125"; }
+.bi-arrow-down-square::before { content: "\f126"; }
+.bi-arrow-down-up::before { content: "\f127"; }
+.bi-arrow-down::before { content: "\f128"; }
+.bi-arrow-left-circle-fill::before { content: "\f129"; }
+.bi-arrow-left-circle::before { content: "\f12a"; }
+.bi-arrow-left-right::before { content: "\f12b"; }
+.bi-arrow-left-short::before { content: "\f12c"; }
+.bi-arrow-left-square-fill::before { content: "\f12d"; }
+.bi-arrow-left-square::before { content: "\f12e"; }
+.bi-arrow-left::before { content: "\f12f"; }
+.bi-arrow-repeat::before { content: "\f130"; }
+.bi-arrow-return-left::before { content: "\f131"; }
+.bi-arrow-return-right::before { content: "\f132"; }
+.bi-arrow-right-circle-fill::before { content: "\f133"; }
+.bi-arrow-right-circle::before { content: "\f134"; }
+.bi-arrow-right-short::before { content: "\f135"; }
+.bi-arrow-right-square-fill::before { content: "\f136"; }
+.bi-arrow-right-square::before { content: "\f137"; }
+.bi-arrow-right::before { content: "\f138"; }
+.bi-arrow-up-circle-fill::before { content: "\f139"; }
+.bi-arrow-up-circle::before { content: "\f13a"; }
+.bi-arrow-up-left-circle-fill::before { content: "\f13b"; }
+.bi-arrow-up-left-circle::before { content: "\f13c"; }
+.bi-arrow-up-left-square-fill::before { content: "\f13d"; }
+.bi-arrow-up-left-square::before { content: "\f13e"; }
+.bi-arrow-up-left::before { content: "\f13f"; }
+.bi-arrow-up-right-circle-fill::before { content: "\f140"; }
+.bi-arrow-up-right-circle::before { content: "\f141"; }
+.bi-arrow-up-right-square-fill::before { content: "\f142"; }
+.bi-arrow-up-right-square::before { content: "\f143"; }
+.bi-arrow-up-right::before { content: "\f144"; }
+.bi-arrow-up-short::before { content: "\f145"; }
+.bi-arrow-up-square-fill::before { content: "\f146"; }
+.bi-arrow-up-square::before { content: "\f147"; }
+.bi-arrow-up::before { content: "\f148"; }
+.bi-arrows-angle-contract::before { content: "\f149"; }
+.bi-arrows-angle-expand::before { content: "\f14a"; }
+.bi-arrows-collapse::before { content: "\f14b"; }
+.bi-arrows-expand::before { content: "\f14c"; }
+.bi-arrows-fullscreen::before { content: "\f14d"; }
+.bi-arrows-move::before { content: "\f14e"; }
+.bi-aspect-ratio-fill::before { content: "\f14f"; }
+.bi-aspect-ratio::before { content: "\f150"; }
+.bi-asterisk::before { content: "\f151"; }
+.bi-at::before { content: "\f152"; }
+.bi-award-fill::before { content: "\f153"; }
+.bi-award::before { content: "\f154"; }
+.bi-back::before { content: "\f155"; }
+.bi-backspace-fill::before { content: "\f156"; }
+.bi-backspace-reverse-fill::before { content: "\f157"; }
+.bi-backspace-reverse::before { content: "\f158"; }
+.bi-backspace::before { content: "\f159"; }
+.bi-badge-3d-fill::before { content: "\f15a"; }
+.bi-badge-3d::before { content: "\f15b"; }
+.bi-badge-4k-fill::before { content: "\f15c"; }
+.bi-badge-4k::before { content: "\f15d"; }
+.bi-badge-8k-fill::before { content: "\f15e"; }
+.bi-badge-8k::before { content: "\f15f"; }
+.bi-badge-ad-fill::before { content: "\f160"; }
+.bi-badge-ad::before { content: "\f161"; }
+.bi-badge-ar-fill::before { content: "\f162"; }
+.bi-badge-ar::before { content: "\f163"; }
+.bi-badge-cc-fill::before { content: "\f164"; }
+.bi-badge-cc::before { content: "\f165"; }
+.bi-badge-hd-fill::before { content: "\f166"; }
+.bi-badge-hd::before { content: "\f167"; }
+.bi-badge-tm-fill::before { content: "\f168"; }
+.bi-badge-tm::before { content: "\f169"; }
+.bi-badge-vo-fill::before { content: "\f16a"; }
+.bi-badge-vo::before { content: "\f16b"; }
+.bi-badge-vr-fill::before { content: "\f16c"; }
+.bi-badge-vr::before { content: "\f16d"; }
+.bi-badge-wc-fill::before { content: "\f16e"; }
+.bi-badge-wc::before { content: "\f16f"; }
+.bi-bag-check-fill::before { content: "\f170"; }
+.bi-bag-check::before { content: "\f171"; }
+.bi-bag-dash-fill::before { content: "\f172"; }
+.bi-bag-dash::before { content: "\f173"; }
+.bi-bag-fill::before { content: "\f174"; }
+.bi-bag-plus-fill::before { content: "\f175"; }
+.bi-bag-plus::before { content: "\f176"; }
+.bi-bag-x-fill::before { content: "\f177"; }
+.bi-bag-x::before { content: "\f178"; }
+.bi-bag::before { content: "\f179"; }
+.bi-bar-chart-fill::before { content: "\f17a"; }
+.bi-bar-chart-line-fill::before { content: "\f17b"; }
+.bi-bar-chart-line::before { content: "\f17c"; }
+.bi-bar-chart-steps::before { content: "\f17d"; }
+.bi-bar-chart::before { content: "\f17e"; }
+.bi-basket-fill::before { content: "\f17f"; }
+.bi-basket::before { content: "\f180"; }
+.bi-basket2-fill::before { content: "\f181"; }
+.bi-basket2::before { content: "\f182"; }
+.bi-basket3-fill::before { content: "\f183"; }
+.bi-basket3::before { content: "\f184"; }
+.bi-battery-charging::before { content: "\f185"; }
+.bi-battery-full::before { content: "\f186"; }
+.bi-battery-half::before { content: "\f187"; }
+.bi-battery::before { content: "\f188"; }
+.bi-bell-fill::before { content: "\f189"; }
+.bi-bell::before { content: "\f18a"; }
+.bi-bezier::before { content: "\f18b"; }
+.bi-bezier2::before { content: "\f18c"; }
+.bi-bicycle::before { content: "\f18d"; }
+.bi-binoculars-fill::before { content: "\f18e"; }
+.bi-binoculars::before { content: "\f18f"; }
+.bi-blockquote-left::before { content: "\f190"; }
+.bi-blockquote-right::before { content: "\f191"; }
+.bi-book-fill::before { content: "\f192"; }
+.bi-book-half::before { content: "\f193"; }
+.bi-book::before { content: "\f194"; }
+.bi-bookmark-check-fill::before { content: "\f195"; }
+.bi-bookmark-check::before { content: "\f196"; }
+.bi-bookmark-dash-fill::before { content: "\f197"; }
+.bi-bookmark-dash::before { content: "\f198"; }
+.bi-bookmark-fill::before { content: "\f199"; }
+.bi-bookmark-heart-fill::before { content: "\f19a"; }
+.bi-bookmark-heart::before { content: "\f19b"; }
+.bi-bookmark-plus-fill::before { content: "\f19c"; }
+.bi-bookmark-plus::before { content: "\f19d"; }
+.bi-bookmark-star-fill::before { content: "\f19e"; }
+.bi-bookmark-star::before { content: "\f19f"; }
+.bi-bookmark-x-fill::before { content: "\f1a0"; }
+.bi-bookmark-x::before { content: "\f1a1"; }
+.bi-bookmark::before { content: "\f1a2"; }
+.bi-bookmarks-fill::before { content: "\f1a3"; }
+.bi-bookmarks::before { content: "\f1a4"; }
+.bi-bookshelf::before { content: "\f1a5"; }
+.bi-bootstrap-fill::before { content: "\f1a6"; }
+.bi-bootstrap-reboot::before { content: "\f1a7"; }
+.bi-bootstrap::before { content: "\f1a8"; }
+.bi-border-all::before { content: "\f1a9"; }
+.bi-border-bottom::before { content: "\f1aa"; }
+.bi-border-center::before { content: "\f1ab"; }
+.bi-border-inner::before { content: "\f1ac"; }
+.bi-border-left::before { content: "\f1ad"; }
+.bi-border-middle::before { content: "\f1ae"; }
+.bi-border-outer::before { content: "\f1af"; }
+.bi-border-right::before { content: "\f1b0"; }
+.bi-border-style::before { content: "\f1b1"; }
+.bi-border-top::before { content: "\f1b2"; }
+.bi-border-width::before { content: "\f1b3"; }
+.bi-border::before { content: "\f1b4"; }
+.bi-bounding-box-circles::before { content: "\f1b5"; }
+.bi-bounding-box::before { content: "\f1b6"; }
+.bi-box-arrow-down-left::before { content: "\f1b7"; }
+.bi-box-arrow-down-right::before { content: "\f1b8"; }
+.bi-box-arrow-down::before { content: "\f1b9"; }
+.bi-box-arrow-in-down-left::before { content: "\f1ba"; }
+.bi-box-arrow-in-down-right::before { content: "\f1bb"; }
+.bi-box-arrow-in-down::before { content: "\f1bc"; }
+.bi-box-arrow-in-left::before { content: "\f1bd"; }
+.bi-box-arrow-in-right::before { content: "\f1be"; }
+.bi-box-arrow-in-up-left::before { content: "\f1bf"; }
+.bi-box-arrow-in-up-right::before { content: "\f1c0"; }
+.bi-box-arrow-in-up::before { content: "\f1c1"; }
+.bi-box-arrow-left::before { content: "\f1c2"; }
+.bi-box-arrow-right::before { content: "\f1c3"; }
+.bi-box-arrow-up-left::before { content: "\f1c4"; }
+.bi-box-arrow-up-right::before { content: "\f1c5"; }
+.bi-box-arrow-up::before { content: "\f1c6"; }
+.bi-box-seam::before { content: "\f1c7"; }
+.bi-box::before { content: "\f1c8"; }
+.bi-braces::before { content: "\f1c9"; }
+.bi-bricks::before { content: "\f1ca"; }
+.bi-briefcase-fill::before { content: "\f1cb"; }
+.bi-briefcase::before { content: "\f1cc"; }
+.bi-brightness-alt-high-fill::before { content: "\f1cd"; }
+.bi-brightness-alt-high::before { content: "\f1ce"; }
+.bi-brightness-alt-low-fill::before { content: "\f1cf"; }
+.bi-brightness-alt-low::before { content: "\f1d0"; }
+.bi-brightness-high-fill::before { content: "\f1d1"; }
+.bi-brightness-high::before { content: "\f1d2"; }
+.bi-brightness-low-fill::before { content: "\f1d3"; }
+.bi-brightness-low::before { content: "\f1d4"; }
+.bi-broadcast-pin::before { content: "\f1d5"; }
+.bi-broadcast::before { content: "\f1d6"; }
+.bi-brush-fill::before { content: "\f1d7"; }
+.bi-brush::before { content: "\f1d8"; }
+.bi-bucket-fill::before { content: "\f1d9"; }
+.bi-bucket::before { content: "\f1da"; }
+.bi-bug-fill::before { content: "\f1db"; }
+.bi-bug::before { content: "\f1dc"; }
+.bi-building::before { content: "\f1dd"; }
+.bi-bullseye::before { content: "\f1de"; }
+.bi-calculator-fill::before { content: "\f1df"; }
+.bi-calculator::before { content: "\f1e0"; }
+.bi-calendar-check-fill::before { content: "\f1e1"; }
+.bi-calendar-check::before { content: "\f1e2"; }
+.bi-calendar-date-fill::before { content: "\f1e3"; }
+.bi-calendar-date::before { content: "\f1e4"; }
+.bi-calendar-day-fill::before { content: "\f1e5"; }
+.bi-calendar-day::before { content: "\f1e6"; }
+.bi-calendar-event-fill::before { content: "\f1e7"; }
+.bi-calendar-event::before { content: "\f1e8"; }
+.bi-calendar-fill::before { content: "\f1e9"; }
+.bi-calendar-minus-fill::before { content: "\f1ea"; }
+.bi-calendar-minus::before { content: "\f1eb"; }
+.bi-calendar-month-fill::before { content: "\f1ec"; }
+.bi-calendar-month::before { content: "\f1ed"; }
+.bi-calendar-plus-fill::before { content: "\f1ee"; }
+.bi-calendar-plus::before { content: "\f1ef"; }
+.bi-calendar-range-fill::before { content: "\f1f0"; }
+.bi-calendar-range::before { content: "\f1f1"; }
+.bi-calendar-week-fill::before { content: "\f1f2"; }
+.bi-calendar-week::before { content: "\f1f3"; }
+.bi-calendar-x-fill::before { content: "\f1f4"; }
+.bi-calendar-x::before { content: "\f1f5"; }
+.bi-calendar::before { content: "\f1f6"; }
+.bi-calendar2-check-fill::before { content: "\f1f7"; }
+.bi-calendar2-check::before { content: "\f1f8"; }
+.bi-calendar2-date-fill::before { content: "\f1f9"; }
+.bi-calendar2-date::before { content: "\f1fa"; }
+.bi-calendar2-day-fill::before { content: "\f1fb"; }
+.bi-calendar2-day::before { content: "\f1fc"; }
+.bi-calendar2-event-fill::before { content: "\f1fd"; }
+.bi-calendar2-event::before { content: "\f1fe"; }
+.bi-calendar2-fill::before { content: "\f1ff"; }
+.bi-calendar2-minus-fill::before { content: "\f200"; }
+.bi-calendar2-minus::before { content: "\f201"; }
+.bi-calendar2-month-fill::before { content: "\f202"; }
+.bi-calendar2-month::before { content: "\f203"; }
+.bi-calendar2-plus-fill::before { content: "\f204"; }
+.bi-calendar2-plus::before { content: "\f205"; }
+.bi-calendar2-range-fill::before { content: "\f206"; }
+.bi-calendar2-range::before { content: "\f207"; }
+.bi-calendar2-week-fill::before { content: "\f208"; }
+.bi-calendar2-week::before { content: "\f209"; }
+.bi-calendar2-x-fill::before { content: "\f20a"; }
+.bi-calendar2-x::before { content: "\f20b"; }
+.bi-calendar2::before { content: "\f20c"; }
+.bi-calendar3-event-fill::before { content: "\f20d"; }
+.bi-calendar3-event::before { content: "\f20e"; }
+.bi-calendar3-fill::before { content: "\f20f"; }
+.bi-calendar3-range-fill::before { content: "\f210"; }
+.bi-calendar3-range::before { content: "\f211"; }
+.bi-calendar3-week-fill::before { content: "\f212"; }
+.bi-calendar3-week::before { content: "\f213"; }
+.bi-calendar3::before { content: "\f214"; }
+.bi-calendar4-event::before { content: "\f215"; }
+.bi-calendar4-range::before { content: "\f216"; }
+.bi-calendar4-week::before { content: "\f217"; }
+.bi-calendar4::before { content: "\f218"; }
+.bi-camera-fill::before { content: "\f219"; }
+.bi-camera-reels-fill::before { content: "\f21a"; }
+.bi-camera-reels::before { content: "\f21b"; }
+.bi-camera-video-fill::before { content: "\f21c"; }
+.bi-camera-video-off-fill::before { content: "\f21d"; }
+.bi-camera-video-off::before { content: "\f21e"; }
+.bi-camera-video::before { content: "\f21f"; }
+.bi-camera::before { content: "\f220"; }
+.bi-camera2::before { content: "\f221"; }
+.bi-capslock-fill::before { content: "\f222"; }
+.bi-capslock::before { content: "\f223"; }
+.bi-card-checklist::before { content: "\f224"; }
+.bi-card-heading::before { content: "\f225"; }
+.bi-card-image::before { content: "\f226"; }
+.bi-card-list::before { content: "\f227"; }
+.bi-card-text::before { content: "\f228"; }
+.bi-caret-down-fill::before { content: "\f229"; }
+.bi-caret-down-square-fill::before { content: "\f22a"; }
+.bi-caret-down-square::before { content: "\f22b"; }
+.bi-caret-down::before { content: "\f22c"; }
+.bi-caret-left-fill::before { content: "\f22d"; }
+.bi-caret-left-square-fill::before { content: "\f22e"; }
+.bi-caret-left-square::before { content: "\f22f"; }
+.bi-caret-left::before { content: "\f230"; }
+.bi-caret-right-fill::before { content: "\f231"; }
+.bi-caret-right-square-fill::before { content: "\f232"; }
+.bi-caret-right-square::before { content: "\f233"; }
+.bi-caret-right::before { content: "\f234"; }
+.bi-caret-up-fill::before { content: "\f235"; }
+.bi-caret-up-square-fill::before { content: "\f236"; }
+.bi-caret-up-square::before { content: "\f237"; }
+.bi-caret-up::before { content: "\f238"; }
+.bi-cart-check-fill::before { content: "\f239"; }
+.bi-cart-check::before { content: "\f23a"; }
+.bi-cart-dash-fill::before { content: "\f23b"; }
+.bi-cart-dash::before { content: "\f23c"; }
+.bi-cart-fill::before { content: "\f23d"; }
+.bi-cart-plus-fill::before { content: "\f23e"; }
+.bi-cart-plus::before { content: "\f23f"; }
+.bi-cart-x-fill::before { content: "\f240"; }
+.bi-cart-x::before { content: "\f241"; }
+.bi-cart::before { content: "\f242"; }
+.bi-cart2::before { content: "\f243"; }
+.bi-cart3::before { content: "\f244"; }
+.bi-cart4::before { content: "\f245"; }
+.bi-cash-stack::before { content: "\f246"; }
+.bi-cash::before { content: "\f247"; }
+.bi-cast::before { content: "\f248"; }
+.bi-chat-dots-fill::before { content: "\f249"; }
+.bi-chat-dots::before { content: "\f24a"; }
+.bi-chat-fill::before { content: "\f24b"; }
+.bi-chat-left-dots-fill::before { content: "\f24c"; }
+.bi-chat-left-dots::before { content: "\f24d"; }
+.bi-chat-left-fill::before { content: "\f24e"; }
+.bi-chat-left-quote-fill::before { content: "\f24f"; }
+.bi-chat-left-quote::before { content: "\f250"; }
+.bi-chat-left-text-fill::before { content: "\f251"; }
+.bi-chat-left-text::before { content: "\f252"; }
+.bi-chat-left::before { content: "\f253"; }
+.bi-chat-quote-fill::before { content: "\f254"; }
+.bi-chat-quote::before { content: "\f255"; }
+.bi-chat-right-dots-fill::before { content: "\f256"; }
+.bi-chat-right-dots::before { content: "\f257"; }
+.bi-chat-right-fill::before { content: "\f258"; }
+.bi-chat-right-quote-fill::before { content: "\f259"; }
+.bi-chat-right-quote::before { content: "\f25a"; }
+.bi-chat-right-text-fill::before { content: "\f25b"; }
+.bi-chat-right-text::before { content: "\f25c"; }
+.bi-chat-right::before { content: "\f25d"; }
+.bi-chat-square-dots-fill::before { content: "\f25e"; }
+.bi-chat-square-dots::before { content: "\f25f"; }
+.bi-chat-square-fill::before { content: "\f260"; }
+.bi-chat-square-quote-fill::before { content: "\f261"; }
+.bi-chat-square-quote::before { content: "\f262"; }
+.bi-chat-square-text-fill::before { content: "\f263"; }
+.bi-chat-square-text::before { content: "\f264"; }
+.bi-chat-square::before { content: "\f265"; }
+.bi-chat-text-fill::before { content: "\f266"; }
+.bi-chat-text::before { content: "\f267"; }
+.bi-chat::before { content: "\f268"; }
+.bi-check-all::before { content: "\f269"; }
+.bi-check-circle-fill::before { content: "\f26a"; }
+.bi-check-circle::before { content: "\f26b"; }
+.bi-check-square-fill::before { content: "\f26c"; }
+.bi-check-square::before { content: "\f26d"; }
+.bi-check::before { content: "\f26e"; }
+.bi-check2-all::before { content: "\f26f"; }
+.bi-check2-circle::before { content: "\f270"; }
+.bi-check2-square::before { content: "\f271"; }
+.bi-check2::before { content: "\f272"; }
+.bi-chevron-bar-contract::before { content: "\f273"; }
+.bi-chevron-bar-down::before { content: "\f274"; }
+.bi-chevron-bar-expand::before { content: "\f275"; }
+.bi-chevron-bar-left::before { content: "\f276"; }
+.bi-chevron-bar-right::before { content: "\f277"; }
+.bi-chevron-bar-up::before { content: "\f278"; }
+.bi-chevron-compact-down::before { content: "\f279"; }
+.bi-chevron-compact-left::before { content: "\f27a"; }
+.bi-chevron-compact-right::before { content: "\f27b"; }
+.bi-chevron-compact-up::before { content: "\f27c"; }
+.bi-chevron-contract::before { content: "\f27d"; }
+.bi-chevron-double-down::before { content: "\f27e"; }
+.bi-chevron-double-left::before { content: "\f27f"; }
+.bi-chevron-double-right::before { content: "\f280"; }
+.bi-chevron-double-up::before { content: "\f281"; }
+.bi-chevron-down::before { content: "\f282"; }
+.bi-chevron-expand::before { content: "\f283"; }
+.bi-chevron-left::before { content: "\f284"; }
+.bi-chevron-right::before { content: "\f285"; }
+.bi-chevron-up::before { content: "\f286"; }
+.bi-circle-fill::before { content: "\f287"; }
+.bi-circle-half::before { content: "\f288"; }
+.bi-circle-square::before { content: "\f289"; }
+.bi-circle::before { content: "\f28a"; }
+.bi-clipboard-check::before { content: "\f28b"; }
+.bi-clipboard-data::before { content: "\f28c"; }
+.bi-clipboard-minus::before { content: "\f28d"; }
+.bi-clipboard-plus::before { content: "\f28e"; }
+.bi-clipboard-x::before { content: "\f28f"; }
+.bi-clipboard::before { content: "\f290"; }
+.bi-clock-fill::before { content: "\f291"; }
+.bi-clock-history::before { content: "\f292"; }
+.bi-clock::before { content: "\f293"; }
+.bi-cloud-arrow-down-fill::before { content: "\f294"; }
+.bi-cloud-arrow-down::before { content: "\f295"; }
+.bi-cloud-arrow-up-fill::before { content: "\f296"; }
+.bi-cloud-arrow-up::before { content: "\f297"; }
+.bi-cloud-check-fill::before { content: "\f298"; }
+.bi-cloud-check::before { content: "\f299"; }
+.bi-cloud-download-fill::before { content: "\f29a"; }
+.bi-cloud-download::before { content: "\f29b"; }
+.bi-cloud-drizzle-fill::before { content: "\f29c"; }
+.bi-cloud-drizzle::before { content: "\f29d"; }
+.bi-cloud-fill::before { content: "\f29e"; }
+.bi-cloud-fog-fill::before { content: "\f29f"; }
+.bi-cloud-fog::before { content: "\f2a0"; }
+.bi-cloud-fog2-fill::before { content: "\f2a1"; }
+.bi-cloud-fog2::before { content: "\f2a2"; }
+.bi-cloud-hail-fill::before { content: "\f2a3"; }
+.bi-cloud-hail::before { content: "\f2a4"; }
+.bi-cloud-haze-fill::before { content: "\f2a6"; }
+.bi-cloud-haze::before { content: "\f2a7"; }
+.bi-cloud-haze2-fill::before { content: "\f2a8"; }
+.bi-cloud-lightning-fill::before { content: "\f2a9"; }
+.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; }
+.bi-cloud-lightning-rain::before { content: "\f2ab"; }
+.bi-cloud-lightning::before { content: "\f2ac"; }
+.bi-cloud-minus-fill::before { content: "\f2ad"; }
+.bi-cloud-minus::before { content: "\f2ae"; }
+.bi-cloud-moon-fill::before { content: "\f2af"; }
+.bi-cloud-moon::before { content: "\f2b0"; }
+.bi-cloud-plus-fill::before { content: "\f2b1"; }
+.bi-cloud-plus::before { content: "\f2b2"; }
+.bi-cloud-rain-fill::before { content: "\f2b3"; }
+.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; }
+.bi-cloud-rain-heavy::before { content: "\f2b5"; }
+.bi-cloud-rain::before { content: "\f2b6"; }
+.bi-cloud-slash-fill::before { content: "\f2b7"; }
+.bi-cloud-slash::before { content: "\f2b8"; }
+.bi-cloud-sleet-fill::before { content: "\f2b9"; }
+.bi-cloud-sleet::before { content: "\f2ba"; }
+.bi-cloud-snow-fill::before { content: "\f2bb"; }
+.bi-cloud-snow::before { content: "\f2bc"; }
+.bi-cloud-sun-fill::before { content: "\f2bd"; }
+.bi-cloud-sun::before { content: "\f2be"; }
+.bi-cloud-upload-fill::before { content: "\f2bf"; }
+.bi-cloud-upload::before { content: "\f2c0"; }
+.bi-cloud::before { content: "\f2c1"; }
+.bi-clouds-fill::before { content: "\f2c2"; }
+.bi-clouds::before { content: "\f2c3"; }
+.bi-cloudy-fill::before { content: "\f2c4"; }
+.bi-cloudy::before { content: "\f2c5"; }
+.bi-code-slash::before { content: "\f2c6"; }
+.bi-code-square::before { content: "\f2c7"; }
+.bi-code::before { content: "\f2c8"; }
+.bi-collection-fill::before { content: "\f2c9"; }
+.bi-collection-play-fill::before { content: "\f2ca"; }
+.bi-collection-play::before { content: "\f2cb"; }
+.bi-collection::before { content: "\f2cc"; }
+.bi-columns-gap::before { content: "\f2cd"; }
+.bi-columns::before { content: "\f2ce"; }
+.bi-command::before { content: "\f2cf"; }
+.bi-compass-fill::before { content: "\f2d0"; }
+.bi-compass::before { content: "\f2d1"; }
+.bi-cone-striped::before { content: "\f2d2"; }
+.bi-cone::before { content: "\f2d3"; }
+.bi-controller::before { content: "\f2d4"; }
+.bi-cpu-fill::before { content: "\f2d5"; }
+.bi-cpu::before { content: "\f2d6"; }
+.bi-credit-card-2-back-fill::before { content: "\f2d7"; }
+.bi-credit-card-2-back::before { content: "\f2d8"; }
+.bi-credit-card-2-front-fill::before { content: "\f2d9"; }
+.bi-credit-card-2-front::before { content: "\f2da"; }
+.bi-credit-card-fill::before { content: "\f2db"; }
+.bi-credit-card::before { content: "\f2dc"; }
+.bi-crop::before { content: "\f2dd"; }
+.bi-cup-fill::before { content: "\f2de"; }
+.bi-cup-straw::before { content: "\f2df"; }
+.bi-cup::before { content: "\f2e0"; }
+.bi-cursor-fill::before { content: "\f2e1"; }
+.bi-cursor-text::before { content: "\f2e2"; }
+.bi-cursor::before { content: "\f2e3"; }
+.bi-dash-circle-dotted::before { content: "\f2e4"; }
+.bi-dash-circle-fill::before { content: "\f2e5"; }
+.bi-dash-circle::before { content: "\f2e6"; }
+.bi-dash-square-dotted::before { content: "\f2e7"; }
+.bi-dash-square-fill::before { content: "\f2e8"; }
+.bi-dash-square::before { content: "\f2e9"; }
+.bi-dash::before { content: "\f2ea"; }
+.bi-diagram-2-fill::before { content: "\f2eb"; }
+.bi-diagram-2::before { content: "\f2ec"; }
+.bi-diagram-3-fill::before { content: "\f2ed"; }
+.bi-diagram-3::before { content: "\f2ee"; }
+.bi-diamond-fill::before { content: "\f2ef"; }
+.bi-diamond-half::before { content: "\f2f0"; }
+.bi-diamond::before { content: "\f2f1"; }
+.bi-dice-1-fill::before { content: "\f2f2"; }
+.bi-dice-1::before { content: "\f2f3"; }
+.bi-dice-2-fill::before { content: "\f2f4"; }
+.bi-dice-2::before { content: "\f2f5"; }
+.bi-dice-3-fill::before { content: "\f2f6"; }
+.bi-dice-3::before { content: "\f2f7"; }
+.bi-dice-4-fill::before { content: "\f2f8"; }
+.bi-dice-4::before { content: "\f2f9"; }
+.bi-dice-5-fill::before { content: "\f2fa"; }
+.bi-dice-5::before { content: "\f2fb"; }
+.bi-dice-6-fill::before { content: "\f2fc"; }
+.bi-dice-6::before { content: "\f2fd"; }
+.bi-disc-fill::before { content: "\f2fe"; }
+.bi-disc::before { content: "\f2ff"; }
+.bi-discord::before { content: "\f300"; }
+.bi-display-fill::before { content: "\f301"; }
+.bi-display::before { content: "\f302"; }
+.bi-distribute-horizontal::before { content: "\f303"; }
+.bi-distribute-vertical::before { content: "\f304"; }
+.bi-door-closed-fill::before { content: "\f305"; }
+.bi-door-closed::before { content: "\f306"; }
+.bi-door-open-fill::before { content: "\f307"; }
+.bi-door-open::before { content: "\f308"; }
+.bi-dot::before { content: "\f309"; }
+.bi-download::before { content: "\f30a"; }
+.bi-droplet-fill::before { content: "\f30b"; }
+.bi-droplet-half::before { content: "\f30c"; }
+.bi-droplet::before { content: "\f30d"; }
+.bi-earbuds::before { content: "\f30e"; }
+.bi-easel-fill::before { content: "\f30f"; }
+.bi-easel::before { content: "\f310"; }
+.bi-egg-fill::before { content: "\f311"; }
+.bi-egg-fried::before { content: "\f312"; }
+.bi-egg::before { content: "\f313"; }
+.bi-eject-fill::before { content: "\f314"; }
+.bi-eject::before { content: "\f315"; }
+.bi-emoji-angry-fill::before { content: "\f316"; }
+.bi-emoji-angry::before { content: "\f317"; }
+.bi-emoji-dizzy-fill::before { content: "\f318"; }
+.bi-emoji-dizzy::before { content: "\f319"; }
+.bi-emoji-expressionless-fill::before { content: "\f31a"; }
+.bi-emoji-expressionless::before { content: "\f31b"; }
+.bi-emoji-frown-fill::before { content: "\f31c"; }
+.bi-emoji-frown::before { content: "\f31d"; }
+.bi-emoji-heart-eyes-fill::before { content: "\f31e"; }
+.bi-emoji-heart-eyes::before { content: "\f31f"; }
+.bi-emoji-laughing-fill::before { content: "\f320"; }
+.bi-emoji-laughing::before { content: "\f321"; }
+.bi-emoji-neutral-fill::before { content: "\f322"; }
+.bi-emoji-neutral::before { content: "\f323"; }
+.bi-emoji-smile-fill::before { content: "\f324"; }
+.bi-emoji-smile-upside-down-fill::before { content: "\f325"; }
+.bi-emoji-smile-upside-down::before { content: "\f326"; }
+.bi-emoji-smile::before { content: "\f327"; }
+.bi-emoji-sunglasses-fill::before { content: "\f328"; }
+.bi-emoji-sunglasses::before { content: "\f329"; }
+.bi-emoji-wink-fill::before { content: "\f32a"; }
+.bi-emoji-wink::before { content: "\f32b"; }
+.bi-envelope-fill::before { content: "\f32c"; }
+.bi-envelope-open-fill::before { content: "\f32d"; }
+.bi-envelope-open::before { content: "\f32e"; }
+.bi-envelope::before { content: "\f32f"; }
+.bi-eraser-fill::before { content: "\f330"; }
+.bi-eraser::before { content: "\f331"; }
+.bi-exclamation-circle-fill::before { content: "\f332"; }
+.bi-exclamation-circle::before { content: "\f333"; }
+.bi-exclamation-diamond-fill::before { content: "\f334"; }
+.bi-exclamation-diamond::before { content: "\f335"; }
+.bi-exclamation-octagon-fill::before { content: "\f336"; }
+.bi-exclamation-octagon::before { content: "\f337"; }
+.bi-exclamation-square-fill::before { content: "\f338"; }
+.bi-exclamation-square::before { content: "\f339"; }
+.bi-exclamation-triangle-fill::before { content: "\f33a"; }
+.bi-exclamation-triangle::before { content: "\f33b"; }
+.bi-exclamation::before { content: "\f33c"; }
+.bi-exclude::before { content: "\f33d"; }
+.bi-eye-fill::before { content: "\f33e"; }
+.bi-eye-slash-fill::before { content: "\f33f"; }
+.bi-eye-slash::before { content: "\f340"; }
+.bi-eye::before { content: "\f341"; }
+.bi-eyedropper::before { content: "\f342"; }
+.bi-eyeglasses::before { content: "\f343"; }
+.bi-facebook::before { content: "\f344"; }
+.bi-file-arrow-down-fill::before { content: "\f345"; }
+.bi-file-arrow-down::before { content: "\f346"; }
+.bi-file-arrow-up-fill::before { content: "\f347"; }
+.bi-file-arrow-up::before { content: "\f348"; }
+.bi-file-bar-graph-fill::before { content: "\f349"; }
+.bi-file-bar-graph::before { content: "\f34a"; }
+.bi-file-binary-fill::before { content: "\f34b"; }
+.bi-file-binary::before { content: "\f34c"; }
+.bi-file-break-fill::before { content: "\f34d"; }
+.bi-file-break::before { content: "\f34e"; }
+.bi-file-check-fill::before { content: "\f34f"; }
+.bi-file-check::before { content: "\f350"; }
+.bi-file-code-fill::before { content: "\f351"; }
+.bi-file-code::before { content: "\f352"; }
+.bi-file-diff-fill::before { content: "\f353"; }
+.bi-file-diff::before { content: "\f354"; }
+.bi-file-earmark-arrow-down-fill::before { content: "\f355"; }
+.bi-file-earmark-arrow-down::before { content: "\f356"; }
+.bi-file-earmark-arrow-up-fill::before { content: "\f357"; }
+.bi-file-earmark-arrow-up::before { content: "\f358"; }
+.bi-file-earmark-bar-graph-fill::before { content: "\f359"; }
+.bi-file-earmark-bar-graph::before { content: "\f35a"; }
+.bi-file-earmark-binary-fill::before { content: "\f35b"; }
+.bi-file-earmark-binary::before { content: "\f35c"; }
+.bi-file-earmark-break-fill::before { content: "\f35d"; }
+.bi-file-earmark-break::before { content: "\f35e"; }
+.bi-file-earmark-check-fill::before { content: "\f35f"; }
+.bi-file-earmark-check::before { content: "\f360"; }
+.bi-file-earmark-code-fill::before { content: "\f361"; }
+.bi-file-earmark-code::before { content: "\f362"; }
+.bi-file-earmark-diff-fill::before { content: "\f363"; }
+.bi-file-earmark-diff::before { content: "\f364"; }
+.bi-file-earmark-easel-fill::before { content: "\f365"; }
+.bi-file-earmark-easel::before { content: "\f366"; }
+.bi-file-earmark-excel-fill::before { content: "\f367"; }
+.bi-file-earmark-excel::before { content: "\f368"; }
+.bi-file-earmark-fill::before { content: "\f369"; }
+.bi-file-earmark-font-fill::before { content: "\f36a"; }
+.bi-file-earmark-font::before { content: "\f36b"; }
+.bi-file-earmark-image-fill::before { content: "\f36c"; }
+.bi-file-earmark-image::before { content: "\f36d"; }
+.bi-file-earmark-lock-fill::before { content: "\f36e"; }
+.bi-file-earmark-lock::before { content: "\f36f"; }
+.bi-file-earmark-lock2-fill::before { content: "\f370"; }
+.bi-file-earmark-lock2::before { content: "\f371"; }
+.bi-file-earmark-medical-fill::before { content: "\f372"; }
+.bi-file-earmark-medical::before { content: "\f373"; }
+.bi-file-earmark-minus-fill::before { content: "\f374"; }
+.bi-file-earmark-minus::before { content: "\f375"; }
+.bi-file-earmark-music-fill::before { content: "\f376"; }
+.bi-file-earmark-music::before { content: "\f377"; }
+.bi-file-earmark-person-fill::before { content: "\f378"; }
+.bi-file-earmark-person::before { content: "\f379"; }
+.bi-file-earmark-play-fill::before { content: "\f37a"; }
+.bi-file-earmark-play::before { content: "\f37b"; }
+.bi-file-earmark-plus-fill::before { content: "\f37c"; }
+.bi-file-earmark-plus::before { content: "\f37d"; }
+.bi-file-earmark-post-fill::before { content: "\f37e"; }
+.bi-file-earmark-post::before { content: "\f37f"; }
+.bi-file-earmark-ppt-fill::before { content: "\f380"; }
+.bi-file-earmark-ppt::before { content: "\f381"; }
+.bi-file-earmark-richtext-fill::before { content: "\f382"; }
+.bi-file-earmark-richtext::before { content: "\f383"; }
+.bi-file-earmark-ruled-fill::before { content: "\f384"; }
+.bi-file-earmark-ruled::before { content: "\f385"; }
+.bi-file-earmark-slides-fill::before { content: "\f386"; }
+.bi-file-earmark-slides::before { content: "\f387"; }
+.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; }
+.bi-file-earmark-spreadsheet::before { content: "\f389"; }
+.bi-file-earmark-text-fill::before { content: "\f38a"; }
+.bi-file-earmark-text::before { content: "\f38b"; }
+.bi-file-earmark-word-fill::before { content: "\f38c"; }
+.bi-file-earmark-word::before { content: "\f38d"; }
+.bi-file-earmark-x-fill::before { content: "\f38e"; }
+.bi-file-earmark-x::before { content: "\f38f"; }
+.bi-file-earmark-zip-fill::before { content: "\f390"; }
+.bi-file-earmark-zip::before { content: "\f391"; }
+.bi-file-earmark::before { content: "\f392"; }
+.bi-file-easel-fill::before { content: "\f393"; }
+.bi-file-easel::before { content: "\f394"; }
+.bi-file-excel-fill::before { content: "\f395"; }
+.bi-file-excel::before { content: "\f396"; }
+.bi-file-fill::before { content: "\f397"; }
+.bi-file-font-fill::before { content: "\f398"; }
+.bi-file-font::before { content: "\f399"; }
+.bi-file-image-fill::before { content: "\f39a"; }
+.bi-file-image::before { content: "\f39b"; }
+.bi-file-lock-fill::before { content: "\f39c"; }
+.bi-file-lock::before { content: "\f39d"; }
+.bi-file-lock2-fill::before { content: "\f39e"; }
+.bi-file-lock2::before { content: "\f39f"; }
+.bi-file-medical-fill::before { content: "\f3a0"; }
+.bi-file-medical::before { content: "\f3a1"; }
+.bi-file-minus-fill::before { content: "\f3a2"; }
+.bi-file-minus::before { content: "\f3a3"; }
+.bi-file-music-fill::before { content: "\f3a4"; }
+.bi-file-music::before { content: "\f3a5"; }
+.bi-file-person-fill::before { content: "\f3a6"; }
+.bi-file-person::before { content: "\f3a7"; }
+.bi-file-play-fill::before { content: "\f3a8"; }
+.bi-file-play::before { content: "\f3a9"; }
+.bi-file-plus-fill::before { content: "\f3aa"; }
+.bi-file-plus::before { content: "\f3ab"; }
+.bi-file-post-fill::before { content: "\f3ac"; }
+.bi-file-post::before { content: "\f3ad"; }
+.bi-file-ppt-fill::before { content: "\f3ae"; }
+.bi-file-ppt::before { content: "\f3af"; }
+.bi-file-richtext-fill::before { content: "\f3b0"; }
+.bi-file-richtext::before { content: "\f3b1"; }
+.bi-file-ruled-fill::before { content: "\f3b2"; }
+.bi-file-ruled::before { content: "\f3b3"; }
+.bi-file-slides-fill::before { content: "\f3b4"; }
+.bi-file-slides::before { content: "\f3b5"; }
+.bi-file-spreadsheet-fill::before { content: "\f3b6"; }
+.bi-file-spreadsheet::before { content: "\f3b7"; }
+.bi-file-text-fill::before { content: "\f3b8"; }
+.bi-file-text::before { content: "\f3b9"; }
+.bi-file-word-fill::before { content: "\f3ba"; }
+.bi-file-word::before { content: "\f3bb"; }
+.bi-file-x-fill::before { content: "\f3bc"; }
+.bi-file-x::before { content: "\f3bd"; }
+.bi-file-zip-fill::before { content: "\f3be"; }
+.bi-file-zip::before { content: "\f3bf"; }
+.bi-file::before { content: "\f3c0"; }
+.bi-files-alt::before { content: "\f3c1"; }
+.bi-files::before { content: "\f3c2"; }
+.bi-film::before { content: "\f3c3"; }
+.bi-filter-circle-fill::before { content: "\f3c4"; }
+.bi-filter-circle::before { content: "\f3c5"; }
+.bi-filter-left::before { content: "\f3c6"; }
+.bi-filter-right::before { content: "\f3c7"; }
+.bi-filter-square-fill::before { content: "\f3c8"; }
+.bi-filter-square::before { content: "\f3c9"; }
+.bi-filter::before { content: "\f3ca"; }
+.bi-flag-fill::before { content: "\f3cb"; }
+.bi-flag::before { content: "\f3cc"; }
+.bi-flower1::before { content: "\f3cd"; }
+.bi-flower2::before { content: "\f3ce"; }
+.bi-flower3::before { content: "\f3cf"; }
+.bi-folder-check::before { content: "\f3d0"; }
+.bi-folder-fill::before { content: "\f3d1"; }
+.bi-folder-minus::before { content: "\f3d2"; }
+.bi-folder-plus::before { content: "\f3d3"; }
+.bi-folder-symlink-fill::before { content: "\f3d4"; }
+.bi-folder-symlink::before { content: "\f3d5"; }
+.bi-folder-x::before { content: "\f3d6"; }
+.bi-folder::before { content: "\f3d7"; }
+.bi-folder2-open::before { content: "\f3d8"; }
+.bi-folder2::before { content: "\f3d9"; }
+.bi-fonts::before { content: "\f3da"; }
+.bi-forward-fill::before { content: "\f3db"; }
+.bi-forward::before { content: "\f3dc"; }
+.bi-front::before { content: "\f3dd"; }
+.bi-fullscreen-exit::before { content: "\f3de"; }
+.bi-fullscreen::before { content: "\f3df"; }
+.bi-funnel-fill::before { content: "\f3e0"; }
+.bi-funnel::before { content: "\f3e1"; }
+.bi-gear-fill::before { content: "\f3e2"; }
+.bi-gear-wide-connected::before { content: "\f3e3"; }
+.bi-gear-wide::before { content: "\f3e4"; }
+.bi-gear::before { content: "\f3e5"; }
+.bi-gem::before { content: "\f3e6"; }
+.bi-geo-alt-fill::before { content: "\f3e7"; }
+.bi-geo-alt::before { content: "\f3e8"; }
+.bi-geo-fill::before { content: "\f3e9"; }
+.bi-geo::before { content: "\f3ea"; }
+.bi-gift-fill::before { content: "\f3eb"; }
+.bi-gift::before { content: "\f3ec"; }
+.bi-github::before { content: "\f3ed"; }
+.bi-globe::before { content: "\f3ee"; }
+.bi-globe2::before { content: "\f3ef"; }
+.bi-google::before { content: "\f3f0"; }
+.bi-graph-down::before { content: "\f3f1"; }
+.bi-graph-up::before { content: "\f3f2"; }
+.bi-grid-1x2-fill::before { content: "\f3f3"; }
+.bi-grid-1x2::before { content: "\f3f4"; }
+.bi-grid-3x2-gap-fill::before { content: "\f3f5"; }
+.bi-grid-3x2-gap::before { content: "\f3f6"; }
+.bi-grid-3x2::before { content: "\f3f7"; }
+.bi-grid-3x3-gap-fill::before { content: "\f3f8"; }
+.bi-grid-3x3-gap::before { content: "\f3f9"; }
+.bi-grid-3x3::before { content: "\f3fa"; }
+.bi-grid-fill::before { content: "\f3fb"; }
+.bi-grid::before { content: "\f3fc"; }
+.bi-grip-horizontal::before { content: "\f3fd"; }
+.bi-grip-vertical::before { content: "\f3fe"; }
+.bi-hammer::before { content: "\f3ff"; }
+.bi-hand-index-fill::before { content: "\f400"; }
+.bi-hand-index-thumb-fill::before { content: "\f401"; }
+.bi-hand-index-thumb::before { content: "\f402"; }
+.bi-hand-index::before { content: "\f403"; }
+.bi-hand-thumbs-down-fill::before { content: "\f404"; }
+.bi-hand-thumbs-down::before { content: "\f405"; }
+.bi-hand-thumbs-up-fill::before { content: "\f406"; }
+.bi-hand-thumbs-up::before { content: "\f407"; }
+.bi-handbag-fill::before { content: "\f408"; }
+.bi-handbag::before { content: "\f409"; }
+.bi-hash::before { content: "\f40a"; }
+.bi-hdd-fill::before { content: "\f40b"; }
+.bi-hdd-network-fill::before { content: "\f40c"; }
+.bi-hdd-network::before { content: "\f40d"; }
+.bi-hdd-rack-fill::before { content: "\f40e"; }
+.bi-hdd-rack::before { content: "\f40f"; }
+.bi-hdd-stack-fill::before { content: "\f410"; }
+.bi-hdd-stack::before { content: "\f411"; }
+.bi-hdd::before { content: "\f412"; }
+.bi-headphones::before { content: "\f413"; }
+.bi-headset::before { content: "\f414"; }
+.bi-heart-fill::before { content: "\f415"; }
+.bi-heart-half::before { content: "\f416"; }
+.bi-heart::before { content: "\f417"; }
+.bi-heptagon-fill::before { content: "\f418"; }
+.bi-heptagon-half::before { content: "\f419"; }
+.bi-heptagon::before { content: "\f41a"; }
+.bi-hexagon-fill::before { content: "\f41b"; }
+.bi-hexagon-half::before { content: "\f41c"; }
+.bi-hexagon::before { content: "\f41d"; }
+.bi-hourglass-bottom::before { content: "\f41e"; }
+.bi-hourglass-split::before { content: "\f41f"; }
+.bi-hourglass-top::before { content: "\f420"; }
+.bi-hourglass::before { content: "\f421"; }
+.bi-house-door-fill::before { content: "\f422"; }
+.bi-house-door::before { content: "\f423"; }
+.bi-house-fill::before { content: "\f424"; }
+.bi-house::before { content: "\f425"; }
+.bi-hr::before { content: "\f426"; }
+.bi-hurricane::before { content: "\f427"; }
+.bi-image-alt::before { content: "\f428"; }
+.bi-image-fill::before { content: "\f429"; }
+.bi-image::before { content: "\f42a"; }
+.bi-images::before { content: "\f42b"; }
+.bi-inbox-fill::before { content: "\f42c"; }
+.bi-inbox::before { content: "\f42d"; }
+.bi-inboxes-fill::before { content: "\f42e"; }
+.bi-inboxes::before { content: "\f42f"; }
+.bi-info-circle-fill::before { content: "\f430"; }
+.bi-info-circle::before { content: "\f431"; }
+.bi-info-square-fill::before { content: "\f432"; }
+.bi-info-square::before { content: "\f433"; }
+.bi-info::before { content: "\f434"; }
+.bi-input-cursor-text::before { content: "\f435"; }
+.bi-input-cursor::before { content: "\f436"; }
+.bi-instagram::before { content: "\f437"; }
+.bi-intersect::before { content: "\f438"; }
+.bi-journal-album::before { content: "\f439"; }
+.bi-journal-arrow-down::before { content: "\f43a"; }
+.bi-journal-arrow-up::before { content: "\f43b"; }
+.bi-journal-bookmark-fill::before { content: "\f43c"; }
+.bi-journal-bookmark::before { content: "\f43d"; }
+.bi-journal-check::before { content: "\f43e"; }
+.bi-journal-code::before { content: "\f43f"; }
+.bi-journal-medical::before { content: "\f440"; }
+.bi-journal-minus::before { content: "\f441"; }
+.bi-journal-plus::before { content: "\f442"; }
+.bi-journal-richtext::before { content: "\f443"; }
+.bi-journal-text::before { content: "\f444"; }
+.bi-journal-x::before { content: "\f445"; }
+.bi-journal::before { content: "\f446"; }
+.bi-journals::before { content: "\f447"; }
+.bi-joystick::before { content: "\f448"; }
+.bi-justify-left::before { content: "\f449"; }
+.bi-justify-right::before { content: "\f44a"; }
+.bi-justify::before { content: "\f44b"; }
+.bi-kanban-fill::before { content: "\f44c"; }
+.bi-kanban::before { content: "\f44d"; }
+.bi-key-fill::before { content: "\f44e"; }
+.bi-key::before { content: "\f44f"; }
+.bi-keyboard-fill::before { content: "\f450"; }
+.bi-keyboard::before { content: "\f451"; }
+.bi-ladder::before { content: "\f452"; }
+.bi-lamp-fill::before { content: "\f453"; }
+.bi-lamp::before { content: "\f454"; }
+.bi-laptop-fill::before { content: "\f455"; }
+.bi-laptop::before { content: "\f456"; }
+.bi-layer-backward::before { content: "\f457"; }
+.bi-layer-forward::before { content: "\f458"; }
+.bi-layers-fill::before { content: "\f459"; }
+.bi-layers-half::before { content: "\f45a"; }
+.bi-layers::before { content: "\f45b"; }
+.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; }
+.bi-layout-sidebar-inset::before { content: "\f45d"; }
+.bi-layout-sidebar-reverse::before { content: "\f45e"; }
+.bi-layout-sidebar::before { content: "\f45f"; }
+.bi-layout-split::before { content: "\f460"; }
+.bi-layout-text-sidebar-reverse::before { content: "\f461"; }
+.bi-layout-text-sidebar::before { content: "\f462"; }
+.bi-layout-text-window-reverse::before { content: "\f463"; }
+.bi-layout-text-window::before { content: "\f464"; }
+.bi-layout-three-columns::before { content: "\f465"; }
+.bi-layout-wtf::before { content: "\f466"; }
+.bi-life-preserver::before { content: "\f467"; }
+.bi-lightbulb-fill::before { content: "\f468"; }
+.bi-lightbulb-off-fill::before { content: "\f469"; }
+.bi-lightbulb-off::before { content: "\f46a"; }
+.bi-lightbulb::before { content: "\f46b"; }
+.bi-lightning-charge-fill::before { content: "\f46c"; }
+.bi-lightning-charge::before { content: "\f46d"; }
+.bi-lightning-fill::before { content: "\f46e"; }
+.bi-lightning::before { content: "\f46f"; }
+.bi-link-45deg::before { content: "\f470"; }
+.bi-link::before { content: "\f471"; }
+.bi-linkedin::before { content: "\f472"; }
+.bi-list-check::before { content: "\f473"; }
+.bi-list-nested::before { content: "\f474"; }
+.bi-list-ol::before { content: "\f475"; }
+.bi-list-stars::before { content: "\f476"; }
+.bi-list-task::before { content: "\f477"; }
+.bi-list-ul::before { content: "\f478"; }
+.bi-list::before { content: "\f479"; }
+.bi-lock-fill::before { content: "\f47a"; }
+.bi-lock::before { content: "\f47b"; }
+.bi-mailbox::before { content: "\f47c"; }
+.bi-mailbox2::before { content: "\f47d"; }
+.bi-map-fill::before { content: "\f47e"; }
+.bi-map::before { content: "\f47f"; }
+.bi-markdown-fill::before { content: "\f480"; }
+.bi-markdown::before { content: "\f481"; }
+.bi-mask::before { content: "\f482"; }
+.bi-megaphone-fill::before { content: "\f483"; }
+.bi-megaphone::before { content: "\f484"; }
+.bi-menu-app-fill::before { content: "\f485"; }
+.bi-menu-app::before { content: "\f486"; }
+.bi-menu-button-fill::before { content: "\f487"; }
+.bi-menu-button-wide-fill::before { content: "\f488"; }
+.bi-menu-button-wide::before { content: "\f489"; }
+.bi-menu-button::before { content: "\f48a"; }
+.bi-menu-down::before { content: "\f48b"; }
+.bi-menu-up::before { content: "\f48c"; }
+.bi-mic-fill::before { content: "\f48d"; }
+.bi-mic-mute-fill::before { content: "\f48e"; }
+.bi-mic-mute::before { content: "\f48f"; }
+.bi-mic::before { content: "\f490"; }
+.bi-minecart-loaded::before { content: "\f491"; }
+.bi-minecart::before { content: "\f492"; }
+.bi-moisture::before { content: "\f493"; }
+.bi-moon-fill::before { content: "\f494"; }
+.bi-moon-stars-fill::before { content: "\f495"; }
+.bi-moon-stars::before { content: "\f496"; }
+.bi-moon::before { content: "\f497"; }
+.bi-mouse-fill::before { content: "\f498"; }
+.bi-mouse::before { content: "\f499"; }
+.bi-mouse2-fill::before { content: "\f49a"; }
+.bi-mouse2::before { content: "\f49b"; }
+.bi-mouse3-fill::before { content: "\f49c"; }
+.bi-mouse3::before { content: "\f49d"; }
+.bi-music-note-beamed::before { content: "\f49e"; }
+.bi-music-note-list::before { content: "\f49f"; }
+.bi-music-note::before { content: "\f4a0"; }
+.bi-music-player-fill::before { content: "\f4a1"; }
+.bi-music-player::before { content: "\f4a2"; }
+.bi-newspaper::before { content: "\f4a3"; }
+.bi-node-minus-fill::before { content: "\f4a4"; }
+.bi-node-minus::before { content: "\f4a5"; }
+.bi-node-plus-fill::before { content: "\f4a6"; }
+.bi-node-plus::before { content: "\f4a7"; }
+.bi-nut-fill::before { content: "\f4a8"; }
+.bi-nut::before { content: "\f4a9"; }
+.bi-octagon-fill::before { content: "\f4aa"; }
+.bi-octagon-half::before { content: "\f4ab"; }
+.bi-octagon::before { content: "\f4ac"; }
+.bi-option::before { content: "\f4ad"; }
+.bi-outlet::before { content: "\f4ae"; }
+.bi-paint-bucket::before { content: "\f4af"; }
+.bi-palette-fill::before { content: "\f4b0"; }
+.bi-palette::before { content: "\f4b1"; }
+.bi-palette2::before { content: "\f4b2"; }
+.bi-paperclip::before { content: "\f4b3"; }
+.bi-paragraph::before { content: "\f4b4"; }
+.bi-patch-check-fill::before { content: "\f4b5"; }
+.bi-patch-check::before { content: "\f4b6"; }
+.bi-patch-exclamation-fill::before { content: "\f4b7"; }
+.bi-patch-exclamation::before { content: "\f4b8"; }
+.bi-patch-minus-fill::before { content: "\f4b9"; }
+.bi-patch-minus::before { content: "\f4ba"; }
+.bi-patch-plus-fill::before { content: "\f4bb"; }
+.bi-patch-plus::before { content: "\f4bc"; }
+.bi-patch-question-fill::before { content: "\f4bd"; }
+.bi-patch-question::before { content: "\f4be"; }
+.bi-pause-btn-fill::before { content: "\f4bf"; }
+.bi-pause-btn::before { content: "\f4c0"; }
+.bi-pause-circle-fill::before { content: "\f4c1"; }
+.bi-pause-circle::before { content: "\f4c2"; }
+.bi-pause-fill::before { content: "\f4c3"; }
+.bi-pause::before { content: "\f4c4"; }
+.bi-peace-fill::before { content: "\f4c5"; }
+.bi-peace::before { content: "\f4c6"; }
+.bi-pen-fill::before { content: "\f4c7"; }
+.bi-pen::before { content: "\f4c8"; }
+.bi-pencil-fill::before { content: "\f4c9"; }
+.bi-pencil-square::before { content: "\f4ca"; }
+.bi-pencil::before { content: "\f4cb"; }
+.bi-pentagon-fill::before { content: "\f4cc"; }
+.bi-pentagon-half::before { content: "\f4cd"; }
+.bi-pentagon::before { content: "\f4ce"; }
+.bi-people-fill::before { content: "\f4cf"; }
+.bi-people::before { content: "\f4d0"; }
+.bi-percent::before { content: "\f4d1"; }
+.bi-person-badge-fill::before { content: "\f4d2"; }
+.bi-person-badge::before { content: "\f4d3"; }
+.bi-person-bounding-box::before { content: "\f4d4"; }
+.bi-person-check-fill::before { content: "\f4d5"; }
+.bi-person-check::before { content: "\f4d6"; }
+.bi-person-circle::before { content: "\f4d7"; }
+.bi-person-dash-fill::before { content: "\f4d8"; }
+.bi-person-dash::before { content: "\f4d9"; }
+.bi-person-fill::before { content: "\f4da"; }
+.bi-person-lines-fill::before { content: "\f4db"; }
+.bi-person-plus-fill::before { content: "\f4dc"; }
+.bi-person-plus::before { content: "\f4dd"; }
+.bi-person-square::before { content: "\f4de"; }
+.bi-person-x-fill::before { content: "\f4df"; }
+.bi-person-x::before { content: "\f4e0"; }
+.bi-person::before { content: "\f4e1"; }
+.bi-phone-fill::before { content: "\f4e2"; }
+.bi-phone-landscape-fill::before { content: "\f4e3"; }
+.bi-phone-landscape::before { content: "\f4e4"; }
+.bi-phone-vibrate-fill::before { content: "\f4e5"; }
+.bi-phone-vibrate::before { content: "\f4e6"; }
+.bi-phone::before { content: "\f4e7"; }
+.bi-pie-chart-fill::before { content: "\f4e8"; }
+.bi-pie-chart::before { content: "\f4e9"; }
+.bi-pin-angle-fill::before { content: "\f4ea"; }
+.bi-pin-angle::before { content: "\f4eb"; }
+.bi-pin-fill::before { content: "\f4ec"; }
+.bi-pin::before { content: "\f4ed"; }
+.bi-pip-fill::before { content: "\f4ee"; }
+.bi-pip::before { content: "\f4ef"; }
+.bi-play-btn-fill::before { content: "\f4f0"; }
+.bi-play-btn::before { content: "\f4f1"; }
+.bi-play-circle-fill::before { content: "\f4f2"; }
+.bi-play-circle::before { content: "\f4f3"; }
+.bi-play-fill::before { content: "\f4f4"; }
+.bi-play::before { content: "\f4f5"; }
+.bi-plug-fill::before { content: "\f4f6"; }
+.bi-plug::before { content: "\f4f7"; }
+.bi-plus-circle-dotted::before { content: "\f4f8"; }
+.bi-plus-circle-fill::before { content: "\f4f9"; }
+.bi-plus-circle::before { content: "\f4fa"; }
+.bi-plus-square-dotted::before { content: "\f4fb"; }
+.bi-plus-square-fill::before { content: "\f4fc"; }
+.bi-plus-square::before { content: "\f4fd"; }
+.bi-plus::before { content: "\f4fe"; }
+.bi-power::before { content: "\f4ff"; }
+.bi-printer-fill::before { content: "\f500"; }
+.bi-printer::before { content: "\f501"; }
+.bi-puzzle-fill::before { content: "\f502"; }
+.bi-puzzle::before { content: "\f503"; }
+.bi-question-circle-fill::before { content: "\f504"; }
+.bi-question-circle::before { content: "\f505"; }
+.bi-question-diamond-fill::before { content: "\f506"; }
+.bi-question-diamond::before { content: "\f507"; }
+.bi-question-octagon-fill::before { content: "\f508"; }
+.bi-question-octagon::before { content: "\f509"; }
+.bi-question-square-fill::before { content: "\f50a"; }
+.bi-question-square::before { content: "\f50b"; }
+.bi-question::before { content: "\f50c"; }
+.bi-rainbow::before { content: "\f50d"; }
+.bi-receipt-cutoff::before { content: "\f50e"; }
+.bi-receipt::before { content: "\f50f"; }
+.bi-reception-0::before { content: "\f510"; }
+.bi-reception-1::before { content: "\f511"; }
+.bi-reception-2::before { content: "\f512"; }
+.bi-reception-3::before { content: "\f513"; }
+.bi-reception-4::before { content: "\f514"; }
+.bi-record-btn-fill::before { content: "\f515"; }
+.bi-record-btn::before { content: "\f516"; }
+.bi-record-circle-fill::before { content: "\f517"; }
+.bi-record-circle::before { content: "\f518"; }
+.bi-record-fill::before { content: "\f519"; }
+.bi-record::before { content: "\f51a"; }
+.bi-record2-fill::before { content: "\f51b"; }
+.bi-record2::before { content: "\f51c"; }
+.bi-reply-all-fill::before { content: "\f51d"; }
+.bi-reply-all::before { content: "\f51e"; }
+.bi-reply-fill::before { content: "\f51f"; }
+.bi-reply::before { content: "\f520"; }
+.bi-rss-fill::before { content: "\f521"; }
+.bi-rss::before { content: "\f522"; }
+.bi-rulers::before { content: "\f523"; }
+.bi-save-fill::before { content: "\f524"; }
+.bi-save::before { content: "\f525"; }
+.bi-save2-fill::before { content: "\f526"; }
+.bi-save2::before { content: "\f527"; }
+.bi-scissors::before { content: "\f528"; }
+.bi-screwdriver::before { content: "\f529"; }
+.bi-search::before { content: "\f52a"; }
+.bi-segmented-nav::before { content: "\f52b"; }
+.bi-server::before { content: "\f52c"; }
+.bi-share-fill::before { content: "\f52d"; }
+.bi-share::before { content: "\f52e"; }
+.bi-shield-check::before { content: "\f52f"; }
+.bi-shield-exclamation::before { content: "\f530"; }
+.bi-shield-fill-check::before { content: "\f531"; }
+.bi-shield-fill-exclamation::before { content: "\f532"; }
+.bi-shield-fill-minus::before { content: "\f533"; }
+.bi-shield-fill-plus::before { content: "\f534"; }
+.bi-shield-fill-x::before { content: "\f535"; }
+.bi-shield-fill::before { content: "\f536"; }
+.bi-shield-lock-fill::before { content: "\f537"; }
+.bi-shield-lock::before { content: "\f538"; }
+.bi-shield-minus::before { content: "\f539"; }
+.bi-shield-plus::before { content: "\f53a"; }
+.bi-shield-shaded::before { content: "\f53b"; }
+.bi-shield-slash-fill::before { content: "\f53c"; }
+.bi-shield-slash::before { content: "\f53d"; }
+.bi-shield-x::before { content: "\f53e"; }
+.bi-shield::before { content: "\f53f"; }
+.bi-shift-fill::before { content: "\f540"; }
+.bi-shift::before { content: "\f541"; }
+.bi-shop-window::before { content: "\f542"; }
+.bi-shop::before { content: "\f543"; }
+.bi-shuffle::before { content: "\f544"; }
+.bi-signpost-2-fill::before { content: "\f545"; }
+.bi-signpost-2::before { content: "\f546"; }
+.bi-signpost-fill::before { content: "\f547"; }
+.bi-signpost-split-fill::before { content: "\f548"; }
+.bi-signpost-split::before { content: "\f549"; }
+.bi-signpost::before { content: "\f54a"; }
+.bi-sim-fill::before { content: "\f54b"; }
+.bi-sim::before { content: "\f54c"; }
+.bi-skip-backward-btn-fill::before { content: "\f54d"; }
+.bi-skip-backward-btn::before { content: "\f54e"; }
+.bi-skip-backward-circle-fill::before { content: "\f54f"; }
+.bi-skip-backward-circle::before { content: "\f550"; }
+.bi-skip-backward-fill::before { content: "\f551"; }
+.bi-skip-backward::before { content: "\f552"; }
+.bi-skip-end-btn-fill::before { content: "\f553"; }
+.bi-skip-end-btn::before { content: "\f554"; }
+.bi-skip-end-circle-fill::before { content: "\f555"; }
+.bi-skip-end-circle::before { content: "\f556"; }
+.bi-skip-end-fill::before { content: "\f557"; }
+.bi-skip-end::before { content: "\f558"; }
+.bi-skip-forward-btn-fill::before { content: "\f559"; }
+.bi-skip-forward-btn::before { content: "\f55a"; }
+.bi-skip-forward-circle-fill::before { content: "\f55b"; }
+.bi-skip-forward-circle::before { content: "\f55c"; }
+.bi-skip-forward-fill::before { content: "\f55d"; }
+.bi-skip-forward::before { content: "\f55e"; }
+.bi-skip-start-btn-fill::before { content: "\f55f"; }
+.bi-skip-start-btn::before { content: "\f560"; }
+.bi-skip-start-circle-fill::before { content: "\f561"; }
+.bi-skip-start-circle::before { content: "\f562"; }
+.bi-skip-start-fill::before { content: "\f563"; }
+.bi-skip-start::before { content: "\f564"; }
+.bi-slack::before { content: "\f565"; }
+.bi-slash-circle-fill::before { content: "\f566"; }
+.bi-slash-circle::before { content: "\f567"; }
+.bi-slash-square-fill::before { content: "\f568"; }
+.bi-slash-square::before { content: "\f569"; }
+.bi-slash::before { content: "\f56a"; }
+.bi-sliders::before { content: "\f56b"; }
+.bi-smartwatch::before { content: "\f56c"; }
+.bi-snow::before { content: "\f56d"; }
+.bi-snow2::before { content: "\f56e"; }
+.bi-snow3::before { content: "\f56f"; }
+.bi-sort-alpha-down-alt::before { content: "\f570"; }
+.bi-sort-alpha-down::before { content: "\f571"; }
+.bi-sort-alpha-up-alt::before { content: "\f572"; }
+.bi-sort-alpha-up::before { content: "\f573"; }
+.bi-sort-down-alt::before { content: "\f574"; }
+.bi-sort-down::before { content: "\f575"; }
+.bi-sort-numeric-down-alt::before { content: "\f576"; }
+.bi-sort-numeric-down::before { content: "\f577"; }
+.bi-sort-numeric-up-alt::before { content: "\f578"; }
+.bi-sort-numeric-up::before { content: "\f579"; }
+.bi-sort-up-alt::before { content: "\f57a"; }
+.bi-sort-up::before { content: "\f57b"; }
+.bi-soundwave::before { content: "\f57c"; }
+.bi-speaker-fill::before { content: "\f57d"; }
+.bi-speaker::before { content: "\f57e"; }
+.bi-speedometer::before { content: "\f57f"; }
+.bi-speedometer2::before { content: "\f580"; }
+.bi-spellcheck::before { content: "\f581"; }
+.bi-square-fill::before { content: "\f582"; }
+.bi-square-half::before { content: "\f583"; }
+.bi-square::before { content: "\f584"; }
+.bi-stack::before { content: "\f585"; }
+.bi-star-fill::before { content: "\f586"; }
+.bi-star-half::before { content: "\f587"; }
+.bi-star::before { content: "\f588"; }
+.bi-stars::before { content: "\f589"; }
+.bi-stickies-fill::before { content: "\f58a"; }
+.bi-stickies::before { content: "\f58b"; }
+.bi-sticky-fill::before { content: "\f58c"; }
+.bi-sticky::before { content: "\f58d"; }
+.bi-stop-btn-fill::before { content: "\f58e"; }
+.bi-stop-btn::before { content: "\f58f"; }
+.bi-stop-circle-fill::before { content: "\f590"; }
+.bi-stop-circle::before { content: "\f591"; }
+.bi-stop-fill::before { content: "\f592"; }
+.bi-stop::before { content: "\f593"; }
+.bi-stoplights-fill::before { content: "\f594"; }
+.bi-stoplights::before { content: "\f595"; }
+.bi-stopwatch-fill::before { content: "\f596"; }
+.bi-stopwatch::before { content: "\f597"; }
+.bi-subtract::before { content: "\f598"; }
+.bi-suit-club-fill::before { content: "\f599"; }
+.bi-suit-club::before { content: "\f59a"; }
+.bi-suit-diamond-fill::before { content: "\f59b"; }
+.bi-suit-diamond::before { content: "\f59c"; }
+.bi-suit-heart-fill::before { content: "\f59d"; }
+.bi-suit-heart::before { content: "\f59e"; }
+.bi-suit-spade-fill::before { content: "\f59f"; }
+.bi-suit-spade::before { content: "\f5a0"; }
+.bi-sun-fill::before { content: "\f5a1"; }
+.bi-sun::before { content: "\f5a2"; }
+.bi-sunglasses::before { content: "\f5a3"; }
+.bi-sunrise-fill::before { content: "\f5a4"; }
+.bi-sunrise::before { content: "\f5a5"; }
+.bi-sunset-fill::before { content: "\f5a6"; }
+.bi-sunset::before { content: "\f5a7"; }
+.bi-symmetry-horizontal::before { content: "\f5a8"; }
+.bi-symmetry-vertical::before { content: "\f5a9"; }
+.bi-table::before { content: "\f5aa"; }
+.bi-tablet-fill::before { content: "\f5ab"; }
+.bi-tablet-landscape-fill::before { content: "\f5ac"; }
+.bi-tablet-landscape::before { content: "\f5ad"; }
+.bi-tablet::before { content: "\f5ae"; }
+.bi-tag-fill::before { content: "\f5af"; }
+.bi-tag::before { content: "\f5b0"; }
+.bi-tags-fill::before { content: "\f5b1"; }
+.bi-tags::before { content: "\f5b2"; }
+.bi-telegram::before { content: "\f5b3"; }
+.bi-telephone-fill::before { content: "\f5b4"; }
+.bi-telephone-forward-fill::before { content: "\f5b5"; }
+.bi-telephone-forward::before { content: "\f5b6"; }
+.bi-telephone-inbound-fill::before { content: "\f5b7"; }
+.bi-telephone-inbound::before { content: "\f5b8"; }
+.bi-telephone-minus-fill::before { content: "\f5b9"; }
+.bi-telephone-minus::before { content: "\f5ba"; }
+.bi-telephone-outbound-fill::before { content: "\f5bb"; }
+.bi-telephone-outbound::before { content: "\f5bc"; }
+.bi-telephone-plus-fill::before { content: "\f5bd"; }
+.bi-telephone-plus::before { content: "\f5be"; }
+.bi-telephone-x-fill::before { content: "\f5bf"; }
+.bi-telephone-x::before { content: "\f5c0"; }
+.bi-telephone::before { content: "\f5c1"; }
+.bi-terminal-fill::before { content: "\f5c2"; }
+.bi-terminal::before { content: "\f5c3"; }
+.bi-text-center::before { content: "\f5c4"; }
+.bi-text-indent-left::before { content: "\f5c5"; }
+.bi-text-indent-right::before { content: "\f5c6"; }
+.bi-text-left::before { content: "\f5c7"; }
+.bi-text-paragraph::before { content: "\f5c8"; }
+.bi-text-right::before { content: "\f5c9"; }
+.bi-textarea-resize::before { content: "\f5ca"; }
+.bi-textarea-t::before { content: "\f5cb"; }
+.bi-textarea::before { content: "\f5cc"; }
+.bi-thermometer-half::before { content: "\f5cd"; }
+.bi-thermometer-high::before { content: "\f5ce"; }
+.bi-thermometer-low::before { content: "\f5cf"; }
+.bi-thermometer-snow::before { content: "\f5d0"; }
+.bi-thermometer-sun::before { content: "\f5d1"; }
+.bi-thermometer::before { content: "\f5d2"; }
+.bi-three-dots-vertical::before { content: "\f5d3"; }
+.bi-three-dots::before { content: "\f5d4"; }
+.bi-toggle-off::before { content: "\f5d5"; }
+.bi-toggle-on::before { content: "\f5d6"; }
+.bi-toggle2-off::before { content: "\f5d7"; }
+.bi-toggle2-on::before { content: "\f5d8"; }
+.bi-toggles::before { content: "\f5d9"; }
+.bi-toggles2::before { content: "\f5da"; }
+.bi-tools::before { content: "\f5db"; }
+.bi-tornado::before { content: "\f5dc"; }
+.bi-trash-fill::before { content: "\f5dd"; }
+.bi-trash::before { content: "\f5de"; }
+.bi-trash2-fill::before { content: "\f5df"; }
+.bi-trash2::before { content: "\f5e0"; }
+.bi-tree-fill::before { content: "\f5e1"; }
+.bi-tree::before { content: "\f5e2"; }
+.bi-triangle-fill::before { content: "\f5e3"; }
+.bi-triangle-half::before { content: "\f5e4"; }
+.bi-triangle::before { content: "\f5e5"; }
+.bi-trophy-fill::before { content: "\f5e6"; }
+.bi-trophy::before { content: "\f5e7"; }
+.bi-tropical-storm::before { content: "\f5e8"; }
+.bi-truck-flatbed::before { content: "\f5e9"; }
+.bi-truck::before { content: "\f5ea"; }
+.bi-tsunami::before { content: "\f5eb"; }
+.bi-tv-fill::before { content: "\f5ec"; }
+.bi-tv::before { content: "\f5ed"; }
+.bi-twitch::before { content: "\f5ee"; }
+.bi-twitter::before { content: "\f5ef"; }
+.bi-type-bold::before { content: "\f5f0"; }
+.bi-type-h1::before { content: "\f5f1"; }
+.bi-type-h2::before { content: "\f5f2"; }
+.bi-type-h3::before { content: "\f5f3"; }
+.bi-type-italic::before { content: "\f5f4"; }
+.bi-type-strikethrough::before { content: "\f5f5"; }
+.bi-type-underline::before { content: "\f5f6"; }
+.bi-type::before { content: "\f5f7"; }
+.bi-ui-checks-grid::before { content: "\f5f8"; }
+.bi-ui-checks::before { content: "\f5f9"; }
+.bi-ui-radios-grid::before { content: "\f5fa"; }
+.bi-ui-radios::before { content: "\f5fb"; }
+.bi-umbrella-fill::before { content: "\f5fc"; }
+.bi-umbrella::before { content: "\f5fd"; }
+.bi-union::before { content: "\f5fe"; }
+.bi-unlock-fill::before { content: "\f5ff"; }
+.bi-unlock::before { content: "\f600"; }
+.bi-upc-scan::before { content: "\f601"; }
+.bi-upc::before { content: "\f602"; }
+.bi-upload::before { content: "\f603"; }
+.bi-vector-pen::before { content: "\f604"; }
+.bi-view-list::before { content: "\f605"; }
+.bi-view-stacked::before { content: "\f606"; }
+.bi-vinyl-fill::before { content: "\f607"; }
+.bi-vinyl::before { content: "\f608"; }
+.bi-voicemail::before { content: "\f609"; }
+.bi-volume-down-fill::before { content: "\f60a"; }
+.bi-volume-down::before { content: "\f60b"; }
+.bi-volume-mute-fill::before { content: "\f60c"; }
+.bi-volume-mute::before { content: "\f60d"; }
+.bi-volume-off-fill::before { content: "\f60e"; }
+.bi-volume-off::before { content: "\f60f"; }
+.bi-volume-up-fill::before { content: "\f610"; }
+.bi-volume-up::before { content: "\f611"; }
+.bi-vr::before { content: "\f612"; }
+.bi-wallet-fill::before { content: "\f613"; }
+.bi-wallet::before { content: "\f614"; }
+.bi-wallet2::before { content: "\f615"; }
+.bi-watch::before { content: "\f616"; }
+.bi-water::before { content: "\f617"; }
+.bi-whatsapp::before { content: "\f618"; }
+.bi-wifi-1::before { content: "\f619"; }
+.bi-wifi-2::before { content: "\f61a"; }
+.bi-wifi-off::before { content: "\f61b"; }
+.bi-wifi::before { content: "\f61c"; }
+.bi-wind::before { content: "\f61d"; }
+.bi-window-dock::before { content: "\f61e"; }
+.bi-window-sidebar::before { content: "\f61f"; }
+.bi-window::before { content: "\f620"; }
+.bi-wrench::before { content: "\f621"; }
+.bi-x-circle-fill::before { content: "\f622"; }
+.bi-x-circle::before { content: "\f623"; }
+.bi-x-diamond-fill::before { content: "\f624"; }
+.bi-x-diamond::before { content: "\f625"; }
+.bi-x-octagon-fill::before { content: "\f626"; }
+.bi-x-octagon::before { content: "\f627"; }
+.bi-x-square-fill::before { content: "\f628"; }
+.bi-x-square::before { content: "\f629"; }
+.bi-x::before { content: "\f62a"; }
+.bi-youtube::before { content: "\f62b"; }
+.bi-zoom-in::before { content: "\f62c"; }
+.bi-zoom-out::before { content: "\f62d"; }
+.bi-bank::before { content: "\f62e"; }
+.bi-bank2::before { content: "\f62f"; }
+.bi-bell-slash-fill::before { content: "\f630"; }
+.bi-bell-slash::before { content: "\f631"; }
+.bi-cash-coin::before { content: "\f632"; }
+.bi-check-lg::before { content: "\f633"; }
+.bi-coin::before { content: "\f634"; }
+.bi-currency-bitcoin::before { content: "\f635"; }
+.bi-currency-dollar::before { content: "\f636"; }
+.bi-currency-euro::before { content: "\f637"; }
+.bi-currency-exchange::before { content: "\f638"; }
+.bi-currency-pound::before { content: "\f639"; }
+.bi-currency-yen::before { content: "\f63a"; }
+.bi-dash-lg::before { content: "\f63b"; }
+.bi-exclamation-lg::before { content: "\f63c"; }
+.bi-file-earmark-pdf-fill::before { content: "\f63d"; }
+.bi-file-earmark-pdf::before { content: "\f63e"; }
+.bi-file-pdf-fill::before { content: "\f63f"; }
+.bi-file-pdf::before { content: "\f640"; }
+.bi-gender-ambiguous::before { content: "\f641"; }
+.bi-gender-female::before { content: "\f642"; }
+.bi-gender-male::before { content: "\f643"; }
+.bi-gender-trans::before { content: "\f644"; }
+.bi-headset-vr::before { content: "\f645"; }
+.bi-info-lg::before { content: "\f646"; }
+.bi-mastodon::before { content: "\f647"; }
+.bi-messenger::before { content: "\f648"; }
+.bi-piggy-bank-fill::before { content: "\f649"; }
+.bi-piggy-bank::before { content: "\f64a"; }
+.bi-pin-map-fill::before { content: "\f64b"; }
+.bi-pin-map::before { content: "\f64c"; }
+.bi-plus-lg::before { content: "\f64d"; }
+.bi-question-lg::before { content: "\f64e"; }
+.bi-recycle::before { content: "\f64f"; }
+.bi-reddit::before { content: "\f650"; }
+.bi-safe-fill::before { content: "\f651"; }
+.bi-safe2-fill::before { content: "\f652"; }
+.bi-safe2::before { content: "\f653"; }
+.bi-sd-card-fill::before { content: "\f654"; }
+.bi-sd-card::before { content: "\f655"; }
+.bi-skype::before { content: "\f656"; }
+.bi-slash-lg::before { content: "\f657"; }
+.bi-translate::before { content: "\f658"; }
+.bi-x-lg::before { content: "\f659"; }
+.bi-safe::before { content: "\f65a"; }
+.bi-apple::before { content: "\f65b"; }
+.bi-microsoft::before { content: "\f65d"; }
+.bi-windows::before { content: "\f65e"; }
+.bi-behance::before { content: "\f65c"; }
+.bi-dribbble::before { content: "\f65f"; }
+.bi-line::before { content: "\f660"; }
+.bi-medium::before { content: "\f661"; }
+.bi-paypal::before { content: "\f662"; }
+.bi-pinterest::before { content: "\f663"; }
+.bi-signal::before { content: "\f664"; }
+.bi-snapchat::before { content: "\f665"; }
+.bi-spotify::before { content: "\f666"; }
+.bi-stack-overflow::before { content: "\f667"; }
+.bi-strava::before { content: "\f668"; }
+.bi-wordpress::before { content: "\f669"; }
+.bi-vimeo::before { content: "\f66a"; }
+.bi-activity::before { content: "\f66b"; }
+.bi-easel2-fill::before { content: "\f66c"; }
+.bi-easel2::before { content: "\f66d"; }
+.bi-easel3-fill::before { content: "\f66e"; }
+.bi-easel3::before { content: "\f66f"; }
+.bi-fan::before { content: "\f670"; }
+.bi-fingerprint::before { content: "\f671"; }
+.bi-graph-down-arrow::before { content: "\f672"; }
+.bi-graph-up-arrow::before { content: "\f673"; }
+.bi-hypnotize::before { content: "\f674"; }
+.bi-magic::before { content: "\f675"; }
+.bi-person-rolodex::before { content: "\f676"; }
+.bi-person-video::before { content: "\f677"; }
+.bi-person-video2::before { content: "\f678"; }
+.bi-person-video3::before { content: "\f679"; }
+.bi-person-workspace::before { content: "\f67a"; }
+.bi-radioactive::before { content: "\f67b"; }
+.bi-webcam-fill::before { content: "\f67c"; }
+.bi-webcam::before { content: "\f67d"; }
+.bi-yin-yang::before { content: "\f67e"; }
+.bi-bandaid-fill::before { content: "\f680"; }
+.bi-bandaid::before { content: "\f681"; }
+.bi-bluetooth::before { content: "\f682"; }
+.bi-body-text::before { content: "\f683"; }
+.bi-boombox::before { content: "\f684"; }
+.bi-boxes::before { content: "\f685"; }
+.bi-dpad-fill::before { content: "\f686"; }
+.bi-dpad::before { content: "\f687"; }
+.bi-ear-fill::before { content: "\f688"; }
+.bi-ear::before { content: "\f689"; }
+.bi-envelope-check-fill::before { content: "\f68b"; }
+.bi-envelope-check::before { content: "\f68c"; }
+.bi-envelope-dash-fill::before { content: "\f68e"; }
+.bi-envelope-dash::before { content: "\f68f"; }
+.bi-envelope-exclamation-fill::before { content: "\f691"; }
+.bi-envelope-exclamation::before { content: "\f692"; }
+.bi-envelope-plus-fill::before { content: "\f693"; }
+.bi-envelope-plus::before { content: "\f694"; }
+.bi-envelope-slash-fill::before { content: "\f696"; }
+.bi-envelope-slash::before { content: "\f697"; }
+.bi-envelope-x-fill::before { content: "\f699"; }
+.bi-envelope-x::before { content: "\f69a"; }
+.bi-explicit-fill::before { content: "\f69b"; }
+.bi-explicit::before { content: "\f69c"; }
+.bi-git::before { content: "\f69d"; }
+.bi-infinity::before { content: "\f69e"; }
+.bi-list-columns-reverse::before { content: "\f69f"; }
+.bi-list-columns::before { content: "\f6a0"; }
+.bi-meta::before { content: "\f6a1"; }
+.bi-nintendo-switch::before { content: "\f6a4"; }
+.bi-pc-display-horizontal::before { content: "\f6a5"; }
+.bi-pc-display::before { content: "\f6a6"; }
+.bi-pc-horizontal::before { content: "\f6a7"; }
+.bi-pc::before { content: "\f6a8"; }
+.bi-playstation::before { content: "\f6a9"; }
+.bi-plus-slash-minus::before { content: "\f6aa"; }
+.bi-projector-fill::before { content: "\f6ab"; }
+.bi-projector::before { content: "\f6ac"; }
+.bi-qr-code-scan::before { content: "\f6ad"; }
+.bi-qr-code::before { content: "\f6ae"; }
+.bi-quora::before { content: "\f6af"; }
+.bi-quote::before { content: "\f6b0"; }
+.bi-robot::before { content: "\f6b1"; }
+.bi-send-check-fill::before { content: "\f6b2"; }
+.bi-send-check::before { content: "\f6b3"; }
+.bi-send-dash-fill::before { content: "\f6b4"; }
+.bi-send-dash::before { content: "\f6b5"; }
+.bi-send-exclamation-fill::before { content: "\f6b7"; }
+.bi-send-exclamation::before { content: "\f6b8"; }
+.bi-send-fill::before { content: "\f6b9"; }
+.bi-send-plus-fill::before { content: "\f6ba"; }
+.bi-send-plus::before { content: "\f6bb"; }
+.bi-send-slash-fill::before { content: "\f6bc"; }
+.bi-send-slash::before { content: "\f6bd"; }
+.bi-send-x-fill::before { content: "\f6be"; }
+.bi-send-x::before { content: "\f6bf"; }
+.bi-send::before { content: "\f6c0"; }
+.bi-steam::before { content: "\f6c1"; }
+.bi-terminal-dash::before { content: "\f6c3"; }
+.bi-terminal-plus::before { content: "\f6c4"; }
+.bi-terminal-split::before { content: "\f6c5"; }
+.bi-ticket-detailed-fill::before { content: "\f6c6"; }
+.bi-ticket-detailed::before { content: "\f6c7"; }
+.bi-ticket-fill::before { content: "\f6c8"; }
+.bi-ticket-perforated-fill::before { content: "\f6c9"; }
+.bi-ticket-perforated::before { content: "\f6ca"; }
+.bi-ticket::before { content: "\f6cb"; }
+.bi-tiktok::before { content: "\f6cc"; }
+.bi-window-dash::before { content: "\f6cd"; }
+.bi-window-desktop::before { content: "\f6ce"; }
+.bi-window-fullscreen::before { content: "\f6cf"; }
+.bi-window-plus::before { content: "\f6d0"; }
+.bi-window-split::before { content: "\f6d1"; }
+.bi-window-stack::before { content: "\f6d2"; }
+.bi-window-x::before { content: "\f6d3"; }
+.bi-xbox::before { content: "\f6d4"; }
+.bi-ethernet::before { content: "\f6d5"; }
+.bi-hdmi-fill::before { content: "\f6d6"; }
+.bi-hdmi::before { content: "\f6d7"; }
+.bi-usb-c-fill::before { content: "\f6d8"; }
+.bi-usb-c::before { content: "\f6d9"; }
+.bi-usb-fill::before { content: "\f6da"; }
+.bi-usb-plug-fill::before { content: "\f6db"; }
+.bi-usb-plug::before { content: "\f6dc"; }
+.bi-usb-symbol::before { content: "\f6dd"; }
+.bi-usb::before { content: "\f6de"; }
+.bi-boombox-fill::before { content: "\f6df"; }
+.bi-displayport::before { content: "\f6e1"; }
+.bi-gpu-card::before { content: "\f6e2"; }
+.bi-memory::before { content: "\f6e3"; }
+.bi-modem-fill::before { content: "\f6e4"; }
+.bi-modem::before { content: "\f6e5"; }
+.bi-motherboard-fill::before { content: "\f6e6"; }
+.bi-motherboard::before { content: "\f6e7"; }
+.bi-optical-audio-fill::before { content: "\f6e8"; }
+.bi-optical-audio::before { content: "\f6e9"; }
+.bi-pci-card::before { content: "\f6ea"; }
+.bi-router-fill::before { content: "\f6eb"; }
+.bi-router::before { content: "\f6ec"; }
+.bi-thunderbolt-fill::before { content: "\f6ef"; }
+.bi-thunderbolt::before { content: "\f6f0"; }
+.bi-usb-drive-fill::before { content: "\f6f1"; }
+.bi-usb-drive::before { content: "\f6f2"; }
+.bi-usb-micro-fill::before { content: "\f6f3"; }
+.bi-usb-micro::before { content: "\f6f4"; }
+.bi-usb-mini-fill::before { content: "\f6f5"; }
+.bi-usb-mini::before { content: "\f6f6"; }
+.bi-cloud-haze2::before { content: "\f6f7"; }
+.bi-device-hdd-fill::before { content: "\f6f8"; }
+.bi-device-hdd::before { content: "\f6f9"; }
+.bi-device-ssd-fill::before { content: "\f6fa"; }
+.bi-device-ssd::before { content: "\f6fb"; }
+.bi-displayport-fill::before { content: "\f6fc"; }
+.bi-mortarboard-fill::before { content: "\f6fd"; }
+.bi-mortarboard::before { content: "\f6fe"; }
+.bi-terminal-x::before { content: "\f6ff"; }
+.bi-arrow-through-heart-fill::before { content: "\f700"; }
+.bi-arrow-through-heart::before { content: "\f701"; }
+.bi-badge-sd-fill::before { content: "\f702"; }
+.bi-badge-sd::before { content: "\f703"; }
+.bi-bag-heart-fill::before { content: "\f704"; }
+.bi-bag-heart::before { content: "\f705"; }
+.bi-balloon-fill::before { content: "\f706"; }
+.bi-balloon-heart-fill::before { content: "\f707"; }
+.bi-balloon-heart::before { content: "\f708"; }
+.bi-balloon::before { content: "\f709"; }
+.bi-box2-fill::before { content: "\f70a"; }
+.bi-box2-heart-fill::before { content: "\f70b"; }
+.bi-box2-heart::before { content: "\f70c"; }
+.bi-box2::before { content: "\f70d"; }
+.bi-braces-asterisk::before { content: "\f70e"; }
+.bi-calendar-heart-fill::before { content: "\f70f"; }
+.bi-calendar-heart::before { content: "\f710"; }
+.bi-calendar2-heart-fill::before { content: "\f711"; }
+.bi-calendar2-heart::before { content: "\f712"; }
+.bi-chat-heart-fill::before { content: "\f713"; }
+.bi-chat-heart::before { content: "\f714"; }
+.bi-chat-left-heart-fill::before { content: "\f715"; }
+.bi-chat-left-heart::before { content: "\f716"; }
+.bi-chat-right-heart-fill::before { content: "\f717"; }
+.bi-chat-right-heart::before { content: "\f718"; }
+.bi-chat-square-heart-fill::before { content: "\f719"; }
+.bi-chat-square-heart::before { content: "\f71a"; }
+.bi-clipboard-check-fill::before { content: "\f71b"; }
+.bi-clipboard-data-fill::before { content: "\f71c"; }
+.bi-clipboard-fill::before { content: "\f71d"; }
+.bi-clipboard-heart-fill::before { content: "\f71e"; }
+.bi-clipboard-heart::before { content: "\f71f"; }
+.bi-clipboard-minus-fill::before { content: "\f720"; }
+.bi-clipboard-plus-fill::before { content: "\f721"; }
+.bi-clipboard-pulse::before { content: "\f722"; }
+.bi-clipboard-x-fill::before { content: "\f723"; }
+.bi-clipboard2-check-fill::before { content: "\f724"; }
+.bi-clipboard2-check::before { content: "\f725"; }
+.bi-clipboard2-data-fill::before { content: "\f726"; }
+.bi-clipboard2-data::before { content: "\f727"; }
+.bi-clipboard2-fill::before { content: "\f728"; }
+.bi-clipboard2-heart-fill::before { content: "\f729"; }
+.bi-clipboard2-heart::before { content: "\f72a"; }
+.bi-clipboard2-minus-fill::before { content: "\f72b"; }
+.bi-clipboard2-minus::before { content: "\f72c"; }
+.bi-clipboard2-plus-fill::before { content: "\f72d"; }
+.bi-clipboard2-plus::before { content: "\f72e"; }
+.bi-clipboard2-pulse-fill::before { content: "\f72f"; }
+.bi-clipboard2-pulse::before { content: "\f730"; }
+.bi-clipboard2-x-fill::before { content: "\f731"; }
+.bi-clipboard2-x::before { content: "\f732"; }
+.bi-clipboard2::before { content: "\f733"; }
+.bi-emoji-kiss-fill::before { content: "\f734"; }
+.bi-emoji-kiss::before { content: "\f735"; }
+.bi-envelope-heart-fill::before { content: "\f736"; }
+.bi-envelope-heart::before { content: "\f737"; }
+.bi-envelope-open-heart-fill::before { content: "\f738"; }
+.bi-envelope-open-heart::before { content: "\f739"; }
+.bi-envelope-paper-fill::before { content: "\f73a"; }
+.bi-envelope-paper-heart-fill::before { content: "\f73b"; }
+.bi-envelope-paper-heart::before { content: "\f73c"; }
+.bi-envelope-paper::before { content: "\f73d"; }
+.bi-filetype-aac::before { content: "\f73e"; }
+.bi-filetype-ai::before { content: "\f73f"; }
+.bi-filetype-bmp::before { content: "\f740"; }
+.bi-filetype-cs::before { content: "\f741"; }
+.bi-filetype-css::before { content: "\f742"; }
+.bi-filetype-csv::before { content: "\f743"; }
+.bi-filetype-doc::before { content: "\f744"; }
+.bi-filetype-docx::before { content: "\f745"; }
+.bi-filetype-exe::before { content: "\f746"; }
+.bi-filetype-gif::before { content: "\f747"; }
+.bi-filetype-heic::before { content: "\f748"; }
+.bi-filetype-html::before { content: "\f749"; }
+.bi-filetype-java::before { content: "\f74a"; }
+.bi-filetype-jpg::before { content: "\f74b"; }
+.bi-filetype-js::before { content: "\f74c"; }
+.bi-filetype-jsx::before { content: "\f74d"; }
+.bi-filetype-key::before { content: "\f74e"; }
+.bi-filetype-m4p::before { content: "\f74f"; }
+.bi-filetype-md::before { content: "\f750"; }
+.bi-filetype-mdx::before { content: "\f751"; }
+.bi-filetype-mov::before { content: "\f752"; }
+.bi-filetype-mp3::before { content: "\f753"; }
+.bi-filetype-mp4::before { content: "\f754"; }
+.bi-filetype-otf::before { content: "\f755"; }
+.bi-filetype-pdf::before { content: "\f756"; }
+.bi-filetype-php::before { content: "\f757"; }
+.bi-filetype-png::before { content: "\f758"; }
+.bi-filetype-ppt::before { content: "\f75a"; }
+.bi-filetype-psd::before { content: "\f75b"; }
+.bi-filetype-py::before { content: "\f75c"; }
+.bi-filetype-raw::before { content: "\f75d"; }
+.bi-filetype-rb::before { content: "\f75e"; }
+.bi-filetype-sass::before { content: "\f75f"; }
+.bi-filetype-scss::before { content: "\f760"; }
+.bi-filetype-sh::before { content: "\f761"; }
+.bi-filetype-svg::before { content: "\f762"; }
+.bi-filetype-tiff::before { content: "\f763"; }
+.bi-filetype-tsx::before { content: "\f764"; }
+.bi-filetype-ttf::before { content: "\f765"; }
+.bi-filetype-txt::before { content: "\f766"; }
+.bi-filetype-wav::before { content: "\f767"; }
+.bi-filetype-woff::before { content: "\f768"; }
+.bi-filetype-xls::before { content: "\f76a"; }
+.bi-filetype-xml::before { content: "\f76b"; }
+.bi-filetype-yml::before { content: "\f76c"; }
+.bi-heart-arrow::before { content: "\f76d"; }
+.bi-heart-pulse-fill::before { content: "\f76e"; }
+.bi-heart-pulse::before { content: "\f76f"; }
+.bi-heartbreak-fill::before { content: "\f770"; }
+.bi-heartbreak::before { content: "\f771"; }
+.bi-hearts::before { content: "\f772"; }
+.bi-hospital-fill::before { content: "\f773"; }
+.bi-hospital::before { content: "\f774"; }
+.bi-house-heart-fill::before { content: "\f775"; }
+.bi-house-heart::before { content: "\f776"; }
+.bi-incognito::before { content: "\f777"; }
+.bi-magnet-fill::before { content: "\f778"; }
+.bi-magnet::before { content: "\f779"; }
+.bi-person-heart::before { content: "\f77a"; }
+.bi-person-hearts::before { content: "\f77b"; }
+.bi-phone-flip::before { content: "\f77c"; }
+.bi-plugin::before { content: "\f77d"; }
+.bi-postage-fill::before { content: "\f77e"; }
+.bi-postage-heart-fill::before { content: "\f77f"; }
+.bi-postage-heart::before { content: "\f780"; }
+.bi-postage::before { content: "\f781"; }
+.bi-postcard-fill::before { content: "\f782"; }
+.bi-postcard-heart-fill::before { content: "\f783"; }
+.bi-postcard-heart::before { content: "\f784"; }
+.bi-postcard::before { content: "\f785"; }
+.bi-search-heart-fill::before { content: "\f786"; }
+.bi-search-heart::before { content: "\f787"; }
+.bi-sliders2-vertical::before { content: "\f788"; }
+.bi-sliders2::before { content: "\f789"; }
+.bi-trash3-fill::before { content: "\f78a"; }
+.bi-trash3::before { content: "\f78b"; }
+.bi-valentine::before { content: "\f78c"; }
+.bi-valentine2::before { content: "\f78d"; }
+.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; }
+.bi-wrench-adjustable-circle::before { content: "\f78f"; }
+.bi-wrench-adjustable::before { content: "\f790"; }
+.bi-filetype-json::before { content: "\f791"; }
+.bi-filetype-pptx::before { content: "\f792"; }
+.bi-filetype-xlsx::before { content: "\f793"; }
+.bi-1-circle-fill::before { content: "\f796"; }
+.bi-1-circle::before { content: "\f797"; }
+.bi-1-square-fill::before { content: "\f798"; }
+.bi-1-square::before { content: "\f799"; }
+.bi-2-circle-fill::before { content: "\f79c"; }
+.bi-2-circle::before { content: "\f79d"; }
+.bi-2-square-fill::before { content: "\f79e"; }
+.bi-2-square::before { content: "\f79f"; }
+.bi-3-circle-fill::before { content: "\f7a2"; }
+.bi-3-circle::before { content: "\f7a3"; }
+.bi-3-square-fill::before { content: "\f7a4"; }
+.bi-3-square::before { content: "\f7a5"; }
+.bi-4-circle-fill::before { content: "\f7a8"; }
+.bi-4-circle::before { content: "\f7a9"; }
+.bi-4-square-fill::before { content: "\f7aa"; }
+.bi-4-square::before { content: "\f7ab"; }
+.bi-5-circle-fill::before { content: "\f7ae"; }
+.bi-5-circle::before { content: "\f7af"; }
+.bi-5-square-fill::before { content: "\f7b0"; }
+.bi-5-square::before { content: "\f7b1"; }
+.bi-6-circle-fill::before { content: "\f7b4"; }
+.bi-6-circle::before { content: "\f7b5"; }
+.bi-6-square-fill::before { content: "\f7b6"; }
+.bi-6-square::before { content: "\f7b7"; }
+.bi-7-circle-fill::before { content: "\f7ba"; }
+.bi-7-circle::before { content: "\f7bb"; }
+.bi-7-square-fill::before { content: "\f7bc"; }
+.bi-7-square::before { content: "\f7bd"; }
+.bi-8-circle-fill::before { content: "\f7c0"; }
+.bi-8-circle::before { content: "\f7c1"; }
+.bi-8-square-fill::before { content: "\f7c2"; }
+.bi-8-square::before { content: "\f7c3"; }
+.bi-9-circle-fill::before { content: "\f7c6"; }
+.bi-9-circle::before { content: "\f7c7"; }
+.bi-9-square-fill::before { content: "\f7c8"; }
+.bi-9-square::before { content: "\f7c9"; }
+.bi-airplane-engines-fill::before { content: "\f7ca"; }
+.bi-airplane-engines::before { content: "\f7cb"; }
+.bi-airplane-fill::before { content: "\f7cc"; }
+.bi-airplane::before { content: "\f7cd"; }
+.bi-alexa::before { content: "\f7ce"; }
+.bi-alipay::before { content: "\f7cf"; }
+.bi-android::before { content: "\f7d0"; }
+.bi-android2::before { content: "\f7d1"; }
+.bi-box-fill::before { content: "\f7d2"; }
+.bi-box-seam-fill::before { content: "\f7d3"; }
+.bi-browser-chrome::before { content: "\f7d4"; }
+.bi-browser-edge::before { content: "\f7d5"; }
+.bi-browser-firefox::before { content: "\f7d6"; }
+.bi-browser-safari::before { content: "\f7d7"; }
+.bi-c-circle-fill::before { content: "\f7da"; }
+.bi-c-circle::before { content: "\f7db"; }
+.bi-c-square-fill::before { content: "\f7dc"; }
+.bi-c-square::before { content: "\f7dd"; }
+.bi-capsule-pill::before { content: "\f7de"; }
+.bi-capsule::before { content: "\f7df"; }
+.bi-car-front-fill::before { content: "\f7e0"; }
+.bi-car-front::before { content: "\f7e1"; }
+.bi-cassette-fill::before { content: "\f7e2"; }
+.bi-cassette::before { content: "\f7e3"; }
+.bi-cc-circle-fill::before { content: "\f7e6"; }
+.bi-cc-circle::before { content: "\f7e7"; }
+.bi-cc-square-fill::before { content: "\f7e8"; }
+.bi-cc-square::before { content: "\f7e9"; }
+.bi-cup-hot-fill::before { content: "\f7ea"; }
+.bi-cup-hot::before { content: "\f7eb"; }
+.bi-currency-rupee::before { content: "\f7ec"; }
+.bi-dropbox::before { content: "\f7ed"; }
+.bi-escape::before { content: "\f7ee"; }
+.bi-fast-forward-btn-fill::before { content: "\f7ef"; }
+.bi-fast-forward-btn::before { content: "\f7f0"; }
+.bi-fast-forward-circle-fill::before { content: "\f7f1"; }
+.bi-fast-forward-circle::before { content: "\f7f2"; }
+.bi-fast-forward-fill::before { content: "\f7f3"; }
+.bi-fast-forward::before { content: "\f7f4"; }
+.bi-filetype-sql::before { content: "\f7f5"; }
+.bi-fire::before { content: "\f7f6"; }
+.bi-google-play::before { content: "\f7f7"; }
+.bi-h-circle-fill::before { content: "\f7fa"; }
+.bi-h-circle::before { content: "\f7fb"; }
+.bi-h-square-fill::before { content: "\f7fc"; }
+.bi-h-square::before { content: "\f7fd"; }
+.bi-indent::before { content: "\f7fe"; }
+.bi-lungs-fill::before { content: "\f7ff"; }
+.bi-lungs::before { content: "\f800"; }
+.bi-microsoft-teams::before { content: "\f801"; }
+.bi-p-circle-fill::before { content: "\f804"; }
+.bi-p-circle::before { content: "\f805"; }
+.bi-p-square-fill::before { content: "\f806"; }
+.bi-p-square::before { content: "\f807"; }
+.bi-pass-fill::before { content: "\f808"; }
+.bi-pass::before { content: "\f809"; }
+.bi-prescription::before { content: "\f80a"; }
+.bi-prescription2::before { content: "\f80b"; }
+.bi-r-circle-fill::before { content: "\f80e"; }
+.bi-r-circle::before { content: "\f80f"; }
+.bi-r-square-fill::before { content: "\f810"; }
+.bi-r-square::before { content: "\f811"; }
+.bi-repeat-1::before { content: "\f812"; }
+.bi-repeat::before { content: "\f813"; }
+.bi-rewind-btn-fill::before { content: "\f814"; }
+.bi-rewind-btn::before { content: "\f815"; }
+.bi-rewind-circle-fill::before { content: "\f816"; }
+.bi-rewind-circle::before { content: "\f817"; }
+.bi-rewind-fill::before { content: "\f818"; }
+.bi-rewind::before { content: "\f819"; }
+.bi-train-freight-front-fill::before { content: "\f81a"; }
+.bi-train-freight-front::before { content: "\f81b"; }
+.bi-train-front-fill::before { content: "\f81c"; }
+.bi-train-front::before { content: "\f81d"; }
+.bi-train-lightrail-front-fill::before { content: "\f81e"; }
+.bi-train-lightrail-front::before { content: "\f81f"; }
+.bi-truck-front-fill::before { content: "\f820"; }
+.bi-truck-front::before { content: "\f821"; }
+.bi-ubuntu::before { content: "\f822"; }
+.bi-unindent::before { content: "\f823"; }
+.bi-unity::before { content: "\f824"; }
+.bi-universal-access-circle::before { content: "\f825"; }
+.bi-universal-access::before { content: "\f826"; }
+.bi-virus::before { content: "\f827"; }
+.bi-virus2::before { content: "\f828"; }
+.bi-wechat::before { content: "\f829"; }
+.bi-yelp::before { content: "\f82a"; }
+.bi-sign-stop-fill::before { content: "\f82b"; }
+.bi-sign-stop-lights-fill::before { content: "\f82c"; }
+.bi-sign-stop-lights::before { content: "\f82d"; }
+.bi-sign-stop::before { content: "\f82e"; }
+.bi-sign-turn-left-fill::before { content: "\f82f"; }
+.bi-sign-turn-left::before { content: "\f830"; }
+.bi-sign-turn-right-fill::before { content: "\f831"; }
+.bi-sign-turn-right::before { content: "\f832"; }
+.bi-sign-turn-slight-left-fill::before { content: "\f833"; }
+.bi-sign-turn-slight-left::before { content: "\f834"; }
+.bi-sign-turn-slight-right-fill::before { content: "\f835"; }
+.bi-sign-turn-slight-right::before { content: "\f836"; }
+.bi-sign-yield-fill::before { content: "\f837"; }
+.bi-sign-yield::before { content: "\f838"; }
+.bi-ev-station-fill::before { content: "\f839"; }
+.bi-ev-station::before { content: "\f83a"; }
+.bi-fuel-pump-diesel-fill::before { content: "\f83b"; }
+.bi-fuel-pump-diesel::before { content: "\f83c"; }
+.bi-fuel-pump-fill::before { content: "\f83d"; }
+.bi-fuel-pump::before { content: "\f83e"; }
+.bi-0-circle-fill::before { content: "\f83f"; }
+.bi-0-circle::before { content: "\f840"; }
+.bi-0-square-fill::before { content: "\f841"; }
+.bi-0-square::before { content: "\f842"; }
+.bi-rocket-fill::before { content: "\f843"; }
+.bi-rocket-takeoff-fill::before { content: "\f844"; }
+.bi-rocket-takeoff::before { content: "\f845"; }
+.bi-rocket::before { content: "\f846"; }
+.bi-stripe::before { content: "\f847"; }
+.bi-subscript::before { content: "\f848"; }
+.bi-superscript::before { content: "\f849"; }
+.bi-trello::before { content: "\f84a"; }
+.bi-envelope-at-fill::before { content: "\f84b"; }
+.bi-envelope-at::before { content: "\f84c"; }
+.bi-regex::before { content: "\f84d"; }
+.bi-text-wrap::before { content: "\f84e"; }
+.bi-sign-dead-end-fill::before { content: "\f84f"; }
+.bi-sign-dead-end::before { content: "\f850"; }
+.bi-sign-do-not-enter-fill::before { content: "\f851"; }
+.bi-sign-do-not-enter::before { content: "\f852"; }
+.bi-sign-intersection-fill::before { content: "\f853"; }
+.bi-sign-intersection-side-fill::before { content: "\f854"; }
+.bi-sign-intersection-side::before { content: "\f855"; }
+.bi-sign-intersection-t-fill::before { content: "\f856"; }
+.bi-sign-intersection-t::before { content: "\f857"; }
+.bi-sign-intersection-y-fill::before { content: "\f858"; }
+.bi-sign-intersection-y::before { content: "\f859"; }
+.bi-sign-intersection::before { content: "\f85a"; }
+.bi-sign-merge-left-fill::before { content: "\f85b"; }
+.bi-sign-merge-left::before { content: "\f85c"; }
+.bi-sign-merge-right-fill::before { content: "\f85d"; }
+.bi-sign-merge-right::before { content: "\f85e"; }
+.bi-sign-no-left-turn-fill::before { content: "\f85f"; }
+.bi-sign-no-left-turn::before { content: "\f860"; }
+.bi-sign-no-parking-fill::before { content: "\f861"; }
+.bi-sign-no-parking::before { content: "\f862"; }
+.bi-sign-no-right-turn-fill::before { content: "\f863"; }
+.bi-sign-no-right-turn::before { content: "\f864"; }
+.bi-sign-railroad-fill::before { content: "\f865"; }
+.bi-sign-railroad::before { content: "\f866"; }
+.bi-building-add::before { content: "\f867"; }
+.bi-building-check::before { content: "\f868"; }
+.bi-building-dash::before { content: "\f869"; }
+.bi-building-down::before { content: "\f86a"; }
+.bi-building-exclamation::before { content: "\f86b"; }
+.bi-building-fill-add::before { content: "\f86c"; }
+.bi-building-fill-check::before { content: "\f86d"; }
+.bi-building-fill-dash::before { content: "\f86e"; }
+.bi-building-fill-down::before { content: "\f86f"; }
+.bi-building-fill-exclamation::before { content: "\f870"; }
+.bi-building-fill-gear::before { content: "\f871"; }
+.bi-building-fill-lock::before { content: "\f872"; }
+.bi-building-fill-slash::before { content: "\f873"; }
+.bi-building-fill-up::before { content: "\f874"; }
+.bi-building-fill-x::before { content: "\f875"; }
+.bi-building-fill::before { content: "\f876"; }
+.bi-building-gear::before { content: "\f877"; }
+.bi-building-lock::before { content: "\f878"; }
+.bi-building-slash::before { content: "\f879"; }
+.bi-building-up::before { content: "\f87a"; }
+.bi-building-x::before { content: "\f87b"; }
+.bi-buildings-fill::before { content: "\f87c"; }
+.bi-buildings::before { content: "\f87d"; }
+.bi-bus-front-fill::before { content: "\f87e"; }
+.bi-bus-front::before { content: "\f87f"; }
+.bi-ev-front-fill::before { content: "\f880"; }
+.bi-ev-front::before { content: "\f881"; }
+.bi-globe-americas::before { content: "\f882"; }
+.bi-globe-asia-australia::before { content: "\f883"; }
+.bi-globe-central-south-asia::before { content: "\f884"; }
+.bi-globe-europe-africa::before { content: "\f885"; }
+.bi-house-add-fill::before { content: "\f886"; }
+.bi-house-add::before { content: "\f887"; }
+.bi-house-check-fill::before { content: "\f888"; }
+.bi-house-check::before { content: "\f889"; }
+.bi-house-dash-fill::before { content: "\f88a"; }
+.bi-house-dash::before { content: "\f88b"; }
+.bi-house-down-fill::before { content: "\f88c"; }
+.bi-house-down::before { content: "\f88d"; }
+.bi-house-exclamation-fill::before { content: "\f88e"; }
+.bi-house-exclamation::before { content: "\f88f"; }
+.bi-house-gear-fill::before { content: "\f890"; }
+.bi-house-gear::before { content: "\f891"; }
+.bi-house-lock-fill::before { content: "\f892"; }
+.bi-house-lock::before { content: "\f893"; }
+.bi-house-slash-fill::before { content: "\f894"; }
+.bi-house-slash::before { content: "\f895"; }
+.bi-house-up-fill::before { content: "\f896"; }
+.bi-house-up::before { content: "\f897"; }
+.bi-house-x-fill::before { content: "\f898"; }
+.bi-house-x::before { content: "\f899"; }
+.bi-person-add::before { content: "\f89a"; }
+.bi-person-down::before { content: "\f89b"; }
+.bi-person-exclamation::before { content: "\f89c"; }
+.bi-person-fill-add::before { content: "\f89d"; }
+.bi-person-fill-check::before { content: "\f89e"; }
+.bi-person-fill-dash::before { content: "\f89f"; }
+.bi-person-fill-down::before { content: "\f8a0"; }
+.bi-person-fill-exclamation::before { content: "\f8a1"; }
+.bi-person-fill-gear::before { content: "\f8a2"; }
+.bi-person-fill-lock::before { content: "\f8a3"; }
+.bi-person-fill-slash::before { content: "\f8a4"; }
+.bi-person-fill-up::before { content: "\f8a5"; }
+.bi-person-fill-x::before { content: "\f8a6"; }
+.bi-person-gear::before { content: "\f8a7"; }
+.bi-person-lock::before { content: "\f8a8"; }
+.bi-person-slash::before { content: "\f8a9"; }
+.bi-person-up::before { content: "\f8aa"; }
+.bi-scooter::before { content: "\f8ab"; }
+.bi-taxi-front-fill::before { content: "\f8ac"; }
+.bi-taxi-front::before { content: "\f8ad"; }
+.bi-amd::before { content: "\f8ae"; }
+.bi-database-add::before { content: "\f8af"; }
+.bi-database-check::before { content: "\f8b0"; }
+.bi-database-dash::before { content: "\f8b1"; }
+.bi-database-down::before { content: "\f8b2"; }
+.bi-database-exclamation::before { content: "\f8b3"; }
+.bi-database-fill-add::before { content: "\f8b4"; }
+.bi-database-fill-check::before { content: "\f8b5"; }
+.bi-database-fill-dash::before { content: "\f8b6"; }
+.bi-database-fill-down::before { content: "\f8b7"; }
+.bi-database-fill-exclamation::before { content: "\f8b8"; }
+.bi-database-fill-gear::before { content: "\f8b9"; }
+.bi-database-fill-lock::before { content: "\f8ba"; }
+.bi-database-fill-slash::before { content: "\f8bb"; }
+.bi-database-fill-up::before { content: "\f8bc"; }
+.bi-database-fill-x::before { content: "\f8bd"; }
+.bi-database-fill::before { content: "\f8be"; }
+.bi-database-gear::before { content: "\f8bf"; }
+.bi-database-lock::before { content: "\f8c0"; }
+.bi-database-slash::before { content: "\f8c1"; }
+.bi-database-up::before { content: "\f8c2"; }
+.bi-database-x::before { content: "\f8c3"; }
+.bi-database::before { content: "\f8c4"; }
+.bi-houses-fill::before { content: "\f8c5"; }
+.bi-houses::before { content: "\f8c6"; }
+.bi-nvidia::before { content: "\f8c7"; }
+.bi-person-vcard-fill::before { content: "\f8c8"; }
+.bi-person-vcard::before { content: "\f8c9"; }
+.bi-sina-weibo::before { content: "\f8ca"; }
+.bi-tencent-qq::before { content: "\f8cb"; }
+.bi-wikipedia::before { content: "\f8cc"; }
+.bi-alphabet-uppercase::before { content: "\f2a5"; }
+.bi-alphabet::before { content: "\f68a"; }
+.bi-amazon::before { content: "\f68d"; }
+.bi-arrows-collapse-vertical::before { content: "\f690"; }
+.bi-arrows-expand-vertical::before { content: "\f695"; }
+.bi-arrows-vertical::before { content: "\f698"; }
+.bi-arrows::before { content: "\f6a2"; }
+.bi-ban-fill::before { content: "\f6a3"; }
+.bi-ban::before { content: "\f6b6"; }
+.bi-bing::before { content: "\f6c2"; }
+.bi-cake::before { content: "\f6e0"; }
+.bi-cake2::before { content: "\f6ed"; }
+.bi-cookie::before { content: "\f6ee"; }
+.bi-copy::before { content: "\f759"; }
+.bi-crosshair::before { content: "\f769"; }
+.bi-crosshair2::before { content: "\f794"; }
+.bi-emoji-astonished-fill::before { content: "\f795"; }
+.bi-emoji-astonished::before { content: "\f79a"; }
+.bi-emoji-grimace-fill::before { content: "\f79b"; }
+.bi-emoji-grimace::before { content: "\f7a0"; }
+.bi-emoji-grin-fill::before { content: "\f7a1"; }
+.bi-emoji-grin::before { content: "\f7a6"; }
+.bi-emoji-surprise-fill::before { content: "\f7a7"; }
+.bi-emoji-surprise::before { content: "\f7ac"; }
+.bi-emoji-tear-fill::before { content: "\f7ad"; }
+.bi-emoji-tear::before { content: "\f7b2"; }
+.bi-envelope-arrow-down-fill::before { content: "\f7b3"; }
+.bi-envelope-arrow-down::before { content: "\f7b8"; }
+.bi-envelope-arrow-up-fill::before { content: "\f7b9"; }
+.bi-envelope-arrow-up::before { content: "\f7be"; }
+.bi-feather::before { content: "\f7bf"; }
+.bi-feather2::before { content: "\f7c4"; }
+.bi-floppy-fill::before { content: "\f7c5"; }
+.bi-floppy::before { content: "\f7d8"; }
+.bi-floppy2-fill::before { content: "\f7d9"; }
+.bi-floppy2::before { content: "\f7e4"; }
+.bi-gitlab::before { content: "\f7e5"; }
+.bi-highlighter::before { content: "\f7f8"; }
+.bi-marker-tip::before { content: "\f802"; }
+.bi-nvme-fill::before { content: "\f803"; }
+.bi-nvme::before { content: "\f80c"; }
+.bi-opencollective::before { content: "\f80d"; }
+.bi-pci-card-network::before { content: "\f8cd"; }
+.bi-pci-card-sound::before { content: "\f8ce"; }
+.bi-radar::before { content: "\f8cf"; }
+.bi-send-arrow-down-fill::before { content: "\f8d0"; }
+.bi-send-arrow-down::before { content: "\f8d1"; }
+.bi-send-arrow-up-fill::before { content: "\f8d2"; }
+.bi-send-arrow-up::before { content: "\f8d3"; }
+.bi-sim-slash-fill::before { content: "\f8d4"; }
+.bi-sim-slash::before { content: "\f8d5"; }
+.bi-sourceforge::before { content: "\f8d6"; }
+.bi-substack::before { content: "\f8d7"; }
+.bi-threads-fill::before { content: "\f8d8"; }
+.bi-threads::before { content: "\f8d9"; }
+.bi-transparency::before { content: "\f8da"; }
+.bi-twitter-x::before { content: "\f8db"; }
+.bi-type-h4::before { content: "\f8dc"; }
+.bi-type-h5::before { content: "\f8dd"; }
+.bi-type-h6::before { content: "\f8de"; }
+.bi-backpack-fill::before { content: "\f8df"; }
+.bi-backpack::before { content: "\f8e0"; }
+.bi-backpack2-fill::before { content: "\f8e1"; }
+.bi-backpack2::before { content: "\f8e2"; }
+.bi-backpack3-fill::before { content: "\f8e3"; }
+.bi-backpack3::before { content: "\f8e4"; }
+.bi-backpack4-fill::before { content: "\f8e5"; }
+.bi-backpack4::before { content: "\f8e6"; }
+.bi-brilliance::before { content: "\f8e7"; }
+.bi-cake-fill::before { content: "\f8e8"; }
+.bi-cake2-fill::before { content: "\f8e9"; }
+.bi-duffle-fill::before { content: "\f8ea"; }
+.bi-duffle::before { content: "\f8eb"; }
+.bi-exposure::before { content: "\f8ec"; }
+.bi-gender-neuter::before { content: "\f8ed"; }
+.bi-highlights::before { content: "\f8ee"; }
+.bi-luggage-fill::before { content: "\f8ef"; }
+.bi-luggage::before { content: "\f8f0"; }
+.bi-mailbox-flag::before { content: "\f8f1"; }
+.bi-mailbox2-flag::before { content: "\f8f2"; }
+.bi-noise-reduction::before { content: "\f8f3"; }
+.bi-passport-fill::before { content: "\f8f4"; }
+.bi-passport::before { content: "\f8f5"; }
+.bi-person-arms-up::before { content: "\f8f6"; }
+.bi-person-raised-hand::before { content: "\f8f7"; }
+.bi-person-standing-dress::before { content: "\f8f8"; }
+.bi-person-standing::before { content: "\f8f9"; }
+.bi-person-walking::before { content: "\f8fa"; }
+.bi-person-wheelchair::before { content: "\f8fb"; }
+.bi-shadows::before { content: "\f8fc"; }
+.bi-suitcase-fill::before { content: "\f8fd"; }
+.bi-suitcase-lg-fill::before { content: "\f8fe"; }
+.bi-suitcase-lg::before { content: "\f8ff"; }
+.bi-suitcase::before { content: "\f900"; }
+.bi-suitcase2-fill::before { content: "\f901"; }
+.bi-suitcase2::before { content: "\f902"; }
+.bi-vignette::before { content: "\f903"; }
diff --git a/public/notebooks/isic-2024_kaggle_files/libs/bootstrap/bootstrap-icons.woff b/public/notebooks/isic-2024_kaggle_files/libs/bootstrap/bootstrap-icons.woff
new file mode 100644
index 0000000..dbeeb05
Binary files /dev/null and b/public/notebooks/isic-2024_kaggle_files/libs/bootstrap/bootstrap-icons.woff differ
diff --git a/public/notebooks/isic-2024_kaggle_files/libs/bootstrap/bootstrap.min.js b/public/notebooks/isic-2024_kaggle_files/libs/bootstrap/bootstrap.min.js
new file mode 100644
index 0000000..e8f21f7
--- /dev/null
+++ b/public/notebooks/isic-2024_kaggle_files/libs/bootstrap/bootstrap.min.js
@@ -0,0 +1,7 @@
+/*!
+ * Bootstrap v5.3.1 (https://getbootstrap.com/)
+ * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=I(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return P(s,{delegateTarget:r}),n.oneOff&&N.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return P(n,{delegateTarget:t}),i.oneOff&&N.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function $(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function I(t){return t=t.replace(y,""),T[t]||t}const N={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))$(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(w,"");a&&!e.includes(s)||D(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;D(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==I(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=P(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function P(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function M(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function j(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const F={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${j(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${j(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=M(t.dataset[n])}return e},getDataAttribute:(t,e)=>M(t.getAttribute(`data-bs-${j(e)}`))};class H{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?F.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?F.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],r=o(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(r))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${r}" but expected type "${s}".`)}var i}}class W extends H{constructor(t,i){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(i),e.set(this._element,this.constructor.DATA_KEY,this))}dispose(){e.remove(this._element,this.constructor.DATA_KEY),N.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return e.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.1"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const B=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return n(e)},z={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))},getSelectorFromElement(t){const e=B(t);return e&&z.findOne(e)?e:null},getElementFromSelector(t){const e=B(t);return e?z.findOne(e):null},getMultipleElementsFromSelector(t){const e=B(t);return e?z.find(e):[]}},R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;N.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const s=z.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},q=".bs.alert",V=`close${q}`,K=`closed${q}`;class Q extends W{static get NAME(){return"alert"}close(){if(N.trigger(this._element,V).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),N.trigger(this._element,K),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Q.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(Q,"close"),m(Q);const X='[data-bs-toggle="button"]';class Y extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=Y.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}N.on(document,"click.bs.button.data-api",X,(t=>{t.preventDefault();const e=t.target.closest(X);Y.getOrCreateInstance(e).toggle()})),m(Y);const U=".bs.swipe",G=`touchstart${U}`,J=`touchmove${U}`,Z=`touchend${U}`,tt=`pointerdown${U}`,et=`pointerup${U}`,it={endCallback:null,leftCallback:null,rightCallback:null},nt={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class st extends H{constructor(t,e){super(),this._element=t,t&&st.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return it}static get DefaultType(){return nt}static get NAME(){return"swipe"}dispose(){N.off(this._element,U)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),g(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&g(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(N.on(this._element,tt,(t=>this._start(t))),N.on(this._element,et,(t=>this._end(t))),this._element.classList.add("pointer-event")):(N.on(this._element,G,(t=>this._start(t))),N.on(this._element,J,(t=>this._move(t))),N.on(this._element,Z,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const ot=".bs.carousel",rt=".data-api",at="next",lt="prev",ct="left",ht="right",dt=`slide${ot}`,ut=`slid${ot}`,ft=`keydown${ot}`,pt=`mouseenter${ot}`,mt=`mouseleave${ot}`,gt=`dragstart${ot}`,_t=`load${ot}${rt}`,bt=`click${ot}${rt}`,vt="carousel",yt="active",wt=".active",At=".carousel-item",Et=wt+At,Tt={ArrowLeft:ht,ArrowRight:ct},Ct={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Ot={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class xt extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===vt&&this.cycle()}static get Default(){return Ct}static get DefaultType(){return Ot}static get NAME(){return"carousel"}next(){this._slide(at)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(lt)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?N.one(this._element,ut,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void N.one(this._element,ut,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?at:lt;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&N.on(this._element,ft,(t=>this._keydown(t))),"hover"===this._config.pause&&(N.on(this._element,pt,(()=>this.pause())),N.on(this._element,mt,(()=>this._maybeEnableCycle()))),this._config.touch&&st.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of z.find(".carousel-item img",this._element))N.on(t,gt,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ct)),rightCallback:()=>this._slide(this._directionToOrder(ht)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new st(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Tt[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=z.findOne(wt,this._indicatorsElement);e.classList.remove(yt),e.removeAttribute("aria-current");const i=z.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(yt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===at,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>N.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(dt).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(yt),i.classList.remove(yt,c,l),this._isSliding=!1,r(ut)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return z.findOne(Et,this._element)}_getItems(){return z.find(At,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===ct?lt:at:t===ct?at:lt}_orderToDirection(t){return p()?t===lt?ct:ht:t===lt?ht:ct}static jQueryInterface(t){return this.each((function(){const e=xt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}N.on(document,bt,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=z.getElementFromSelector(this);if(!e||!e.classList.contains(vt))return;t.preventDefault();const i=xt.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===F.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),N.on(window,_t,(()=>{const t=z.find('[data-bs-ride="carousel"]');for(const e of t)xt.getOrCreateInstance(e)})),m(xt);const kt=".bs.collapse",Lt=`show${kt}`,St=`shown${kt}`,Dt=`hide${kt}`,$t=`hidden${kt}`,It=`click${kt}.data-api`,Nt="show",Pt="collapse",Mt="collapsing",jt=`:scope .${Pt} .${Pt}`,Ft='[data-bs-toggle="collapse"]',Ht={parent:null,toggle:!0},Wt={parent:"(null|element)",toggle:"boolean"};class Bt extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=z.find(Ft);for(const t of i){const e=z.getSelectorFromElement(t),i=z.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Ht}static get DefaultType(){return Wt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Bt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(N.trigger(this._element,Lt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Pt),this._element.classList.add(Mt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt,Nt),this._element.style[e]="",N.trigger(this._element,St)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(N.trigger(this._element,Dt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(Mt),this._element.classList.remove(Pt,Nt);for(const t of this._triggerArray){const e=z.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt),N.trigger(this._element,$t)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(Nt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Ft);for(const e of t){const t=z.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=z.find(jt,this._config.parent);return z.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Bt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}N.on(document,It,Ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of z.getMultipleElementsFromSelector(this))Bt.getOrCreateInstance(t,{toggle:!1}).toggle()})),m(Bt);var zt="top",Rt="bottom",qt="right",Vt="left",Kt="auto",Qt=[zt,Rt,qt,Vt],Xt="start",Yt="end",Ut="clippingParents",Gt="viewport",Jt="popper",Zt="reference",te=Qt.reduce((function(t,e){return t.concat([e+"-"+Xt,e+"-"+Yt])}),[]),ee=[].concat(Qt,[Kt]).reduce((function(t,e){return t.concat([e,e+"-"+Xt,e+"-"+Yt])}),[]),ie="beforeRead",ne="read",se="afterRead",oe="beforeMain",re="main",ae="afterMain",le="beforeWrite",ce="write",he="afterWrite",de=[ie,ne,se,oe,re,ae,le,ce,he];function ue(t){return t?(t.nodeName||"").toLowerCase():null}function fe(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function pe(t){return t instanceof fe(t).Element||t instanceof Element}function me(t){return t instanceof fe(t).HTMLElement||t instanceof HTMLElement}function ge(t){return"undefined"!=typeof ShadowRoot&&(t instanceof fe(t).ShadowRoot||t instanceof ShadowRoot)}const _e={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];me(s)&&ue(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});me(n)&&ue(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function be(t){return t.split("-")[0]}var ve=Math.max,ye=Math.min,we=Math.round;function Ae(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ee(){return!/^((?!chrome|android).)*safari/i.test(Ae())}function Te(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&me(t)&&(s=t.offsetWidth>0&&we(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&we(n.height)/t.offsetHeight||1);var r=(pe(t)?fe(t):window).visualViewport,a=!Ee()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function Ce(t){var e=Te(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Oe(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&ge(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function xe(t){return fe(t).getComputedStyle(t)}function ke(t){return["table","td","th"].indexOf(ue(t))>=0}function Le(t){return((pe(t)?t.ownerDocument:t.document)||window.document).documentElement}function Se(t){return"html"===ue(t)?t:t.assignedSlot||t.parentNode||(ge(t)?t.host:null)||Le(t)}function De(t){return me(t)&&"fixed"!==xe(t).position?t.offsetParent:null}function $e(t){for(var e=fe(t),i=De(t);i&&ke(i)&&"static"===xe(i).position;)i=De(i);return i&&("html"===ue(i)||"body"===ue(i)&&"static"===xe(i).position)?e:i||function(t){var e=/firefox/i.test(Ae());if(/Trident/i.test(Ae())&&me(t)&&"fixed"===xe(t).position)return null;var i=Se(t);for(ge(i)&&(i=i.host);me(i)&&["html","body"].indexOf(ue(i))<0;){var n=xe(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Ie(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function Ne(t,e,i){return ve(t,ye(e,i))}function Pe(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function Me(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const je={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=be(i.placement),l=Ie(a),c=[Vt,qt].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return Pe("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:Me(t,Qt))}(s.padding,i),d=Ce(o),u="y"===l?zt:Vt,f="y"===l?Rt:qt,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=$e(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=Ne(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Oe(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Fe(t){return t.split("-")[1]}var He={top:"auto",right:"auto",bottom:"auto",left:"auto"};function We(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,m=void 0===p?0:p,g="function"==typeof h?h({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=Vt,y=zt,w=window;if(c){var A=$e(i),E="clientHeight",T="clientWidth";A===fe(i)&&"static"!==xe(A=Le(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===zt||(s===Vt||s===qt)&&o===Yt)&&(y=Rt,m-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,m*=l?1:-1),s!==Vt&&(s!==zt&&s!==Rt||o!==Yt)||(v=qt,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&He),x=!0===h?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:we(i*s)/s||0,y:we(n*s)/s||0}}({x:f,y:m},fe(i)):{x:f,y:m};return f=x.x,m=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?m+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const Be={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:be(e.placement),variation:Fe(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,We(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,We(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var ze={passive:!0};const Re={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=fe(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,ze)})),a&&l.addEventListener("resize",i.update,ze),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,ze)})),a&&l.removeEventListener("resize",i.update,ze)}},data:{}};var qe={left:"right",right:"left",bottom:"top",top:"bottom"};function Ve(t){return t.replace(/left|right|bottom|top/g,(function(t){return qe[t]}))}var Ke={start:"end",end:"start"};function Qe(t){return t.replace(/start|end/g,(function(t){return Ke[t]}))}function Xe(t){var e=fe(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ye(t){return Te(Le(t)).left+Xe(t).scrollLeft}function Ue(t){var e=xe(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ge(t){return["html","body","#document"].indexOf(ue(t))>=0?t.ownerDocument.body:me(t)&&Ue(t)?t:Ge(Se(t))}function Je(t,e){var i;void 0===e&&(e=[]);var n=Ge(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=fe(n),r=s?[o].concat(o.visualViewport||[],Ue(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Je(Se(r)))}function Ze(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function ti(t,e,i){return e===Gt?Ze(function(t,e){var i=fe(t),n=Le(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ee();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ye(t),y:l}}(t,i)):pe(e)?function(t,e){var i=Te(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):Ze(function(t){var e,i=Le(t),n=Xe(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ve(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ve(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ye(t),l=-n.scrollTop;return"rtl"===xe(s||i).direction&&(a+=ve(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Le(t)))}function ei(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?be(s):null,r=s?Fe(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case zt:e={x:a,y:i.y-n.height};break;case Rt:e={x:a,y:i.y+i.height};break;case qt:e={x:i.x+i.width,y:l};break;case Vt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Ie(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case Xt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Yt:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ii(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Ut:a,c=i.rootBoundary,h=void 0===c?Gt:c,d=i.elementContext,u=void 0===d?Jt:d,f=i.altBoundary,p=void 0!==f&&f,m=i.padding,g=void 0===m?0:m,_=Pe("number"!=typeof g?g:Me(g,Qt)),b=u===Jt?Zt:Jt,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Je(Se(t)),i=["absolute","fixed"].indexOf(xe(t).position)>=0&&me(t)?$e(t):t;return pe(i)?e.filter((function(t){return pe(t)&&Oe(t,i)&&"body"!==ue(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=ti(t,i,n);return e.top=ve(s.top,e.top),e.right=ye(s.right,e.right),e.bottom=ye(s.bottom,e.bottom),e.left=ve(s.left,e.left),e}),ti(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(pe(y)?y:y.contextElement||Le(t.elements.popper),l,h,r),A=Te(t.elements.reference),E=ei({reference:A,element:v,strategy:"absolute",placement:s}),T=Ze(Object.assign({},v,E)),C=u===Jt?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===Jt&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[qt,Rt].indexOf(t)>=0?1:-1,i=[zt,Rt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function ni(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?ee:l,h=Fe(n),d=h?a?te:te.filter((function(t){return Fe(t)===h})):Qt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ii(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[be(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const si={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=be(g),b=l||(_!==g&&p?function(t){if(be(t)===Kt)return[];var e=Ve(t);return[Qe(t),e,Qe(e)]}(g):[Ve(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(be(i)===Kt?ni(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,S=L?"width":"height",D=ii(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),$=L?k?qt:Vt:k?Rt:zt;y[S]>w[S]&&($=Ve($));var I=Ve($),N=[];if(o&&N.push(D[x]<=0),a&&N.push(D[$]<=0,D[I]<=0),N.every((function(t){return t}))){T=O,E=!1;break}A.set(O,N)}if(E)for(var P=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==P(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function oi(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ri(t){return[zt,qt,Rt,Vt].some((function(e){return t[e]>=0}))}const ai={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ii(e,{elementContext:"reference"}),a=ii(e,{altBoundary:!0}),l=oi(r,n),c=oi(a,s,o),h=ri(l),d=ri(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},li={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=ee.reduce((function(t,i){return t[i]=function(t,e,i){var n=be(t),s=[Vt,zt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[Vt,qt].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},ci={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ei({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},hi={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ii(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=be(e.placement),b=Fe(e.placement),v=!b,y=Ie(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,S="y"===y?zt:Vt,D="y"===y?Rt:qt,$="y"===y?"height":"width",I=A[y],N=I+g[S],P=I-g[D],M=f?-T[$]/2:0,j=b===Xt?E[$]:T[$],F=b===Xt?-T[$]:-E[$],H=e.elements.arrow,W=f&&H?Ce(H):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=B[S],R=B[D],q=Ne(0,E[$],W[$]),V=v?E[$]/2-M-q-z-O.mainAxis:j-q-z-O.mainAxis,K=v?-E[$]/2+M+q+R+O.mainAxis:F+q+R+O.mainAxis,Q=e.elements.arrow&&$e(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=I+K-Y,G=Ne(f?ye(N,I+V-Y-X):N,I,f?ve(P,U):P);A[y]=G,k[y]=G-I}if(a){var J,Z="x"===y?zt:Vt,tt="x"===y?Rt:qt,et=A[w],it="y"===w?"height":"width",nt=et+g[Z],st=et-g[tt],ot=-1!==[zt,Vt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=Ne(t,e,i);return n>i?i:n}(at,et,lt):Ne(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function di(t,e,i){void 0===i&&(i=!1);var n,s,o=me(e),r=me(e)&&function(t){var e=t.getBoundingClientRect(),i=we(e.width)/t.offsetWidth||1,n=we(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=Le(e),l=Te(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==ue(e)||Ue(a))&&(c=(n=e)!==fe(n)&&me(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:Xe(n)),me(e)?((h=Te(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ye(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function ui(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var fi={placement:"bottom",modifiers:[],strategy:"absolute"};function pi(){for(var t=arguments.length,e=new Array(t),i=0;iNumber.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(F.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...g(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=z.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Ti,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=z.find(Ni);for(const i of e){const e=qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Ei,Ti].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ii)?this:z.prev(this,Ii)[0]||z.next(this,Ii)[0]||z.findOne(Ii,t.delegateTarget.parentNode),o=qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}N.on(document,Si,Ii,qi.dataApiKeydownHandler),N.on(document,Si,Pi,qi.dataApiKeydownHandler),N.on(document,Li,qi.clearMenus),N.on(document,Di,qi.clearMenus),N.on(document,Li,Ii,(function(t){t.preventDefault(),qi.getOrCreateInstance(this).toggle()})),m(qi);const Vi="backdrop",Ki="show",Qi=`mousedown.bs.${Vi}`,Xi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Yi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Ui extends H{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Xi}static get DefaultType(){return Yi}static get NAME(){return Vi}show(t){if(!this._config.isVisible)return void g(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(Ki),this._emulateAnimation((()=>{g(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Ki),this._emulateAnimation((()=>{this.dispose(),g(t)}))):g(t)}dispose(){this._isAppended&&(N.off(this._element,Qi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),N.on(t,Qi,(()=>{g(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const Gi=".bs.focustrap",Ji=`focusin${Gi}`,Zi=`keydown.tab${Gi}`,tn="backward",en={autofocus:!0,trapElement:null},nn={autofocus:"boolean",trapElement:"element"};class sn extends H{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return en}static get DefaultType(){return nn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),N.off(document,Gi),N.on(document,Ji,(t=>this._handleFocusin(t))),N.on(document,Zi,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,N.off(document,Gi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=z.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===tn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?tn:"forward")}}const on=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",rn=".sticky-top",an="padding-right",ln="margin-right";class cn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,an,(e=>e+t)),this._setElementAttributes(on,an,(e=>e+t)),this._setElementAttributes(rn,ln,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,an),this._resetElementAttributes(on,an),this._resetElementAttributes(rn,ln)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&F.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=F.getDataAttribute(t,e);null!==i?(F.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of z.find(t,this._element))e(i)}}const hn=".bs.modal",dn=`hide${hn}`,un=`hidePrevented${hn}`,fn=`hidden${hn}`,pn=`show${hn}`,mn=`shown${hn}`,gn=`resize${hn}`,_n=`click.dismiss${hn}`,bn=`mousedown.dismiss${hn}`,vn=`keydown.dismiss${hn}`,yn=`click${hn}.data-api`,wn="modal-open",An="show",En="modal-static",Tn={backdrop:!0,focus:!0,keyboard:!0},Cn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class On extends W{constructor(t,e){super(t,e),this._dialog=z.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new cn,this._addEventListeners()}static get Default(){return Tn}static get DefaultType(){return Cn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||N.trigger(this._element,pn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(wn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(N.trigger(this._element,dn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(An),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){N.off(window,hn),N.off(this._dialog,hn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Ui({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=z.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(An),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,N.trigger(this._element,mn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){N.on(this._element,vn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),N.on(window,gn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),N.on(this._element,bn,(t=>{N.one(this._element,_n,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(wn),this._resetAdjustments(),this._scrollBar.reset(),N.trigger(this._element,fn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(N.trigger(this._element,un).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(En)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(En),this._queueCallback((()=>{this._element.classList.remove(En),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=On.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}N.on(document,yn,'[data-bs-toggle="modal"]',(function(t){const e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),N.one(e,pn,(t=>{t.defaultPrevented||N.one(e,fn,(()=>{a(this)&&this.focus()}))}));const i=z.findOne(".modal.show");i&&On.getInstance(i).hide(),On.getOrCreateInstance(e).toggle(this)})),R(On),m(On);const xn=".bs.offcanvas",kn=".data-api",Ln=`load${xn}${kn}`,Sn="show",Dn="showing",$n="hiding",In=".offcanvas.show",Nn=`show${xn}`,Pn=`shown${xn}`,Mn=`hide${xn}`,jn=`hidePrevented${xn}`,Fn=`hidden${xn}`,Hn=`resize${xn}`,Wn=`click${xn}${kn}`,Bn=`keydown.dismiss${xn}`,zn={backdrop:!0,keyboard:!0,scroll:!1},Rn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class qn extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return zn}static get DefaultType(){return Rn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||N.trigger(this._element,Nn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new cn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Dn),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Sn),this._element.classList.remove(Dn),N.trigger(this._element,Pn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(N.trigger(this._element,Mn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add($n),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Sn,$n),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new cn).reset(),N.trigger(this._element,Fn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Ui({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():N.trigger(this._element,jn)}:null})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_addEventListeners(){N.on(this._element,Bn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():N.trigger(this._element,jn))}))}static jQueryInterface(t){return this.each((function(){const e=qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}N.on(document,Wn,'[data-bs-toggle="offcanvas"]',(function(t){const e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;N.one(e,Fn,(()=>{a(this)&&this.focus()}));const i=z.findOne(In);i&&i!==e&&qn.getInstance(i).hide(),qn.getOrCreateInstance(e).toggle(this)})),N.on(window,Ln,(()=>{for(const t of z.find(In))qn.getOrCreateInstance(t).show()})),N.on(window,Hn,(()=>{for(const t of z.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&qn.getOrCreateInstance(t).hide()})),R(qn),m(qn);const Vn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Kn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Qn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Xn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Kn.has(i)||Boolean(Qn.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Yn={allowList:Vn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:""},Un={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Gn={entry:"(string|element|function|null)",selector:"(string|element)"};class Jn extends H{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Yn}static get DefaultType(){return Un}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Gn)}_setContent(t,e,i){const n=z.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Xn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return g(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Zn=new Set(["sanitize","allowList","sanitizeFn"]),ts="fade",es="show",is=".modal",ns="hide.bs.modal",ss="hover",os="focus",rs={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},as={allowList:Vn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ls={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class cs extends W{constructor(t,e){if(void 0===vi)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return as}static get DefaultType(){return ls}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),N.off(this._element.closest(is),ns,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=N.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),N.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.on(t,"mouseover",h);this._queueCallback((()=>{N.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!N.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger[os]=!1,this._activeTrigger[ss]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),N.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ts,es),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ts),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Jn({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ts)}_isShown(){return this.tip&&this.tip.classList.contains(es)}_createPopper(t){const e=g(this._config.placement,[this,t,this._element]),i=rs[e.toUpperCase()];return bi(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return g(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...g(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)N.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ss?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ss?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");N.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?os:ss]=!0,e._enter()})),N.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?os:ss]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},N.on(this._element.closest(is),ns,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=F.getDataAttributes(this._element);for(const t of Object.keys(e))Zn.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=cs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(cs);const hs={...cs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},ds={...cs.DefaultType,content:"(null|string|element|function)"};class us extends cs{static get Default(){return hs}static get DefaultType(){return ds}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=us.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(us);const fs=".bs.scrollspy",ps=`activate${fs}`,ms=`click${fs}`,gs=`load${fs}.data-api`,_s="active",bs="[href]",vs=".nav-link",ys=`${vs}, .nav-item > ${vs}, .list-group-item`,ws={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},As={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Es extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ws}static get DefaultType(){return As}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(N.off(this._config.target,ms),N.on(this._config.target,ms,bs,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=z.find(bs,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=z.findOne(decodeURI(e.hash),this._element);a(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(_s),this._activateParents(t),N.trigger(this._element,ps,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))z.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(_s);else for(const e of z.parents(t,".nav, .list-group"))for(const t of z.prev(e,ys))t.classList.add(_s)}_clearActiveClass(t){t.classList.remove(_s);const e=z.find(`${bs}.${_s}`,t);for(const t of e)t.classList.remove(_s)}static jQueryInterface(t){return this.each((function(){const e=Es.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(window,gs,(()=>{for(const t of z.find('[data-bs-spy="scroll"]'))Es.getOrCreateInstance(t)})),m(Es);const Ts=".bs.tab",Cs=`hide${Ts}`,Os=`hidden${Ts}`,xs=`show${Ts}`,ks=`shown${Ts}`,Ls=`click${Ts}`,Ss=`keydown${Ts}`,Ds=`load${Ts}`,$s="ArrowLeft",Is="ArrowRight",Ns="ArrowUp",Ps="ArrowDown",Ms="Home",js="End",Fs="active",Hs="fade",Ws="show",Bs=":not(.dropdown-toggle)",zs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Rs=`.nav-link${Bs}, .list-group-item${Bs}, [role="tab"]${Bs}, ${zs}`,qs=`.${Fs}[data-bs-toggle="tab"], .${Fs}[data-bs-toggle="pill"], .${Fs}[data-bs-toggle="list"]`;class Vs extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),N.on(this._element,Ss,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?N.trigger(e,Cs,{relatedTarget:t}):null;N.trigger(t,xs,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Fs),this._activate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),N.trigger(t,ks,{relatedTarget:e})):t.classList.add(Ws)}),t,t.classList.contains(Hs)))}_deactivate(t,e){t&&(t.classList.remove(Fs),t.blur(),this._deactivate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),N.trigger(t,Os,{relatedTarget:e})):t.classList.remove(Ws)}),t,t.classList.contains(Hs)))}_keydown(t){if(![$s,Is,Ns,Ps,Ms,js].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!l(t)));let i;if([Ms,js].includes(t.key))i=e[t.key===Ms?0:e.length-1];else{const n=[Is,Ps].includes(t.key);i=b(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Vs.getOrCreateInstance(i).show())}_getChildren(){return z.find(Rs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=z.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=z.findOne(t,i);s&&s.classList.toggle(n,e)};n(".dropdown-toggle",Fs),n(".dropdown-menu",Ws),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Fs)}_getInnerElement(t){return t.matches(Rs)?t:z.findOne(Rs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Vs.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(document,Ls,zs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||Vs.getOrCreateInstance(this).show()})),N.on(window,Ds,(()=>{for(const t of z.find(qs))Vs.getOrCreateInstance(t)})),m(Vs);const Ks=".bs.toast",Qs=`mouseover${Ks}`,Xs=`mouseout${Ks}`,Ys=`focusin${Ks}`,Us=`focusout${Ks}`,Gs=`hide${Ks}`,Js=`hidden${Ks}`,Zs=`show${Ks}`,to=`shown${Ks}`,eo="hide",io="show",no="showing",so={animation:"boolean",autohide:"boolean",delay:"number"},oo={animation:!0,autohide:!0,delay:5e3};class ro extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return oo}static get DefaultType(){return so}static get NAME(){return"toast"}show(){N.trigger(this._element,Zs).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(eo),d(this._element),this._element.classList.add(io,no),this._queueCallback((()=>{this._element.classList.remove(no),N.trigger(this._element,to),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(N.trigger(this._element,Gs).defaultPrevented||(this._element.classList.add(no),this._queueCallback((()=>{this._element.classList.add(eo),this._element.classList.remove(no,io),N.trigger(this._element,Js)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(io),super.dispose()}isShown(){return this._element.classList.contains(io)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){N.on(this._element,Qs,(t=>this._onInteraction(t,!0))),N.on(this._element,Xs,(t=>this._onInteraction(t,!1))),N.on(this._element,Ys,(t=>this._onInteraction(t,!0))),N.on(this._element,Us,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=ro.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(ro),m(ro),{Alert:Q,Button:Y,Carousel:xt,Collapse:Bt,Dropdown:qi,Modal:On,Offcanvas:qn,Popover:us,ScrollSpy:Es,Tab:Vs,Toast:ro,Tooltip:cs}}));
+//# sourceMappingURL=bootstrap.bundle.min.js.map
\ No newline at end of file
diff --git a/public/notebooks/isic-2024_kaggle_files/libs/clipboard/clipboard.min.js b/public/notebooks/isic-2024_kaggle_files/libs/clipboard/clipboard.min.js
new file mode 100644
index 0000000..1103f81
--- /dev/null
+++ b/public/notebooks/isic-2024_kaggle_files/libs/clipboard/clipboard.min.js
@@ -0,0 +1,7 @@
+/*!
+ * clipboard.js v2.0.11
+ * https://clipboardjs.com/
+ *
+ * Licensed MIT © Zeno Rocha
+ */
+!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1.anchorjs-link,.anchorjs-link:focus{opacity:1}",A.sheet.cssRules.length),A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",A.sheet.cssRules.length),A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',A.sheet.cssRules.length)),h=document.querySelectorAll("[id]"),t=[].map.call(h,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}});
+// @license-end
\ No newline at end of file
diff --git a/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/popper.min.js b/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/popper.min.js
new file mode 100644
index 0000000..e3726d7
--- /dev/null
+++ b/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/popper.min.js
@@ -0,0 +1,6 @@
+/**
+ * @popperjs/core v2.11.7 - MIT License
+ */
+
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(){var e=navigator.userAgentData;return null!=e&&e.brands&&Array.isArray(e.brands)?e.brands.map((function(e){return e.brand+"/"+e.version})).join(" "):navigator.userAgent}function c(){return!/^((?!chrome|android).)*safari/i.test(f())}function p(e,o,i){void 0===o&&(o=!1),void 0===i&&(i=!1);var a=e.getBoundingClientRect(),f=1,p=1;o&&r(e)&&(f=e.offsetWidth>0&&s(a.width)/e.offsetWidth||1,p=e.offsetHeight>0&&s(a.height)/e.offsetHeight||1);var u=(n(e)?t(e):window).visualViewport,l=!c()&&i,d=(a.left+(l&&u?u.offsetLeft:0))/f,h=(a.top+(l&&u?u.offsetTop:0))/p,m=a.width/f,v=a.height/p;return{width:m,height:v,top:h,right:d+m,bottom:h+v,left:d,x:d,y:h}}function u(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function l(e){return e?(e.nodeName||"").toLowerCase():null}function d(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function h(e){return p(d(e)).left+u(e).scrollLeft}function m(e){return t(e).getComputedStyle(e)}function v(e){var t=m(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function y(e,n,o){void 0===o&&(o=!1);var i,a,f=r(n),c=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),m=d(n),y=p(e,c,o),g={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(f||!f&&!o)&&(("body"!==l(n)||v(m))&&(g=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:u(i)),r(n)?((b=p(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):m&&(b.x=h(m))),{x:y.left+g.scrollLeft-b.x,y:y.top+g.scrollTop-b.y,width:y.width,height:y.height}}function g(e){var t=p(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function b(e){return"html"===l(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||d(e)}function x(e){return["html","body","#document"].indexOf(l(e))>=0?e.ownerDocument.body:r(e)&&v(e)?e:x(b(e))}function w(e,n){var r;void 0===n&&(n=[]);var o=x(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],v(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(w(b(s)))}function O(e){return["table","td","th"].indexOf(l(e))>=0}function j(e){return r(e)&&"fixed"!==m(e).position?e.offsetParent:null}function E(e){for(var n=t(e),i=j(e);i&&O(i)&&"static"===m(i).position;)i=j(i);return i&&("html"===l(i)||"body"===l(i)&&"static"===m(i).position)?n:i||function(e){var t=/firefox/i.test(f());if(/Trident/i.test(f())&&r(e)&&"fixed"===m(e).position)return null;var n=b(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(l(n))<0;){var i=m(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var D="top",A="bottom",L="right",P="left",M="auto",k=[D,A,L,P],W="start",B="end",H="viewport",T="popper",R=k.reduce((function(e,t){return e.concat([t+"-"+W,t+"-"+B])}),[]),S=[].concat(k,[M]).reduce((function(e,t){return e.concat([t,t+"-"+W,t+"-"+B])}),[]),V=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function q(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function N(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function I(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function _(e,r,o){return r===H?I(function(e,n){var r=t(e),o=d(e),i=r.visualViewport,a=o.clientWidth,s=o.clientHeight,f=0,p=0;if(i){a=i.width,s=i.height;var u=c();(u||!u&&"fixed"===n)&&(f=i.offsetLeft,p=i.offsetTop)}return{width:a,height:s,x:f+h(e),y:p}}(e,o)):n(r)?function(e,t){var n=p(e,!1,"fixed"===t);return n.top=n.top+e.clientTop,n.left=n.left+e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}(r,o):I(function(e){var t,n=d(e),r=u(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+h(e),c=-r.scrollTop;return"rtl"===m(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:c}}(d(e)))}function F(e,t,o,s){var f="clippingParents"===t?function(e){var t=w(b(e)),o=["absolute","fixed"].indexOf(m(e).position)>=0&&r(e)?E(e):e;return n(o)?t.filter((function(e){return n(e)&&N(e,o)&&"body"!==l(e)})):[]}(e):[].concat(t),c=[].concat(f,[o]),p=c[0],u=c.reduce((function(t,n){var r=_(e,n,s);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),_(e,p,s));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function U(e){return e.split("-")[1]}function z(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function X(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?U(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case D:t={x:s,y:n.y-r.height};break;case A:t={x:s,y:n.y+n.height};break;case L:t={x:n.x+n.width,y:f};break;case P:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?z(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case W:t[c]=t[c]-(n[p]/2-r[p]/2);break;case B:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function Y(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function G(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function J(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.strategy,s=void 0===a?e.strategy:a,f=r.boundary,c=void 0===f?"clippingParents":f,u=r.rootBoundary,l=void 0===u?H:u,h=r.elementContext,m=void 0===h?T:h,v=r.altBoundary,y=void 0!==v&&v,g=r.padding,b=void 0===g?0:g,x=Y("number"!=typeof b?b:G(b,k)),w=m===T?"reference":T,O=e.rects.popper,j=e.elements[y?w:m],E=F(n(j)?j:j.contextElement||d(e.elements.popper),c,l,s),P=p(e.elements.reference),M=X({reference:P,element:O,strategy:"absolute",placement:i}),W=I(Object.assign({},O,M)),B=m===T?W:P,R={top:E.top-B.top+x.top,bottom:B.bottom-E.bottom+x.bottom,left:E.left-B.left+x.left,right:B.right-E.right+x.right},S=e.modifiersData.offset;if(m===T&&S){var V=S[i];Object.keys(R).forEach((function(e){var t=[L,A].indexOf(e)>=0?1:-1,n=[D,A].indexOf(e)>=0?"y":"x";R[e]+=V[n]*t}))}return R}var K={placement:"bottom",modifiers:[],strategy:"absolute"};function Q(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[P,L].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},se={left:"right",right:"left",bottom:"top",top:"bottom"};function fe(e){return e.replace(/left|right|bottom|top/g,(function(e){return se[e]}))}var ce={start:"end",end:"start"};function pe(e){return e.replace(/start|end/g,(function(e){return ce[e]}))}function ue(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?S:f,p=U(r),u=p?s?R:R.filter((function(e){return U(e)===p})):k,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=J(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var le={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,y=C(v),g=f||(y===v||!h?[fe(v)]:function(e){if(C(e)===M)return[];var t=fe(e);return[pe(e),t,pe(t)]}(v)),b=[v].concat(g).reduce((function(e,n){return e.concat(C(n)===M?ue(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,j=!0,E=b[0],k=0;k=0,S=R?"width":"height",V=J(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),q=R?T?L:P:T?A:D;x[S]>w[S]&&(q=fe(q));var N=fe(q),I=[];if(i&&I.push(V[H]<=0),s&&I.push(V[q]<=0,V[N]<=0),I.every((function(e){return e}))){E=B,j=!1;break}O.set(B,I)}if(j)for(var _=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return E=t,"break"},F=h?3:1;F>0;F--){if("break"===_(F))break}t.placement!==E&&(t.modifiersData[r]._skip=!0,t.placement=E,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function de(e,t,n){return i(e,a(t,n))}var he={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,v=n.tetherOffset,y=void 0===v?0:v,b=J(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=U(t.placement),O=!w,j=z(x),M="x"===j?"y":"x",k=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,V={x:0,y:0};if(k){if(s){var q,N="y"===j?D:P,I="y"===j?A:L,_="y"===j?"height":"width",F=k[j],X=F+b[N],Y=F-b[I],G=m?-H[_]/2:0,K=w===W?B[_]:H[_],Q=w===W?-H[_]:-B[_],Z=t.elements.arrow,$=m&&Z?g(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=de(0,B[_],$[_]),oe=O?B[_]/2-G-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=O?-B[_]/2+G+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&E(t.elements.arrow),se=ae?"y"===j?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(q=null==S?void 0:S[j])?q:0,ce=F+ie-fe,pe=de(m?a(X,F+oe-fe-se):X,F,m?i(Y,ce):Y);k[j]=pe,V[j]=pe-F}if(c){var ue,le="x"===j?D:P,he="x"===j?A:L,me=k[M],ve="y"===M?"height":"width",ye=me+b[le],ge=me-b[he],be=-1!==[D,P].indexOf(x),xe=null!=(ue=null==S?void 0:S[M])?ue:0,we=be?ye:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ge,je=m&&be?function(e,t,n){var r=de(e,t,n);return r>n?n:r}(we,me,Oe):de(m?we:ye,me,m?Oe:ge);k[M]=je,V[M]=je-me}t.modifiersData[r]=V}},requiresIfExists:["offset"]};var me={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=z(s),c=[P,L].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return Y("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:G(e,k))}(o.padding,n),u=g(i),l="y"===f?D:P,d="y"===f?A:L,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],v=E(i),y=v?"y"===f?v.clientHeight||0:v.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],O=y/2-u[c]/2+b,j=de(x,O,w),M=f;n.modifiersData[r]=((t={})[M]=j,t.centerOffset=j-O,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&N(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ve(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function ye(e){return[D,L,A,P].some((function(t){return e[t]>=0}))}var ge={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=J(t,{elementContext:"reference"}),s=J(t,{altBoundary:!0}),f=ve(a,r),c=ve(s,o,i),p=ye(f),u=ye(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},be=Z({defaultModifiers:[ee,te,oe,ie]}),xe=[ee,te,oe,ie,ae,le,he,me,ge],we=Z({defaultModifiers:xe});e.applyStyles=ie,e.arrow=me,e.computeStyles=oe,e.createPopper=we,e.createPopperLite=be,e.defaultModifiers=xe,e.detectOverflow=J,e.eventListeners=ee,e.flip=le,e.hide=ge,e.offset=ae,e.popperGenerator=Z,e.popperOffsets=te,e.preventOverflow=he,Object.defineProperty(e,"__esModule",{value:!0})}));
+
diff --git a/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/quarto-syntax-highlighting-2f5df379a58b258e96c21c0638c20c03.css b/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/quarto-syntax-highlighting-2f5df379a58b258e96c21c0638c20c03.css
new file mode 100644
index 0000000..48bb62a
--- /dev/null
+++ b/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/quarto-syntax-highlighting-2f5df379a58b258e96c21c0638c20c03.css
@@ -0,0 +1,205 @@
+/* quarto syntax highlight colors */
+:root {
+ --quarto-hl-ot-color: #003B4F;
+ --quarto-hl-at-color: #657422;
+ --quarto-hl-ss-color: #20794D;
+ --quarto-hl-an-color: #5E5E5E;
+ --quarto-hl-fu-color: #4758AB;
+ --quarto-hl-st-color: #20794D;
+ --quarto-hl-cf-color: #003B4F;
+ --quarto-hl-op-color: #5E5E5E;
+ --quarto-hl-er-color: #AD0000;
+ --quarto-hl-bn-color: #AD0000;
+ --quarto-hl-al-color: #AD0000;
+ --quarto-hl-va-color: #111111;
+ --quarto-hl-bu-color: inherit;
+ --quarto-hl-ex-color: inherit;
+ --quarto-hl-pp-color: #AD0000;
+ --quarto-hl-in-color: #5E5E5E;
+ --quarto-hl-vs-color: #20794D;
+ --quarto-hl-wa-color: #5E5E5E;
+ --quarto-hl-do-color: #5E5E5E;
+ --quarto-hl-im-color: #00769E;
+ --quarto-hl-ch-color: #20794D;
+ --quarto-hl-dt-color: #AD0000;
+ --quarto-hl-fl-color: #AD0000;
+ --quarto-hl-co-color: #5E5E5E;
+ --quarto-hl-cv-color: #5E5E5E;
+ --quarto-hl-cn-color: #8f5902;
+ --quarto-hl-sc-color: #5E5E5E;
+ --quarto-hl-dv-color: #AD0000;
+ --quarto-hl-kw-color: #003B4F;
+}
+
+/* other quarto variables */
+:root {
+ --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+}
+
+pre > code.sourceCode > span {
+ color: #003B4F;
+}
+
+code span {
+ color: #003B4F;
+}
+
+code.sourceCode > span {
+ color: #003B4F;
+}
+
+div.sourceCode,
+div.sourceCode pre.sourceCode {
+ color: #003B4F;
+}
+
+code span.ot {
+ color: #003B4F;
+ font-style: inherit;
+}
+
+code span.at {
+ color: #657422;
+ font-style: inherit;
+}
+
+code span.ss {
+ color: #20794D;
+ font-style: inherit;
+}
+
+code span.an {
+ color: #5E5E5E;
+ font-style: inherit;
+}
+
+code span.fu {
+ color: #4758AB;
+ font-style: inherit;
+}
+
+code span.st {
+ color: #20794D;
+ font-style: inherit;
+}
+
+code span.cf {
+ color: #003B4F;
+ font-weight: bold;
+ font-style: inherit;
+}
+
+code span.op {
+ color: #5E5E5E;
+ font-style: inherit;
+}
+
+code span.er {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.bn {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.al {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.va {
+ color: #111111;
+ font-style: inherit;
+}
+
+code span.bu {
+ font-style: inherit;
+}
+
+code span.ex {
+ font-style: inherit;
+}
+
+code span.pp {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.in {
+ color: #5E5E5E;
+ font-style: inherit;
+}
+
+code span.vs {
+ color: #20794D;
+ font-style: inherit;
+}
+
+code span.wa {
+ color: #5E5E5E;
+ font-style: italic;
+}
+
+code span.do {
+ color: #5E5E5E;
+ font-style: italic;
+}
+
+code span.im {
+ color: #00769E;
+ font-style: inherit;
+}
+
+code span.ch {
+ color: #20794D;
+ font-style: inherit;
+}
+
+code span.dt {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.fl {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.co {
+ color: #5E5E5E;
+ font-style: inherit;
+}
+
+code span.cv {
+ color: #5E5E5E;
+ font-style: italic;
+}
+
+code span.cn {
+ color: #8f5902;
+ font-style: inherit;
+}
+
+code span.sc {
+ color: #5E5E5E;
+ font-style: inherit;
+}
+
+code span.dv {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.kw {
+ color: #003B4F;
+ font-weight: bold;
+ font-style: inherit;
+}
+
+.prevent-inlining {
+ content: "";
+}
+
+/*# sourceMappingURL=35eb38a806ee71ea6d2563be2308c832.css.map */
diff --git a/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/quarto.js b/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/quarto.js
new file mode 100644
index 0000000..3c24c29
--- /dev/null
+++ b/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/quarto.js
@@ -0,0 +1,911 @@
+const sectionChanged = new CustomEvent("quarto-sectionChanged", {
+ detail: {},
+ bubbles: true,
+ cancelable: false,
+ composed: false,
+});
+
+const layoutMarginEls = () => {
+ // Find any conflicting margin elements and add margins to the
+ // top to prevent overlap
+ const marginChildren = window.document.querySelectorAll(
+ ".column-margin.column-container > *, .margin-caption, .aside"
+ );
+
+ let lastBottom = 0;
+ for (const marginChild of marginChildren) {
+ if (marginChild.offsetParent !== null) {
+ // clear the top margin so we recompute it
+ marginChild.style.marginTop = null;
+ const top = marginChild.getBoundingClientRect().top + window.scrollY;
+ if (top < lastBottom) {
+ const marginChildStyle = window.getComputedStyle(marginChild);
+ const marginBottom = parseFloat(marginChildStyle["marginBottom"]);
+ const margin = lastBottom - top + marginBottom;
+ marginChild.style.marginTop = `${margin}px`;
+ }
+ const styles = window.getComputedStyle(marginChild);
+ const marginTop = parseFloat(styles["marginTop"]);
+ lastBottom = top + marginChild.getBoundingClientRect().height + marginTop;
+ }
+ }
+};
+
+window.document.addEventListener("DOMContentLoaded", function (_event) {
+ // Recompute the position of margin elements anytime the body size changes
+ if (window.ResizeObserver) {
+ const resizeObserver = new window.ResizeObserver(
+ throttle(() => {
+ layoutMarginEls();
+ if (
+ window.document.body.getBoundingClientRect().width < 990 &&
+ isReaderMode()
+ ) {
+ quartoToggleReader();
+ }
+ }, 50)
+ );
+ resizeObserver.observe(window.document.body);
+ }
+
+ const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]');
+ const sidebarEl = window.document.getElementById("quarto-sidebar");
+ const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left");
+ const marginSidebarEl = window.document.getElementById(
+ "quarto-margin-sidebar"
+ );
+ // function to determine whether the element has a previous sibling that is active
+ const prevSiblingIsActiveLink = (el) => {
+ const sibling = el.previousElementSibling;
+ if (sibling && sibling.tagName === "A") {
+ return sibling.classList.contains("active");
+ } else {
+ return false;
+ }
+ };
+
+ // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior)
+ function fireSlideEnter(e) {
+ const event = window.document.createEvent("Event");
+ event.initEvent("slideenter", true, true);
+ window.document.dispatchEvent(event);
+ }
+ const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]');
+ tabs.forEach((tab) => {
+ tab.addEventListener("shown.bs.tab", fireSlideEnter);
+ });
+
+ // fire slideEnter for tabby tab activations (for htmlwidget resize behavior)
+ document.addEventListener("tabby", fireSlideEnter, false);
+
+ // Track scrolling and mark TOC links as active
+ // get table of contents and sidebar (bail if we don't have at least one)
+ const tocLinks = tocEl
+ ? [...tocEl.querySelectorAll("a[data-scroll-target]")]
+ : [];
+ const makeActive = (link) => tocLinks[link].classList.add("active");
+ const removeActive = (link) => tocLinks[link].classList.remove("active");
+ const removeAllActive = () =>
+ [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link));
+
+ // activate the anchor for a section associated with this TOC entry
+ tocLinks.forEach((link) => {
+ link.addEventListener("click", () => {
+ if (link.href.indexOf("#") !== -1) {
+ const anchor = link.href.split("#")[1];
+ const heading = window.document.querySelector(
+ `[data-anchor-id="${anchor}"]`
+ );
+ if (heading) {
+ // Add the class
+ heading.classList.add("reveal-anchorjs-link");
+
+ // function to show the anchor
+ const handleMouseout = () => {
+ heading.classList.remove("reveal-anchorjs-link");
+ heading.removeEventListener("mouseout", handleMouseout);
+ };
+
+ // add a function to clear the anchor when the user mouses out of it
+ heading.addEventListener("mouseout", handleMouseout);
+ }
+ }
+ });
+ });
+
+ const sections = tocLinks.map((link) => {
+ const target = link.getAttribute("data-scroll-target");
+ if (target.startsWith("#")) {
+ return window.document.getElementById(decodeURI(`${target.slice(1)}`));
+ } else {
+ return window.document.querySelector(decodeURI(`${target}`));
+ }
+ });
+
+ const sectionMargin = 200;
+ let currentActive = 0;
+ // track whether we've initialized state the first time
+ let init = false;
+
+ const updateActiveLink = () => {
+ // The index from bottom to top (e.g. reversed list)
+ let sectionIndex = -1;
+ if (
+ window.innerHeight + window.pageYOffset >=
+ window.document.body.offsetHeight
+ ) {
+ // This is the no-scroll case where last section should be the active one
+ sectionIndex = 0;
+ } else {
+ // This finds the last section visible on screen that should be made active
+ sectionIndex = [...sections].reverse().findIndex((section) => {
+ if (section) {
+ return window.pageYOffset >= section.offsetTop - sectionMargin;
+ } else {
+ return false;
+ }
+ });
+ }
+ if (sectionIndex > -1) {
+ const current = sections.length - sectionIndex - 1;
+ if (current !== currentActive) {
+ removeAllActive();
+ currentActive = current;
+ makeActive(current);
+ if (init) {
+ window.dispatchEvent(sectionChanged);
+ }
+ init = true;
+ }
+ }
+ };
+
+ const inHiddenRegion = (top, bottom, hiddenRegions) => {
+ for (const region of hiddenRegions) {
+ if (top <= region.bottom && bottom >= region.top) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ const categorySelector = "header.quarto-title-block .quarto-category";
+ const activateCategories = (href) => {
+ // Find any categories
+ // Surround them with a link pointing back to:
+ // #category=Authoring
+ try {
+ const categoryEls = window.document.querySelectorAll(categorySelector);
+ for (const categoryEl of categoryEls) {
+ const categoryText = categoryEl.textContent;
+ if (categoryText) {
+ const link = `${href}#category=${encodeURIComponent(categoryText)}`;
+ const linkEl = window.document.createElement("a");
+ linkEl.setAttribute("href", link);
+ for (const child of categoryEl.childNodes) {
+ linkEl.append(child);
+ }
+ categoryEl.appendChild(linkEl);
+ }
+ }
+ } catch {
+ // Ignore errors
+ }
+ };
+ function hasTitleCategories() {
+ return window.document.querySelector(categorySelector) !== null;
+ }
+
+ function offsetRelativeUrl(url) {
+ const offset = getMeta("quarto:offset");
+ return offset ? offset + url : url;
+ }
+
+ function offsetAbsoluteUrl(url) {
+ const offset = getMeta("quarto:offset");
+ const baseUrl = new URL(offset, window.location);
+
+ const projRelativeUrl = url.replace(baseUrl, "");
+ if (projRelativeUrl.startsWith("/")) {
+ return projRelativeUrl;
+ } else {
+ return "/" + projRelativeUrl;
+ }
+ }
+
+ // read a meta tag value
+ function getMeta(metaName) {
+ const metas = window.document.getElementsByTagName("meta");
+ for (let i = 0; i < metas.length; i++) {
+ if (metas[i].getAttribute("name") === metaName) {
+ return metas[i].getAttribute("content");
+ }
+ }
+ return "";
+ }
+
+ async function findAndActivateCategories() {
+ // Categories search with listing only use path without query
+ const currentPagePath = offsetAbsoluteUrl(
+ window.location.origin + window.location.pathname
+ );
+ const response = await fetch(offsetRelativeUrl("listings.json"));
+ if (response.status == 200) {
+ return response.json().then(function (listingPaths) {
+ const listingHrefs = [];
+ for (const listingPath of listingPaths) {
+ const pathWithoutLeadingSlash = listingPath.listing.substring(1);
+ for (const item of listingPath.items) {
+ if (
+ item === currentPagePath ||
+ item === currentPagePath + "index.html"
+ ) {
+ // Resolve this path against the offset to be sure
+ // we already are using the correct path to the listing
+ // (this adjusts the listing urls to be rooted against
+ // whatever root the page is actually running against)
+ const relative = offsetRelativeUrl(pathWithoutLeadingSlash);
+ const baseUrl = window.location;
+ const resolvedPath = new URL(relative, baseUrl);
+ listingHrefs.push(resolvedPath.pathname);
+ break;
+ }
+ }
+ }
+
+ // Look up the tree for a nearby linting and use that if we find one
+ const nearestListing = findNearestParentListing(
+ offsetAbsoluteUrl(window.location.pathname),
+ listingHrefs
+ );
+ if (nearestListing) {
+ activateCategories(nearestListing);
+ } else {
+ // See if the referrer is a listing page for this item
+ const referredRelativePath = offsetAbsoluteUrl(document.referrer);
+ const referrerListing = listingHrefs.find((listingHref) => {
+ const isListingReferrer =
+ listingHref === referredRelativePath ||
+ listingHref === referredRelativePath + "index.html";
+ return isListingReferrer;
+ });
+
+ if (referrerListing) {
+ // Try to use the referrer if possible
+ activateCategories(referrerListing);
+ } else if (listingHrefs.length > 0) {
+ // Otherwise, just fall back to the first listing
+ activateCategories(listingHrefs[0]);
+ }
+ }
+ });
+ }
+ }
+ if (hasTitleCategories()) {
+ findAndActivateCategories();
+ }
+
+ const findNearestParentListing = (href, listingHrefs) => {
+ if (!href || !listingHrefs) {
+ return undefined;
+ }
+ // Look up the tree for a nearby linting and use that if we find one
+ const relativeParts = href.substring(1).split("/");
+ while (relativeParts.length > 0) {
+ const path = relativeParts.join("/");
+ for (const listingHref of listingHrefs) {
+ if (listingHref.startsWith(path)) {
+ return listingHref;
+ }
+ }
+ relativeParts.pop();
+ }
+
+ return undefined;
+ };
+
+ const manageSidebarVisiblity = (el, placeholderDescriptor) => {
+ let isVisible = true;
+ let elRect;
+
+ return (hiddenRegions) => {
+ if (el === null) {
+ return;
+ }
+
+ // Find the last element of the TOC
+ const lastChildEl = el.lastElementChild;
+
+ if (lastChildEl) {
+ // Converts the sidebar to a menu
+ const convertToMenu = () => {
+ for (const child of el.children) {
+ child.style.opacity = 0;
+ child.style.overflow = "hidden";
+ child.style.pointerEvents = "none";
+ }
+
+ nexttick(() => {
+ const toggleContainer = window.document.createElement("div");
+ toggleContainer.style.width = "100%";
+ toggleContainer.classList.add("zindex-over-content");
+ toggleContainer.classList.add("quarto-sidebar-toggle");
+ toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom
+ toggleContainer.id = placeholderDescriptor.id;
+ toggleContainer.style.position = "fixed";
+
+ const toggleIcon = window.document.createElement("i");
+ toggleIcon.classList.add("quarto-sidebar-toggle-icon");
+ toggleIcon.classList.add("bi");
+ toggleIcon.classList.add("bi-caret-down-fill");
+
+ const toggleTitle = window.document.createElement("div");
+ const titleEl = window.document.body.querySelector(
+ placeholderDescriptor.titleSelector
+ );
+ if (titleEl) {
+ toggleTitle.append(
+ titleEl.textContent || titleEl.innerText,
+ toggleIcon
+ );
+ }
+ toggleTitle.classList.add("zindex-over-content");
+ toggleTitle.classList.add("quarto-sidebar-toggle-title");
+ toggleContainer.append(toggleTitle);
+
+ const toggleContents = window.document.createElement("div");
+ toggleContents.classList = el.classList;
+ toggleContents.classList.add("zindex-over-content");
+ toggleContents.classList.add("quarto-sidebar-toggle-contents");
+ for (const child of el.children) {
+ if (child.id === "toc-title") {
+ continue;
+ }
+
+ const clone = child.cloneNode(true);
+ clone.style.opacity = 1;
+ clone.style.pointerEvents = null;
+ clone.style.display = null;
+ toggleContents.append(clone);
+ }
+ toggleContents.style.height = "0px";
+ const positionToggle = () => {
+ // position the element (top left of parent, same width as parent)
+ if (!elRect) {
+ elRect = el.getBoundingClientRect();
+ }
+ toggleContainer.style.left = `${elRect.left}px`;
+ toggleContainer.style.top = `${elRect.top}px`;
+ toggleContainer.style.width = `${elRect.width}px`;
+ };
+ positionToggle();
+
+ toggleContainer.append(toggleContents);
+ el.parentElement.prepend(toggleContainer);
+
+ // Process clicks
+ let tocShowing = false;
+ // Allow the caller to control whether this is dismissed
+ // when it is clicked (e.g. sidebar navigation supports
+ // opening and closing the nav tree, so don't dismiss on click)
+ const clickEl = placeholderDescriptor.dismissOnClick
+ ? toggleContainer
+ : toggleTitle;
+
+ const closeToggle = () => {
+ if (tocShowing) {
+ toggleContainer.classList.remove("expanded");
+ toggleContents.style.height = "0px";
+ tocShowing = false;
+ }
+ };
+
+ // Get rid of any expanded toggle if the user scrolls
+ window.document.addEventListener(
+ "scroll",
+ throttle(() => {
+ closeToggle();
+ }, 50)
+ );
+
+ // Handle positioning of the toggle
+ window.addEventListener(
+ "resize",
+ throttle(() => {
+ elRect = undefined;
+ positionToggle();
+ }, 50)
+ );
+
+ window.addEventListener("quarto-hrChanged", () => {
+ elRect = undefined;
+ });
+
+ // Process the click
+ clickEl.onclick = () => {
+ if (!tocShowing) {
+ toggleContainer.classList.add("expanded");
+ toggleContents.style.height = null;
+ tocShowing = true;
+ } else {
+ closeToggle();
+ }
+ };
+ });
+ };
+
+ // Converts a sidebar from a menu back to a sidebar
+ const convertToSidebar = () => {
+ for (const child of el.children) {
+ child.style.opacity = 1;
+ child.style.overflow = null;
+ child.style.pointerEvents = null;
+ }
+
+ const placeholderEl = window.document.getElementById(
+ placeholderDescriptor.id
+ );
+ if (placeholderEl) {
+ placeholderEl.remove();
+ }
+
+ el.classList.remove("rollup");
+ };
+
+ if (isReaderMode()) {
+ convertToMenu();
+ isVisible = false;
+ } else {
+ // Find the top and bottom o the element that is being managed
+ const elTop = el.offsetTop;
+ const elBottom =
+ elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight;
+
+ if (!isVisible) {
+ // If the element is current not visible reveal if there are
+ // no conflicts with overlay regions
+ if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) {
+ convertToSidebar();
+ isVisible = true;
+ }
+ } else {
+ // If the element is visible, hide it if it conflicts with overlay regions
+ // and insert a placeholder toggle (or if we're in reader mode)
+ if (inHiddenRegion(elTop, elBottom, hiddenRegions)) {
+ convertToMenu();
+ isVisible = false;
+ }
+ }
+ }
+ }
+ };
+ };
+
+ const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]');
+ for (const tabEl of tabEls) {
+ const id = tabEl.getAttribute("data-bs-target");
+ if (id) {
+ const columnEl = document.querySelector(
+ `${id} .column-margin, .tabset-margin-content`
+ );
+ if (columnEl)
+ tabEl.addEventListener("shown.bs.tab", function (event) {
+ const el = event.srcElement;
+ if (el) {
+ const visibleCls = `${el.id}-margin-content`;
+ // walk up until we find a parent tabset
+ let panelTabsetEl = el.parentElement;
+ while (panelTabsetEl) {
+ if (panelTabsetEl.classList.contains("panel-tabset")) {
+ break;
+ }
+ panelTabsetEl = panelTabsetEl.parentElement;
+ }
+
+ if (panelTabsetEl) {
+ const prevSib = panelTabsetEl.previousElementSibling;
+ if (
+ prevSib &&
+ prevSib.classList.contains("tabset-margin-container")
+ ) {
+ const childNodes = prevSib.querySelectorAll(
+ ".tabset-margin-content"
+ );
+ for (const childEl of childNodes) {
+ if (childEl.classList.contains(visibleCls)) {
+ childEl.classList.remove("collapse");
+ } else {
+ childEl.classList.add("collapse");
+ }
+ }
+ }
+ }
+ }
+
+ layoutMarginEls();
+ });
+ }
+ }
+
+ // Manage the visibility of the toc and the sidebar
+ const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, {
+ id: "quarto-toc-toggle",
+ titleSelector: "#toc-title",
+ dismissOnClick: true,
+ });
+ const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, {
+ id: "quarto-sidebarnav-toggle",
+ titleSelector: ".title",
+ dismissOnClick: false,
+ });
+ let tocLeftScrollVisibility;
+ if (leftTocEl) {
+ tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, {
+ id: "quarto-lefttoc-toggle",
+ titleSelector: "#toc-title",
+ dismissOnClick: true,
+ });
+ }
+
+ // Find the first element that uses formatting in special columns
+ const conflictingEls = window.document.body.querySelectorAll(
+ '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]'
+ );
+
+ // Filter all the possibly conflicting elements into ones
+ // the do conflict on the left or ride side
+ const arrConflictingEls = Array.from(conflictingEls);
+ const leftSideConflictEls = arrConflictingEls.filter((el) => {
+ if (el.tagName === "ASIDE") {
+ return false;
+ }
+ return Array.from(el.classList).find((className) => {
+ return (
+ className !== "column-body" &&
+ className.startsWith("column-") &&
+ !className.endsWith("right") &&
+ !className.endsWith("container") &&
+ className !== "column-margin"
+ );
+ });
+ });
+ const rightSideConflictEls = arrConflictingEls.filter((el) => {
+ if (el.tagName === "ASIDE") {
+ return true;
+ }
+
+ const hasMarginCaption = Array.from(el.classList).find((className) => {
+ return className == "margin-caption";
+ });
+ if (hasMarginCaption) {
+ return true;
+ }
+
+ return Array.from(el.classList).find((className) => {
+ return (
+ className !== "column-body" &&
+ !className.endsWith("container") &&
+ className.startsWith("column-") &&
+ !className.endsWith("left")
+ );
+ });
+ });
+
+ const kOverlapPaddingSize = 10;
+ function toRegions(els) {
+ return els.map((el) => {
+ const boundRect = el.getBoundingClientRect();
+ const top =
+ boundRect.top +
+ document.documentElement.scrollTop -
+ kOverlapPaddingSize;
+ return {
+ top,
+ bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize,
+ };
+ });
+ }
+
+ let hasObserved = false;
+ const visibleItemObserver = (els) => {
+ let visibleElements = [...els];
+ const intersectionObserver = new IntersectionObserver(
+ (entries, _observer) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ if (visibleElements.indexOf(entry.target) === -1) {
+ visibleElements.push(entry.target);
+ }
+ } else {
+ visibleElements = visibleElements.filter((visibleEntry) => {
+ return visibleEntry !== entry;
+ });
+ }
+ });
+
+ if (!hasObserved) {
+ hideOverlappedSidebars();
+ }
+ hasObserved = true;
+ },
+ {}
+ );
+ els.forEach((el) => {
+ intersectionObserver.observe(el);
+ });
+
+ return {
+ getVisibleEntries: () => {
+ return visibleElements;
+ },
+ };
+ };
+
+ const rightElementObserver = visibleItemObserver(rightSideConflictEls);
+ const leftElementObserver = visibleItemObserver(leftSideConflictEls);
+
+ const hideOverlappedSidebars = () => {
+ marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries()));
+ sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries()));
+ if (tocLeftScrollVisibility) {
+ tocLeftScrollVisibility(
+ toRegions(leftElementObserver.getVisibleEntries())
+ );
+ }
+ };
+
+ window.quartoToggleReader = () => {
+ // Applies a slow class (or removes it)
+ // to update the transition speed
+ const slowTransition = (slow) => {
+ const manageTransition = (id, slow) => {
+ const el = document.getElementById(id);
+ if (el) {
+ if (slow) {
+ el.classList.add("slow");
+ } else {
+ el.classList.remove("slow");
+ }
+ }
+ };
+
+ manageTransition("TOC", slow);
+ manageTransition("quarto-sidebar", slow);
+ };
+ const readerMode = !isReaderMode();
+ setReaderModeValue(readerMode);
+
+ // If we're entering reader mode, slow the transition
+ if (readerMode) {
+ slowTransition(readerMode);
+ }
+ highlightReaderToggle(readerMode);
+ hideOverlappedSidebars();
+
+ // If we're exiting reader mode, restore the non-slow transition
+ if (!readerMode) {
+ slowTransition(!readerMode);
+ }
+ };
+
+ const highlightReaderToggle = (readerMode) => {
+ const els = document.querySelectorAll(".quarto-reader-toggle");
+ if (els) {
+ els.forEach((el) => {
+ if (readerMode) {
+ el.classList.add("reader");
+ } else {
+ el.classList.remove("reader");
+ }
+ });
+ }
+ };
+
+ const setReaderModeValue = (val) => {
+ if (window.location.protocol !== "file:") {
+ window.localStorage.setItem("quarto-reader-mode", val);
+ } else {
+ localReaderMode = val;
+ }
+ };
+
+ const isReaderMode = () => {
+ if (window.location.protocol !== "file:") {
+ return window.localStorage.getItem("quarto-reader-mode") === "true";
+ } else {
+ return localReaderMode;
+ }
+ };
+ let localReaderMode = null;
+
+ const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded");
+ const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1;
+
+ // Walk the TOC and collapse/expand nodes
+ // Nodes are expanded if:
+ // - they are top level
+ // - they have children that are 'active' links
+ // - they are directly below an link that is 'active'
+ const walk = (el, depth) => {
+ // Tick depth when we enter a UL
+ if (el.tagName === "UL") {
+ depth = depth + 1;
+ }
+
+ // It this is active link
+ let isActiveNode = false;
+ if (el.tagName === "A" && el.classList.contains("active")) {
+ isActiveNode = true;
+ }
+
+ // See if there is an active child to this element
+ let hasActiveChild = false;
+ for (child of el.children) {
+ hasActiveChild = walk(child, depth) || hasActiveChild;
+ }
+
+ // Process the collapse state if this is an UL
+ if (el.tagName === "UL") {
+ if (tocOpenDepth === -1 && depth > 1) {
+ // toc-expand: false
+ el.classList.add("collapse");
+ } else if (
+ depth <= tocOpenDepth ||
+ hasActiveChild ||
+ prevSiblingIsActiveLink(el)
+ ) {
+ el.classList.remove("collapse");
+ } else {
+ el.classList.add("collapse");
+ }
+
+ // untick depth when we leave a UL
+ depth = depth - 1;
+ }
+ return hasActiveChild || isActiveNode;
+ };
+
+ // walk the TOC and expand / collapse any items that should be shown
+ if (tocEl) {
+ updateActiveLink();
+ walk(tocEl, 0);
+ }
+
+ // Throttle the scroll event and walk peridiocally
+ window.document.addEventListener(
+ "scroll",
+ throttle(() => {
+ if (tocEl) {
+ updateActiveLink();
+ walk(tocEl, 0);
+ }
+ if (!isReaderMode()) {
+ hideOverlappedSidebars();
+ }
+ }, 5)
+ );
+ window.addEventListener(
+ "resize",
+ throttle(() => {
+ if (tocEl) {
+ updateActiveLink();
+ walk(tocEl, 0);
+ }
+ if (!isReaderMode()) {
+ hideOverlappedSidebars();
+ }
+ }, 10)
+ );
+ hideOverlappedSidebars();
+ highlightReaderToggle(isReaderMode());
+});
+
+// grouped tabsets
+window.addEventListener("pageshow", (_event) => {
+ function getTabSettings() {
+ const data = localStorage.getItem("quarto-persistent-tabsets-data");
+ if (!data) {
+ localStorage.setItem("quarto-persistent-tabsets-data", "{}");
+ return {};
+ }
+ if (data) {
+ return JSON.parse(data);
+ }
+ }
+
+ function setTabSettings(data) {
+ localStorage.setItem(
+ "quarto-persistent-tabsets-data",
+ JSON.stringify(data)
+ );
+ }
+
+ function setTabState(groupName, groupValue) {
+ const data = getTabSettings();
+ data[groupName] = groupValue;
+ setTabSettings(data);
+ }
+
+ function toggleTab(tab, active) {
+ const tabPanelId = tab.getAttribute("aria-controls");
+ const tabPanel = document.getElementById(tabPanelId);
+ if (active) {
+ tab.classList.add("active");
+ tabPanel.classList.add("active");
+ } else {
+ tab.classList.remove("active");
+ tabPanel.classList.remove("active");
+ }
+ }
+
+ function toggleAll(selectedGroup, selectorsToSync) {
+ for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) {
+ const active = selectedGroup === thisGroup;
+ for (const tab of tabs) {
+ toggleTab(tab, active);
+ }
+ }
+ }
+
+ function findSelectorsToSyncByLanguage() {
+ const result = {};
+ const tabs = Array.from(
+ document.querySelectorAll(`div[data-group] a[id^='tabset-']`)
+ );
+ for (const item of tabs) {
+ const div = item.parentElement.parentElement.parentElement;
+ const group = div.getAttribute("data-group");
+ if (!result[group]) {
+ result[group] = {};
+ }
+ const selectorsToSync = result[group];
+ const value = item.innerHTML;
+ if (!selectorsToSync[value]) {
+ selectorsToSync[value] = [];
+ }
+ selectorsToSync[value].push(item);
+ }
+ return result;
+ }
+
+ function setupSelectorSync() {
+ const selectorsToSync = findSelectorsToSyncByLanguage();
+ Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => {
+ Object.entries(tabSetsByValue).forEach(([value, items]) => {
+ items.forEach((item) => {
+ item.addEventListener("click", (_event) => {
+ setTabState(group, value);
+ toggleAll(value, selectorsToSync[group]);
+ });
+ });
+ });
+ });
+ return selectorsToSync;
+ }
+
+ const selectorsToSync = setupSelectorSync();
+ for (const [group, selectedName] of Object.entries(getTabSettings())) {
+ const selectors = selectorsToSync[group];
+ // it's possible that stale state gives us empty selections, so we explicitly check here.
+ if (selectors) {
+ toggleAll(selectedName, selectors);
+ }
+ }
+});
+
+function throttle(func, wait) {
+ let waiting = false;
+ return function () {
+ if (!waiting) {
+ func.apply(this, arguments);
+ waiting = true;
+ setTimeout(function () {
+ waiting = false;
+ }, wait);
+ }
+ };
+}
+
+function nexttick(func) {
+ return setTimeout(func, 0);
+}
diff --git a/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/tippy.css b/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/tippy.css
new file mode 100644
index 0000000..e6ae635
--- /dev/null
+++ b/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/tippy.css
@@ -0,0 +1 @@
+.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}
\ No newline at end of file
diff --git a/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/tippy.umd.min.js b/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/tippy.umd.min.js
new file mode 100644
index 0000000..ca292be
--- /dev/null
+++ b/public/notebooks/isic-2024_kaggle_files/libs/quarto-html/tippy.umd.min.js
@@ -0,0 +1,2 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F}));
+
diff --git a/public/notebooks/music-recommendation.html b/public/notebooks/music-recommendation.html
new file mode 100644
index 0000000..a52e6c5
--- /dev/null
+++ b/public/notebooks/music-recommendation.html
@@ -0,0 +1,2560 @@
+
+
+
+
+
+
+
+
+
+music-recommendation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Initialization
+
+ Importing Libraries and datasets
+
+
# Mounting the drive
+from google.colab import drive
+drive.mount('/content/drive')
+
+
+
# Used to ignore the warning given as output of the code
+import warnings
+warnings.filterwarnings('ignore')
+
+# Basic libraries of python for numeric and dataframe computations
+import numpy as np
+import pandas as pd
+
+# Import Matplotlib the Basic library for data visualization
+import matplotlib.pyplot as plt
+
+# Import seaborn - Slightly advanced library for data visualization
+import seaborn as sns
+
+# Import the required library to compute the cosine similarity between two vectors
+from sklearn.metrics.pairwise import cosine_similarity
+
+# Import defaultdict from collections A dictionary output that does not raise a key error
+from collections import defaultdict
+
+# Impoort mean_squared_error : a performance metrics in sklearn
+from sklearn.metrics import mean_squared_error
+
+
+
# Importing the datasets
+song_df = pd.read_csv('/content/drive/MyDrive/capstone/song_data.csv')
+count_df = pd.read_csv('/content/drive/MyDrive/capstone/count_data.csv',
+ usecols=['user_id','song_id','play_count'])
+
+
+
+ Understanding the data by viewing a few observations
+
+
# Display first 10 records of count_df data
+count_df.head(10)
+
+
+
# Display first 10 records of song_df data
+song_df.head(10)
+
+
+
+ Let us check the data types and and missing values of each column
+
+
# Display info of count_df
+count_df.info(show_counts=True)
+
+
+
# Display info of song_df
+song_df.info()
+
+
+ The count data contains:
+
+- 2000000 observations and 3 columns.
+- user_id and sond_id columns are of object data type but play_count column is of numeric data type.
+- There is no missing value.
+
+
+
+ The song data contains:
+
+- 1000000 observations and 5 columns.
+- All the columns are of object data type except the year column. The year column is of numeric data type.
+- The title and release columns contains 1000000 - 999983 = 17 null values. All the other columns do not contain null values.
+
+
+
# Left merge count_df and song_df on "song_id". Drop duplicates from song_df data simultaneously
+df = pd.merge(count_df, song_df.drop_duplicates('song_id'), how='left')
+
+# Drop the column 'Unnamed: 0'
+ # Already droped while loading the dataset
+
+## Name the obtained dataframe as "df"
+ # Already named df
+
+Think About It: As the user_id and song_id are encrypted. Can they be encoded to numeric features? - Yes, in this case encoding categorical feature increase the execution performance of the models although it does not impact the qualiy of the predictions
+
+
# Apply label encoding for "user_id" and "song_id"
+from sklearn.preprocessing import LabelEncoder
+
+#saving the original df in df_copy
+df_copy = df.copy()
+
+#storing transformers in a dictionnary for decoding if needed
+label_object = {}
+categorical_columns = ['user_id','song_id']
+for col in categorical_columns:
+ labelencoder = LabelEncoder()
+ labelencoder.fit(df[col])
+ df[col] = labelencoder.fit_transform(df[col])
+ label_object[col] = labelencoder
+
+Think About It: As the data also contains users who have listened to very few songs and vice versa, is it required to filter the data so that it contains users who have listened to a good count of songs and vice versa? - Yes, filtering the data will indeed reduce the sparcity in the matrices used for modelling. Hence increasing the performance of the computation as well as the quality of the recommendations
+A dataset of size 2000000 rows x 7 columns can be quite large and may require a lot of computing resources to process. This can lead to long processing times and can make it difficult to train and evaluate your model efficiently. In order to address this issue, it may be necessary to trim down your dataset to a more manageable size.
+
+
# Get the column containing the users
+users = df.user_id
+
+# Create a dictionary that maps users(listeners) to the number of songs that they have listened to
+playing_count = dict()
+
+for user in users:
+ # If we already have the user, just add 1 to their playing count
+ if user in playing_count:
+ playing_count[user] += 1
+
+ # Otherwise, set their playing count to 1
+ else:
+ playing_count[user] = 1
+
+
+
# We want our users to have listened at least 90 songs
+SONG_COUNT_CUTOFF = 90
+
+# Create a list of users who need to be removed
+remove_users = []
+
+for user, num_songs in playing_count.items():
+
+ if num_songs < SONG_COUNT_CUTOFF:
+ remove_users.append(user)
+
+df = df.loc[ ~ df.user_id.isin(remove_users)]
+
+
+
# Get the column containing the songs
+songs = df.song_id
+
+# Create a dictionary that maps songs to its number of users(listeners)
+playing_count = dict()
+
+for song in songs:
+ # If we already have the song, just add 1 to their playing count
+ if song in playing_count:
+ playing_count[song] += 1
+
+ # Otherwise, set their playing count to 1
+ else:
+ playing_count[song] = 1
+
+
+
# We want our song to be listened by at least 120 users to be considred
+LISTENER_COUNT_CUTOFF = 120
+
+remove_songs = []
+
+for song, num_users in playing_count.items():
+ if num_users < LISTENER_COUNT_CUTOFF:
+ remove_songs.append(song)
+
+df_final = df.loc[ ~ df.song_id.isin(remove_songs)]
+
+Out of all the songs available, songs with play_count less than or equal to 5 are in almost 90% abundance. So for building the recommendation system let us consider only those songs.
+
+
# Keep only records of songs with play_count less than or equal to (<=) 5
+df_final = df_final[df_final.play_count<=5]
+
+
+
# Check the shape of the data
+df_final.shape
+
+
+
+
+
+ Exploratory Data Analysis
+
+ Let’s check the total number of unique users, songs, artists in the data
+Total number of unique user id
+
+
# Display total number of unique user_id
+df_final.user_id.nunique()
+
+Total number of unique song id
+
+
# Display total number of unique song_id
+df_final.song_id.nunique()
+
+Total number of unique artists
+
+
# Display total number of unique artists
+df_final.artist_name.nunique()
+
+
+ The data contains :
+
+- 117876 observations
+- 3155 unique users
+- 563 unique songs
+
+
+
+ As per the number of unique users and songs, there is a possibility of 3155 * 563 = 1.776.265 interactions in the dataset. But we only have 117.876 interactions meeting our criterias(90 songs count per user, 120 users count per song and at most 5 play counts per song), i.e., not every user has listened every song in the dataset, which is quite understandable.This creates the possibility of building a recommendation system to recommend products to the users which they have not interacted with.
+
+
+
+ Let’s find out about the most interacted songs and interacted users
+Most interacted songs
+
+
# Printing the top ten songs
+df_final.song_id.value_counts()[:10]
+
+Most interacted users
+
+
# Printing the top ten users
+df_final.user_id.value_counts()[:10]
+
+
+ The top user and song are the user with id 2548 and the song with id 8581
+
+- The top user listened to 243 songs
+
+- Since there are 563 songs, it is possible to make recommations to this particular user and to all users in general. For this top user, there are 563 - 243 = 320 possible recommendations left.
+
+- The most popular song was listened to by 751 users
+
+- Since there are 3155 users, there are 3155 - 751 = 2404 possible users left to whom we could recommend this song.
+
+
+Songs released on yearly basis
+
+
# Find out the number of songs released in a year, use the songs_df
+ # Hint: Use groupby function on the 'year' column
+song_df.drop_duplicates('song_id').groupby('year').song_id.count().sort_values(ascending=False)[:10]
+
+
+
# Create a barplot plot with y label as "number of titles played" and x -axis year
+nbr_titles_played_by_year = df_final.groupby('year').title.nunique()
+
+plt.style.use('ggplot')
+
+# Set the figure size
+plt.figure(figsize=(7, 5))
+#from the previous cell, we could see there is an abnormal year 0, which seems to be
+#a default, so we're dropping that year
+plt.bar(x=nbr_titles_played_by_year.index[1:], height=nbr_titles_played_by_year[1:])
+
+# Set the x label of the plot
+plt.xlabel('year')
+
+# Set the y label of the plot
+plt.ylabel('number of titles played')
+
+# Setting all the ticks
+plt.xticks(ticks=nbr_titles_played_by_year.index[1:], rotation=90, size=7)
+
+# Show the plot
+plt.show()
+
+
+
+ The number of titles played can be segmented into 3 groups. The groups are created below by dividing the year colonne into 3 segments:
+
+- [1969 - 1983] : in this segment, the number of titles played is very low, less or equal to 2.
+- [1986 - 1997] : in this segment, the number of titles played is less or equal to 7, 1991 having 7.
+- [1999 - 2010] : in this segment, the number of titles played vary beetween 8 and 79, 1999 having the lowest and 2007 having the maximum.
+
+Think About It: What other insights can be drawn using exploratory data analysis? - We can also visualize the distribution of the play counts:
+
+
data = df_final
+sns.countplot(data=data, x=data.play_count, hue='play_count', palette='dark',
+ legend=False, stat='proportion')
+
+From the above figure, we can see: - most of the songs, 60 percent have been played only once and - 80 percent of the songs have been played less than 3 times.
+Now that we have explored the data, let’s apply different algorithms to build recommendation systems.
+Note: Use the shorter version of the data, i.e., the data after the cutoffs as used in Milestone 1.
+
+
+
+
+ Building various models
+
+ Popularity-Based Recommendation Systems
+Let’s take the count and sum of play counts of the songs and build the popularity recommendation systems based on the sum of play counts.
+
+
# Calculating average play_count
+ # Hint: Use groupby function on the song_id column
+play_avg = df_final.groupby('song_id').play_count.mean()
+
+# Calculating the frequency a song is played
+ # Hint: Use groupby function on the song_id column
+play_freq = 100 * df_final.groupby('song_id').play_count.sum()
+
+
+
# Making a dataframe with the average_count and play_freq
+df_songs_count_freq = pd.DataFrame({'play_avg':play_avg, 'play_freq':play_freq})
+
+# Let us see the first five records of the final_play dataset
+df_songs_count_freq[:5]
+
+Now, let’s create a function to find the top n songs for a recommendation based on the average play count of song. We can also add a threshold for a minimum number of playcounts for a song to be considered for recommendation.
+
+
# Build the function to find top n songs
+def top_n_songs(df, n, min_playcount = 50):
+ """
+ It gives top n songs among those being played for more than min_playcount
+ """
+ # Finding products with minimum number of interactions
+ recommendations = df[df.play_freq > min_playcount]
+
+ # Sorting values with respect to average rating
+ recommendations = recommendations.sort_values(by='play_avg', ascending=False)
+
+ return list(recommendations.index[:n])
+
+
+
# Recommend top 10 songs using the function defined above
+top_n_songs(df_songs_count_freq, 10)
+
+
+
+ User User Similarity-Based Collaborative Filtering
+To build the user-user-similarity-based and subsequent models we will use the “surprise” library.
+
+
# Install the surprise package using pip. Uncomment and run the below code to do the same
+!pip install surprise
+
+
+
# Import necessary libraries
+from surprise import similarities
+from sklearn.metrics.pairwise import cosine_similarity
+
+# To compute the accuracy of models
+from surprise import accuracy
+
+# This class is used to parse a file containing play_counts, data should be in structure - user; item; play_count
+from surprise.reader import Reader
+
+
+# Class for loading datasets
+from surprise.dataset import Dataset
+
+
+# For tuning model hyperparameters
+from surprise.model_selection import GridSearchCV
+
+
+# For splitting the data in train and test dataset
+from surprise.model_selection import train_test_split
+
+
+# For implementing similarity-based recommendation system
+from surprise.prediction_algorithms.knns import KNNBasic
+
+
+# For implementing matrix factorization based recommendation system
+from surprise.prediction_algorithms.matrix_factorization import SVD
+
+
+# For implementing KFold cross-validation
+from surprise.model_selection import KFold
+
+
+# For implementing clustering-based recommendation system
+from surprise import CoClustering
+
+
+
+ Some useful functions
+Below is the function to calculate precision@k and recall@k, RMSE, and F1_Score@k to evaluate the model performance.
+Think About It: Which metric should be used for this problem to compare different models?
+
+
def precision_recall_at_k(model, k=30, threshold=1.5, testset=None):
+ """Return precision and recall at k metrics for each user"""
+
+ # First map the predictions to each user.
+ user_est_true = defaultdict(list)
+
+ #Making predictions on the test data
+ predictions = model.test(testset)
+
+ for uid, _, true_r, est, _ in predictions:
+ user_est_true[uid].append((est, true_r))
+
+ precisions = dict()
+ recalls = dict()
+
+ for uid, playing_count in user_est_true.items():
+
+ # Sort play count by estimated value
+ playing_count.sort(key=lambda x: x[0], reverse=True)
+
+ # Number of relevant items
+ n_rel = sum((true_r >= threshold) for (_, true_r) in playing_count)
+
+ # Number of recommended items in top k
+ n_rec_k = sum((est >= threshold) for (est, _) in playing_count[:k])
+
+ # Number of relevant and recommended items in top k
+ n_rel_and_rec_k = sum(((true_r >= threshold) and (est >= threshold))
+ for (est, true_r) in playing_count[:k])
+
+ # Precision@K: Proportion of recommended items that are relevant
+ # When n_rec_k is 0, Precision is undefined. We here set Precision to 0 when n_rec_k is 0.
+
+ precisions[uid] = n_rel_and_rec_k / n_rec_k if n_rec_k != 0 else 0
+
+ # Recall@K: Proportion of relevant items that are recommended
+ # When n_rel is 0, Recall is undefined. We here set Recall to 0 when n_rel is 0.
+
+ recalls[uid] = n_rel_and_rec_k / n_rel if n_rel != 0 else 0
+
+ #Mean of all the predicted precisions are calculated.
+ precision = round((sum(prec for prec in precisions.values()) / len(precisions)),3)
+ #Mean of all the predicted recalls are calculated.
+ recall = round((sum(rec for rec in recalls.values()) / len(recalls)),3)
+
+ accuracy.rmse(predictions)
+ print('Precision: ', precision) #Command to print the overall precision
+ print('Recall: ', recall) #Command to print the overall recall
+ print('F_1 score: ', round((2*precision*recall)/(precision+recall),3)) # Formula to compute the F-1 score.
+
+Think About It: In the function precision_recall_at_k above the threshold value used is 1.5. How precision and recall are affected by changing the threshold? What is the intuition behind using the threshold value of 1.5? - Impact of the threshold on recall and precision - If we decrease the threshold, most of the predictions would be above the threshold, hence many of the items would be recommended. As the data is imbalanced (many low playcounts), The TP rate would increase and Precision and Recall would increase.
+- If we increase the threshold, most of the prediction would be below the threshold, hence few songs would be recommended. As the data is imbalanced (very few high playcounts), the TP rate would decrease. So Precision and Recall would decrease. - So a good threshold is a medium threshold but in the present case most of the predictions are around 1, so 1.5 is a good threshold. Moreover 1 playcount is not enough to say a user like a song. The higher the the threshold, the higher the likely hood the user would love the song.
+
+Evaluation parameters
+To compute precision and recall, a threshold of 1.5 and k value of 30 will be considered for the recommended and relevant ratings.
+Performance metrice selection
+FP and FN costs are high in the present case. If the FP rate is high (precision is low), we recommend products that the user might not buy. And, if FN rate is high (recall is low) we fail to recommend relevant products that the user might purchase. Therefore, precision and recall need to be optimized. So the correct performance measure is the F_1 score.
+
+Below we are loading the dataset, which is a pandas dataframe, into a different format called surprise.dataset.DatasetAutoFolds which is required by this library. To do this we will be using the classes Reader and Dataset
+You will also notice here that we read the dataset by providing a scale of ratings. However, as you would know, we do not have ratings data of the songs. In this case, we are going to use play_count as a proxy for ratings with the assumption that the more the user listens to a song, the higher the chance that they like the song
+
+
# Instantiating Reader scale with expected rating scale
+ #use rating scale (0, 5)
+reader = Reader(rating_scale=(0, 5))
+
+# Loading the dataset
+ # Take only "user_id","song_id", and "play_count"
+data = Dataset.load_from_df(df_final[['user_id', 'song_id', 'play_count']],
+ reader=reader)
+
+# Splitting the data into train and test dataset
+ # Take test_size = 0.4, random_state = 42
+trainset, testset = train_test_split(data, test_size=0.4, random_state=42)
+
+Think About It: How changing the test size would change the results and outputs?
+As the data is imbalanced : - If we increase the test size, the performance of the model increase, as more diversity will appear in the testset - And conversely, if we decrease, the test size, the performance decrease.
+
+
# Build the default user-user-similarity model
+sim_options=dict(name='cosine', user_based=True)
+
+# KNN algorithm is used to find desired similar items
+ # Use random_state = 1
+sim_user_user_model = KNNBasic(sim_options=sim_options, random_state=1,
+ verbose=True)
+
+# Train the algorithm on the trainset, and predict play_count for the testset
+sim_user_user_model.fit(trainset)
+
+# Let us compute precision@k, recall@k, and f_1 score with k = 30
+ # Use sim_user_user model
+precision_recall_at_k(sim_user_user_model, testset=testset)
+
+The F_1 score is not good, this is likely due to the imbalanced data. We need more observations.
+
+
# selecting a sample user with a listened song, let's choose the first observation
+user_id_interaction, song_id_interaction, play_count_interaction = df_final[['user_id'
+, 'song_id', 'play_count']].iloc[0].values
+print('user_id chosen is:', user_id_interaction,
+ '\nsong_id chosen listened by the user is:', song_id_interaction,
+ '\nand the play count of this pair is:', play_count_interaction)
+
+
+
# Predicting play_count for a sample user with a listened song
+# Use any user id and song_id
+sim_user_user_model.predict(user_id_interaction, song_id_interaction, r_ui=1)
+
+
+
# Function to find the list of users who have not listened to the song song_id.
+def n_users_not_listened_song(n, data, song_id):
+ users_listened_song = set(data[data['song_id'] == song_id]['user_id'])
+ all_users = set(data['user_id'])
+ # n is the number of elements to get in the list
+ return list(all_users.difference(users_listened_song))[:n]
+
+
+
# Randomly choosing a song_id
+song_id_no_interaction = df_final.song_id.values[10]
+
+# Finding a user who has not listened to the song
+user_id_no_interaction = n_users_not_listened_song(1, df_final,
+ song_id_no_interaction)[0]
+
+print('user_id chosen is:', user_id_no_interaction,
+ '\nsong_id chosen and not listened by the user is:', song_id_no_interaction)
+
+
+
# Predicting play_count for a sample user with a song not-listened by the user
+ #predict play_count for any sample user
+sim_user_user_model.predict(user_id_no_interaction, song_id_no_interaction)
+
+The predicted play count for the interacted user-song pair is 1.2. The actual play count is 1, so the prediction is pretty good.
+The predicted play count the for the user-song pair with no interaciton is 1.74
+Now, let’s try to tune the model and see if we can improve the model performance.
+
+
# Setting up parameter grid to tune the hyperparameters
+params_grid = dict(k=[60, 80], min_k=[40, 50],
+ sim_options=dict(name=['msd', 'cosine'],
+ #min_support = [5, 10, 20],
+ user_based=[True]))
+
+# Performing 3-fold cross-validation to tune the hyperparameters
+gs = GridSearchCV(KNNBasic, param_grid=params_grid, cv=3, measures=['rmse'],
+ n_jobs=-1)
+
+# Fitting the data
+ # Use entire data for GridSearch
+gs.fit(data)
+
+# Best RMSE score
+print("Best RMSE score:", gs.best_score['rmse'])
+
+# Combination of parameters that gave the best RMSE score
+print("Best_params:", gs.best_params['rmse'])
+
+
+
# Train the best model found in above gridsearch
+# Using the optimal similarity measure for user-user based collaborative filtering
+best_params = gs.best_params['rmse']
+best_k = best_params['k']
+best_min_k = best_params['min_k']
+best_sim_options = best_params['sim_options']
+
+# Creating an instance of KNNBasic with optimal hyperparameter values
+sim_user_user_tuned_model = KNNBasic(k = best_k, min_k=best_min_k,
+ sim_options=best_sim_options,
+ random_state=1, verbose=False)
+
+# Training the algorithm on the trainset
+sim_user_user_tuned_model.fit(trainset)
+
+# Let us compute precision@k and recall@k also
+precision_recall_at_k(sim_user_user_tuned_model, testset=testset)
+
+The F1 score of this tuned model is 0.513. So it has silightly increased compared to the baseline model which was 0.504.
+
+
# Predict the play count for a user who has listened to the song. Take user_id 6958, song_id 1671 and r_ui = 2
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+sim_user_user_tuned_model.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+
+
+
# Function to find the list of songs who have not been listened by user_id.
+def n_songs_not_listened_user(n, data, user_id):
+ songs_listened_user = set(data[data['user_id'] == user_id]['song_id'])
+ all_songs = set(data['song_id'])
+ # n is the number of elements to get in the list
+ return list(all_songs.difference(songs_listened_user))[:n]
+
+
+
user_id_no_interaction = 6958
+
+# Finding a song who has not been listened by the user above
+song_id_no_interaction = n_songs_not_listened_user(1, df_final, user_id_no_interaction)[0]
+
+print('user_id chosen is:', user_id_no_interaction,
+ '\nsong_id chosen and not listened by the user is:', song_id_no_interaction)
+
+
+
# Verifying that the user has not listened to the song
+df_final.query('user_id == @user_id_no_interaction & song_id == @song_id_no_interaction')
+
+
+
# Predict the play count for a song that is not listened to by the user (with user_id 6958)
+sim_user_user_tuned_model.predict(user_id_no_interaction, song_id_no_interaction)
+
+The predicted play count for the interacted user-song pair is 1.49. The actual play count is 2, so the prediction is far from the actual.
+The predicted play count the for the user-song pair with no interaciton is 1.53
+Think About It: Along with making predictions on listened and unknown songs can we get 5 nearest neighbors (most similar) to a certain song? - Yes, we can get the list of the songs similar to a song
+
+
# Use inner id 0
+sim_user_user_tuned_model.get_neighbors(iid=0, k=5)
+
+Below we will be implementing a function where the input parameters are:
+
+- data: A song dataset
+- user_id: A user-id against which we want the recommendations
+- top_n: The number of songs we want to recommend
+- algo: The algorithm we want to use for predicting the play_count
+- The output of the function is a set of top_n items recommended for the given user_id based on the given algorithm
+
+
+
def get_recommendations(data, user_id, top_n, algo):
+
+ # Creating an empty list to store the recommended song ids
+ recommendations = []
+
+ # Creating an user item interactions matrix
+ user_item_interactions_matrix = data.pivot(index = 'user_id',
+ columns = 'song_id',
+ values = 'play_count')
+
+ user_songs = user_item_interactions_matrix.loc[user_id]
+ # Extracting those song ids which the user_id has not played yet
+ non_played_songs = user_songs[user_songs.isnull()].index.tolist()
+
+ # Looping through each of the song ids which user_id has not interacted yet
+ for song_id in non_played_songs:
+
+ # Predicting the users for those non played song ids by this user
+ est = algo.predict(user_id, song_id).est
+
+ # Appending the predicted play_counts
+ recommendations.append((song_id, est))
+
+ # Sorting the predicted play_counts in descending order
+ recommendations.sort(key = lambda x: x[1], reverse = True)
+
+ # Returing top n highest predicted play_count songs for this user
+ return recommendations[:top_n]
+
+
+
# Make top 5 recommendations for any user_id with a similarity-based recommendation engine
+recommendations = get_recommendations(df_final, user_id_interaction, 5,
+ sim_user_user_tuned_model)
+
+
+
# Building the dataframe for above recommendations with columns "song_id" and "predicted_play_count"
+recommendations_df = pd.DataFrame(recommendations, columns=['song_id', 'predicted_play_count'])
+recommendations_df
+
+Above we can see the list of the songs recommended by the user-user colaborative filtering model. - the top recommendation is the song 7224 with 2.93 play_counts. - the fith recommendation is the song 4831 with 2.34 playcounts. - all the recommended songs have a predicted playcounts greater than 2.
+
+
+ Correcting the play_counts and Ranking the above songs
+
+
def ranking_songs(recommendations, playing_count):
+ # Sort the songs based on play counts
+ ranked_songs = playing_count.loc[[items[0] for items in recommendations]].sort_values('play_freq', ascending = False)[['play_freq']].reset_index()
+
+ # Merge with the recommended songs to get predicted play_counts
+ ranked_songs = ranked_songs.merge(pd.DataFrame(recommendations, columns = ['song_id', 'predicted_play_count']), on = 'song_id', how = 'inner')
+
+ # Rank the songs based on corrected play_counts
+ ranked_songs['corrected_play_count'] = ranked_songs['predicted_play_count'] - 1 / np.sqrt(ranked_songs['play_freq'])
+
+ # Sort the songs based on corrected play_counts
+ ranked_songs = ranked_songs.sort_values('corrected_play_count', ascending = False)
+
+ return ranked_songs
+
+Think About It: In the above function to correct the predicted play_count a quantity 1/np.sqrt(n) is subtracted. What is the intuition behind it? Is it also possible to add this quantity instead of subtracting? - we can correct the predictions of the songs by using the total play_count of the song. The intuition behind is the higher the playcount the higher the likely hood the song will be liked. So we can add the quantity 1/np.sqrt(n) to have optimistic predictions or in contrario we can substract that quantity to have play count below 5. - So the confidence interval of the predictions could be seen as : predicted_prediction - 1/np.sqrt(n), predicted_prediction + 1/np.sqrt(n)
+
+
# Applying the ranking_songs function on the final_play data
+ranking_songs(recommendations, df_songs_count_freq)
+
+As expected, the corrected play counts are slightly lower than the predicted ones
+
+
+ Item Item Similarity-based collaborative filtering recommendation systems
+
+
# Apply the item-item similarity collaborative filtering model with random_state = 1 and evaluate the model performance
+# Declaring the similarity options
+similarity_options = dict(name='cosine', user_based=False)
+
+# KNN algorithm is used to find desired similar items. Use random_state=1
+sim_item_item_model = KNNBasic(sim_options=similarity_options,
+ random_state=1, verbose=False)
+
+# Train the algorithm on the trainset, and predict ratings for the test set
+sim_item_item_model.fit(trainset)
+
+# Let us compute precision@k, recall@k, and f_1 score
+precision_recall_at_k(sim_item_item_model, testset=testset)
+
+The F1 score of this basic item-item colaborative filtering model is 0.39 - This is not a good score and is less than the F1 score of this basic user-user colaborative filtering model - So for this case, considering similar user seems to be better than considering similar items when building the model.
+
+
# Predicting play count for a sample user_id 6958 and song (with song_id 1671) listened to by the user
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+sim_item_item_model.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+
+
+
song_id_no_interaction = 1671
+
+# Finding a user who has not listened to the song
+user_id_no_interaction = n_users_not_listened_song(1, df_final,
+ song_id_no_interaction)[0]
+
+print('user_id chosen is:', user_id_no_interaction,
+ '\nsong_id chosen and not listened by the user is:', song_id_no_interaction)
+
+
+
# Predict the play count for a user that has not listened to the song (with song_id 1671)
+sim_item_item_model.predict(user_id_no_interaction, song_id_no_interaction)
+
+The predicted play count for the interacted user-song pair is 1.36. The actual play count is 2, so the prediction is far from the actual. The number of neighbors available was 20.
+The predicted play counts the for the user-song pair with no interaciton is 1.64.
+
+
# Apply grid search for enhancing model performance
+# Setting up parameter grid to tune the hyperparameters
+params_grid_item = dict(k=[30, 50], min_k=[3, 6, 9],
+ sim_options=dict(name=['cosine', 'msd'],
+ min_support = [9, 20],
+ user_based=[False]))
+
+# Performing 3-fold cross-validation to tune the hyperparameters
+gs = GridSearchCV(KNNBasic, param_grid=params_grid_item, cv=3,
+ measures=['rmse'], n_jobs=-1)
+
+# Fitting the data
+gs.fit(data)
+
+# Find the best RMSE score
+print("Best RMSE score:", gs.best_score['rmse'])
+
+# Extract the combination of parameters that gave the best RMSE score
+print("best_params:", gs.best_params['rmse'])
+
+Think About It: How do the parameters affect the performance of the model? Can we improve the performance of the model further? Check the list of hyperparameters here. - Choosing the right set of parameters is critical to some algorithms. - In this case, the similarity options and the number of neighbors enhance the performance of the models
+
+
# Apply the best model found in the grid search
+# Using the optimal similarity measure for item-item based collaborative filtering
+best_params_item = gs.best_params['rmse']
+best_k_item = best_params_item['k']
+best_min_k_item = best_params_item['min_k']
+best_sim_options_item = best_params_item['sim_options']
+
+# Creating an instance of KNNBasic with optimal hyperparameter values
+sim_item_item_tuned_model = KNNBasic(k = best_k_item, min_k=best_min_k_item,
+ sim_options=best_sim_options_item, randm_state=1,
+ verbose=False)
+
+# Training the algorithm on the trainset
+sim_item_item_tuned_model.fit(trainset)
+
+# Let us compute precision@k and recall@k, f1_score and RMSE
+precision_recall_at_k(sim_item_item_tuned_model, testset=testset)
+
+The performance has increased compared to the baseline item-item model: - The F1 score of this tuned model is 0.49 while the the F1 score of the baseline item-item model was 0.39. So the perfomance has increased by 0.1 points.
+
+
# Predict the play_count by a user(user_id 6958) for the song (song_id 1671)
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+sim_item_item_tuned_model.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+
+
+
# Predicting play count for a sample user_id 6958 with song_id 3232 which is not listened to by the user
+user_id_no_interaction, song_id_no_interaction = (6958, 3232)
+sim_item_item_tuned_model.predict(user_id_no_interaction, song_id_no_interaction)
+
+The predicted play count for the interacted user-song pair is 1.45. The actual play count is 2, so the prediction is far from the actual. The number of neighbors available was 15.
+The predicted play count the for the user-song pair with no interaciton is 1.69.
+
+
# Find five most similar items to the item with inner id 0
+sim_item_item_tuned_model.get_neighbors(iid=0, k=5)
+
+
+
# Making top 5 recommendations for any user_id with item_item_similarity-based recommendation engine
+recommendations = get_recommendations(df_final, user_id_interaction, 5,
+ sim_item_item_tuned_model)
+
+
+
# Building the dataframe for above recommendations with columns "song_id" and "predicted_play_count"
+recommendations_df = pd.DataFrame(recommendations, columns=['song_id', 'predicted_play_count'])
+recommendations_df
+
+
+
# Applying the ranking_songs function
+ranking_songs(recommendations, df_songs_count_freq)
+
+Above we can see the list of the songs recommended by the user-user colaborative filtering model. - The songs recommended by this model vary from the ones recommended by the user-user model - The predicted play counts vary beetwen 1.96 and 1.77
+
+
+ Model Based Collaborative Filtering - Matrix Factorization
+Model-based Collaborative Filtering is a personalized recommendation system, the recommendations are based on the past behavior of the user and it is not dependent on any additional information. We use latent features to find recommendations for each user.
+
+
# Build baseline model using svd
+svd = SVD(random_state=1)
+
+# Training the algorithm on the trainset
+svd.fit(trainset)
+
+# Use the function precision_recall_at_k to compute precision@k, recall@k, F1-Score, and RMSE
+precision_recall_at_k(svd, testset=testset)
+
+
+
# Making prediction for user (with user_id 6958) to song (with song_id 1671), take r_ui = 2
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+svd.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+
+
+
# Making a prediction for the user who has not listened to the song (song_id 3232)
+user_id_no_interaction, song_id_no_interaction = (6958, 3232)
+svd.predict(user_id_no_interaction, song_id_no_interaction)
+
+
+ Improving matrix factorization based recommendation system by tuning its hyperparameters
+
+
# Set the parameter space to tune
+params = dict(n_epochs=[45, 60], lr_all=[0.0001, 0.005], reg_all=[0.05, 0.1],
+ reg_bu=[0.05, 0.1])
+
+# Performe 3-fold grid-search cross-validation
+gs = GridSearchCV(SVD, param_grid=params, n_jobs=-1, measures=['rmse'], cv=3)
+
+# Fitting data
+gs.fit(data)
+
+# Best RMSE score
+print(gs.best_score['rmse'])
+
+# Combination of parameters that gave the best RMSE score
+print(gs.best_params['rmse'])
+
+Think About It: How do the parameters affect the performance of the model? Can we improve the performance of the model further? Check the available hyperparameters here. - This svd algorithm has a lot of tunnable parameters. - Tweeking theses parameters might improve the predictions.
+
+
# Building the optimized SVD model using optimal hyperparameters
+best_params_svd = gs.best_params['rmse']
+best_n_epochs = best_params_svd['n_epochs']
+best_lr = best_params_svd['lr_all']
+best_reg = best_params_svd['reg_all']
+best_reg_bu = best_params_svd['reg_bu']
+
+svd_tuned = SVD(n_epochs=best_n_epochs, lr_all=best_lr, reg_all=best_reg,
+ reg_bu=best_reg_bu, random_state=1)
+
+# Train the algorithm on the trainset
+svd_tuned.fit(trainset)
+
+# Use the function precision_recall_at_k to compute precision@k, recall@k, F1-Score, and RMSE
+precision_recall_at_k(svd_tuned, testset=testset)
+
+The performance of this tuned model has not increased compared to the baseline svd model
+
+
# Using svd_algo_optimized model to recommend for userId 6958 and song_id 1671
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+svd_tuned.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+
+
+
# Using svd_algo_optimized model to recommend for userId 6958 and song_id 3232 with unknown baseline play_count
+user_id_no_interaction, song_id_no_interaction = (6958, 3232)
+svd_tuned.predict(user_id_no_interaction, song_id_no_interaction)
+
+The predicted play count for the interacted user-song pair is 1.48. The actual play count is 2, so the prediction is far from the actual.
+The predicted play count the for the user-song pair with no interaction is 1.54.
+
+
# Getting top 5 recommendations for user_id 6958 using "svd_optimized" algorithm
+recommendations = get_recommendations(df_final, user_id_interaction, 5,
+ sim_item_item_tuned_model)
+
+
+
# Ranking songs based on above recommendations
+recommendations_df = pd.DataFrame(recommendations, columns=['song_id', 'predicted_play_count'])
+recommendations_df
+
+Above we can see the list of the songs recommended by the matrix factorization colaborative filtering model. - The songs recommended by this model are the ones recommended by the item-item model - The predicted play counts vary beetwen 1.96 and 1.77
+
+
+
+ Cluster Based Recommendation System
+In clustering-based recommendation systems, we explore the similarities and differences in people’s tastes in songs based on how they rate different songs. We cluster similar users together and recommend songs to a user based on play_counts from other users in the same cluster.
+
+
# Make baseline clustering model
+clustering_model = CoClustering(random_state=1)
+
+# Train the algorithm on the trainset
+clustering_model.fit(trainset)
+
+# Use the function precision_recall_at_k to compute precision@k, recall@k, F1-Score, and RMSE
+precision_recall_at_k(clustering_model, testset=testset)
+
+
+
# Making prediction for user_id 6958 and song_id 1671
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+clustering_model.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+
+
+
# Making prediction for user (userid 6958) for a song(song_id 3232) not listened to by the user
+user_id_no_interaction, song_id_no_interaction = (6958, 3232)
+clustering_model.predict(user_id_no_interaction, song_id_no_interaction)
+
+
+ Improving clustering-based recommendation system by tuning its hyper-parameters
+
+
# Set the parameter space to tune
+param_grid = {'n_cltr_u': [1, 3, 10], 'n_cltr_i': [1, 3, 10 ],
+ 'n_epochs': [10, 30, 40]}
+
+# Performing 3-fold grid search cross-validation
+gs = GridSearchCV(CoClustering, param_grid, measures = ['rmse'], cv = 3, n_jobs = -1)
+
+# Fitting data
+gs.fit(data)
+
+# Best RMSE score
+print(gs.best_score['rmse'])
+
+# Combination of parameters that gave the best RMSE score
+print(gs.best_params['rmse'])
+
+Think About It: How do the parameters affect the performance of the model? Can we improve the performance of the model further? Check the available hyperparameters here. - The coclustering algorithm has some tunnable parameters. - Tweeking theses parameters might improve the predictions.
+
+
# Train the tuned Coclustering algorithm
+best_params_clust = gs.best_params['rmse']
+best_n_cltr_u = best_params_clust['n_cltr_u']
+best_n_cltr_i = best_params_clust['n_cltr_i']
+best_n_epochs = best_params_clust['n_epochs']
+
+clustering_model_tuned = CoClustering(n_cltr_u=best_n_cltr_u,
+ n_cltr_i=best_n_cltr_i,
+ n_epochs=best_n_epochs,
+ random_state=1)
+
+# Train the algorithm on the trainset
+clustering_model_tuned.fit(trainset)
+
+# Use the function precision_recall_at_k to compute precision@k, recall@k, F1-Score, and RMSE
+precision_recall_at_k(clustering_model_tuned, testset=testset)
+
+The performance of this tuned model has not increased compared to the baseline coclustering model.
+
+
# Using co_clustering_optimized model to recommend for userId 6958 and song_id 1671
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+clustering_model_tuned.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+
+
+
# Use Co_clustering based optimized model to recommend for userId 6958 and song_id 3232 with unknown baseline play_count
+user_id_no_interaction, song_id_no_interaction = (6958, 3232)
+clustering_model_tuned.predict(user_id_no_interaction, song_id_no_interaction)
+
+The predicted play count for the interacted user-song pair is 1.12. The actual play count is 2, so the prediction is far from the actual.
+The predicted play count the for the user-song pair with no interaction is 1.31.
+
+
+ Implementing the recommendation algorithm based on optimized CoClustering model
+
+
# Getting top 5 recommendations for user_id 6958 using "Co-clustering based optimized" algorithm
+recommendations = get_recommendations(df_final, user_id_interaction, 5,
+ clustering_model_tuned)
+
+
+
+
+ Correcting the play_count and Ranking the above songs
+
+
# Ranking songs based on the above recommendations
+ranking_songs(recommendations, df_songs_count_freq)
+
+Above we can see the list of the songs recommended by the svd model. - the top recommendation is the song 7224 with 2.93 play_counts. - the fifth recommendation is the song 4831 with 2.34 playcounts. - all the recommended songs have a predicted playcounts greater than 2. - the songs are the same recommended by the user-user colaborative filtering model.
+
+
+ Content Based Recommendation Systems
+Think About It: So far we have only used the play_count of songs to find recommendations but we have other information/features on songs as well. Can we take those song features into account? - Yes we can recommend songs which features are similar to a target song
+
+
# Concatenate the "title", "release", "artist_name" columns to create a different column named "text"
+df_final['text'] = df_final['title'] + ' ' + df_final['release'] + ' ' + df_final['artist_name']
+
+
+
# Selecting a random subset of the original data
+df_small = df_final.sample(n = 100000, random_state = 42)
+
+# Select the columns 'user_id', 'song_id', 'play_count', 'title', 'text' from df_small data
+df_small = df_small[['user_id', 'song_id', 'play_count', 'title', 'text']]
+
+# Drop the duplicates from the title column
+df_small = df_small.drop_duplicates('title')
+
+# Set the title column as the index
+df_small.set_index('title', inplace=True)
+
+# See the first 5 records of the df_small dataset
+df_small.head(5)
+
+
+
# Create the series of indices from the data
+indices = pd.Series(df_small.index)
+
+# Let us see the first 5 indices
+indices[:5]
+
+
+
# Importing necessary packages to work with text data
+import nltk
+
+# Download punkt library
+nltk.download("punkt")
+
+# Download stopwords library
+nltk.download('stopwords')
+
+# Download wordnet
+nltk.download('wordnet')
+
+# Import regular expression
+import re
+
+# Import word_tokenizer
+from nltk import word_tokenize
+
+# Import WordNetLemmatizer
+from nltk.stem import WordNetLemmatizer
+
+# Import stopwords
+from nltk.corpus import stopwords
+
+# Import CountVectorizer and TfidfVectorizer
+from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
+
+We will create a function to pre-process the text data:
+
+
# Create a function to tokenize the text
+def tokenize(text):
+
+ # Making each letter as lowercase and removing non-alphabetical text
+ text = re.sub(r"[^a-zA-Z]", " ", text.lower())
+
+ # Extracting each word in the text
+ tokens = word_tokenize(text)
+
+ # Removing stopwords
+ words = [word for word in tokens if word not in stopwords.words("english")]
+
+ # Lemmatize the words
+ text_lems = [WordNetLemmatizer().lemmatize(lem).strip() for lem in words]
+
+ return text_lems
+
+
+
# Create tfidf vectorizer
+tfidf = TfidfVectorizer(tokenizer = tokenize)
+
+# Fit_transfrom the above vectorizer on the text column and then convert the output into an array
+songs_tfidf = tfidf.fit_transform(df_small['text'].values).toarray()
+
+
+
# Compute the cosine similarity for the tfidf above output
+similar_songs = cosine_similarity(songs_tfidf, songs_tfidf)
+
+Finally, let’s create a function to find most similar songs to recommend for a given song.
+
+
# Function that takes in song title as input and returns the top 10 recommended songs
+def recommendations(title, similar_songs, indices = indices, data = df_small):
+
+ recommended_songs = []
+
+ # Getting the index of the song that matches the title
+ idx = indices[indices == title].index[0]
+
+ # Creating a Series with the similarity scores in descending order
+ score_series = pd.Series(similar_songs[idx]).sort_values(ascending = False)
+
+ # Getting the indexes of the 10 most similar songs
+ top_10_indexes = list(score_series.iloc[1:11].index)
+
+ # Populating the list with the titles of the best 10 matching songs
+ for i in top_10_indexes:
+ recommended_songs.append(list(data.index)[i])
+
+ return recommended_songs
+
+Recommending 10 songs similar to Learn to Fly
+
+
# Make the recommendation for the song with title 'Learn To Fly'
+recommendations("Learn To Fly", similar_songs, indices, df_small)
+
+The list above contains the songs predicted similar to the song Learn To Fly - To verify that the recommendation is working, we can check if the “title”, “release” and “artist_name” of the predicted songs are similar to the ones of the target song. They seems to be different. - We need more compelling features for this task, the genre of the songs or some tags describing each songs …
+
+
+
+ Conclusion and Recommendations
+1. Comparison of various techniques and their relative performance based on chosen Metric (Measure of success): - How do different techniques perform? Which one is performing relatively better? Is there scope to improve the performance further?
+
+- The F1 scores of the models built so far are:
+
+- user-user colaborative filtering baseline model: 0.504
+- user-user colaborative filtering tuned model: 0.513
+- item-item colaborative filtering baseline model: 0.397
+- item-item colaborative filtering tuned model: 0.483
+- matrix factorization baseline model: 0.498
+- matrix factorization tuned model: 0.504
+- coclustering baseline model: 0.472
+- coclustering tuned model: 0.472
+- content based model: no score computed
+- popularity based model : no score computed
+
+- The model with the best F1 score is the user-user colaborative filtering tuned model.
+- The F1 scores are very low. We can definitely improve these scores. This can be done by testing different technics, such as:
+- data balancing
+- changing the cutoffs thresholds.
+- customizing the implementation of the algorithms for this specific problem
+- collecting more data if possible
+- building hybrid recommendations systems. This can be done by analyzing the observations for which the model is not performing well and use another model which performs better on those observations. Thus combining the predictions of both models at the same time.
+- For the content based model, we need more features describing the songs, for example the genres of the songs.
+
+2. Refined insights: - What are the most meaningful insights from the data relevant to the problem? - The data is imbalanced, most of the play counts are 1 and 2, very few are 3, 4 or 5. - Adding more boservations with high interactions will increase the performance of the models. - Some observations date from 1969, we need further analysis to see how the rejection of these observations impacts the quality of the predictions. This could increase performance as it would likely reduce the sparsity of matrices and improve similarity measures. some old songs are no longer listened to and users tastes may have changed.
+3. Proposal for the final solution design: - What model do you propose to be adopted? Why is this the best solution to adopt? - Based on this analysis, the proposed model is the user-user colaborative filtering tuned model. This model has the best F1 score and the metric suited for this business is the F1 score. - We can increase the performance of this model by investigating further technics as mentionned above.
+
+
+ Further analysis
+Testing the cutoffs thresholds and discarding old songs
+
+
df = pd.merge(count_df, song_df.drop_duplicates('song_id'), how='left')
+
+# Apply label encoding for "user_id" and "song_id"
+from sklearn.preprocessing import LabelEncoder
+
+#storing transformers in a dictionnary for decoding if needed
+label_object = {}
+categorical_columns = ['user_id','song_id']
+for col in categorical_columns:
+ labelencoder = LabelEncoder()
+ labelencoder.fit(df[col])
+ df[col] = labelencoder.fit_transform(df[col])
+ label_object[col] = labelencoder
+
+#saving the original df in df_copy
+df_copy = df.copy()
+
+
+
# Get the column containing the users
+users = df.user_id
+
+# Create a dictionary that maps users(listeners) to the number of songs that they have listened to
+playing_count = dict()
+
+for user in users:
+ # If we already have the user, just add 1 to their playing count
+ if user in playing_count:
+ playing_count[user] += 1
+
+ # Otherwise, set their playing count to 1
+ else:
+ playing_count[user] = 1
+
+
+
# We want our users to have listened at least 90 songs
+SONG_COUNT_CUTOFF = 160
+
+# Create a list of users who need to be removed
+remove_users = []
+
+for user, num_songs in playing_count.items():
+
+ if num_songs < SONG_COUNT_CUTOFF:
+ remove_users.append(user)
+
+df = df.loc[ ~ df.user_id.isin(remove_users)]
+
+
+
# Get the column containing the songs
+songs = df.song_id
+
+# Create a dictionary that maps songs to its number of users(listeners)
+playing_count = dict()
+
+for song in songs:
+ # If we already have the song, just add 1 to their playing count
+ if song in playing_count:
+ playing_count[song] += 1
+
+ # Otherwise, set their playing count to 1
+ else:
+ playing_count[song] = 1
+
+
+
# We want our song to be listened by at least 120 users to be considred
+LISTENER_COUNT_CUTOFF = 160
+
+remove_songs = []
+
+for song, num_users in playing_count.items():
+ if num_users < LISTENER_COUNT_CUTOFF:
+ remove_songs.append(song)
+
+df_final = df.loc[ ~ df.song_id.isin(remove_songs)]
+
+
+
+
# Keeping the records with play counts greater than 5 by decreasing their play_count to 5
+df_final.loc[df_final.play_count>5, 'play_count'] = 5
+
+# discarding old observations
+df_final = df_final[(df_final.year > 1999) | (df_final.year == 0)]
+
+
+
+
# Create a barplot plot with y label as "number of titles played" and x -axis year
+nbr_titles_played_by_year = df_final.groupby('year').title.nunique()
+
+# Set the figure size
+plt.figure(figsize=(7, 5))
+#from the previous cell, we could see there is an abnormal year 0, which seems to be
+#a default, so we're dropping that year
+plt.bar(x=nbr_titles_played_by_year.index[1:], height=nbr_titles_played_by_year[1:])
+
+# Set the x label of the plot
+plt.xlabel('year')
+
+# Set the y label of the plot
+plt.ylabel('number of titles played')
+
+# Setting all the ticks
+plt.xticks(ticks=nbr_titles_played_by_year.index[1:], rotation=90, size=7)
+
+# Show the plot
+plt.show()
+
+
+
# Instantiating Reader scale with expected rating scale
+ #use rating scale (0, 5)
+reader = Reader(rating_scale=(0, 5))
+
+# Loading the dataset
+ # Take only "user_id","song_id", and "play_count"
+data = Dataset.load_from_df(df_final[['user_id', 'song_id', 'play_count']],
+ reader=reader)
+
+# Splitting the data into train and test dataset
+ # Take test_size = 0.4, random_state = 42
+trainset, testset = train_test_split(data, test_size=0.4, random_state=42)
+
+
+
# Build the default user-user-similarity model
+sim_options=dict(name='cosine', user_based=True)
+
+# KNN algorithm is used to find desired similar items
+ # Use random_state = 1
+sim_user_user_model = KNNBasic(sim_options=sim_options, random_state=1,
+ verbose=True)
+
+# Train the algorithm on the trainset, and predict play_count for the testset
+sim_user_user_model.fit(trainset)
+
+# Let us compute precision@k, recall@k, and f_1 score with k = 30
+ # Use sim_user_user model
+precision_recall_at_k(sim_user_user_model, testset=testset)
+
+The new F1 score is 0.66. This is better than 0.51 obtained in the conclusion. The precision is still bad.
+
+
+ Tuning the model
+
+
# Setting up parameter grid to tune the hyperparameters
+params_grid = dict(k=[60, 80, 100], min_k=[10, 40, 50],
+ sim_options=dict(name=['msd', 'cosine'],
+ #min_support = [5, 10, 20],
+ user_based=[True]))
+# Performing 3-fold cross-validation to tune the hyperparameters
+gs = GridSearchCV(KNNBasic, param_grid=params_grid, cv=3, measures=['rmse'],
+ n_jobs=-1)
+
+# Fitting the data
+ # Use entire data for GridSearch
+gs.fit(data)
+
+# Best RMSE score
+print("Best RMSE score:", gs.best_score['rmse'])
+
+# Combination of parameters that gave the best RMSE score
+print("Best_params:", gs.best_params['rmse'])
+
+# Train the best model found in above gridsearch
+# Using the optimal similarity measure for user-user based collaborative filtering
+best_params = gs.best_params['rmse']
+best_k = best_params['k']
+best_min_k = best_params['min_k']
+best_sim_options = best_params['sim_options']
+
+# Creating an instance of KNNBasic with optimal hyperparameter values
+sim_user_user_tuned_model = KNNBasic(k = best_k, min_k=best_min_k,
+ sim_options=best_sim_options,
+ random_state=1, verbose=False)
+
+# Training the algorithm on the trainset
+sim_user_user_tuned_model.fit(trainset)
+
+# Let us compute precision@k and recall@k also
+precision_recall_at_k(sim_user_user_tuned_model, testset=testset)
+
+Tuning has not increased the performance.
+
+
+ Balancing the data
+We only balance the trainset. As balancing the whole dataset would leak information of the testset in the training.
+
+
trainset_df = pd.DataFrame(trainset.build_testset(), columns = ['user_id', 'song_id', 'play_count'])
+
+# We will oversample the under representated classes using smote
+from imblearn.over_sampling import SMOTE
+
+smote = SMOTE(random_state=42)
+
+#balancing with smote
+x, y = smote.fit_resample(trainset_df[['user_id', 'song_id']], trainset_df.play_count)
+
+trainset_df_balanced = pd.concat([x,y], axis=1)
+
+# converting the trainset balanced into suprise dataset
+trainset_data_balanced = Dataset.load_from_df(trainset_df_balanced[['user_id', 'song_id', 'play_count']],
+ reader=reader)
+
+# rebuild the trainset for surprise algorithms training
+trainset_balanced = trainset_data_balanced.build_full_trainset()
+
+# balanced the whole dataset for comparison purposes
+x2, y2 = smote.fit_resample(df_final[['user_id', 'song_id']], df_final.play_count)
+df_final_balanced = pd.concat([x2,y2], axis=1)
+data_balanced = Dataset.load_from_df(df_final_balanced[['user_id', 'song_id', 'play_count']],
+ reader=reader)
+
+
+
trainset_df_balanced.play_count.value_counts()
+
+
+
# Build the default user-user-similarity model
+sim_options=dict(name='cosine', user_based=True)
+
+# KNN algorithm is used to find desired similar items
+ # Use random_state = 1
+sim_user_user_model = KNNBasic(k = 40, min_k=1, sim_options=sim_options, random_state=1,
+ verbose=True)
+
+# Train the algorithm on the trainset, and predict play_count for the testset
+sim_user_user_model.fit(trainset_balanced)
+
+# Let us compute precision@k, recall@k, and f_1 score with k = 30
+ # Use sim_user_user model
+precision_recall_at_k(sim_user_user_model, k=30, threshold=1.5,testset=testset)
+
+Balancing the data has not increased the F1 score
+
+
+
+ Customizing the KNNBasic algorithm
+
+
from surprise.prediction_algorithms.knns import SymmetricAlgo
+
+from surprise.prediction_algorithms.predictions import PredictionImpossible
+from sklearn.ensemble import RandomForestRegressor
+
+class CollaborativeRandomForest(SymmetricAlgo):
+ """A basic collaborative filtering algorithm.
+
+ The prediction :math:`\\hat{r}_{ui}` is set as:
+
+ .. math::
+ \\hat{r}_{ui} = \\frac{
+ \\sum\\limits_{v \\in N^k_i(u)} \\text{sim}(u, v) \\cdot r_{vi}}
+ {\\sum\\limits_{v \\in N^k_i(u)} \\text{sim}(u, v)}
+
+ or
+
+ .. math::
+ \\hat{r}_{ui} = \\frac{
+ \\sum\\limits_{j \\in N^k_u(i)} \\text{sim}(i, j) \\cdot r_{uj}}
+ {\\sum\\limits_{j \\in N^k_u(i)} \\text{sim}(i, j)}
+
+ depending on the ``user_based`` field of the ``sim_options`` parameter.
+
+ Args:
+ k(int): The (max) number of neighbors to take into account for
+ aggregation (see :ref:`this note <actual_k_note>`). Default is
+ ``40``.
+ min_k(int): The minimum number of neighbors to take into account for
+ aggregation. If there are not enough neighbors, the prediction is
+ set to the global mean of all ratings. Default is ``1``.
+ sim_options(dict): A dictionary of options for the similarity
+ measure. See :ref:`similarity_measures_configuration` for accepted
+ options.
+ verbose(bool): Whether to print trace messages of bias estimation,
+ similarity, etc. Default is True.
+ """
+
+ def __init__(self, k=40, min_k=1, sim_options={}, verbose=True, **kwargs):
+
+ SymmetricAlgo.__init__(self, sim_options=sim_options, verbose=verbose, **kwargs)
+ self.k = k
+ self.min_k = min_k
+ self.rf = RandomForestRegressor()
+
+ def compute_rating_and_sim_averages(self, neighbors):
+ # compute weighted average
+ sum_sim = sum_ratings = actual_k = rating = 0
+ if (len(neighbors) != 0):
+ for (sim, r) in neighbors:
+ sum_sim += sim
+ sum_ratings += sim * r
+ else:
+ rating = self.trainset.global_mean
+
+ if sum_sim != 0 :
+ rating = sum_ratings/sum_sim
+ return (sum_sim, rating)
+
+ def fit(self, trainset):
+ SymmetricAlgo.fit(self, trainset)
+ self.sim = self.compute_similarities()
+
+ #The feature matrix
+ # an observation in x is the wheighted average of similarities for each user.
+ # For each user we compute the wheighted average of the similarities
+ # of all the users who listened to a target song for all the songs in the training dataset
+
+ X = []
+ #The target, the play counts
+
+ Y = []
+ # an observation in Y is the wheighted average of play counts for each user.
+ # For each user we compute the wheighted average of the play counts
+ # of all the users who listened to a target song for all the songs in the training dataset
+ for u in trainset.all_users():
+ for i in trainset.all_items():
+ x, y = self.switch(u, i)
+ neighbors = [(self.sim[x, x2], r) for (x2, r) in self.yr[y]]
+ n = self.compute_rating_and_sim_averages(neighbors)
+ X.append(n[0])
+ Y.append(n[1])
+
+ self.rf.fit(np.array(X).reshape(-1, 1), Y)
+
+ return self
+
+ def estimate(self, u, i):
+ if not (self.trainset.knows_user(u) and self.trainset.knows_item(i)):
+ raise PredictionImpossible("User and/or item is unknown.")
+
+ x, y = self.switch(u, i)
+
+ neighbors = [(self.sim[x, x2], r) for (x2, r) in self.yr[y]]
+
+ n = self.compute_rating_and_sim_averages(neighbors)[0]
+ est = self.rf.predict(np.array(n).reshape(-1, 1))[0]
+
+ details = {"actual_k": 6}
+ return est, details
+
+
+
# Build the default KNNBasicWithRandomForest model
+sim_options=dict(name='cosine', user_based=True)
+
+# KNN algorithm is used to find desired similar items
+ # Use random_state = 1
+sim_user_user_model = CollaborativeRandomForest(sim_options=sim_options,
+ random_state=1, verbose=False)
+
+# Train the algorithm on the trainset, and predict play_count for the testset
+sim_user_user_model.fit(trainset)
+
+# Let us compute precision@k, recall@k, and f_1 score with k = 30
+ # Use sim_user_user model
+precision_recall_at_k(sim_user_user_model, threshold=1.5, testset=testset)
+
+The F1 score is still 0.66. So there is no improvement.
+The performance of this model is not high. This is because we are using only one future, the weighted similarities. This is comparable to predicting the skin color of a person based on his age, it doesn’t make much sense.
+
+
+ Building an hybrid recommendation system:
+
+- We will use LightFM a newly discovered library
+
+
+
# Installing lightfm
+!pip install lightfm
+
+
+
# Importing packages
+from lightfm import LightFM
+from lightfm.data import Dataset
+from lightfm.evaluation import precision_at_k, recall_at_k, auc_score
+from lightfm.cross_validation import random_train_test_split
+
+
+
# Modifying the dataset cutoffs thresholds
+# Get the column containing the users
+df = df_copy
+users = df.user_id
+
+# Create a dictionary that maps users(listeners) to the number of songs that they have listened to
+playing_count = dict()
+
+for user in users:
+ # If we already have the user, just add 1 to their playing count
+ if user in playing_count:
+ playing_count[user] += 1
+
+ # Otherwise, set their playing count to 1
+ else:
+ playing_count[user] = 1
+
+# We want our users to have listened at least 90 songs
+SONG_COUNT_CUTOFF = 90
+
+# Create a list of users who need to be removed
+remove_users = []
+
+for user, num_songs in playing_count.items():
+
+ if num_songs < SONG_COUNT_CUTOFF:
+ remove_users.append(user)
+
+df = df.loc[ ~ df.user_id.isin(remove_users)]
+
+
+# Get the column containing the songs
+songs = df.song_id
+
+# Create a dictionary that maps songs to its number of users(listeners)
+playing_count = dict()
+
+for song in songs:
+ # If we already have the song, just add 1 to their playing count
+ if song in playing_count:
+ playing_count[song] += 1
+
+ # Otherwise, set their playing count to 1
+ else:
+ playing_count[song] = 1
+
+# We want our song to be listened by at least 120 users to be considred
+LISTENER_COUNT_CUTOFF = 120
+
+remove_songs = []
+
+for song, num_users in playing_count.items():
+ if num_users < LISTENER_COUNT_CUTOFF:
+ remove_songs.append(song)
+
+df_final = df.loc[ ~ df.song_id.isin(remove_songs)]
+
+
+
# Caping the play count to 5
+df_final.loc[df_final.play_count>5, 'play_count'] = 5
+
+
+
# rescaling the playcount [0, 1]
+df_final['play_count_scaled'] = 1 / df_final.play_count
+
+
+
# Setting desired positive pairs (playcount>1.5 or play_count_scaled>0.3 ) to 1
+# and negatives pairs to 0
+df_final.loc[df_final['play_count_scaled'] < 0.3, 'play_count_scaled'] = 0
+df_final.loc[df_final['play_count_scaled'] >= 0.3, 'play_count_scaled'] = 1
+
+
+
# Defining users and songs sets
+user_ids = set(df_final.user_id)
+songs_ids = set(df_final.song_id)
+
+
+
# Defining user features
+nbr_user_listened_songs = df_final.groupby('user_id').play_count.count()
+nbr_user_listened_songs = nbr_user_listened_songs / nbr_user_listened_songs.values.sum()
+
+
+
# Defining songs features
+df_final['text'] = df_final['title'] + ' ' + df_final['release'] + ' ' + df_final['artist_name']
+
+
+
# Create a Dataset object and fit users, songs, and features
+dataset = Dataset()
+dataset.fit(users=user_ids,
+ items=songs_ids,
+ user_features = set(nbr_user_listened_songs),
+ item_features = set(df_final.text))
+
+
+# Build interactions and features
+interactions = [(x[0], x[1], x[2]) for x in df_final[['user_id', 'song_id', 'play_count_scaled']].values]
+(interactions, weights) = dataset.build_interactions(interactions)
+
+
+
# Split the interactions matrix into a train and test set
+test_ratio = 0.2
+train_interactions, test_interactions = random_train_test_split(interactions, test_percentage=test_ratio, random_state=2)
+train_weights, test_weights = random_train_test_split(weights, test_percentage=test_ratio, random_state=2)
+
+
+
# Builiding user features
+user_features = list(zip(nbr_user_listened_songs.index, nbr_user_listened_songs.apply(lambda x: [x])))
+user_features = dataset.build_user_features(user_features)
+
+
+# Builiding items features
+item_features = list((x[0], [x[1]]) for x in df_final[['song_id', 'text']].drop_duplicates().values)
+item_features = dataset.build_item_features(item_features)
+
+
+
# Creating the LightFM model
+basic_lfm = LightFM(
+ no_components=150,
+ learning_rate=0.05,
+ loss='warp',
+ item_alpha=0.0001,
+ user_alpha=0.0001,
+ random_state=200)
+
+
+
# Training the model
+basic_lfm.fit(train_interactions,
+ sample_weight=train_weights,
+ user_features=user_features,
+ item_features=item_features,
+ epochs=50, num_threads=50, verbose=True)
+
+
+
# A function to evaluate the model using LightFm evaluation metrics
+def evaluate_lfm_model(model, test_interactions, user_features, item_features,
+ k=30):
+ # compute the auc
+ print('AUC', np.nanmean(auc_score(model,
+ test_interactions,
+ user_features=user_features,
+ item_features=item_features)))
+ # calculate precision_at_k
+ precision = precision_at_k(model,
+ test_interactions,
+ user_features=user_features,
+ item_features=item_features,
+ k=k
+ ).mean()
+ print("Precision:", precision)
+
+
+ # calculate recall_at_k
+ recall = recall_at_k(model,
+ test_interactions,
+ user_features=user_features,
+ item_features=item_features,
+ k=k).mean()
+ print("Recall:", recall)
+
+ # Calculate f1_score_at_k
+ f1_score = 2 * precision * recall / (recall + precision)
+ print("F1_score:", f1_score)
+
+
+
# Evaluating the performance of the model using lightfm evaluation metrics
+evaluate_lfm_model(basic_lfm, test_interactions, user_features,
+ item_features, 30)
+
+The AUC is 0.76. It measures the probability that a randomly chosen positive interaction (e.g., a user liking a song) is ranked higher by the model than a randomly chosen negative interaction (e.g., a user not liking a song)
+
+
+
+
+ Final Conclusion
+We improved the F1 score of the user user collaborative filtering model by 0.15 points (0.66 vs 0.51 before) just by: - changing the cutoffs thresholds - dropping old observations - caping the play counts greater than 5 to 5.
+Balancing the data and creating a custom Random Forest model have not improved the performance.
+We have also built an hybrid recommendation system using the LightFm library. The AUC obtained with a basic hybrid model is 0.76. The results can be improved provided: - more computer power to perform the training. - more data on users, for example demographic or behavior data. - more data on songs, for example the genre or the category of a song.
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/notebooks/music-recommendation.ipynb b/public/notebooks/music-recommendation.ipynb
new file mode 100644
index 0000000..34259cd
--- /dev/null
+++ b/public/notebooks/music-recommendation.ipynb
@@ -0,0 +1,9329 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "NRJtXkTrHxMQ"
+ },
+ "source": [
+ "### **Importing Libraries and the Dataset**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "6SRzOPXI2Efn",
+ "outputId": "e7effdb8-0329-4b7a-df1e-fd9ff208f8d6"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount(\"/content/drive\", force_remount=True).\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Mounting the drive\n",
+ "from google.colab import drive\n",
+ "drive.mount('/content/drive')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "R4YvKrpzId3K"
+ },
+ "outputs": [],
+ "source": [
+ "# Used to ignore the warning given as output of the code\n",
+ "import warnings\n",
+ "warnings.filterwarnings('ignore')\n",
+ "\n",
+ "# Basic libraries of python for numeric and dataframe computations\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "\n",
+ "# Import Matplotlib the Basic library for data visualization\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "# Import seaborn - Slightly advanced library for data visualization\n",
+ "import seaborn as sns\n",
+ "\n",
+ "# Import the required library to compute the cosine similarity between two vectors\n",
+ "from sklearn.metrics.pairwise import cosine_similarity\n",
+ "\n",
+ "# Import defaultdict from collections A dictionary output that does not raise a key error\n",
+ "from collections import defaultdict\n",
+ "\n",
+ "# Impoort mean_squared_error : a performance metrics in sklearn\n",
+ "from sklearn.metrics import mean_squared_error\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "bUGKX140wf-S"
+ },
+ "source": [
+ "### **Load the dataset**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "si6ulhIYImck"
+ },
+ "outputs": [],
+ "source": [
+ "# Importing the datasets\n",
+ "song_df = pd.read_csv('/content/drive/MyDrive/capstone/song_data.csv')\n",
+ "count_df = pd.read_csv('/content/drive/MyDrive/capstone/count_data.csv',\n",
+ " usecols=['user_id','song_id','play_count'])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "12TKB2M7XyC6"
+ },
+ "source": [
+ "### **Understanding the data by viewing a few observations**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 363
+ },
+ "id": "GCLzBuYiXlPM",
+ "outputId": "24675cf6-3739-46c6-d51f-3eacdf250af3"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "count_df"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " user_id | \n",
+ " song_id | \n",
+ " play_count | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " b80344d063b5ccb3212f76538f3d9e43d87dca9e | \n",
+ " SOAKIMP12A8C130995 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " b80344d063b5ccb3212f76538f3d9e43d87dca9e | \n",
+ " SOBBMDR12A8C13253B | \n",
+ " 2 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " b80344d063b5ccb3212f76538f3d9e43d87dca9e | \n",
+ " SOBXHDL12A81C204C0 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " b80344d063b5ccb3212f76538f3d9e43d87dca9e | \n",
+ " SOBYHAJ12A6701BF1D | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " b80344d063b5ccb3212f76538f3d9e43d87dca9e | \n",
+ " SODACBL12A8C13C273 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " b80344d063b5ccb3212f76538f3d9e43d87dca9e | \n",
+ " SODDNQT12A6D4F5F7E | \n",
+ " 5 | \n",
+ "
\n",
+ " \n",
+ " | 6 | \n",
+ " b80344d063b5ccb3212f76538f3d9e43d87dca9e | \n",
+ " SODXRTY12AB0180F3B | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " | 7 | \n",
+ " b80344d063b5ccb3212f76538f3d9e43d87dca9e | \n",
+ " SOFGUAY12AB017B0A8 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " | 8 | \n",
+ " b80344d063b5ccb3212f76538f3d9e43d87dca9e | \n",
+ " SOFRQTD12A81C233C0 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " | 9 | \n",
+ " b80344d063b5ccb3212f76538f3d9e43d87dca9e | \n",
+ " SOHQWYZ12A6D4FA701 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ " user_id song_id play_count\n",
+ "0 b80344d063b5ccb3212f76538f3d9e43d87dca9e SOAKIMP12A8C130995 1\n",
+ "1 b80344d063b5ccb3212f76538f3d9e43d87dca9e SOBBMDR12A8C13253B 2\n",
+ "2 b80344d063b5ccb3212f76538f3d9e43d87dca9e SOBXHDL12A81C204C0 1\n",
+ "3 b80344d063b5ccb3212f76538f3d9e43d87dca9e SOBYHAJ12A6701BF1D 1\n",
+ "4 b80344d063b5ccb3212f76538f3d9e43d87dca9e SODACBL12A8C13C273 1\n",
+ "5 b80344d063b5ccb3212f76538f3d9e43d87dca9e SODDNQT12A6D4F5F7E 5\n",
+ "6 b80344d063b5ccb3212f76538f3d9e43d87dca9e SODXRTY12AB0180F3B 1\n",
+ "7 b80344d063b5ccb3212f76538f3d9e43d87dca9e SOFGUAY12AB017B0A8 1\n",
+ "8 b80344d063b5ccb3212f76538f3d9e43d87dca9e SOFRQTD12A81C233C0 1\n",
+ "9 b80344d063b5ccb3212f76538f3d9e43d87dca9e SOHQWYZ12A6D4FA701 1"
+ ]
+ },
+ "execution_count": 155,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Display first 10 records of count_df data\n",
+ "count_df.head(10)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 363
+ },
+ "id": "tV1ed0ApXpu3",
+ "outputId": "6816e592-8393-4658-ee9d-74ba6aca2f6a"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "type": "dataframe",
+ "variable_name": "song_df"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " song_id | \n",
+ " title | \n",
+ " release | \n",
+ " artist_name | \n",
+ " year | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " SOQMMHC12AB0180CB8 | \n",
+ " Silent Night | \n",
+ " Monster Ballads X-Mas | \n",
+ " Faster Pussy cat | \n",
+ " 2003 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " SOVFVAK12A8C1350D9 | \n",
+ " Tanssi vaan | \n",
+ " Karkuteillä | \n",
+ " Karkkiautomaatti | \n",
+ " 1995 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " SOGTUKN12AB017F4F1 | \n",
+ " No One Could Ever | \n",
+ " Butter | \n",
+ " Hudson Mohawke | \n",
+ " 2006 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " SOBNYVR12A8C13558C | \n",
+ " Si Vos Querés | \n",
+ " De Culo | \n",
+ " Yerba Brava | \n",
+ " 2003 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " SOHSBXH12A8C13B0DF | \n",
+ " Tangle Of Aspens | \n",
+ " Rene Ablaze Presents Winter Sessions | \n",
+ " Der Mystic | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " SOZVAPQ12A8C13B63C | \n",
+ " Symphony No. 1 G minor \"Sinfonie Serieuse\"/All... | \n",
+ " Berwald: Symphonies Nos. 1/2/3/4 | \n",
+ " David Montgomery | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " | 6 | \n",
+ " SOQVRHI12A6D4FB2D7 | \n",
+ " We Have Got Love | \n",
+ " Strictly The Best Vol. 34 | \n",
+ " Sasha / Turbulence | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " | 7 | \n",
+ " SOEYRFT12AB018936C | \n",
+ " 2 Da Beat Ch'yall | \n",
+ " Da Bomb | \n",
+ " Kris Kross | \n",
+ " 1993 | \n",
+ "
\n",
+ " \n",
+ " | 8 | \n",
+ " SOPMIYT12A6D4F851E | \n",
+ " Goodbye | \n",
+ " Danny Boy | \n",
+ " Joseph Locke | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " | 9 | \n",
+ " SOJCFMH12A8C13B0C2 | \n",
+ " Mama_ mama can't you see ? | \n",
+ " March to cadence with the US marines | \n",
+ " The Sun Harbor's Chorus-Documentary Recordings | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ " song_id title \\\n",
+ "0 SOQMMHC12AB0180CB8 Silent Night \n",
+ "1 SOVFVAK12A8C1350D9 Tanssi vaan \n",
+ "2 SOGTUKN12AB017F4F1 No One Could Ever \n",
+ "3 SOBNYVR12A8C13558C Si Vos Querés \n",
+ "4 SOHSBXH12A8C13B0DF Tangle Of Aspens \n",
+ "5 SOZVAPQ12A8C13B63C Symphony No. 1 G minor \"Sinfonie Serieuse\"/All... \n",
+ "6 SOQVRHI12A6D4FB2D7 We Have Got Love \n",
+ "7 SOEYRFT12AB018936C 2 Da Beat Ch'yall \n",
+ "8 SOPMIYT12A6D4F851E Goodbye \n",
+ "9 SOJCFMH12A8C13B0C2 Mama_ mama can't you see ? \n",
+ "\n",
+ " release \\\n",
+ "0 Monster Ballads X-Mas \n",
+ "1 Karkuteillä \n",
+ "2 Butter \n",
+ "3 De Culo \n",
+ "4 Rene Ablaze Presents Winter Sessions \n",
+ "5 Berwald: Symphonies Nos. 1/2/3/4 \n",
+ "6 Strictly The Best Vol. 34 \n",
+ "7 Da Bomb \n",
+ "8 Danny Boy \n",
+ "9 March to cadence with the US marines \n",
+ "\n",
+ " artist_name year \n",
+ "0 Faster Pussy cat 2003 \n",
+ "1 Karkkiautomaatti 1995 \n",
+ "2 Hudson Mohawke 2006 \n",
+ "3 Yerba Brava 2003 \n",
+ "4 Der Mystic 0 \n",
+ "5 David Montgomery 0 \n",
+ "6 Sasha / Turbulence 0 \n",
+ "7 Kris Kross 1993 \n",
+ "8 Joseph Locke 0 \n",
+ "9 The Sun Harbor's Chorus-Documentary Recordings 0 "
+ ]
+ },
+ "execution_count": 116,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Display first 10 records of song_df data\n",
+ "song_df.head(10)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "bvKb5FHcXzcN"
+ },
+ "source": [
+ "### **Let us check the data types and and missing values of each column**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "yyoHc_cnX19J",
+ "outputId": "8ab05608-c9b6-4aaa-9d90-f9db2f168192"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "RangeIndex: 2000000 entries, 0 to 1999999\n",
+ "Data columns (total 3 columns):\n",
+ " # Column Non-Null Count Dtype \n",
+ "--- ------ -------------- ----- \n",
+ " 0 user_id 2000000 non-null object\n",
+ " 1 song_id 2000000 non-null object\n",
+ " 2 play_count 2000000 non-null int64 \n",
+ "dtypes: int64(1), object(2)\n",
+ "memory usage: 45.8+ MB\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Display info of count_df\n",
+ "count_df.info(show_counts=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "rz3zDx_LX42y",
+ "outputId": "cf46a82d-fd5b-4956-fcfd-19a0fbdc71bd"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "RangeIndex: 1000000 entries, 0 to 999999\n",
+ "Data columns (total 5 columns):\n",
+ " # Column Non-Null Count Dtype \n",
+ "--- ------ -------------- ----- \n",
+ " 0 song_id 1000000 non-null object\n",
+ " 1 title 999983 non-null object\n",
+ " 2 release 999993 non-null object\n",
+ " 3 artist_name 1000000 non-null object\n",
+ " 4 year 1000000 non-null int64 \n",
+ "dtypes: int64(1), object(4)\n",
+ "memory usage: 38.1+ MB\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Display info of song_df\n",
+ "song_df.info()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Ze2TlWxpYadn"
+ },
+ "source": [
+ "#### **The count data contains:**\n",
+ "* **2000000 observations** and **3 columns**.\n",
+ "* **user_id** and **sond_id** columns are of **object data type** but **play_count** column is of **numeric data type**.\n",
+ "* There is **no missing value**.\n",
+ "\n",
+ "\n",
+ "\n",
+ "#### **The song data contains:**\n",
+ "* **1000000 observations** and **5 columns**.\n",
+ "* **All the columns** are of **object data type** except the year column. The **year column** is of **numeric data type**.\n",
+ "* The **title** and **release** columns contains 1000000 - 999983 = **17 null values**. All the other columns do not contain null values."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "oTeurvID2T9U"
+ },
+ "outputs": [],
+ "source": [
+ "# Left merge count_df and song_df on \"song_id\". Drop duplicates from song_df data simultaneously\n",
+ "df = pd.merge(count_df, song_df.drop_duplicates('song_id'), how='left')\n",
+ "\n",
+ "# Drop the column 'Unnamed: 0'\n",
+ " # Already droped while loading the dataset\n",
+ "\n",
+ "## Name the obtained dataframe as \"df\"\n",
+ " # Already named df"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "yWeY9ZT43XFX"
+ },
+ "source": [
+ "**Think About It:** As the user_id and song_id are encrypted. Can they be encoded to numeric features?\n",
+ " - **Yes**, in this case encoding categorical feature **increase the execution performance** of the models although it does not impact the qualiy of the predictions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "oxeoOVxh2T9U"
+ },
+ "outputs": [],
+ "source": [
+ "# Apply label encoding for \"user_id\" and \"song_id\"\n",
+ "from sklearn.preprocessing import LabelEncoder\n",
+ "\n",
+ "#saving the original df in df_copy\n",
+ "df_copy = df.copy()\n",
+ "\n",
+ "#storing transformers in a dictionnary for decoding if needed\n",
+ "label_object = {}\n",
+ "categorical_columns = ['user_id','song_id']\n",
+ "for col in categorical_columns:\n",
+ " labelencoder = LabelEncoder()\n",
+ " labelencoder.fit(df[col])\n",
+ " df[col] = labelencoder.fit_transform(df[col])\n",
+ " label_object[col] = labelencoder"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "6Q9EFYwj35Ju"
+ },
+ "source": [
+ "**Think About It:** As the data also contains users who have listened to very few songs and vice versa, is it required to filter the data so that it contains users who have listened to a good count of songs and vice versa?\n",
+ " - **Yes**, filtering the data will indeed **reduce the sparcity** in the matrices used for modelling. Hence **increasing the performance of the computation** as well as the **quality of the recommendations**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "gcY5LKAQvk9J"
+ },
+ "source": [
+ "A dataset of size 2000000 rows x 7 columns can be quite large and may require a lot of computing resources to process. This can lead to long processing times and can make it difficult to train and evaluate your model efficiently.\n",
+ "In order to address this issue, it may be necessary to trim down your dataset to a more manageable size."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "7GGH9TW0_9uX"
+ },
+ "outputs": [],
+ "source": [
+ "# Get the column containing the users\n",
+ "users = df.user_id\n",
+ "\n",
+ "# Create a dictionary that maps users(listeners) to the number of songs that they have listened to\n",
+ "playing_count = dict()\n",
+ "\n",
+ "for user in users:\n",
+ " # If we already have the user, just add 1 to their playing count\n",
+ " if user in playing_count:\n",
+ " playing_count[user] += 1\n",
+ "\n",
+ " # Otherwise, set their playing count to 1\n",
+ " else:\n",
+ " playing_count[user] = 1"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "-cc6mOK7_9uX"
+ },
+ "outputs": [],
+ "source": [
+ "# We want our users to have listened at least 90 songs\n",
+ "SONG_COUNT_CUTOFF = 90\n",
+ "\n",
+ "# Create a list of users who need to be removed\n",
+ "remove_users = []\n",
+ "\n",
+ "for user, num_songs in playing_count.items():\n",
+ "\n",
+ " if num_songs < SONG_COUNT_CUTOFF:\n",
+ " remove_users.append(user)\n",
+ "\n",
+ "df = df.loc[ ~ df.user_id.isin(remove_users)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "B5BS-Wk5_9uY"
+ },
+ "outputs": [],
+ "source": [
+ "# Get the column containing the songs\n",
+ "songs = df.song_id\n",
+ "\n",
+ "# Create a dictionary that maps songs to its number of users(listeners)\n",
+ "playing_count = dict()\n",
+ "\n",
+ "for song in songs:\n",
+ " # If we already have the song, just add 1 to their playing count\n",
+ " if song in playing_count:\n",
+ " playing_count[song] += 1\n",
+ "\n",
+ " # Otherwise, set their playing count to 1\n",
+ " else:\n",
+ " playing_count[song] = 1"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "_nCtGwGO_9uY"
+ },
+ "outputs": [],
+ "source": [
+ "# We want our song to be listened by at least 120 users to be considred\n",
+ "LISTENER_COUNT_CUTOFF = 120\n",
+ "\n",
+ "remove_songs = []\n",
+ "\n",
+ "for song, num_users in playing_count.items():\n",
+ " if num_users < LISTENER_COUNT_CUTOFF:\n",
+ " remove_songs.append(song)\n",
+ "\n",
+ "df_final = df.loc[ ~ df.song_id.isin(remove_songs)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "aZt4U_C7n0vJ"
+ },
+ "source": [
+ "Out of all the songs available, songs with play_count less than or equal to 5 are in almost 90% abundance. So for building the recommendation system let us consider only those songs."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "8qaKeoMcGpad"
+ },
+ "outputs": [],
+ "source": [
+ "# Keep only records of songs with play_count less than or equal to (<=) 5\n",
+ "df_final = df_final[df_final.play_count<=5]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "aL1JZ00o5JtQ",
+ "outputId": "bb2aaab2-02fa-4020-c1e0-b2a8f5714878"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(117876, 7)"
+ ]
+ },
+ "execution_count": 166,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Check the shape of the data\n",
+ "df_final.shape"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "uZcr1Eke2T9W"
+ },
+ "source": [
+ "## **Exploratory Data Analysis**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ByuHmvWDeBJI"
+ },
+ "source": [
+ "### **Let's check the total number of unique users, songs, artists in the data**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "DE_gukSJ2T9W"
+ },
+ "source": [
+ "Total number of unique user id"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "n5E24_Ec2T9W",
+ "outputId": "27cbd251-b2e1-4170-f32f-05acb8126990"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3155"
+ ]
+ },
+ "execution_count": 167,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Display total number of unique user_id\n",
+ "df_final.user_id.nunique()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "wV3BOTdJII-t"
+ },
+ "source": [
+ "Total number of unique song id"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "5SlpPkIE2T9W",
+ "outputId": "59327b3a-a88a-4f5f-9f61-1470a89fb189"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "563"
+ ]
+ },
+ "execution_count": 168,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Display total number of unique song_id\n",
+ "df_final.song_id.nunique()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "eGXPsCjXVpUW"
+ },
+ "source": [
+ "Total number of unique artists"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "qSVUwb8h2T9X",
+ "outputId": "98aacb0b-f6c8-431f-8720-2ca8a1ab6a75"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "232"
+ ]
+ },
+ "execution_count": 169,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Display total number of unique artists\n",
+ "df_final.artist_name.nunique()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "bvk-YAo-eGGW"
+ },
+ "source": [
+ "#### **The data contains :**\n",
+ "\n",
+ "* **117876** observations\n",
+ "* **3155** unique users\n",
+ "* **563** unique songs\n",
+ "\n",
+ "#### As per the number of unique users and songs, there is a possibility of 3155 * 563 = **1.776.265 interactions in the dataset**. But we only have **117.876 interactions** meeting our criterias(90 songs count per user, 120 users count per song and at most 5 play counts per song), i.e., **not every user has listened every song** in the dataset, which is quite understandable.This creates the **possibility of building a recommendation system to recommend products to the users which they have not interacted with**.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "rLdIfv22ISBK"
+ },
+ "source": [
+ "### **Let's find out about the most interacted songs and interacted users**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "W3DyN_8atsCx"
+ },
+ "source": [
+ "Most interacted songs"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "qWDrvIFF2T9X",
+ "outputId": "a0662c0b-74e3-4a0b-8190-90798d1af5ce"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "song_id\n",
+ "8582 751\n",
+ "352 748\n",
+ "2220 713\n",
+ "1118 662\n",
+ "4152 652\n",
+ "5531 618\n",
+ "4448 609\n",
+ "6189 606\n",
+ "6293 583\n",
+ "1334 570\n",
+ "Name: count, dtype: int64"
+ ]
+ },
+ "execution_count": 170,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Printing the top ten songs\n",
+ "df_final.song_id.value_counts()[:10]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "nnoXCc9zIV45"
+ },
+ "source": [
+ "Most interacted users"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "971EiBdf2T9X",
+ "outputId": "a768ef6b-6818-4530-8e7d-6dcbc80d4c03"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "user_id\n",
+ "61472 243\n",
+ "15733 227\n",
+ "37049 202\n",
+ "9570 184\n",
+ "23337 177\n",
+ "10763 176\n",
+ "26616 175\n",
+ "9097 175\n",
+ "43041 174\n",
+ "65994 171\n",
+ "Name: count, dtype: int64"
+ ]
+ },
+ "execution_count": 171,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Printing the top ten users\n",
+ "df_final.user_id.value_counts()[:10]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "tPZRc1e-eyyO"
+ },
+ "source": [
+ "#### **The top user and song are the user with id 2548 and the song with id 8581**\n",
+ "\n",
+ "* The **top user** listened to **243 songs**\n",
+ " * Since there are **563 songs**, it is **possible to make recommations** to this particular user and to all users in general. For this top user, there are 563 - 243 = **320 possible recommendations left**.\n",
+ "* The **most popular song** was listened to by **751 users**\n",
+ " * Since there are **3155 users**, there are 3155 - 751 = **2404 possible users left to whom we could recommend this song**.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "joFF5zndX1Dk"
+ },
+ "source": [
+ "Songs released on yearly basis"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "bQp2iVMC2T9Y",
+ "outputId": "3b61d9f2-77eb-4995-8ce0-cec373fc84d5"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "year\n",
+ "0 484284\n",
+ "2007 39340\n",
+ "2006 37486\n",
+ "2005 34872\n",
+ "2008 34684\n",
+ "2009 30966\n",
+ "2004 29564\n",
+ "2003 27323\n",
+ "2002 23450\n",
+ "2001 21570\n",
+ "Name: song_id, dtype: int64"
+ ]
+ },
+ "execution_count": 172,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Find out the number of songs released in a year, use the songs_df\n",
+ " # Hint: Use groupby function on the 'year' column\n",
+ "song_df.drop_duplicates('song_id').groupby('year').song_id.count().sort_values(ascending=False)[:10]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 480
+ },
+ "id": "bZCkOiAB2T9Y",
+ "outputId": "def2e997-4bab-4737-b1e6-b78469d77b74"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAmUAAAHPCAYAAAAS4K0yAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABLtklEQVR4nO3de1gWdf7/8deNgIIEiEh4PqPr+VhpnjK3g1nK6mqlaVu4mdbXNDuamVu2a6WbW7a5pim6bh7y0Ja6mpZhHltrJS1BzTyiEAICIjcwvz+6uH/dgsY93Df33PB8XJfXxcy85zPvz3xoeDf3Z+a2GYZhCAAAAF7l5+0EAAAAQFEGAABgCRRlAAAAFkBRBgAAYAEUZQAAABZAUQYAAGABFGUAAAAWQFEGAABgARRlAAAAFkBRBgAAYAH+3k7AWy5cuKCCgoIKO16dOnWUmppqmXhXWC13+uqeeE+eRzN8ua++PK6+Hu8Kq+XOuFo/F3fw9/dXrVq1yhbr4Vwsq6CgQHa7vUKOZbPZHMcsy1eNejreFVbLnb66J96T59EMX+6rL4+rr8e7wmq5M67Wz8Ub+PgSAADAAijKAAAALICiDAAAwAIoygAAACyAogwAAMACKMoAAAAswFKvxCgqKtLKlSuVkJCgjIwMRUREqG/fvho6dKjjMVbDMLRy5Upt3bpVOTk5at26teLi4lS3bl0vZw8AAGCepe6UrVu3Tlu2bNHDDz+sv/71rxo5cqQ++ugjbdy40RGzfv16bdy4UWPHjtWrr76q6tWra+bMmcrPz/di5gAAAOVjqaIsKSlJ3bp1U5cuXRQVFaWbbrpJHTp00JEjRyT9fJdsw4YN+t3vfqfu3burcePGeuyxx3ThwgXt27fPy9kDAACYZ6miLCYmRt9++63OnDkjSTp+/LgOHz6szp07S5LOnz+vjIwMdejQwbFPcHCwWrRooaSkJK/kDAAA4A6WmlM2ZMgQXbp0SZMmTZKfn5+Kiop07733qnfv3pKkjIwMSVJYWJjTfmFhYY5tV7Lb7U5fp2Sz2RQUFOT4uSIUH6esx/N0vCusljt9dU+8J8+jGb7cV18eV1+Pd4XVcmdcrZ+LN9gMC33505dffqlly5Zp1KhRatiwoY4fP67Fixdr9OjR6tevnw4fPqxp06Zp/vz5Tl/uOWfOHNlsNk2aNKlEmytXrtTq1asdy02bNtWsWbMqpD8AAABlZak7ZcuWLdPgwYN18803S5IaNWqk1NRUrVu3Tv369VN4eLgkKTMz06koy8zMVJMmTUptMzY2VoMGDXIsF1fHqampKigo8ExHrmCz2RQdHa2UlJQyf2GqJ+NdYbXc6at74j15Hs3w5b768rj6erwrrJY742r9XNzF399fderUKVush3NxyeXLl+Xn5zzNzc/Pz3HioqKiFB4ersTEREcRlpubqyNHjui2224rtc2AgAAFBASUuq2i/xgZhuHSMT0d7wqr5U5f3RPvyfNohi/31ZfH1dfjXWG13BlX6+dSkSxVlHXt2lVr1qxRZGSkGjRooOPHj+vjjz/WLbfcIunnCnfgwIFas2aN6tatq6ioKH3wwQeqVauWunfv7uXsAQAAzLNUUfbQQw9pxYoVeu+995SZmamIiAj99re/1bBhwxwxgwcP1uXLlzV//nzl5uaqdevWev755xUYGOjFzAEAgCQVjr2nxLqTVyxXW/BRxSTjYyxVlAUFBenBBx/Ugw8+eNUYm82mESNGaMSIERWXGAAAgIdZ6j1lAAAAVRVFGQAAgAVQlAEAAFgARRkAAIAFUJQBAABYAEUZAACABVCUAQAAWABFGQAAgAVQlAEAAFgARRkAAIAFUJQBAABYAEUZAACABVCUAQAAWABFGQAAgAVQlAEAAFgARRkAAIAFUJQBAABYAEUZAACABVCUAQAAWABFGQAAgAVQlAEAAFgARRkAAIAFUJQBAABYAEUZAACABVCUAQAAWABFGQAAgAVQlAEAAFgARRkAAIAFUJQBAABYAEUZAACABVCUAQAAWABFGQAAgAVQlAEAAFgARRkAAIAFUJQBAABYAEUZAACABfh7O4FfmjBhglJTU0usv+222xQXF6f8/HzFx8dr586dstvt6tixo+Li4hQeHl7xyQIAALiRpYqyP//5zyoqKnIsnzhxQq+88op69OghSVqyZIn279+vyZMnKzg4WAsXLtTs2bP18ssveytlAAAAt7DUx5ehoaEKDw93/Nu/f7+uv/56tWnTRrm5udq2bZvGjBmjdu3aqVmzZho/frwOHz6spKQkb6cOAABQLpa6U/ZLBQUFSkhI0F133SWbzaZjx46psLBQ7du3d8TUr19fkZGRSkpKUkxMTKnt2O122e12x7LNZlNQUJDj54pQfJyyHs/T8a6wWu701T3xnjyPZvhyX315XH093hVWy70qjeu12qjoXKx27buSzTAMw9tJlGbnzp3629/+pnfeeUcRERHasWOH3nnnHS1fvtwp7rnnnlPbtm01atSoUttZuXKlVq9e7Vhu2rSpZs2a5dHcAQCoqk7e1e1XYxp+8lUFZOJ7LHun7LPPPlOnTp0UERFRrnZiY2M1aNAgx3JxdZyamqqCgoJytV1WNptN0dHRSklJUVlqYE/Hu8JqudNX98R78jya4ct99eVx9fV4V1gt96o0rqU5e/asV3LxxrXP399fderUKVush3MxJTU1VQcOHNCUKVMc68LDw1VQUKCcnBzVrFnTsT4zM/OaT18GBAQoICCg1G0V/cfIMAyXjunpeFdYLXf66p54T55HM3y5r748rr4e7wqr5V6VxvXKfb2Zi9WufcUsNdG/2GeffaawsDB16dLFsa5Zs2aqVq2aEhMTHevOnDmjtLS0q84nAwAA8BWWu1NWVFSkzz//XH379lW1atUc64ODg9W/f3/Fx8crJCREwcHBWrRokWJiYijKAACAz7NcUZaYmKi0tDTdcsstJbaNGTNGNptNs2fPVkFBgePlsQAAAL7OckVZx44dtXLlylK3BQYGKi4ujkIMAABUOpacUwYAAFDVUJQBAABYAEUZAACABVCUAQAAWABFGQAAgAVQlAEAAFgARRkAAIAFUJQBAABYAEUZAACABVCUAQAAWABFGQAAgAVQlAEAAFiA5b6QHAAAVB2FY+9xWj5ZSky1BR9VTDJexp0yAAAAC6AoAwAAsACKMgAAAAtgThkAAPAZlXkOGnfKAAAALICiDAAAwAIoygAAACyAogwAAMACKMoAAAAsgKIMAADAAijKAAAALICiDAAAwAIoygAAACyAogwAAMACKMoAAAAsgKIMAADAAijKAAAALMC/LEGHDh0y1XibNm1M7QcAAFDVlKkomzFjhqnGV6xYYWo/AACAqqZMRdn06dOdlu12u5YtW6b8/HzdeuutqlevniTpzJkz2rp1q6pXr65Ro0a5P1sAAIBKqkxF2ZUfQy5ZskT+/v6aOXOmAgMDnbbdfvvteumll/TNN9+oQ4cO7ssUAACgEjM10X/Hjh3q06dPiYJMkqpXr67evXsrISGh3MkBAABUFWW6U3alvLw8Xbhw4arbMzIydPnyZVMJpaena9myZfrmm290+fJlRUdHa/z48WrevLkkyTAMrVy5Ulu3blVOTo5at26tuLg41a1b19TxAAAArMDUnbL27dtr48aN2rNnT4ltu3fv1oYNG9S+fXuX283Ozta0adPk7++v559/Xn/96181evRo1axZ0xGzfv16bdy4UWPHjtWrr76q6tWra+bMmcrPzzfTFQAAAEswdacsLi5OM2bM0Jw5c1SrVi1FR0dLks6dO6f09HRFR0froYcecrnd9evXq3bt2ho/frxjXVRUlONnwzC0YcMG/e53v1P37t0lSY899pjGjh2rffv26eabbzbTHQAAAK8zVZRFRETo9ddf16effqqvv/5aaWlpkqQGDRro7rvv1oABA0qdb/ZrvvrqK3Xs2FFz5szRoUOHFBERodtuu00DBgyQJJ0/f14ZGRlODxAEBwerRYsWSkpKKrUos9vtstvtjmWbzaagoCDHzxWh+DhlPZ6n411htdzpq3viPXkezfDlvvryuPp6vCuslntVGtdrtVHR8Va79l3JZhiG4e0kio0cOVKSdNddd6lHjx46evSo3n//fY0dO1b9+vXT4cOHNW3aNM2fP1+1atVy7DdnzhzZbDZNmjSpRJsrV67U6tWrHctNmzbVrFmzPN8ZAACqoJN3dfvVmIaffFVh8b7E1J2yYna7XT/88IMyMzPVqlUrhYaGliuZoqIiNW/eXPfff7+knwuoEydOaMuWLerXr5+pNmNjYzVo0CDHcnF1nJqaqoKCgnLlW1Y2m03R0dFKSUlRWWpgT8e7wmq501f3xHvyPJrhy3315XH19XhXWC33qjSupTl79qxX4r1x7fP391edOnXKFmv2IBs2bNCqVauUm5srSZo2bZratWunrKwsTZo0SSNHjlT//v1darNWrVpq0KCB07oGDRo4HigIDw+XJGVmZjrdKcvMzFSTJk1KbTMgIEABAQGlbqvoP0aGYbh0TE/Hu8JqudNX98R78jya4ct99eVx9fV4V1gt96o0rlfu6814q137ipl6+vKzzz7TkiVL1KlTJz366KNO20JDQ9W2bVvt3LnT5XZbtWqlM2fOOK07c+aMo8KMiopSeHi4EhMTHdtzc3N15MgRxcTEmOgJAACANZgqyj7++GN169ZNEydOVNeuXUtsb9asmU6ePOlyu3fddZeSk5O1Zs0apaSkaMeOHdq6datuv/12ST/fdhw4cKDWrFmjr776SidOnNDbb7+tWrVqOZ7GBAAA8EWmPr5MSUnRnXfeedXtISEhys7OdrndFi1aaMqUKVq+fLk+/PBDRUVFacyYMerdu7cjZvDgwbp8+bLmz5+v3NxctW7dWs8//7yppz0BAACswlRRFhwcrKysrKtuP3XqlGP+l6u6du1a6t23YjabTSNGjNCIESNMtQ8AAGBFpj6+7Ny5s+Nrjq508uRJbd269ZqFFQAAAJyZulN27733aurUqXryyScdxdfnn3+ubdu2ac+ePapVq5aGDRvm1kQBAAAqM9Nv9P/LX/6if/3rX46nLBMSElSjRg3dfPPNGjlyZLnfWQYAAFCVmH5PWVhYmMaNG6dx48YpKytLRUVFCg0NlZ+fqU9EAQAAqjRTFdTOnTuVn5/vWA4NDVV4eDgFGQAAgEmm7pTNnTtXQUFBuvHGG9W7d2+1a9fO3XkBAABUKaaKsj/96U9KSEjQ7t279fnnnysiIkK9evVS79691ahRI3fnCAAAUOmZKspatWqlVq1a6Q9/+IO++eYbJSQkaNOmTfroo4/UqFEj9enTR7169XL6fkoAAABcnemJ/pJUrVo1x8te8/LytHfvXn3++edatmyZli9frjZt2qhv377q2bOn/P3LdSgAAIBKzW0z80+cOKEjR47oxIkTkqR69eopOztb8+bN0+OPP67vv//eXYcCAACodMp1++rMmTPasWOHduzYoXPnzik0NFS9evVSnz591KxZM0nS0aNH9e6772rBggWaPXu2W5IGAACobEwVZRs2bFBCQoKOHTumgIAAde3aVQ8++KA6depU4rUYzZs311133aV3333XLQkDAABURqaKsiVLlqhVq1YaO3asevbsqeDg4GvGN2/eXEOHDjWVIAAAQFVgqih76623FBUVVeb4hg0bqmHDhmYOBQAAUCWYmujvSkEGAACAX2d6on9+fr727NmjH374Qbm5uSoqKnLabrPZ9Oijj5Y7QQAAgKrAVFGWmpqqGTNmKDU1VcHBwcrNzVVISIijOLvuuutUo0YNd+cKAABQaZn6+HLp0qXKzc3VzJkzNXfuXEnSpEmTFB8fr5EjRyowMFBTp051a6IAAACVmami7ODBg7rtttvUokULxyswDMNQQECA7rnnHrVr106LFy92Z54AAACVmqmi7PLly47J/kFBQZKk3Nxcx/aYmBje4A8AAOACU0VZZGSkfvrpJ0k/f/9lRESEkpOTHdtPnTqlwMBA92QIAABQBZia6N+uXTt99dVX+v3vfy9J6tevn9atW6fs7GwZhqEvvvhCffv2dWuiAAAAlZmpomzIkCE6cuSI7Ha7AgICFBsbqwsXLmjPnj3y8/NTr169NHr0aHfnCgAAUGmZKsoiIyMVGRnpWA4MDNS4ceM0btw4tyUGAABQlZiaUwYAAAD3KtOdstWrV5tqfNiwYab2AwAAqGrKVJStWrXKVOMUZQAAAGVTpqJsxYoVns4DAACgSmNOGQAAgAWYevqyWHZ2tg4cOKDU1FRJUp06ddS+fXtdd911bkkOAACgqjBdlK1cuVLr169XQUGBc4P+/rrnnns0YsSIcicHAABQVZgqylavXq0PP/xQnTt31h133KF69epJks6cOaNNmzZpzZo1qlatGhP9AQAAyshUUbZlyxZ17dpVTz/9tNP6qKgoderUSX/5y1+0ZcsWijIAAIAyMjXRPzc3V506dbrq9i5duujSpUtmcwIAAKhyTBVlrVu3VnJy8lW3Jycnq1WrVqaTAgAAqGpMfXw5duxYzZw5U4sXL9Ydd9yhqKgoSdL58+e1adMmJScn6/nnn3e53ZUrV5b49oB69erpzTfflCTl5+crPj5eO3fulN1uV8eOHRUXF6fw8HAz3QAAALAMU0XZlClTZBiGNm7cqI0bN8rP7+cbbkVFRZKkgIAAPfXUUyX2W7Jkya+23bBhQ02bNs2xXNx28f779+/X5MmTFRwcrIULF2r27Nl6+eWXzXQDAADAMkwVZTfeeKNsNpu7c5H0cxFW2p2v3Nxcbdu2TRMnTlS7du0kSePHj9ekSZOUlJSkmJgYj+QDAABQEUwVZRMmTHB3Hg4pKSl65JFHFBAQoJiYGN1///2KjIzUsWPHVFhYqPbt2zti69evr8jISIoyAADg88r1Rn93a9mypcaPH6969erpwoULWr16tV588UXNnj1bGRkZ8vf3V82aNZ32CQsLU0ZGxlXbtNvtstvtjmWbzaagoCDHzxWh+DhlPZ6n411htdzpq3viPXkezfDlvvryuPp6vCuslntVGtdrtVHR8Va79l3JZhiG4e0kriYnJ0fjx4/XmDFjFBgYqHfeeUfLly93innuuefUtm1bjRo1qtQ2rnx4oGnTppo1a5ZH8wYAoKo6eVe3X41p+MlXFRbvSyx1p+xKNWvWVL169ZSSkqIOHTqooKBAOTk5TnfLMjMzr/n0ZWxsrAYNGuRYLq6OU1NTS3xFlKfYbDZFR0crJSVFZamBPR3vCqvlTl/dE+/J82iGL/fVl8fV1+NdYbXcq9K4lubs2bNeiffGtc/f31916tQpW6yHcymXvLw8paSkqHfv3mrWrJmqVaumxMRE3XTTTZJ+/lqntLS0a84nCwgIUEBAQKnbKvqPkWEYLh3T0/GusFru9NU98Z48j2b4cl99eVx9Pd4VVsu9Ko3rlft6M95q175ilirK4uPj1a1bN0VGRurChQtauXKl/Pz81KtXLwUHB6t///6Kj49XSEiIgoODtWjRIsXExDDJHwAA+DxLFWXp6emaO3euLl68qNDQULVu3VozZ85UaGioJGnMmDGy2WyaPXu2CgoKHC+PBQAA8HWmirK0tDSlpaWpdevWjnXHjx/Xxx9/LLvdrptvvlk33HCDy+0+8cQT19weGBiouLg4CjEAAFDpmPruy0WLFmnVqlWO5YyMDM2YMUN79uzRd999p9mzZ2vPnj1uSxIAAKCyM1WUHT161Oklrl988YXy8/P1+uuv691331X79u3173//221JAgAAVHamirLs7GyFhYU5lv/73/+qTZs2io6Olp+fn2644QadPn3abUkCAABUdqaKstDQUKWmpkr6+QWvycnJ6tixo2N7UVGR48vJAQAA8OtMTfRv3769Nm7cqODgYB08eFCGYThN7D916pRq167ttiQBAAAqO1NF2f3336+zZ89q6dKl8vf31wMPPKCoqChJP3/X5K5du3TzzTe7NVEAAIDKzFRRFh4erpdfflm5ubkKDAyUv///b8YwDE2bNk2RkZFuSxIAAKCyK9fLY4ODg0usCwwMVJMmTcrTLAAAQJVjuihLS0vTmjVrdPDgQWVlZempp55SmzZtlJWVpdWrV+uWW25R06ZN3ZkrAABApWXq6ctTp07p6aef1q5duxQVFaXc3FzH05ahoaE6fPiwNm3a5NZEAQAAKjNTRdmyZctUs2ZNzZ07V48//niJ7Z07d9b3339f7uQAAACqClNF2Xfffaff/va3Cg0Nlc1mK7E9MjJS6enp5U4OAACgqjBVlBUVFal69epX3Z6VleX0RCYAAACuzVRR1qxZM+3fv7/UbYWFhdq5c6diYmLKlRgAAEBVYqooGzJkiL755hstWLBAJ0+elCRlZGTowIEDeuWVV3T69GkNHjzYrYkCAABUZqY+Y+zcubMmTJig999/X59++qkk6a233pIkBQUFacKECWrTpo37sgQAAKjkTE/86tOnj2644QYdOHBAKSkpKioqUnR0tDp27KigoCB35ggAAFDplWs2fo0aNZy+iBwAAADmlKkoS0tLM9U4338JAABQNmUqyiZMmGCq8RUrVpjaDwAAoKopU1H26KOPejoPAACAKq1MRVm/fv08nAYAAEDVZuo9Ze+8846Sk5Ovuv3IkSN65513TCcFAABQ1ZgqyrZv365z585ddfv58+e1fft200kBAABUNaaKsl+Tnp6uwMBATzQNAABQKZX5PWX79u3Tvn37HMuffvqpDhw4UCIuNzdXiYmJatGihXsyBAAAqALKXJSdOnVKu3fvdiwnJyfr2LFjTjE2m03Vq1fXb37zG40ePdp9WQIAAFRyZS7KYmNjFRsbK0kaMWKEHn30UfXq1ctjiQEAAFQlpr5miZfCAgAAuJdHJvoDAADANWW6UzZixAjZbDYtW7ZM/v7+GjFixK/uY7PZ9MEHH5Q7QQAAgKqgTEXZ0KFDZbPZ5Ofn57QMAADcr3DsPU7LJ0uJqbbgI6/kInk3n8qsTEXZ8OHDr7kMAACA8jE1p2z16tU6ceLEVbefPHlSq1evNp0UAABAVWOqKFu1atWvFmWrVq0ynRQAAEBV45GnL7Ozs+Xvb+ptGwAAAFVSmSunQ4cO6dChQ47lPXv2KCUlpURcTk6Odu7cqUaNGpUrsXXr1mn58uUaOHCgHnzwQUlSfn6+4uPjtXPnTtntdnXs2FFxcXEKDw8v17EAAAC8rcxF2cGDB53mie3du1d79+4tNbZBgwZ66KGHTCd15MgRbdmyRY0bN3Zav2TJEu3fv1+TJ09WcHCwFi5cqNmzZ+vll182fSwAAAArKHNRNnjwYN1xxx0yDENjx47V2LFjdeONNzrF2Gw2BQYGKjAw0HRCeXl5euutt/TII49ozZo1jvW5ubnatm2bJk6cqHbt2kmSxo8fr0mTJikpKUkxMTGmjwkAAOBtZS7Kfllsvf322woNDVX16tXdntB7772nzp07q0OHDk5F2bFjx1RYWKj27ds71tWvX1+RkZHXLMrsdrvsdrtj2WazKSgoyPFzRSg+TlmP5+l4V1gtd/rqnnhPnkczfLmvvjyuvh7vCqvl7o6+Xm1fb513V/Z39Vjuirfate9Kpmbj16lTx915SJK+/PJL/fDDD/rzn/9cYltGRob8/f1Vs2ZNp/VhYWHKyMi4aptr1651+ti1adOmmjVrlsf6cC3R0dGWivdk21aL92TbvhzvyfNohi/31ZfH1dfjPdm2t+JLeznrlerWrWuZXH6Zj6u5ezq+NFa79hWzzCOSaWlpWrx4sV544YVyffx5pdjYWA0aNMixXFwdp6amqqCgwG3HuRabzabo6GilpKTIMAyvx7vCarnTV/fEe/I8muHLffXlcfX1eFdYLXd39PXs2bOWyeVa+ZQ31p3x3rj2+fv7l/lGkGWKsmPHjikzM1PPPPOMY11RUZG+++47bdq0SVOnTlVBQYFycnKc7pZlZmZe8+nLgIAABQQElLqtov8YGYbh0jE9He8Kq+VOX90T78nzaIYv99WXx9XX411htdzL09df26+iz7urx/JU22WJt9q1r5hlirL27dvrjTfecFr397//XfXq1dPgwYMVGRmpatWqKTExUTfddJMk6cyZM0pLS2OSPwAA8HllKso2bNigTp06qV69eh5LJCgoqMS7zapXr67rrrvOsb5///6Kj49XSEiIgoODtWjRIsXExFCUAQAAn1emomzJkiUKDQ11FGUjRozQ448/rl69enk0uSuNGTNGNptNs2fPVkFBgePlsQAAAL6uTEVZSEjINZ9w9JSXXnrJaTkwMFBxcXEUYgAAoNIpU1HWpk0brVq1SsePH1dwcLAkafv27UpKSrrqPjabTX/4wx/ckyUAAEAlV6aiLC4uTosXL9aBAweUmZkpSTpw4IAOHDhwzf0oygAAAMqmTEVZWFiYJk6c6Fj21pwyAACAysrPzE6PPvooTzwCAAC4kan3lPXr18/x86lTp5Samirp569fatCggVsSAwAAqEpMvzx23759io+P1/nz553WR0VFacyYMerWrVu5kwMAAKgqTBVl+/fv1+zZs1WnTh3dd999jrtjp06d0tatW/XGG2/o2WefVadOndyZKwAAQKVlqij78MMP1bhxY82YMUM1atRwrO/WrZvuuOMOvfjii1q1ahVFGQAAQBmZmuh/4sQJ9e3b16kgK1ajRg3169dPJ06cKHdyAAAAVYWpoiwgIEDZ2dlX3Z6dna2AgADTSQEAAFQ1poqydu3aacOGDaW+0T85OVkbN25U+/bty50cAABAVWFqTtmoUaM0depUTZs2TS1atHB8UfmZM2d05MgRhYWFaeTIkW5NFAAAoDIzVZRFRUXpjTfe0Nq1a/XNN99o586dkn5+T9nAgQM1ZMgQhYWFuTVRAACAysz0e8rCwsL04IMPujEVAACAqsvUnDIAAAC4F0UZAACABVCUAQAAWABFGQAAgAVQlAEAAFiAy0XZ5cuX9cwzz2jz5s2eyAcAAKBKcrkoq169us6fPy+bzeaJfAAAAKokUx9fdurUSf/73//cnQsAAECVZaooGzp0qM6ePau33npL33//vdLT05WdnV3iHwAAAMrG1Bv9n3zySUnSqVOntGPHjqvGrVixwlxWAAAAVYypomzo0KHMKQMAAHAjU0XZ8OHD3Z0HAABAleaW95Tl5uaqqKjIHU0BAABUSaaLsqNHj2rmzJkaNWqUHnroIR06dEiSlJWVpddee00HDx50W5IAAACVnami7PDhw3rxxReVkpKi3r17yzAMx7bQ0FDl5uZqy5YtbksSAACgsjNVlP3rX/9S/fr1NWfOHN13330ltrdt21ZHjhwpd3IAAABVhami7OjRo+rXr58CAgJKfQozIiJCGRkZ5c0NAACgyjBVlFWrVs3pI8srpaenq0aNGqaTAgAAqGpMFWUtW7bU7t27S92Wl5enzz//XG3atClXYgAAAFWJqaJs+PDhOnbsmP785z/r66+/liQdP35cW7du1bPPPqusrCwNHTrUrYkCAABUZqZeHtuyZUs999xzWrBggebNmydJWrp0qSTp+uuv13PPPafGjRu7L0sAAIBKzlRRJknt2rXT3Llz9cMPPyglJUWGYej6669Xs2bNTH8F0+bNm7V582alpqZKkho0aKBhw4apc+fOkqT8/HzFx8dr586dstvt6tixo+Li4hQeHm62GwAAAJZguigr1rRpUzVt2tQduSgiIkL333+/6tatK8MwtH37dr322mt67bXX1LBhQy1ZskT79+/X5MmTFRwcrIULF2r27Nl6+eWX3XJ8AAAAbzFdlNntdm3dulVff/21zp8/L0mKiopS586d1b9/fwUGBrrcZrdu3ZyW77vvPm3evFnJycmqXbu2tm3bpokTJ6pdu3aSpPHjx2vSpElKSkpSTEyM2a4AAAB4nami7KefftIrr7yiM2fOKDw8XNHR0ZJ+nuz/zTffaNOmTZo2bZpq165tOrGioiLt2rVLly9fVkxMjI4dO6bCwkK1b9/eEVO/fn1FRkZSlAEAAJ9nqihbuHChUlNTNWnSJN10001O23bt2qV58+Zp4cKFevrpp11u+8SJE5o6darsdrtq1KihKVOmqEGDBjp+/Lj8/f1Vs2ZNp/iwsLBrvqjWbrfLbrc7lm02m4KCghw/V4Ti45T1eJ6Od4XVcqev7on35Hk0w5f76svj6uvxrrBa7u7o69X29dZ5d2V/V4/lrnirXfuuZKooS0xM1F133VWiIJOkHj166IcfftDGjRtNJVSvXj29/vrrys3N1e7duzVv3jzNmDHDVFuStHbtWq1evdqx3LRpU82aNUt16tQx3aZZxXcUrRLvybatFu/Jtn053pPn0Qxf7qsvj6uvx3uybW/FnyzDvnXr1rVMLr/Mx9XcPR1fGqtd+4qZKsqCgoIUFhZ21e3h4eGOu1EuJ+Tv7zhZzZo109GjR7Vhwwb17NlTBQUFysnJcbpblpmZec2nL2NjYzVo0CDHcnF1nJqaqoKCAlM5uspmsyk6OtrxlKq3411htdzpq3viPXkezfDlvvryuPp6vCuslrs7+nr27FnL5HKtfMob6854b1z7/P39y3wjyFRR1q9fP33++ee69dZbVb16dadteXl5+uyzz9S/f38zTZdQVFQku92uZs2aqVq1akpMTHTcoTtz5ozS0tKuOZ8sICBAAQEBpW6r6D9GhmG4dExPx7vCarnTV/fEe/I8muHLffXlcfX1eFdYLffy9PXX9qvo8+7qsTzVdlnirXbtK1amomzPnj1Oy02bNtXXX3+tJ554Qn379nXc2UpJSdH27dsVEhKiRo0auZzM8uXL1alTJ0VGRiovL087duzQoUOHNHXqVAUHB6t///6Kj49XSEiIgoODtWjRIsXExDDJHwAA+LwyFWVz5sy56ra1a9eWWJeenq65c+eqZ8+eLiWTmZmpefPm6cKFCwoODlbjxo01depUdejQQZI0ZswY2Ww2zZ49WwUFBY6XxwIAgLIpHHtPiXWlzdOqtuAjzycDJ2UqyqZPn+7pPCRJjz766DW3BwYGKi4ujkIMAABUOmUqytq0aePpPAAAAKo0P28nAAAAgHJ8zdL333+vbdu26fz588rJySnxFIPNZtPrr79e7gQBAACqAlNF2ccff6ylS5cqMDBQ9erVU0hIiLvzAgAAqFJMFWUfffSRWrdurWeeeUbBwcHuzgkAAKDKMTWn7PLly+rVqxcFGQAAgJuYKsratm2rEydOuDsXAACAKstUUfbQQw/p22+/1UcffaTs7Gx35wQAAFDlmJpTFhkZqQEDBmjp0qX65z//qcDAQPn5lazvlixZUu4EAQAAqgJTRdmKFSu0Zs0aRUREqHnz5swtAwAAKCdTRdmWLVvUpUsXPfXUU6XeIQMAAIBrTFVUBQUF6tKlCwUZAACAm5iqqrp06aLvvvvO3bkAAABUWaaKst///vc6ffq03nvvPR07dkxZWVnKzs4u8Q8AAABlY2pO2RNPPCFJOn78uLZs2XLVuBUrVphKCgAAoKoxVZQNHTpUNpvN3bkAAABUWaaKsuHDh7s7DwAAgCqNxycBAAAswNSdstWrV5cpbtiwYWaaBwAAqHJMFWWrVq0qUxxFGQAAQNmY/pqlKxUVFSktLU2bNm3Sd999p+eff77cyQEAAFQVbptT5ufnp6ioKI0ePVp169bVokWL3NU0AABApeeRif6/+c1v9PXXX3uiaQAAgErJI0XZ0aNHeY8ZAACAC0zNKdu+fXup63NycvTdd99p79696t+/f7kSAwAAqEpMFWXvvPPOVbddd911Gjx4ME9eAgAAuMBUUfb222+XWGez2VSzZk0FBQWVOykAAICqxlRRVqdOHXfnAQAAUKWZKsp+KS8vT9nZ2aVui4yMLG/zAAAAVYKpoiw/P1+rV6/Wtm3bdPHixavGlfaSWQAAAJRkqih77733tH37dnXv3l2/+c1vVLNmTXfnBQAAUKWYKsr27t2rW2+9VX/84x/dnQ8AABWucOw9JdadLCWu2oKPPNa+2bZReZh6eazNZlPTpk3dnQsAAECVZaoo69atmxITE92dCwAAQJVlqigbOnSozp07p/nz5+vYsWPKyspSdnZ2iX8AAAAoG1NzyiZOnChJOn78uLZt23bVOJ6+BAAAKBtTRdnQoUM98oXja9eu1d69e3X69GkFBgYqJiZGo0aNUr169Rwx+fn5io+P186dO2W329WxY0fFxcUpPDzc7fkAAABUFFNF2fDhw92dhyTp0KFDuv3229W8eXMVFhbqX//6l1555RXNmTNHNWrUkCQtWbJE+/fv1+TJkxUcHKyFCxdq9uzZevnllz2SEwAAQEUwNafMU6ZOnap+/fqpYcOGatKkiSZMmKC0tDQdO3ZMkpSbm6tt27ZpzJgxateunZo1a6bx48fr8OHDSkpK8nL2AAAA5pX7a5Y8KTc3V5IUEhIiSTp27JgKCwvVvn17R0z9+vUVGRmppKQkxcTElGjDbrfLbrc7lm02m+NL0z3xEWxpio9T1uN5Ot4VVsudvron3pPn0Qxf7qsvj6uvx7vCXW1fbX93tO/qvp7MxZ35VHTb14q32rXvSpYtyoqKirR48WK1atVKjRo1kiRlZGTI39+/xDcIhIWFKSMjo9R21q5dq9WrVzuWmzZtqlmzZnnlS9Wjo6MtFe/Jtq0W78m2fTnek+fRDF/uqy+Pq6/Hu6Pt0l4UW5q6det6rP1ftu1qvLtz+WX7nogvT189eW68zbJF2cKFC3Xy5En96U9/Klc7sbGxGjRokGO5uDpOTU1VQUFBudouK5vNpujoaKWkpMgwDK/Hu8JqudNX98R78jya4ct99eVx9fV4V7ir7bNnz3qs/au17Y1c3JlPRbd9rXhvXPv8/f3LfCPIkkXZwoULtX//fs2YMUO1a9d2rA8PD1dBQYFycnKc7pZlZmZe9enLgIAABQQElLqtov8YGYbh0jE9He8Kq+VOX90T78nzaIYv99WXx9XX411R3rZ/bd/ytO/qfp7MxRP5VFTbZYm32rWvmKUm+huGoYULF2rv3r168cUXFRUV5bS9WbNmqlatmtO3CZw5c0ZpaWmlzicDAADwFZa6U7Zw4ULt2LFDTz/9tIKCghzzxIKDgxUYGKjg4GD1799f8fHxCgkJUXBwsBYtWqSYmBiKMgAA4NMsVZRt3rxZkvTSSy85rR8/frz69esnSRozZoxsNptmz56tgoICx8tjAQAAfJmlirKVK1f+akxgYKDi4uIoxAAAQKViqTllAAAAVRVFGQAAgAVQlAEAAFgARRkAAIAFUJQBAABYAEUZAACABVCUAQAAWABFGQAAgAVQlAEAAFgARRkAAIAFUJQBAABYAEUZAACABVjqC8kBAADcpXDsPSXWnSwlrtqCjzyfTBlwpwwAAMACKMoAAAAsgKIMAADAAphTBgCAi3xtrhJ8A3fKAAAALICiDAAAwAIoygAAACyAogwAAMACKMoAAAAsgKIMAADAAijKAAAALICiDAAAwAIoygAAACyAN/oDAODjrvyGAb5dwDdxpwwAAMACKMoAAAAsgKIMAADAAijKAAAALICiDAAAwAIoygAAACyAogwAAMACKMoAAAAswFIvjz106JA++ugj/fDDD7pw4YKmTJmiG264wbHdMAytXLlSW7duVU5Ojlq3bq24uDjVrVvXi1kDAACUn6XulF2+fFlNmjTRww8/XOr29evXa+PGjRo7dqxeffVVVa9eXTNnzlR+fn4FZwoAAOBelirKOnfurHvvvdfp7lgxwzC0YcMG/e53v1P37t3VuHFjPfbYY7pw4YL27dvnhWwBAADcx1JF2bWcP39eGRkZ6tChg2NdcHCwWrRooaSkJC9mBgAAUH6WmlN2LRkZGZKksLAwp/VhYWGObaWx2+2y2+2OZZvNpqCgIMfPFaH4OGU9nqfjXWG13Omre+I9eR7N8OW++vK4+nq8K9zVtqv7uxLvybatFm+lXMzEe4rPFGVmrV27VqtXr3YsN23aVLNmzVKdOnUqPJfo6GhLxXuybavFe7JtX4735Hk0w5f76svj6uvx7mj7ZBn3L36wzBPxv3xozZPx9PXa7XuTzxRl4eHhkqTMzEzVqlXLsT4zM1NNmjS56n6xsbEaNGiQY7m4Gk5NTVVBQYFHcr2SzWZTdHS0UlJSZBiG1+NdYbXc6at74j15Hs3w5b768rj6erwr3NX22bNnPRbvybatFm+lXMzEu8Lf37/MN4J8piiLiopSeHi4EhMTHUVYbm6ujhw5ottuu+2q+wUEBCggIKDUbRX9x8gwDJeO6el4V1gtd/rqnnhPnkczfLmvvjyuvh7vivK27eq+rvbbKrl4Ot5KuZiJ9xRLFWV5eXlKSUlxLJ8/f17Hjx9XSEiIIiMjNXDgQK1Zs0Z169ZVVFSUPvjgA9WqVUvdu3f3YtYAAADlZ6mi7OjRo5oxY4ZjOT4+XpLUt29fTZgwQYMHD9bly5c1f/585ebmqnXr1nr++ecVGBjorZQBAADcwlJFWdu2bbVy5cqrbrfZbBoxYoRGjBhRgVkBAAB4ns+8pwwAAKAyoygDAACwAIoyAAAAC7DUnDIAANylcOw9TsulvUi02oKPKiYZoAy4UwYAAGABFGUAAAAWQFEGAABgAcwpAwAPunJek8TcJgCl404ZAACABVCUAQAAWABFGQAAgAVQlAEAAFgARRkAAIAFUJQBAABYAEUZAACABVCUAQAAWABFGQAAgAXwRn8AFY633F9dWc7NL8/LlfG+dB5d7StQ2XGnDAAAwAIoygAAACyAogwAAMACmFMGAFWIL89BAyo77pQBAABYAEUZAACABVCUAQAAWABFGQAAgAUw0R9ApcNkdu/gpcBA+XCnDAAAwAIoygAAACyAogwAAMACKMoAAAAsgIn+HlKVJhp7sq9WmzhclcYVAFCxuFMGAABgARRlAAAAFkBRBgAAYAE+Oads06ZN+ve//62MjAw1btxYDz30kFq0aOHttExzdd6Up+dZMUfMPbl4elzLEv/L3H15PpyrffV2LpJ1z6UrqlJfASvwuTtlO3fuVHx8vIYNG6ZZs2apcePGmjlzpjIzM72dGgAAgGk+V5R9/PHHuvXWW3XLLbeoQYMGGjt2rAIDA/XZZ595OzUAAADTfKooKygo0LFjx9S+fXvHOj8/P7Vv315JSUlezAwAAKB8fGpOWVZWloqKihQeHu60Pjw8XGfOnCl1H7vdLrvd7li22WwKCgqSv79nu+7XvNWvxlQLCChzbHniXeVK7q7G09eKjTebu6f5cl99fVx9+XfYVb7c16o0rlbqqye4Um/YDMMwPJaJm6Wnp2vcuHF65ZVXFBMT41i/bNkyHTp0SK+++mqJfVauXKnVq1c7lm+++WZNnDixQvIFAAAoK5/6+DI0NFR+fn7KyMhwWp+RkVHi7lmx2NhYLV682PFv7NixTnfOKsKlS5f0zDPP6NKlS5aId4XVcqev7on35Hk0w5f76svj6uvxrrBa7oyr9XPxBp8qyvz9/dWsWTN9++23jnVFRUX69ttvne6c/VJAQICCg4Od/gVU0Ec0xQzD0A8//KCy3pT0dLwrrJY7fXVPvCfPoxm+3FdfHldfj3eF1XJnXK2fizf41JwySRo0aJDmzZunZs2aqUWLFtqwYYMuX76sfv36eTs1AAAA03yuKOvZs6eysrK0cuVKZWRkqEmTJnr++eev+vElAACAL/C5okyS7rjjDt1xxx3eTqPMAgICNGzYsDJ/bOrpeFdYLXf66p54T55HM3y5r748rr4e7wqr5c64Wj8Xb/Cppy8BAAAqK5+a6A8AAFBZUZQBAABYAEUZAACABVCUAQAAWABFGQAAlUxeXp63U4AJPvlKDFhbXl6eatSo4e00AKDMsrOztXbtWv33v/9Vbm6uJKlmzZrq0qWLhgwZouuuu67Mbf31r3/VpEmTHMt5eXn66KOPlJ6ero4dO6pHjx6ObYsWLdJDDz3ktH9WVpbWrFkjm82moUOHasOGDdqzZ4/q16+vBx98UBEREU7xly9fdlo2DENPPvmk/vrXv8owDFWvXt1p+65duxw5ZGZmauHChTp27JgaNGighx9+WHXq1HGK37Bhg3r16qXQ0FCdPXtW77zzjs6ePas6derokUceUZMmTRyxzzzzjLp3766ePXuqXr16v3qujh07pn/961+qVauWhg8frr///e86fvy4IiMj9cgjj6hZs2ZO8ZcuXdK6deu0d+9epaenq0aNGqpdu7YGDBig/v37l2jfneNaESjKPKCoqEjbtm3T3r17deHCBfn5+Sk6OloDBgxQ+/btS8R78mJghisXBCtdDMxw5YLg6sWgoKBAW7du1VdffaULFy5IkmrVqqWuXbvq1ltvdek9OfHx8Ro9erTTuqKiIn3xxRdKT09Xhw4d1KJFC8e2tWvXKjY21ik+Pz9fn376qSTpt7/9rb788kvHuA4bNqxCC+ndu3erXbt2CgkJUWZmppYsWaIff/xR9evX1+jRoxUZGekU/8Ybb6h79+7q3r27goODf7X98+fPa/369apVq5YGDRqkxYsXKzk5WXXr1tUDDzyg66+/3nTuVhpXd4zpU089pddff73Ubd9//71at24t6ef/1letWqWjR4+qYcOGGjFihGrWrOkU7+lxdYWr1+E5c+boxhtv1KuvvurIJTc3V1988YXmzJmj6dOnO8WfOnWq1OMahqHvv//ead3bb7+t+vXrq0uXLtq+fbsSEhL0xBNPKDAwUIcPHy7RxltvvaWuXbvq8uXLmj59uvr376/p06dr7969WrBggZ555hmn+OJza7PZHF8flJ6erkmTJslms+ntt992il+3bp3jOrxkyRK1bt1aEyZM0N69e/WPf/xDU6dOdYrfunWrBg4cKOnnInLYsGHq2LGjjhw5ogULFmjmzJmO2JycHBUUFOi1115TQECAevbsqZ49e171v7kFCxZoxIgRys3N1QsvvKBHHnlEnTt3VlJSkt577z29+uqrTvFvvvmmevfurTvvvFO7d+9WVlaWBgwYoDVr1uj06dN64IEHnOJdHVdvoyjzgLffflsNGzbUsGHDtHfvXgUGBqpDhw5av369jh8/rrvvvtsp3pMXAzNcuSBY6WJghisXBFcvBnPnzlVUVJRGjhzpKGTT09OVkJCguXPnasqUKU7xVxa4xQzD0K5du0r88f7HP/6h/Px8NW/eXAsXLlRMTIzGjBkjPz8/7d69u0RRNm/ePNWuXVt2u12vvvqqmjZtqqFDh+qrr77SP/7xD/3f//2f6fPoqpUrV+qmm26SJL333nvq2LGjHnzwQR08eFDz5s0r8Tt/5MgRVa9eXUuXLlVMTIx69uypbt26XbXoePvtt9W7d2/l5ubqueee05133qn7779fiYmJ+vvf/66XXnrJdO5WGldXx/Thhx+WzWZzHF/6+Y9o8fr33nvPKf7999/XrFmzJElLly5VYGCgHn74Ye3bt0/z58/X5MmTneI9Pa6ucPU6/NNPP+n22293WhccHKw77rhDGzZsKNH+lClT9Jvf/KbUY2dnZzstnzt3zvF7ccMNN2jDhg2aPn26nnrqqVL3z8zMdLwgffPmzbrrrrskSQMGDNDmzZtLxP/hD3/Q119/reHDh6t58+aSpAkTJmjevHmltv9LJ0+edPye9O7dW//+979LxBQWFqqoqEh+fn7KyclRx44dJUktWrQo8TFpzZo1de+99+ree+/V8ePHtWvXLs2cOVM1a9ZUjx49dM8995Rou1OnTpJ+/h3r3LmzJCkmJkZ2u71ELunp6erVq5ekn18k/+yzz2r48OGKi4vTE088UeI67Oq4ehtFmQecOHHC8UseExOj5557TsOHD1fLli315JNPVujFwAxXLghWuhiY4coFwdWLwYkTJ/Tkk086rQsNDVWTJk00ceLEErmMHj1aderUcfqi3OJiNyMjo0T80aNHHXc47rzzTsXHx2vWrFmaNGlSqV+2e+bMGce2cePGOf5AtmjR4qp/HDylsLDQ8XNqaqoGDBggSerRo4fWrFlTIj40NFSPP/64CgoK9PXXX2vXrl1avHix2rRpox49ejh9HCT9/HHRb3/7W0nSpk2bdNttt0mSbr75Zq1fv75cuVtpXF0d0759+yozM1P333+/ateuLans/70ePnxYr732mmw2mxo0aFBq+54eV1e4eh1u2LChli1bpj59+jgV29u3b1fDhg1LtF+/fn2NHTu21I/oHn30UaflgoIC2e12x13UgQMHKioqSn/605906dKlEvv/cpyLf4+L/fIcF7vjjjvUs2dPLV++XFu2bNF9993nKL5Lk56ermXLlskwDGVnZ6ugoED+/v5Xbb9///567bXXFBsbq86dO2vZsmW68cYbdfDgwRLn5pe5N2nSRE2aNNF9992nY8eOadeuXSXaDg4O1qeffqq8vDzVrFlTmzdvVo8ePZSYmFjikxZJCgkJ0e7du9W2bVvt2bPH6ZOb0q57ro6rt1GUeUD16tV19OhRNW/eXAcOHHDc/apWrVqp8Z68GJjhygXBShcDM1y5ILh6MYiIiNDmzZvVu3dvBQUFSfr5I9Dt27erVq1aJeKjo6P1wgsvlPgIVyp9XO12u6Ng9fPz04MPPqj//Oc/mj59unJycq7aZ5vN5rib4S3du3fXggULNHToUHXt2lWbN2/WDTfcoIMHD5b6PbbFv1P+/v6Oj7vy8/O1f/9+7d69u8Qf74CAAB04cECXLl2SzWbTN998o06dOik5Ofmav59lYcVxLeuYjh49WqdOndKCBQvUokULDR48+JrnIysrS//5z39kGEaJ/wkq7Xfe0+PqClevwxMnTtTWrVu1dOlSpaenS/p5rLt06aIRI0aUiB85cqT8/Ep/Vu6Pf/yj0/Ktt96q5ORktWnTxrGuW7duioiI0D//+c8S+/ft29cxN3fIkCGO9SkpKWrZsmWpxwwNDdW4ceOUlJSkN99885r/0zpy5EjHzyNGjFBeXp5CQkKUkZGhrl27loi/55571KRJE33yySdKSUlRYWGhfvzxR3Xr1k2PPfaYU2zbtm1LPWazZs1KzA+TpMcff1zr16+XzWbTCy+8oB07duill15yTCG50vjx4xUfH6+VK1eqcePGiouLkyRdvHix1HG6clxtNptq1ap11XH1Nr5myQOOHz+ud999Vz/99JOuv/56jRs3Tg0aNFBWVpa++OILDRo0yCnebrdr69at+u9//1vil+bWW29VYGCgU/z+/ftVr149RUdHlzj2119/7bjbY9bHH3+sAQMGlPgIISUlRevWrdO4ceNK3S85OVnLly93XPRL8/nnnzstd+vWzXEx2LBhg+6///4S+yQmJmrLli2Oi0Ht2rXVrVs39e/f31HQmfXTTz9p/fr18vPz05AhQ7Rjxw599tlnioyM1KhRo5wKv9TUVMXHx+v06dNq3LixHnjgAUVEROjixYtKTExUz549ndq+ePGi1q1bp6+++kqXLl2SYRhOcwVDQ0Od4rdt26aWLVuWWmx++umnjrsOxZYuXapOnTqVmB+zf/9+LV68WH/729+c1sfHx2v48OGljuv777+v5557ruwnrpwMw9C2bdv06aef6qefflJeXp5q166trl27asiQIQoJCXGKf/PNN/XEE0+Uuf0ff/xRH3zwgWw2m0aNGqX//Oc/2rlzp0JCQjR27FinP46u+uW4ljYHtCLHtTxjunPnTm3cuFHnzp3TP/7xj1JjVq1a5bR8++23KzQ0VBkZGVq2bFmJP8iGYeizzz7Tli1bPDKurii+DqelpSk6OvpXr8OViWEYunTpktvn6cHzKMrgVlwMAN9x+fJlnTt3To0aNfJ2Kl5X/ABH8f8cS9d+gMOVBz5cfTikouJd7WtZ4q2Uy68p7UEbb6Mo84DDhw+rYcOGCg4OVl5entatW6fjx4+rQYMGio2NLfHE0pIlS3TDDTdcdZ7YlVyNd9WSJUvUvXv3Mt1NcCW2ON5qfS1r+67mkpOToy1btigiIkK9e/fW2rVrlZSUpHr16ik2NrbEU7XF8bVq1VKfPn28Hu9JVjs3rjpw4IDjKdzip/r69+9/1VcAeDLeSrlURLwrXGl79uzZioqKUu/evUs8wPHLifpm4j3ZttXirZSLdO0HbSZNmqS///3vpW73FuaUecD8+fP1xhtvSPr56aXw8HD9/ve/dzyB9PTTTzvFJyQk6OjRo0pNTdWNN96onj17KiYm5qrtuxrvKlfa93TuvtzXN998U02bNlVaWpq2bt2qli1b6ve//70SExM1b948Pfvss1eN37Ztm2JiYrwa70kVfW5+rX1XxMfHKy8vT23bttVXX32l2rVrq3Hjxpo3b57uvPNOx8MgFRFvpVwqIt6T4+TqAxyuxHuybavFWykXyfUHbbzOgNtNnDjR8fPTTz/ttG3KlCkl4p966inDMAzjp59+Mj755BPjhRdeMMaPH2/Ex8cbycnJ5Y53lSvtezr3ytBXwzCMRx555KrbrBrvSb7c18mTJzt+LiwsNJ5//nnDMAzj0qVLxqRJkyo03kq5VES8K1xt+6WXXjL+85//GLm5uY51ubm5xsaNG43p06eXK96TbVst3kq5GIZh/N///Z9x/vz5EusNwzDGjRtX6npv4k6ZB8TExGjdunUaNGiQWrVq5XjyKykpqdT37xQ/gRQREaGBAwdq4MCBSktL0+7du/X++++XeBeXq/GucqV9T+fu631NSUlRbm6u7Ha7fvzxRzVu3FipqamlPmlqtXhP8uW+VqtWTWlpaYqMjNTp06cdT+DVqFGj1CcSPRlvpVwqIt4VrrY9efJkrVu3Ts8++2yJBziufB+bq/GebNtq8VbKRZIGDx581SdRhw4dWup6b2JOmQfY7XZ9+OGHSkhIULVq1XTu3DkFBwerQ4cOGjlypKKiopzip0+frhkzZpS5fVfjXeVK+57O3Zf7mpiYqCVLlshmsykuLk6ffPKJTp8+raysLMXFxenGG2+0dLwn+XJfDxw4oPnz56tGjRq6fPmyHn/8cbVq1UpZWVlau3atxowZU2HxVsqlIuI9OU6AFVCUeVhubq4KCwst9/1a8I6srCyFhIRc9f1GVo/3JF/qq2EYunjxYonXX3gj3kq5VES8K1xte//+/SWe6uvWrZu6dOlS7nhPtm21eCvlYibemyjKPOTs2bNOT/3UrVtXN998c4knLysq3pP501fXcu/Vq9dVXxlitXhP8uW+Wul33kq5VES8K1xp+7333lNWVlapT/Vdd911Gjt2rOl4T7ZttXgr5WIm3tu8/7/HldC///1vLV68WIZh6Pjx4yosLFR2drZeeuklHThwoMLjPZk/fXU99+nTp/tEvCf5cl+t9DtvpVwqIt4VrradmJioyZMnq3v37mrevLmaN2+u7t27a/LkyUpMTCxXvCfbtlq8lXIxE+91bn90AMbkyZONwsJCwzAM4/Lly8aLL75oGIZhXLhwodSnLz0d78n86av3+mq1c+kKX+6rlXK3Ui4VEe8KV9t+9tlnjf3795dY/9///td45plnyhXvybatFm+lXMzEextPX3pIXl6egoODlZOTo/z8fElSeHj4VZ/88nS8J/Onr97rq9XOpSt8ua9Wyt1KuVREvCtcafuJJ57QP//5T7377ru67rrrZBiGcnJy1LJly1K/CsqVeE+2bbV4K+ViJt7bKMo8YMiQIXrmmWdUv359nTx5Un/4wx8k/TzRuEGDBhUe78n86av3+mq1c+kKX+6rlXK3Ui4VEe8KV9u+/vrr9dhjj+nixYuO7xsufkDr5MmT5Yr3ZNtWi7dSLmbivc5Ld+gqvaysLCM5Odm4ePGi0/qkpCSvxLvKlfbpq/f6arVz6Qpf7quVcrdSLhUR7wpX2t61a5fxyCOPGE899ZTx5JNPOsVc+RJwV+M92bbV4q2Ui5l4b6Moq2CuvkHY0/GucqV9+lp54z3Jl/tqpdytlEtFxJe37SlTphgXLlwwDMMwjh49akyePNlISEgwDKP0b4FwJd6TbVst3kq5mIn3Nj6+9IA5c+ZcdVt2dnaFx7vKlfbp69Vz8fV4T/LlvlopdyvlUhHxrnC17cLCQoWHh0uSmjVrphkzZuj1119XSkqK49s8zMZ7sm2rxVspFzPx3kZR5gGJiYl6/PHHS3ylkmEY+u677yo83pP501fv9dVq59IVvtxXK+VupVwqIt4VrrYdFhbm+DouSQoJCdG0adP09ttv68cffyxXvCfbtlq8lXIxE+9tFGUe0LZtW9WoUUNt2rQpsa1Ro0YVHu8qV9qnr1fPxdfjPcmX+2ql3K2US0XEu8LVth977DFVq1bNaZ2/v7+eeOIJff/99+WK92TbVou3Ui5m4r2NN/oDAABYAG/0BwAAsACKMgAAAAugKAMAALAAijIAAAALoCgDAACwAIoyAAAAC6AoAwAAsACKMgCoQPn5+SoqKvJ2GgAsiDf6A6j0vv32W/3pT3/SlClTdMMNNzht27Fjh/72t7/plVdeUUxMjE6fPq0PPvhA3377rfLz89WwYUMNGzZM3bp1c+yTnZ2tNWvW6H//+5/Onz8vPz8/tWrVSvfff7+aNGniiDt48KBmzJihiRMn6uTJk/rss8+UkZGhRYsWqWbNmhXVfQA+gjtlACq9tm3bqnbt2kpISCixLSEhQddff71iYmJ08uRJTZ06VadPn9aQIUP0wAMPqHr16nr99de1d+9exz7nzp3Tvn371LVrV40ZM0Z33323Tpw4oZdeeknp6ekljvHhhx9q//79uvvuu3XffffJ35//HwZQElcGAJWezWZT79699cknnyg3N1fBwcGSpKysLB04cECxsbGSpMWLFysyMlJ//vOfFRAQIEm6/fbb9eKLL+qf//yn4y5bo0aNNHfuXPn5/f//r+3Tp48mTZqkbdu2adiwYU7Ht9vt+stf/qLAwMCK6C4AH8WdMgBVQt++fWW327V7927Hup07d6qwsFB9+vRRdna2vv32W/Xo0UOXLl1SVlaWsrKydPHiRXXs2FFnz5513AULCAhwFGRFRUW6ePGiatSooXr16umHH34o9dgUZAB+DXfKAFQJ9evXV/PmzZWQkKD+/ftL+vmjy5YtWyo6OlpHjhyRYRhasWKFVqxYUWobmZmZioiIUFFRkTZs2KDNmzfr/PnzThP3Q0JCSuwXFRXlmU4BqFQoygBUGX379tX777+vn376SXa7XcnJyXrooYckyVFY3X333erYsWOp+0dHR0uS1q5dqxUrVuiWW27RiBEjFBISIpvNpiVLlsgwjBL7cZcMQFlQlAGoMnr27KklS5boyy+/VH5+vqpVq6aePXtKkq6//npJUrVq1dShQ4drtrN79261bdtWjz76qNP6nJwcXXfddZ5JHkClx5wyAFVGaGioOnfurISEBCUkJKhTp04KDQ2VJIWFhalt27b69NNPdeHChRL7ZmVlOX7+5QT/Yrt27Sr1yUsAKCvulAGoUvr06aM5c+ZIkkaMGOG07eGHH9a0adM0ZcoU3XrrrYqKilJmZqaSkpKUnp6u119/XZLUtWtXrV69Wu+8845iYmJ04sQJ7dixw3G3DQDMoCgDUKV069ZNNWvWlGEYTi+ElaQGDRroL3/5i1atWqXPP/9cFy9eVFhYmJo0aaKhQ4c64mJjY5WXl6cvv/xSO3fuVNOmTfXss89q+fLlFd0dAJWIzShtVioAVFKFhYV65JFH1LVr1xJzwgDAm5hTBqBK2bdvn7KystS3b19vpwIATvj4EkCVkJycrB9//FEffvihmjZtqjZt2ng7JQBwQlEGoErYvHmzEhIS1KRJE40fP97b6QBACcwpAwAAsADmlAEAAFgARRkAAIAFUJQBAABYAEUZAACABVCUAQAAWABFGQAAgAVQlAEAAFgARRkAAIAFUJQBAABYwP8DkioF6iDDgZ4AAAAASUVORK5CYII=",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Create a barplot plot with y label as \"number of titles played\" and x -axis year\n",
+ "nbr_titles_played_by_year = df_final.groupby('year').title.nunique()\n",
+ "\n",
+ "plt.style.use('ggplot')\n",
+ "\n",
+ "# Set the figure size\n",
+ "plt.figure(figsize=(7, 5))\n",
+ "#from the previous cell, we could see there is an abnormal year 0, which seems to be\n",
+ "#a default, so we're dropping that year\n",
+ "plt.bar(x=nbr_titles_played_by_year.index[1:], height=nbr_titles_played_by_year[1:])\n",
+ "\n",
+ "# Set the x label of the plot\n",
+ "plt.xlabel('year')\n",
+ "\n",
+ "# Set the y label of the plot\n",
+ "plt.ylabel('number of titles played')\n",
+ "\n",
+ "# Setting all the ticks\n",
+ "plt.xticks(ticks=nbr_titles_played_by_year.index[1:], rotation=90, size=7)\n",
+ "\n",
+ "# Show the plot\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "VUcXc7ZYfaGl"
+ },
+ "source": [
+ "#### **The number of titles played can be segmented into 3 groups.** The groups are created below by dividing the year colonne into 3 segments:\n",
+ "\n",
+ "* **[1969 - 1983]** : in this segment, the **number of titles played is very low, less or equal to 2**.\n",
+ "* **[1986 - 1997]** : in this segment, the **number of titles played is less or equal to 7**, 1991 having 7.\n",
+ "* **[1999 - 2010]** : in this segment, the **number of titles played vary beetween 8 and 79**, 1999 having the lowest and 2007 having the maximum.\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "RtAjyDMioHCp"
+ },
+ "source": [
+ "**Think About It:** What other insights can be drawn using exploratory data analysis?\n",
+ " - We can also **visualize the distribution of the play counts:**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 471
+ },
+ "id": "FP5l9tlUpXhb",
+ "outputId": "4e3c8f9d-0a77-490e-ebbf-a1da343a6cc0"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 174,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAG1CAYAAAAfhDVuAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAtmUlEQVR4nO3de3QUZZ7/8U+33THhEppIgMRILoSgOEEQxQGJBtCAbHTAzSCguypjdBTXRXfEEccRf4qeiMvgCuzZWbyQXW4xY5AAhiA3uSkqKER0uERFDDGJ0ETAxDTdvz+QXtskmqQ76ebJ+3UOx66nnqr6dpdHPzz1VJXF4/F4BAAAYChrsAsAAABoTYQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0W7ALCBXHjh2Ty+UKdhkAAKAJbDabunbt2rS+rVzLOcPlcqmuri7YZQAAgADjMhYAADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0WzBLuBccmn6c8Eu4Zz28cZpwS4BANAOMbIDAACMRtgBAABGI+wAAACjEXYAAIDRQm6CclFRkQoLC+V0OhUfH6/JkycrOTm50f4nT57UkiVLtGPHDp04cULR0dG6/fbbdfnll7dh1QAAIFSFVNjZtm2bcnNzlZ2drT59+mjVqlWaOXOm5syZoy5dutTr73K59PTTTysyMlIPPfSQoqKiVFVVpQ4dOgShegAAEIpCKuysXLlSI0eO1PDhwyVJ2dnZ2rlzpzZs2KCxY8fW679+/XqdOHFCTz31lGy2M1+le/fubVkyAAAIcSETdlwul0pLS31CjdVqVWpqqvbt29fgNh988IH69Omjl156Se+//74iIyN19dVXa+zYsbJaG56OVFdXp7q6Ou+yxWJRRESE9zNaD78vACAYQibsVFdXy+12y+Fw+LQ7HA6VlZU1uM3XX3+tyspKDRs2TI8++qjKy8u1YMECnT59Wr/97W8b3KagoED5+fne5cTEROXk5Cg6Ojpg3wUNi4mJCXYJAIB2KGTCTkt4PB5FRkbqnnvukdVqVVJSko4ePaoVK1Y0GnbGjRunzMxM7/LZ0YbKykq5XK42qbu9OnLkSLBLAAAYwmazNXmgImTCTmRkpKxWq5xOp0+70+msN9pzlsPhkM1m87lkdeGFF8rpdMrlcnnn8fyY3W6X3W5vcH8ej6fF9eOX8fsCAIIhZJ6zY7PZlJSUpJKSEm+b2+1WSUmJUlJSGtymb9++Ki8vl9vt9rYdOXJEXbt2bTDoAACA9idkwo4kZWZmat26ddq4caMOHz6sBQsWqLa2Vunp6ZKkuXPnavHixd7+GRkZOnHihF599VWVlZVp586dKigo0KhRo4L0DQAAQKgJqeGPoUOHqrq6Wnl5eXI6nUpISND06dO9l7Gqqqp87ujp1q2bHnvsMS1cuFAPP/ywoqKidMMNNzR4mzoAAGifLB4mUkg6M0H5x7ekN+TS9OfaqBozfbxxWrBLAAAYwm63N3mCckhdxgIAAAg0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACj2YJdQEOKiopUWFgop9Op+Ph4TZ48WcnJyQ323bhxo+bPn+/TZrfbtWjRorYoFQAAhLiQCzvbtm1Tbm6usrOz1adPH61atUozZ87UnDlz1KVLlwa3iYiI0AsvvNDGlQIAgHNByF3GWrlypUaOHKnhw4crLi5O2dnZCgsL04YNGxrdxmKxyOFw+PwBAACQQmxkx+VyqbS0VGPHjvW2Wa1Wpaamat++fY1uV1NTo/vuu08ej0eJiYmaOHGiLrroogb71tXVqa6uzrtssVgUERHh/YzWw+8LAAiGkAo71dXVcrvd9UZmHA6HysrKGtwmNjZW9957r+Lj43Xq1CmtWLFCf/rTnzR79mxdcMEF9foXFBQoPz/fu5yYmKicnBxFR0cH9LugvpiYmGCXAABoh0Iq7LRESkqKUlJSfJYffPBBrV27VhMmTKjXf9y4ccrMzPQunx1tqKyslMvlav2C27EjR44EuwQAgCFsNluTBypCKuxERkbKarXK6XT6tDudzibPw7HZbEpMTFR5eXmD6+12u+x2e4PrPB5Pc8pFM/H7AgCCIaQmKNtsNiUlJamkpMTb5na7VVJS4jN683PcbrcOHTqkrl27tlaZAADgHBJSIzuSlJmZqXnz5ikpKUnJyclavXq1amtrlZ6eLkmaO3euoqKiNGnSJElSfn6++vTpo549e+rkyZNasWKFKisrNXLkyCB+CwAAECpCLuwMHTpU1dXVysvLk9PpVEJCgqZPn+69jFVVVeVzV8+JEyf0X//1X3I6nerYsaOSkpL09NNPKy4uLkjfAAAAhBKLh4kUks5MUP7xLekNuTT9uTaqxkwfb5wW7BIAAIaw2+1NnqAcUnN2AAAAAo2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGi2YBfQkKKiIhUWFsrpdCo+Pl6TJ09WcnLyL263detWvfDCC7riiis0bdq0NqgUAACEupAb2dm2bZtyc3OVlZWlnJwcxcfHa+bMmTp+/PjPbldRUaH/+Z//0SWXXNJGlQIAgHNByIWdlStXauTIkRo+fLji4uKUnZ2tsLAwbdiwodFt3G63XnzxRY0fP17du3dvw2oBAECoC6nLWC6XS6WlpRo7dqy3zWq1KjU1Vfv27Wt0u/z8fEVGRmrEiBH65JNPfvYYdXV1qqur8y5bLBZFRER4P6P18PsCAIIhpMJOdXW13G63HA6HT7vD4VBZWVmD23z66adav369nnvuuSYdo6CgQPn5+d7lxMRE5eTkKDo6usV1o2liYmKCXQIAoB0KqbDTXN99951efPFF3XPPPYqMjGzSNuPGjVNmZqZ3+exoQ2VlpVwuV6vUiTOOHDkS7BIAAIaw2WxNHqgIqbATGRkpq9Uqp9Pp0+50OuuN9kjS119/rcrKSuXk5HjbPB6PJGnChAmaM2eOevbs6bON3W6X3W5v8Phnt0Xr4PcFAARDSIUdm82mpKQklZSUaPDgwZLOTD4uKSnR6NGj6/WPjY3V888/79O2dOlS1dTU6I477lC3bt3apG4AABC6QirsSFJmZqbmzZunpKQkJScna/Xq1aqtrVV6erokae7cuYqKitKkSZMUFhamXr16+WzfsWNHSarXDgAA2qeQCztDhw5VdXW18vLy5HQ6lZCQoOnTp3svY1VVVXFXDwAAaDKLx4+JFB6PR2+99ZbWr1+viooKnThxov4BLBYtXbrUryLbQmVlpc8t6Q25NL1pd3yhYR9v5KnWAIDAsNvtbTNB+X//93+1cuVKJSQkKC0tzXsJCQAAIFT4FXY2bdqkq666Sg899FCg6gEAAAgov14X8f3336t///6BqgUAACDg/Ao7v/rVr3TgwIFA1QIAABBwfoWdu+66S/v379frr7+ub7/9NlA1AQAABIxfc3amTp0qj8ejZcuWadmyZQoLC5PVWj8/LVy40J/DAAAAtJhfYeeqq67imTcAACCk+RV2pkyZEqg6AAAAWoVfc3YAAABCnd+vizh16pRWrVqlnTt3qqqqSpLUrVs3DRo0SGPGjFGHDh38LhIAAKCl/BrZOXr0qB555BHl5+erpqZGffv2Vd++fVVbW6vXXntNjzzyiI4dOxaoWgEAAJrNr5GdRYsWyel06pFHHtHll1/us27Xrl2aPXu2Fi1apPvvv9+vIgEAAFrKr5GdDz/8UGPGjKkXdCRp4MCBuuGGG7Rr1y5/DgEAAOAXv8JObW2tunTp0uh6h8Oh2tpafw4BAADgF7/CTlxcnLZu3SqXy1Vvncvl0tatWxUXF+fPIQAAAPzi15yd3/zmN5ozZ44effRRjRo1SjExMZKksrIyrV27Vl988YUefPDBgBQKAADQEn6FnSFDhqi2tlaLFi3Sf//3f/usi4yM1L333qtf//rXfhUIAADgD7+fs5Oenq60tDQdPHjQ5zk7vXv31nnnned3gQAAAP7wO+xI0nnnnaeUlBSlpKQEYncAAAAB06yws3fvXklSv379fJZ/ydn+AAAAba1ZYefJJ5+UdOZhgjabzbv8S5YtW9b8ygAAAAKgWWHniSeeOLORzeazDAAAEKqaFXZ+ejmKy1MAACDU+fVQwSeffFJ79uxpdH1JSUmTL3UBAAC0Br/Czt69e3X8+PFG11dXVzd5EjMAAEBr8Cvs/JLy8nJFRES05iEAAAB+VrOfs7Nx40Zt2rTJu/z6669r3bp19fqdOnVKX3zxhQYOHOhfhQAAAH5odtj5/vvvVV1d7V3+7rvvZLFYfPpYLBadf/75uv7665WVleV/lQAAAC3U7LCTkZGhjIwMSdKUKVN055136oorrgh4YQAAAIHQ4jk733//va688spA1gIAABBwLQ47YWFhWrdu3c/ejQUAABBsft2NlZSUpC+//DJQtQAAAAScX2Hn9ttv19atW7Vu3TqdPn06UDUBAAAETLMnKP/Y/PnzZbVa9de//lWvvPKKoqKiFBYW5tPHYrFo1qxZfhUJAADQUn6FnU6dOqlz586KjY0NVD0AAAAB5VfYmTFjRoDKAAAAaB2t+roIAACAYPNrZEeS3G633n77be3cuVNVVVWSpG7dumnQoEFKS0uT1UqeAgAAweNX2Dl16pRmzpypAwcOKCIiQj169JAk7dmzR++++66Ki4v12GOPqUOHDgEpFgAAoLn8CjtLlixRaWmpJk+erJEjR8pmO7M7l8ul9evX65VXXtHSpUs1efLkgBQLAADQXH5dY9qxY4cyMjI0atQob9CRJJvNpoyMDF1//fV69913/S4SAACgpfwKOydOnPjZ284vvPBCnThxwp9DAAAA+MWvsNOzZ0+9//77ja5///33vfN4AAAAgsGvsJORkaHdu3fr2Wef1UcffaSKigpVVFToww8/1LPPPqvdu3dr9OjRgaoVAACg2fyaoDxq1CgdP35cb7zxhj788EPfHdtsysrKUkZGhj+HAAAA8Ivfz9kZP368Ro8erd27d3ufsxMdHa3U1FRFRkb6XSAAAIA//A47khQZGalhw4YFYleSpKKiIhUWFsrpdCo+Pl6TJ09WcnJyg33fffddFRQUqLy8XKdPn1bPnj1144036pprrglYPQAA4NwVkLDzwQcfaNeuXaqsrJR0ZmRn4MCBGjRoULP3tW3bNuXm5io7O1t9+vTRqlWrNHPmTM2ZM0ddunSp179Tp066+eabFRsbK5vNpp07d2r+/PmKjIzUgAED/P1qAADgHOdX2Dl58qSef/557d27V1arVV27dpUk7d69W2vXrtUll1yihx9+WB07dmzyPleuXKmRI0dq+PDhkqTs7Gzt3LlTGzZs0NixY+v1v/TSS32Wx4wZo02bNunTTz8l7AAAAP/CziuvvKJPPvlEt956qzIyMhQeHi5JqqmpUXFxsRYvXqxXXnlF999/f5P253K5VFpa6hNqrFarUlNTtW/fvl/c3uPxqKSkRGVlZbr11ltb9J0AAIBZ/Ao77733njIyMnTTTTf5tIeHh+umm25SVVWVNm3a1OT9VVdXy+12y+Fw+LQ7HA6VlZU1ut2pU6d0zz33yOVyyWq16ne/+5369+/fYN+6ujrV1dV5ly0WiyIiIryf0Xr4fQEAweBX2LHZbD/7BOWz82haW3h4uGbNmqWamhrt2bNHubm56tGjR71LXJJUUFCg/Px873JiYqJycnIUHR3d6nW2dzExMcEuAQDQDvmVRK666iq98847ysjIkNXq+3zC06dPa/v27fr1r3/d5P1FRkbKarXK6XT6tDudznqjPT9mtVrVs2dPSVJCQoK++uorLV++vMGwM27cOGVmZnqXz442VFZWyuVyNblWNN+RI0eCXQIAwBA2m63JAxV+hZ20tDS9/PLL+tOf/qTrrrvOGziOHDmit956Sy6XS2lpaSotLfXZLikpqdHCk5KSVFJSosGDB0uS3G63SkpKmvUkZrfb7XOp6sfsdrvsdnuD6zweT5OPgebj9wUABINfYWfGjBnezwcPHmywzxNPPFGvbdmyZY3uMzMzU/PmzVNSUpKSk5O1evVq1dbWKj09XZI0d+5cRUVFadKkSZLOXJbq3bu3evToobq6Ou3atUubN2/WXXfd1fIvBgAAjOFX2Ln33nsDVYfX0KFDVV1drby8PDmdTiUkJGj69Oney1hVVVU+E11ra2u1YMECffPNNwoLC9OFF16of/mXf9HQoUMDXhsAADj3WDxcW5B0Zs5OY5e+zro0/bk2qsZMH2+cFuwSAACGsNvtbTNn58dqamq878bq1q2b95k7AAAAweR32Dlw4IAWLVqkTz/9VG63W9KZu6Muvvhi3Xbbberdu7ffRQIAALSUX2Fn//79mjFjhmw2m0aMGKELL7xQkvTVV19p69ateuKJJzRjxoxGX+IJAADQ2vwKO0uXLlVUVJSeeuqpes/B+e1vf6vHH39cS5Ys0eOPP+7PYQAAAFrM+stdGrd//35df/31DT7wz+Fw6LrrrtP+/fv9OQQAAIBf/Ao7FotFp0+fbnS92+3mfUgAACCo/Ao7ffv21Zo1a1RZWVlvXVVVlYqLi3XxxRf7cwgAAAC/+DVnZ+LEifrzn/+sqVOnavDgwd4XPZaVlen999/Xeeedp4kTJwakUAAAgJbwK+wkJibq2Wef1ZIlS/T+++/r+++/lySFhYVpwIABmjBhguLi4gJSKAAAQEu0OOzU1dXpo48+UnR0tB5++GG53W5VV1dL+r+3lwMAAARbixOJzWbT7Nmz9fe///3MjqxWORwOORwOgg4AAAgZLU4lFotFMTEx+vbbbwNZDwAAQED5NQQzbtw4FRUVqaysLFD1AAAABJRfE5T37dunzp0769/+7d/Ur18/RUdHKywszKePxWLRnXfe6VeRAAAALeVX2FmzZo33c0lJSaP9CDsAACBY/Ao7y5YtC1QdAAAArcKvsHPWoUOHtGvXLu+TlLt3764BAwaoV69egdg9AABAi/kVdurq6vTXv/5Vb7/9tiR534Pl8Xi0aNEipaWl6fe//71stoBkKgAAgGbzK4UsWrRIb7/9tjIyMnTDDTeoR48eslgsKi8v1+rVq7V27Vp16tRJd9xxR4DKBQAAaB6/bj3fvHmz0tLS9Lvf/U6xsbE677zzZLVaFRsbq7vuukvDhg3T5s2bA1UrAABAs/kVdlwul1JSUhpd37dvX50+fdqfQwAAAPjFr7Bz2WWX6cMPP2x0/Ycffqj+/fv7cwgAAAC/+BV2JkyYoMrKSj3//PPas2ePKisrVVlZqd27d2vWrFmqrKzUhAkTdOLECZ8/AAAAbcXi8Xg8Ld34lltuadF2ofh8nsrKStXV1f1sn0vTn2ujasz08cZpwS4BAGAIu92u6OjoJvX1626sf/zHf/Tebg4AABCK/Ao748ePD1QdAAAArcKvOTsAAAChjrADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0WzBLqAhRUVFKiwslNPpVHx8vCZPnqzk5OQG+7711lt6++239eWXX0qSkpKSNHHixEb7AwCA9iXkRna2bdum3NxcZWVlKScnR/Hx8Zo5c6aOHz/eYP+9e/fq6quv1hNPPKGnn35aF1xwgZ5++mkdPXq0jSsHAAChKORGdlauXKmRI0dq+PDhkqTs7Gzt3LlTGzZs0NixY+v1f+CBB3yWf//73+vdd9/Vnj17dO2117ZFyQiCot8NC3YJ57TRL20JdgkA0GZCKuy4XC6Vlpb6hBqr1arU1FTt27evSfuora2Vy+VSp06dGlxfV1enuro677LFYlFERIT3M1oPv2/o4FwAaE9CKuxUV1fL7XbL4XD4tDscDpWVlTVpH4sWLVJUVJRSU1MbXF9QUKD8/HzvcmJionJychQdHd3iutE0MTExwS4BP+BcAGhPQirs+Gv58uXaunWrZsyYobCwsAb7jBs3TpmZmd7ls3/DrayslMvlapM626sjR44EuwT8gHMB4Fxns9maPFARUmEnMjJSVqtVTqfTp93pdNYb7fmpFStWaPny5Xr88ccVHx/faD+73S673d7gOo/H09yS0Qz8vqGDcwGgPQmpu7FsNpuSkpJUUlLibXO73SopKVFKSkqj273xxhv629/+punTp6t3795tUSoAADhHhFTYkaTMzEytW7dOGzdu1OHDh7VgwQLV1tYqPT1dkjR37lwtXrzY23/58uVatmyZ7r33XnXv3l1Op1NOp1M1NTVB+gYAACCUhNRlLEkaOnSoqqurlZeXJ6fTqYSEBE2fPt17GauqqsrnTpK1a9fK5XJp9uzZPvvJysrS+PHj27J0AAAQgiweLt5LOjNB+ce3pDfk0vTn2qgaM328cVrA9sVzdvzDc3YAnOvsdnuTJyiH3GUsAACAQCLsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQcAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMJot2AX8VFFRkQoLC+V0OhUfH6/JkycrOTm5wb5ffvmlli1bps8++0yVlZW6/fbb9Q//8A9tXDEAAAhlITWys23bNuXm5iorK0s5OTmKj4/XzJkzdfz48Qb719bWqkePHpo0aZIcDkfbFgsAAM4JIRV2Vq5cqZEjR2r48OGKi4tTdna2wsLCtGHDhgb7Jycn65/+6Z909dVXy263t3G1AADgXBAyl7FcLpdKS0s1duxYb5vValVqaqr27dsXsOPU1dWprq7Ou2yxWBQREeH9jNbD7xs6OBcA2pOQCTvV1dVyu931Lkc5HA6VlZUF7DgFBQXKz8/3LicmJionJ0fR0dEBOwYaFhMTE+wS8APOBYD2JGTCTlsZN26cMjMzvctn/4ZbWVkpl8sVrLLahSNHjgS7BPyAcwHgXGez2Zo8UBEyYScyMlJWq1VOp9On3el0BnTysd1ub3R+j8fjCdhxUB+/b+jgXABoT0JmgrLNZlNSUpJKSkq8bW63WyUlJUpJSQliZQAA4FwWMiM7kpSZmal58+YpKSlJycnJWr16tWpra5Weni5Jmjt3rqKiojRp0iRJZyY1Hz582Pv56NGj+vzzzxUeHq6ePXsG62sAAIAQElJhZ+jQoaqurlZeXp6cTqcSEhI0ffp072Wsqqoqn7tIjh49qmnTpnmXCwsLVVhYqH79+mnGjBltXD3Qfg2ZkR7sEs5Z22dsDHYJgPFCKuxI0ujRozV69OgG1/00wHTv3l15eXltUBUAADhXhcycHQAAgNZA2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0W7ALAAAEzvzLfxXsEs5Z9+0sCXYJaCWM7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDQmKAMA0Aqmjvx/wS7hnDVn3Z8Duj9GdgAAgNEIOwAAwGiEHQAAYDTCDgAAMBphBwAAGI2wAwAAjEbYAQAARiPsAAAAoxF2AACA0Qg7AADAaIQdAABgNMIOAAAwGmEHAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBotmAX0JCioiIVFhbK6XQqPj5ekydPVnJycqP9t2/frmXLlqmyslI9e/bUrbfeqssvv7wNKwYAAKEq5EZ2tm3bptzcXGVlZSknJ0fx8fGaOXOmjh8/3mD/v//973rhhRc0YsQI5eTk6Morr9SsWbN06NChNq4cAACEopALOytXrtTIkSM1fPhwxcXFKTs7W2FhYdqwYUOD/VevXq0BAwbopptuUlxcnCZMmKCkpCQVFRW1ceUAACAUhdRlLJfLpdLSUo0dO9bbZrValZqaqn379jW4zb59+5SZmenTdtlll+m9995rsH9dXZ3q6uq8yxaLRREREbLZfvmn6N8vrgnfAo2x2+0B29cFvS8N2L7ao0CeC0n6Va9+Ad1fexLocxGT2j+g+2tPAn0uEvtdFND9tSdNORdN+f+2t68/xQRadXW13G63HA6HT7vD4VBZWVmD2zidTnXp0sWnrUuXLnI6nQ32LygoUH5+vnf56quv1r/+67+qa9euv1jfpuV/+MU+aBu/mVsY7BLwI6v+tDzYJeAHU9ZuDHYJ+EHO3x4Ldgn4Qchdxmpt48aN06uvvur9k52d7TPSc6767rvv9Mgjj+i7774LdintHucidHAuQgfnIrS0t/MRUiM7kZGRslqt9UZlnE5nvdGesxwOR73Jy8ePH2+0v91uD/hQZSjweDz67LPP5PF4gl1Ku8e5CB2ci9DBuQgt7e18hNTIjs1mU1JSkkpKSrxtbrdbJSUlSklJaXCblJQU7dmzx6dt9+7d6tOnT6vWCgAAzg0hFXYkKTMzU+vWrdPGjRt1+PBhLViwQLW1tUpPT5ckzZ07V4sXL/b2HzNmjD766CMVFhbqq6++Ul5eng4ePKjRo0cH6RsAAIBQElKXsSRp6NChqq6uVl5enpxOpxISEjR9+nTvZamqqipZLBZv/759++qBBx7Q0qVLtWTJEsXExOjhhx9Wr169gvQNgsNutysrK8vIS3TnGs5F6OBchA7ORWhpb+fD4mkvF+wAAEC7FHKXsQAAAAKJsAMAAIxG2AEAAEYj7AAAAKOF3N1YaJ69e/dqxYoV+uyzz3Ts2DH94Q9/0ODBg4NdVrtUUFCgHTt26KuvvlJYWJhSUlJ02223KTY2NtiltTvFxcUqLi5WZWWlJCkuLk5ZWVkaOHBgkCvD8uXLtXjxYo0ZM0Z33HFHsMtpV/Ly8nxelyRJsbGxmjNnTnAKakOEnXNcbW2tEhISNGLECD3//PPBLqdd27t3r0aNGqXevXvr9OnTWrJkiZ5++mnNnj1b4eHhwS6vXYmKitKkSZMUExMjj8ejTZs26bnnntNzzz2niy7i5YzBcuDAAa1du1bx8fHBLqXduuiii/T44497l63W9nGBh7Bzjhs4cCB/Ww0Rjz3m+9K/KVOm6K677lJpaan69eOt4G3piiuu8FmeOHGiiouLtX//fsJOkNTU1OjFF1/UPffco9dffz3Y5bRbVqu10dcpmYywA7SSU6dOSZI6deoU5EraN7fbre3bt6u2trbR186g9S1YsEADBw5U//79CTtBVF5ernvuuUd2u10pKSmaNGmSunXrFuyyWh1hB2gFbrdbr776qvr27dvunuYdKg4dOqTHHntMdXV1Cg8P1x/+8AfFxcUFu6x2aevWrfrss8/07LPPBruUdq1Pnz667777FBsbq2PHjik/P19//vOf9e///u+KiIgIdnmtqn1crAPa2EsvvaQvv/xSU6dODXYp7VZsbKxmzZqlZ555RhkZGZo3b54OHz4c7LLanaqqKr366qt64IEHFBYWFuxy2rWBAwdqyJAhio+P14ABA/Too4/q5MmT2r59e7BLa3WM7AAB9tJLL2nnzp168skndcEFFwS7nHbLZrOpZ8+ekqSkpCQdPHhQq1ev1t133x3kytqX0tJSHT9+XI888oi3ze1265NPPlFRUZEWL17cbibJhpqOHTsqNjZW5eXlwS6l1RF2gADxeDx6+eWXtWPHDs2YMUPdu3cPdkn4Ebfbrbq6umCX0e6kpqbWu1P0P//zPxUbG6vf/OY3BJ0gqqmpUXl5udLS0oJdSqsj7Jzjzv7LelZFRYU+//xzderUqV1MOgslL730krZs2aJp06YpIiJCTqdTktShQweG79vY4sWLNWDAAHXr1k01NTXasmWL9u7dW++OObS+iIiIevPWzj//fHXu3Jn5bG0sNzdXV1xxhbp166Zjx44pLy9PVqtVw4YNC3ZprY6wc447ePCgnnzySe9ybm6uJOnaa6/VlClTglVWu1RcXCxJmjFjhk/7fffdp/T09LYvqB07fvy45s2bp2PHjqlDhw6Kj4/XY489pv79+we7NCBojh49qhdeeEHffvutIiMjdfHFF2vmzJmKjIwMdmmtzuLxeDzBLgIAAKC1cLEUAAAYjbADAACMRtgBAABGI+wAAACjEXYAAIDRCDsAAMBohB0AAGA0wg4AADAaYQdAq5kyZYrmzZsX7DIAtHOEHQAIoi1btmjVqlXBLgMwGmEHAIJoy5YtWr16dbDLAIxG2AEAAEbjrecAmi0vL0/5+fn6y1/+omXLlumjjz7Seeedp7S0NN16660KCwtrcLsTJ07o9ddf10cffaSKigpZrVb17dtXkyZNUkJCgiSppqZG2dnZGjFihO68806f7b/55hvdd999mjBhgsaNG9fket9++229+eab+vLLL2W329WrVy/dfPPNuuyyy7x91qxZozVr1qi8vFydO3fWlVdeqYkTJ6pjx47ePlOmTFG/fv00ZcoUn/2ffdP92X9+/PHHevLJJzV16lSVl5eruLhY3377rfr27au7775bPXv29Pbfu3evJGn8+PGSpOjoaOY5AQFG2AHQYn/5y18UHR2tiRMnav/+/XrzzTd18uRJ3X///Q32//rrr/Xee+9pyJAh6t69u5xOp9566y3NmDFDs2fPVlRUlMLDw3XllVdq27Ztuv3222W1/t8A9NatWyVJaWlpTa7xtdde02uvvaa+fftq/PjxstlsOnDggEpKSrxh52x4S01NVUZGhsrKylRcXKyDBw/qqaeeks3Wsv9UvvHGG7JYLLrxxht16tQprVixQv/xH/+hZ555RpJ0880369SpU/rmm290++23S5LCw8NbdCwAjSPsAGix7t27a9q0aZKk0aNHKyIiQsXFxbrxxhsVHx9fr3+vXr30wgsv+ASYa665Rg8++KDWr1+vrKwsSdK1116rLVu2aPfu3RowYIC37+bNm3XJJZeoW7duTaqvvLxc+fn5Gjx4sB566CGf43o8HklSdXW1li9frssuu0yPPvqot09sbKxefvllbd68WcOHD2/eD/OD77//XrNmzfKGpY4dO+rVV1/VoUOH1KtXL/Xv319RUVE6efKkrrnmmhYdA8AvY84OgBYbNWqUz/INN9wgSdq1a1eD/e12uzdMuN1uffvttwoPD1dsbKw+++wzb7/U1FR17dpVmzdv9rYdOnRIX3zxRbNGdXbs2CGPx6OsrCyfoCNJFotFkrR79265XC6NGTPGp891112niIgI7dy5s8nH+6nhw4f7jApdcsklkqSKiooW7xNA8zGyA6DFYmJifJZ79Oghi8XS6P/M3W63Vq9ereLiYlVUVMjtdnvXderUyfvZarUqLS1NxcXFqq2t1fnnn68tW7bIbrdryJAhTa7v66+/lsViUVxcXKN9qqqqJJ0Zyfkxm82mHj16eNe3xE9HoM7O/zlx4kSL9wmg+Qg7AALm7GhJYwoKCrRs2TINHz5ct9xyizp16iSLxaKFCxd6Lyuddc0112jFihV67733dPXVV2vLli0aNGiQOnTo0JpfoUXcbne9kSNJDbYBaHuEHQAtduTIEXXv3t27XF5eLo/H49P2Y++8844uvfRS3XvvvT7tJ0+eVOfOnX3aevXqpcTERG3evFlRUVGqqqrS5MmTm1Vfjx495PF4dPjwYe/dXj91dvSlrKxMPXr08La7XC5VVFQoNTXV29apUyedPHmy3j4qKyt9tgUQWvhrB4AWW7Nmjc/ym2++KUk+k4p/rKGRju3bt+vo0aMN9k9LS9Pu3bu1evVqde7cudH9Nmbw4MGyWCzKz8/3uWQm/d8E5f79+8tms+nNN9/0GV1av369Tp06pcsvv9zb1qNHD+3fv18ul8vb9sEHH+ibb75pVl0/Fh4erlOnTrV4ewC/jJEdAC1WUVGhnJwcDRgwQPv27dPmzZs1bNiwRkdRBg0apPz8fM2fP18pKSk6dOiQtmzZ0uioyLBhw7Ro0SLt2LFDGRkZzb4FvGfPnrr55pv1t7/9TU888YQGDx4su92uAwcOKCoqSpMmTVJkZKTGjh2r/Px8PfPMMxo0aJD31vPevXv7TIgeMWKE3nnnHc2cOVNDhgzR119/rc2bN/s1qpOUlKRt27Zp4cKF6t27t8LDw3XFFVe0eH8A6iPsAGixqVOnKi8vT4sXL5bVatXo0aN12223Ndp/3Lhxqqmp0datW7Vt2zYlJibqj3/8oxYvXtxgf4fDof79+2vXrl0tvjX7lltuUffu3VVUVKSlS5cqLCxM8fHxPvsbP368IiMjtWbNGi1cuFCdOnXSddddp4kTJ/oErAEDBuif//mftXLlSi1cuFBJSUn64x//qNzc3BbVJkkZGRn6/PPPtXHjRq1atUrR0dGEHSDALJ6fzgoEgF9w9iF8CxYsUGRkZKsea9asWTp06JBefPHFVj0OAHMxZwdAyDp27Jh27tzJA/cA+IXLWABCTkVFhT799FOtX79eNptN119/fb0+TqfzZ/cRFhYWkrepA2h7hB0AIWfv3r2aP3++unXrpilTpsjhcNTrc/fdd//sPq699tp6L+wE0D4xZwfAOWn37t0/uz4qKupnn5wMoP0g7AAAAKMxQRkAABiNsAMAAIxG2AEAAEYj7AAAAKMRdgAAgNEIOwAAwGiEHQAAYDTCDgAAMNr/ByuhTxPgdUD1AAAAAElFTkSuQmCC",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "data = df_final\n",
+ "sns.countplot(data=data, x=data.play_count, hue='play_count', palette='dark',\n",
+ " legend=False, stat='proportion')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "0rcjoJT8vnoO"
+ },
+ "source": [
+ "From the above figure, we can see:\n",
+ " - **most of the songs, 60 percent** have been played only once and\n",
+ " - **80 percent of the songs** have been **played less than 3 times**."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "OWO4C8KsK_5e"
+ },
+ "source": [
+ "Now that we have explored the data, let's apply different algorithms to build recommendation systems.\n",
+ "\n",
+ "**Note:** Use the shorter version of the data, i.e., the data after the cutoffs as used in Milestone 1."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "9VThYg7voGIz"
+ },
+ "source": [
+ "## Building various models"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Ituk9wA4Idib"
+ },
+ "source": [
+ "### **Popularity-Based Recommendation Systems**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "462hsbxaI1ED"
+ },
+ "source": [
+ "Let's take the count and sum of play counts of the songs and build the popularity recommendation systems based on the sum of play counts."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "UXhBZlDE-jEu"
+ },
+ "outputs": [],
+ "source": [
+ "# Calculating average play_count\n",
+ " # Hint: Use groupby function on the song_id column\n",
+ "play_avg = df_final.groupby('song_id').play_count.mean()\n",
+ "\n",
+ "# Calculating the frequency a song is played\n",
+ " # Hint: Use groupby function on the song_id column\n",
+ "play_freq = 100 * df_final.groupby('song_id').play_count.sum()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 237
+ },
+ "id": "v2XYdXvWdyys",
+ "outputId": "cbde5471-5153-4e8a-8dfb-2abc0fc60ab1"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "summary": "{\n \"name\": \"df_songs_count_freq[:5]\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"song_id\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 30,\n \"min\": 21,\n \"max\": 93,\n \"num_unique_values\": 5,\n \"samples\": [\n 22,\n 93,\n 52\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"play_avg\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.12934084087359338,\n \"min\": 1.4521739130434783,\n \"max\": 1.7292161520190024,\n \"num_unique_values\": 5,\n \"samples\": [\n 1.4924242424242424,\n 1.4521739130434783,\n 1.7292161520190024\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"play_freq\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 23947,\n \"min\": 16700,\n \"max\": 72800,\n \"num_unique_values\": 4,\n \"samples\": [\n 19700,\n 16700,\n 43000\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}",
+ "type": "dataframe"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " play_avg | \n",
+ " play_freq | \n",
+ "
\n",
+ " \n",
+ " | song_id | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 21 | \n",
+ " 1.622642 | \n",
+ " 43000 | \n",
+ "
\n",
+ " \n",
+ " | 22 | \n",
+ " 1.492424 | \n",
+ " 19700 | \n",
+ "
\n",
+ " \n",
+ " | 52 | \n",
+ " 1.729216 | \n",
+ " 72800 | \n",
+ "
\n",
+ " \n",
+ " | 62 | \n",
+ " 1.728070 | \n",
+ " 19700 | \n",
+ "
\n",
+ " \n",
+ " | 93 | \n",
+ " 1.452174 | \n",
+ " 16700 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ " play_avg play_freq\n",
+ "song_id \n",
+ "21 1.622642 43000\n",
+ "22 1.492424 19700\n",
+ "52 1.729216 72800\n",
+ "62 1.728070 19700\n",
+ "93 1.452174 16700"
+ ]
+ },
+ "execution_count": 176,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Making a dataframe with the average_count and play_freq\n",
+ "df_songs_count_freq = pd.DataFrame({'play_avg':play_avg, 'play_freq':play_freq})\n",
+ "\n",
+ "# Let us see the first five records of the final_play dataset\n",
+ "df_songs_count_freq[:5]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "WnCT-A7RK_5g"
+ },
+ "source": [
+ "Now, let's create a function to find the top n songs for a recommendation based on the average play count of song. We can also add a threshold for a minimum number of playcounts for a song to be considered for recommendation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "QiT9FV3GNCrb"
+ },
+ "outputs": [],
+ "source": [
+ "# Build the function to find top n songs\n",
+ "def top_n_songs(df, n, min_playcount = 50):\n",
+ " \"\"\"\n",
+ " It gives top n songs among those being played for more than min_playcount\n",
+ " \"\"\"\n",
+ " # Finding products with minimum number of interactions\n",
+ " recommendations = df[df.play_freq > min_playcount]\n",
+ "\n",
+ " # Sorting values with respect to average rating\n",
+ " recommendations = recommendations.sort_values(by='play_avg', ascending=False)\n",
+ "\n",
+ " return list(recommendations.index[:n])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "GpZt_BeXgz4F",
+ "outputId": "af8284b0-99ac-4e77-cf2d-e8c6c893a694"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[7224, 8324, 6450, 9942, 5531, 5653, 8483, 2220, 657, 614]"
+ ]
+ },
+ "execution_count": 178,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Recommend top 10 songs using the function defined above\n",
+ "top_n_songs(df_songs_count_freq, 10)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "gf13HrPPJeWT"
+ },
+ "source": [
+ "### **User User Similarity-Based Collaborative Filtering**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ROcEpduohdua"
+ },
+ "source": [
+ "To build the user-user-similarity-based and subsequent models we will use the \"surprise\" library."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "aKLrKn8IfGjk",
+ "outputId": "cb10a4f8-1a03-489e-959c-c980cba04c73"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Collecting surprise\n",
+ " Downloading surprise-0.1-py2.py3-none-any.whl (1.8 kB)\n",
+ "Collecting scikit-surprise (from surprise)\n",
+ " Downloading scikit_surprise-1.1.4.tar.gz (154 kB)\n",
+ "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m154.4/154.4 kB\u001b[0m \u001b[31m3.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+ "\u001b[?25h Installing build dependencies ... \u001b[?25l\u001b[?25hdone\n",
+ " Getting requirements to build wheel ... \u001b[?25l\u001b[?25hdone\n",
+ " Preparing metadata (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
+ "Requirement already satisfied: joblib>=1.2.0 in /usr/local/lib/python3.10/dist-packages (from scikit-surprise->surprise) (1.4.2)\n",
+ "Requirement already satisfied: numpy>=1.19.5 in /usr/local/lib/python3.10/dist-packages (from scikit-surprise->surprise) (1.25.2)\n",
+ "Requirement already satisfied: scipy>=1.6.0 in /usr/local/lib/python3.10/dist-packages (from scikit-surprise->surprise) (1.11.4)\n",
+ "Building wheels for collected packages: scikit-surprise\n",
+ " Building wheel for scikit-surprise (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
+ " Created wheel for scikit-surprise: filename=scikit_surprise-1.1.4-cp310-cp310-linux_x86_64.whl size=2357251 sha256=581b6aafdc74cb64eb37410fb69b8c067e5d74b9185552bb41f746675554cfc9\n",
+ " Stored in directory: /root/.cache/pip/wheels/4b/3f/df/6acbf0a40397d9bf3ff97f582cc22fb9ce66adde75bc71fd54\n",
+ "Successfully built scikit-surprise\n",
+ "Installing collected packages: scikit-surprise, surprise\n",
+ "Successfully installed scikit-surprise-1.1.4 surprise-0.1\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Install the surprise package using pip. Uncomment and run the below code to do the same\n",
+ "!pip install surprise"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "UJ1wEylUpexj"
+ },
+ "outputs": [],
+ "source": [
+ "# Import necessary libraries\n",
+ "from surprise import similarities\n",
+ "from sklearn.metrics.pairwise import cosine_similarity\n",
+ "\n",
+ "# To compute the accuracy of models\n",
+ "from surprise import accuracy\n",
+ "\n",
+ "# This class is used to parse a file containing play_counts, data should be in structure - user; item; play_count\n",
+ "from surprise.reader import Reader\n",
+ "\n",
+ "\n",
+ "# Class for loading datasets\n",
+ "from surprise.dataset import Dataset\n",
+ "\n",
+ "\n",
+ "# For tuning model hyperparameters\n",
+ "from surprise.model_selection import GridSearchCV\n",
+ "\n",
+ "\n",
+ "# For splitting the data in train and test dataset\n",
+ "from surprise.model_selection import train_test_split\n",
+ "\n",
+ "\n",
+ "# For implementing similarity-based recommendation system\n",
+ "from surprise.prediction_algorithms.knns import KNNBasic\n",
+ "\n",
+ "\n",
+ "# For implementing matrix factorization based recommendation system\n",
+ "from surprise.prediction_algorithms.matrix_factorization import SVD\n",
+ "\n",
+ "\n",
+ "# For implementing KFold cross-validation\n",
+ "from surprise.model_selection import KFold\n",
+ "\n",
+ "\n",
+ "# For implementing clustering-based recommendation system\n",
+ "from surprise import CoClustering"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "jBW4BUhWTsnm"
+ },
+ "source": [
+ "### Some useful functions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ZhFa_4aHHchr"
+ },
+ "source": [
+ "Below is the function to calculate precision@k and recall@k, RMSE, and F1_Score@k to evaluate the model performance."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ZOvOgjGWrMVV"
+ },
+ "source": [
+ "**Think About It:** Which metric should be used for this problem to compare different models?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Rxn-GahOTsnm"
+ },
+ "outputs": [],
+ "source": [
+ "def precision_recall_at_k(model, k=30, threshold=1.5, testset=None):\n",
+ " \"\"\"Return precision and recall at k metrics for each user\"\"\"\n",
+ "\n",
+ " # First map the predictions to each user.\n",
+ " user_est_true = defaultdict(list)\n",
+ "\n",
+ " #Making predictions on the test data\n",
+ " predictions = model.test(testset)\n",
+ "\n",
+ " for uid, _, true_r, est, _ in predictions:\n",
+ " user_est_true[uid].append((est, true_r))\n",
+ "\n",
+ " precisions = dict()\n",
+ " recalls = dict()\n",
+ "\n",
+ " for uid, playing_count in user_est_true.items():\n",
+ "\n",
+ " # Sort play count by estimated value\n",
+ " playing_count.sort(key=lambda x: x[0], reverse=True)\n",
+ "\n",
+ " # Number of relevant items\n",
+ " n_rel = sum((true_r >= threshold) for (_, true_r) in playing_count)\n",
+ "\n",
+ " # Number of recommended items in top k\n",
+ " n_rec_k = sum((est >= threshold) for (est, _) in playing_count[:k])\n",
+ "\n",
+ " # Number of relevant and recommended items in top k\n",
+ " n_rel_and_rec_k = sum(((true_r >= threshold) and (est >= threshold))\n",
+ " for (est, true_r) in playing_count[:k])\n",
+ "\n",
+ " # Precision@K: Proportion of recommended items that are relevant\n",
+ " # When n_rec_k is 0, Precision is undefined. We here set Precision to 0 when n_rec_k is 0.\n",
+ "\n",
+ " precisions[uid] = n_rel_and_rec_k / n_rec_k if n_rec_k != 0 else 0\n",
+ "\n",
+ " # Recall@K: Proportion of relevant items that are recommended\n",
+ " # When n_rel is 0, Recall is undefined. We here set Recall to 0 when n_rel is 0.\n",
+ "\n",
+ " recalls[uid] = n_rel_and_rec_k / n_rel if n_rel != 0 else 0\n",
+ "\n",
+ " #Mean of all the predicted precisions are calculated.\n",
+ " precision = round((sum(prec for prec in precisions.values()) / len(precisions)),3)\n",
+ " #Mean of all the predicted recalls are calculated.\n",
+ " recall = round((sum(rec for rec in recalls.values()) / len(recalls)),3)\n",
+ "\n",
+ " accuracy.rmse(predictions)\n",
+ " print('Precision: ', precision) #Command to print the overall precision\n",
+ " print('Recall: ', recall) #Command to print the overall recall\n",
+ " print('F_1 score: ', round((2*precision*recall)/(precision+recall),3)) # Formula to compute the F-1 score."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "PcmLRxH4IjfG"
+ },
+ "source": [
+ "**Think About It:** In the function precision_recall_at_k above the threshold value used is 1.5. How precision and recall are affected by changing the threshold? What is the intuition behind using the threshold value of 1.5?\n",
+ "- Impact of the threshold on recall and precision\n",
+ " - If we decrease the threshold, most of the predictions would be above the threshold, hence many of the items would be recommended. As the data is imbalanced (many low playcounts), The TP rate would increase and Precision and Recall would increase. \n",
+ " - If we **increase the threshold**, most of the prediction would be below the threshold, hence few songs would be recommended. As the data is imbalanced (very few high playcounts), the TP rate would decrease. **So Precision and Recall would decrease**.\n",
+ " - So a good threshold is a medium threshold but in the present case most of the predictions are around 1, so 1.5 is a good threshold. Moreover 1 playcount is not enough to say a user like a song. The higher the the threshold, the higher the likely hood the user would love the song.\n",
+ "\n",
+ "- Evaluation parameters\n",
+ " - To compute **precision and recall**, a **threshold of 1.5 and k value of 30 will be considered for the recommended and relevant ratings**.\n",
+ "\n",
+ "- Performance metrice selection\n",
+ " - FP and FN costs are high in the present case. If the FP rate is high (precision is low), we recommend products that the user might not buy. And, if FN rate is high (recall is low) we fail to recommend relevant products that the user might purchase. Therefore, precision and recall need to be optimized. So the **correct performance measure is the F_1 score**."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "13kBr-ULn0vW"
+ },
+ "source": [
+ "Below we are loading the **dataset**, which is a **pandas dataframe**, into a **different format called `surprise.dataset.DatasetAutoFolds`** which is required by this library. To do this we will be **using the classes `Reader` and `Dataset`**\n",
+ "\n",
+ "You will also notice here that we read the dataset by providing a scale of ratings. However, as you would know, we do not have ratings data of the songs. In this case, we are going to use play_count as a proxy for ratings with the assumption that the more the user listens to a song, the higher the chance that they like the song"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "rGfYDiOCpe4X"
+ },
+ "outputs": [],
+ "source": [
+ "# Instantiating Reader scale with expected rating scale\n",
+ " #use rating scale (0, 5)\n",
+ "reader = Reader(rating_scale=(0, 5))\n",
+ "\n",
+ "# Loading the dataset\n",
+ " # Take only \"user_id\",\"song_id\", and \"play_count\"\n",
+ "data = Dataset.load_from_df(df_final[['user_id',\t'song_id', 'play_count']],\n",
+ " reader=reader)\n",
+ "\n",
+ "# Splitting the data into train and test dataset\n",
+ " # Take test_size = 0.4, random_state = 42\n",
+ "trainset, testset = train_test_split(data, test_size=0.4, random_state=42)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "CuTmLjUP1aED"
+ },
+ "source": [
+ "**Think About It:** How changing the test size would change the results and outputs?\n",
+ "\n",
+ "As the data is imbalanced :\n",
+ " - If we increase the test size, the performance of the model increase, as more diversity will appear in the testset\n",
+ " - And conversely, if we decrease, the test size, the performance decrease.\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "vO3FL7iape8A",
+ "outputId": "2521a7a0-3224-4eb8-d150-95a78a69cfbb",
+ "scrolled": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Computing the cosine similarity matrix...\n",
+ "Done computing similarity matrix.\n",
+ "RMSE: 1.0878\n",
+ "Precision: 0.396\n",
+ "Recall: 0.692\n",
+ "F_1 score: 0.504\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Build the default user-user-similarity model\n",
+ "sim_options=dict(name='cosine', user_based=True)\n",
+ "\n",
+ "# KNN algorithm is used to find desired similar items\n",
+ " # Use random_state = 1\n",
+ "sim_user_user_model = KNNBasic(sim_options=sim_options, random_state=1,\n",
+ " verbose=True)\n",
+ "\n",
+ "# Train the algorithm on the trainset, and predict play_count for the testset\n",
+ "sim_user_user_model.fit(trainset)\n",
+ "\n",
+ "# Let us compute precision@k, recall@k, and f_1 score with k = 30\n",
+ " # Use sim_user_user model\n",
+ "precision_recall_at_k(sim_user_user_model, testset=testset)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "mzcdlWmer6GA"
+ },
+ "source": [
+ "The F_1 score is not good, this is likely due to the imbalanced data. We need more observations. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "pGJbAAu-Rnty",
+ "outputId": "78b7bd5d-2e35-45fa-afb0-b6445cd17e3d"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "user_id chosen is: 6958 \n",
+ "song_id chosen listened by the user is: 447 \n",
+ "and the play count of this pair is: 1\n"
+ ]
+ }
+ ],
+ "source": [
+ "# selecting a sample user with a listened song, let's choose the first observation\n",
+ "user_id_interaction, song_id_interaction, play_count_interaction = df_final[['user_id'\n",
+ ", 'song_id', 'play_count']].iloc[0].values\n",
+ "print('user_id chosen is:', user_id_interaction,\n",
+ " '\\nsong_id chosen listened by the user is:', song_id_interaction,\n",
+ " '\\nand the play count of this pair is:', play_count_interaction)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "Sxd23bZ9pe_x",
+ "outputId": "6b7e28ea-114c-4f6c-843d-b78706f1aeed"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=447, r_ui=1, est=1.1222839992769744, details={'actual_k': 40, 'was_impossible': False})"
+ ]
+ },
+ "execution_count": 185,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Predicting play_count for a sample user with a listened song\n",
+ "# Use any user id and song_id\n",
+ "sim_user_user_model.predict(user_id_interaction, song_id_interaction, r_ui=1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "8hMhblLoQqlW"
+ },
+ "outputs": [],
+ "source": [
+ "# Function to find the list of users who have not listened to the song song_id.\n",
+ "def n_users_not_listened_song(n, data, song_id):\n",
+ " users_listened_song = set(data[data['song_id'] == song_id]['user_id'])\n",
+ " all_users = set(data['user_id'])\n",
+ " # n is the number of elements to get in the list\n",
+ " return list(all_users.difference(users_listened_song))[:n]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "mIwEufV4WeZI",
+ "outputId": "965257e0-0ce8-453b-9071-ebe131f6f09e"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "user_id chosen is: 8194 \n",
+ "song_id chosen and not listened by the user is: 1756\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Randomly choosing a song_id\n",
+ "song_id_no_interaction = df_final.song_id.values[10]\n",
+ "\n",
+ "# Finding a user who has not listened to the song\n",
+ "user_id_no_interaction = n_users_not_listened_song(1, df_final,\n",
+ " song_id_no_interaction)[0]\n",
+ "\n",
+ "print('user_id chosen is:', user_id_no_interaction,\n",
+ " '\\nsong_id chosen and not listened by the user is:', song_id_no_interaction)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "PbFcBj1PpfEV",
+ "outputId": "00a7e8bd-bec3-4d5a-e2d9-9f7f4cf9cd10"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=8194, iid=1756, r_ui=None, est=1.7469530495676857, details={'actual_k': 40, 'was_impossible': False})"
+ ]
+ },
+ "execution_count": 188,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Predicting play_count for a sample user with a song not-listened by the user\n",
+ " #predict play_count for any sample user\n",
+ "sim_user_user_model.predict(user_id_no_interaction, song_id_no_interaction)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "P9EVM7DysC47"
+ },
+ "source": [
+ "**The predicted play count** for the **interacted user-song pair is 1.2**. The **actual play count is 1**, so the prediction is pretty good.\n",
+ "\n",
+ "**The predicted play count** the for the **user-song pair with no interaciton is 1.74**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Lt1QBiylsIOm"
+ },
+ "source": [
+ "Now, let's try to tune the model and see if we can improve the model performance."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "T3diJPL7-tVw",
+ "outputId": "07ab5246-bcd4-4d6d-ceac-02607a0159ae"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Best RMSE score: 1.053329957920412\n",
+ "Best_params: {'k': 80, 'min_k': 40, 'sim_options': {'name': 'msd', 'user_based': True}}\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Setting up parameter grid to tune the hyperparameters\n",
+ "params_grid = dict(k=[60, 80], min_k=[40, 50],\n",
+ " sim_options=dict(name=['msd', 'cosine'],\n",
+ " #min_support = [5, 10, 20],\n",
+ " user_based=[True]))\n",
+ "\n",
+ "# Performing 3-fold cross-validation to tune the hyperparameters\n",
+ "gs = GridSearchCV(KNNBasic, param_grid=params_grid, cv=3, measures=['rmse'],\n",
+ " n_jobs=-1)\n",
+ "\n",
+ "# Fitting the data\n",
+ " # Use entire data for GridSearch\n",
+ "gs.fit(data)\n",
+ "\n",
+ "# Best RMSE score\n",
+ "print(\"Best RMSE score:\", gs.best_score['rmse'])\n",
+ "\n",
+ "# Combination of parameters that gave the best RMSE score\n",
+ "print(\"Best_params:\", gs.best_params['rmse'])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "PujRJA8X_JEJ",
+ "outputId": "e0abe3bb-b75e-409c-82bd-d18b0fa8a0c4"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "RMSE: 1.0571\n",
+ "Precision: 0.411\n",
+ "Recall: 0.682\n",
+ "F_1 score: 0.513\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Train the best model found in above gridsearch\n",
+ "# Using the optimal similarity measure for user-user based collaborative filtering\n",
+ "best_params = gs.best_params['rmse']\n",
+ "best_k = best_params['k']\n",
+ "best_min_k = best_params['min_k']\n",
+ "best_sim_options = best_params['sim_options']\n",
+ "\n",
+ "# Creating an instance of KNNBasic with optimal hyperparameter values\n",
+ "sim_user_user_tuned_model = KNNBasic(k = best_k, min_k=best_min_k,\n",
+ " sim_options=best_sim_options,\n",
+ " random_state=1, verbose=False)\n",
+ "\n",
+ "# Training the algorithm on the trainset\n",
+ "sim_user_user_tuned_model.fit(trainset)\n",
+ "\n",
+ "# Let us compute precision@k and recall@k also\n",
+ "precision_recall_at_k(sim_user_user_tuned_model, testset=testset)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "MH5OBZ7Nse6m"
+ },
+ "source": [
+ "The F1 score of this tuned model is 0.513. So it has silightly increased compared to the baseline model which was 0.504."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "FgV63lHiq1TV",
+ "outputId": "b24a0891-6745-4bff-cc4a-9068c2167163"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=1671, r_ui=2, est=1.4997599871491851, details={'actual_k': 80, 'was_impossible': False})"
+ ]
+ },
+ "execution_count": 191,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Predict the play count for a user who has listened to the song. Take user_id 6958, song_id 1671 and r_ui = 2\n",
+ "user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2\n",
+ "sim_user_user_tuned_model.predict(user_id_interaction, song_id_interaction,\n",
+ " r_ui = play_count_interaction)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Bf9E9pujcQQR"
+ },
+ "outputs": [],
+ "source": [
+ "# Function to find the list of songs who have not been listened by user_id.\n",
+ "def n_songs_not_listened_user(n, data, user_id):\n",
+ " songs_listened_user = set(data[data['user_id'] == user_id]['song_id'])\n",
+ " all_songs = set(data['song_id'])\n",
+ " # n is the number of elements to get in the list\n",
+ " return list(all_songs.difference(songs_listened_user))[:n]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "HXO2Ztjhq1bN",
+ "outputId": "a7efb155-9b17-4b5a-c852-9cc6d2de66b0"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "user_id chosen is: 6958 \n",
+ "song_id chosen and not listened by the user is: 2048\n"
+ ]
+ }
+ ],
+ "source": [
+ "user_id_no_interaction = 6958\n",
+ "\n",
+ "# Finding a song who has not been listened by the user above\n",
+ "song_id_no_interaction = n_songs_not_listened_user(1, df_final, user_id_no_interaction)[0]\n",
+ "\n",
+ "print('user_id chosen is:', user_id_no_interaction,\n",
+ " '\\nsong_id chosen and not listened by the user is:', song_id_no_interaction)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 53
+ },
+ "id": "m3I34l7kdEcO",
+ "outputId": "ce68eba3-6643-4af9-8249-6e1f69d28808"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "repr_error": "Out of range float values are not JSON compliant: nan",
+ "type": "dataframe"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " user_id | \n",
+ " song_id | \n",
+ " play_count | \n",
+ " title | \n",
+ " release | \n",
+ " artist_name | \n",
+ " year | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "Empty DataFrame\n",
+ "Columns: [user_id, song_id, play_count, title, release, artist_name, year]\n",
+ "Index: []"
+ ]
+ },
+ "execution_count": 194,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Verifying that the user has not listened to the song\n",
+ "df_final.query('user_id == @user_id_no_interaction & song_id == @song_id_no_interaction')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "l0flLbEad1pu",
+ "outputId": "3319ad75-1798-4b91-f127-9ef6c16e086e"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=2048, r_ui=None, est=1.533851879295488, details={'actual_k': 80, 'was_impossible': False})"
+ ]
+ },
+ "execution_count": 195,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Predict the play count for a song that is not listened to by the user (with user_id 6958)\n",
+ "sim_user_user_tuned_model.predict(user_id_no_interaction, song_id_no_interaction)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "SdpJ--8QWuzz"
+ },
+ "source": [
+ "**The predicted play count** for the **interacted user-song pair is 1.49**. The **actual play count is 2**, so the prediction is far from the actual.\n",
+ "\n",
+ "**The predicted play count** the for the **user-song pair with no interaciton is 1.53**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "SQ9M4pplNbWS"
+ },
+ "source": [
+ "**Think About It:** Along with making predictions on listened and unknown songs can we get 5 nearest neighbors (most similar) to a certain song?\n",
+ " - Yes, we can get the list of the songs similar to a song"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "TbFle7cKmBJG",
+ "outputId": "0ac2c433-044e-41a9-df5f-bf902ac63499"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[31, 82, 90, 116, 125]"
+ ]
+ },
+ "execution_count": 196,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Use inner id 0\n",
+ "sim_user_user_tuned_model.get_neighbors(iid=0, k=5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "U3ESobDynVNI"
+ },
+ "source": [
+ "Below we will be implementing a function where the input parameters are:\n",
+ "\n",
+ "- data: A **song** dataset\n",
+ "- user_id: A user-id **against which we want the recommendations**\n",
+ "- top_n: The **number of songs we want to recommend**\n",
+ "- algo: The algorithm we want to use **for predicting the play_count**\n",
+ "- The output of the function is a **set of top_n items** recommended for the given user_id based on the given algorithm"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "vW9V1Tk65HlY"
+ },
+ "outputs": [],
+ "source": [
+ "def get_recommendations(data, user_id, top_n, algo):\n",
+ "\n",
+ " # Creating an empty list to store the recommended song ids\n",
+ " recommendations = []\n",
+ "\n",
+ " # Creating an user item interactions matrix\n",
+ " user_item_interactions_matrix = data.pivot(index = 'user_id',\n",
+ " columns = 'song_id',\n",
+ " values = 'play_count')\n",
+ "\n",
+ " user_songs = user_item_interactions_matrix.loc[user_id]\n",
+ " # Extracting those song ids which the user_id has not played yet\n",
+ " non_played_songs = user_songs[user_songs.isnull()].index.tolist()\n",
+ "\n",
+ " # Looping through each of the song ids which user_id has not interacted yet\n",
+ " for song_id in non_played_songs:\n",
+ "\n",
+ " # Predicting the users for those non played song ids by this user\n",
+ " est = algo.predict(user_id, song_id).est\n",
+ "\n",
+ " # Appending the predicted play_counts\n",
+ " recommendations.append((song_id, est))\n",
+ "\n",
+ " # Sorting the predicted play_counts in descending order\n",
+ " recommendations.sort(key = lambda x: x[1], reverse = True)\n",
+ "\n",
+ " # Returing top n highest predicted play_count songs for this user\n",
+ " return recommendations[:top_n]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "qWbR85mI5Hrk"
+ },
+ "outputs": [],
+ "source": [
+ "# Make top 5 recommendations for any user_id with a similarity-based recommendation engine\n",
+ "recommendations = get_recommendations(df_final, user_id_interaction, 5,\n",
+ " sim_user_user_tuned_model)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 206
+ },
+ "id": "b5WfIX0Z6_q2",
+ "outputId": "cea0684a-113c-4b1a-edfc-17b840dc8c7c"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "summary": "{\n \"name\": \"recommendations_df\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"song_id\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 2981,\n \"min\": 657,\n \"max\": 7496,\n \"num_unique_values\": 5,\n \"samples\": [\n 2220,\n 657,\n 6860\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"predicted_play_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.043119339328598606,\n \"min\": 2.1198501976357838,\n \"max\": 2.2125,\n \"num_unique_values\": 5,\n \"samples\": [\n 2.2,\n 2.1198501976357838,\n 2.1621256573025796\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}",
+ "type": "dataframe",
+ "variable_name": "recommendations_df"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " song_id | \n",
+ " predicted_play_count | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 5531 | \n",
+ " 2.212500 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " 2220 | \n",
+ " 2.200000 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 6860 | \n",
+ " 2.162126 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 7496 | \n",
+ " 2.121104 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 657 | \n",
+ " 2.119850 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ " song_id predicted_play_count\n",
+ "0 5531 2.212500\n",
+ "1 2220 2.200000\n",
+ "2 6860 2.162126\n",
+ "3 7496 2.121104\n",
+ "4 657 2.119850"
+ ]
+ },
+ "execution_count": 199,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Building the dataframe for above recommendations with columns \"song_id\" and \"predicted_play_count\"\n",
+ "recommendations_df = pd.DataFrame(recommendations, columns=['song_id', 'predicted_play_count'])\n",
+ "recommendations_df"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "kyhThMOttWjj"
+ },
+ "source": [
+ "**Above we can see the list of the songs recommended by the user-user colaborative filtering model**.\n",
+ " - the top recommendation is the song 7224 with 2.93 play_counts.\n",
+ " - the fith recommendation is the song 4831 with 2.34 playcounts.\n",
+ " - all the recommended songs have a predicted playcounts greater than 2.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ghwEJY2e7INB"
+ },
+ "source": [
+ "### Correcting the play_counts and Ranking the above songs"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "39Hs7ZbO9v3O"
+ },
+ "outputs": [],
+ "source": [
+ "def ranking_songs(recommendations, playing_count):\n",
+ " # Sort the songs based on play counts\n",
+ " ranked_songs = playing_count.loc[[items[0] for items in recommendations]].sort_values('play_freq', ascending = False)[['play_freq']].reset_index()\n",
+ "\n",
+ " # Merge with the recommended songs to get predicted play_counts\n",
+ " ranked_songs = ranked_songs.merge(pd.DataFrame(recommendations, columns = ['song_id', 'predicted_play_count']), on = 'song_id', how = 'inner')\n",
+ "\n",
+ " # Rank the songs based on corrected play_counts\n",
+ " ranked_songs['corrected_play_count'] = ranked_songs['predicted_play_count'] - 1 / np.sqrt(ranked_songs['play_freq'])\n",
+ "\n",
+ " # Sort the songs based on corrected play_counts\n",
+ " ranked_songs = ranked_songs.sort_values('corrected_play_count', ascending = False)\n",
+ "\n",
+ " return ranked_songs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "zQvst41lOoMX"
+ },
+ "source": [
+ "**Think About It:** In the above function to correct the predicted play_count a quantity 1/np.sqrt(n) is subtracted. What is the intuition behind it? Is it also possible to add this quantity instead of subtracting?\n",
+ " - we can correct the predictions of the songs by using the total play_count of the song. The intuition behind is the higher the playcount the higher the likely hood the song will be liked. So we can add the quantity 1/np.sqrt(n) to have optimistic predictions or in contrario we can substract that quantity to have play count below 5.\n",
+ " - So the confidence interval of the predictions could be seen as : predicted_prediction - 1/np.sqrt(n), predicted_prediction + 1/np.sqrt(n)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 206
+ },
+ "id": "xoiAL_vH8miC",
+ "outputId": "d78fcc6c-7930-4116-de2f-be7fff04f530"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "summary": "{\n \"name\": \"ranking_songs(recommendations, df_songs_count_freq)\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"song_id\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 2981,\n \"min\": 657,\n \"max\": 7496,\n \"num_unique_values\": 5,\n \"samples\": [\n 2220,\n 657,\n 6860\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"play_freq\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 59506,\n \"min\": 33500,\n \"max\": 158300,\n \"num_unique_values\": 5,\n \"samples\": [\n 158300,\n 33500,\n 36700\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"predicted_play_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.043119339328598606,\n \"min\": 2.1198501976357838,\n \"max\": 2.2125,\n \"num_unique_values\": 5,\n \"samples\": [\n 2.2,\n 2.1198501976357838,\n 2.1621256573025796\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"corrected_play_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.04420495048507308,\n \"min\": 2.114386613988702,\n \"max\": 2.209852792324103,\n \"num_unique_values\": 5,\n \"samples\": [\n 2.197486611969163,\n 2.114386613988702,\n 2.1569056997928606\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}",
+ "type": "dataframe"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " song_id | \n",
+ " play_freq | \n",
+ " predicted_play_count | \n",
+ " corrected_play_count | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1 | \n",
+ " 5531 | \n",
+ " 142700 | \n",
+ " 2.212500 | \n",
+ " 2.209853 | \n",
+ "
\n",
+ " \n",
+ " | 0 | \n",
+ " 2220 | \n",
+ " 158300 | \n",
+ " 2.200000 | \n",
+ " 2.197487 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 6860 | \n",
+ " 36700 | \n",
+ " 2.162126 | \n",
+ " 2.156906 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 7496 | \n",
+ " 62800 | \n",
+ " 2.121104 | \n",
+ " 2.117114 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 657 | \n",
+ " 33500 | \n",
+ " 2.119850 | \n",
+ " 2.114387 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ " song_id play_freq predicted_play_count corrected_play_count\n",
+ "1 5531 142700 2.212500 2.209853\n",
+ "0 2220 158300 2.200000 2.197487\n",
+ "3 6860 36700 2.162126 2.156906\n",
+ "2 7496 62800 2.121104 2.117114\n",
+ "4 657 33500 2.119850 2.114387"
+ ]
+ },
+ "execution_count": 201,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Applying the ranking_songs function on the final_play data\n",
+ "ranking_songs(recommendations, df_songs_count_freq)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "KOwwGsH8toLG"
+ },
+ "source": [
+ "**As expected, the corrected play counts are slightly lower than the predicted ones**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "QgbzJKk7Tsnr"
+ },
+ "source": [
+ "### Item Item Similarity-based collaborative filtering recommendation systems"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "W5RMcdzjTsns",
+ "outputId": "08afb46f-58e9-49f5-d245-a69058791284",
+ "scrolled": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "RMSE: 1.0394\n",
+ "Precision: 0.307\n",
+ "Recall: 0.562\n",
+ "F_1 score: 0.397\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Apply the item-item similarity collaborative filtering model with random_state = 1 and evaluate the model performance\n",
+ "# Declaring the similarity options\n",
+ "similarity_options = dict(name='cosine', user_based=False)\n",
+ "\n",
+ "# KNN algorithm is used to find desired similar items. Use random_state=1\n",
+ "sim_item_item_model = KNNBasic(sim_options=similarity_options,\n",
+ " random_state=1, verbose=False)\n",
+ "\n",
+ "# Train the algorithm on the trainset, and predict ratings for the test set\n",
+ "sim_item_item_model.fit(trainset)\n",
+ "\n",
+ "# Let us compute precision@k, recall@k, and f_1 score\n",
+ "precision_recall_at_k(sim_item_item_model, testset=testset)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "JfdIJ6XWunx0"
+ },
+ "source": [
+ "**The F1 score of this basic item-item colaborative filtering model is 0.39**\n",
+ "- This is not a good score and is less than the F1 score of this basic user-user colaborative filtering model\n",
+ "- So for this case, considering similar user seems to be better than considering similar items when building the model."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "5yILOxXRTsns",
+ "outputId": "10f1f889-b55e-4e77-a985-7a46e36a1d70"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=1671, r_ui=2, est=1.3614157231762556, details={'actual_k': 20, 'was_impossible': False})"
+ ]
+ },
+ "execution_count": 203,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Predicting play count for a sample user_id 6958 and song (with song_id 1671) listened to by the user\n",
+ "user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2\n",
+ "sim_item_item_model.predict(user_id_interaction, song_id_interaction,\n",
+ " r_ui = play_count_interaction)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "jSn8oK3JZsTc",
+ "outputId": "6b9f052e-d189-461d-d9b3-3232325f4678"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "user_id chosen is: 8194 \n",
+ "song_id chosen and not listened by the user is: 1671\n"
+ ]
+ }
+ ],
+ "source": [
+ "song_id_no_interaction = 1671\n",
+ "\n",
+ "# Finding a user who has not listened to the song\n",
+ "user_id_no_interaction = n_users_not_listened_song(1, df_final,\n",
+ " song_id_no_interaction)[0]\n",
+ "\n",
+ "print('user_id chosen is:', user_id_no_interaction,\n",
+ " '\\nsong_id chosen and not listened by the user is:', song_id_no_interaction)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "iBtWpS3wkJn_",
+ "outputId": "263a7f3e-65d3-482b-a7ee-aba631b0ffdd"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=8194, iid=1671, r_ui=None, est=1.6455179645415152, details={'actual_k': 40, 'was_impossible': False})"
+ ]
+ },
+ "execution_count": 205,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Predict the play count for a user that has not listened to the song (with song_id 1671)\n",
+ "sim_item_item_model.predict(user_id_no_interaction, song_id_no_interaction)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "dxE9fJ8Dupby"
+ },
+ "source": [
+ "**The predicted play count** for the **interacted user-song pair is 1.36**. The **actual play count is 2**, so the prediction is far from the actual. The number of neighbors available was **20**.\n",
+ "\n",
+ "**The predicted play counts** the for the **user-song pair with no interaciton is 1.64.**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "f5bcZ3HgTsnt",
+ "outputId": "7602c2cc-f0c9-4109-ef9b-cae9c7c26b65"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Best RMSE score: 1.0263725500183078\n",
+ "best_params: {'k': 30, 'min_k': 3, 'sim_options': {'name': 'msd', 'min_support': 9, 'user_based': False}}\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Apply grid search for enhancing model performance\n",
+ "# Setting up parameter grid to tune the hyperparameters\n",
+ "params_grid_item = dict(k=[30, 50], min_k=[3, 6, 9],\n",
+ " sim_options=dict(name=['cosine', 'msd'],\n",
+ " min_support = [9, 20],\n",
+ " user_based=[False]))\n",
+ "\n",
+ "# Performing 3-fold cross-validation to tune the hyperparameters\n",
+ "gs = GridSearchCV(KNNBasic, param_grid=params_grid_item, cv=3,\n",
+ " measures=['rmse'], n_jobs=-1)\n",
+ "\n",
+ "# Fitting the data\n",
+ "gs.fit(data)\n",
+ "\n",
+ "# Find the best RMSE score\n",
+ "print(\"Best RMSE score:\", gs.best_score['rmse'])\n",
+ "\n",
+ "# Extract the combination of parameters that gave the best RMSE score\n",
+ "print(\"best_params:\", gs.best_params['rmse'])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "SXLxjLEQYvWk"
+ },
+ "source": [
+ "**Think About It:** How do the parameters affect the performance of the model? Can we improve the performance of the model further? Check the list of hyperparameters [here](https://surprise.readthedocs.io/en/stable/knn_inspired.html).\n",
+ " - Choosing the right set of parameters is critical to some algorithms.\n",
+ " - In this case, the similarity options and the number of neighbors enhance the performance of the models"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "dSeiM1qeTsnt",
+ "outputId": "a56655a1-f868-4d66-da64-246df5630774"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "RMSE: 1.0354\n",
+ "Precision: 0.394\n",
+ "Recall: 0.625\n",
+ "F_1 score: 0.483\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Apply the best model found in the grid search\n",
+ "# Using the optimal similarity measure for item-item based collaborative filtering\n",
+ "best_params_item = gs.best_params['rmse']\n",
+ "best_k_item = best_params_item['k']\n",
+ "best_min_k_item = best_params_item['min_k']\n",
+ "best_sim_options_item = best_params_item['sim_options']\n",
+ "\n",
+ "# Creating an instance of KNNBasic with optimal hyperparameter values\n",
+ "sim_item_item_tuned_model = KNNBasic(k = best_k_item, min_k=best_min_k_item,\n",
+ " sim_options=best_sim_options_item, randm_state=1,\n",
+ " verbose=False)\n",
+ "\n",
+ "# Training the algorithm on the trainset\n",
+ "sim_item_item_tuned_model.fit(trainset)\n",
+ "\n",
+ "# Let us compute precision@k and recall@k, f1_score and RMSE\n",
+ "precision_recall_at_k(sim_item_item_tuned_model, testset=testset)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "XxXelRIluvfh"
+ },
+ "source": [
+ "**The performance has increased compared to the baseline item-item model:**\n",
+ " - The F1 score of this tuned model is 0.49 while the the F1 score of the baseline item-item model was 0.39. So the perfomance has increased by 0.1 points."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "gIBRRvdoTsnt",
+ "outputId": "61613f1e-5384-43c9-f293-3984306f17b3"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=1671, r_ui=2, est=1.4512884989967099, details={'actual_k': 15, 'was_impossible': False})"
+ ]
+ },
+ "execution_count": 208,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Predict the play_count by a user(user_id 6958) for the song (song_id 1671)\n",
+ "user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2\n",
+ "sim_item_item_tuned_model.predict(user_id_interaction, song_id_interaction,\n",
+ " r_ui = play_count_interaction)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "LNEgcI9PTsnu",
+ "outputId": "3afbec3f-7150-4870-c59b-9cf71734bcc1"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=3232, r_ui=None, est=1.0, details={'actual_k': 4, 'was_impossible': False})"
+ ]
+ },
+ "execution_count": 209,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Predicting play count for a sample user_id 6958 with song_id 3232 which is not listened to by the user\n",
+ "user_id_no_interaction, song_id_no_interaction = (6958, 3232)\n",
+ "sim_item_item_tuned_model.predict(user_id_no_interaction, song_id_no_interaction)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "yf3kDSepuwcw"
+ },
+ "source": [
+ "**The predicted play count** for the **interacted user-song pair is 1.45**. The **actual play count is 2**, so the prediction is far from the actual. The number of neighbors available was **15**.\n",
+ "\n",
+ "**The predicted play count** the for the **user-song pair with no interaciton is 1.69.**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "ZRJS4oDFTsnu",
+ "outputId": "c61a7ce1-06b5-4f09-c855-44a43ad50a40"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[351, 324, 491, 128, 124]"
+ ]
+ },
+ "execution_count": 210,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Find five most similar items to the item with inner id 0\n",
+ "sim_item_item_tuned_model.get_neighbors(iid=0, k=5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "rzoEbuZFTsnu"
+ },
+ "outputs": [],
+ "source": [
+ "# Making top 5 recommendations for any user_id with item_item_similarity-based recommendation engine\n",
+ "recommendations = get_recommendations(df_final, user_id_interaction, 5,\n",
+ " sim_item_item_tuned_model)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 206
+ },
+ "id": "_kXVTiysTsnv",
+ "outputId": "b999242c-eedc-4f68-ac1f-ec63dccac3e8"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "summary": "{\n \"name\": \"recommendations_df\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"song_id\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3175,\n \"min\": 771,\n \"max\": 9160,\n \"num_unique_values\": 5,\n \"samples\": [\n 2342,\n 4949,\n 771\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"predicted_play_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.08600227845289393,\n \"min\": 1.7727520115837148,\n \"max\": 1.9630104191253326,\n \"num_unique_values\": 5,\n \"samples\": [\n 1.9602846793585966,\n 1.7727520115837148,\n 1.9173271606285103\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}",
+ "type": "dataframe",
+ "variable_name": "recommendations_df"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " song_id | \n",
+ " predicted_play_count | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 9160 | \n",
+ " 1.963010 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " 2342 | \n",
+ " 1.960285 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 771 | \n",
+ " 1.917327 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 4327 | \n",
+ " 1.820176 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 4949 | \n",
+ " 1.772752 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ " song_id predicted_play_count\n",
+ "0 9160 1.963010\n",
+ "1 2342 1.960285\n",
+ "2 771 1.917327\n",
+ "3 4327 1.820176\n",
+ "4 4949 1.772752"
+ ]
+ },
+ "execution_count": 212,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Building the dataframe for above recommendations with columns \"song_id\" and \"predicted_play_count\"\n",
+ "recommendations_df = pd.DataFrame(recommendations, columns=['song_id', 'predicted_play_count'])\n",
+ "recommendations_df"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 206
+ },
+ "id": "7gewfmTATsnv",
+ "outputId": "8043069d-73ac-4eae-e429-82bc0fd1d998"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "summary": "{\n \"name\": \"ranking_songs(recommendations, df_songs_count_freq)\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"song_id\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3175,\n \"min\": 771,\n \"max\": 9160,\n \"num_unique_values\": 5,\n \"samples\": [\n 2342,\n 4949,\n 771\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"play_freq\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 5144,\n \"min\": 14600,\n \"max\": 28100,\n \"num_unique_values\": 5,\n \"samples\": [\n 14600,\n 25900,\n 28100\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"predicted_play_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.08600227845289393,\n \"min\": 1.7727520115837148,\n \"max\": 1.9630104191253326,\n \"num_unique_values\": 5,\n \"samples\": [\n 1.9602846793585966,\n 1.7727520115837148,\n 1.9173271606285103\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"corrected_play_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.08558214722119833,\n \"min\": 1.7665383139237028,\n \"max\": 1.956528381889811,\n \"num_unique_values\": 5,\n \"samples\": [\n 1.9520086204725728,\n 1.7665383139237028,\n 1.9113616607657913\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}",
+ "type": "dataframe"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " song_id | \n",
+ " play_freq | \n",
+ " predicted_play_count | \n",
+ " corrected_play_count | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 2 | \n",
+ " 9160 | \n",
+ " 23800 | \n",
+ " 1.963010 | \n",
+ " 1.956528 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 2342 | \n",
+ " 14600 | \n",
+ " 1.960285 | \n",
+ " 1.952009 | \n",
+ "
\n",
+ " \n",
+ " | 0 | \n",
+ " 771 | \n",
+ " 28100 | \n",
+ " 1.917327 | \n",
+ " 1.911362 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 4327 | \n",
+ " 23700 | \n",
+ " 1.820176 | \n",
+ " 1.813681 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " 4949 | \n",
+ " 25900 | \n",
+ " 1.772752 | \n",
+ " 1.766538 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ " song_id play_freq predicted_play_count corrected_play_count\n",
+ "2 9160 23800 1.963010 1.956528\n",
+ "4 2342 14600 1.960285 1.952009\n",
+ "0 771 28100 1.917327 1.911362\n",
+ "3 4327 23700 1.820176 1.813681\n",
+ "1 4949 25900 1.772752 1.766538"
+ ]
+ },
+ "execution_count": 213,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Applying the ranking_songs function\n",
+ "ranking_songs(recommendations, df_songs_count_freq)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Ore9XTFgv5Np"
+ },
+ "source": [
+ "**Above we can see the list of the songs recommended by the user-user colaborative filtering model**.\n",
+ " - The songs recommended by this model vary from the ones recommended by the user-user model\n",
+ " - The predicted play counts vary beetwen 1.96 and 1.77\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "rKgJpSA9vOOL"
+ },
+ "source": [
+ "### Model Based Collaborative Filtering - Matrix Factorization"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "hJynidJCw-ti"
+ },
+ "source": [
+ "Model-based Collaborative Filtering is a **personalized recommendation system**, the recommendations are based on the past behavior of the user and it is not dependent on any additional information. We use **latent features** to find recommendations for each user."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "07-2PT5Ssjqm",
+ "outputId": "eb47bd52-e51e-4a97-c937-7982526615ef"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "RMSE: 1.0252\n",
+ "Precision: 0.41\n",
+ "Recall: 0.633\n",
+ "F_1 score: 0.498\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Build baseline model using svd\n",
+ "svd = SVD(random_state=1)\n",
+ "\n",
+ "# Training the algorithm on the trainset\n",
+ "svd.fit(trainset)\n",
+ "\n",
+ "# Use the function precision_recall_at_k to compute precision@k, recall@k, F1-Score, and RMSE\n",
+ "precision_recall_at_k(svd, testset=testset)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "yWIhfdxXsjqm",
+ "outputId": "e9e1d92d-2bcc-47e4-9826-2f28c71f75c6"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=1671, r_ui=2, est=1.267473397214638, details={'was_impossible': False})"
+ ]
+ },
+ "execution_count": 215,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Making prediction for user (with user_id 6958) to song (with song_id 1671), take r_ui = 2\n",
+ "user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2\n",
+ "svd.predict(user_id_interaction, song_id_interaction,\n",
+ " r_ui = play_count_interaction)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "APm-uMSvcAMf",
+ "outputId": "393ae385-c7a1-447e-c0cd-b8f84dd84c81"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=3232, r_ui=None, est=1.5561675084403663, details={'was_impossible': False})"
+ ]
+ },
+ "execution_count": 216,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Making a prediction for the user who has not listened to the song (song_id 3232)\n",
+ "user_id_no_interaction, song_id_no_interaction = (6958, 3232)\n",
+ "svd.predict(user_id_no_interaction, song_id_no_interaction)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "23tnRUJJxWTR"
+ },
+ "source": [
+ "#### Improving matrix factorization based recommendation system by tuning its hyperparameters"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "4bM81V_hvtwv",
+ "outputId": "1418d91b-f5d2-4eb4-900f-8ffa74d97f20"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "1.0042567317892475\n",
+ "{'n_epochs': 60, 'lr_all': 0.005, 'reg_all': 0.1, 'reg_bu': 0.1}\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Set the parameter space to tune\n",
+ "params = dict(n_epochs=[45, 60], lr_all=[0.0001, 0.005], reg_all=[0.05, 0.1],\n",
+ " reg_bu=[0.05, 0.1])\n",
+ "\n",
+ "# Performe 3-fold grid-search cross-validation\n",
+ "gs = GridSearchCV(SVD, param_grid=params, n_jobs=-1, measures=['rmse'], cv=3)\n",
+ "\n",
+ "# Fitting data\n",
+ "gs.fit(data)\n",
+ "\n",
+ "# Best RMSE score\n",
+ "print(gs.best_score['rmse'])\n",
+ "\n",
+ "# Combination of parameters that gave the best RMSE score\n",
+ "print(gs.best_params['rmse'])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "aSgBRcL1xnVC"
+ },
+ "source": [
+ "**Think About It**: How do the parameters affect the performance of the model? Can we improve the performance of the model further? Check the available hyperparameters [here](https://surprise.readthedocs.io/en/stable/matrix_factorization.html).\n",
+ " - This svd algorithm has a lot of tunnable parameters.\n",
+ " - Tweeking theses parameters might improve the predictions."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "TA_7xe-nnhuu",
+ "outputId": "2a33c2cd-b04b-431c-d0eb-15bba0308f58"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "RMSE: 1.0078\n",
+ "Precision: 0.416\n",
+ "Recall: 0.619\n",
+ "F_1 score: 0.498\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Building the optimized SVD model using optimal hyperparameters\n",
+ "best_params_svd = gs.best_params['rmse']\n",
+ "best_n_epochs = best_params_svd['n_epochs']\n",
+ "best_lr = best_params_svd['lr_all']\n",
+ "best_reg = best_params_svd['reg_all']\n",
+ "best_reg_bu = best_params_svd['reg_bu']\n",
+ "\n",
+ "svd_tuned = SVD(n_epochs=best_n_epochs, lr_all=best_lr, reg_all=best_reg,\n",
+ " reg_bu=best_reg_bu, random_state=1)\n",
+ "\n",
+ "# Train the algorithm on the trainset\n",
+ "svd_tuned.fit(trainset)\n",
+ "\n",
+ "# Use the function precision_recall_at_k to compute precision@k, recall@k, F1-Score, and RMSE\n",
+ "precision_recall_at_k(svd_tuned, testset=testset)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "l3t5JdBmxz8l"
+ },
+ "source": [
+ "The performance of this tuned model has not increased compared to the baseline svd model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "s6C1PAfboM8_",
+ "outputId": "ead17454-d4d6-4738-95d5-24f91351f7d9"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=1671, r_ui=2, est=1.466802714386287, details={'was_impossible': False})"
+ ]
+ },
+ "execution_count": 219,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Using svd_algo_optimized model to recommend for userId 6958 and song_id 1671\n",
+ "user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2\n",
+ "svd_tuned.predict(user_id_interaction, song_id_interaction,\n",
+ " r_ui = play_count_interaction)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "k1xjn3kOoQyg",
+ "outputId": "c6e677ff-cf9b-486b-a600-350852803704"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=3232, r_ui=None, est=1.5208469366289472, details={'was_impossible': False})"
+ ]
+ },
+ "execution_count": 220,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Using svd_algo_optimized model to recommend for userId 6958 and song_id 3232 with unknown baseline play_count\n",
+ "user_id_no_interaction, song_id_no_interaction = (6958, 3232)\n",
+ "svd_tuned.predict(user_id_no_interaction, song_id_no_interaction)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Qm732Wuvy76R"
+ },
+ "source": [
+ "**The predicted play count** for the **interacted user-song pair is 1.48**. The **actual play count is 2**, so the prediction is far from the actual.\n",
+ "\n",
+ "**The predicted play count** the for the **user-song pair with no interaction is 1.54.**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "1LGeE2EB_n90"
+ },
+ "outputs": [],
+ "source": [
+ "# Getting top 5 recommendations for user_id 6958 using \"svd_optimized\" algorithm\n",
+ "recommendations = get_recommendations(df_final, user_id_interaction, 5,\n",
+ " sim_item_item_tuned_model)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 206
+ },
+ "id": "6ngiGSJU818M",
+ "outputId": "d1d901a1-b71e-45c0-8c80-dc8f00bb3e28"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "summary": "{\n \"name\": \"recommendations_df\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"song_id\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3175,\n \"min\": 771,\n \"max\": 9160,\n \"num_unique_values\": 5,\n \"samples\": [\n 2342,\n 4949,\n 771\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"predicted_play_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.08600227845289393,\n \"min\": 1.7727520115837148,\n \"max\": 1.9630104191253326,\n \"num_unique_values\": 5,\n \"samples\": [\n 1.9602846793585966,\n 1.7727520115837148,\n 1.9173271606285103\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}",
+ "type": "dataframe",
+ "variable_name": "recommendations_df"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " song_id | \n",
+ " predicted_play_count | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 9160 | \n",
+ " 1.963010 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " 2342 | \n",
+ " 1.960285 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 771 | \n",
+ " 1.917327 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 4327 | \n",
+ " 1.820176 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 4949 | \n",
+ " 1.772752 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ " song_id predicted_play_count\n",
+ "0 9160 1.963010\n",
+ "1 2342 1.960285\n",
+ "2 771 1.917327\n",
+ "3 4327 1.820176\n",
+ "4 4949 1.772752"
+ ]
+ },
+ "execution_count": 222,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Ranking songs based on above recommendations\n",
+ "recommendations_df = pd.DataFrame(recommendations, columns=['song_id', 'predicted_play_count'])\n",
+ "recommendations_df"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "SepUU1Efy_9Z"
+ },
+ "source": [
+ "**Above we can see the list of the songs recommended by the matrix factorization colaborative filtering model**.\n",
+ " - The songs recommended by this model are the ones recommended by the item-item model\n",
+ " - The predicted play counts vary beetwen 1.96 and 1.77\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "57b31de5"
+ },
+ "source": [
+ "### Cluster Based Recommendation System"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "9Xv2AZCszCdN"
+ },
+ "source": [
+ "In **clustering-based recommendation systems**, we explore the **similarities and differences** in people's tastes in songs based on how they rate different songs. We cluster similar users together and recommend songs to a user based on play_counts from other users in the same cluster."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "0c4b20e4",
+ "outputId": "b55f3ef4-5c35-49ba-a9d5-99cc73dc901a"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "RMSE: 1.0487\n",
+ "Precision: 0.397\n",
+ "Recall: 0.582\n",
+ "F_1 score: 0.472\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Make baseline clustering model\n",
+ "clustering_model = CoClustering(random_state=1)\n",
+ "\n",
+ "# Train the algorithm on the trainset\n",
+ "clustering_model.fit(trainset)\n",
+ "\n",
+ "# Use the function precision_recall_at_k to compute precision@k, recall@k, F1-Score, and RMSE\n",
+ "precision_recall_at_k(clustering_model, testset=testset)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "11dbdc0f",
+ "outputId": "b3937519-cee0-4052-a784-38a586e9499b"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=1671, r_ui=2, est=1.2941824757363074, details={'was_impossible': False})"
+ ]
+ },
+ "execution_count": 224,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Making prediction for user_id 6958 and song_id 1671\n",
+ "user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2\n",
+ "clustering_model.predict(user_id_interaction, song_id_interaction,\n",
+ " r_ui = play_count_interaction)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "dab1aaed",
+ "outputId": "a92c01c3-c349-4d20-e328-9f920a1091dd"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=3232, r_ui=None, est=1.4785259100797417, details={'was_impossible': False})"
+ ]
+ },
+ "execution_count": 225,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Making prediction for user (userid 6958) for a song(song_id 3232) not listened to by the user\n",
+ "user_id_no_interaction, song_id_no_interaction = (6958, 3232)\n",
+ "clustering_model.predict(user_id_no_interaction, song_id_no_interaction)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "c2fd66f5"
+ },
+ "source": [
+ "#### Improving clustering-based recommendation system by tuning its hyper-parameters"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "efe7d8e6",
+ "outputId": "dfc6d0c6-002d-48be-8fa0-cb9230ee23f9"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "1.0292797210629505\n",
+ "{'n_cltr_u': 1, 'n_cltr_i': 1, 'n_epochs': 10}\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Set the parameter space to tune\n",
+ "param_grid = {'n_cltr_u': [1, 3, 10], 'n_cltr_i': [1, 3, 10 ],\n",
+ " 'n_epochs': [10, 30, 40]}\n",
+ "\n",
+ "# Performing 3-fold grid search cross-validation\n",
+ "gs = GridSearchCV(CoClustering, param_grid, measures = ['rmse'], cv = 3, n_jobs = -1)\n",
+ "\n",
+ "# Fitting data\n",
+ "gs.fit(data)\n",
+ "\n",
+ "# Best RMSE score\n",
+ "print(gs.best_score['rmse'])\n",
+ "\n",
+ "# Combination of parameters that gave the best RMSE score\n",
+ "print(gs.best_params['rmse'])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "CS6aMVJLyj21"
+ },
+ "source": [
+ "**Think About It**: How do the parameters affect the performance of the model? Can we improve the performance of the model further? Check the available hyperparameters [here](https://surprise.readthedocs.io/en/stable/co_clustering.html).\n",
+ " - The coclustering algorithm has some tunnable parameters.\n",
+ " - Tweeking theses parameters might improve the predictions."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "5a7a8a30",
+ "outputId": "0273c7a9-95c1-4a1f-82cb-9d9bea37ca21"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "RMSE: 1.0315\n",
+ "Precision: 0.395\n",
+ "Recall: 0.587\n",
+ "F_1 score: 0.472\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Train the tuned Coclustering algorithm\n",
+ "best_params_clust = gs.best_params['rmse']\n",
+ "best_n_cltr_u = best_params_clust['n_cltr_u']\n",
+ "best_n_cltr_i = best_params_clust['n_cltr_i']\n",
+ "best_n_epochs = best_params_clust['n_epochs']\n",
+ "\n",
+ "clustering_model_tuned = CoClustering(n_cltr_u=best_n_cltr_u,\n",
+ " n_cltr_i=best_n_cltr_i,\n",
+ " n_epochs=best_n_epochs,\n",
+ " random_state=1)\n",
+ "\n",
+ "# Train the algorithm on the trainset\n",
+ "clustering_model_tuned.fit(trainset)\n",
+ "\n",
+ "# Use the function precision_recall_at_k to compute precision@k, recall@k, F1-Score, and RMSE\n",
+ "precision_recall_at_k(clustering_model_tuned, testset=testset)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "l-Jvce1gznKa"
+ },
+ "source": [
+ "The performance of this tuned model has not increased compared to the baseline coclustering model."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "6ba5b26b",
+ "outputId": "316bec9c-bcb0-41da-9d39-c3ec197681e0"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=1671, r_ui=2, est=1.1257867112267959, details={'was_impossible': False})"
+ ]
+ },
+ "execution_count": 229,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Using co_clustering_optimized model to recommend for userId 6958 and song_id 1671\n",
+ "user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2\n",
+ "clustering_model_tuned.predict(user_id_interaction, song_id_interaction,\n",
+ " r_ui = play_count_interaction)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "ec582940",
+ "outputId": "8ec0c825-7b1a-4d41-ebfc-337100e9e8c6"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Prediction(uid=6958, iid=3232, r_ui=None, est=1.3101301455702306, details={'was_impossible': False})"
+ ]
+ },
+ "execution_count": 230,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Use Co_clustering based optimized model to recommend for userId 6958 and song_id 3232 with unknown baseline play_count\n",
+ "user_id_no_interaction, song_id_no_interaction = (6958, 3232)\n",
+ "clustering_model_tuned.predict(user_id_no_interaction, song_id_no_interaction)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "rjGUSMqrzoDH"
+ },
+ "source": [
+ "**The predicted play count** for the **interacted user-song pair is 1.12**. The **actual play count is 2**, so the prediction is far from the actual.\n",
+ "\n",
+ "**The predicted play count** the for the **user-song pair with no interaction is 1.31.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "df9e28ba"
+ },
+ "source": [
+ "#### Implementing the recommendation algorithm based on optimized CoClustering model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "e0f36e15"
+ },
+ "outputs": [],
+ "source": [
+ "# Getting top 5 recommendations for user_id 6958 using \"Co-clustering based optimized\" algorithm\n",
+ "recommendations = get_recommendations(df_final, user_id_interaction, 5,\n",
+ " clustering_model_tuned)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "f1696941"
+ },
+ "source": [
+ "### Correcting the play_count and Ranking the above songs"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 206
+ },
+ "id": "c186f13b",
+ "outputId": "98a48d5d-6fd4-4914-b989-7acae9178a2f",
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "summary": "{\n \"name\": \"ranking_songs(recommendations, df_songs_count_freq)\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"song_id\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1675,\n \"min\": 5653,\n \"max\": 9942,\n \"num_unique_values\": 5,\n \"samples\": [\n 6450,\n 9942,\n 8324\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"play_freq\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 6209,\n \"min\": 24800,\n \"max\": 37300,\n \"num_unique_values\": 5,\n \"samples\": [\n 26300,\n 37300,\n 25200\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"predicted_play_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.36395982819857486,\n \"min\": 2.046643632083717,\n \"max\": 2.926401555319901,\n \"num_unique_values\": 5,\n \"samples\": [\n 2.1666642364793214,\n 2.046643632083717,\n 2.1431027285428135\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"corrected_play_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.3642352542456686,\n \"min\": 2.041465828352932,\n \"max\": 2.9211383974251643,\n \"num_unique_values\": 5,\n \"samples\": [\n 2.1604979723195394,\n 2.041465828352932,\n 2.136803320659326\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}",
+ "type": "dataframe"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " song_id | \n",
+ " play_freq | \n",
+ " predicted_play_count | \n",
+ " corrected_play_count | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1 | \n",
+ " 7224 | \n",
+ " 36100 | \n",
+ " 2.926402 | \n",
+ " 2.921138 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 6450 | \n",
+ " 26300 | \n",
+ " 2.166664 | \n",
+ " 2.160498 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 8324 | \n",
+ " 25200 | \n",
+ " 2.143103 | \n",
+ " 2.136803 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 5653 | \n",
+ " 24800 | \n",
+ " 2.118781 | \n",
+ " 2.112431 | \n",
+ "
\n",
+ " \n",
+ " | 0 | \n",
+ " 9942 | \n",
+ " 37300 | \n",
+ " 2.046644 | \n",
+ " 2.041466 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ " song_id play_freq predicted_play_count corrected_play_count\n",
+ "1 7224 36100 2.926402 2.921138\n",
+ "2 6450 26300 2.166664 2.160498\n",
+ "3 8324 25200 2.143103 2.136803\n",
+ "4 5653 24800 2.118781 2.112431\n",
+ "0 9942 37300 2.046644 2.041466"
+ ]
+ },
+ "execution_count": 232,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Ranking songs based on the above recommendations\n",
+ "ranking_songs(recommendations, df_songs_count_freq)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "_uJ_nZjBzvKH"
+ },
+ "source": [
+ "**Above we can see the list of the songs recommended by the svd model**.\n",
+ " - the top recommendation is the song 7224 with 2.93 play_counts.\n",
+ " - the fifth recommendation is the song 4831 with 2.34 playcounts.\n",
+ " - all the recommended songs have a predicted playcounts greater than 2.\n",
+ " - the songs are the same recommended by the user-user colaborative filtering model."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "5U56oSNsR-F2"
+ },
+ "source": [
+ "### Content Based Recommendation Systems"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "9aTEqaOjhoEg"
+ },
+ "source": [
+ "**Think About It:** So far we have only used the play_count of songs to find recommendations but we have other information/features on songs as well. Can we take those song features into account?\n",
+ " - Yes we can recommend songs which features are similar to a target song"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "UX826CsjR-F3"
+ },
+ "outputs": [],
+ "source": [
+ "# Concatenate the \"title\", \"release\", \"artist_name\" columns to create a different column named \"text\"\n",
+ "df_final['text'] = df_final['title'] + ' ' + df_final['release'] + ' ' + df_final['artist_name']"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 237
+ },
+ "id": "WdXw4U-wR-F4",
+ "outputId": "529d6f9a-c6aa-40d1-c8fe-b13506680047"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "summary": "{\n \"name\": \"df_small\",\n \"rows\": 561,\n \"fields\": [\n {\n \"column\": \"title\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 561,\n \"samples\": [\n \"Lost\",\n \"Livin' On A Prayer\",\n \"Alejandro\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"user_id\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 22733,\n \"min\": 169,\n \"max\": 76331,\n \"num_unique_values\": 481,\n \"samples\": [\n 20124,\n 39552,\n 8682\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"song_id\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 2898,\n \"min\": 21,\n \"max\": 9989,\n \"num_unique_values\": 561,\n \"samples\": [\n 4564,\n 2610,\n 7103\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"play_count\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1,\n \"min\": 1,\n \"max\": 5,\n \"num_unique_values\": 5,\n \"samples\": [\n 1,\n 5,\n 3\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"text\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 561,\n \"samples\": [\n \"Lost One Of The Boys Katy Perry\",\n \"Livin' On A Prayer Cross Road Bon Jovi\",\n \"Alejandro The Fame Monster Lady GaGa\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}",
+ "type": "dataframe",
+ "variable_name": "df_small"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " user_id | \n",
+ " song_id | \n",
+ " play_count | \n",
+ " text | \n",
+ "
\n",
+ " \n",
+ " | title | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Step Through The Door | \n",
+ " 64559 | \n",
+ " 211 | \n",
+ " 2 | \n",
+ " Step Through The Door Hell Train Soltero | \n",
+ "
\n",
+ " \n",
+ " | Here Without You | \n",
+ " 19725 | \n",
+ " 5697 | \n",
+ " 1 | \n",
+ " Here Without You Here Without You 3 Doors Down | \n",
+ "
\n",
+ " \n",
+ " | Revelry | \n",
+ " 57650 | \n",
+ " 7416 | \n",
+ " 1 | \n",
+ " Revelry Only By The Night Kings Of Leon | \n",
+ "
\n",
+ " \n",
+ " | Trouble | \n",
+ " 62621 | \n",
+ " 9702 | \n",
+ " 1 | \n",
+ " Trouble Trouble (B-Side) Coldplay | \n",
+ "
\n",
+ " \n",
+ " | OMG | \n",
+ " 57336 | \n",
+ " 8092 | \n",
+ " 2 | \n",
+ " OMG OMG - The Remixes Usher featuring will.i.am | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ " user_id song_id play_count \\\n",
+ "title \n",
+ "Step Through The Door 64559 211 2 \n",
+ "Here Without You 19725 5697 1 \n",
+ "Revelry 57650 7416 1 \n",
+ "Trouble 62621 9702 1 \n",
+ "OMG 57336 8092 2 \n",
+ "\n",
+ " text \n",
+ "title \n",
+ "Step Through The Door Step Through The Door Hell Train Soltero \n",
+ "Here Without You Here Without You Here Without You 3 Doors Down \n",
+ "Revelry Revelry Only By The Night Kings Of Leon \n",
+ "Trouble Trouble Trouble (B-Side) Coldplay \n",
+ "OMG OMG OMG - The Remixes Usher featuring will.i.am "
+ ]
+ },
+ "execution_count": 234,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Selecting a random subset of the original data\n",
+ "df_small = df_final.sample(n = 100000, random_state = 42)\n",
+ "\n",
+ "# Select the columns 'user_id', 'song_id', 'play_count', 'title', 'text' from df_small data\n",
+ "df_small = df_small[['user_id', 'song_id', 'play_count', 'title', 'text']]\n",
+ "\n",
+ "# Drop the duplicates from the title column\n",
+ "df_small = df_small.drop_duplicates('title')\n",
+ "\n",
+ "# Set the title column as the index\n",
+ "df_small.set_index('title', inplace=True)\n",
+ "\n",
+ "# See the first 5 records of the df_small dataset\n",
+ "df_small.head(5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "qDcYHwZTR-F5",
+ "outputId": "ef4ef780-422c-4dda-9c38-03a90d444eae"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0 Step Through The Door\n",
+ "1 Here Without You\n",
+ "2 Revelry\n",
+ "3 Trouble\n",
+ "4 OMG\n",
+ "Name: title, dtype: object"
+ ]
+ },
+ "execution_count": 235,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Create the series of indices from the data\n",
+ "indices = pd.Series(df_small.index)\n",
+ "\n",
+ "# Let us see the first 5 indices\n",
+ "indices[:5]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "9UINF3Nwvwfr",
+ "outputId": "0a19544b-580f-41c7-eb64-08696db6e538"
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "[nltk_data] Downloading package punkt to /root/nltk_data...\n",
+ "[nltk_data] Package punkt is already up-to-date!\n",
+ "[nltk_data] Downloading package stopwords to /root/nltk_data...\n",
+ "[nltk_data] Package stopwords is already up-to-date!\n",
+ "[nltk_data] Downloading package wordnet to /root/nltk_data...\n",
+ "[nltk_data] Package wordnet is already up-to-date!\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Importing necessary packages to work with text data\n",
+ "import nltk\n",
+ "\n",
+ "# Download punkt library\n",
+ "nltk.download(\"punkt\")\n",
+ "\n",
+ "# Download stopwords library\n",
+ "nltk.download('stopwords')\n",
+ "\n",
+ "# Download wordnet\n",
+ "nltk.download('wordnet')\n",
+ "\n",
+ "# Import regular expression\n",
+ "import re\n",
+ "\n",
+ "# Import word_tokenizer\n",
+ "from nltk import word_tokenize\n",
+ "\n",
+ "# Import WordNetLemmatizer\n",
+ "from nltk.stem import WordNetLemmatizer\n",
+ "\n",
+ "# Import stopwords\n",
+ "from nltk.corpus import stopwords\n",
+ "\n",
+ "# Import CountVectorizer and TfidfVectorizer\n",
+ "from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Jt2vitlnhoEg"
+ },
+ "source": [
+ "We will create a **function to pre-process the text data:**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "j5QSSeUvR-F6"
+ },
+ "outputs": [],
+ "source": [
+ "# Create a function to tokenize the text\n",
+ "def tokenize(text):\n",
+ "\n",
+ " # Making each letter as lowercase and removing non-alphabetical text\n",
+ " text = re.sub(r\"[^a-zA-Z]\", \" \", text.lower())\n",
+ "\n",
+ " # Extracting each word in the text\n",
+ " tokens = word_tokenize(text)\n",
+ "\n",
+ " # Removing stopwords\n",
+ " words = [word for word in tokens if word not in stopwords.words(\"english\")]\n",
+ "\n",
+ " # Lemmatize the words\n",
+ " text_lems = [WordNetLemmatizer().lemmatize(lem).strip() for lem in words]\n",
+ "\n",
+ " return text_lems"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "RI_onIGdR-F6"
+ },
+ "outputs": [],
+ "source": [
+ "# Create tfidf vectorizer\n",
+ "tfidf = TfidfVectorizer(tokenizer = tokenize)\n",
+ "\n",
+ "# Fit_transfrom the above vectorizer on the text column and then convert the output into an array\n",
+ "songs_tfidf = tfidf.fit_transform(df_small['text'].values).toarray()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Beak6ODRR-F7"
+ },
+ "outputs": [],
+ "source": [
+ "# Compute the cosine similarity for the tfidf above output\n",
+ "similar_songs = cosine_similarity(songs_tfidf, songs_tfidf)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "3Jjo3UHKhoEh"
+ },
+ "source": [
+ " Finally, let's create a function to find most similar songs to recommend for a given song."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "upANOISkR-F8"
+ },
+ "outputs": [],
+ "source": [
+ "# Function that takes in song title as input and returns the top 10 recommended songs\n",
+ "def recommendations(title, similar_songs, indices = indices, data = df_small):\n",
+ "\n",
+ " recommended_songs = []\n",
+ "\n",
+ " # Getting the index of the song that matches the title\n",
+ " idx = indices[indices == title].index[0]\n",
+ "\n",
+ " # Creating a Series with the similarity scores in descending order\n",
+ " score_series = pd.Series(similar_songs[idx]).sort_values(ascending = False)\n",
+ "\n",
+ " # Getting the indexes of the 10 most similar songs\n",
+ " top_10_indexes = list(score_series.iloc[1:11].index)\n",
+ "\n",
+ " # Populating the list with the titles of the best 10 matching songs\n",
+ " for i in top_10_indexes:\n",
+ " recommended_songs.append(list(data.index)[i])\n",
+ "\n",
+ " return recommended_songs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "o4EINBmkR-F8"
+ },
+ "source": [
+ "Recommending 10 songs similar to Learn to Fly"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "ohEK5dkVR-F8",
+ "outputId": "5fa2143a-ad6a-46f7-cfaf-fcf9ab79b3db"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['Everlong',\n",
+ " 'The Pretender',\n",
+ " 'Nothing Better (Album)',\n",
+ " 'From Left To Right',\n",
+ " 'Lifespan Of A Fly',\n",
+ " 'Politik',\n",
+ " 'You Belong With Me',\n",
+ " 'Closer',\n",
+ " 'Fences',\n",
+ " \"I Can't Stay\"]"
+ ]
+ },
+ "execution_count": 241,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Make the recommendation for the song with title 'Learn To Fly'\n",
+ "recommendations(\"Learn To Fly\", similar_songs, indices, df_small)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "UQ7iI5QJ0oem"
+ },
+ "source": [
+ "**The list above contains the songs predicted similar to the song Learn To Fly**\n",
+ " - To verify that the recommendation is working, we can check if the \"title\", \"release\" and \"artist_name\" of the predicted songs are similar to the ones of the target song. They seems to be different.\n",
+ " - We need more compelling features for this task, the genre of the songs or some tags describing each songs ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "73367782"
+ },
+ "source": [
+ "## **Conclusion and Recommendations**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "N5BT7Ocwqf5x"
+ },
+ "source": [
+ "**1. Comparison of various techniques and their relative performance based on chosen Metric (Measure of success)**:\n",
+ "- How do different techniques perform? Which one is performing relatively better? Is there scope to improve the performance further?\n",
+ "\n",
+ "- The F1 scores of the models built so far are:\n",
+ " - user-user colaborative filtering baseline model: 0.504\n",
+ " - user-user colaborative filtering tuned model: **0.513**\n",
+ " - item-item colaborative filtering baseline model: 0.397\n",
+ " - item-item colaborative filtering tuned model: 0.483\n",
+ " - matrix factorization baseline model: 0.498\n",
+ " - matrix factorization tuned model: 0.504\n",
+ " - coclustering baseline model: 0.472\n",
+ " - coclustering tuned model: 0.472\n",
+ " - content based model: no score computed\n",
+ " - popularity based model : no score computed\n",
+ "\n",
+ "- The model with the best F1 score is the **user-user colaborative filtering tuned model**.\n",
+ "- The F1 scores are very low. We can definitely improve these scores. This can be done by testing different technics, such as:\n",
+ " - data balancing\n",
+ " - changing the cutoffs thresholds.\n",
+ " - customizing the implementation of the algorithms for this specific problem\n",
+ " - collecting more data if possible\n",
+ " - building hybrid recommendations systems. This can be done by analyzing the observations for which the model is not performing well and use another model which performs better on those observations. Thus combining the predictions of both models at the same time.\n",
+ " - For the content based model, we need more features describing the songs, for example the genres of the songs. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "wjc6vTcoqp6v"
+ },
+ "source": [
+ "**2. Refined insights**:\n",
+ "- What are the most meaningful insights from the data relevant to the problem?\n",
+ "- The data is imbalanced, most of the play counts are 1 and 2, very few are 3, 4 or 5.\n",
+ "- Adding more boservations with **high interactions** will increase the performance of the models.\n",
+ "- Some observations date from 1969, we need further analysis to see how the rejection of these observations impacts the quality of the predictions. This could increase performance as it would likely reduce the sparsity of matrices and improve similarity measures. some old songs are no longer listened to and users tastes may have changed.\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "hK6PMGUtoxVx"
+ },
+ "source": [
+ "**3. Proposal for the final solution design:**\n",
+ "- What model do you propose to be adopted? Why is this the best solution to adopt?\n",
+ "- Based on this analysis, the proposed model is the **user-user colaborative filtering tuned model**. This model has the best F1 score and the metric suited for this business is the F1 score.\n",
+ "- We can increase the performance of this model by investigating further technics as mentionned above."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "72JfyrsI5a2A"
+ },
+ "source": [
+ "## **Further analysis**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "jYXH6kxj592j"
+ },
+ "source": [
+ "Testing the cutoffs thresholds and discarding old songs"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "oyw459hFL9nM"
+ },
+ "outputs": [],
+ "source": [
+ "df = pd.merge(count_df, song_df.drop_duplicates('song_id'), how='left')\n",
+ "\n",
+ "# Apply label encoding for \"user_id\" and \"song_id\"\n",
+ "from sklearn.preprocessing import LabelEncoder\n",
+ "\n",
+ "#storing transformers in a dictionnary for decoding if needed\n",
+ "label_object = {}\n",
+ "categorical_columns = ['user_id','song_id']\n",
+ "for col in categorical_columns:\n",
+ " labelencoder = LabelEncoder()\n",
+ " labelencoder.fit(df[col])\n",
+ " df[col] = labelencoder.fit_transform(df[col])\n",
+ " label_object[col] = labelencoder\n",
+ "\n",
+ "#saving the original df in df_copy\n",
+ "df_copy = df.copy()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "83YuTno7IqxA"
+ },
+ "outputs": [],
+ "source": [
+ "# Get the column containing the users\n",
+ "users = df.user_id\n",
+ "\n",
+ "# Create a dictionary that maps users(listeners) to the number of songs that they have listened to\n",
+ "playing_count = dict()\n",
+ "\n",
+ "for user in users:\n",
+ " # If we already have the user, just add 1 to their playing count\n",
+ " if user in playing_count:\n",
+ " playing_count[user] += 1\n",
+ "\n",
+ " # Otherwise, set their playing count to 1\n",
+ " else:\n",
+ " playing_count[user] = 1"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "HZoCsXxEIwCL"
+ },
+ "outputs": [],
+ "source": [
+ "# We want our users to have listened at least 90 songs\n",
+ "SONG_COUNT_CUTOFF = 160\n",
+ "\n",
+ "# Create a list of users who need to be removed\n",
+ "remove_users = []\n",
+ "\n",
+ "for user, num_songs in playing_count.items():\n",
+ "\n",
+ " if num_songs < SONG_COUNT_CUTOFF:\n",
+ " remove_users.append(user)\n",
+ "\n",
+ "df = df.loc[ ~ df.user_id.isin(remove_users)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "AUZ7CQ2fHeVO"
+ },
+ "outputs": [],
+ "source": [
+ "# Get the column containing the songs\n",
+ "songs = df.song_id\n",
+ "\n",
+ "# Create a dictionary that maps songs to its number of users(listeners)\n",
+ "playing_count = dict()\n",
+ "\n",
+ "for song in songs:\n",
+ " # If we already have the song, just add 1 to their playing count\n",
+ " if song in playing_count:\n",
+ " playing_count[song] += 1\n",
+ "\n",
+ " # Otherwise, set their playing count to 1\n",
+ " else:\n",
+ " playing_count[song] = 1"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "lKnSEWJ86PPN"
+ },
+ "outputs": [],
+ "source": [
+ "# We want our song to be listened by at least 120 users to be considred\n",
+ "LISTENER_COUNT_CUTOFF = 160\n",
+ "\n",
+ "remove_songs = []\n",
+ "\n",
+ "for song, num_users in playing_count.items():\n",
+ " if num_users < LISTENER_COUNT_CUTOFF:\n",
+ " remove_songs.append(song)\n",
+ "\n",
+ "df_final = df.loc[ ~ df.song_id.isin(remove_songs)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "UCF-zbM2GuRA",
+ "outputId": "038f25c1-0515-4570-8cd8-f0f804e9c3d8"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(8987, 7)"
+ ]
+ },
+ "execution_count": 247,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df_final.shape"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "kzNqKO2r74oj"
+ },
+ "outputs": [],
+ "source": [
+ "# Keeping the records with play counts greater than 5 by decreasing their play_count to 5\n",
+ "df_final.loc[df_final.play_count>5, 'play_count'] = 5\n",
+ "\n",
+ "# discarding old observations\n",
+ "df_final = df_final[(df_final.year > 1999) | (df_final.year == 0)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "7CB4T3GCHnWn",
+ "outputId": "f38c4c53-e0c8-4e54-e692-fc2d625296a5"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(8430, 7)"
+ ]
+ },
+ "execution_count": 249,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df_final.shape"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 480
+ },
+ "id": "Zw1lyEIK7d7k",
+ "outputId": "b7fdef7d-1e77-4f33-b6ad-ac5bdab2c4f7"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlwAAAHPCAYAAACGBgTAAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAxqklEQVR4nO3deXQUZaLG4bdDEshCgJDEiGwBBA0ghGT0imwCKgNuOTDgig4jA8hR5IjLyCDiFRVR5nIV9F5Ag6AOEBaRCRw2wSCQqOAgi7IEhrAEAjGEEIGE7vuHlx5jEgiVru7+2t9zDudQS1e/XTTkpeqrKofL5XIJAAAAtgnydQAAAIBAR+ECAACwGYULAADAZhQuAAAAm1G4AAAAbEbhAgAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALBZsK8D2OHHH39UWVmZr2NcVmxsrPLz830d44qZmNvEzJK5uU1k6r4mt/eYmFkit92Cg4PVoEGDy6/nhSxeV1ZWptLSUl/HuCSHwyHp56wmPc7SxNwmZpbMzW0iU/c1ub3HxMwSuf0JpxQBAABsRuECAACwGYULAADAZhQuAAAAm1G4AAAAbEbhAgAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwGYULAADAZsG+DgAAACq6MPRuj2wn1yNbkWrNWOqhLf02cYQLAADAZhQuAAAAm1G4AAAAbEbhAgAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwGYULAADAZhQuAAAAm1G4AAAAbEbhAgAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwGYULAADAZhQuAAAAm1G4AAAAbEbhAgAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwWbCvA/yS0+nU/PnzlZmZqcLCQkVHR6t79+7q37+/HA6Hr+MBAABY4leFa8mSJVq1apVGjhypxo0bKycnR9OnT1d4eLj69u3r63gAAACW+FXh2r17t1JSUtSpUydJUlxcnDZs2KC9e/f6OBkAAIB1flW4WrdurTVr1ujIkSNq1KiRDhw4oB9++EGDBw+udP3S0lKVlpa6px0Oh8LCwty/92cX8/l7zl8zMbeJmSVzc5vI1H1Nbu8xMbOnefOzB+L+drhcLpevQ1zkdDr1ySefaOnSpQoKCpLT6dR9992n1NTUStefP3++0tPT3dMJCQmaNGmSt+ICAGCb3H4pvo5QTpN/fO3rCEbzqyNcmzZt0oYNG/Tkk0+qSZMmOnDggNLS0tSgQQP16NGjwvqpqam688473dMXm3B+fr7Kysq8FdsSh8Oh+Ph45eXlyY8672WZmNvEzJK5uU1k6r4mt/eYmNnTjh496rX3Mml/BwcHKzY29vLreSFLtc2dO1f33HOPbrnlFklS06ZNlZ+fryVLllRauEJCQhQSElLptvz9D+gil8tlTNZfMjG3iZklc3ObyNR9TW7vMTGzp/jicwfS/var+3CdO3dOQUHlIwUFBQXMzgYAAL9NfnWEKzk5WYsWLVJMTIwaN26sAwcOaNmyZbr11lt9HQ0AAMAyvypcQ4YM0bx58zRz5kydOnVK0dHRuu222zRgwABfRwMAALDMrwpXWFiYHn30UT366KO+jgIAAOAxfjWGCwAAIBBRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwGYULAADAZhQuAAAAm1G4AAAAbEbhAgAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwGYULAADAZhQuAAAAm1G4AAAAbEbhAgAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwWXB1Vtq5c6eljScmJlp6HQAAQCCpVuGaMGGCpY3PmzfP0usAAAACSbUK1/jx48tNl5aWau7cuTp//rx69eqlRo0aSZKOHDmiNWvWqHbt2nrooYc8nxYAAMBA1Spcvz41OHv2bAUHB2vixIkKDQ0tt+yOO+7QSy+9pG+//VY33HCD55ICAAAYytKg+Q0bNqhbt24VypYk1a5dW127dlVmZmaNwwEAAAQCS4Xr7Nmz+vHHH6tcXlhYqHPnzlkOBQAAEEgsFa727dtr+fLlysrKqrBs8+bNysjIUPv27WscDgAAIBBUawzXrz322GOaMGGCpkyZogYNGig+Pl6SdOzYMRUUFCg+Pl5DhgzxaFAAAABTWSpc0dHRmjx5slavXq2tW7fqxIkTkqTGjRvrrrvuUu/evSsd3wUAAPBbZKlwSVJoaKj69u2rvn37ejIPAABAwLFcuKSf78e1f/9+nTp1Sm3atFFUVJSncgEAAAQMy4UrIyNDCxYsUElJiSRp3LhxateunYqKijR69Gg9+OCD6tmzp8eCAgAAmMrSVYqff/65Zs+erY4dO2rEiBHllkVFRalt27bauHGjRwICAACYzlLhWrZsmVJSUjRq1CglJydXWN6iRQvl5ubWOBwAAEAgsFS48vLylJSUVOXyyMhIFRcXWw4FAAAQSCwVrvDwcBUVFVW5/NChQ6pfv77VTAAAAAHFUuFKSkrSmjVrdObMmQrLcnNztWbNmkpPNQIAAPwWWbpK8b777tPYsWP19NNPu4vVunXrtHbtWmVlZalBgwYaMGCAR4MCAACYyvKd5l9//XV98skn7qsRMzMzVadOHd1yyy168MEHuScXAADA/7N8H6569epp+PDhGj58uIqKiuR0OhUVFaWgIEtnKQEAAAKWpXa0ceNGnT9/3j0dFRWl+vXrU7YAAAAqYekI19SpUxUWFqabbrpJXbt2Vbt27TydCwAAIGBYKlwvv/yyMjMztXnzZq1bt07R0dHq0qWLunbtqqZNm3o6IwAAgNEsFa42bdqoTZs2+uMf/6hvv/1WmZmZWrFihZYuXaqmTZuqW7du6tKlixo0aODpvAAAAMaxPGhekmrVqqXk5GQlJyfr7Nmzys7O1rp16zR37lx9/PHHSkxMVPfu3dW5c2cFB9forQAAAIzlsVHuBw8e1N69e3Xw4EFJUqNGjVRcXKxp06bpiSee0Pfff++ptwIAADBKjQ47HTlyRBs2bNCGDRt07NgxRUVFqUuXLurWrZtatGghSdq3b5/ee+89zZgxQ2+99ZZHQgMAAJjEUuHKyMhQZmamcnJyFBISouTkZD366KPq2LFjhVtDtGzZUv369dN7773nkcAAAACmsVS4Zs+erTZt2mjo0KHq3LmzwsPDL7l+y5Yt1b9/f0sBAQAATGepcL399tuKi4ur9vpNmjRRkyZNrLwVAACA8SwNmr+SsgUAAPBbZ3nQ/Pnz55WVlaX9+/erpKRETqez3HKHw6ERI0bUOCAAAIDpLBWu/Px8TZgwQfn5+QoPD1dJSYkiIyPdxatu3bqqU6eOp7MCAAAYydIpxTlz5qikpEQTJ07U1KlTJUmjR4/Whx9+qAcffFChoaEaO3asR4MCAACYylLh2rFjh26//Xa1atXKfRsIl8ulkJAQ3X333WrXrp3S0tI8mRMAAMBYlgrXuXPn3APnw8LCJEklJSXu5a1bt+bO8gAAAP/PUuGKiYnRyZMnJf38PMXo6Gjt2bPHvfzQoUMKDQ31TEIAAADDWRo0365dO3399df6wx/+IEnq0aOHlixZouLiYrlcLn3xxRfq3r27R4MCAACYylLhuvfee7V3716VlpYqJCREqamp+vHHH5WVlaWgoCB16dJFgwcP9nRWAAAAI1kqXDExMYqJiXFPh4aGavjw4Ro+fHiNAxUUFGju3Ln69ttvde7cOcXHx+vxxx9Xy5Yta7xtAAAAX7B841M7FBcXa9y4cWrbtq1eeOEFRUVF6ejRo4qIiPB1NAAAAMuqVbjS09MtbXzAgAFXtP6nn36qhg0b6vHHH3fP4zFCAADAdNUqXAsWLLC08SstXF9//bU6dOigKVOmaOfOnYqOjtbtt9+u3r17W3p/AAAAf1CtwjVv3jy7c0iSjh8/rlWrVqlfv35KTU3Vvn379MEHHyg4OFg9evSosH5paalKS0vd0w6Hw31fMIfD4ZXMVl3M5+85f83E3CZmlszNbSJT9zW5vcfEzJ7mzc8eiPvbr8ZwOZ1OtWzZUg888IAkKSEhQQcPHtSqVasqLVyLFy8ud7ozISFBkyZNUmxsrLci11h8fLyvI1hiYm4TM0vm5jaRqfua3JeW2y/FM9vxyFZ+1uQfX3v1/Tzh6quv9vp7mvrdrkyNCldxcbG2bdum/Px8SVJsbKzat2+vunXrWtpegwYN1Lhx43LzGjdurKysrErXT01N1Z133umevtiE8/PzVVZWZimDtzgcDsXHxysvL08ul8vXcarNxNwmZpbMzW0iU/c1uc119OhRX0e4Yt7MbNJ3JDg4uFoHeiwXrvnz5+vTTz+tUGyCg4N19913a9CgQVe8zTZt2ujIkSPl5h05cqTKDxISEqKQkJBKl/n7H9BFLpfLmKy/ZGJuEzNL5uY2kan7mtzmMfFz+yJzIH1HLBWu9PR0LVy4UElJSerTp48aNWok6edytGLFCi1atEi1atW64kHz/fr107hx47Ro0SJ17txZe/fu1Zo1a/TnP//ZSkwAAAC/YKlwrVq1SsnJyXr22WfLzY+Li1PHjh31+uuva9WqVVdcuFq1aqUxY8bo448/1sKFCxUXF6dHHnlEXbt2tRITAADAL1gqXCUlJerYsWOVyzt16qSdO3daCpScnKzk5GRLrwUAAPBHQVZedN1112nPnj1VLt+zZ4/atGljORQAAEAgsVS4hg4dqt27dystLU15eXlyOp1yOp3Ky8tTWlqa9uzZo6FDh3o6KwAAgJEsnVIcM2aMXC6Xli9fruXLlyso6Ofe5nQ6Jf189eAzzzxT4XWzZ8+uQVQAAAAzWSpcN910U0Dd/RUAAMBOlgrXyJEjPZ0DAAAgYFkawwUAAIDqo3ABAADYjMIFAABgMwoXAACAzShcAAAANqNwAQAA2MzSbSFOnDihEydO6LrrrnPPO3DggJYtW6bS0lLdcsstuvHGGz0WEgAAwGSWjnC9//77WrBggXu6sLBQEyZMUFZWlnbt2qW33npLWVlZHgsJAABgMkuFa9++fWrfvr17+osvvtD58+c1efJkvffee2rfvr0+++wzj4UEAAAwmaXCVVxcrHr16rmnv/nmGyUmJio+Pl5BQUG68cYbdfjwYY+FBAAAMJmlwhUVFaX8/HxJ0pkzZ7Rnzx516NDBvdzpdLofZA0AAPBbZ2nQfPv27bV8+XKFh4drx44dcrlc5QbJHzp0SA0bNvRYSAAAAJNZKlwPPPCAjh49qjlz5ig4OFgPP/yw4uLiJEmlpaXatGmTbrnlFo8GBQAAMJWlwlW/fn3953/+p0pKShQaGqrg4H9vxuVyady4cYqJifFYSAAAAJNZKlwXhYeHV5gXGhqq5s2b12SzAAAAAcVy4Tpx4oQWLVqkHTt2qKioSM8884wSExNVVFSk9PR03XrrrUpISPBkVgAAACNZukrx0KFDevbZZ7Vp0ybFxcWppKTEfVViVFSUfvjhB61YscKjQQEAAExlqXDNnTtXERERmjp1qp544okKy5OSkvT999/XOBwAAEAgsFS4du3apdtuu01RUVFyOBwVlsfExKigoKDG4QAAAAKBpcLldDpVu3btKpcXFRWVu3IRAADgt8xS4WrRooW2bNlS6bILFy5o48aNat26dY2CAQAABApLhevee+/Vt99+qxkzZig3N1eSVFhYqG3btumVV17R4cOHdc8993g0KAAAgKksnfdLSkrSyJEj9cEHH2j16tWSpLfffluSFBYWppEjRyoxMdFzKQEAAAxmeaBVt27ddOONN2rbtm3Ky8uT0+lUfHy8OnTooLCwME9mBAAAMFqNRrbXqVOn3EOrAQAAUFG1CteJEycsbZznKQIAAFSzcI0cOdLSxufNm2fpdQAAAIGkWoVrxIgRducAAAAIWNUqXD169LA5BgAAQOCydB+u6dOna8+ePVUu37t3r6ZPn245FAAAQCCxVLjWr1+vY8eOVbn8+PHjWr9+veVQAAAAgcRS4bqcgoIChYaG2rFpAAAA41T7PlxfffWVvvrqK/f06tWrtW3btgrrlZSU6LvvvlOrVq08kxAAAMBw1S5chw4d0ubNm93Te/bsUU5OTrl1HA6Hateureuvv16DBw/2XEoAAACDVbtwpaamKjU1VZI0aNAgjRgxQl26dLEtGAAAQKCw9GgfbmgKAABQfbYMmgcAAMC/VesI16BBg+RwODR37lwFBwdr0KBBl32Nw+HQ3//+9xoHBAAAMF21Clf//v3lcDgUFBRUbhoAAACXV63CNXDgwEtOAwAAoGqWxnClp6fr4MGDVS7Pzc1Venq65VAAAACBxFLhWrBgwWUL14IFCyyHAgAACCS2XKVYXFys4GBLd5wAAAAIONVuRTt37tTOnTvd01lZWcrLy6uw3pkzZ7Rx40Y1bdrUMwkBAAAMV+3CtWPHjnLjsrKzs5WdnV3puo0bN9aQIUNqng4AACAAVLtw3XPPPerTp49cLpeGDh2qoUOH6qabbiq3jsPhUGhoqEJDQz0eFAAAwFTVLly/LFLvvPOOoqKiVLt2bduCAQAABApLI9tjY2M9nQMAACBg8SxFAAAAm1G4AAAAbEbhAgAAsFm1CldGRoaOHDlidxYAAICAVK3CNXv2bOXk5LinBw0apA0bNtgWCgAAIJBUq3BFRkaqsLDQ5igAAACBqVq3hUhMTNSCBQt04MABhYeHS5LWr1+v3bt3V/kah8OhP/7xj55JCQAAYLBqFa7HHntMaWlp2rZtm06dOiVJ2rZtm7Zt23bJ11G4AAAAqlm46tWrp1GjRrmnBw0apCeeeEJdunSxLRgAAECgsHRbiBEjRqh169aezgIAABCQLD3ap0ePHu7fHzp0SPn5+ZJ+fuRP48aNPRIMAAAgUFgqXJL01Vdf6cMPP9Tx48fLzY+Li9MjjzyilJSUGocDAAAIBJYK15YtW/TWW28pNjZW999/v/uo1qFDh7RmzRq9+eabev7559WxY0dPZgUAADCSpcK1cOFCNWvWTBMmTFCdOnXc81NSUtSnTx+9+OKLWrBgAYULAABAFgfNHzx4UN27dy9Xti6qU6eOevTooYMHD9Y4HAAAQCCwVLhCQkJUXFxc5fLi4mKFhIRYDgUAABBILBWudu3aKSMjo9I7ze/Zs0fLly9X+/btaxwOAAAgEFgaw/XQQw9p7NixGjdunFq1aqVGjRpJko4cOaK9e/eqXr16evDBBz0aFAAAwFSWjnDFxcXpzTff1O9//3udOXNGGzdu1MaNG3XmzBn17dtXkydPVlxcXI2CLVmyRAMHDlRaWlqNtgMAAOBrlu/DVa9ePT366KMejPJve/fu1apVq9SsWTNbtg8AAOBNlo5w2ens2bN6++23NWzYMEVERPg6DgAAQI1ZPsJll5kzZyopKUk33HCDFi1adMl1S0tLVVpa6p52OBwKCwtz/96fXczn7zl/zcTcJmaWzM1tIlP3NbnNZeJn92bmQPyO+FXh+vLLL7V//3699tpr1Vp/8eLFSk9Pd08nJCRo0qRJio2NtSuix8XHx/s6giUm5jYxs2RubhOZuq+9lTu3n+ce2Zbroe00+cfXXnsvT7r66qsvu46/5a5OZk8z9e9kZfymcJ04cUJpaWn661//qtDQ0Gq9JjU1VXfeead7+mITzs/PV1lZmS05PcXhcCg+Pl55eXlyuVy+jlNtJuY2MbNkbm4TmbqvTc3tSUePHvV1BEtMzO3NzCZ9t4ODg6t1oMdvCldOTo5OnTql5557zj3P6XRq165dWrFihT7++GMFBZUfchYSElLlDVb9/Q/oIpfLZUzWXzIxt4mZJXNzm8jUfW1qbk8w9XObmNsXmQPpu33FhevcuXN68cUX1atXL91+++0eC9K+fXu9+eab5ea9++67atSoke65554KZQsAAMAUV1y4ateurePHj3t8IFtYWJiaNm1a4b3q1q1bYT4AAIBJLB026tixo/75z396OgsAAEBAsjSGq3///vrb3/6mt99+W7fddpvi4uIqHegeGRlZo3AvvfRSjV4PAADgDywVrqefflqSdOjQIW3YsKHK9ebNm2ctFQAAQACxfIQrkG5GBgAAYCdLhWvgwIGezgEAABCwPHKvhZKSEjmdTk9sCgAAIOBYLlz79u3TxIkT9dBDD2nIkCHauXOnJKmoqEhvvPGGduzY4bGQAAAAJrNUuH744Qe9+OKLysvLU9euXcvdBTYqKkolJSVatWqVx0ICAACYzFLh+uSTT3TNNddoypQpuv/++yssb9u2rfbu3VvjcAAAAIHAUuHat2+fevTooZCQkEqvVoyOjlZhYWFNswEAAAQES4WrVq1al3yYZEFBgerUqWM5FAAAQCCxVLiuvfZabd68udJlZ8+e1bp165SYmFijYAAAAIHCUuEaOHCgcnJy9Nprr2nr1q2SpAMHDmjNmjV6/vnnVVRUpP79+3s0KAAAgKks3fj02muv1V/+8hfNmDFD06ZNkyTNmTNHknTVVVfpL3/5i5o1a+a5lAAAAAazVLgkqV27dpo6dar279+vvLw8uVwuXXXVVWrRogWP/QEAAPgFy4XrooSEBCUkJHgiCwAAQECyXLhKS0u1Zs0abd26VcePH5ckxcXFKSkpST179lRoaKjHQgIAAJjMUuE6efKkXnnlFR05ckT169dXfHy8pJ8Hzn/77bdasWKFxo0bp4YNG3o0LAAAgIksFa5Zs2YpPz9fo0eP1n/8x3+UW7Zp0yZNmzZNs2bN0rPPPuuRkAAAACazVLi+++479evXr0LZkqSbb75Z+/fv1/Lly2scDgAAIBBYug9XWFiY6tWrV+Xy+vXrKywszHIoAACAQGKpcPXo0UPr1q3TuXPnKiw7e/asPv/8c/Xs2bPG4QAAAAJBtU4pZmVllZtOSEjQ1q1b9dRTT6l79+7uQfN5eXlav369IiMj1bRpU8+nBQAAMFC1CteUKVOqXLZ48eIK8woKCjR16lR17tzZejIAAIAAUa3CNX78eLtzAAAABKxqFa7ExES7cwAAAAQsS4PmAQAAUH2WH+3z/fffa+3atTp+/LjOnDkjl8tVbrnD4dDkyZNrHBAAAMB0lgrXsmXLNGfOHIWGhqpRo0aKjIz0dC4AAICAYalwLV26VNddd52ee+45hYeHezoTAABAQLE0huvcuXPq0qULZQsAAKAaLBWutm3b6uDBg57OAgAAEJAsFa4hQ4Zo+/btWrp0qYqLiz2dCQAAIKBYGsMVExOj3r17a86cOfroo48UGhqqoKCK3W327Nk1DggAAGA6S4Vr3rx5WrRokaKjo9WyZUvGcgEAAFyCpcK1atUqderUSc8880ylR7YAAADwb5YKV1lZmTp16kTZAgAAbheG3u2xbeV6aDu1Ziz10JZqxlJj6tSpk3bt2uXpLAAAAAHJUuH6wx/+oMOHD2vmzJnKyclRUVGRiouLK/wCAACAxVOKTz31lCTpwIEDWrVqVZXrzZs3z1IoAACAQGKpcPXv318Oh8PTWQAAAAKSpcI1cOBAT+cAAAAIWFxmCAAAYDNLR7jS09Ortd6AAQOsbB4AACCgWCpcCxYsqNZ6FC4AAIAaPNrn15xOp06cOKEVK1Zo165deuGFF2ocDgAAIBB4bAxXUFCQ4uLiNHjwYF199dV6//33PbVpAAAAo9kyaP7666/X1q1b7dg0AACAcWwpXPv27eM+XQAAAP/P0hiu9evXVzr/zJkz2rVrl7Kzs9WzZ88aBQMAAAgUlgrX9OnTq1xWt25d3XPPPVyhCAAA8P8sFa533nmnwjyHw6GIiAiFhYXVOBQAAEAgsVS4YmNjPZ0DAAAgYFkqXL909uxZFRcXV7osJiamppsHAAAwnqXCdf78eaWnp2vt2rU6ffp0letVdoNUAACA3xpLhWvmzJlav369fve73+n6669XRESEp3MBAAAEDEuFKzs7W7169dKf//xnT+cBAAAIOJZufOpwOJSQkODpLAAAAAHJUuFKSUnRd9995+ksAAAAAclS4erfv7+OHTum//mf/1FOTo6KiopUXFxc4RcAAAAsjuEaNWqUJOnAgQNau3ZtletxlSIAAIDFwtW/f38eTg0AAFBNlgrXwIEDPZ0DAAAgYFkawwUAAIDqo3ABAADYjMIFAABgMwoXAACAzShcAAAANqNwAQAA2IzCBQAAYDMKFwAAgM0oXAAAADazdKd5uyxevFjZ2dk6fPiwQkND1bp1az300ENq1KiRr6MBAABY5leFa+fOnbrjjjvUsmVLXbhwQZ988oleeeUVTZkyRXXq1PF1PAAAAEv8qnCNHTu23PTIkSP12GOPKScnR4mJiT5KBQAAUDN+Vbh+raSkRJIUGRlZ6fLS0lKVlpa6px0Oh8LCwty/92cX8/l7zl8zMbeJmSVzc5vI1H1tam5PMvWzm5jbxMyS/+T228LldDqVlpamNm3aqGnTppWus3jxYqWnp7unExISNGnSJMXGxtqaLbdfime245GtSE3+8bWHtlR98fHxXnkff9vXUvX2t7/l9sV3xETe+l57mtf+PnrlXa7M1Vdffdl1yO0ZJmaWqpfbG/y2cM2aNUu5ubl6+eWXq1wnNTVVd955p3v6YovNz89XWVmZ7Rn9xdGjR732Xg6HQ/Hx8crLy5PL5fLa+/oTb+5vTzExszeZ+r02NbcnmfrdNjG3iZkl+3MHBwdX60CPXxauWbNmacuWLZowYYIaNmxY5XohISEKCQmpdNlv6R8fX3xWl8v1m9rHv2Ti5zYxsy+Y+r02NbcnmPq5TcxtYmbJf3L71X24XC6XZs2apezsbL344ouKi4vzdSQAAIAa86vCNWvWLGVmZmrUqFEKCwtTYWGhCgsLdf78eV9HAwAAsMyvTimuXLlSkvTSSy+Vm//444+rR48e3g8EAADgAX5VuObPn+/rCAAAAB7nV6cUAQAAAhGFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwGYULAADAZhQuAAAAm1G4AAAAbEbhAgAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwGYULAADAZhQuAAAAm1G4AAAAbEbhAgAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwGYULAADAZsG+DgDvuDD0bo9tK9dD26k1Y6mHtoTfMk99tz31vZaq9932t9z8fQTsxREuAAAAm1G4AAAAbEbhAgAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwGYULAADAZhQuAAAAm1G4AAAAbEbhAgAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwGYULAADAZhQuAAAAm1G4AAAAbEbhAgAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAAABsRuECAACwWbCvA1RmxYoV+uyzz1RYWKhmzZppyJAhatWqla9jAQAAWOJ3R7g2btyoDz/8UAMGDNCkSZPUrFkzTZw4UadOnfJ1NAAAAEv8rnAtW7ZMvXr10q233qrGjRtr6NChCg0N1eeff+7raAAAAJb4VeEqKytTTk6O2rdv754XFBSk9u3ba/fu3T5MBgAAYJ1fjeEqKiqS0+lU/fr1y82vX7++jhw5UmH90tJSlZaWuqcdDofCwsIUHGzvxwpq2cbW7V+pWiEhl13H3zJL5Pam6mQ2lb/taylwvyP+llkitzeZmFmy/9+/6nYOh8vlctma5AoUFBRo+PDheuWVV9S6dWv3/Llz52rnzp169dVXy60/f/58paenu6dvueUWjRo1ymt5AQAAqsOvTilGRUUpKChIhYWF5eYXFhZWOOolSampqUpLS3P/Gjp0aLkjXv7sp59+0nPPPaeffvrJ11GuiIm5TcwsmZvbRKbua3J7j4mZJXL7E78qXMHBwWrRooW2b9/unud0OrV9+/ZyR7wuCgkJUXh4eLlfIYacOnG5XNq/f7/86ABjtZiY28TMkrm5TWTqvia395iYWSK3P/GrMVySdOedd2ratGlq0aKFWrVqpYyMDJ07d049evTwdTQAAABL/K5wde7cWUVFRZo/f74KCwvVvHlzvfDCC5WeUgQAADCB3xUuSerTp4/69Onj6xi2CgkJ0YABA4w5BXqRiblNzCyZm9tEpu5rcnuPiZklcvsTv7pKEQAAIBD51aB5AACAQEThAgAAsBmFCwAAwGYULgAAAJtRuAAAAGzml7eFCETFxcVavHixvvnmG5WUlEiSIiIi1KlTJ917772qW7eujxNemb/97W8aPXq0r2NU6uzZs1q6dKkKCgrUoUMH3Xzzze5l77//voYMGeLDdFUrKirSokWL5HA41L9/f2VkZCgrK0vXXHONHn30UUVHR/s6YsDYtGmT+3tx6tQpzZo1Szk5OWrcuLH+9Kc/KTY21scJK5eRkaEuXbooKipKR48e1fTp03X06FHFxsZq2LBhat68ua8jVvDcc8/pd7/7nTp37qxGjRr5Ok615eTk6JNPPlGDBg00cOBAvfvuuzpw4IBiYmI0bNgwtWjRwtcRK/XTTz9pyZIlys7OVkFBgerUqaOGDRuqd+/e6tmzp6/jVSrQfj5WhcLlJVOmTNFNN92kV199VeHh4ZKkkpISffHFF5oyZYrGjx/v44QVHTp0qNL5LpdL33//vZfTVN8777yja665Rp06ddL69euVmZmpp556SqGhofrhhx98Ha9Kb7/9tpKTk3Xu3DmNHz9ePXv21Pjx45Wdna0ZM2boueee83XEgLFkyRJ34Zo9e7auu+46jRw5UtnZ2frf//1fjR071scJK7dmzRr17dtX0s//eRgwYIA6dOigvXv3asaMGZo4caKPE1Z05swZlZWV6Y033lBISIg6d+6szp0766qrrvJ1tEuaMWOGBg0apJKSEv31r3/VsGHDlJSUpN27d2vmzJl69dVXfR2xUv/1X/+lrl276ve//702b96soqIi9e7dW4sWLdLhw4f18MMP+zpiBSb+fLSCwuUlJ0+e1B133FFuXnh4uPr06aOMjAwfpbq0MWPG6Prrr690WXFxsZfTVN+xY8c0ZswYSdKNN96ojIwMjR8/Xs8884yPk13aqVOn3Df8Xblypfr16ydJ6t27t1auXOnLaAEtNzdXTz75pCSpa9eu+uyzz3ycqGoXLlyQ0+lUUFCQzpw5ow4dOkiSWrVqpbNnz/o4XeUiIiJ033336b777tOBAwe0adMmTZw4UREREbr55pt19913+zpipS5cuKCOHTtKkubMmaOkpCRJUuvWrVVaWurDZJdWUFCgLl26SPr5JuLPP/+8Bg4cqMcee0xPPfWUXxYuE38+WkHh8pImTZpo7ty56tatm/vUUEFBgdavX68mTZr4OF3lrrnmGg0dOrTS0wAjRozwQaLqKSsrU2lpqfsOxX379lVcXJxefvllv37y/C/vQXzbbbeVW3bhwgVvxwloBQUFmjt3rlwul4qLi1VWVqbg4J//OfTnfd2zZ0+98cYbSk1NVVJSkubOnaubbrpJO3bs8Nt/R375vW7evLmaN2+u+++/Xzk5Odq0aZMPk11aeHi4Vq9erbNnzyoiIkIrV67UzTffrO+++061a9f2dbwqRUZGavPmzWrbtq2ysrLKDUXw1/ucm/jz0QruNO8lpaWlWrNmjb755hsVFBRIkqKjo9WpUyf16tVLoaGhPk5Y0ZYtW9SoUSPFx8dXWLZ161b3//j8zbJly9SiRQslJiaWm5+Tk6OPPvpI48aN81GyS1u2bJl69+6tOnXqlJufl5enJUuWaPjw4T5KFnjWrVtXbjolJUWRkZEqLCxURkaGHnjgAd8Eq4Zt27Zp9erVysvL04ULFxQdHa2UlBT16tXLXRr9yezZs/XII4/4OsYVO3nypD799FM5HA6lpqZqw4YN+vzzzxUTE6OHHnrIb4tAfn6+PvzwQx0+fFjNmjXTww8/rOjoaJ0+fVrfffedOnfu7OuIFfz656PD4VCDBg38+uejFRQuAAAAm/nff4cCVFlZWYUjXA0aNFBycrJ69erllw/ovJj566+/1o8//ijJ/zNL5Mblmfj3UTIzt6nfaxP3tWRu7qp8+OGHGjx4sK9jeARHuLzkrbfeUlxcnLp27VruHHVmZma5Qd7+xMTMErlxeabuaxNzm5hZIrc3nTt3rtL5LpdLo0eP1rvvvuvlRPbgCJeXHDx4UE8//XS5eVFRUWrevLlGjRrlo1SXZmJmidy4PFP3tYm5TcwskdubBg8erNjY2HKD+h0Oh1wulwoLC30XzMO407yXREdHa+XKleWukvvpp5+0YsUKNWjQwIfJqmZiZoncuDxT97WJuU3MLJHbm+Lj4zV+/HhNmzbN/eudd97RtGnTVL9+fV/H8xhOKXrJ6dOntWTJEn399deV3kk3KirKxwkrMjGzRG5cnqn72sTcJmaWyO1Na9eu1bXXXlvplZ+rV69W7969fZDK8yhcAAAANmMMlxdt2bKlwpUjKSkp6tSpk4+TVc3EzBK5cXmm7msTc5uYWSK3N5mY+UpxhMtLZs6cqaKiokqvHKlbt66GDh3q44QVmZhZIjcuz9R9bWJuEzNL5PYmEzNb4oJXPPnkk1Uue+KJJ7yYpPpMzOxykRuXZ+q+NjG3iZldLnJ7k4mZreAqRS8JDw/X1q1bK8zfsmWL++no/sbEzBK5cXmm7msTc5uYWSK3N5mY2QpOKXrJsWPH9NFHH+mHH35Q3bp15XK5dObMGV177bV68MEHK31eoa+ZmFkiNy7P1H1tYm4TM0vk9iYTM1tB4fKi8+fP6/Tp0+4HcdatW1eSlJub67cPQjUxs0RuXJ6p+9rE3CZmlsjtTSZmvlJcpeglmzdvVlpamqKiouR0OjVs2DD3F+qdd97RpEmTfJywIhMzS+TG5Zm6r03MbWJmidzeZGJmKyhcXrJw4UK9/vrrql+/vnJycjRt2jSlpqaqS5cu8teDjCZmlsiNyzN1X5uY28TMErm9ycTMVlC4vOTChQvuRxS0aNFCEyZM0OTJk5WXlyeHw+HbcFUwMbNEblyeqfvaxNwmZpbI7U0mZraCqxS9pF69evrXv/7lno6MjNS4ceN06NChcvP9iYmZJXLj8kzd1ybmNjGzRG5vMjGzFQya95KTJ0+qVq1alT6I8/vvv9d1113n/VCXYWJmidy4PFP3tYm5TcwskdubTMxsBYULAADAZpxSBAAAsBmFCwAAwGYULgAAAJtRuAAAAGxG4QIAALAZhQsAAMBmFC4AAACbUbgAwEPOnz8vp9Pp6xgA/BDPUgRgtO3bt+vll1/WmDFjdOONN5ZbtmHDBv33f/+3XnnlFbVu3VqHDx/W3//+d23fvl3nz59XkyZNNGDAAKWkpLhfU1xcrEWLFumf//ynjh8/rqCgILVp00YPPPCAmjdv7l5vx44dmjBhgkaNGqXc3Fx9/vnnKiws1Pvvv6+IiAhvfXwAhuAIFwCjtW3bVg0bNlRmZmaFZZmZmbrqqqvUunVr5ebmauzYsTp8+LDuvfdePfzww6pdu7YmT56s7Oxs92uOHTumr776SsnJyXrkkUd011136eDBg3rppZdUUFBQ4T0WLlyoLVu26K677tL999+v4GD+HwugIv5lAGA0h8Ohrl276h//+IdKSkoUHh4uSSoqKtK2bduUmpoqSUpLS1NMTIxee+01hYSESJLuuOMOvfjii/roo4/cR8eaNm2qqVOnKijo3/8f7datm0aPHq21a9dqwIAB5d6/tLRUr7/+ukJDQ73xcQEYiiNcAIzXvXt3lZaWavPmze55Gzdu1IULF9StWzcVFxdr+/btuvnmm/XTTz+pqKhIRUVFOn36tDp06KCjR4+6j16FhIS4y5bT6dTp06dVp04dNWrUSPv376/0vSlbAC6HI1wAjHfNNdeoZcuWyszMVM+ePSX9fDrx2muvVXx8vPbu3SuXy6V58+Zp3rx5lW7j1KlTio6OltPpVEZGhlauXKnjx4+XGwQfGRlZ4XVxcXH2fCgAAYXCBSAgdO/eXR988IFOnjyp0tJS7dmzR0OGDJEkd2m666671KFDh0pfHx8fL0lavHix5s2bp1tvvVWDBg1SZGSkHA6HZs+eLZfLVeF1HN0CUB0ULgABoXPnzpo9e7a+/PJLnT9/XrVq1VLnzp0lSVdddZUkqVatWrrhhhsuuZ3Nmzerbdu2GjFiRLn5Z86cUd26de0JDyDgMYYLQECIiopSUlKSMjMzlZmZqY4dOyoqKkqSVK9ePbVt21arV6/Wjz/+WOG1RUVF7t//crD8RZs2bar0CkUAqC6OcAEIGN26ddOUKVMkSYMGDSq37E9/+pPGjRunMWPGqFevXoqLi9OpU6e0e/duFRQUaPLkyZKk5ORkpaena/r06WrdurUOHjyoDRs2uI+SAYAVFC4AASMlJUURERFyuVzlbmYqSY0bN9brr7+uBQsWaN26dTp9+rTq1aun5s2bq3///u71UlNTdfbsWX355ZfauHGjEhIS9Pzzz+vjjz/29scBEEAcrspGgQKAgS5cuKBhw4YpOTm5whgsAPAlxnABCBhfffWVioqK1L17d19HAYByOKUIwHh79uzRv/71Ly1cuFAJCQlKTEz0dSQAKIfCBcB4K1euVGZmppo3b67HH3/c13EAoALGcAEAANiMMVwAAAA2o3ABAADYjMIFAABgMwoXAACAzShcAAAANqNwAQAA2IzCBQAAYDMKFwAAgM0oXAAAADb7PwXlNA6oNKDBAAAAAElFTkSuQmCC",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Create a barplot plot with y label as \"number of titles played\" and x -axis year\n",
+ "nbr_titles_played_by_year = df_final.groupby('year').title.nunique()\n",
+ "\n",
+ "# Set the figure size\n",
+ "plt.figure(figsize=(7, 5))\n",
+ "#from the previous cell, we could see there is an abnormal year 0, which seems to be\n",
+ "#a default, so we're dropping that year\n",
+ "plt.bar(x=nbr_titles_played_by_year.index[1:], height=nbr_titles_played_by_year[1:])\n",
+ "\n",
+ "# Set the x label of the plot\n",
+ "plt.xlabel('year')\n",
+ "\n",
+ "# Set the y label of the plot\n",
+ "plt.ylabel('number of titles played')\n",
+ "\n",
+ "# Setting all the ticks\n",
+ "plt.xticks(ticks=nbr_titles_played_by_year.index[1:], rotation=90, size=7)\n",
+ "\n",
+ "# Show the plot\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "mKMorIHk8i2x"
+ },
+ "outputs": [],
+ "source": [
+ "# Instantiating Reader scale with expected rating scale\n",
+ " #use rating scale (0, 5)\n",
+ "reader = Reader(rating_scale=(0, 5))\n",
+ "\n",
+ "# Loading the dataset\n",
+ " # Take only \"user_id\",\"song_id\", and \"play_count\"\n",
+ "data = Dataset.load_from_df(df_final[['user_id',\t'song_id', 'play_count']],\n",
+ " reader=reader)\n",
+ "\n",
+ "# Splitting the data into train and test dataset\n",
+ " # Take test_size = 0.4, random_state = 42\n",
+ "trainset, testset = train_test_split(data, test_size=0.4, random_state=42)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "EVg6KaGA8Gku",
+ "outputId": "1b3103a8-07c1-4a61-9fc3-590d32027585"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Computing the cosine similarity matrix...\n",
+ "Done computing similarity matrix.\n",
+ "RMSE: 1.5210\n",
+ "Precision: 0.55\n",
+ "Recall: 0.824\n",
+ "F_1 score: 0.66\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Build the default user-user-similarity model\n",
+ "sim_options=dict(name='cosine', user_based=True)\n",
+ "\n",
+ "# KNN algorithm is used to find desired similar items\n",
+ " # Use random_state = 1\n",
+ "sim_user_user_model = KNNBasic(sim_options=sim_options, random_state=1,\n",
+ " verbose=True)\n",
+ "\n",
+ "# Train the algorithm on the trainset, and predict play_count for the testset\n",
+ "sim_user_user_model.fit(trainset)\n",
+ "\n",
+ "# Let us compute precision@k, recall@k, and f_1 score with k = 30\n",
+ " # Use sim_user_user model\n",
+ "precision_recall_at_k(sim_user_user_model, testset=testset)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "_ILUu1V4_nUV"
+ },
+ "source": [
+ "The new F1 score is 0.66. This is better than 0.51 obtained in the conclusion. The precision is still bad."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "u7EZAZz1_4af"
+ },
+ "source": [
+ "## Tuning the model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "nixwwKHu8_EL",
+ "outputId": "4c87c935-862b-42ef-ca4b-95c81f304686"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Best RMSE score: 1.3998637614150191\n",
+ "Best_params: {'k': 60, 'min_k': 10, 'sim_options': {'name': 'msd', 'user_based': True}}\n",
+ "RMSE: 1.4120\n",
+ "Precision: 0.55\n",
+ "Recall: 0.821\n",
+ "F_1 score: 0.659\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Setting up parameter grid to tune the hyperparameters\n",
+ "params_grid = dict(k=[60, 80, 100], min_k=[10, 40, 50],\n",
+ " sim_options=dict(name=['msd', 'cosine'],\n",
+ " #min_support = [5, 10, 20],\n",
+ " user_based=[True]))\n",
+ "# Performing 3-fold cross-validation to tune the hyperparameters\n",
+ "gs = GridSearchCV(KNNBasic, param_grid=params_grid, cv=3, measures=['rmse'],\n",
+ " n_jobs=-1)\n",
+ "\n",
+ "# Fitting the data\n",
+ " # Use entire data for GridSearch\n",
+ "gs.fit(data)\n",
+ "\n",
+ "# Best RMSE score\n",
+ "print(\"Best RMSE score:\", gs.best_score['rmse'])\n",
+ "\n",
+ "# Combination of parameters that gave the best RMSE score\n",
+ "print(\"Best_params:\", gs.best_params['rmse'])\n",
+ "\n",
+ "# Train the best model found in above gridsearch\n",
+ "# Using the optimal similarity measure for user-user based collaborative filtering\n",
+ "best_params = gs.best_params['rmse']\n",
+ "best_k = best_params['k']\n",
+ "best_min_k = best_params['min_k']\n",
+ "best_sim_options = best_params['sim_options']\n",
+ "\n",
+ "# Creating an instance of KNNBasic with optimal hyperparameter values\n",
+ "sim_user_user_tuned_model = KNNBasic(k = best_k, min_k=best_min_k,\n",
+ " sim_options=best_sim_options,\n",
+ " random_state=1, verbose=False)\n",
+ "\n",
+ "# Training the algorithm on the trainset\n",
+ "sim_user_user_tuned_model.fit(trainset)\n",
+ "\n",
+ "# Let us compute precision@k and recall@k also\n",
+ "precision_recall_at_k(sim_user_user_tuned_model, testset=testset)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "D9vx87Zj_k29"
+ },
+ "source": [
+ "Tuning has not increased the performance."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "vqNtbLdTALvI"
+ },
+ "source": [
+ "## Balancing the data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "PjluKKhrAXUF"
+ },
+ "source": [
+ "We only balance the trainset. As balancing the whole dataset would leak information of the testset in the training. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "VEy6WORZAQoo"
+ },
+ "outputs": [],
+ "source": [
+ "trainset_df = pd.DataFrame(trainset.build_testset(), columns = ['user_id', 'song_id', 'play_count'])\n",
+ "\n",
+ "# We will oversample the under representated classes using smote\n",
+ "from imblearn.over_sampling import SMOTE\n",
+ "\n",
+ "smote = SMOTE(random_state=42)\n",
+ "\n",
+ "#balancing with smote\n",
+ "x, y = smote.fit_resample(trainset_df[['user_id', 'song_id']], trainset_df.play_count)\n",
+ "\n",
+ "trainset_df_balanced = pd.concat([x,y], axis=1)\n",
+ "\n",
+ "# converting the trainset balanced into suprise dataset\n",
+ "trainset_data_balanced = Dataset.load_from_df(trainset_df_balanced[['user_id',\t'song_id', 'play_count']],\n",
+ " reader=reader)\n",
+ "\n",
+ "# rebuild the trainset for surprise algorithms training\n",
+ "trainset_balanced = trainset_data_balanced.build_full_trainset()\n",
+ "\n",
+ "# balanced the whole dataset for comparison purposes\n",
+ "x2, y2 = smote.fit_resample(df_final[['user_id', 'song_id']], df_final.play_count)\n",
+ "df_final_balanced = pd.concat([x2,y2], axis=1)\n",
+ "data_balanced = Dataset.load_from_df(df_final_balanced[['user_id',\t'song_id', 'play_count']],\n",
+ " reader=reader)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "nEr-uMdIOuH-",
+ "outputId": "abeed1a9-15ef-4098-d95a-0bc7c52c8979"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "play_count\n",
+ "5.0 1956\n",
+ "1.0 1956\n",
+ "2.0 1956\n",
+ "3.0 1956\n",
+ "4.0 1956\n",
+ "Name: count, dtype: int64"
+ ]
+ },
+ "execution_count": 255,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "trainset_df_balanced.play_count.value_counts()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "wiVGj6RYCSEc",
+ "outputId": "b8b7502f-d701-4c25-c8d2-8c9008b659dd"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Computing the cosine similarity matrix...\n",
+ "Done computing similarity matrix.\n",
+ "RMSE: 1.5292\n",
+ "Precision: 0.55\n",
+ "Recall: 0.824\n",
+ "F_1 score: 0.66\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Build the default user-user-similarity model\n",
+ "sim_options=dict(name='cosine', user_based=True)\n",
+ "\n",
+ "# KNN algorithm is used to find desired similar items\n",
+ " # Use random_state = 1\n",
+ "sim_user_user_model = KNNBasic(k = 40, min_k=1, sim_options=sim_options, random_state=1,\n",
+ " verbose=True)\n",
+ "\n",
+ "# Train the algorithm on the trainset, and predict play_count for the testset\n",
+ "sim_user_user_model.fit(trainset_balanced)\n",
+ "\n",
+ "# Let us compute precision@k, recall@k, and f_1 score with k = 30\n",
+ " # Use sim_user_user model\n",
+ "precision_recall_at_k(sim_user_user_model, k=30, threshold=1.5,testset=testset)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "z6IMV6pcOfEy"
+ },
+ "source": [
+ "Balancing the data has not increased the F1 score"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "oLnj9mQQO9b3"
+ },
+ "source": [
+ "## Examining where the model is not performing well"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "pgW0smV-RYjJ"
+ },
+ "outputs": [],
+ "source": [
+ "# Let's build a pandas dataframe with all the predictions\n",
+ "def get_Iu(uid):\n",
+ " \"\"\"Return the number of items rated by given user\n",
+ "\n",
+ " Args:\n",
+ " uid: The raw id of the user.\n",
+ " Returns:\n",
+ " The number of items rated by the user.\n",
+ " \"\"\"\n",
+ " try:\n",
+ " return len(trainset.ur[trainset.to_inner_uid(uid)])\n",
+ " except ValueError: # user was not part of the trainset\n",
+ " return 0\n",
+ "\n",
+ "def get_Ui(iid):\n",
+ " \"\"\"Return the number of users that have rated given item\n",
+ "\n",
+ " Args:\n",
+ " iid: The raw id of the item.\n",
+ " Returns:\n",
+ " The number of users that have rated the item.\n",
+ " \"\"\"\n",
+ " try:\n",
+ " return len(trainset.ir[trainset.to_inner_iid(iid)])\n",
+ " except ValueError: # item was not part of the trainset\n",
+ " return 0\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "td_UkUT-PEI_"
+ },
+ "outputs": [],
+ "source": [
+ "predictions = sim_user_user_model.test(testset)\n",
+ "df = pd.DataFrame(predictions, columns=['uid', 'iid', 'rui', 'est', 'details'])\n",
+ "df['Iu'] = df.uid.apply(get_Iu)\n",
+ "df['Ui'] = df.iid.apply(get_Ui)\n",
+ "df['err'] = abs(df.est - df.rui)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "zvnE-hUmQNeA"
+ },
+ "outputs": [],
+ "source": [
+ "best_predictions = df.sort_values(by='err')[:10]\n",
+ "worst_predictions = df.sort_values(by='err')[-10:]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 363
+ },
+ "id": "K2TFccCAQTUZ",
+ "outputId": "18c629b6-7a69-4fef-e7d9-963855cf0a75"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "summary": "{\n \"name\": \"best_predictions\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"uid\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 20475,\n \"min\": 4848,\n \"max\": 67753,\n \"num_unique_values\": 8,\n \"samples\": [\n 23972,\n 50098,\n 4848\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"iid\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3564,\n \"min\": 154,\n \"max\": 9931,\n \"num_unique_values\": 9,\n \"samples\": [\n 7416,\n 9931,\n 5943\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"rui\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.5163977794943223,\n \"min\": 2.0,\n \"max\": 3.0,\n \"num_unique_values\": 2,\n \"samples\": [\n 2.0,\n 3.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"est\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.5163977794943223,\n \"min\": 2.0,\n \"max\": 3.0,\n \"num_unique_values\": 2,\n \"samples\": [\n 2.0,\n 3.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"details\",\n \"properties\": {\n \"dtype\": \"object\",\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Iu\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 3,\n \"min\": 0,\n \"max\": 8,\n \"num_unique_values\": 5,\n \"samples\": [\n 2,\n 8\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Ui\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 33,\n \"min\": 86,\n \"max\": 184,\n \"num_unique_values\": 9,\n \"samples\": [\n 135,\n 117\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"err\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 0.0,\n \"max\": 0.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 0.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}",
+ "type": "dataframe",
+ "variable_name": "best_predictions"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " uid | \n",
+ " iid | \n",
+ " rui | \n",
+ " est | \n",
+ " details | \n",
+ " Iu | \n",
+ " Ui | \n",
+ " err | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1625 | \n",
+ " 4848 | \n",
+ " 154 | \n",
+ " 3.0 | \n",
+ " 3.0 | \n",
+ " {'was_impossible': True, 'reason': 'User and/o... | \n",
+ " 0 | \n",
+ " 86 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 3298 | \n",
+ " 23972 | \n",
+ " 9931 | \n",
+ " 2.0 | \n",
+ " 2.0 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 2 | \n",
+ " 117 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 2089 | \n",
+ " 39813 | \n",
+ " 8582 | \n",
+ " 2.0 | \n",
+ " 2.0 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 6 | \n",
+ " 168 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 575 | \n",
+ " 27727 | \n",
+ " 352 | \n",
+ " 3.0 | \n",
+ " 3.0 | \n",
+ " {'was_impossible': True, 'reason': 'User and/o... | \n",
+ " 0 | \n",
+ " 184 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 1860 | \n",
+ " 67753 | \n",
+ " 6293 | \n",
+ " 2.0 | \n",
+ " 2.0 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 5 | \n",
+ " 120 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 3324 | \n",
+ " 27727 | \n",
+ " 5943 | \n",
+ " 3.0 | \n",
+ " 3.0 | \n",
+ " {'was_impossible': True, 'reason': 'User and/o... | \n",
+ " 0 | \n",
+ " 96 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 2280 | \n",
+ " 50098 | \n",
+ " 9162 | \n",
+ " 3.0 | \n",
+ " 3.0 | \n",
+ " {'was_impossible': True, 'reason': 'User and/o... | \n",
+ " 0 | \n",
+ " 110 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 351 | \n",
+ " 12020 | \n",
+ " 7416 | \n",
+ " 3.0 | \n",
+ " 3.0 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 6 | \n",
+ " 135 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 2006 | \n",
+ " 27727 | \n",
+ " 9262 | \n",
+ " 3.0 | \n",
+ " 3.0 | \n",
+ " {'was_impossible': True, 'reason': 'User and/o... | \n",
+ " 0 | \n",
+ " 108 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 1498 | \n",
+ " 62126 | \n",
+ " 8582 | \n",
+ " 2.0 | \n",
+ " 2.0 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 8 | \n",
+ " 168 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ " uid iid rui est \\\n",
+ "1625 4848 154 3.0 3.0 \n",
+ "3298 23972 9931 2.0 2.0 \n",
+ "2089 39813 8582 2.0 2.0 \n",
+ "575 27727 352 3.0 3.0 \n",
+ "1860 67753 6293 2.0 2.0 \n",
+ "3324 27727 5943 3.0 3.0 \n",
+ "2280 50098 9162 3.0 3.0 \n",
+ "351 12020 7416 3.0 3.0 \n",
+ "2006 27727 9262 3.0 3.0 \n",
+ "1498 62126 8582 2.0 2.0 \n",
+ "\n",
+ " details Iu Ui err \n",
+ "1625 {'was_impossible': True, 'reason': 'User and/o... 0 86 0.0 \n",
+ "3298 {'actual_k': 40, 'was_impossible': False} 2 117 0.0 \n",
+ "2089 {'actual_k': 40, 'was_impossible': False} 6 168 0.0 \n",
+ "575 {'was_impossible': True, 'reason': 'User and/o... 0 184 0.0 \n",
+ "1860 {'actual_k': 40, 'was_impossible': False} 5 120 0.0 \n",
+ "3324 {'was_impossible': True, 'reason': 'User and/o... 0 96 0.0 \n",
+ "2280 {'was_impossible': True, 'reason': 'User and/o... 0 110 0.0 \n",
+ "351 {'actual_k': 40, 'was_impossible': False} 6 135 0.0 \n",
+ "2006 {'was_impossible': True, 'reason': 'User and/o... 0 108 0.0 \n",
+ "1498 {'actual_k': 40, 'was_impossible': False} 8 168 0.0 "
+ ]
+ },
+ "execution_count": 260,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Let's take a look at the best predictions of the algorithm\n",
+ "best_predictions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "pIuU3su8Qxj0",
+ "outputId": "a77713b3-174a-4e08-c02d-bf6f97558034"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "86"
+ ]
+ },
+ "execution_count": 261,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "trainset_df.query(\"song_id==154\").user_id.nunique()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "LeeiGbl8SQby"
+ },
+ "source": [
+ "**|Ui| is always big** (> 86), meaning that many users have played the target song in the trainset. **|Iu| is always very small**, meaning that the target user have note played many songs in the trainset.\n",
+ "\n",
+ "So it is easy to find users who have listened the song (as they are many), hence it is to find **k** users similar to the target user. And as they are similar to the target user, it is likely that they will rate the target song as the target user.\n",
+ "\n",
+ "**So all these predictions make sens.**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 363
+ },
+ "id": "oYuLVcDyT1K8",
+ "outputId": "20762d88-92c3-4f96-84b2-cfb2d5f14b0b"
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.google.colaboratory.intrinsic+json": {
+ "summary": "{\n \"name\": \"worst_predictions\",\n \"rows\": 10,\n \"fields\": [\n {\n \"column\": \"uid\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 26850,\n \"min\": 6840,\n \"max\": 75456,\n \"num_unique_values\": 10,\n \"samples\": [\n 8401,\n 11387,\n 74130\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"iid\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 4036,\n \"min\": 154,\n \"max\": 9931,\n \"num_unique_values\": 6,\n \"samples\": [\n 154,\n 9931,\n 7399\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"rui\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0,\n \"min\": 5.0,\n \"max\": 5.0,\n \"num_unique_values\": 1,\n \"samples\": [\n 5.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"est\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.0742675272274201,\n \"min\": 1.5002907250895041,\n \"max\": 1.7501146975128803,\n \"num_unique_values\": 10,\n \"samples\": [\n 1.6230815258209723\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"details\",\n \"properties\": {\n \"dtype\": \"object\",\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Iu\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 5,\n \"min\": 2,\n \"max\": 21,\n \"num_unique_values\": 6,\n \"samples\": [\n 2\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Ui\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 23,\n \"min\": 86,\n \"max\": 168,\n \"num_unique_values\": 6,\n \"samples\": [\n 86\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"err\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 0.07426752722742011,\n \"min\": 3.2498853024871197,\n \"max\": 3.499709274910496,\n \"num_unique_values\": 10,\n \"samples\": [\n 3.3769184741790275\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}",
+ "type": "dataframe",
+ "variable_name": "worst_predictions"
+ },
+ "text/html": [
+ "\n",
+ " \n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " uid | \n",
+ " iid | \n",
+ " rui | \n",
+ " est | \n",
+ " details | \n",
+ " Iu | \n",
+ " Ui | \n",
+ " err | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1857 | \n",
+ " 6840 | \n",
+ " 154 | \n",
+ " 5.0 | \n",
+ " 1.750115 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 2 | \n",
+ " 86 | \n",
+ " 3.249885 | \n",
+ "
\n",
+ " \n",
+ " | 2394 | \n",
+ " 11387 | \n",
+ " 9931 | \n",
+ " 5.0 | \n",
+ " 1.749322 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 4 | \n",
+ " 117 | \n",
+ " 3.250678 | \n",
+ "
\n",
+ " \n",
+ " | 2033 | \n",
+ " 29185 | \n",
+ " 8582 | \n",
+ " 5.0 | \n",
+ " 1.722656 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 15 | \n",
+ " 168 | \n",
+ " 3.277344 | \n",
+ "
\n",
+ " \n",
+ " | 777 | \n",
+ " 9193 | \n",
+ " 2672 | \n",
+ " 5.0 | \n",
+ " 1.713873 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 7 | \n",
+ " 101 | \n",
+ " 3.286127 | \n",
+ "
\n",
+ " \n",
+ " | 2873 | \n",
+ " 75456 | \n",
+ " 9162 | \n",
+ " 5.0 | \n",
+ " 1.706098 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 7 | \n",
+ " 110 | \n",
+ " 3.293902 | \n",
+ "
\n",
+ " \n",
+ " | 2488 | \n",
+ " 74130 | \n",
+ " 2672 | \n",
+ " 5.0 | \n",
+ " 1.697641 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 7 | \n",
+ " 101 | \n",
+ " 3.302359 | \n",
+ "
\n",
+ " \n",
+ " | 1481 | \n",
+ " 7113 | \n",
+ " 154 | \n",
+ " 5.0 | \n",
+ " 1.678663 | \n",
+ " {'actual_k': 35, 'was_impossible': False} | \n",
+ " 4 | \n",
+ " 86 | \n",
+ " 3.321337 | \n",
+ "
\n",
+ " \n",
+ " | 1331 | \n",
+ " 12355 | \n",
+ " 2672 | \n",
+ " 5.0 | \n",
+ " 1.659427 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 11 | \n",
+ " 101 | \n",
+ " 3.340573 | \n",
+ "
\n",
+ " \n",
+ " | 453 | \n",
+ " 8401 | \n",
+ " 7399 | \n",
+ " 5.0 | \n",
+ " 1.623082 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 21 | \n",
+ " 99 | \n",
+ " 3.376918 | \n",
+ "
\n",
+ " \n",
+ " | 3010 | \n",
+ " 25615 | \n",
+ " 9931 | \n",
+ " 5.0 | \n",
+ " 1.500291 | \n",
+ " {'actual_k': 40, 'was_impossible': False} | \n",
+ " 4 | \n",
+ " 117 | \n",
+ " 3.499709 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ " uid iid rui est details \\\n",
+ "1857 6840 154 5.0 1.750115 {'actual_k': 40, 'was_impossible': False} \n",
+ "2394 11387 9931 5.0 1.749322 {'actual_k': 40, 'was_impossible': False} \n",
+ "2033 29185 8582 5.0 1.722656 {'actual_k': 40, 'was_impossible': False} \n",
+ "777 9193 2672 5.0 1.713873 {'actual_k': 40, 'was_impossible': False} \n",
+ "2873 75456 9162 5.0 1.706098 {'actual_k': 40, 'was_impossible': False} \n",
+ "2488 74130 2672 5.0 1.697641 {'actual_k': 40, 'was_impossible': False} \n",
+ "1481 7113 154 5.0 1.678663 {'actual_k': 35, 'was_impossible': False} \n",
+ "1331 12355 2672 5.0 1.659427 {'actual_k': 40, 'was_impossible': False} \n",
+ "453 8401 7399 5.0 1.623082 {'actual_k': 40, 'was_impossible': False} \n",
+ "3010 25615 9931 5.0 1.500291 {'actual_k': 40, 'was_impossible': False} \n",
+ "\n",
+ " Iu Ui err \n",
+ "1857 2 86 3.249885 \n",
+ "2394 4 117 3.250678 \n",
+ "2033 15 168 3.277344 \n",
+ "777 7 101 3.286127 \n",
+ "2873 7 110 3.293902 \n",
+ "2488 7 101 3.302359 \n",
+ "1481 4 86 3.321337 \n",
+ "1331 11 101 3.340573 \n",
+ "453 21 99 3.376918 \n",
+ "3010 4 117 3.499709 "
+ ]
+ },
+ "execution_count": 262,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Now, let's look at the prediction with the biggest error\n",
+ "worst_predictions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "XS-RZksAXGaX"
+ },
+ "source": [
+ "**|Ui| is always big** (> 86), meaning that many users have played the target song in the trainset. **|Iu| is always very small**, meaning that the target user have note played many songs in the trainset.\n",
+ "\n",
+ "So it is easy to find users who have listened the song (as they are many), hence it is to find **k** users similar to the target user. And as they are similar to the target user, it is likely that they will rate the target song as the target user. But the predictions are not good. The explanation left is the taste of this song by this target is different from all his similar users. \n",
+ "\n",
+ "**So all these predictions are not good because we are in the cases of biased users.**\n",
+ "\n",
+ "These are situations where baseline estimates would be quite helpful, in order to deal with highly biased users (and items)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 485
+ },
+ "id": "MdJGJdgTY1Ph",
+ "outputId": "6eda90c5-d36f-4335-bf3b-45d6756cbb5f"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjYAAAHUCAYAAAAp0mIhAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABJD0lEQVR4nO3deXxMd9//8fdE9qRJaBJBNZKSKgmitloaxdVL1U2Vi9L20ip3r6L7oqq2qqKqF5dWby2KFqUqpS5cFF0sVWvtopYbjZTIokSSiZzfH70zPyOLZJKYycnr+Xh4yJxz5pzPnO/M5J1zvud7LIZhGAIAADABN2cXAAAAUFYINgAAwDQINgAAwDQINgAAwDQINgAAwDQINgAAwDQINgAAwDQINgAAwDQINgAAwDQINihX7du3l8VicXYZZero0aPq0aOHwsLCZLFYFBQU5OySXM6YMWNksVj03XffObsUG1esyRFz586VxWLR3LlznV1KqVgsFrVv375Yy548eVIWi0VPPPFEudYEcyDYVAAWi0UWi0Xh4eHKzMwscJk6derIYrEoJyfnJldXuVy9elUPPfSQVq1apa5du2r06NF6/fXXnV0WUCk98cQTslgsOnnypLNLKRGr1app06bpySefVJMmTeTp6SmLxaJZs2YV+py8QFvYv//5n/+54XYTEhLk5+cni8Wixx57rCxfkktxd3YBKL5Tp05p6tSp/CJ1ohMnTujgwYMaNGiQPv74Y2eXgxIYOnSoHnnkEd1+++3OLgUlVKtWLR06dEiBgYHOLqVMXL58WS+88IIkqXr16goLC9Pp06eL9dzu3burSZMm+aY3a9asyOfl5OTo8ccfl5ub+Y9nEGwqiKpVq8pisWjixIkaOHCggoODnV1SpZSYmChJqlmzppMrQUkFBwfzuamgPDw8VL9+fWeXUWZ8fX21atUqNWnSRDVq1NCYMWM0duzYYj33oYcecuiU3DvvvKM9e/Zo8uTJev7550v8/IrE/NHNJHx9fTVy5Eilp6cX+wPw3XffyWKxaMyYMQXOr1OnjurUqWM37drz9+vWrVO7du3k7++vkJAQPfnkk0pLS5Mk7d69W127dlXVqlXl7++vbt26FXk4OCsrS2+++aYiIiLk5eWlO+64Q2PHjlV2dnaByx8+fFhPPPGEateuLU9PT1WvXl39+vXTkSNH8i2bdzj6+PHjmj59uho1aiQfH59in7/fuXOnevbsqdDQUHl5eSk8PFyDBw/W2bNn7ZazWCyKi4uTJI0dO9Z2CLiw/ZvHkXbIzs7Wv/71LzVt2lRVq1aVr6+v6tSpo+7du+vbb7/Nt46y3F+GYWjevHlq3bq1QkJC5O3trdq1a+uvf/2rFi9eXORrLcjSpUvVokUL+fr6qlq1anrkkUf022+/5Vtu586dev7559W4cWNVq1ZN3t7eqlevnl5++WWlpqbaLTtx4kRZLBZNmzatwG0mJibK3d3d7q/YwvrY5PX1SE5O1n//93+rRo0a8vLyUsOGDfXpp58WuP6srCyNGTNGkZGR8vLyUkREhN58801lZWU53Hfk8OHDeuihh1StWjX5+fmpbdu2Wrt2bbHWI0kbN27Uf//3f6tBgwYKCAiQj4+PoqOjNXbs2HynsIcPHy6LxaJ58+YVuK6dO3fKYrGoa9eudtMzMjI0YcIENWnSRH5+fvL399c999yjRYsWFbie7OxsjRs3TnfccUe+/VQSBfWxubb+iIgI2+fx+s9SSkqKhg8frrvuuks+Pj4KDAxUx44dC9y35fX9dz1PT0898MADqlGjRon2g6N27NihcePGaeTIkWrUqNFN2aYzccSmAhkyZIg++OADzZw5U88995zq1atXbttasWKFVq5cqa5du+of//iHtmzZorlz5+rkyZOaMGGCOnbsqHbt2umpp57Svn379M033+j48ePau3dvgYc6e/fure3bt6tXr17y8PDQ8uXLNWbMGO3YsUMrVqyw62C8Zs0aPfzww7Jarfqv//ov1a1bV2fOnNGyZcv073//Wxs3blTTpk3zbeP555/Xjz/+qAcffFBdunRRlSpVbvg6V65cqZ49e8owDPXq1Uvh4eHauXOnPvroIy1fvlybNm1SRESEJGn06NE6efKk5s2bp7i4ONsvr+L+EiuJJ554QosWLVJ0dLT+/ve/y8fHR4mJidq0aZPWrFmjTp062ZYt6/01YsQITZgwQREREerdu7cCAwN19uxZbd++XV9++aX69OlT7NcxY8YMrVixQt26dVNcXJy2bdumxYsX65dfftGePXvk5eVlW/aTTz5RfHy84uLi1KlTJ+Xm5mrnzp16//33tXr1am3btk233HKLJOnxxx/XiBEjNH/+/AL/+vz888919erVYv9lm5aWpjZt2sjT01O9evVSVlaWvvzySw0YMEBubm7q37+/bVnDMNSzZ0/9+9//Vr169TR06FBZrVbNnTtXBw4cKPa+udaJEyd0zz33KCYmRk8//bTOnj2rxYsX64EHHtDChQuLtc8nTZqkw4cPq3Xr1nrwwQeVmZmpzZs3a8yYMfruu+/07bff2tr46aef1rvvvquPP/7Y7rXlmTlzpiTpH//4h90+6tChg3bv3q2mTZtqwIABys3N1X/+8x/169dPBw4c0Ntvv223n3r37q3ly5frjjvu0NChQ5Wdna05c+Zo3759Du2na40ePVpff/21fvnlFz3//PO2TvzXdub/3//9X7Vv314nT55Uu3bt1LlzZ12+fFkrV65U586dNXPmTA0aNCjfusv6+68s7dmzR1OnTlVmZqZq1aql++67T7fddluhy1+5ckWPP/64mjRpotdff12bNm0q1/pcggGXJ8moVauWYRiG8eWXXxqSjB49etgtEx4ebkgyrFarbdrGjRsNScbo0aMLXG94eLgRHh5uN+3TTz81JBlVqlQxvvvuO9v0q1evGp06dTIkGVWrVjU+//xzu+cNGDDAkGR8/fXXdtPj4uIMSUa9evWMlJQU2/QrV64YrVq1MiQZ8+fPt01PSUkxgoKCjFtvvdU4cOCA3br27dtn+Pn5GbGxsXbT+/fvb0gyatasaRw/frzA11qQP/74w6hWrZrh5uZm/PDDD3bzJk6caEgy/vKXv9hNv9E+LUhJ2yEtLc2wWCzG3XffbeTk5ORbPjk52fZzeeyvatWqGbVq1TIuX76cb9758+eLeqk2o0ePNiQZt9xyi7F37167eX379jUkGYsXL7abfvLkyQJf76xZswxJxsSJE+2m33///YYkY9++ffme06BBA8PT09NuX+XVtHHjRrtlJRmSjKeeespu+wcOHDCqVKli3HXXXXbLz58/35BktGvXzsjKyrJNT01NNe68805DkhEXF1fwjrnOiRMnbNt/5ZVX7OZt377dcHd3N4KCgoz09HTb9LzP6Keffmq3/LFjx4zc3Nx823jzzTcNScYXX3xhN/3BBx8scP9dvHjR8Pf3N2rXrm23P/LeN5MmTbJb/sqVK8Zf//pXw2KxGLt377ZNX7BggSHJaNWqlXHlyhXb9AsXLhiRkZEO7af+/fvbTc+r6cSJEwU+Ly4uzrBYLMaiRYvspqemphqNGzc2vL29jaSkJNv0sv7+K6689+Ynn3xS6DJ5tV3/r0qVKsbTTz9tt4+v9dxzzxleXl6274e876NHH33UoVorAk5FVTC9evXSPffco/j4+HJN3n379rWddpEkNzc3Pf7445Kk6OhoPfroo3bL//3vf5f0518TBRk5cqSqVq1qe+zt7a0JEyZIkubMmWObPn/+fKWlpWns2LFq0KCB3Tqio6M1aNAg7d69WwcPHsy3jddee812dKU4li9frpSUFPXp00ft2rWzm/fyyy+rTp06WrdunU6dOlXsdZYFi8UiwzDk5eVV4F9/t956q+3n8tpfHh4eBR7xKmkfleeee04xMTF20/L+Qv7555/tpoeHhxe4zQEDBiggIED/+c9/7KbnHWm4/nTKjh07dPDgQT344IN2+6oovr6+ev/99+2236BBA7Vp00aHDh3SpUuXbNPztvf222/L09PTNj0oKEgjR44s1vauFxgYqFGjRtlNa9asmR599FGlpaUpPj7+huuIjIwscGiFF198UZLy7b9nnnlG0v8/OpNn4cKFunTpkgYOHGjbHxcuXNDnn3+uZs2a6bXXXrNb3tvbW5MmTZJhGFq4cKFtet5pvHfeeUfe3t626dWqVXN4P5XEL7/8ou+//149e/bUI488YjcvKCjIdoruq6++yvfcsv7+KwsRERGaPn26jhw5osuXLysxMVFLlixRnTp1NHPmTA0YMCDfc9avX6/p06frrbfeyvf9YGaciqqApkyZotatW+uVV17RTz/9VC7bKKiHfV6H2bvvvjvfvFq1akmSzpw5U+D6rv2SyNO2bVtVqVJFu3fvtk3bunWrpD+/lArqk5KQkCBJOnToUL4PaosWLQrcdmF27dolSerQoUO+ee7u7rr33nt18uRJ7d69+6ZeSRMQEKD/+q//0jfffKMmTZqoZ8+eateunVq2bClfX1+7Zctjfz366KOaPn26GjRooN69eysuLk733HOPQ1ekFPQ+ql27tiTl6zdjtVo1c+ZMffHFFzp48KDS09OVm5trm399v5wePXooMDBQCxYs0MSJE22/hPOCR0k6WNarV08BAQFF1urv7y/pz/4Vbm5uat26db7l27ZtW+xtXqtp06a202zXat++vebNm6fdu3cXeMroWpcvX9a0adMUHx+vhIQE/fHHHzIMwzb/+v33wAMPKCIiQp999pkmTZpke299/PHHcnd318CBA23Lbt++XVevXi20r5jVapX05/ssz65du+Tm5lbgPimP07fXy/tspKenF1jz+fPnJdnXnKesv//KQlxcnN33qK+vr/72t7+pVatWaty4sRYtWqRhw4apcePGkv48dfjEE0+oZcuWevnll8utLldEsKmA7rnnHvXq1UtLly7V4sWLS9TnobgK+iXm7u5+w3l5X3DXq169eoHPCQ4O1rlz52zTLly4IOnP/hZFufYv6DxhYWFFPud66enpklRoB7686XkdBm+mxYsXa9KkSVq4cKFGjx4t6c+/jHv16qX33nvPtj/LY3/985//VGRkpD799FNNnDhREydOlLu7u7p06aIpU6aobt26xX4dBQ1emPdeuXr1qt30Pn36KD4+XpGRkerevbvCwsJsfXCmTp2ar8Opj4+PevfurU8++URr167VAw88oOzsbC1atEghISF64IEHSlVnYbWmp6erWrVqtnnXKuh9XhyFPS+vjfLeq4WxWq3q0KGDfv75Z0VHR6tPnz4KCQmRh4eHpD87u1+//9zc3PT000/r9ddf1+LFi/Xkk09q586d2rVrlx566CG7K//y3mfbt2/X9u3bC63j2vdZ3n7Kq6Gg11We8mpet26d1q1bV+hyBX02yvr7rzzVrl1bXbp00YIFC/TDDz/Ygs1LL72kCxcu2PWtqiw4FVVBTZgwQR4eHho+fHihVxblncYobNC+m/kL+/fff883LScnR8nJyXZ/Ked9afzyyy8yDKPQfwX99VrSEY7ztpWUlFTg/Lyroko7doYj7eDj46MxY8YoISFBp06d0ueff662bdvq888/V69evWzLlcf+qlKlil544QX98ssv+v333/XVV1+pR48eWrFihTp37lziK1qKY8eOHYqPj1enTp105MgRffrpp5owYYLGjBmjUaNGFfoev/501L///W9duHBB/fr1K/AXalkICAhQSkpKge1Z0Pu8OAp7Xt5780bvweXLl+vnn3/WE088oX379unjjz/W+PHjNWbMGD399NOFPm/AgAHy8vKynY7K+//65+Rt/8UXXyzyfbZx40a756SkpBT4y76wz1xZyqt52rRpRdZc2JVvFUlISIikP4/a5dm1a5euXLmi+vXr2w3kd99990mSFixYIIvFUuCYOBUdwaaCqlu3rgYPHqwTJ05o+vTpBS6T16eloIGffv311xv+FViWvv/++3zTNm3apKtXryo2NtY2rVWrVpKkH3/8sdxryttuQUPs5+Tk2Goo6IqikihtO9SuXVuPPvqo/vOf/6hu3bratGmT7a/R8t5foaGhevjhh7VkyRJ16NBBx44d0/79+8t8O7/++qskqVu3bvmOhPz888+6cuVKgc9r06aN6tWrp+XLlys9Pd0WcG502qY0YmNjlZubqy1btuSb52i/t127dumPP/7INz3vvXntZ6Qgefvv4YcfzjevoM9enpCQEPXq1Uvbtm3T5s2btWjRIkVEROj++++3W65FixZyc3Mr0fusadOmys3NLXCflNVtLfKORFx/9E+6ud8lzrZt2zZJf/azyvPwww/rqaeeyvevS5cukqQ77rhDTz31VIHvmYqOYFOBjRo1SkFBQRo/fnyBh1Pr16+vgIAALV++3O50z5UrV/Tcc8/dzFI1btw4uz4VmZmZGj58uCTpySeftE1/8sknbR37ru9cKkm5ubll9qWYN2bIokWL8vVVmjp1qk6cOKFOnTqVun9NSdvh/PnzBV4Oe/nyZV26dEnu7u62Tqtlvb+ysrK0efPmfNOtVqtSUlIkKV8/n7KQN/bI9bWeO3dOQ4YMKfK5/fv3V2ZmpmbMmKFVq1apUaNGNwwCpZHXUfTNN9+0O5KUnp6ucePGObTO9PR0vfXWW3bTduzYoQULFigwMFA9evQo8vmF7b/jx49r2LBhRT43rxNxnz59dOnSJQ0aNChfp/XQ0FA9+uijtvFQCgoSx44d04kTJ2yP8z7XI0aMsBtHJyUlxe6y8NLI6xxeUAf/Zs2aqV27dlq2bJndBQrX2rdvn91n0pXt2LEj37Tc3FxNmDBBW7duVXBwsDp37mybN2rUKM2aNSvfv1dffVXSn8Fv1qxZ+TqtmwF9bCqwatWq6Y033sh3lUIeDw8PPf/88xo3bpxiY2PVo0cP5eTkaN26dapZs+ZNHT33rrvuUsOGDe3GsTl27JgefPBB29UG0p9fVEuXLlWPHj3UqlUrdezYUQ0bNpTFYtHp06e1detWXbhwodB7ZpWEv7+/5syZo7/97W+Ki4vT3/72N91+++3auXOn1q5dq7CwsHxXjDiipO3w22+/KTY2VjExMWrUqJFq166tixcvauXKlUpKStJzzz1n62ha1vvrypUratu2rerWrau7777bdn+ydevW6dChQ+rWrZvuuuuuUu+T6zVv3lxt2rTRsmXL1Lp1a7Vt21a///67Vq9erTvvvLPI9+rjjz+uUaNGafTo0bJareV6tEb6M9h88cUXWrNmjaKjo9WtWzdZrVZ99dVXat68uY4cOVLisUzuvfdezZo1S9u2bVObNm1s49jk5uZq5syZBXZsvlbe+EXvv/++9u3bp9jYWJ06dUorV67Ugw8+WOSVfW3atFHjxo31yy+/yMPDo8CrayTpgw8+0NGjRzVq1Ch99tlnatu2rapXr67ExEQdOnRI27dvtx3xkf68smjx4sVasWKFoqOj1b17d1mtVi1dulTNmzfXsWPHSrSPCtKxY0dNnjxZgwYNUs+ePXXLLbcoKChIQ4cOlfTnFV4dOnTQU089pX/9619q2bKlgoKCdObMGe3du1f79+/X1q1bFRoaWupaSmrixIk6fPiwpP9/NdWnn35qO8LVtm1buw7czZs3V3R0tBo3bqxatWopPT1dmzdv1v79++Xr66sFCxbc8H1SadyES8pRSrpmHJvrZWZmGnXq1LGNaXDtODaGYRi5ubnGhAkTjMjISMPDw8OoXbu28eqrrxqXL18uchyb68fIMIyix2MpbJyJvHFsMjMzjREjRhh16tQxPD09jYiICGPMmDFGZmZmga/rxIkTxpAhQ4y6desaXl5exi233GLceeedxmOPPWbEx8fbLXujsSxu5OeffzYeeughIzg42LaP/vGPfxi//fZbifZBUUrSDqmpqcbYsWON++67z6hZs6bh6elphIWFGXFxccbChQsLHKukrPZXdna2MWnSJKNz585G7dq1DS8vLyM4ONho2bKl8dFHH9mN21KUwsaMyau1oPfKhQsXjGeeecYIDw83vLy8jMjISGP48OGFvlev1bFjR0OS4e7ubjcuSXFqUhHjqRS2r65cuWKMHDnS9n4ODw833njjDePMmTOGJKN79+6F1nqta/fFwYMHjW7duhlBQUGGj4+P0bp1a2PNmjX5nlPYZ/TUqVNGv379jJo1axre3t5GgwYNjEmTJhlWq/WGY8ZMnTrVkGT06tWryHqzsrKM6dOnG/fcc48REBBgeHp6GrVr1zY6dOhg/POf/7QbNyhv+bFjxxoRERF2+ykzM7NMxrExDMOYMmWKUb9+fcPT09OQlO99cvHiRWP8+PFG06ZNDT8/P8Pb29uoU6eO0aVLF2PmzJnGpUuXbMuW9fdfUfK+Gwv7d/26XnnlFePee+81atSoYXh5eRk+Pj7GnXfeaQwZMsQ4duxYsbdbGcaxsRjGNdcDAgActm7dOt1///16/fXXbeM0FeXkyZOKiIhQ//79NXfu3PIvsBBPPPGE5s2bp2+//VYdO3Z0Wh1AWaCPDQCUUN7NUK914cIFvf7665J0wz4xruT06dP64osvdNdddxU4phNQ0dDHBgBK6KWXXtIvv/xiu1HomTNntHr1aqWkpOjpp58u8WCRzrBw4UIlJCToiy++UFZWlsaNG1fiIRMAV0SwAYASevjhh/X777/rm2++UVpamry9vdWwYUPbJbUVwccff6wffvhBtWvX1j//+U/17NnT2SUBZYI+NgAAwDToYwMAAEyDYAMAAEyDYAMAAEyDYAMAAEyj0l4VlZqaWujdliuKkJAQnT9/3tllQLSFK6EtXAdt4TrM0Bbu7u62mwoXudxNqMUl5eTkyGq1OrsMh+WNN5GTkyMubHMu2sJ10Baug7ZwHZWtLTgVBQAATINgAwAATINgAwAATINgAwAATINgAwAATINgAwAATINgAwAATINgAwAATINgAwAATINgAwAATMOlbqmwZMkSLV261G5azZo1NXXqVElSdna25s+fry1btshqtapx48YaOHCggoKCbn6xAADA5bhUsJGk2rVra+TIkbbHbm7//6DSvHnztGvXLr300kvy9fXV7NmzNWXKFI0bN84ZpQIAABfjcqei3NzcFBQUZPsXEBAgScrIyNCGDRvUv39/RUdHKzIyUoMHD9aRI0eUkJDg5KoBAIArcLkjNklJSXr66afl4eGhqKgo9evXT8HBwTp+/LiuXr2qmJgY27K1atVScHCwEhISFBUV5cSqAQCAK3CpYFOvXj0NHjxYNWvWVGpqqpYuXapRo0ZpypQpSktLk7u7u/z8/OyeExgYqLS0tELXabVaZbVabY8tFot8fHxsP1dUebVX5NdgFrSF66AtXAdt4ToqW1u4VLCJjY21/RweHm4LOlu3bpWnp6dD64yPj7frkBwREaFJkyYpJCSk1PW6grCwMGeXgP9DW7gO2uLGTj/YrPy3Ue5bkGr/e8dN2Io5VJbPhUsFm+v5+fmpZs2aSkpKUqNGjZSTk6PLly/bHbVJT08v8qqoHj16qGvXrrbHeYn1/PnzysnJKbfay5vFYlFYWJiSkpJkGIazy6nUaAvXQVtUPmfPnnV2CS7PLJ8Ld3f3Yh2UcOlgk5mZqaSkJLVr106RkZGqUqWK9u3bp1atWkmSEhMTlZycXGT/Gg8PD3l4eBQ4ryI3cB7DMEzxOsyAtnAdtEXlQTsXX2X5XLhUsJk/f76aNWum4OBgpaamasmSJXJzc1Pbtm3l6+urDh06aP78+fL395evr6/mzJmjqKgoOg4DAABJLhZsUlJSNG3aNP3xxx8KCAhQ/fr1NX78eNsl3/3795fFYtGUKVOUk5NjG6APAABAcrFg88ILLxQ539PTUwMHDiTMAACAArncAH0AAACOItgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTINgAAADTcHd2AYX5+uuvtXDhQnXp0kVPPPGEJCk7O1vz58/Xli1bZLVa1bhxYw0cOFBBQUFOrRUAALgGlzxi8+uvv2rdunUKDw+3mz5v3jzt3LlTL730ksaOHavU1FRNmTLFSVUCAABX43LBJjMzU9OnT9fTTz8tPz8/2/SMjAxt2LBB/fv3V3R0tCIjIzV48GAdOXJECQkJTqwYAAC4Cpc7FTVr1izFxsaqUaNGWrZsmW368ePHdfXqVcXExNim1apVS8HBwUpISFBUVFSB67NarbJarbbHFotFPj4+tp8rqrzaK/JrMAvawnXQFpUPbX1jle1z4VLBZvPmzTpx4oQmTJiQb15aWprc3d3tjuJIUmBgoNLS0gpdZ3x8vJYuXWp7HBERoUmTJikkJKTM6namsLAwZ5eA/0NbuA7a4sZOO7uAMlKjRg1nl1BhVJbPhcsEm+TkZM2dO1dvvvmmPD09y2y9PXr0UNeuXW2P8xLr+fPnlZOTU2bbudksFovCwsKUlJQkwzCcXU6lRlu4Dtqi8jl79qyzS3B5ZvlcuLu7F+ughMsEm+PHjys9PV3Dhg2zTcvNzdWhQ4e0Zs0ajRgxQjk5Obp8+bLdUZv09PQir4ry8PCQh4dHgfMqcgPnMQzDFK/DDGgL10FbVB60c/FVls+FywSbmJgYvffee3bTPvroI9WsWVPdu3dXcHCwqlSpon379qlVq1aSpMTERCUnJxfavwYAAFQuLhNsfHx8dPvtt9tN8/Ly0i233GKb3qFDB82fP1/+/v7y9fXVnDlzFBUVRbABAACSXCjYFEf//v1lsVg0ZcoU5eTk2AboAwAAkFw82IwZM8busaenpwYOHEiYAQAABXK5AfoAAAAcRbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACmQbABAACm4e7Ik65cuaLLly8rODjYNi0lJUXr1q2T1WpVq1atVLdu3TIrEgAAoDgcCjYzZ87U+fPnNX78eElSRkaGRowYoZSUFFksFq1evVpvvPGGGjZsWKbFAgAAFMWhU1FHjhxR06ZNbY9//PFHpaamaty4cfr00091++23a9myZWVWJAAAQHE4FGwuXryoatWq2R7v2LFD9evXV1RUlHx8fBQXF6eTJ0+WVY0AAADF4lCw8fPzU1pamiQpOztbhw8fVqNGjf7/St3clJ2dXSYFAgAAFJdDfWyioqK0du1a1apVS3v27FF2draaN29um3/27Fm7IzoAAAA3g0NHbB599FFVqVJFU6ZM0fr169W1a1fVrl1bkpSbm6uffvpJd911V5kWCgAAcCMOHbGpUaOGpk6dqjNnzsjX11ehoaG2eVlZWRowYIDCw8PLrEgAAIDiKHGwycrK0vTp09WyZUu1a9cu33wfHx+701IAAAA3S4lPRXl5eWnfvn3Kysoqj3oAAAAc5lAfm/r16yshIaGsawEAACgVh4LNgAEDdPjwYX3xxRe6cOFCWdcEAADgEIc6D7/66qu6evWq4uPjFR8frypVqsjDwyPfcvPmzSt1gQAAAMXlULBp2bKlLBZLWdcCAABQKg4FmyFDhpR1HQAAAKXmUB8bAAAAV+TQERtJSk5O1rJly3TgwAFdvHhRr776qho0aKCLFy9q6dKluu+++xQREVGWtQIAABTJoSM2Z86c0WuvvaatW7cqNDRUGRkZys3NlSQFBAToyJEjWrNmTZkWCgAAcCMOBZvPP/9cfn5+mjZtmp599tl882NjY3X48OFSFwcAAFASDgWbQ4cO6S9/+YsCAgIKvDoqODhYKSkppS4OAACgJBwKNrm5ufLy8ip0/sWLF+Xu7nD3HQAAAIc4FGwiIyO1a9euAuddvXpVW7ZsUVRUVKkKAwAAKCmHgs1DDz2kPXv26JNPPtHp06clSWlpadq7d6/efvtt/fbbb+revXuZFgoAAHAjDp0vio2N1ZAhQ/Tpp5/q22+/lSRNnz5dkuTj46MhQ4aoQYMGZVclAABAMTjcEebee+9VixYttHfvXiUlJSk3N1dhYWFq3LixfHx8yrJGAACAYilVD19vb2+1aNGirGoBAAAoFYeCTXJyspKTk1W/fn3btJMnT2rlypWyWq1q06YNgQcAANx0DnUenjNnjr788kvb47S0NI0dO1bbtm3ToUOHNGXKFG3btq3MigQAACgOh4LNsWPHFBMTY3v8ww8/KDs7W5MnT9b//M//KCYmRt98802ZFQkAAFAcDgWbS5cuKTAw0PZ4586datCggcLCwuTm5qYWLVrot99+K7MiAQAAisOhYBMQEKDz589Lki5fvqyjR4+qcePGtvm5ubm2m2ICAADcLA51Ho6JidHq1avl6+urAwcOyDAMu87CZ86c0a233lpmRQIAABSHQ8GmX79+Onv2rD777DO5u7vr8ccfV2hoqCTJarVq69atatOmTZkWCgAAcCMOBZugoCCNGzdOGRkZ8vT0tLvhpWEYGjlypIKDg8usSAAAgOIo1QB9vr6++aZ5enqqTp06pVktAACAQxwKNt9//32xlouLi3Nk9QAAAA5xKNjMmDGjWMsRbAAAwM3kULD54IMP8k3Lzc3V+fPn9Z///EfJyckaMmRIide7du1arV271nYp+W233aZevXopNjZWkpSdna358+dry5Ytslqtaty4sQYOHKigoCBHXgYAADAZh8axCQkJyfevevXqio6O1ssvv6yAgACtWbOmxOutVq2a+vXrp4kTJ2rChAmKjo7Wu+++q9OnT0uS5s2bp507d+qll17S2LFjlZqaqilTpjjyEgAAgAk5FGxu5O6779bWrVtL/LxmzZqpadOmqlGjhmrWrKm+ffvK29tbR48eVUZGhjZs2KD+/fsrOjpakZGRGjx4sI4cOaKEhIRyeBUAAKCiKZdgk5SUJKvVWqp15ObmavPmzcrKylJUVJSOHz+uq1ev2t2jqlatWgoODibYAAAASQ72sTl48GCB0zMyMnTw4EGtXr1azZs3d6igU6dOacSIEbJarfL29tYrr7yi2267TSdPnpS7u7v8/Pzslg8MDFRaWlqh67NarXYhy2KxyMfHx/ZzRZVXe0V+DWZBW7gO2qLyoa1vrLJ9LhwKNmPHji10npubm1q1aqUBAwY4VFDNmjU1efJkZWRk6KefftKHH35Y5PZuJD4+XkuXLrU9joiI0KRJkxQSEuLwOl1JWFiYs0vA/6EtXAdtcWOnnV1AGalRo4azS6gwKsvnwqFgM3r06AKn+/v7Kzg4uMCB+4pdkLu7bedHRkbq2LFjWrVqlVq3bq2cnBxdvnzZ7qhNenp6kVdF9ejRQ127drU9zkus58+fV05OjsN1OpvFYlFYWJiSkpJkGIazy6nUaAvXQVtUPmfPnnV2CS7PLJ8Ld3f3Yh2UcCjYNGjQwJGnOSQ3N1dWq1WRkZGqUqWK9u3bp1atWkmSEhMTlZycrKioqEKf7+HhIQ8PjwLnVeQGzmMYhilehxnQFq6Dtqg8aOfiqyyfi1LdUqGsLVy4UE2aNFFwcLAyMzO1adMmHTx4UCNGjJCvr686dOig+fPny9/fX76+vpozZ46ioqKKDDYAAKDycKlgk56erg8//FCpqany9fVVeHi4RowYoUaNGkmS+vfvL4vFoilTpignJ8c2QB8AAIDkYsHmmWeeKXK+p6enBg4cSJgBAAAFKpdxbAAAAJyhWMFm1apVSkxMLO9aAAAASqVYwWbevHk6fvy47XGfPn20adOmcisKAADAEcUKNv7+/kWO7gsAAOAKitV5uEGDBvryyy918uRJ2+B733//fZH3aLJYLHryySfLpkoAAIBiKFawGThwoObOnau9e/cqPT1dkrR3717t3bu3yOcRbAAAwM1UrGATGBio559/3va4T58+evbZZ9W2bdtyKwwAAKCkHLrc+5lnnmG0XwAA4HIcGqCvffv2tp/PnDmj8+fPS5JCQkJ02223lUlhAAAAJeXwyMPbt2/X/Pnzde7cObvpoaGh6t+/v5o1a1bq4gAAAErCoWCza9cuTZkyRSEhIerbt6/tKM2ZM2e0fv16vffee3r99dfVpEmTsqwVAACgSA4Fm6+++krh4eEaO3asvL29bdObNWumzp07a9SoUfryyy8JNgAA4KZyqPPwqVOnFBcXZxdq8nh7e6t9+/Y6depUqYsDAAAoCYeCjYeHhy5dulTo/EuXLsnDw8PhogAAABzhULCJjo7WqlWrChx5+OjRo1q9erViYmJKXRwAAEBJONTH5rHHHtOIESM0cuRI1a1bVzVr1pQkJSYm6tdff1VgYKAeffTRMi0UAADgRhwKNqGhoXrvvfcUHx+vPXv2aMuWLZL+HMemS5cueuihhxQYGFimhQIAANyIw+PYBAYG6oknnijDUgAAAErHoT42AAAArohgAwAATINgAwAATINgAwAATINgAwAATKPEwSYrK0vDhg3T2rVry6MeAAAAh5U42Hh5eencuXOyWCzlUQ8AAIDDHDoV1aRJE/3yyy9lXQsAAECpOBRsevbsqbNnz2r69Ok6fPiwUlJSdOnSpXz/AAAAbiaHRh5++eWXJUlnzpzRpk2bCl1u8eLFjlUFAADgAIeCTc+ePeljAwAAXI5DwaZ3795lXQcAAECplck4NhkZGcrNzS2LVQEAADjM4WBz7NgxjR8/Xo899pgGDBiggwcPSpIuXryod999VwcOHCizIgEAAIrDoWBz5MgRjRo1SklJSWrXrp0Mw7DNCwgIUEZGhtatW1dmRQIAABSHQ8Fm0aJFqlWrlt5//3317ds33/yGDRvq119/LXVxAAAAJeFQsDl27Jjat28vDw+PAq+OqlatmtLS0kpbGwAAQIk4FGyqVKlid/rpeikpKfL29na4KAAAAEc4FGzq1aunn376qcB5mZmZ+u6779SgQYNSFQYAAFBSDgWb3r176/jx45owYYJ2794tSTp58qTWr1+v119/XRcvXlTPnj3LtFAAAIAbcWiAvnr16mn48OH65JNP9OGHH0qSPvvsM0lS9erVNXz4cIWHh5ddlQAAAMXgULCRpOjoaE2bNk0nTpxQUlKSDMNQ9erVFRkZye0WAACAUzgcbPJEREQoIiKiLGoBAAAoFYeDjdVq1fr167V7926dO3dOkhQaGqrY2Fh16NBBnp6eZVYkAABAcTgUbC5cuKC3335biYmJCgoKUlhYmKQ/OxDv2bNHa9as0ciRI3XrrbeWabEAAABFcSjYzJ49W+fPn9eLL76oVq1a2c3bunWrPvzwQ82ePVuvvfZamRQJAABQHA4Fm3379unBBx/MF2ok6Z577tGJEye0evXqUhcHAABQEg6NY+Pj46PAwMBC5wcFBcnHx8fhogAAABzhULBp3769vvvuO2VlZeWbl5mZqY0bN6pDhw6lLg4AAKAkinUqatu2bXaPIyIitHv3br3wwguKi4uzdR5OSkrS999/L39/f91+++1lXy0AAEARihVs3n///ULnxcfH55uWkpKiadOmqXXr1o5XBgAAUELFCjajR48u7zoAAABKrVjBhjt1AwCAisChzsMAAACuyOFbKhw+fFgbNmzQuXPndPnyZRmGYTffYrFo8uTJpS4QAACguBwKNitXrtRnn30mT09P1axZU/7+/mVdFwAAQIk5FGxWrFih+vXra9iwYfL19S3rmgAAABziUB+brKwstW3bllADAABcikPBpmHDhjp16lRZ1wIAAFAqDgWbAQMGaP/+/VqxYoUuXbpU1jUBAAA4xKE+NsHBwerUqZM+++wzLViwQJ6ennJzy5+R5s2bV+oCAQAAisuhYLN48WItW7ZM1apV0x133EFfGwAA4BIcCjbr1q1T06ZN9eqrrxZ4pMZR8fHx+vnnn/Xbb7/J09NTUVFReuyxx1SzZk3bMtnZ2Zo/f762bNkiq9Wqxo0ba+DAgQoKCiqzOgAAQMXkUCrJyclR06ZNyzTUSNLBgwf117/+VePHj9ebb76pq1ev6u2331ZmZqZtmXnz5mnnzp166aWXNHbsWKWmpmrKlCllWgcAAKiYHEomTZs21aFDh8q6Fo0YMULt27dX7dq1VadOHQ0ZMkTJyck6fvy4JCkjI0MbNmxQ//79FR0drcjISA0ePFhHjhxRQkJCmdcDAAAqFodORf3tb3/T1KlTNWvWLHXo0EHBwcEFHr0p7YjEGRkZdus5fvy4rl69qpiYGNsytWrVUnBwsBISEhQVFZVvHVarVVar1fbYYrHIx8fH9nNFlVd7RX4NZkFbuA7aovKhrW+ssn0uHAo2L7zwgiTp5MmTWrduXaHLLV682KGiJCk3N1dz587VnXfeqdtvv12SlJaWJnd3d/n5+dktGxgYqLS0tALXEx8fr6VLl9oeR0REaNKkSQoJCXG4NlcSFhbm7BLwf2gL10Fb3NhpZxdQRmrUqOHsEiqMyvK5cCjY9OzZs9yT3+zZs3X69Gm99dZbpVpPjx491LVrV9vjvLrPnz+vnJycUq3bmSwWi8LCwpSUlJTvBqS4uWgL10FbVD5nz551dgkuzyyfC3d392IdlHAo2PTu3duRpxXb7NmztWvXLo0dO1a33nqrbXpQUJBycnJ0+fJlu6M26enphV4V5eHhIQ8PjwLnVeQGzmMYhilehxnQFq6Dtqg8aOfiqyyfi7K9rKmUDMPQ7Nmz9fPPP2vUqFEKDQ21mx8ZGakqVapo3759tmmJiYlKTk4usH8NAACoXBw6YnNtn5Wi9OrVq0TrnT17tjZt2qTXXntNPj4+tn4zvr6+8vT0lK+vrzp06KD58+fL399fvr6+mjNnjqKiogg2AADAsWDz5ZdfFmu5kgabtWvXSpLGjBljN33w4MFq3769JKl///6yWCyaMmWKcnJybAP0AQAAOHxLhevl5uYqOTlZa9as0aFDh/TGG2+UeL1Lliy54TKenp4aOHAgYQYAAORTZn1s3NzcFBoaqr///e+qUaOG5syZU1arBgAAKJZy6Tx81113affu3eWxagAAgEKVS7A5duxYpRnhEAAAuA6H+th8//33BU6/fPmyDh06pJ9//lkdOnQoVWEAAAAl5VCwmTFjRqHzbrnlFnXv3r3EV0QBAACUlkPB5oMPPsg3zWKxyM/Pz3aDSQAAgJvNoWBjlhtIAgAAc3GpWyoAAACURrGP2LzyyislWrHFYtHkyZNLXBAAAICjih1s/P39i3UJd1pamhITE0tVFAAAgCOKHWyuv3/T9dLS0vT111/r6NGjcnNzU7t27UpbGwAAQIk41Hn4WnmBZv369crJyVG7du308MMPKywsrCzqAwAAKDaHg01BgaZnz56qXr16WdYHAABQbCUONtcHmnvvvVc9e/ZUaGhoedQHAABQbMUONqmpqbZAc/XqVcXFxenhhx8m0BTi6qBu5b6N0+W8/iqfrCjnLQAAULaKHWyeffZZWa1W1alTRz169FBoaKguXbqkS5cuFfqcyMjIMikSAACgOIodbKxWqyTp5MmT+uc//1ms5yxevNixqgAAABxQ7GDzzDPPlGcdAAAApVbsYNO+fftyLAMAAKD0uFcUAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDYINAAAwDXdnFwCg8rg6qFu5b+N0uW9BqvLJipuwFQCO4IgNAAAwDYINAAAwDZc6FXXw4EGtWLFCJ06cUGpqql555RW1aNHCNt8wDC1ZskTr16/X5cuXVb9+fQ0cOFA1atRwYtUAAMBVuNQRm6ysLNWpU0dPPfVUgfOXL1+u1atXa9CgQXrnnXfk5eWl8ePHKzs7+yZXCgAAXJFLBZvY2Fg98sgjdkdp8hiGoVWrVunhhx9W8+bNFR4erqFDhyo1NVXbt293QrUAAMDVuNSpqKKcO3dOaWlpatSokW2ar6+v6tatq4SEBLVp06bA51mtVlmtVttji8UiHx8f288oHPunePL2E/ur8qCtXQdtcWOV7TuqwgSbtLQ0SVJgYKDd9MDAQNu8gsTHx2vp0qW2xxEREZo0aZJCQkLKo0ybm3HJaXmj71LJhIWFObsEl2eGz4Vkjs8GbVH5VJbvqAoTbBzVo0cPde3a1fY4L7GeP39eOTk5ziqrQjh79qyzS6gQLBaLwsLClJSUJMMwnF0ObgI+G66Dtrgxs3xHubu7F+ugRIUJNkFBQZKk9PR0Va1a1TY9PT1dderUKfR5Hh4e8vDwKHBeRW7gm4H9UzKGYbDPKgna2XXQFsVXWb6jXKrzcFFCQ0MVFBSkffv22aZlZGTo119/VVRUlBMrAwAArsKljthkZmYqKSnJ9vjcuXM6efKk/P39FRwcrC5dumjZsmWqUaOGQkND9cUXX6hq1apq3ry5E6sGAACuwqWCzbFjxzR27Fjb4/nz50uS4uLiNGTIEHXv3l1ZWVmaOXOmMjIyVL9+fb3xxhvy9PR0VskAAMCFuFSwadiwoZYsWVLofIvFoj59+qhPnz43sSoAAFBRVJg+NgAAADdCsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKZBsAEAAKbh7uwCgPJ2dVC3ct/G6XLfglTlkxU3YSsAULFxxAYAAJgGwQYAAJgGwQYAAJgGwQYAAJgGwQYAAJgGwQYAAJgGwQYAAJgGwQYAAJgGwQYAAJgGwQYAAJgGwQYAAJgGwQYAAJgGwQYAAJgGd/cGAMDJrg7qVq7rP12ua/9TlU9W3ISt3BhHbAAAgGkQbAAAgGkQbAAAgGkQbAAAgGkQbAAAgGkQbAAAgGkQbAAAgGkQbAAAgGkQbAAAgGkQbAAAgGkQbAAAgGkQbAAAgGkQbAAAgGkQbAAAgGkQbAAAgGm4O7sAR6xZs0bffPON0tLSFB4ergEDBqhu3brOLgsAADhZhTtis2XLFs2fP1+9evXSpEmTFB4ervHjxys9Pd3ZpQEAACercMFm5cqV6tixo+677z7ddtttGjRokDw9PbVx40ZnlwYAAJysQgWbnJwcHT9+XDExMbZpbm5uiomJUUJCghMrAwAArqBC9bG5ePGicnNzFRQUZDc9KChIiYmJBT7HarXKarXaHlssFvn4+MjdvXxfutsdd5br+m+GKh4ezi6hTJihLSRztAdt4TpoC9dihvYo77Yo7u/tChVsHBEfH6+lS5faHrdp00bPP/+8qlatWr4b/teC8l0/io+2cB20heugLVwL7VFmKtSpqICAALm5uSktLc1uelpaWr6jOHl69OihuXPn2v4NGjTI7ghORXXlyhUNGzZMV65ccXYplR5t4TpoC9dBW7iOytYWFSrYuLu7KzIyUvv377dNy83N1f79+xUVFVXgczw8POTr62v3z8MEhy4Nw9CJEydkGIazS6n0aAvXQVu4DtrCdVS2tqhwp6K6du2qDz/8UJGRkapbt65WrVqlrKwstW/f3tmlAQAAJ6twwaZ169a6ePGilixZorS0NNWpU0dvvPFGoaeiAABA5VHhgo0kde7cWZ07d3Z2GU7l4eGhXr16meK0WkVHW7gO2sJ10Bauo7K1hcWoLCfdAACA6VWozsMAAABFIdgAAADTINgAAADTINgAAADTINgAAADTINgAAADTqJDj2FRGZ86c0Zo1a5SQkGC7V1ZQUJCioqLUuXNn3Xbbbc4tEACukXdPvsoydoorqey/LxjHpgLYvXu3Jk+erIiICDVu3Ng2ynJaWpr27dun48eP69VXX1WTJk2cWmdlUdm/NFwJbeFa9u7dq3//+99KSEhQRkaGJMnX11dRUVF68MEH1ahRIydXaH78viDYVAivvvqqmjVrpj59+hQ4f8mSJfr555/13nvv3eTKKh++NFwHbeFavvvuO82cOVMtW7ZUkyZNFBgYKElKT0/XL7/8op9++knPPPOM7r33XidXam78vuBUVIWQmJiodu3aFTq/bdu2Wr58+U2sqPJauHChunfvXuCXRu/evbVkyRJ9/vnn/DK9CWgL1xIfH6/+/fsXeLub9u3bq379+lq6dCnBppzx+4LOwxVCaGiodu3aVej8Xbt2KSQk5CZWVHkV50vj7NmzN7Giyou2cC3JyclFnmqKiYnRhQsXbmJFlRO/LzhiUyH07t1b//rXv3TgwAHFxMTYHXLfv3+/9uzZo+eee865RVYSeV8aNWvWLHB+ZfjScBW0hWu57bbbtGHDBj322GMFzt+wYQN9nm4Cfl/Qx6bCOHLkiFavXl1gJ8kuXbooKirKuQVWElu3btW//vUvNWnSpMgvjVatWjm30EqAtnAtBw4c0MSJE1W9enXFxMTY9bHZv3+/fv/9d73++utq0KCBkys1v8r++4JgA5RQZf/ScCW0hWs5d+6c1q5dq6NHj+Zrj7/85S8KDQ11boGoFAg2AADANOg8bAILFy7UjBkznF0GAMDFVYbfFwQbE0hJSdH58+edXQZUOb40KgrawrV88MEHGjt2rLPLqPQqw+8LrooygaFDhzq7BPyflJQULml1EbSFa6lWrZrc3Phb2lkMw5DFYqkUvy/oYwMAgMn17dtXkydPrhSX3HPEpoLIzs7W8ePH5e/vn++NmZ2dra1btyouLs5J1VVemZmZ2rp1q5KSklS1alW1adNGt9xyi7PLqjTOnDmjo0ePKioqSrVq1dJvv/2mVatWyWq16t5771V0dLSzS8T/SU5O1pIlSzR48GBnl2Jq8+bNK3B6bm6uvv76a9v3U//+/W9mWTcVwaYCSExM1Pjx45WcnCxJql+/vl544QVVrVpVkpSRkaEZM2YQbG6CF198UePGjZO/v7+Sk5M1evRoXb58WTVq1NDvv/+ur776SuPHj+ey1ptgz549evfdd+Xt7a2srCy9+uqr+uCDDxQeHi7DMPT222/rzTffJNy4iEuXLun7778n2JSzVatWKTw8XH5+fvnm/fbbb/L29nZCVTcXwaYCWLBggWrXrq0JEyYoIyNDc+fO1ciRIzVmzBgFBwc7u7xKJTExUVevXpX0Z+fUatWqafLkyfL19VVmZqYmT56sRYsW6fnnn3dypea3dOlSdevWTY888og2b96sadOm6f7771ffvn0l/dk+X3/9NcHmJtmxY0eR83///febVEnl1rdvX3377bf6+9//bvfe79u3r4YMGcKpKLiGhIQEjRw5UgEBAQoICNCwYcM0a9YsjRo1SqNHj5aXl5ezS6yUjh49qkGDBsnX11eS5O3trd69e2vq1KnOLaySOH36tK0j5D333KMPPvjAbpThtm3bauPGjc4qr9KZPHmys0uApIceekjR0dGaPn267r77bvXr10/u7pXrV33lerUVVHZ2tt3VBBaLRYMGDdLs2bM1ZswY09/3w9VYLBZJf7ZL3jD+eapVq6aLFy86oarKzc3NTR4eHraQKUk+Pj7KyMhwYlWVS1BQkAYOHKjmzZsXOP/kyZMaNmzYTa6qcqpbt64mTZqkWbNmafjw4Xr22WedXdJNRbCpAGrWrKnjx4/nO4T41FNPSZLeffddZ5RVab311luqUqWKrly5osTERN1+++22eefPn6fz8E0SGhqqpKQkhYWFSZLefvttu1OzycnJtn5oKH+RkZE6fvx4ocEGN5e3t7eGDh2qzZs3a9y4ccrNzXV2STcNwaYCaNGihTZv3qx7770337ynnnpKhmFo3bp1Tqis8unVq5fd4+s74u3cuVP169e/mSVVWn/5y1/svqyvDZiStHv3bvrX3ETdunVTVlZWofPDwsI0evTom1gRJKlNmzaqX7++jh8/Xmn6ZDKODQAAMA2GgQQAAKZBsAEAAKZBsAEAAKZBsAFQpg4cOKDevXvrwIEDzi4FQCXEVVEAiuW7777TjBkzbI89PDwUHBysRo0aqWfPnvnG9IG9lJQUffvtt2rRooXq1Knj7HIA0yLYACiR3r17KzQ0VFarVYcPH9batWu1e/duTZkyhVGwi5CamqqlS5cqNDSUYAOUI4INgBKJjY3VHXfcIUnq2LGjbrnlFq1cuVLbt29X27ZtnVwdgMqOYAOgVKKjo7Vy5UqdO3eu0GUOHTqk1atX6+jRo0pPT1dgYKBatmypfv36ydPTU5K0ceNGffTRR5o0aZIiIiLsnr9s2TItXrxYH330kapVq1bodlJSUrR48WLt2bNHf/zxh6pWraomTZroySeftN0v5/fff9eCBQu0b98+Wa1WhYeHq2fPnmratKltPXmn3T744AO7O7UfOHBAY8eO1ejRo9WwYUNJ0pgxY/THH3/oxRdf1OzZs3X06FH5+fmpS5cu6t69u93zJGnGjBm2U3qDBw9W+/bti7urARQDwQZAqSQlJUlSkbeS2Lp1q7KysnT//ffrlltu0a+//qo1a9YoJSVFL730kiSpVatWmj17tn788cd8wWbTpk1q2LDhDUPN8OHDlZGRoY4dO6pWrVpKSUnRTz/9pKysLLm7uystLU1vvvmmsrOz9cADD8jf31/ff/+9Jk2apJdfflktWrRwaB9cunRJ48ePV8uWLXXPPffop59+0oIFC3T77bcrNjZWtWrVUu/evbVkyRJ16tTJNjr1nXfe6dD2ABSOYAOgRDIyMnTx4kVZrVYdOXJEX331lTw9PXX33XcX+pzHHnvMdmRGkjp16qSwsDAtWrRIycnJCg4Olo+Pj5o3b67Nmzfrscces9349cSJEzpz5oy6detWZF0LFy5UWlqa3nnnHdupMknq06eP8gZY//rrr5Wenq633nrLFi46deqkV155RfPmzVOzZs3sbjhbXKmpqRo6dKjtticdOnTQ4MGDtWHDBsXGxiooKEixsbFasmSJoqKiCrw9CoCyQbABUCLjxo2zexwSEqJnn322yKMp14aazMxMZWdnKyoqSoZh6MSJE7Z72MTFxWnz5s06cOCAYmJiJEk//vijPD091bJly0LXn5ubq+3bt+vuu++2CzV58u7Ivnv3btWtW9fufl7e3t7q1KmTFi5cqDNnzuS751RxeHt7q127drbH7u7uqlu3bpGn5wCUD4INgBJ56qmnVKNGDVWpUkWBgYGqWbPmDY9yJCcna/HixdqxY4cuX75sNy8jI8P2c6NGjVS1alX9+OOPiomJUW5urjZv3qxmzZrJx8en0PVfvHhRV65cuWEoSU5OVr169fJNr1Wrlm2+I8Hm1ltvtYWnPH5+fvrf//3fEq8LQOkQbACUSN26dQs8KlKY3NxcjRs3TpcuXVL37t1Vq1YteXl5KSUlRTNmzNC19+F1c3NTmzZttH79eg0cOFBHjhxRamqqy5y6ufZu4tdy5PQVgPJBsAFQrk6dOqWzZ89qyJAhiouLs03fu3dvgcvHxcVp5cqV2rlzp3bv3q2AgAA1bty4yG0EBATIx8dHp06dKnK54OBgJSYm5pv+22+/2eZLkr+/vyT7o0mSdP78+SLXX5Trj+gAKB/8mQGgXOUdzbj2yIxhGFq1alWBy4eHhys8PFwbNmzQtm3b1Lp1a1WpUuWG22jevLl27typY8eO5Zuft+3Y2Fj9+uuvSkhIsM3LzMzU+vXrFRISottuu02SVL16dUnSwYMHbcvl5uZq/fr1xXnJBcobvPD6U3EAyhZHbACUq5o1a6p69er67LPPlJKSIl9fX23btk2XLl0q9Dn33nuvPvvsM9vPxdGvXz/t3btXY8aMUceOHXXbbbcpNTVVP/30k9566y35+fnpoYce0ubNm/XOO+/YXe597tw5vfzyy7YQVrt2bdWrV0+LFi3SpUuX5O/vry1btujq1asO74fq1avLz89P69atk4+Pj7y8vFSvXj27cXIAlB5HbACUK3d3dw0bNkx16tTR119/rS+//FJhYWEaOnRooc9p166d3NzcVKNGDdWtW7dY26lWrZreeecdtWzZUps2bdKnn36qH374QQ0aNLAdLQkKCtLbb7+tRo0aac2aNVq4cKGtvuvHsHnuuecUFRWl5cuXKz4+Xg0bNlS/fv1KtR+GDBkiNzc3ffLJJ5o2bZrdESEAZcNiXHt8GABcwMWLF/X000+rZ8+e6tWrl7PLAVCBcMQGgMv57rvvlJub6zJXQwGoOOhjA8Bl7N+/X2fOnFF8fLyaN29O/xMAJUawAeAyli5dqiNHjqh+/foaMGCAs8sBUAHRxwYAAJgGfWwAAIBpEGwAAIBpEGwAAIBpEGwAAIBpEGwAAIBpEGwAAIBpEGwAAIBpEGwAAIBpEGwAAIBp/D83o5BEq+jCEAAAAABJRU5ErkJggg==",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "from collections import Counter\n",
+ "\n",
+ "import matplotlib.pyplot as plt\n",
+ "import matplotlib\n",
+ "%matplotlib inline\n",
+ "\n",
+ "matplotlib.style.use('ggplot')\n",
+ "counter = Counter([r for (_, r) in trainset.ir[trainset.to_inner_iid(154)]])\n",
+ "pd.DataFrame.from_dict(counter, orient='index').plot(kind='bar', legend=False)\n",
+ "plt.xlabel('Play count')\n",
+ "plt.ylabel('Number of users')\n",
+ "plt.title('Number of users having played item 154')\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "hKlNh8XidH1d"
+ },
+ "source": [
+ "As we can see in the above figure, most of the users played the song only once and very small number played the song 5 times and our target user is among them. So he is a biased user.\n",
+ "\n",
+ "**We can build another model for those biased users. And combine the models to create an hybrid recommendation system.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "zx8VFfPReMMQ"
+ },
+ "source": [
+ "## Customizing the KNNBasic algorithm"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "qUz-34xAezev"
+ },
+ "outputs": [],
+ "source": [
+ "from surprise.prediction_algorithms.knns import SymmetricAlgo\n",
+ "\n",
+ "from surprise.prediction_algorithms.predictions import PredictionImpossible\n",
+ "from sklearn.ensemble import RandomForestRegressor\n",
+ "\n",
+ "class CollaborativeRandomForest(SymmetricAlgo):\n",
+ " \"\"\"A basic collaborative filtering algorithm.\n",
+ "\n",
+ " The prediction :math:`\\\\hat{r}_{ui}` is set as:\n",
+ "\n",
+ " .. math::\n",
+ " \\\\hat{r}_{ui} = \\\\frac{\n",
+ " \\\\sum\\\\limits_{v \\\\in N^k_i(u)} \\\\text{sim}(u, v) \\\\cdot r_{vi}}\n",
+ " {\\\\sum\\\\limits_{v \\\\in N^k_i(u)} \\\\text{sim}(u, v)}\n",
+ "\n",
+ " or\n",
+ "\n",
+ " .. math::\n",
+ " \\\\hat{r}_{ui} = \\\\frac{\n",
+ " \\\\sum\\\\limits_{j \\\\in N^k_u(i)} \\\\text{sim}(i, j) \\\\cdot r_{uj}}\n",
+ " {\\\\sum\\\\limits_{j \\\\in N^k_u(i)} \\\\text{sim}(i, j)}\n",
+ "\n",
+ " depending on the ``user_based`` field of the ``sim_options`` parameter.\n",
+ "\n",
+ " Args:\n",
+ " k(int): The (max) number of neighbors to take into account for\n",
+ " aggregation (see :ref:`this note `). Default is\n",
+ " ``40``.\n",
+ " min_k(int): The minimum number of neighbors to take into account for\n",
+ " aggregation. If there are not enough neighbors, the prediction is\n",
+ " set to the global mean of all ratings. Default is ``1``.\n",
+ " sim_options(dict): A dictionary of options for the similarity\n",
+ " measure. See :ref:`similarity_measures_configuration` for accepted\n",
+ " options.\n",
+ " verbose(bool): Whether to print trace messages of bias estimation,\n",
+ " similarity, etc. Default is True.\n",
+ " \"\"\"\n",
+ "\n",
+ " def __init__(self, k=40, min_k=1, sim_options={}, verbose=True, **kwargs):\n",
+ "\n",
+ " SymmetricAlgo.__init__(self, sim_options=sim_options, verbose=verbose, **kwargs)\n",
+ " self.k = k\n",
+ " self.min_k = min_k\n",
+ " self.rf = RandomForestRegressor()\n",
+ "\n",
+ " def compute_rating_and_sim_averages(self, neighbors):\n",
+ " # compute weighted average\n",
+ " sum_sim = sum_ratings = actual_k = rating = 0\n",
+ " if (len(neighbors) != 0):\n",
+ " for (sim, r) in neighbors:\n",
+ " sum_sim += sim\n",
+ " sum_ratings += sim * r\n",
+ " else:\n",
+ " rating = self.trainset.global_mean\n",
+ "\n",
+ " if sum_sim != 0 :\n",
+ " rating = sum_ratings/sum_sim\n",
+ " return (sum_sim, rating)\n",
+ "\n",
+ " def fit(self, trainset):\n",
+ " SymmetricAlgo.fit(self, trainset)\n",
+ " self.sim = self.compute_similarities()\n",
+ "\n",
+ " #The feature matrix\n",
+ " # an observation in x is the wheighted average of similarities for each user.\n",
+ " # For each user we compute the wheighted average of the similarities\n",
+ " # of all the users who listened to a target song for all the songs in the training dataset\n",
+ "\n",
+ " X = []\n",
+ " #The target, the play counts\n",
+ "\n",
+ " Y = []\n",
+ " # an observation in Y is the wheighted average of play counts for each user.\n",
+ " # For each user we compute the wheighted average of the play counts\n",
+ " # of all the users who listened to a target song for all the songs in the training dataset\n",
+ " for u in trainset.all_users():\n",
+ " for i in trainset.all_items():\n",
+ " x, y = self.switch(u, i)\n",
+ " neighbors = [(self.sim[x, x2], r) for (x2, r) in self.yr[y]]\n",
+ " n = self.compute_rating_and_sim_averages(neighbors)\n",
+ " X.append(n[0])\n",
+ " Y.append(n[1])\n",
+ "\n",
+ " self.rf.fit(np.array(X).reshape(-1, 1), Y)\n",
+ "\n",
+ " return self\n",
+ "\n",
+ " def estimate(self, u, i):\n",
+ " if not (self.trainset.knows_user(u) and self.trainset.knows_item(i)):\n",
+ " raise PredictionImpossible(\"User and/or item is unknown.\")\n",
+ "\n",
+ " x, y = self.switch(u, i)\n",
+ "\n",
+ " neighbors = [(self.sim[x, x2], r) for (x2, r) in self.yr[y]]\n",
+ "\n",
+ " n = self.compute_rating_and_sim_averages(neighbors)[0]\n",
+ " est = self.rf.predict(np.array(n).reshape(-1, 1))[0]\n",
+ "\n",
+ " details = {\"actual_k\": 6}\n",
+ " return est, details"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "wKN1UHcrfPIM",
+ "outputId": "d8ca0036-16d5-43eb-b7bf-8d97d9701da0"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "RMSE: 1.5212\n",
+ "Precision: 0.55\n",
+ "Recall: 0.825\n",
+ "F_1 score: 0.66\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Build the default KNNBasicWithRandomForest model\n",
+ "sim_options=dict(name='cosine', user_based=True)\n",
+ "\n",
+ "# KNN algorithm is used to find desired similar items\n",
+ " # Use random_state = 1\n",
+ "sim_user_user_model = CollaborativeRandomForest(sim_options=sim_options,\n",
+ " random_state=1, verbose=False)\n",
+ "\n",
+ "# Train the algorithm on the trainset, and predict play_count for the testset\n",
+ "sim_user_user_model.fit(trainset)\n",
+ "\n",
+ "# Let us compute precision@k, recall@k, and f_1 score with k = 30\n",
+ " # Use sim_user_user model\n",
+ "precision_recall_at_k(sim_user_user_model, threshold=1.5, testset=testset)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "9f_g_fBxm-0P"
+ },
+ "source": [
+ "The F1 score is still 0.66. So there is no improvement.\n",
+ "\n",
+ "The performance of this model is not high. This is because we are using only one future, the weighted similarities. This is comparable to predicting the skin color of a person based on his age, it doesn't make much sense."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "RjNjIClc5zJx"
+ },
+ "source": [
+ "## Building an hybrid recommendation system:\n",
+ " - We will use LightFM a newly discovered library"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "BSPO0OGa5uDn",
+ "outputId": "5dd6c42d-ff6e-413c-8097-3ce23b6666e7"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Collecting lightfm\n",
+ " Downloading lightfm-1.17.tar.gz (316 kB)\n",
+ "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m316.4/316.4 kB\u001b[0m \u001b[31m5.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+ "\u001b[?25h Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
+ "Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from lightfm) (1.25.2)\n",
+ "Requirement already satisfied: scipy>=0.17.0 in /usr/local/lib/python3.10/dist-packages (from lightfm) (1.11.4)\n",
+ "Requirement already satisfied: requests in /usr/local/lib/python3.10/dist-packages (from lightfm) (2.31.0)\n",
+ "Requirement already satisfied: scikit-learn in /usr/local/lib/python3.10/dist-packages (from lightfm) (1.2.2)\n",
+ "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests->lightfm) (3.3.2)\n",
+ "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests->lightfm) (3.7)\n",
+ "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests->lightfm) (2.0.7)\n",
+ "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests->lightfm) (2024.6.2)\n",
+ "Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from scikit-learn->lightfm) (1.4.2)\n",
+ "Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from scikit-learn->lightfm) (3.5.0)\n",
+ "Building wheels for collected packages: lightfm\n",
+ " Building wheel for lightfm (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
+ " Created wheel for lightfm: filename=lightfm-1.17-cp310-cp310-linux_x86_64.whl size=808332 sha256=d64d30146d7416b7fe06ca9cedc9b73684e7c816de8f801d9ac91613e4102458\n",
+ " Stored in directory: /root/.cache/pip/wheels/4f/9b/7e/0b256f2168511d8fa4dae4fae0200fdbd729eb424a912ad636\n",
+ "Successfully built lightfm\n",
+ "Installing collected packages: lightfm\n",
+ "Successfully installed lightfm-1.17\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Installing lightfm\n",
+ "!pip install lightfm"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Hpzd-9XZ6mes"
+ },
+ "outputs": [],
+ "source": [
+ "# Importing packages\n",
+ "from lightfm import LightFM\n",
+ "from lightfm.data import Dataset\n",
+ "from lightfm.evaluation import precision_at_k, recall_at_k, auc_score\n",
+ "from lightfm.cross_validation import random_train_test_split"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "jvunwYG562-U"
+ },
+ "outputs": [],
+ "source": [
+ "# Modifying the dataset cutoffs thresholds\n",
+ "# Get the column containing the users\n",
+ "df = df_copy\n",
+ "users = df.user_id\n",
+ "\n",
+ "# Create a dictionary that maps users(listeners) to the number of songs that they have listened to\n",
+ "playing_count = dict()\n",
+ "\n",
+ "for user in users:\n",
+ " # If we already have the user, just add 1 to their playing count\n",
+ " if user in playing_count:\n",
+ " playing_count[user] += 1\n",
+ "\n",
+ " # Otherwise, set their playing count to 1\n",
+ " else:\n",
+ " playing_count[user] = 1\n",
+ "\n",
+ "# We want our users to have listened at least 90 songs\n",
+ "SONG_COUNT_CUTOFF = 90\n",
+ "\n",
+ "# Create a list of users who need to be removed\n",
+ "remove_users = []\n",
+ "\n",
+ "for user, num_songs in playing_count.items():\n",
+ "\n",
+ " if num_songs < SONG_COUNT_CUTOFF:\n",
+ " remove_users.append(user)\n",
+ "\n",
+ "df = df.loc[ ~ df.user_id.isin(remove_users)]\n",
+ "\n",
+ "\n",
+ "# Get the column containing the songs\n",
+ "songs = df.song_id\n",
+ "\n",
+ "# Create a dictionary that maps songs to its number of users(listeners)\n",
+ "playing_count = dict()\n",
+ "\n",
+ "for song in songs:\n",
+ " # If we already have the song, just add 1 to their playing count\n",
+ " if song in playing_count:\n",
+ " playing_count[song] += 1\n",
+ "\n",
+ " # Otherwise, set their playing count to 1\n",
+ " else:\n",
+ " playing_count[song] = 1\n",
+ "\n",
+ "# We want our song to be listened by at least 120 users to be considred\n",
+ "LISTENER_COUNT_CUTOFF = 120\n",
+ "\n",
+ "remove_songs = []\n",
+ "\n",
+ "for song, num_users in playing_count.items():\n",
+ " if num_users < LISTENER_COUNT_CUTOFF:\n",
+ " remove_songs.append(song)\n",
+ "\n",
+ "df_final = df.loc[ ~ df.song_id.isin(remove_songs)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "IOs_VeEb9nRn"
+ },
+ "outputs": [],
+ "source": [
+ "# Caping the play count to 5\n",
+ "df_final.loc[df_final.play_count>5, 'play_count'] = 5"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "CfYCO0b18_jy"
+ },
+ "outputs": [],
+ "source": [
+ "# rescaling the playcount [0, 1]\n",
+ "df_final['play_count_scaled'] = 1 / df_final.play_count"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Xk_rGIJt9bh0"
+ },
+ "outputs": [],
+ "source": [
+ "# Setting desired positive pairs (playcount>1.5 or play_count_scaled>0.3 ) to 1\n",
+ "# and negatives pairs to 0\n",
+ "df_final.loc[df_final['play_count_scaled'] < 0.3, 'play_count_scaled'] = 0\n",
+ "df_final.loc[df_final['play_count_scaled'] >= 0.3, 'play_count_scaled'] = 1"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "76IJiwGW-XuY"
+ },
+ "outputs": [],
+ "source": [
+ "# Defining users and songs sets\n",
+ "user_ids = set(df_final.user_id)\n",
+ "songs_ids = set(df_final.song_id)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "EQ-X68nc-nHy"
+ },
+ "outputs": [],
+ "source": [
+ "# Defining user features\n",
+ "nbr_user_listened_songs = df_final.groupby('user_id').play_count.count()\n",
+ "nbr_user_listened_songs = nbr_user_listened_songs / nbr_user_listened_songs.values.sum()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "42g9Pgxt-2UT"
+ },
+ "outputs": [],
+ "source": [
+ "# Defining songs features\n",
+ "df_final['text'] = df_final['title'] + ' ' + df_final['release'] + ' ' + df_final['artist_name']"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "H2Yo7Q2e_OEW"
+ },
+ "outputs": [],
+ "source": [
+ "# Create a Dataset object and fit users, songs, and features\n",
+ "dataset = Dataset()\n",
+ "dataset.fit(users=user_ids,\n",
+ " items=songs_ids,\n",
+ " user_features = set(nbr_user_listened_songs),\n",
+ " item_features = set(df_final.text))\n",
+ "\n",
+ "\n",
+ "# Build interactions and features\n",
+ "interactions = [(x[0], x[1], x[2]) for x in df_final[['user_id', 'song_id', 'play_count_scaled']].values]\n",
+ "(interactions, weights) = dataset.build_interactions(interactions)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "izc2by8-AU1z"
+ },
+ "outputs": [],
+ "source": [
+ "# Split the interactions matrix into a train and test set\n",
+ "test_ratio = 0.2\n",
+ "train_interactions, test_interactions = random_train_test_split(interactions, test_percentage=test_ratio, random_state=2)\n",
+ "train_weights, test_weights = random_train_test_split(weights, test_percentage=test_ratio, random_state=2)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "YRnZRWUHABvo"
+ },
+ "outputs": [],
+ "source": [
+ "# Builiding user features\n",
+ "user_features = list(zip(nbr_user_listened_songs.index, nbr_user_listened_songs.apply(lambda x: [x])))\n",
+ "user_features = dataset.build_user_features(user_features)\n",
+ "\n",
+ "\n",
+ "# Builiding items features\n",
+ "item_features = list((x[0], [x[1]]) for x in df_final[['song_id', 'text']].drop_duplicates().values)\n",
+ "item_features = dataset.build_item_features(item_features)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "uXGuF8Y2Agrh"
+ },
+ "outputs": [],
+ "source": [
+ "# Creating the LightFM model\n",
+ "basic_lfm = LightFM(\n",
+ " no_components=150,\n",
+ " learning_rate=0.05,\n",
+ " loss='warp',\n",
+ " item_alpha=0.0001,\n",
+ " user_alpha=0.0001,\n",
+ " random_state=200)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "3wyw_BAlApGT",
+ "outputId": "a54998f5-5593-4c10-cfae-9971751dfa9e"
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Epoch: 100%|██████████| 50/50 [00:52<00:00, 1.05s/it]\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 105,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Training the model\n",
+ "basic_lfm.fit(train_interactions,\n",
+ " sample_weight=train_weights,\n",
+ " user_features=user_features,\n",
+ " item_features=item_features,\n",
+ " epochs=50, num_threads=50, verbose=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "a8ZSBWjVA7gF"
+ },
+ "outputs": [],
+ "source": [
+ "# A function to evaluate the model using LightFm evaluation metrics\n",
+ "def evaluate_lfm_model(model, test_interactions, user_features, item_features,\n",
+ " k=30):\n",
+ " # compute the auc\n",
+ " print('AUC', np.nanmean(auc_score(model,\n",
+ " test_interactions,\n",
+ " user_features=user_features,\n",
+ " item_features=item_features)))\n",
+ " # calculate precision_at_k\n",
+ " precision = precision_at_k(model,\n",
+ " test_interactions,\n",
+ " user_features=user_features,\n",
+ " item_features=item_features,\n",
+ " k=k\n",
+ " ).mean()\n",
+ " print(\"Precision:\", precision)\n",
+ "\n",
+ "\n",
+ " # calculate recall_at_k\n",
+ " recall = recall_at_k(model,\n",
+ " test_interactions,\n",
+ " user_features=user_features,\n",
+ " item_features=item_features,\n",
+ " k=k).mean()\n",
+ " print(\"Recall:\", recall)\n",
+ "\n",
+ " # Calculate f1_score_at_k\n",
+ " f1_score = 2 * precision * recall / (recall + precision)\n",
+ " print(\"F1_score:\", f1_score)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "BoHvblWODigS",
+ "outputId": "3d3a7aca-2627-4956-b953-7a8b0dce680d"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "AUC 0.7671651\n",
+ "Precision: 0.029273298\n",
+ "Recall: 0.16732867337841772\n",
+ "F1_score: 0.049829226368503614\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Evaluating the performance of the model using lightfm evaluation metrics\n",
+ "evaluate_lfm_model(basic_lfm, test_interactions, user_features,\n",
+ " item_features, 30)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "moTYO782EQSX"
+ },
+ "source": [
+ "The AUC is 0.76. It measures the probability that a randomly chosen positive interaction (e.g., a user liking a song) is ranked higher by the model than a randomly chosen negative interaction (e.g., a user not liking a song)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "_2vGSnD4Znno"
+ },
+ "source": [
+ "### Let's evaluate the performance of the model with a threshold in Surprise library manner"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "UJEGaNOnKESI"
+ },
+ "outputs": [],
+ "source": [
+ "# Evaluating the model in an explicit feedback manner\n",
+ "# Let's get the predictions and actual playcount for every user-song pair in the testset\n",
+ "def get_predictions(model, df_final, dataset, test_interactions):\n",
+ "\n",
+ " # Get the mappings\n",
+ " user_id_mapping, user_feature_mapping, item_id_mapping, item_feature_mapping = dataset.mapping()\n",
+ "\n",
+ " # Create reverse mappings\n",
+ " reverse_user_id_mapping = {v: k for k, v in user_id_mapping.items()}\n",
+ " reverse_item_id_mapping = {v: k for k, v in item_id_mapping.items()}\n",
+ "\n",
+ " df_interactions = df_final[['user_id', 'song_id', 'play_count_scaled']]\n",
+ "\n",
+ " # Function to get real ratings using internal IDs\n",
+ " def get_real_rating(internal_user_id, internal_item_id, real_user_id = None,\n",
+ " real_item_id = None):\n",
+ " if not real_user_id:\n",
+ " real_user_id = reverse_user_id_mapping.get(internal_user_id, None)\n",
+ " if not real_item_id:\n",
+ " real_item_id = reverse_item_id_mapping.get(internal_item_id, None)\n",
+ " if real_user_id is not None and real_item_id is not None:\n",
+ " rating = df_interactions.loc[\n",
+ " (df_interactions['user_id'] == real_user_id) &\n",
+ " (df_interactions['song_id'] == real_item_id), 'play_count_scaled']\n",
+ " if not rating.empty:\n",
+ " return rating.values[0]\n",
+ " return None\n",
+ "\n",
+ "\n",
+ " # Function to get real user_id and item_id\n",
+ " def get_real_ids(internal_user_id, internal_item_id):\n",
+ " # Reverse the user_id_mapping and item_id_mapping to map internal IDs to real IDs\n",
+ " real_user_id = {v: k for k, v in user_id_mapping.items()}[internal_user_id]\n",
+ " real_item_id = {v: k for k, v in item_id_mapping.items()}[internal_item_id]\n",
+ " return real_user_id, real_item_id\n",
+ "\n",
+ "\n",
+ " user_song_pairs = test_interactions.nonzero()\n",
+ " user_and_song_iternal_ids = list(zip(user_song_pairs[0], user_song_pairs[1]))\n",
+ "\n",
+ " ests = []\n",
+ " trues = []\n",
+ " user_est_true_lfm = defaultdict(list)\n",
+ " for iternal_user, internal_song in user_and_song_iternal_ids:\n",
+ "\n",
+ " real_user_id, real_item_id = get_real_ids(iternal_user, internal_song)\n",
+ "\n",
+ " score = model.predict(int(iternal_user), [int(internal_song)])[0]\n",
+ " playcount = get_real_rating(iternal_user, internal_song, real_user_id, real_item_id)\n",
+ " user_est_true_lfm[real_user_id].append((score, playcount))\n",
+ " ests.append(score)\n",
+ " trues.append(playcount)\n",
+ "\n",
+ " return user_est_true_lfm, ests, trues\n",
+ "\n",
+ "\n",
+ "\n",
+ "# Let's customize the precision_recall_at_k function\n",
+ "def precision_recall_at_k_lfm(k=30, threshold=0, user_est_true=None):\n",
+ " \"\"\"Return precision and recall at k metrics for each user\"\"\"\n",
+ "\n",
+ " precisions = dict()\n",
+ " recalls = dict()\n",
+ "\n",
+ " for uid, playing_count in user_est_true.items():\n",
+ "\n",
+ " # Sort play count by estimated value\n",
+ " playing_count.sort(key=lambda x: x[0], reverse=True)\n",
+ "\n",
+ " # Number of relevant items\n",
+ " n_rel = sum((true_r >= threshold) for (_, true_r) in playing_count)\n",
+ "\n",
+ " # Number of recommended items in top k\n",
+ " n_rec_k = sum((est >= threshold) for (est, _) in playing_count[:k])\n",
+ "\n",
+ " # Number of relevant and recommended items in top k\n",
+ " n_rel_and_rec_k = sum(((true_r >= threshold) and (est >= threshold))\n",
+ " for (est, true_r) in playing_count[:k])\n",
+ "\n",
+ " # Precision@K: Proportion of recommended items that are relevant\n",
+ " # When n_rec_k is 0, Precision is undefined. We here set Precision to 0 when n_rec_k is 0.\n",
+ "\n",
+ " precisions[uid] = n_rel_and_rec_k / n_rec_k if n_rec_k != 0 else 0\n",
+ "\n",
+ " # Recall@K: Proportion of relevant items that are recommended\n",
+ " # When n_rel is 0, Recall is undefined. We here set Recall to 0 when n_rel is 0.\n",
+ "\n",
+ " recalls[uid] = n_rel_and_rec_k / n_rel if n_rel != 0 else 0\n",
+ "\n",
+ "\n",
+ " #Mean of all the predicted precisions are calculated.\n",
+ " precision = round((sum(prec for prec in precisions.values()) / len(precisions)),3)\n",
+ " #Mean of all the predicted recalls are calculated.\n",
+ " recall = round((sum(rec for rec in recalls.values()) / len(recalls)),3)\n",
+ "\n",
+ " #accuracy.rmse(predictions)\n",
+ " print('Precision: ', precision) #Command to print the overall precision\n",
+ " print('Recall: ', recall) #Command to print the overall recall\n",
+ " print('F_1 score: ', round((2*precision*recall)/(precision+recall),3)) # Formula to compute the F-1 score."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "vzYpAkWhM6DG",
+ "outputId": "0886a365-313d-442d-f570-aa2684c16c6a"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "min_est_score: -7.2498407\n",
+ "max_est_score: 5.147733\n"
+ ]
+ }
+ ],
+ "source": [
+ "# getting the prediction for each user-pair of the testset\n",
+ "user_est_true_lfm = get_predictions(basic_lfm, df_final, dataset, test_interactions)\n",
+ "ests = user_est_true_lfm[1]\n",
+ "print('min_est_score:', min(ests))\n",
+ "print('max_est_score:', max(ests))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "XuggL7kWSnxD",
+ "outputId": "3487b15c-d2e7-4ab7-b15c-c024f1792b7f"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Precision: 0.959\n",
+ "Recall: 0.68\n",
+ "F_1 score: 0.796\n"
+ ]
+ }
+ ],
+ "source": [
+ "# evaluate the model with a threshold\n",
+ "precision_recall_at_k_lfm(user_est_true=user_est_true_lfm[0], threshold=-1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Eeo4XxTQEmW4"
+ },
+ "source": [
+ "### Rebuilidng the model by transforming the item features with a vectorizer"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "S-QQsigDFlA1",
+ "outputId": "6d51d7e7-3ccc-4305-c716-f7885e3d757b"
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Epoch: 100%|██████████| 50/50 [07:22<00:00, 8.84s/it]\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 124,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Taking a sample of the dataset\n",
+ "df_final = df_final.sample(3000, random_state=42)\n",
+ "\n",
+ "# Defining users and songs sets\n",
+ "user_ids = set(df_final.user_id)\n",
+ "songs_ids = set(df_final.song_id)\n",
+ "\n",
+ "# Defining user features\n",
+ "nbr_user_listened_songs = df_final.groupby('user_id').play_count.count()\n",
+ "nbr_user_listened_songs = nbr_user_listened_songs / nbr_user_listened_songs.values.sum()\n",
+ "\n",
+ "# Defining songs features\n",
+ "df_final['text'] = df_final['title'] + ' ' + df_final['release'] + ' ' + df_final['artist_name']\n",
+ "\n",
+ "# Create tfidf vectorizer\n",
+ "tfidf = TfidfVectorizer(tokenizer = tokenize)\n",
+ "\n",
+ "# Creating the songs tfidf\n",
+ "songs_tfidf = tfidf.fit_transform(df_final['text'].values).toarray()\n",
+ "\n",
+ "# Create a Dataset object and fit users, songs, and features\n",
+ "dataset = Dataset()\n",
+ "dataset.fit(users=user_ids, items=songs_ids,\n",
+ " user_features = set(nbr_user_listened_songs),\n",
+ " item_features = set(songs_tfidf.ravel()))\n",
+ "\n",
+ "\n",
+ "# Build interactions and features\n",
+ "interactions = [(x[0], x[1], x[2]) for x in df_final[['user_id', 'song_id', 'play_count_scaled']].values]\n",
+ "(interactions, weights) = dataset.build_interactions(interactions)\n",
+ "\n",
+ "\n",
+ "# Split the interactions matrix into a train and test set\n",
+ "test_ratio = 0.2\n",
+ "train_interactions, test_interactions = random_train_test_split(interactions, test_percentage=test_ratio, random_state=2)\n",
+ "train_weights, test_weights = random_train_test_split(weights, test_percentage=test_ratio, random_state=2)\n",
+ "\n",
+ "\n",
+ "# Builiding user features\n",
+ "user_features = list(zip(nbr_user_listened_songs.index, nbr_user_listened_songs.apply(lambda x: [x])))\n",
+ "user_features = dataset.build_user_features(user_features)\n",
+ "\n",
+ "\n",
+ "# Builiding items features\n",
+ " # Map item IDs to the order of the TF-IDF matrix\n",
+ "item_features_map = dict(zip(songs_ids, songs_tfidf))\n",
+ " # Fit the dataset with item features\n",
+ "dataset.fit_partial(items=songs_ids, item_features=range(songs_tfidf.shape[1]))\n",
+ "\n",
+ "itl = []\n",
+ " # Build the item features list\n",
+ "for item_id in songs_ids:\n",
+ " for i, value in enumerate(item_features_map[item_id]):\n",
+ " itl.append((item_id, (i, value)))\n",
+ "\n",
+ "item_features = dataset.build_item_features(itl)\n",
+ "\n",
+ "\n",
+ "vectorized_lfm = LightFM(\n",
+ " no_components=150,\n",
+ " learning_rate=0.05,\n",
+ " loss='warp',\n",
+ " random_state=200)\n",
+ "\n",
+ "\n",
+ "vectorized_lfm.fit(train_interactions,\n",
+ " sample_weight=train_weights,\n",
+ " user_features=user_features,\n",
+ " item_features=item_features,\n",
+ " epochs=50, num_threads=50, verbose=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "ykL_2uVqIpmm",
+ "outputId": "0930aacb-2283-4cc9-b9e3-0a44fff31ae9"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "AUC 0.56468457\n",
+ "Precision: 0.0035021894\n",
+ "Recall: 0.09036898061288305\n",
+ "F1_score: 0.006743056195333613\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Evaluating the model with lightfm evaluation metrics\n",
+ "evaluate_lfm_model(vectorized_lfm, test_interactions, user_features,\n",
+ " item_features, 30)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "wyC5wI9Qg-WK"
+ },
+ "source": [
+ "The AUC has not improved\n",
+ " - It is due to the small number of observations used to train the model.\n",
+ " - With the same number of observations this approach gives a slightly better performance. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "l-k8y5EuZ6qd",
+ "outputId": "8ac65ce2-bb32-40fd-b239-cc517f78b8b2"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "min_est_score: -1.80968\n",
+ "max_est_score: 0.08069802\n"
+ ]
+ }
+ ],
+ "source": [
+ "\n",
+ "# getting the prediction for each user-pair of the testset\n",
+ "user_est_true_lfm = get_predictions(vectorized_lfm, df_final, dataset, test_interactions)\n",
+ "ests = user_est_true_lfm[1]\n",
+ "print('min_est_score:', min(ests))\n",
+ "print('max_est_score:', max(ests))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "wZnSVuhBaDqO",
+ "outputId": "6d351beb-27c0-4c0e-c3fa-e08cbb517b6b"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Precision: 0.368\n",
+ "Recall: 0.357\n",
+ "F_1 score: 0.362\n"
+ ]
+ }
+ ],
+ "source": [
+ "# evaluate the model with a threshold in Surprise manner\n",
+ "precision_recall_at_k_lfm(user_est_true=user_est_true_lfm[0], threshold=-0.01)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "2OgZ75qyhmk0"
+ },
+ "source": [
+ "With no surprise the threshold evaluation f1 score is not better."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "uZUihhnknRVa"
+ },
+ "source": [
+ "## Final Conclusion\n",
+ "\n",
+ "**We improved the F1 score of the user user collaborative filtering model by 0.15 points (0.66 vs 0.51 before)** just by:\n",
+ " - changing the **cutoffs thresholds**\n",
+ " - **dropping old observations**\n",
+ " - **caping the play counts** greater than 5 **to 5**.\n",
+ "\n",
+ "Balancing the data and creating a custom Random Forest model have not improved the performance. \n",
+ "\n",
+ "We have also built an **hybrid recommendation system** using the LightFm library. The AUC obtained with a basic hybrid model is 0.76.\n",
+ "The results can be improved provided:\n",
+ "- more computer power to perform the training.\n",
+ "- more data on users, for example demographic or behavior data.\n",
+ "- more data on songs, for example the genre or the category of a song. "
+ ]
+ }
+ ],
+ "metadata": {
+ "accelerator": "TPU",
+ "colab": {
+ "gpuType": "V28",
+ "machine_shape": "hm",
+ "provenance": []
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.13"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/public/notebooks/music-recommendation.qmd b/public/notebooks/music-recommendation.qmd
new file mode 100644
index 0000000..09d7526
--- /dev/null
+++ b/public/notebooks/music-recommendation.qmd
@@ -0,0 +1,2289 @@
+---
+format:
+ html:
+ title: ''
+ toc: true
+ toc-title: "Table of contents"
+ toc-location: right
+ toc-depth: 5
+ toc-expand: true
+ toc-float:
+ collapsed: true
+ smooth-scroll: true
+ width: 300px
+ theme: cosmo
+ code-fold: false
+ code-fold-show: true
+ page-layout: full
+ number-sections: true
+ code-tools: false
+ code-line-numbers: false
+ code-summary: "Show Code"
+ code-block-bg: true
+ include-in-header: styles/toc-scrollbar-hide.html
+execute:
+ enabled: false
+---
+
+
+## Initialization
+
+### **Importing Libraries and datasets**
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Mounting the drive
+from google.colab import drive
+drive.mount('/content/drive')
+```
+
+```{python}
+# Used to ignore the warning given as output of the code
+import warnings
+warnings.filterwarnings('ignore')
+
+# Basic libraries of python for numeric and dataframe computations
+import numpy as np
+import pandas as pd
+
+# Import Matplotlib the Basic library for data visualization
+import matplotlib.pyplot as plt
+
+# Import seaborn - Slightly advanced library for data visualization
+import seaborn as sns
+
+# Import the required library to compute the cosine similarity between two vectors
+from sklearn.metrics.pairwise import cosine_similarity
+
+# Import defaultdict from collections A dictionary output that does not raise a key error
+from collections import defaultdict
+
+# Impoort mean_squared_error : a performance metrics in sklearn
+from sklearn.metrics import mean_squared_error
+```
+
+### **Load the dataset**
+
+```{python}
+# Importing the datasets
+song_df = pd.read_csv('/content/drive/MyDrive/capstone/song_data.csv')
+count_df = pd.read_csv('/content/drive/MyDrive/capstone/count_data.csv',
+ usecols=['user_id','song_id','play_count'])
+```
+
+### **Understanding the data by viewing a few observations**
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 363}
+# Display first 10 records of count_df data
+count_df.head(10)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 363}
+# Display first 10 records of song_df data
+song_df.head(10)
+```
+
+### **Let us check the data types and and missing values of each column**
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Display info of count_df
+count_df.info(show_counts=True)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Display info of song_df
+song_df.info()
+```
+
+#### **The count data contains:**
+* **2000000 observations** and **3 columns**.
+* **user_id** and **sond_id** columns are of **object data type** but **play_count** column is of **numeric data type**.
+* There is **no missing value**.
+
+
+
+#### **The song data contains:**
+* **1000000 observations** and **5 columns**.
+* **All the columns** are of **object data type** except the year column. The **year column** is of **numeric data type**.
+* The **title** and **release** columns contains 1000000 - 999983 = **17 null values**. All the other columns do not contain null values.
+
+```{python}
+# Left merge count_df and song_df on "song_id". Drop duplicates from song_df data simultaneously
+df = pd.merge(count_df, song_df.drop_duplicates('song_id'), how='left')
+
+# Drop the column 'Unnamed: 0'
+ # Already droped while loading the dataset
+
+## Name the obtained dataframe as "df"
+ # Already named df
+```
+
+**Think About It:** As the user_id and song_id are encrypted. Can they be encoded to numeric features?
+ - **Yes**, in this case encoding categorical feature **increase the execution performance** of the models although it does not impact the qualiy of the predictions
+
+```{python}
+# Apply label encoding for "user_id" and "song_id"
+from sklearn.preprocessing import LabelEncoder
+
+#saving the original df in df_copy
+df_copy = df.copy()
+
+#storing transformers in a dictionnary for decoding if needed
+label_object = {}
+categorical_columns = ['user_id','song_id']
+for col in categorical_columns:
+ labelencoder = LabelEncoder()
+ labelencoder.fit(df[col])
+ df[col] = labelencoder.fit_transform(df[col])
+ label_object[col] = labelencoder
+```
+
+**Think About It:** As the data also contains users who have listened to very few songs and vice versa, is it required to filter the data so that it contains users who have listened to a good count of songs and vice versa?
+ - **Yes**, filtering the data will indeed **reduce the sparcity** in the matrices used for modelling. Hence **increasing the performance of the computation** as well as the **quality of the recommendations**
+
+A dataset of size 2000000 rows x 7 columns can be quite large and may require a lot of computing resources to process. This can lead to long processing times and can make it difficult to train and evaluate your model efficiently.
+In order to address this issue, it may be necessary to trim down your dataset to a more manageable size.
+
+```{python}
+# Get the column containing the users
+users = df.user_id
+
+# Create a dictionary that maps users(listeners) to the number of songs that they have listened to
+playing_count = dict()
+
+for user in users:
+ # If we already have the user, just add 1 to their playing count
+ if user in playing_count:
+ playing_count[user] += 1
+
+ # Otherwise, set their playing count to 1
+ else:
+ playing_count[user] = 1
+```
+
+```{python}
+# We want our users to have listened at least 90 songs
+SONG_COUNT_CUTOFF = 90
+
+# Create a list of users who need to be removed
+remove_users = []
+
+for user, num_songs in playing_count.items():
+
+ if num_songs < SONG_COUNT_CUTOFF:
+ remove_users.append(user)
+
+df = df.loc[ ~ df.user_id.isin(remove_users)]
+```
+
+```{python}
+# Get the column containing the songs
+songs = df.song_id
+
+# Create a dictionary that maps songs to its number of users(listeners)
+playing_count = dict()
+
+for song in songs:
+ # If we already have the song, just add 1 to their playing count
+ if song in playing_count:
+ playing_count[song] += 1
+
+ # Otherwise, set their playing count to 1
+ else:
+ playing_count[song] = 1
+```
+
+```{python}
+# We want our song to be listened by at least 120 users to be considred
+LISTENER_COUNT_CUTOFF = 120
+
+remove_songs = []
+
+for song, num_users in playing_count.items():
+ if num_users < LISTENER_COUNT_CUTOFF:
+ remove_songs.append(song)
+
+df_final = df.loc[ ~ df.song_id.isin(remove_songs)]
+```
+
+Out of all the songs available, songs with play_count less than or equal to 5 are in almost 90% abundance. So for building the recommendation system let us consider only those songs.
+
+```{python}
+# Keep only records of songs with play_count less than or equal to (<=) 5
+df_final = df_final[df_final.play_count<=5]
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Check the shape of the data
+df_final.shape
+```
+
+## **Exploratory Data Analysis**
+
+### **Let's check the total number of unique users, songs, artists in the data**
+
+Total number of unique user id
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Display total number of unique user_id
+df_final.user_id.nunique()
+```
+
+Total number of unique song id
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Display total number of unique song_id
+df_final.song_id.nunique()
+```
+
+Total number of unique artists
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Display total number of unique artists
+df_final.artist_name.nunique()
+```
+
+#### **The data contains :**
+
+* **117876** observations
+* **3155** unique users
+* **563** unique songs
+
+#### As per the number of unique users and songs, there is a possibility of 3155 * 563 = **1.776.265 interactions in the dataset**. But we only have **117.876 interactions** meeting our criterias(90 songs count per user, 120 users count per song and at most 5 play counts per song), i.e., **not every user has listened every song** in the dataset, which is quite understandable.This creates the **possibility of building a recommendation system to recommend products to the users which they have not interacted with**.
+
+### **Let's find out about the most interacted songs and interacted users**
+
+Most interacted songs
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Printing the top ten songs
+df_final.song_id.value_counts()[:10]
+```
+
+Most interacted users
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Printing the top ten users
+df_final.user_id.value_counts()[:10]
+```
+
+#### **The top user and song are the user with id 2548 and the song with id 8581**
+
+* The **top user** listened to **243 songs**
+ * Since there are **563 songs**, it is **possible to make recommations** to this particular user and to all users in general. For this top user, there are 563 - 243 = **320 possible recommendations left**.
+* The **most popular song** was listened to by **751 users**
+ * Since there are **3155 users**, there are 3155 - 751 = **2404 possible users left to whom we could recommend this song**.
+
+Songs released on yearly basis
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Find out the number of songs released in a year, use the songs_df
+ # Hint: Use groupby function on the 'year' column
+song_df.drop_duplicates('song_id').groupby('year').song_id.count().sort_values(ascending=False)[:10]
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 480}
+# Create a barplot plot with y label as "number of titles played" and x -axis year
+nbr_titles_played_by_year = df_final.groupby('year').title.nunique()
+
+plt.style.use('ggplot')
+
+# Set the figure size
+plt.figure(figsize=(7, 5))
+#from the previous cell, we could see there is an abnormal year 0, which seems to be
+#a default, so we're dropping that year
+plt.bar(x=nbr_titles_played_by_year.index[1:], height=nbr_titles_played_by_year[1:])
+
+# Set the x label of the plot
+plt.xlabel('year')
+
+# Set the y label of the plot
+plt.ylabel('number of titles played')
+
+# Setting all the ticks
+plt.xticks(ticks=nbr_titles_played_by_year.index[1:], rotation=90, size=7)
+
+# Show the plot
+plt.show()
+```
+
+#### **The number of titles played can be segmented into 3 groups.** The groups are created below by dividing the year colonne into 3 segments:
+
+* **[1969 - 1983]** : in this segment, the **number of titles played is very low, less or equal to 2**.
+* **[1986 - 1997]** : in this segment, the **number of titles played is less or equal to 7**, 1991 having 7.
+* **[1999 - 2010]** : in this segment, the **number of titles played vary beetween 8 and 79**, 1999 having the lowest and 2007 having the maximum.
+
+
+**Think About It:** What other insights can be drawn using exploratory data analysis?
+ - We can also **visualize the distribution of the play counts:**
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 471}
+data = df_final
+sns.countplot(data=data, x=data.play_count, hue='play_count', palette='dark',
+ legend=False, stat='proportion')
+```
+
+From the above figure, we can see:
+ - **most of the songs, 60 percent** have been played only once and
+ - **80 percent of the songs** have been **played less than 3 times**.
+
+Now that we have explored the data, let's apply different algorithms to build recommendation systems.
+
+**Note:** Use the shorter version of the data, i.e., the data after the cutoffs as used in Milestone 1.
+
+## Building various models
+
+### **Popularity-Based Recommendation Systems**
+
+Let's take the count and sum of play counts of the songs and build the popularity recommendation systems based on the sum of play counts.
+
+```{python}
+# Calculating average play_count
+ # Hint: Use groupby function on the song_id column
+play_avg = df_final.groupby('song_id').play_count.mean()
+
+# Calculating the frequency a song is played
+ # Hint: Use groupby function on the song_id column
+play_freq = 100 * df_final.groupby('song_id').play_count.sum()
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 237}
+# Making a dataframe with the average_count and play_freq
+df_songs_count_freq = pd.DataFrame({'play_avg':play_avg, 'play_freq':play_freq})
+
+# Let us see the first five records of the final_play dataset
+df_songs_count_freq[:5]
+```
+
+Now, let's create a function to find the top n songs for a recommendation based on the average play count of song. We can also add a threshold for a minimum number of playcounts for a song to be considered for recommendation.
+
+```{python}
+# Build the function to find top n songs
+def top_n_songs(df, n, min_playcount = 50):
+ """
+ It gives top n songs among those being played for more than min_playcount
+ """
+ # Finding products with minimum number of interactions
+ recommendations = df[df.play_freq > min_playcount]
+
+ # Sorting values with respect to average rating
+ recommendations = recommendations.sort_values(by='play_avg', ascending=False)
+
+ return list(recommendations.index[:n])
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Recommend top 10 songs using the function defined above
+top_n_songs(df_songs_count_freq, 10)
+```
+
+### **User User Similarity-Based Collaborative Filtering**
+
+To build the user-user-similarity-based and subsequent models we will use the "surprise" library.
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Install the surprise package using pip. Uncomment and run the below code to do the same
+!pip install surprise
+```
+
+```{python}
+# Import necessary libraries
+from surprise import similarities
+from sklearn.metrics.pairwise import cosine_similarity
+
+# To compute the accuracy of models
+from surprise import accuracy
+
+# This class is used to parse a file containing play_counts, data should be in structure - user; item; play_count
+from surprise.reader import Reader
+
+
+# Class for loading datasets
+from surprise.dataset import Dataset
+
+
+# For tuning model hyperparameters
+from surprise.model_selection import GridSearchCV
+
+
+# For splitting the data in train and test dataset
+from surprise.model_selection import train_test_split
+
+
+# For implementing similarity-based recommendation system
+from surprise.prediction_algorithms.knns import KNNBasic
+
+
+# For implementing matrix factorization based recommendation system
+from surprise.prediction_algorithms.matrix_factorization import SVD
+
+
+# For implementing KFold cross-validation
+from surprise.model_selection import KFold
+
+
+# For implementing clustering-based recommendation system
+from surprise import CoClustering
+```
+
+### Some useful functions
+
+Below is the function to calculate precision@k and recall@k, RMSE, and F1_Score@k to evaluate the model performance.
+
+**Think About It:** Which metric should be used for this problem to compare different models?
+
+```{python}
+def precision_recall_at_k(model, k=30, threshold=1.5, testset=None):
+ """Return precision and recall at k metrics for each user"""
+
+ # First map the predictions to each user.
+ user_est_true = defaultdict(list)
+
+ #Making predictions on the test data
+ predictions = model.test(testset)
+
+ for uid, _, true_r, est, _ in predictions:
+ user_est_true[uid].append((est, true_r))
+
+ precisions = dict()
+ recalls = dict()
+
+ for uid, playing_count in user_est_true.items():
+
+ # Sort play count by estimated value
+ playing_count.sort(key=lambda x: x[0], reverse=True)
+
+ # Number of relevant items
+ n_rel = sum((true_r >= threshold) for (_, true_r) in playing_count)
+
+ # Number of recommended items in top k
+ n_rec_k = sum((est >= threshold) for (est, _) in playing_count[:k])
+
+ # Number of relevant and recommended items in top k
+ n_rel_and_rec_k = sum(((true_r >= threshold) and (est >= threshold))
+ for (est, true_r) in playing_count[:k])
+
+ # Precision@K: Proportion of recommended items that are relevant
+ # When n_rec_k is 0, Precision is undefined. We here set Precision to 0 when n_rec_k is 0.
+
+ precisions[uid] = n_rel_and_rec_k / n_rec_k if n_rec_k != 0 else 0
+
+ # Recall@K: Proportion of relevant items that are recommended
+ # When n_rel is 0, Recall is undefined. We here set Recall to 0 when n_rel is 0.
+
+ recalls[uid] = n_rel_and_rec_k / n_rel if n_rel != 0 else 0
+
+ #Mean of all the predicted precisions are calculated.
+ precision = round((sum(prec for prec in precisions.values()) / len(precisions)),3)
+ #Mean of all the predicted recalls are calculated.
+ recall = round((sum(rec for rec in recalls.values()) / len(recalls)),3)
+
+ accuracy.rmse(predictions)
+ print('Precision: ', precision) #Command to print the overall precision
+ print('Recall: ', recall) #Command to print the overall recall
+ print('F_1 score: ', round((2*precision*recall)/(precision+recall),3)) # Formula to compute the F-1 score.
+```
+
+**Think About It:** In the function precision_recall_at_k above the threshold value used is 1.5. How precision and recall are affected by changing the threshold? What is the intuition behind using the threshold value of 1.5?
+- Impact of the threshold on recall and precision
+ - If we decrease the threshold, most of the predictions would be above the threshold, hence many of the items would be recommended. As the data is imbalanced (many low playcounts), The TP rate would increase and Precision and Recall would increase.
+ - If we **increase the threshold**, most of the prediction would be below the threshold, hence few songs would be recommended. As the data is imbalanced (very few high playcounts), the TP rate would decrease. **So Precision and Recall would decrease**.
+ - So a good threshold is a medium threshold but in the present case most of the predictions are around 1, so 1.5 is a good threshold. Moreover 1 playcount is not enough to say a user like a song. The higher the the threshold, the higher the likely hood the user would love the song.
+
+- Evaluation parameters
+ - To compute **precision and recall**, a **threshold of 1.5 and k value of 30 will be considered for the recommended and relevant ratings**.
+
+- Performance metrice selection
+ - FP and FN costs are high in the present case. If the FP rate is high (precision is low), we recommend products that the user might not buy. And, if FN rate is high (recall is low) we fail to recommend relevant products that the user might purchase. Therefore, precision and recall need to be optimized. So the **correct performance measure is the F_1 score**.
+
+Below we are loading the **dataset**, which is a **pandas dataframe**, into a **different format called `surprise.dataset.DatasetAutoFolds`** which is required by this library. To do this we will be **using the classes `Reader` and `Dataset`**
+
+You will also notice here that we read the dataset by providing a scale of ratings. However, as you would know, we do not have ratings data of the songs. In this case, we are going to use play_count as a proxy for ratings with the assumption that the more the user listens to a song, the higher the chance that they like the song
+
+```{python}
+# Instantiating Reader scale with expected rating scale
+ #use rating scale (0, 5)
+reader = Reader(rating_scale=(0, 5))
+
+# Loading the dataset
+ # Take only "user_id","song_id", and "play_count"
+data = Dataset.load_from_df(df_final[['user_id', 'song_id', 'play_count']],
+ reader=reader)
+
+# Splitting the data into train and test dataset
+ # Take test_size = 0.4, random_state = 42
+trainset, testset = train_test_split(data, test_size=0.4, random_state=42)
+```
+
+**Think About It:** How changing the test size would change the results and outputs?
+
+As the data is imbalanced :
+ - If we increase the test size, the performance of the model increase, as more diversity will appear in the testset
+ - And conversely, if we decrease, the test size, the performance decrease.
+
+
+```{python}
+#| scrolled: false
+#| colab: {base_uri: https://localhost:8080/}
+# Build the default user-user-similarity model
+sim_options=dict(name='cosine', user_based=True)
+
+# KNN algorithm is used to find desired similar items
+ # Use random_state = 1
+sim_user_user_model = KNNBasic(sim_options=sim_options, random_state=1,
+ verbose=True)
+
+# Train the algorithm on the trainset, and predict play_count for the testset
+sim_user_user_model.fit(trainset)
+
+# Let us compute precision@k, recall@k, and f_1 score with k = 30
+ # Use sim_user_user model
+precision_recall_at_k(sim_user_user_model, testset=testset)
+```
+
+The F_1 score is not good, this is likely due to the imbalanced data. We need more observations.
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# selecting a sample user with a listened song, let's choose the first observation
+user_id_interaction, song_id_interaction, play_count_interaction = df_final[['user_id'
+, 'song_id', 'play_count']].iloc[0].values
+print('user_id chosen is:', user_id_interaction,
+ '\nsong_id chosen listened by the user is:', song_id_interaction,
+ '\nand the play count of this pair is:', play_count_interaction)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Predicting play_count for a sample user with a listened song
+# Use any user id and song_id
+sim_user_user_model.predict(user_id_interaction, song_id_interaction, r_ui=1)
+```
+
+```{python}
+# Function to find the list of users who have not listened to the song song_id.
+def n_users_not_listened_song(n, data, song_id):
+ users_listened_song = set(data[data['song_id'] == song_id]['user_id'])
+ all_users = set(data['user_id'])
+ # n is the number of elements to get in the list
+ return list(all_users.difference(users_listened_song))[:n]
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Randomly choosing a song_id
+song_id_no_interaction = df_final.song_id.values[10]
+
+# Finding a user who has not listened to the song
+user_id_no_interaction = n_users_not_listened_song(1, df_final,
+ song_id_no_interaction)[0]
+
+print('user_id chosen is:', user_id_no_interaction,
+ '\nsong_id chosen and not listened by the user is:', song_id_no_interaction)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Predicting play_count for a sample user with a song not-listened by the user
+ #predict play_count for any sample user
+sim_user_user_model.predict(user_id_no_interaction, song_id_no_interaction)
+```
+
+**The predicted play count** for the **interacted user-song pair is 1.2**. The **actual play count is 1**, so the prediction is pretty good.
+
+**The predicted play count** the for the **user-song pair with no interaciton is 1.74**
+
+Now, let's try to tune the model and see if we can improve the model performance.
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Setting up parameter grid to tune the hyperparameters
+params_grid = dict(k=[60, 80], min_k=[40, 50],
+ sim_options=dict(name=['msd', 'cosine'],
+ #min_support = [5, 10, 20],
+ user_based=[True]))
+
+# Performing 3-fold cross-validation to tune the hyperparameters
+gs = GridSearchCV(KNNBasic, param_grid=params_grid, cv=3, measures=['rmse'],
+ n_jobs=-1)
+
+# Fitting the data
+ # Use entire data for GridSearch
+gs.fit(data)
+
+# Best RMSE score
+print("Best RMSE score:", gs.best_score['rmse'])
+
+# Combination of parameters that gave the best RMSE score
+print("Best_params:", gs.best_params['rmse'])
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Train the best model found in above gridsearch
+# Using the optimal similarity measure for user-user based collaborative filtering
+best_params = gs.best_params['rmse']
+best_k = best_params['k']
+best_min_k = best_params['min_k']
+best_sim_options = best_params['sim_options']
+
+# Creating an instance of KNNBasic with optimal hyperparameter values
+sim_user_user_tuned_model = KNNBasic(k = best_k, min_k=best_min_k,
+ sim_options=best_sim_options,
+ random_state=1, verbose=False)
+
+# Training the algorithm on the trainset
+sim_user_user_tuned_model.fit(trainset)
+
+# Let us compute precision@k and recall@k also
+precision_recall_at_k(sim_user_user_tuned_model, testset=testset)
+```
+
+The F1 score of this tuned model is 0.513. So it has silightly increased compared to the baseline model which was 0.504.
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Predict the play count for a user who has listened to the song. Take user_id 6958, song_id 1671 and r_ui = 2
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+sim_user_user_tuned_model.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+```
+
+```{python}
+# Function to find the list of songs who have not been listened by user_id.
+def n_songs_not_listened_user(n, data, user_id):
+ songs_listened_user = set(data[data['user_id'] == user_id]['song_id'])
+ all_songs = set(data['song_id'])
+ # n is the number of elements to get in the list
+ return list(all_songs.difference(songs_listened_user))[:n]
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+user_id_no_interaction = 6958
+
+# Finding a song who has not been listened by the user above
+song_id_no_interaction = n_songs_not_listened_user(1, df_final, user_id_no_interaction)[0]
+
+print('user_id chosen is:', user_id_no_interaction,
+ '\nsong_id chosen and not listened by the user is:', song_id_no_interaction)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 53}
+# Verifying that the user has not listened to the song
+df_final.query('user_id == @user_id_no_interaction & song_id == @song_id_no_interaction')
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Predict the play count for a song that is not listened to by the user (with user_id 6958)
+sim_user_user_tuned_model.predict(user_id_no_interaction, song_id_no_interaction)
+```
+
+**The predicted play count** for the **interacted user-song pair is 1.49**. The **actual play count is 2**, so the prediction is far from the actual.
+
+**The predicted play count** the for the **user-song pair with no interaciton is 1.53**
+
+**Think About It:** Along with making predictions on listened and unknown songs can we get 5 nearest neighbors (most similar) to a certain song?
+ - Yes, we can get the list of the songs similar to a song
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Use inner id 0
+sim_user_user_tuned_model.get_neighbors(iid=0, k=5)
+```
+
+Below we will be implementing a function where the input parameters are:
+
+- data: A **song** dataset
+- user_id: A user-id **against which we want the recommendations**
+- top_n: The **number of songs we want to recommend**
+- algo: The algorithm we want to use **for predicting the play_count**
+- The output of the function is a **set of top_n items** recommended for the given user_id based on the given algorithm
+
+```{python}
+def get_recommendations(data, user_id, top_n, algo):
+
+ # Creating an empty list to store the recommended song ids
+ recommendations = []
+
+ # Creating an user item interactions matrix
+ user_item_interactions_matrix = data.pivot(index = 'user_id',
+ columns = 'song_id',
+ values = 'play_count')
+
+ user_songs = user_item_interactions_matrix.loc[user_id]
+ # Extracting those song ids which the user_id has not played yet
+ non_played_songs = user_songs[user_songs.isnull()].index.tolist()
+
+ # Looping through each of the song ids which user_id has not interacted yet
+ for song_id in non_played_songs:
+
+ # Predicting the users for those non played song ids by this user
+ est = algo.predict(user_id, song_id).est
+
+ # Appending the predicted play_counts
+ recommendations.append((song_id, est))
+
+ # Sorting the predicted play_counts in descending order
+ recommendations.sort(key = lambda x: x[1], reverse = True)
+
+ # Returing top n highest predicted play_count songs for this user
+ return recommendations[:top_n]
+```
+
+```{python}
+# Make top 5 recommendations for any user_id with a similarity-based recommendation engine
+recommendations = get_recommendations(df_final, user_id_interaction, 5,
+ sim_user_user_tuned_model)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 206}
+# Building the dataframe for above recommendations with columns "song_id" and "predicted_play_count"
+recommendations_df = pd.DataFrame(recommendations, columns=['song_id', 'predicted_play_count'])
+recommendations_df
+```
+
+**Above we can see the list of the songs recommended by the user-user colaborative filtering model**.
+ - the top recommendation is the song 7224 with 2.93 play_counts.
+ - the fith recommendation is the song 4831 with 2.34 playcounts.
+ - all the recommended songs have a predicted playcounts greater than 2.
+
+### Correcting the play_counts and Ranking the above songs
+
+```{python}
+def ranking_songs(recommendations, playing_count):
+ # Sort the songs based on play counts
+ ranked_songs = playing_count.loc[[items[0] for items in recommendations]].sort_values('play_freq', ascending = False)[['play_freq']].reset_index()
+
+ # Merge with the recommended songs to get predicted play_counts
+ ranked_songs = ranked_songs.merge(pd.DataFrame(recommendations, columns = ['song_id', 'predicted_play_count']), on = 'song_id', how = 'inner')
+
+ # Rank the songs based on corrected play_counts
+ ranked_songs['corrected_play_count'] = ranked_songs['predicted_play_count'] - 1 / np.sqrt(ranked_songs['play_freq'])
+
+ # Sort the songs based on corrected play_counts
+ ranked_songs = ranked_songs.sort_values('corrected_play_count', ascending = False)
+
+ return ranked_songs
+```
+
+**Think About It:** In the above function to correct the predicted play_count a quantity 1/np.sqrt(n) is subtracted. What is the intuition behind it? Is it also possible to add this quantity instead of subtracting?
+ - we can correct the predictions of the songs by using the total play_count of the song. The intuition behind is the higher the playcount the higher the likely hood the song will be liked. So we can add the quantity 1/np.sqrt(n) to have optimistic predictions or in contrario we can substract that quantity to have play count below 5.
+ - So the confidence interval of the predictions could be seen as : predicted_prediction - 1/np.sqrt(n), predicted_prediction + 1/np.sqrt(n)
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 206}
+# Applying the ranking_songs function on the final_play data
+ranking_songs(recommendations, df_songs_count_freq)
+```
+
+**As expected, the corrected play counts are slightly lower than the predicted ones**
+
+### Item Item Similarity-based collaborative filtering recommendation systems
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+#| scrolled: false
+# Apply the item-item similarity collaborative filtering model with random_state = 1 and evaluate the model performance
+# Declaring the similarity options
+similarity_options = dict(name='cosine', user_based=False)
+
+# KNN algorithm is used to find desired similar items. Use random_state=1
+sim_item_item_model = KNNBasic(sim_options=similarity_options,
+ random_state=1, verbose=False)
+
+# Train the algorithm on the trainset, and predict ratings for the test set
+sim_item_item_model.fit(trainset)
+
+# Let us compute precision@k, recall@k, and f_1 score
+precision_recall_at_k(sim_item_item_model, testset=testset)
+```
+
+**The F1 score of this basic item-item colaborative filtering model is 0.39**
+- This is not a good score and is less than the F1 score of this basic user-user colaborative filtering model
+- So for this case, considering similar user seems to be better than considering similar items when building the model.
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Predicting play count for a sample user_id 6958 and song (with song_id 1671) listened to by the user
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+sim_item_item_model.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+song_id_no_interaction = 1671
+
+# Finding a user who has not listened to the song
+user_id_no_interaction = n_users_not_listened_song(1, df_final,
+ song_id_no_interaction)[0]
+
+print('user_id chosen is:', user_id_no_interaction,
+ '\nsong_id chosen and not listened by the user is:', song_id_no_interaction)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Predict the play count for a user that has not listened to the song (with song_id 1671)
+sim_item_item_model.predict(user_id_no_interaction, song_id_no_interaction)
+```
+
+**The predicted play count** for the **interacted user-song pair is 1.36**. The **actual play count is 2**, so the prediction is far from the actual. The number of neighbors available was **20**.
+
+**The predicted play counts** the for the **user-song pair with no interaciton is 1.64.**
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Apply grid search for enhancing model performance
+# Setting up parameter grid to tune the hyperparameters
+params_grid_item = dict(k=[30, 50], min_k=[3, 6, 9],
+ sim_options=dict(name=['cosine', 'msd'],
+ min_support = [9, 20],
+ user_based=[False]))
+
+# Performing 3-fold cross-validation to tune the hyperparameters
+gs = GridSearchCV(KNNBasic, param_grid=params_grid_item, cv=3,
+ measures=['rmse'], n_jobs=-1)
+
+# Fitting the data
+gs.fit(data)
+
+# Find the best RMSE score
+print("Best RMSE score:", gs.best_score['rmse'])
+
+# Extract the combination of parameters that gave the best RMSE score
+print("best_params:", gs.best_params['rmse'])
+```
+
+**Think About It:** How do the parameters affect the performance of the model? Can we improve the performance of the model further? Check the list of hyperparameters [here](https://surprise.readthedocs.io/en/stable/knn_inspired.html).
+ - Choosing the right set of parameters is critical to some algorithms.
+ - In this case, the similarity options and the number of neighbors enhance the performance of the models
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Apply the best model found in the grid search
+# Using the optimal similarity measure for item-item based collaborative filtering
+best_params_item = gs.best_params['rmse']
+best_k_item = best_params_item['k']
+best_min_k_item = best_params_item['min_k']
+best_sim_options_item = best_params_item['sim_options']
+
+# Creating an instance of KNNBasic with optimal hyperparameter values
+sim_item_item_tuned_model = KNNBasic(k = best_k_item, min_k=best_min_k_item,
+ sim_options=best_sim_options_item, randm_state=1,
+ verbose=False)
+
+# Training the algorithm on the trainset
+sim_item_item_tuned_model.fit(trainset)
+
+# Let us compute precision@k and recall@k, f1_score and RMSE
+precision_recall_at_k(sim_item_item_tuned_model, testset=testset)
+```
+
+**The performance has increased compared to the baseline item-item model:**
+ - The F1 score of this tuned model is 0.49 while the the F1 score of the baseline item-item model was 0.39. So the perfomance has increased by 0.1 points.
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Predict the play_count by a user(user_id 6958) for the song (song_id 1671)
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+sim_item_item_tuned_model.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Predicting play count for a sample user_id 6958 with song_id 3232 which is not listened to by the user
+user_id_no_interaction, song_id_no_interaction = (6958, 3232)
+sim_item_item_tuned_model.predict(user_id_no_interaction, song_id_no_interaction)
+```
+
+**The predicted play count** for the **interacted user-song pair is 1.45**. The **actual play count is 2**, so the prediction is far from the actual. The number of neighbors available was **15**.
+
+**The predicted play count** the for the **user-song pair with no interaciton is 1.69.**
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Find five most similar items to the item with inner id 0
+sim_item_item_tuned_model.get_neighbors(iid=0, k=5)
+```
+
+```{python}
+# Making top 5 recommendations for any user_id with item_item_similarity-based recommendation engine
+recommendations = get_recommendations(df_final, user_id_interaction, 5,
+ sim_item_item_tuned_model)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 206}
+# Building the dataframe for above recommendations with columns "song_id" and "predicted_play_count"
+recommendations_df = pd.DataFrame(recommendations, columns=['song_id', 'predicted_play_count'])
+recommendations_df
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 206}
+# Applying the ranking_songs function
+ranking_songs(recommendations, df_songs_count_freq)
+```
+
+**Above we can see the list of the songs recommended by the user-user colaborative filtering model**.
+ - The songs recommended by this model vary from the ones recommended by the user-user model
+ - The predicted play counts vary beetwen 1.96 and 1.77
+
+### Model Based Collaborative Filtering - Matrix Factorization
+
+Model-based Collaborative Filtering is a **personalized recommendation system**, the recommendations are based on the past behavior of the user and it is not dependent on any additional information. We use **latent features** to find recommendations for each user.
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Build baseline model using svd
+svd = SVD(random_state=1)
+
+# Training the algorithm on the trainset
+svd.fit(trainset)
+
+# Use the function precision_recall_at_k to compute precision@k, recall@k, F1-Score, and RMSE
+precision_recall_at_k(svd, testset=testset)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Making prediction for user (with user_id 6958) to song (with song_id 1671), take r_ui = 2
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+svd.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Making a prediction for the user who has not listened to the song (song_id 3232)
+user_id_no_interaction, song_id_no_interaction = (6958, 3232)
+svd.predict(user_id_no_interaction, song_id_no_interaction)
+```
+
+#### Improving matrix factorization based recommendation system by tuning its hyperparameters
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Set the parameter space to tune
+params = dict(n_epochs=[45, 60], lr_all=[0.0001, 0.005], reg_all=[0.05, 0.1],
+ reg_bu=[0.05, 0.1])
+
+# Performe 3-fold grid-search cross-validation
+gs = GridSearchCV(SVD, param_grid=params, n_jobs=-1, measures=['rmse'], cv=3)
+
+# Fitting data
+gs.fit(data)
+
+# Best RMSE score
+print(gs.best_score['rmse'])
+
+# Combination of parameters that gave the best RMSE score
+print(gs.best_params['rmse'])
+```
+
+**Think About It**: How do the parameters affect the performance of the model? Can we improve the performance of the model further? Check the available hyperparameters [here](https://surprise.readthedocs.io/en/stable/matrix_factorization.html).
+ - This svd algorithm has a lot of tunnable parameters.
+ - Tweeking theses parameters might improve the predictions.
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Building the optimized SVD model using optimal hyperparameters
+best_params_svd = gs.best_params['rmse']
+best_n_epochs = best_params_svd['n_epochs']
+best_lr = best_params_svd['lr_all']
+best_reg = best_params_svd['reg_all']
+best_reg_bu = best_params_svd['reg_bu']
+
+svd_tuned = SVD(n_epochs=best_n_epochs, lr_all=best_lr, reg_all=best_reg,
+ reg_bu=best_reg_bu, random_state=1)
+
+# Train the algorithm on the trainset
+svd_tuned.fit(trainset)
+
+# Use the function precision_recall_at_k to compute precision@k, recall@k, F1-Score, and RMSE
+precision_recall_at_k(svd_tuned, testset=testset)
+```
+
+The performance of this tuned model has not increased compared to the baseline svd model
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Using svd_algo_optimized model to recommend for userId 6958 and song_id 1671
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+svd_tuned.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Using svd_algo_optimized model to recommend for userId 6958 and song_id 3232 with unknown baseline play_count
+user_id_no_interaction, song_id_no_interaction = (6958, 3232)
+svd_tuned.predict(user_id_no_interaction, song_id_no_interaction)
+```
+
+**The predicted play count** for the **interacted user-song pair is 1.48**. The **actual play count is 2**, so the prediction is far from the actual.
+
+**The predicted play count** the for the **user-song pair with no interaction is 1.54.**
+
+```{python}
+# Getting top 5 recommendations for user_id 6958 using "svd_optimized" algorithm
+recommendations = get_recommendations(df_final, user_id_interaction, 5,
+ sim_item_item_tuned_model)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 206}
+# Ranking songs based on above recommendations
+recommendations_df = pd.DataFrame(recommendations, columns=['song_id', 'predicted_play_count'])
+recommendations_df
+```
+
+**Above we can see the list of the songs recommended by the matrix factorization colaborative filtering model**.
+ - The songs recommended by this model are the ones recommended by the item-item model
+ - The predicted play counts vary beetwen 1.96 and 1.77
+
+### Cluster Based Recommendation System
+
+In **clustering-based recommendation systems**, we explore the **similarities and differences** in people's tastes in songs based on how they rate different songs. We cluster similar users together and recommend songs to a user based on play_counts from other users in the same cluster.
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Make baseline clustering model
+clustering_model = CoClustering(random_state=1)
+
+# Train the algorithm on the trainset
+clustering_model.fit(trainset)
+
+# Use the function precision_recall_at_k to compute precision@k, recall@k, F1-Score, and RMSE
+precision_recall_at_k(clustering_model, testset=testset)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Making prediction for user_id 6958 and song_id 1671
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+clustering_model.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Making prediction for user (userid 6958) for a song(song_id 3232) not listened to by the user
+user_id_no_interaction, song_id_no_interaction = (6958, 3232)
+clustering_model.predict(user_id_no_interaction, song_id_no_interaction)
+```
+
+#### Improving clustering-based recommendation system by tuning its hyper-parameters
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Set the parameter space to tune
+param_grid = {'n_cltr_u': [1, 3, 10], 'n_cltr_i': [1, 3, 10 ],
+ 'n_epochs': [10, 30, 40]}
+
+# Performing 3-fold grid search cross-validation
+gs = GridSearchCV(CoClustering, param_grid, measures = ['rmse'], cv = 3, n_jobs = -1)
+
+# Fitting data
+gs.fit(data)
+
+# Best RMSE score
+print(gs.best_score['rmse'])
+
+# Combination of parameters that gave the best RMSE score
+print(gs.best_params['rmse'])
+```
+
+**Think About It**: How do the parameters affect the performance of the model? Can we improve the performance of the model further? Check the available hyperparameters [here](https://surprise.readthedocs.io/en/stable/co_clustering.html).
+ - The coclustering algorithm has some tunnable parameters.
+ - Tweeking theses parameters might improve the predictions.
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Train the tuned Coclustering algorithm
+best_params_clust = gs.best_params['rmse']
+best_n_cltr_u = best_params_clust['n_cltr_u']
+best_n_cltr_i = best_params_clust['n_cltr_i']
+best_n_epochs = best_params_clust['n_epochs']
+
+clustering_model_tuned = CoClustering(n_cltr_u=best_n_cltr_u,
+ n_cltr_i=best_n_cltr_i,
+ n_epochs=best_n_epochs,
+ random_state=1)
+
+# Train the algorithm on the trainset
+clustering_model_tuned.fit(trainset)
+
+# Use the function precision_recall_at_k to compute precision@k, recall@k, F1-Score, and RMSE
+precision_recall_at_k(clustering_model_tuned, testset=testset)
+```
+
+The performance of this tuned model has not increased compared to the baseline coclustering model.
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Using co_clustering_optimized model to recommend for userId 6958 and song_id 1671
+user_id_interaction, song_id_interaction, play_count_interaction = 6958, 1671, 2
+clustering_model_tuned.predict(user_id_interaction, song_id_interaction,
+ r_ui = play_count_interaction)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Use Co_clustering based optimized model to recommend for userId 6958 and song_id 3232 with unknown baseline play_count
+user_id_no_interaction, song_id_no_interaction = (6958, 3232)
+clustering_model_tuned.predict(user_id_no_interaction, song_id_no_interaction)
+```
+
+**The predicted play count** for the **interacted user-song pair is 1.12**. The **actual play count is 2**, so the prediction is far from the actual.
+
+**The predicted play count** the for the **user-song pair with no interaction is 1.31.**
+
+#### Implementing the recommendation algorithm based on optimized CoClustering model
+
+```{python}
+# Getting top 5 recommendations for user_id 6958 using "Co-clustering based optimized" algorithm
+recommendations = get_recommendations(df_final, user_id_interaction, 5,
+ clustering_model_tuned)
+```
+
+### Correcting the play_count and Ranking the above songs
+
+```{python}
+#| scrolled: true
+#| colab: {base_uri: https://localhost:8080/, height: 206}
+# Ranking songs based on the above recommendations
+ranking_songs(recommendations, df_songs_count_freq)
+```
+
+**Above we can see the list of the songs recommended by the svd model**.
+ - the top recommendation is the song 7224 with 2.93 play_counts.
+ - the fifth recommendation is the song 4831 with 2.34 playcounts.
+ - all the recommended songs have a predicted playcounts greater than 2.
+ - the songs are the same recommended by the user-user colaborative filtering model.
+
+### Content Based Recommendation Systems
+
+**Think About It:** So far we have only used the play_count of songs to find recommendations but we have other information/features on songs as well. Can we take those song features into account?
+ - Yes we can recommend songs which features are similar to a target song
+
+```{python}
+# Concatenate the "title", "release", "artist_name" columns to create a different column named "text"
+df_final['text'] = df_final['title'] + ' ' + df_final['release'] + ' ' + df_final['artist_name']
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 237}
+# Selecting a random subset of the original data
+df_small = df_final.sample(n = 100000, random_state = 42)
+
+# Select the columns 'user_id', 'song_id', 'play_count', 'title', 'text' from df_small data
+df_small = df_small[['user_id', 'song_id', 'play_count', 'title', 'text']]
+
+# Drop the duplicates from the title column
+df_small = df_small.drop_duplicates('title')
+
+# Set the title column as the index
+df_small.set_index('title', inplace=True)
+
+# See the first 5 records of the df_small dataset
+df_small.head(5)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Create the series of indices from the data
+indices = pd.Series(df_small.index)
+
+# Let us see the first 5 indices
+indices[:5]
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Importing necessary packages to work with text data
+import nltk
+
+# Download punkt library
+nltk.download("punkt")
+
+# Download stopwords library
+nltk.download('stopwords')
+
+# Download wordnet
+nltk.download('wordnet')
+
+# Import regular expression
+import re
+
+# Import word_tokenizer
+from nltk import word_tokenize
+
+# Import WordNetLemmatizer
+from nltk.stem import WordNetLemmatizer
+
+# Import stopwords
+from nltk.corpus import stopwords
+
+# Import CountVectorizer and TfidfVectorizer
+from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
+```
+
+We will create a **function to pre-process the text data:**
+
+```{python}
+# Create a function to tokenize the text
+def tokenize(text):
+
+ # Making each letter as lowercase and removing non-alphabetical text
+ text = re.sub(r"[^a-zA-Z]", " ", text.lower())
+
+ # Extracting each word in the text
+ tokens = word_tokenize(text)
+
+ # Removing stopwords
+ words = [word for word in tokens if word not in stopwords.words("english")]
+
+ # Lemmatize the words
+ text_lems = [WordNetLemmatizer().lemmatize(lem).strip() for lem in words]
+
+ return text_lems
+```
+
+```{python}
+# Create tfidf vectorizer
+tfidf = TfidfVectorizer(tokenizer = tokenize)
+
+# Fit_transfrom the above vectorizer on the text column and then convert the output into an array
+songs_tfidf = tfidf.fit_transform(df_small['text'].values).toarray()
+```
+
+```{python}
+# Compute the cosine similarity for the tfidf above output
+similar_songs = cosine_similarity(songs_tfidf, songs_tfidf)
+```
+
+ Finally, let's create a function to find most similar songs to recommend for a given song.
+
+```{python}
+# Function that takes in song title as input and returns the top 10 recommended songs
+def recommendations(title, similar_songs, indices = indices, data = df_small):
+
+ recommended_songs = []
+
+ # Getting the index of the song that matches the title
+ idx = indices[indices == title].index[0]
+
+ # Creating a Series with the similarity scores in descending order
+ score_series = pd.Series(similar_songs[idx]).sort_values(ascending = False)
+
+ # Getting the indexes of the 10 most similar songs
+ top_10_indexes = list(score_series.iloc[1:11].index)
+
+ # Populating the list with the titles of the best 10 matching songs
+ for i in top_10_indexes:
+ recommended_songs.append(list(data.index)[i])
+
+ return recommended_songs
+```
+
+Recommending 10 songs similar to Learn to Fly
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Make the recommendation for the song with title 'Learn To Fly'
+recommendations("Learn To Fly", similar_songs, indices, df_small)
+```
+
+**The list above contains the songs predicted similar to the song Learn To Fly**
+ - To verify that the recommendation is working, we can check if the "title", "release" and "artist_name" of the predicted songs are similar to the ones of the target song. They seems to be different.
+ - We need more compelling features for this task, the genre of the songs or some tags describing each songs ...
+
+## **Conclusion and Recommendations**
+
+**1. Comparison of various techniques and their relative performance based on chosen Metric (Measure of success)**:
+- How do different techniques perform? Which one is performing relatively better? Is there scope to improve the performance further?
+
+- The F1 scores of the models built so far are:
+ - user-user colaborative filtering baseline model: 0.504
+ - user-user colaborative filtering tuned model: **0.513**
+ - item-item colaborative filtering baseline model: 0.397
+ - item-item colaborative filtering tuned model: 0.483
+ - matrix factorization baseline model: 0.498
+ - matrix factorization tuned model: 0.504
+ - coclustering baseline model: 0.472
+ - coclustering tuned model: 0.472
+ - content based model: no score computed
+ - popularity based model : no score computed
+
+- The model with the best F1 score is the **user-user colaborative filtering tuned model**.
+- The F1 scores are very low. We can definitely improve these scores. This can be done by testing different technics, such as:
+ - data balancing
+ - changing the cutoffs thresholds.
+ - customizing the implementation of the algorithms for this specific problem
+ - collecting more data if possible
+ - building hybrid recommendations systems. This can be done by analyzing the observations for which the model is not performing well and use another model which performs better on those observations. Thus combining the predictions of both models at the same time.
+ - For the content based model, we need more features describing the songs, for example the genres of the songs.
+
+**2. Refined insights**:
+- What are the most meaningful insights from the data relevant to the problem?
+- The data is imbalanced, most of the play counts are 1 and 2, very few are 3, 4 or 5.
+- Adding more boservations with **high interactions** will increase the performance of the models.
+- Some observations date from 1969, we need further analysis to see how the rejection of these observations impacts the quality of the predictions. This could increase performance as it would likely reduce the sparsity of matrices and improve similarity measures. some old songs are no longer listened to and users tastes may have changed.
+
+
+**3. Proposal for the final solution design:**
+- What model do you propose to be adopted? Why is this the best solution to adopt?
+- Based on this analysis, the proposed model is the **user-user colaborative filtering tuned model**. This model has the best F1 score and the metric suited for this business is the F1 score.
+- We can increase the performance of this model by investigating further technics as mentionned above.
+
+## **Further analysis**
+
+Testing the cutoffs thresholds and discarding old songs
+
+```{python}
+df = pd.merge(count_df, song_df.drop_duplicates('song_id'), how='left')
+
+# Apply label encoding for "user_id" and "song_id"
+from sklearn.preprocessing import LabelEncoder
+
+#storing transformers in a dictionnary for decoding if needed
+label_object = {}
+categorical_columns = ['user_id','song_id']
+for col in categorical_columns:
+ labelencoder = LabelEncoder()
+ labelencoder.fit(df[col])
+ df[col] = labelencoder.fit_transform(df[col])
+ label_object[col] = labelencoder
+
+#saving the original df in df_copy
+df_copy = df.copy()
+```
+
+```{python}
+# Get the column containing the users
+users = df.user_id
+
+# Create a dictionary that maps users(listeners) to the number of songs that they have listened to
+playing_count = dict()
+
+for user in users:
+ # If we already have the user, just add 1 to their playing count
+ if user in playing_count:
+ playing_count[user] += 1
+
+ # Otherwise, set their playing count to 1
+ else:
+ playing_count[user] = 1
+```
+
+```{python}
+# We want our users to have listened at least 90 songs
+SONG_COUNT_CUTOFF = 160
+
+# Create a list of users who need to be removed
+remove_users = []
+
+for user, num_songs in playing_count.items():
+
+ if num_songs < SONG_COUNT_CUTOFF:
+ remove_users.append(user)
+
+df = df.loc[ ~ df.user_id.isin(remove_users)]
+```
+
+```{python}
+# Get the column containing the songs
+songs = df.song_id
+
+# Create a dictionary that maps songs to its number of users(listeners)
+playing_count = dict()
+
+for song in songs:
+ # If we already have the song, just add 1 to their playing count
+ if song in playing_count:
+ playing_count[song] += 1
+
+ # Otherwise, set their playing count to 1
+ else:
+ playing_count[song] = 1
+```
+
+```{python}
+# We want our song to be listened by at least 120 users to be considred
+LISTENER_COUNT_CUTOFF = 160
+
+remove_songs = []
+
+for song, num_users in playing_count.items():
+ if num_users < LISTENER_COUNT_CUTOFF:
+ remove_songs.append(song)
+
+df_final = df.loc[ ~ df.song_id.isin(remove_songs)]
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+df_final.shape
+```
+
+```{python}
+# Keeping the records with play counts greater than 5 by decreasing their play_count to 5
+df_final.loc[df_final.play_count>5, 'play_count'] = 5
+
+# discarding old observations
+df_final = df_final[(df_final.year > 1999) | (df_final.year == 0)]
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+df_final.shape
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 480}
+# Create a barplot plot with y label as "number of titles played" and x -axis year
+nbr_titles_played_by_year = df_final.groupby('year').title.nunique()
+
+# Set the figure size
+plt.figure(figsize=(7, 5))
+#from the previous cell, we could see there is an abnormal year 0, which seems to be
+#a default, so we're dropping that year
+plt.bar(x=nbr_titles_played_by_year.index[1:], height=nbr_titles_played_by_year[1:])
+
+# Set the x label of the plot
+plt.xlabel('year')
+
+# Set the y label of the plot
+plt.ylabel('number of titles played')
+
+# Setting all the ticks
+plt.xticks(ticks=nbr_titles_played_by_year.index[1:], rotation=90, size=7)
+
+# Show the plot
+plt.show()
+```
+
+```{python}
+# Instantiating Reader scale with expected rating scale
+ #use rating scale (0, 5)
+reader = Reader(rating_scale=(0, 5))
+
+# Loading the dataset
+ # Take only "user_id","song_id", and "play_count"
+data = Dataset.load_from_df(df_final[['user_id', 'song_id', 'play_count']],
+ reader=reader)
+
+# Splitting the data into train and test dataset
+ # Take test_size = 0.4, random_state = 42
+trainset, testset = train_test_split(data, test_size=0.4, random_state=42)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Build the default user-user-similarity model
+sim_options=dict(name='cosine', user_based=True)
+
+# KNN algorithm is used to find desired similar items
+ # Use random_state = 1
+sim_user_user_model = KNNBasic(sim_options=sim_options, random_state=1,
+ verbose=True)
+
+# Train the algorithm on the trainset, and predict play_count for the testset
+sim_user_user_model.fit(trainset)
+
+# Let us compute precision@k, recall@k, and f_1 score with k = 30
+ # Use sim_user_user model
+precision_recall_at_k(sim_user_user_model, testset=testset)
+```
+
+The new F1 score is 0.66. This is better than 0.51 obtained in the conclusion. The precision is still bad.
+
+## Tuning the model
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Setting up parameter grid to tune the hyperparameters
+params_grid = dict(k=[60, 80, 100], min_k=[10, 40, 50],
+ sim_options=dict(name=['msd', 'cosine'],
+ #min_support = [5, 10, 20],
+ user_based=[True]))
+# Performing 3-fold cross-validation to tune the hyperparameters
+gs = GridSearchCV(KNNBasic, param_grid=params_grid, cv=3, measures=['rmse'],
+ n_jobs=-1)
+
+# Fitting the data
+ # Use entire data for GridSearch
+gs.fit(data)
+
+# Best RMSE score
+print("Best RMSE score:", gs.best_score['rmse'])
+
+# Combination of parameters that gave the best RMSE score
+print("Best_params:", gs.best_params['rmse'])
+
+# Train the best model found in above gridsearch
+# Using the optimal similarity measure for user-user based collaborative filtering
+best_params = gs.best_params['rmse']
+best_k = best_params['k']
+best_min_k = best_params['min_k']
+best_sim_options = best_params['sim_options']
+
+# Creating an instance of KNNBasic with optimal hyperparameter values
+sim_user_user_tuned_model = KNNBasic(k = best_k, min_k=best_min_k,
+ sim_options=best_sim_options,
+ random_state=1, verbose=False)
+
+# Training the algorithm on the trainset
+sim_user_user_tuned_model.fit(trainset)
+
+# Let us compute precision@k and recall@k also
+precision_recall_at_k(sim_user_user_tuned_model, testset=testset)
+```
+
+Tuning has not increased the performance.
+
+## Balancing the data
+
+We only balance the trainset. As balancing the whole dataset would leak information of the testset in the training.
+
+```{python}
+trainset_df = pd.DataFrame(trainset.build_testset(), columns = ['user_id', 'song_id', 'play_count'])
+
+# We will oversample the under representated classes using smote
+from imblearn.over_sampling import SMOTE
+
+smote = SMOTE(random_state=42)
+
+#balancing with smote
+x, y = smote.fit_resample(trainset_df[['user_id', 'song_id']], trainset_df.play_count)
+
+trainset_df_balanced = pd.concat([x,y], axis=1)
+
+# converting the trainset balanced into suprise dataset
+trainset_data_balanced = Dataset.load_from_df(trainset_df_balanced[['user_id', 'song_id', 'play_count']],
+ reader=reader)
+
+# rebuild the trainset for surprise algorithms training
+trainset_balanced = trainset_data_balanced.build_full_trainset()
+
+# balanced the whole dataset for comparison purposes
+x2, y2 = smote.fit_resample(df_final[['user_id', 'song_id']], df_final.play_count)
+df_final_balanced = pd.concat([x2,y2], axis=1)
+data_balanced = Dataset.load_from_df(df_final_balanced[['user_id', 'song_id', 'play_count']],
+ reader=reader)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+trainset_df_balanced.play_count.value_counts()
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Build the default user-user-similarity model
+sim_options=dict(name='cosine', user_based=True)
+
+# KNN algorithm is used to find desired similar items
+ # Use random_state = 1
+sim_user_user_model = KNNBasic(k = 40, min_k=1, sim_options=sim_options, random_state=1,
+ verbose=True)
+
+# Train the algorithm on the trainset, and predict play_count for the testset
+sim_user_user_model.fit(trainset_balanced)
+
+# Let us compute precision@k, recall@k, and f_1 score with k = 30
+ # Use sim_user_user model
+precision_recall_at_k(sim_user_user_model, k=30, threshold=1.5,testset=testset)
+```
+
+Balancing the data has not increased the F1 score
+
+## Examining where the model is not performing well
+
+```{python}
+# Let's build a pandas dataframe with all the predictions
+def get_Iu(uid):
+ """Return the number of items rated by given user
+
+ Args:
+ uid: The raw id of the user.
+ Returns:
+ The number of items rated by the user.
+ """
+ try:
+ return len(trainset.ur[trainset.to_inner_uid(uid)])
+ except ValueError: # user was not part of the trainset
+ return 0
+
+def get_Ui(iid):
+ """Return the number of users that have rated given item
+
+ Args:
+ iid: The raw id of the item.
+ Returns:
+ The number of users that have rated the item.
+ """
+ try:
+ return len(trainset.ir[trainset.to_inner_iid(iid)])
+ except ValueError: # item was not part of the trainset
+ return 0
+```
+
+```{python}
+predictions = sim_user_user_model.test(testset)
+df = pd.DataFrame(predictions, columns=['uid', 'iid', 'rui', 'est', 'details'])
+df['Iu'] = df.uid.apply(get_Iu)
+df['Ui'] = df.iid.apply(get_Ui)
+df['err'] = abs(df.est - df.rui)
+```
+
+```{python}
+best_predictions = df.sort_values(by='err')[:10]
+worst_predictions = df.sort_values(by='err')[-10:]
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 363}
+# Let's take a look at the best predictions of the algorithm
+best_predictions
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+trainset_df.query("song_id==154").user_id.nunique()
+```
+
+**|Ui| is always big** (> 86), meaning that many users have played the target song in the trainset. **|Iu| is always very small**, meaning that the target user have note played many songs in the trainset.
+
+So it is easy to find users who have listened the song (as they are many), hence it is to find **k** users similar to the target user. And as they are similar to the target user, it is likely that they will rate the target song as the target user.
+
+**So all these predictions make sens.**
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 363}
+# Now, let's look at the prediction with the biggest error
+worst_predictions
+```
+
+**|Ui| is always big** (> 86), meaning that many users have played the target song in the trainset. **|Iu| is always very small**, meaning that the target user have note played many songs in the trainset.
+
+So it is easy to find users who have listened the song (as they are many), hence it is to find **k** users similar to the target user. And as they are similar to the target user, it is likely that they will rate the target song as the target user. But the predictions are not good. The explanation left is the taste of this song by this target is different from all his similar users.
+
+**So all these predictions are not good because we are in the cases of biased users.**
+
+These are situations where baseline estimates would be quite helpful, in order to deal with highly biased users (and items).
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/, height: 485}
+from collections import Counter
+
+import matplotlib.pyplot as plt
+import matplotlib
+%matplotlib inline
+
+matplotlib.style.use('ggplot')
+counter = Counter([r for (_, r) in trainset.ir[trainset.to_inner_iid(154)]])
+pd.DataFrame.from_dict(counter, orient='index').plot(kind='bar', legend=False)
+plt.xlabel('Play count')
+plt.ylabel('Number of users')
+plt.title('Number of users having played item 154')
+plt.show()
+```
+
+As we can see in the above figure, most of the users played the song only once and very small number played the song 5 times and our target user is among them. So he is a biased user.
+
+**We can build another model for those biased users. And combine the models to create an hybrid recommendation system.**
+
+## Customizing the KNNBasic algorithm
+
+```{python}
+from surprise.prediction_algorithms.knns import SymmetricAlgo
+
+from surprise.prediction_algorithms.predictions import PredictionImpossible
+from sklearn.ensemble import RandomForestRegressor
+
+class CollaborativeRandomForest(SymmetricAlgo):
+ """A basic collaborative filtering algorithm.
+
+ The prediction :math:`\\hat{r}_{ui}` is set as:
+
+ .. math::
+ \\hat{r}_{ui} = \\frac{
+ \\sum\\limits_{v \\in N^k_i(u)} \\text{sim}(u, v) \\cdot r_{vi}}
+ {\\sum\\limits_{v \\in N^k_i(u)} \\text{sim}(u, v)}
+
+ or
+
+ .. math::
+ \\hat{r}_{ui} = \\frac{
+ \\sum\\limits_{j \\in N^k_u(i)} \\text{sim}(i, j) \\cdot r_{uj}}
+ {\\sum\\limits_{j \\in N^k_u(i)} \\text{sim}(i, j)}
+
+ depending on the ``user_based`` field of the ``sim_options`` parameter.
+
+ Args:
+ k(int): The (max) number of neighbors to take into account for
+ aggregation (see :ref:`this note `). Default is
+ ``40``.
+ min_k(int): The minimum number of neighbors to take into account for
+ aggregation. If there are not enough neighbors, the prediction is
+ set to the global mean of all ratings. Default is ``1``.
+ sim_options(dict): A dictionary of options for the similarity
+ measure. See :ref:`similarity_measures_configuration` for accepted
+ options.
+ verbose(bool): Whether to print trace messages of bias estimation,
+ similarity, etc. Default is True.
+ """
+
+ def __init__(self, k=40, min_k=1, sim_options={}, verbose=True, **kwargs):
+
+ SymmetricAlgo.__init__(self, sim_options=sim_options, verbose=verbose, **kwargs)
+ self.k = k
+ self.min_k = min_k
+ self.rf = RandomForestRegressor()
+
+ def compute_rating_and_sim_averages(self, neighbors):
+ # compute weighted average
+ sum_sim = sum_ratings = actual_k = rating = 0
+ if (len(neighbors) != 0):
+ for (sim, r) in neighbors:
+ sum_sim += sim
+ sum_ratings += sim * r
+ else:
+ rating = self.trainset.global_mean
+
+ if sum_sim != 0 :
+ rating = sum_ratings/sum_sim
+ return (sum_sim, rating)
+
+ def fit(self, trainset):
+ SymmetricAlgo.fit(self, trainset)
+ self.sim = self.compute_similarities()
+
+ #The feature matrix
+ # an observation in x is the wheighted average of similarities for each user.
+ # For each user we compute the wheighted average of the similarities
+ # of all the users who listened to a target song for all the songs in the training dataset
+
+ X = []
+ #The target, the play counts
+
+ Y = []
+ # an observation in Y is the wheighted average of play counts for each user.
+ # For each user we compute the wheighted average of the play counts
+ # of all the users who listened to a target song for all the songs in the training dataset
+ for u in trainset.all_users():
+ for i in trainset.all_items():
+ x, y = self.switch(u, i)
+ neighbors = [(self.sim[x, x2], r) for (x2, r) in self.yr[y]]
+ n = self.compute_rating_and_sim_averages(neighbors)
+ X.append(n[0])
+ Y.append(n[1])
+
+ self.rf.fit(np.array(X).reshape(-1, 1), Y)
+
+ return self
+
+ def estimate(self, u, i):
+ if not (self.trainset.knows_user(u) and self.trainset.knows_item(i)):
+ raise PredictionImpossible("User and/or item is unknown.")
+
+ x, y = self.switch(u, i)
+
+ neighbors = [(self.sim[x, x2], r) for (x2, r) in self.yr[y]]
+
+ n = self.compute_rating_and_sim_averages(neighbors)[0]
+ est = self.rf.predict(np.array(n).reshape(-1, 1))[0]
+
+ details = {"actual_k": 6}
+ return est, details
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Build the default KNNBasicWithRandomForest model
+sim_options=dict(name='cosine', user_based=True)
+
+# KNN algorithm is used to find desired similar items
+ # Use random_state = 1
+sim_user_user_model = CollaborativeRandomForest(sim_options=sim_options,
+ random_state=1, verbose=False)
+
+# Train the algorithm on the trainset, and predict play_count for the testset
+sim_user_user_model.fit(trainset)
+
+# Let us compute precision@k, recall@k, and f_1 score with k = 30
+ # Use sim_user_user model
+precision_recall_at_k(sim_user_user_model, threshold=1.5, testset=testset)
+```
+
+The F1 score is still 0.66. So there is no improvement.
+
+The performance of this model is not high. This is because we are using only one future, the weighted similarities. This is comparable to predicting the skin color of a person based on his age, it doesn't make much sense.
+
+## Building an hybrid recommendation system:
+ - We will use LightFM a newly discovered library
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Installing lightfm
+!pip install lightfm
+```
+
+```{python}
+# Importing packages
+from lightfm import LightFM
+from lightfm.data import Dataset
+from lightfm.evaluation import precision_at_k, recall_at_k, auc_score
+from lightfm.cross_validation import random_train_test_split
+```
+
+```{python}
+# Modifying the dataset cutoffs thresholds
+# Get the column containing the users
+df = df_copy
+users = df.user_id
+
+# Create a dictionary that maps users(listeners) to the number of songs that they have listened to
+playing_count = dict()
+
+for user in users:
+ # If we already have the user, just add 1 to their playing count
+ if user in playing_count:
+ playing_count[user] += 1
+
+ # Otherwise, set their playing count to 1
+ else:
+ playing_count[user] = 1
+
+# We want our users to have listened at least 90 songs
+SONG_COUNT_CUTOFF = 90
+
+# Create a list of users who need to be removed
+remove_users = []
+
+for user, num_songs in playing_count.items():
+
+ if num_songs < SONG_COUNT_CUTOFF:
+ remove_users.append(user)
+
+df = df.loc[ ~ df.user_id.isin(remove_users)]
+
+
+# Get the column containing the songs
+songs = df.song_id
+
+# Create a dictionary that maps songs to its number of users(listeners)
+playing_count = dict()
+
+for song in songs:
+ # If we already have the song, just add 1 to their playing count
+ if song in playing_count:
+ playing_count[song] += 1
+
+ # Otherwise, set their playing count to 1
+ else:
+ playing_count[song] = 1
+
+# We want our song to be listened by at least 120 users to be considred
+LISTENER_COUNT_CUTOFF = 120
+
+remove_songs = []
+
+for song, num_users in playing_count.items():
+ if num_users < LISTENER_COUNT_CUTOFF:
+ remove_songs.append(song)
+
+df_final = df.loc[ ~ df.song_id.isin(remove_songs)]
+```
+
+```{python}
+# Caping the play count to 5
+df_final.loc[df_final.play_count>5, 'play_count'] = 5
+```
+
+```{python}
+# rescaling the playcount [0, 1]
+df_final['play_count_scaled'] = 1 / df_final.play_count
+```
+
+```{python}
+# Setting desired positive pairs (playcount>1.5 or play_count_scaled>0.3 ) to 1
+# and negatives pairs to 0
+df_final.loc[df_final['play_count_scaled'] < 0.3, 'play_count_scaled'] = 0
+df_final.loc[df_final['play_count_scaled'] >= 0.3, 'play_count_scaled'] = 1
+```
+
+```{python}
+# Defining users and songs sets
+user_ids = set(df_final.user_id)
+songs_ids = set(df_final.song_id)
+```
+
+```{python}
+# Defining user features
+nbr_user_listened_songs = df_final.groupby('user_id').play_count.count()
+nbr_user_listened_songs = nbr_user_listened_songs / nbr_user_listened_songs.values.sum()
+```
+
+```{python}
+# Defining songs features
+df_final['text'] = df_final['title'] + ' ' + df_final['release'] + ' ' + df_final['artist_name']
+```
+
+```{python}
+# Create a Dataset object and fit users, songs, and features
+dataset = Dataset()
+dataset.fit(users=user_ids,
+ items=songs_ids,
+ user_features = set(nbr_user_listened_songs),
+ item_features = set(df_final.text))
+
+
+# Build interactions and features
+interactions = [(x[0], x[1], x[2]) for x in df_final[['user_id', 'song_id', 'play_count_scaled']].values]
+(interactions, weights) = dataset.build_interactions(interactions)
+```
+
+```{python}
+# Split the interactions matrix into a train and test set
+test_ratio = 0.2
+train_interactions, test_interactions = random_train_test_split(interactions, test_percentage=test_ratio, random_state=2)
+train_weights, test_weights = random_train_test_split(weights, test_percentage=test_ratio, random_state=2)
+```
+
+```{python}
+# Builiding user features
+user_features = list(zip(nbr_user_listened_songs.index, nbr_user_listened_songs.apply(lambda x: [x])))
+user_features = dataset.build_user_features(user_features)
+
+
+# Builiding items features
+item_features = list((x[0], [x[1]]) for x in df_final[['song_id', 'text']].drop_duplicates().values)
+item_features = dataset.build_item_features(item_features)
+```
+
+```{python}
+# Creating the LightFM model
+basic_lfm = LightFM(
+ no_components=150,
+ learning_rate=0.05,
+ loss='warp',
+ item_alpha=0.0001,
+ user_alpha=0.0001,
+ random_state=200)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Training the model
+basic_lfm.fit(train_interactions,
+ sample_weight=train_weights,
+ user_features=user_features,
+ item_features=item_features,
+ epochs=50, num_threads=50, verbose=True)
+```
+
+```{python}
+# A function to evaluate the model using LightFm evaluation metrics
+def evaluate_lfm_model(model, test_interactions, user_features, item_features,
+ k=30):
+ # compute the auc
+ print('AUC', np.nanmean(auc_score(model,
+ test_interactions,
+ user_features=user_features,
+ item_features=item_features)))
+ # calculate precision_at_k
+ precision = precision_at_k(model,
+ test_interactions,
+ user_features=user_features,
+ item_features=item_features,
+ k=k
+ ).mean()
+ print("Precision:", precision)
+
+
+ # calculate recall_at_k
+ recall = recall_at_k(model,
+ test_interactions,
+ user_features=user_features,
+ item_features=item_features,
+ k=k).mean()
+ print("Recall:", recall)
+
+ # Calculate f1_score_at_k
+ f1_score = 2 * precision * recall / (recall + precision)
+ print("F1_score:", f1_score)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Evaluating the performance of the model using lightfm evaluation metrics
+evaluate_lfm_model(basic_lfm, test_interactions, user_features,
+ item_features, 30)
+```
+
+The AUC is 0.76. It measures the probability that a randomly chosen positive interaction (e.g., a user liking a song) is ranked higher by the model than a randomly chosen negative interaction (e.g., a user not liking a song)
+
+### Let's evaluate the performance of the model with a threshold in Surprise library manner
+
+```{python}
+# Evaluating the model in an explicit feedback manner
+# Let's get the predictions and actual playcount for every user-song pair in the testset
+def get_predictions(model, df_final, dataset, test_interactions):
+
+ # Get the mappings
+ user_id_mapping, user_feature_mapping, item_id_mapping, item_feature_mapping = dataset.mapping()
+
+ # Create reverse mappings
+ reverse_user_id_mapping = {v: k for k, v in user_id_mapping.items()}
+ reverse_item_id_mapping = {v: k for k, v in item_id_mapping.items()}
+
+ df_interactions = df_final[['user_id', 'song_id', 'play_count_scaled']]
+
+ # Function to get real ratings using internal IDs
+ def get_real_rating(internal_user_id, internal_item_id, real_user_id = None,
+ real_item_id = None):
+ if not real_user_id:
+ real_user_id = reverse_user_id_mapping.get(internal_user_id, None)
+ if not real_item_id:
+ real_item_id = reverse_item_id_mapping.get(internal_item_id, None)
+ if real_user_id is not None and real_item_id is not None:
+ rating = df_interactions.loc[
+ (df_interactions['user_id'] == real_user_id) &
+ (df_interactions['song_id'] == real_item_id), 'play_count_scaled']
+ if not rating.empty:
+ return rating.values[0]
+ return None
+
+
+ # Function to get real user_id and item_id
+ def get_real_ids(internal_user_id, internal_item_id):
+ # Reverse the user_id_mapping and item_id_mapping to map internal IDs to real IDs
+ real_user_id = {v: k for k, v in user_id_mapping.items()}[internal_user_id]
+ real_item_id = {v: k for k, v in item_id_mapping.items()}[internal_item_id]
+ return real_user_id, real_item_id
+
+
+ user_song_pairs = test_interactions.nonzero()
+ user_and_song_iternal_ids = list(zip(user_song_pairs[0], user_song_pairs[1]))
+
+ ests = []
+ trues = []
+ user_est_true_lfm = defaultdict(list)
+ for iternal_user, internal_song in user_and_song_iternal_ids:
+
+ real_user_id, real_item_id = get_real_ids(iternal_user, internal_song)
+
+ score = model.predict(int(iternal_user), [int(internal_song)])[0]
+ playcount = get_real_rating(iternal_user, internal_song, real_user_id, real_item_id)
+ user_est_true_lfm[real_user_id].append((score, playcount))
+ ests.append(score)
+ trues.append(playcount)
+
+ return user_est_true_lfm, ests, trues
+
+
+
+# Let's customize the precision_recall_at_k function
+def precision_recall_at_k_lfm(k=30, threshold=0, user_est_true=None):
+ """Return precision and recall at k metrics for each user"""
+
+ precisions = dict()
+ recalls = dict()
+
+ for uid, playing_count in user_est_true.items():
+
+ # Sort play count by estimated value
+ playing_count.sort(key=lambda x: x[0], reverse=True)
+
+ # Number of relevant items
+ n_rel = sum((true_r >= threshold) for (_, true_r) in playing_count)
+
+ # Number of recommended items in top k
+ n_rec_k = sum((est >= threshold) for (est, _) in playing_count[:k])
+
+ # Number of relevant and recommended items in top k
+ n_rel_and_rec_k = sum(((true_r >= threshold) and (est >= threshold))
+ for (est, true_r) in playing_count[:k])
+
+ # Precision@K: Proportion of recommended items that are relevant
+ # When n_rec_k is 0, Precision is undefined. We here set Precision to 0 when n_rec_k is 0.
+
+ precisions[uid] = n_rel_and_rec_k / n_rec_k if n_rec_k != 0 else 0
+
+ # Recall@K: Proportion of relevant items that are recommended
+ # When n_rel is 0, Recall is undefined. We here set Recall to 0 when n_rel is 0.
+
+ recalls[uid] = n_rel_and_rec_k / n_rel if n_rel != 0 else 0
+
+
+ #Mean of all the predicted precisions are calculated.
+ precision = round((sum(prec for prec in precisions.values()) / len(precisions)),3)
+ #Mean of all the predicted recalls are calculated.
+ recall = round((sum(rec for rec in recalls.values()) / len(recalls)),3)
+
+ #accuracy.rmse(predictions)
+ print('Precision: ', precision) #Command to print the overall precision
+ print('Recall: ', recall) #Command to print the overall recall
+ print('F_1 score: ', round((2*precision*recall)/(precision+recall),3)) # Formula to compute the F-1 score.
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# getting the prediction for each user-pair of the testset
+user_est_true_lfm = get_predictions(basic_lfm, df_final, dataset, test_interactions)
+ests = user_est_true_lfm[1]
+print('min_est_score:', min(ests))
+print('max_est_score:', max(ests))
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# evaluate the model with a threshold
+precision_recall_at_k_lfm(user_est_true=user_est_true_lfm[0], threshold=-1)
+```
+
+### Rebuilidng the model by transforming the item features with a vectorizer
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Taking a sample of the dataset
+df_final = df_final.sample(3000, random_state=42)
+
+# Defining users and songs sets
+user_ids = set(df_final.user_id)
+songs_ids = set(df_final.song_id)
+
+# Defining user features
+nbr_user_listened_songs = df_final.groupby('user_id').play_count.count()
+nbr_user_listened_songs = nbr_user_listened_songs / nbr_user_listened_songs.values.sum()
+
+# Defining songs features
+df_final['text'] = df_final['title'] + ' ' + df_final['release'] + ' ' + df_final['artist_name']
+
+# Create tfidf vectorizer
+tfidf = TfidfVectorizer(tokenizer = tokenize)
+
+# Creating the songs tfidf
+songs_tfidf = tfidf.fit_transform(df_final['text'].values).toarray()
+
+# Create a Dataset object and fit users, songs, and features
+dataset = Dataset()
+dataset.fit(users=user_ids, items=songs_ids,
+ user_features = set(nbr_user_listened_songs),
+ item_features = set(songs_tfidf.ravel()))
+
+
+# Build interactions and features
+interactions = [(x[0], x[1], x[2]) for x in df_final[['user_id', 'song_id', 'play_count_scaled']].values]
+(interactions, weights) = dataset.build_interactions(interactions)
+
+
+# Split the interactions matrix into a train and test set
+test_ratio = 0.2
+train_interactions, test_interactions = random_train_test_split(interactions, test_percentage=test_ratio, random_state=2)
+train_weights, test_weights = random_train_test_split(weights, test_percentage=test_ratio, random_state=2)
+
+
+# Builiding user features
+user_features = list(zip(nbr_user_listened_songs.index, nbr_user_listened_songs.apply(lambda x: [x])))
+user_features = dataset.build_user_features(user_features)
+
+
+# Builiding items features
+ # Map item IDs to the order of the TF-IDF matrix
+item_features_map = dict(zip(songs_ids, songs_tfidf))
+ # Fit the dataset with item features
+dataset.fit_partial(items=songs_ids, item_features=range(songs_tfidf.shape[1]))
+
+itl = []
+ # Build the item features list
+for item_id in songs_ids:
+ for i, value in enumerate(item_features_map[item_id]):
+ itl.append((item_id, (i, value)))
+
+item_features = dataset.build_item_features(itl)
+
+
+vectorized_lfm = LightFM(
+ no_components=150,
+ learning_rate=0.05,
+ loss='warp',
+ random_state=200)
+
+
+vectorized_lfm.fit(train_interactions,
+ sample_weight=train_weights,
+ user_features=user_features,
+ item_features=item_features,
+ epochs=50, num_threads=50, verbose=True)
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# Evaluating the model with lightfm evaluation metrics
+evaluate_lfm_model(vectorized_lfm, test_interactions, user_features,
+ item_features, 30)
+```
+
+The AUC has not improved
+ - It is due to the small number of observations used to train the model.
+ - With the same number of observations this approach gives a slightly better performance.
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+
+# getting the prediction for each user-pair of the testset
+user_est_true_lfm = get_predictions(vectorized_lfm, df_final, dataset, test_interactions)
+ests = user_est_true_lfm[1]
+print('min_est_score:', min(ests))
+print('max_est_score:', max(ests))
+```
+
+```{python}
+#| colab: {base_uri: https://localhost:8080/}
+# evaluate the model with a threshold in Surprise manner
+precision_recall_at_k_lfm(user_est_true=user_est_true_lfm[0], threshold=-0.01)
+```
+
+With no surprise the threshold evaluation f1 score is not better.
+
+## Final Conclusion
+
+**We improved the F1 score of the user user collaborative filtering model by 0.15 points (0.66 vs 0.51 before)** just by:
+ - changing the **cutoffs thresholds**
+ - **dropping old observations**
+ - **caping the play counts** greater than 5 **to 5**.
+
+Balancing the data and creating a custom Random Forest model have not improved the performance.
+
+We have also built an **hybrid recommendation system** using the LightFm library. The AUC obtained with a basic hybrid model is 0.76.
+The results can be improved provided:
+- more computer power to perform the training.
+- more data on users, for example demographic or behavior data.
+- more data on songs, for example the genre or the category of a song.
+
diff --git a/public/notebooks/music-recommendation_files/libs/bootstrap/bootstrap-10daa034703793678e481cc8cee6d76f.min.css b/public/notebooks/music-recommendation_files/libs/bootstrap/bootstrap-10daa034703793678e481cc8cee6d76f.min.css
new file mode 100644
index 0000000..1226b46
--- /dev/null
+++ b/public/notebooks/music-recommendation_files/libs/bootstrap/bootstrap-10daa034703793678e481cc8cee6d76f.min.css
@@ -0,0 +1,12 @@
+/*!
+ * Bootstrap v5.3.1 (https://getbootstrap.com/)
+ * Copyright 2011-2023 The Bootstrap Authors
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */@import"https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;700&display=swap";:root,[data-bs-theme=light]{--bs-blue: #2780e3;--bs-indigo: #6610f2;--bs-purple: #613d7c;--bs-pink: #e83e8c;--bs-red: #ff0039;--bs-orange: #f0ad4e;--bs-yellow: #ff7518;--bs-green: #3fb618;--bs-teal: #20c997;--bs-cyan: #9954bb;--bs-black: #000;--bs-white: #fff;--bs-gray: #868e96;--bs-gray-dark: #373a3c;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #868e96;--bs-gray-700: #495057;--bs-gray-800: #373a3c;--bs-gray-900: #212529;--bs-default: #373a3c;--bs-primary: #2780e3;--bs-secondary: #373a3c;--bs-success: #3fb618;--bs-info: #9954bb;--bs-warning: #ff7518;--bs-danger: #ff0039;--bs-light: #f8f9fa;--bs-dark: #373a3c;--bs-default-rgb: 55, 58, 60;--bs-primary-rgb: 39, 128, 227;--bs-secondary-rgb: 55, 58, 60;--bs-success-rgb: 63, 182, 24;--bs-info-rgb: 153, 84, 187;--bs-warning-rgb: 255, 117, 24;--bs-danger-rgb: 255, 0, 57;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 55, 58, 60;--bs-primary-text-emphasis: #10335b;--bs-secondary-text-emphasis: #161718;--bs-success-text-emphasis: #19490a;--bs-info-text-emphasis: #3d224b;--bs-warning-text-emphasis: #662f0a;--bs-danger-text-emphasis: #660017;--bs-light-text-emphasis: #495057;--bs-dark-text-emphasis: #495057;--bs-primary-bg-subtle: #d4e6f9;--bs-secondary-bg-subtle: #d7d8d8;--bs-success-bg-subtle: #d9f0d1;--bs-info-bg-subtle: #ebddf1;--bs-warning-bg-subtle: #ffe3d1;--bs-danger-bg-subtle: #ffccd7;--bs-light-bg-subtle: #fcfcfd;--bs-dark-bg-subtle: #ced4da;--bs-primary-border-subtle: #a9ccf4;--bs-secondary-border-subtle: #afb0b1;--bs-success-border-subtle: #b2e2a3;--bs-info-border-subtle: #d6bbe4;--bs-warning-border-subtle: #ffc8a3;--bs-danger-border-subtle: #ff99b0;--bs-light-border-subtle: #e9ecef;--bs-dark-border-subtle: #adb5bd;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-font-sans-serif: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-root-font-size: 17px;--bs-body-font-family: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #373a3c;--bs-body-color-rgb: 55, 58, 60;--bs-body-bg: #fff;--bs-body-bg-rgb: 255, 255, 255;--bs-emphasis-color: #000;--bs-emphasis-color-rgb: 0, 0, 0;--bs-secondary-color: rgba(55, 58, 60, 0.75);--bs-secondary-color-rgb: 55, 58, 60;--bs-secondary-bg: #e9ecef;--bs-secondary-bg-rgb: 233, 236, 239;--bs-tertiary-color: rgba(55, 58, 60, 0.5);--bs-tertiary-color-rgb: 55, 58, 60;--bs-tertiary-bg: #f8f9fa;--bs-tertiary-bg-rgb: 248, 249, 250;--bs-heading-color: inherit;--bs-link-color: #2761e3;--bs-link-color-rgb: 39, 97, 227;--bs-link-decoration: underline;--bs-link-hover-color: #1f4eb6;--bs-link-hover-color-rgb: 31, 78, 182;--bs-code-color: #7d12ba;--bs-highlight-bg: #ffe3d1;--bs-border-width: 1px;--bs-border-style: solid;--bs-border-color: #e1e1e2;--bs-border-color-translucent: rgba(0, 0, 0, 0.175);--bs-border-radius: 0.375rem;--bs-border-radius-sm: 0.25rem;--bs-border-radius-lg: 0.5rem;--bs-border-radius-xl: 1rem;--bs-border-radius-xxl: 2rem;--bs-border-radius-2xl: var(--bs-border-radius-xxl);--bs-border-radius-pill: 50rem;--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width: 0.25rem;--bs-focus-ring-opacity: 0.25;--bs-focus-ring-color: rgba(39, 128, 227, 0.25);--bs-form-valid-color: #3fb618;--bs-form-valid-border-color: #3fb618;--bs-form-invalid-color: #ff0039;--bs-form-invalid-border-color: #ff0039}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color: #dee2e6;--bs-body-color-rgb: 222, 226, 230;--bs-body-bg: #212529;--bs-body-bg-rgb: 33, 37, 41;--bs-emphasis-color: #fff;--bs-emphasis-color-rgb: 255, 255, 255;--bs-secondary-color: rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb: 222, 226, 230;--bs-secondary-bg: #373a3c;--bs-secondary-bg-rgb: 55, 58, 60;--bs-tertiary-color: rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb: 222, 226, 230;--bs-tertiary-bg: #2c3033;--bs-tertiary-bg-rgb: 44, 48, 51;--bs-primary-text-emphasis: #7db3ee;--bs-secondary-text-emphasis: #87898a;--bs-success-text-emphasis: #8cd374;--bs-info-text-emphasis: #c298d6;--bs-warning-text-emphasis: #ffac74;--bs-danger-text-emphasis: #ff6688;--bs-light-text-emphasis: #f8f9fa;--bs-dark-text-emphasis: #dee2e6;--bs-primary-bg-subtle: #081a2d;--bs-secondary-bg-subtle: #0b0c0c;--bs-success-bg-subtle: #0d2405;--bs-info-bg-subtle: #1f1125;--bs-warning-bg-subtle: #331705;--bs-danger-bg-subtle: #33000b;--bs-light-bg-subtle: #373a3c;--bs-dark-bg-subtle: #1c1d1e;--bs-primary-border-subtle: #174d88;--bs-secondary-border-subtle: #212324;--bs-success-border-subtle: #266d0e;--bs-info-border-subtle: #5c3270;--bs-warning-border-subtle: #99460e;--bs-danger-border-subtle: #990022;--bs-light-border-subtle: #495057;--bs-dark-border-subtle: #373a3c;--bs-heading-color: inherit;--bs-link-color: #7db3ee;--bs-link-hover-color: #97c2f1;--bs-link-color-rgb: 125, 179, 238;--bs-link-hover-color-rgb: 151, 194, 241;--bs-code-color: white;--bs-border-color: #495057;--bs-border-color-translucent: rgba(255, 255, 255, 0.15);--bs-form-valid-color: #8cd374;--bs-form-valid-border-color: #8cd374;--bs-form-invalid-color: #ff6688;--bs-form-invalid-border-color: #ff6688}*,*::before,*::after{box-sizing:border-box}:root{font-size:var(--bs-root-font-size)}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;border:0;border-top:1px solid;opacity:.25}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2;color:var(--bs-heading-color)}h1,.h1{font-size:calc(1.325rem + 0.9vw)}@media(min-width: 1200px){h1,.h1{font-size:2rem}}h2,.h2{font-size:calc(1.29rem + 0.48vw)}@media(min-width: 1200px){h2,.h2{font-size:1.65rem}}h3,.h3{font-size:calc(1.27rem + 0.24vw)}@media(min-width: 1200px){h3,.h3{font-size:1.45rem}}h4,.h4{font-size:1.25rem}h5,.h5{font-size:1.1rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{text-decoration:underline dotted;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;-ms-text-decoration:underline dotted;-o-text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem;padding:.625rem 1.25rem;border-left:.25rem solid #e9ecef}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}b,strong{font-weight:bolder}small,.small{font-size:0.875em}mark,.mark{padding:.1875em;background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}a:hover{--bs-link-color-rgb: var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f8f9fa;line-height:1.5;padding:.5rem;border:1px solid var(--bs-border-color, #e1e1e2)}pre code{background-color:rgba(0,0,0,0);font-size:inherit;color:inherit;word-break:normal}code{font-size:0.875em;color:var(--bs-code-color);background-color:#f8f9fa;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#373a3c}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:rgba(55,58,60,.75);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none !important}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + 0.3vw);line-height:inherit}@media(min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:0.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:0.875em;color:#868e96}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #e1e1e2;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:rgba(55,58,60,.75)}.container,.container-fluid,.container-xxl,.container-xl,.container-lg,.container-md,.container-sm{--bs-gutter-x: 1.5rem;--bs-gutter-y: 0;width:100%;padding-right:calc(var(--bs-gutter-x)*.5);padding-left:calc(var(--bs-gutter-x)*.5);margin-right:auto;margin-left:auto}@media(min-width: 576px){.container-sm,.container{max-width:540px}}@media(min-width: 768px){.container-md,.container-sm,.container{max-width:720px}}@media(min-width: 992px){.container-lg,.container-md,.container-sm,.container{max-width:960px}}@media(min-width: 1200px){.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1140px}}@media(min-width: 1400px){.container-xxl,.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1320px}}:root{--bs-breakpoint-xs: 0;--bs-breakpoint-sm: 576px;--bs-breakpoint-md: 768px;--bs-breakpoint-lg: 992px;--bs-breakpoint-xl: 1200px;--bs-breakpoint-xxl: 1400px}.grid{display:grid;grid-template-rows:repeat(var(--bs-rows, 1), 1fr);grid-template-columns:repeat(var(--bs-columns, 12), 1fr);gap:var(--bs-gap, 1.5rem)}.grid .g-col-1{grid-column:auto/span 1}.grid .g-col-2{grid-column:auto/span 2}.grid .g-col-3{grid-column:auto/span 3}.grid .g-col-4{grid-column:auto/span 4}.grid .g-col-5{grid-column:auto/span 5}.grid .g-col-6{grid-column:auto/span 6}.grid .g-col-7{grid-column:auto/span 7}.grid .g-col-8{grid-column:auto/span 8}.grid .g-col-9{grid-column:auto/span 9}.grid .g-col-10{grid-column:auto/span 10}.grid .g-col-11{grid-column:auto/span 11}.grid .g-col-12{grid-column:auto/span 12}.grid .g-start-1{grid-column-start:1}.grid .g-start-2{grid-column-start:2}.grid .g-start-3{grid-column-start:3}.grid .g-start-4{grid-column-start:4}.grid .g-start-5{grid-column-start:5}.grid .g-start-6{grid-column-start:6}.grid .g-start-7{grid-column-start:7}.grid .g-start-8{grid-column-start:8}.grid .g-start-9{grid-column-start:9}.grid .g-start-10{grid-column-start:10}.grid .g-start-11{grid-column-start:11}@media(min-width: 576px){.grid .g-col-sm-1{grid-column:auto/span 1}.grid .g-col-sm-2{grid-column:auto/span 2}.grid .g-col-sm-3{grid-column:auto/span 3}.grid .g-col-sm-4{grid-column:auto/span 4}.grid .g-col-sm-5{grid-column:auto/span 5}.grid .g-col-sm-6{grid-column:auto/span 6}.grid .g-col-sm-7{grid-column:auto/span 7}.grid .g-col-sm-8{grid-column:auto/span 8}.grid .g-col-sm-9{grid-column:auto/span 9}.grid .g-col-sm-10{grid-column:auto/span 10}.grid .g-col-sm-11{grid-column:auto/span 11}.grid .g-col-sm-12{grid-column:auto/span 12}.grid .g-start-sm-1{grid-column-start:1}.grid .g-start-sm-2{grid-column-start:2}.grid .g-start-sm-3{grid-column-start:3}.grid .g-start-sm-4{grid-column-start:4}.grid .g-start-sm-5{grid-column-start:5}.grid .g-start-sm-6{grid-column-start:6}.grid .g-start-sm-7{grid-column-start:7}.grid .g-start-sm-8{grid-column-start:8}.grid .g-start-sm-9{grid-column-start:9}.grid .g-start-sm-10{grid-column-start:10}.grid .g-start-sm-11{grid-column-start:11}}@media(min-width: 768px){.grid .g-col-md-1{grid-column:auto/span 1}.grid .g-col-md-2{grid-column:auto/span 2}.grid .g-col-md-3{grid-column:auto/span 3}.grid .g-col-md-4{grid-column:auto/span 4}.grid .g-col-md-5{grid-column:auto/span 5}.grid .g-col-md-6{grid-column:auto/span 6}.grid .g-col-md-7{grid-column:auto/span 7}.grid .g-col-md-8{grid-column:auto/span 8}.grid .g-col-md-9{grid-column:auto/span 9}.grid .g-col-md-10{grid-column:auto/span 10}.grid .g-col-md-11{grid-column:auto/span 11}.grid .g-col-md-12{grid-column:auto/span 12}.grid .g-start-md-1{grid-column-start:1}.grid .g-start-md-2{grid-column-start:2}.grid .g-start-md-3{grid-column-start:3}.grid .g-start-md-4{grid-column-start:4}.grid .g-start-md-5{grid-column-start:5}.grid .g-start-md-6{grid-column-start:6}.grid .g-start-md-7{grid-column-start:7}.grid .g-start-md-8{grid-column-start:8}.grid .g-start-md-9{grid-column-start:9}.grid .g-start-md-10{grid-column-start:10}.grid .g-start-md-11{grid-column-start:11}}@media(min-width: 992px){.grid .g-col-lg-1{grid-column:auto/span 1}.grid .g-col-lg-2{grid-column:auto/span 2}.grid .g-col-lg-3{grid-column:auto/span 3}.grid .g-col-lg-4{grid-column:auto/span 4}.grid .g-col-lg-5{grid-column:auto/span 5}.grid .g-col-lg-6{grid-column:auto/span 6}.grid .g-col-lg-7{grid-column:auto/span 7}.grid .g-col-lg-8{grid-column:auto/span 8}.grid .g-col-lg-9{grid-column:auto/span 9}.grid .g-col-lg-10{grid-column:auto/span 10}.grid .g-col-lg-11{grid-column:auto/span 11}.grid .g-col-lg-12{grid-column:auto/span 12}.grid .g-start-lg-1{grid-column-start:1}.grid .g-start-lg-2{grid-column-start:2}.grid .g-start-lg-3{grid-column-start:3}.grid .g-start-lg-4{grid-column-start:4}.grid .g-start-lg-5{grid-column-start:5}.grid .g-start-lg-6{grid-column-start:6}.grid .g-start-lg-7{grid-column-start:7}.grid .g-start-lg-8{grid-column-start:8}.grid .g-start-lg-9{grid-column-start:9}.grid .g-start-lg-10{grid-column-start:10}.grid .g-start-lg-11{grid-column-start:11}}@media(min-width: 1200px){.grid .g-col-xl-1{grid-column:auto/span 1}.grid .g-col-xl-2{grid-column:auto/span 2}.grid .g-col-xl-3{grid-column:auto/span 3}.grid .g-col-xl-4{grid-column:auto/span 4}.grid .g-col-xl-5{grid-column:auto/span 5}.grid .g-col-xl-6{grid-column:auto/span 6}.grid .g-col-xl-7{grid-column:auto/span 7}.grid .g-col-xl-8{grid-column:auto/span 8}.grid .g-col-xl-9{grid-column:auto/span 9}.grid .g-col-xl-10{grid-column:auto/span 10}.grid .g-col-xl-11{grid-column:auto/span 11}.grid .g-col-xl-12{grid-column:auto/span 12}.grid .g-start-xl-1{grid-column-start:1}.grid .g-start-xl-2{grid-column-start:2}.grid .g-start-xl-3{grid-column-start:3}.grid .g-start-xl-4{grid-column-start:4}.grid .g-start-xl-5{grid-column-start:5}.grid .g-start-xl-6{grid-column-start:6}.grid .g-start-xl-7{grid-column-start:7}.grid .g-start-xl-8{grid-column-start:8}.grid .g-start-xl-9{grid-column-start:9}.grid .g-start-xl-10{grid-column-start:10}.grid .g-start-xl-11{grid-column-start:11}}@media(min-width: 1400px){.grid .g-col-xxl-1{grid-column:auto/span 1}.grid .g-col-xxl-2{grid-column:auto/span 2}.grid .g-col-xxl-3{grid-column:auto/span 3}.grid .g-col-xxl-4{grid-column:auto/span 4}.grid .g-col-xxl-5{grid-column:auto/span 5}.grid .g-col-xxl-6{grid-column:auto/span 6}.grid .g-col-xxl-7{grid-column:auto/span 7}.grid .g-col-xxl-8{grid-column:auto/span 8}.grid .g-col-xxl-9{grid-column:auto/span 9}.grid .g-col-xxl-10{grid-column:auto/span 10}.grid .g-col-xxl-11{grid-column:auto/span 11}.grid .g-col-xxl-12{grid-column:auto/span 12}.grid .g-start-xxl-1{grid-column-start:1}.grid .g-start-xxl-2{grid-column-start:2}.grid .g-start-xxl-3{grid-column-start:3}.grid .g-start-xxl-4{grid-column-start:4}.grid .g-start-xxl-5{grid-column-start:5}.grid .g-start-xxl-6{grid-column-start:6}.grid .g-start-xxl-7{grid-column-start:7}.grid .g-start-xxl-8{grid-column-start:8}.grid .g-start-xxl-9{grid-column-start:9}.grid .g-start-xxl-10{grid-column-start:10}.grid .g-start-xxl-11{grid-column-start:11}}.table{--bs-table-color-type: initial;--bs-table-bg-type: initial;--bs-table-color-state: initial;--bs-table-bg-state: initial;--bs-table-color: #373a3c;--bs-table-bg: #fff;--bs-table-border-color: #e1e1e2;--bs-table-accent-bg: transparent;--bs-table-striped-color: #373a3c;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #373a3c;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #373a3c;--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(1px*2) solid #9b9d9e}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type: var(--bs-table-striped-color);--bs-table-bg-type: var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(even){--bs-table-color-type: var(--bs-table-striped-color);--bs-table-bg-type: var(--bs-table-striped-bg)}.table-active{--bs-table-color-state: var(--bs-table-active-color);--bs-table-bg-state: var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state: var(--bs-table-hover-color);--bs-table-bg-state: var(--bs-table-hover-bg)}.table-primary{--bs-table-color: #000;--bs-table-bg: #d4e6f9;--bs-table-border-color: #bfcfe0;--bs-table-striped-bg: #c9dbed;--bs-table-striped-color: #000;--bs-table-active-bg: #bfcfe0;--bs-table-active-color: #000;--bs-table-hover-bg: #c4d5e6;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color: #000;--bs-table-bg: #d7d8d8;--bs-table-border-color: #c2c2c2;--bs-table-striped-bg: #cccdcd;--bs-table-striped-color: #000;--bs-table-active-bg: #c2c2c2;--bs-table-active-color: #000;--bs-table-hover-bg: #c7c8c8;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color: #000;--bs-table-bg: #d9f0d1;--bs-table-border-color: #c3d8bc;--bs-table-striped-bg: #cee4c7;--bs-table-striped-color: #000;--bs-table-active-bg: #c3d8bc;--bs-table-active-color: #000;--bs-table-hover-bg: #c9dec1;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color: #000;--bs-table-bg: #ebddf1;--bs-table-border-color: #d4c7d9;--bs-table-striped-bg: #dfd2e5;--bs-table-striped-color: #000;--bs-table-active-bg: #d4c7d9;--bs-table-active-color: #000;--bs-table-hover-bg: #d9ccdf;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color: #000;--bs-table-bg: #ffe3d1;--bs-table-border-color: #e6ccbc;--bs-table-striped-bg: #f2d8c7;--bs-table-striped-color: #000;--bs-table-active-bg: #e6ccbc;--bs-table-active-color: #000;--bs-table-hover-bg: #ecd2c1;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color: #000;--bs-table-bg: #ffccd7;--bs-table-border-color: #e6b8c2;--bs-table-striped-bg: #f2c2cc;--bs-table-striped-color: #000;--bs-table-active-bg: #e6b8c2;--bs-table-active-color: #000;--bs-table-hover-bg: #ecbdc7;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color: #000;--bs-table-bg: #f8f9fa;--bs-table-border-color: #dfe0e1;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color: #fff;--bs-table-bg: #373a3c;--bs-table-border-color: #4b4e50;--bs-table-striped-bg: #414446;--bs-table-striped-color: #fff;--bs-table-active-bg: #4b4e50;--bs-table-active-color: #fff;--bs-table-hover-bg: #46494b;--bs-table-hover-color: #fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label,.shiny-input-container .control-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.875rem}.form-text{margin-top:.25rem;font-size:0.875em;color:rgba(55,58,60,.75)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#fff;background-clip:padding-box;border:1px solid #e1e1e2;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#373a3c;background-color:#fff;border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::placeholder{color:rgba(55,58,60,.75);opacity:1}.form-control:disabled{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#373a3c;background-color:#f8f9fa;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#e9ecef}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#373a3c;background-color:rgba(0,0,0,0);border:solid rgba(0,0,0,0);border-width:1px 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + calc(1px * 2));padding:.25rem .5rem;font-size:0.875rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(1px * 2));padding:.5rem 1rem;font-size:1.25rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + calc(1px * 2))}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + calc(1px * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(1px * 2))}.form-control-color{width:3rem;height:calc(1.5em + 0.75rem + calc(1px * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0 !important}.form-control-color::-webkit-color-swatch{border:0 !important}.form-control-color.form-control-sm{height:calc(1.5em + 0.5rem + calc(1px * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(1px * 2))}.form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23373a3c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#fff;background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon, none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #e1e1e2;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 #373a3c}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check,.shiny-input-container .checkbox,.shiny-input-container .radio{display:block;min-height:1.5rem;padding-left:0;margin-bottom:.125rem}.form-check .form-check-input,.form-check .shiny-input-container .checkbox input,.form-check .shiny-input-container .radio input,.shiny-input-container .checkbox .form-check-input,.shiny-input-container .checkbox .shiny-input-container .checkbox input,.shiny-input-container .checkbox .shiny-input-container .radio input,.shiny-input-container .radio .form-check-input,.shiny-input-container .radio .shiny-input-container .checkbox input,.shiny-input-container .radio .shiny-input-container .radio input{float:left;margin-left:0}.form-check-reverse{padding-right:0;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:0;margin-left:0}.form-check-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{--bs-form-check-bg: #fff;width:1em;height:1em;margin-top:.25em;vertical-align:top;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid #e1e1e2;print-color-adjust:exact}.form-check-input[type=radio],.shiny-input-container .checkbox input[type=radio],.shiny-input-container .checkbox-inline input[type=radio],.shiny-input-container .radio input[type=radio],.shiny-input-container .radio-inline input[type=radio]{border-radius:50%}.form-check-input:active,.shiny-input-container .checkbox input:active,.shiny-input-container .checkbox-inline input:active,.shiny-input-container .radio input:active,.shiny-input-container .radio-inline input:active{filter:brightness(90%)}.form-check-input:focus,.shiny-input-container .checkbox input:focus,.shiny-input-container .checkbox-inline input:focus,.shiny-input-container .radio input:focus,.shiny-input-container .radio-inline input:focus{border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-check-input:checked,.shiny-input-container .checkbox input:checked,.shiny-input-container .checkbox-inline input:checked,.shiny-input-container .radio input:checked,.shiny-input-container .radio-inline input:checked{background-color:#2780e3;border-color:#2780e3}.form-check-input:checked[type=checkbox],.shiny-input-container .checkbox input:checked[type=checkbox],.shiny-input-container .checkbox-inline input:checked[type=checkbox],.shiny-input-container .radio input:checked[type=checkbox],.shiny-input-container .radio-inline input:checked[type=checkbox]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio],.shiny-input-container .checkbox input:checked[type=radio],.shiny-input-container .checkbox-inline input:checked[type=radio],.shiny-input-container .radio input:checked[type=radio],.shiny-input-container .radio-inline input:checked[type=radio]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate,.shiny-input-container .checkbox input[type=checkbox]:indeterminate,.shiny-input-container .checkbox-inline input[type=checkbox]:indeterminate,.shiny-input-container .radio input[type=checkbox]:indeterminate,.shiny-input-container .radio-inline input[type=checkbox]:indeterminate{background-color:#2780e3;border-color:#2780e3;--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled,.shiny-input-container .checkbox input:disabled,.shiny-input-container .checkbox-inline input:disabled,.shiny-input-container .radio input:disabled,.shiny-input-container .radio-inline input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input[disabled]~span,.form-check-input:disabled~.form-check-label,.form-check-input:disabled~span,.shiny-input-container .checkbox input[disabled]~.form-check-label,.shiny-input-container .checkbox input[disabled]~span,.shiny-input-container .checkbox input:disabled~.form-check-label,.shiny-input-container .checkbox input:disabled~span,.shiny-input-container .checkbox-inline input[disabled]~.form-check-label,.shiny-input-container .checkbox-inline input[disabled]~span,.shiny-input-container .checkbox-inline input:disabled~.form-check-label,.shiny-input-container .checkbox-inline input:disabled~span,.shiny-input-container .radio input[disabled]~.form-check-label,.shiny-input-container .radio input[disabled]~span,.shiny-input-container .radio input:disabled~.form-check-label,.shiny-input-container .radio input:disabled~span,.shiny-input-container .radio-inline input[disabled]~.form-check-label,.shiny-input-container .radio-inline input[disabled]~span,.shiny-input-container .radio-inline input:disabled~.form-check-label,.shiny-input-container .radio-inline input:disabled~span{cursor:default;opacity:.5}.form-check-label,.shiny-input-container .checkbox label,.shiny-input-container .checkbox-inline label,.shiny-input-container .radio label,.shiny-input-container .radio-inline label{cursor:pointer}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2393c0f1'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:rgba(0,0,0,0)}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#2780e3;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#bed9f7}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#f8f9fa;border-color:rgba(0,0,0,0)}.form-range::-moz-range-thumb{width:1rem;height:1rem;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#2780e3;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#bed9f7}.form-range::-moz-range-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#f8f9fa;border-color:rgba(0,0,0,0)}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:rgba(55,58,60,.75)}.form-range:disabled::-moz-range-thumb{background-color:rgba(55,58,60,.75)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(1px * 2));min-height:calc(3.5rem + calc(1px * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control::placeholder,.form-floating>.form-control-plaintext::placeholder{color:rgba(0,0,0,0)}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill,.form-floating>.form-control-plaintext:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-control-plaintext~label,.form-floating>.form-select~label{color:rgba(var(--bs-body-color-rgb), 0.65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:focus~label::after,.form-floating>.form-control:not(:placeholder-shown)~label::after,.form-floating>.form-control-plaintext~label::after,.form-floating>.form-select~label::after{position:absolute;inset:1rem .375rem;z-index:-1;height:1.5em;content:"";background-color:#fff}.form-floating>.form-control:-webkit-autofill~label{color:rgba(var(--bs-body-color-rgb), 0.65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control-plaintext~label{border-width:1px 0}.form-floating>:disabled~label,.form-floating>.form-control:disabled~label{color:#868e96}.form-floating>:disabled~label::after,.form-floating>.form-control:disabled~label::after{background-color:#e9ecef}.input-group{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:stretch;-webkit-align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select,.input-group>.form-floating{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus,.input-group>.form-floating:focus-within{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;text-align:center;white-space:nowrap;background-color:#f8f9fa;border:1px solid #e1e1e2}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(1px*-1)}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#3fb618}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:#3fb618}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#3fb618;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233fb618' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#3fb618}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233fb618' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-control-color:valid,.form-control-color.is-valid{width:calc(3rem + calc(1.5em + 0.75rem))}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#3fb618}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#3fb618}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#3fb618}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):valid,.input-group>.form-control:not(:focus).is-valid,.was-validated .input-group>.form-select:not(:focus):valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.input-group>.form-floating:not(:focus-within).is-valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#ff0039}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:#ff0039}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#ff0039;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff0039'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff0039' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#ff0039}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff0039'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff0039' stroke='none'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-control-color:invalid,.form-control-color.is-invalid{width:calc(3rem + calc(1.5em + 0.75rem))}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#ff0039}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#ff0039}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#ff0039}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):invalid,.input-group>.form-control:not(:focus).is-invalid,.was-validated .input-group>.form-select:not(:focus):invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.input-group>.form-floating:not(:focus-within).is-invalid{z-index:4}.btn{--bs-btn-padding-x: 0.75rem;--bs-btn-padding-y: 0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight: 400;--bs-btn-line-height: 1.5;--bs-btn-color: #373a3c;--bs-btn-bg: transparent;--bs-btn-border-width: 1px;--bs-btn-border-color: transparent;--bs-btn-border-radius: 0.375rem;--bs-btn-hover-border-color: transparent;--bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity: 0.65;--bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,:not(.btn-check)+.btn:active,.btn:first-child:active,.btn.active,.btn.show{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,:not(.btn-check)+.btn:active:focus-visible,.btn:first-child:active:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-default{--bs-btn-color: #fff;--bs-btn-bg: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2f3133;--bs-btn-hover-border-color: #2c2e30;--bs-btn-focus-shadow-rgb: 85, 88, 89;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2c2e30;--bs-btn-active-border-color: #292c2d;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #373a3c;--bs-btn-disabled-border-color: #373a3c}.btn-primary{--bs-btn-color: #fff;--bs-btn-bg: #2780e3;--bs-btn-border-color: #2780e3;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #216dc1;--bs-btn-hover-border-color: #1f66b6;--bs-btn-focus-shadow-rgb: 71, 147, 231;--bs-btn-active-color: #fff;--bs-btn-active-bg: #1f66b6;--bs-btn-active-border-color: #1d60aa;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #2780e3;--bs-btn-disabled-border-color: #2780e3}.btn-secondary{--bs-btn-color: #fff;--bs-btn-bg: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2f3133;--bs-btn-hover-border-color: #2c2e30;--bs-btn-focus-shadow-rgb: 85, 88, 89;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2c2e30;--bs-btn-active-border-color: #292c2d;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #373a3c;--bs-btn-disabled-border-color: #373a3c}.btn-success{--bs-btn-color: #fff;--bs-btn-bg: #3fb618;--bs-btn-border-color: #3fb618;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #369b14;--bs-btn-hover-border-color: #329213;--bs-btn-focus-shadow-rgb: 92, 193, 59;--bs-btn-active-color: #fff;--bs-btn-active-bg: #329213;--bs-btn-active-border-color: #2f8912;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #3fb618;--bs-btn-disabled-border-color: #3fb618}.btn-info{--bs-btn-color: #fff;--bs-btn-bg: #9954bb;--bs-btn-border-color: #9954bb;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #82479f;--bs-btn-hover-border-color: #7a4396;--bs-btn-focus-shadow-rgb: 168, 110, 197;--bs-btn-active-color: #fff;--bs-btn-active-bg: #7a4396;--bs-btn-active-border-color: #733f8c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #9954bb;--bs-btn-disabled-border-color: #9954bb}.btn-warning{--bs-btn-color: #fff;--bs-btn-bg: #ff7518;--bs-btn-border-color: #ff7518;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #d96314;--bs-btn-hover-border-color: #cc5e13;--bs-btn-focus-shadow-rgb: 255, 138, 59;--bs-btn-active-color: #fff;--bs-btn-active-bg: #cc5e13;--bs-btn-active-border-color: #bf5812;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #ff7518;--bs-btn-disabled-border-color: #ff7518}.btn-danger{--bs-btn-color: #fff;--bs-btn-bg: #ff0039;--bs-btn-border-color: #ff0039;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #d90030;--bs-btn-hover-border-color: #cc002e;--bs-btn-focus-shadow-rgb: 255, 38, 87;--bs-btn-active-color: #fff;--bs-btn-active-bg: #cc002e;--bs-btn-active-border-color: #bf002b;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #ff0039;--bs-btn-disabled-border-color: #ff0039}.btn-light{--bs-btn-color: #000;--bs-btn-bg: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #d3d4d5;--bs-btn-hover-border-color: #c6c7c8;--bs-btn-focus-shadow-rgb: 211, 212, 213;--bs-btn-active-color: #000;--bs-btn-active-bg: #c6c7c8;--bs-btn-active-border-color: #babbbc;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #f8f9fa;--bs-btn-disabled-border-color: #f8f9fa}.btn-dark{--bs-btn-color: #fff;--bs-btn-bg: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #555859;--bs-btn-hover-border-color: #4b4e50;--bs-btn-focus-shadow-rgb: 85, 88, 89;--bs-btn-active-color: #fff;--bs-btn-active-bg: #5f6163;--bs-btn-active-border-color: #4b4e50;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #373a3c;--bs-btn-disabled-border-color: #373a3c}.btn-outline-default{--bs-btn-color: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #373a3c;--bs-btn-hover-border-color: #373a3c;--bs-btn-focus-shadow-rgb: 55, 58, 60;--bs-btn-active-color: #fff;--bs-btn-active-bg: #373a3c;--bs-btn-active-border-color: #373a3c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #373a3c;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #373a3c;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-primary{--bs-btn-color: #2780e3;--bs-btn-border-color: #2780e3;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #2780e3;--bs-btn-hover-border-color: #2780e3;--bs-btn-focus-shadow-rgb: 39, 128, 227;--bs-btn-active-color: #fff;--bs-btn-active-bg: #2780e3;--bs-btn-active-border-color: #2780e3;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #2780e3;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #2780e3;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-secondary{--bs-btn-color: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #373a3c;--bs-btn-hover-border-color: #373a3c;--bs-btn-focus-shadow-rgb: 55, 58, 60;--bs-btn-active-color: #fff;--bs-btn-active-bg: #373a3c;--bs-btn-active-border-color: #373a3c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #373a3c;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #373a3c;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-success{--bs-btn-color: #3fb618;--bs-btn-border-color: #3fb618;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #3fb618;--bs-btn-hover-border-color: #3fb618;--bs-btn-focus-shadow-rgb: 63, 182, 24;--bs-btn-active-color: #fff;--bs-btn-active-bg: #3fb618;--bs-btn-active-border-color: #3fb618;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #3fb618;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #3fb618;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-info{--bs-btn-color: #9954bb;--bs-btn-border-color: #9954bb;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #9954bb;--bs-btn-hover-border-color: #9954bb;--bs-btn-focus-shadow-rgb: 153, 84, 187;--bs-btn-active-color: #fff;--bs-btn-active-bg: #9954bb;--bs-btn-active-border-color: #9954bb;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #9954bb;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #9954bb;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-warning{--bs-btn-color: #ff7518;--bs-btn-border-color: #ff7518;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #ff7518;--bs-btn-hover-border-color: #ff7518;--bs-btn-focus-shadow-rgb: 255, 117, 24;--bs-btn-active-color: #fff;--bs-btn-active-bg: #ff7518;--bs-btn-active-border-color: #ff7518;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ff7518;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ff7518;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-danger{--bs-btn-color: #ff0039;--bs-btn-border-color: #ff0039;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #ff0039;--bs-btn-hover-border-color: #ff0039;--bs-btn-focus-shadow-rgb: 255, 0, 57;--bs-btn-active-color: #fff;--bs-btn-active-bg: #ff0039;--bs-btn-active-border-color: #ff0039;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ff0039;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ff0039;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-light{--bs-btn-color: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #f8f9fa;--bs-btn-hover-border-color: #f8f9fa;--bs-btn-focus-shadow-rgb: 248, 249, 250;--bs-btn-active-color: #000;--bs-btn-active-bg: #f8f9fa;--bs-btn-active-border-color: #f8f9fa;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #f8f9fa;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #f8f9fa;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-dark{--bs-btn-color: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #fff;--bs-btn-hover-bg: #373a3c;--bs-btn-hover-border-color: #373a3c;--bs-btn-focus-shadow-rgb: 55, 58, 60;--bs-btn-active-color: #fff;--bs-btn-active-bg: #373a3c;--bs-btn-active-border-color: #373a3c;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #373a3c;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #373a3c;--bs-btn-bg: transparent;--bs-gradient: none}.btn-link{--bs-btn-font-weight: 400;--bs-btn-color: #2761e3;--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: #1f4eb6;--bs-btn-hover-border-color: transparent;--bs-btn-active-color: #1f4eb6;--bs-btn-active-border-color: transparent;--bs-btn-disabled-color: #868e96;--bs-btn-disabled-border-color: transparent;--bs-btn-box-shadow: 0 0 0 #000;--bs-btn-focus-shadow-rgb: 71, 121, 231;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-lg,.btn-group-lg>.btn{--bs-btn-padding-y: 0.5rem;--bs-btn-padding-x: 1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius: 0.5rem}.btn-sm,.btn-group-sm>.btn{--bs-btn-padding-y: 0.25rem;--bs-btn-padding-x: 0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius: 0.25rem}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .2s ease}@media(prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart,.dropup-center,.dropdown-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid rgba(0,0,0,0);border-bottom:0;border-left:.3em solid rgba(0,0,0,0)}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex: 1000;--bs-dropdown-min-width: 10rem;--bs-dropdown-padding-x: 0;--bs-dropdown-padding-y: 0.5rem;--bs-dropdown-spacer: 0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color: #373a3c;--bs-dropdown-bg: #fff;--bs-dropdown-border-color: rgba(0, 0, 0, 0.175);--bs-dropdown-border-radius: 0.375rem;--bs-dropdown-border-width: 1px;--bs-dropdown-inner-border-radius: calc(0.375rem - 1px);--bs-dropdown-divider-bg: rgba(0, 0, 0, 0.175);--bs-dropdown-divider-margin-y: 0.5rem;--bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-dropdown-link-color: #373a3c;--bs-dropdown-link-hover-color: #373a3c;--bs-dropdown-link-hover-bg: #f8f9fa;--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #2780e3;--bs-dropdown-link-disabled-color: rgba(55, 58, 60, 0.5);--bs-dropdown-item-padding-x: 1rem;--bs-dropdown-item-padding-y: 0.25rem;--bs-dropdown-header-color: #868e96;--bs-dropdown-header-padding-x: 1rem;--bs-dropdown-header-padding-y: 0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid rgba(0,0,0,0);border-bottom:.3em solid;border-left:.3em solid rgba(0,0,0,0)}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:0;border-bottom:.3em solid rgba(0,0,0,0);border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:.3em solid;border-bottom:.3em solid rgba(0,0,0,0)}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap;background-color:rgba(0,0,0,0);border:0}.dropdown-item:hover,.dropdown-item:focus{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:rgba(0,0,0,0)}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:0.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color: #dee2e6;--bs-dropdown-bg: #373a3c;--bs-dropdown-border-color: rgba(0, 0, 0, 0.175);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color: #dee2e6;--bs-dropdown-link-hover-color: #fff;--bs-dropdown-divider-bg: rgba(0, 0, 0, 0.175);--bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color: #fff;--bs-dropdown-link-active-bg: #2780e3;--bs-dropdown-link-disabled-color: #adb5bd;--bs-dropdown-header-color: #adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;justify-content:flex-start;-webkit-justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(1px*-1)}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;-webkit-flex-direction:column;align-items:flex-start;-webkit-align-items:flex-start;justify-content:center;-webkit-justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:calc(1px*-1)}.nav{--bs-nav-link-padding-x: 1rem;--bs-nav-link-padding-y: 0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: #2761e3;--bs-nav-link-hover-color: #1f4eb6;--bs-nav-link-disabled-color: rgba(55, 58, 60, 0.75);display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background:none;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width: 1px;--bs-nav-tabs-border-color: #e1e1e2;--bs-nav-tabs-border-radius: 0.375rem;--bs-nav-tabs-link-hover-border-color: #e9ecef #e9ecef #e1e1e2;--bs-nav-tabs-link-active-color: #000;--bs-nav-tabs-link-active-bg: #fff;--bs-nav-tabs-link-active-border-color: #e1e1e2 #e1e1e2 #fff;border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1*var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid rgba(0,0,0,0)}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1*var(--bs-nav-tabs-border-width))}.nav-pills{--bs-nav-pills-border-radius: 0.375rem;--bs-nav-pills-link-active-color: #fff;--bs-nav-pills-link-active-bg: #2780e3}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap: 1rem;--bs-nav-underline-border-width: 0.125rem;--bs-nav-underline-link-active-color: #000;gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid rgba(0,0,0,0)}.nav-underline .nav-link:hover,.nav-underline .nav-link:focus{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;-webkit-flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;-webkit-flex-basis:0;flex-grow:1;-webkit-flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x: 0;--bs-navbar-padding-y: 0.5rem;--bs-navbar-color: #545555;--bs-navbar-hover-color: rgba(31, 78, 182, 0.8);--bs-navbar-disabled-color: rgba(84, 85, 85, 0.75);--bs-navbar-active-color: #1f4eb6;--bs-navbar-brand-padding-y: 0.3125rem;--bs-navbar-brand-margin-end: 1rem;--bs-navbar-brand-font-size: 1.25rem;--bs-navbar-brand-color: #545555;--bs-navbar-brand-hover-color: #1f4eb6;--bs-navbar-nav-link-padding-x: 0.5rem;--bs-navbar-toggler-padding-y: 0.25;--bs-navbar-toggler-padding-x: 0;--bs-navbar-toggler-font-size: 1.25rem;--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23545555' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color: rgba(84, 85, 85, 0);--bs-navbar-toggler-border-radius: 0.375rem;--bs-navbar-toggler-focus-width: 0.25rem;--bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out;position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-sm,.navbar>.container-md,.navbar>.container-lg,.navbar>.container-xl,.navbar>.container-xxl{display:flex;display:-webkit-flex;flex-wrap:inherit;-webkit-flex-wrap:inherit;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-brand:hover,.navbar-brand:focus{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x: 0;--bs-nav-link-padding-y: 0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: var(--bs-navbar-color);--bs-nav-link-hover-color: var(--bs-navbar-hover-color);--bs-nav-link-disabled-color: var(--bs-navbar-disabled-color);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:hover,.navbar-text a:focus{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;-webkit-flex-basis:100%;flex-grow:1;-webkit-flex-grow:1;align-items:center;-webkit-align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:rgba(0,0,0,0);border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);transition:var(--bs-navbar-toggler-transition)}@media(prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media(min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color: #545555;--bs-navbar-hover-color: rgba(31, 78, 182, 0.8);--bs-navbar-disabled-color: rgba(84, 85, 85, 0.75);--bs-navbar-active-color: #1f4eb6;--bs-navbar-brand-color: #545555;--bs-navbar-brand-hover-color: #1f4eb6;--bs-navbar-toggler-border-color: rgba(84, 85, 85, 0);--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23545555' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23545555' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y: 1rem;--bs-card-spacer-x: 1rem;--bs-card-title-spacer-y: 0.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width: 1px;--bs-card-border-color: rgba(0, 0, 0, 0.175);--bs-card-border-radius: 0.375rem;--bs-card-box-shadow: ;--bs-card-inner-border-radius: calc(0.375rem - 1px);--bs-card-cap-padding-y: 0.5rem;--bs-card-cap-padding-x: 1rem;--bs-card-cap-bg: rgba(55, 58, 60, 0.03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg: #fff;--bs-card-img-overlay-padding: 1rem;--bs-card-group-margin: 0.75rem;position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0}.card>.list-group:last-child{border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;-webkit-flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-0.5*var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header-tabs{margin-right:calc(-0.5*var(--bs-card-cap-padding-x));margin-bottom:calc(-1*var(--bs-card-cap-padding-y));margin-left:calc(-0.5*var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-0.5*var(--bs-card-cap-padding-x));margin-left:calc(-0.5*var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media(min-width: 576px){.card-group{display:flex;display:-webkit-flex;flex-flow:row wrap;-webkit-flex-flow:row wrap}.card-group>.card{flex:1 0 0%;-webkit-flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}}.accordion{--bs-accordion-color: #373a3c;--bs-accordion-bg: #fff;--bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease;--bs-accordion-border-color: #e1e1e2;--bs-accordion-border-width: 1px;--bs-accordion-border-radius: 0.375rem;--bs-accordion-inner-border-radius: calc(0.375rem - 1px);--bs-accordion-btn-padding-x: 1.25rem;--bs-accordion-btn-padding-y: 1rem;--bs-accordion-btn-color: #373a3c;--bs-accordion-btn-bg: #fff;--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23373a3c'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width: 1.25rem;--bs-accordion-btn-icon-transform: rotate(-180deg);--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2310335b'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-focus-border-color: #93c0f1;--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-accordion-body-padding-x: 1.25rem;--bs-accordion-body-padding-y: 1rem;--bs-accordion-active-color: #10335b;--bs-accordion-active-bg: #d4e6f9}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1*var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:var(--bs-accordion-btn-focus-border-color);outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:not(:first-of-type){border-top:0}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%237db3ee'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%237db3ee'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x: 0;--bs-breadcrumb-padding-y: 0;--bs-breadcrumb-margin-bottom: 1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color: rgba(55, 58, 60, 0.75);--bs-breadcrumb-item-padding-x: 0.5rem;--bs-breadcrumb-item-active-color: rgba(55, 58, 60, 0.75);display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, ">") /* rtl: var(--bs-breadcrumb-divider, ">") */}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x: 0.75rem;--bs-pagination-padding-y: 0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color: #2761e3;--bs-pagination-bg: #fff;--bs-pagination-border-width: 1px;--bs-pagination-border-color: #e1e1e2;--bs-pagination-border-radius: 0.375rem;--bs-pagination-hover-color: #1f4eb6;--bs-pagination-hover-bg: #f8f9fa;--bs-pagination-hover-border-color: #e1e1e2;--bs-pagination-focus-color: #1f4eb6;--bs-pagination-focus-bg: #e9ecef;--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-pagination-active-color: #fff;--bs-pagination-active-bg: #2780e3;--bs-pagination-active-border-color: #2780e3;--bs-pagination-disabled-color: rgba(55, 58, 60, 0.75);--bs-pagination-disabled-bg: #e9ecef;--bs-pagination-disabled-border-color: #e1e1e2;display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.page-link.active,.active>.page-link{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.page-link.disabled,.disabled>.page-link{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(1px*-1)}.pagination-lg{--bs-pagination-padding-x: 1.5rem;--bs-pagination-padding-y: 0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius: 0.5rem}.pagination-sm{--bs-pagination-padding-x: 0.5rem;--bs-pagination-padding-y: 0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius: 0.25rem}.badge{--bs-badge-padding-x: 0.65em;--bs-badge-padding-y: 0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight: 700;--bs-badge-color: #fff;--bs-badge-border-radius: 0.375rem;display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg: transparent;--bs-alert-padding-x: 1rem;--bs-alert-padding-y: 1rem;--bs-alert-margin-bottom: 1rem;--bs-alert-color: inherit;--bs-alert-border-color: transparent;--bs-alert-border: 0 solid var(--bs-alert-border-color);--bs-alert-border-radius: 0.375rem;--bs-alert-link-color: inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{--bs-alert-color: var(--bs-default-text-emphasis);--bs-alert-bg: var(--bs-default-bg-subtle);--bs-alert-border-color: var(--bs-default-border-subtle);--bs-alert-link-color: var(--bs-default-text-emphasis)}.alert-primary{--bs-alert-color: var(--bs-primary-text-emphasis);--bs-alert-bg: var(--bs-primary-bg-subtle);--bs-alert-border-color: var(--bs-primary-border-subtle);--bs-alert-link-color: var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color: var(--bs-secondary-text-emphasis);--bs-alert-bg: var(--bs-secondary-bg-subtle);--bs-alert-border-color: var(--bs-secondary-border-subtle);--bs-alert-link-color: var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color: var(--bs-success-text-emphasis);--bs-alert-bg: var(--bs-success-bg-subtle);--bs-alert-border-color: var(--bs-success-border-subtle);--bs-alert-link-color: var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color: var(--bs-info-text-emphasis);--bs-alert-bg: var(--bs-info-bg-subtle);--bs-alert-border-color: var(--bs-info-border-subtle);--bs-alert-link-color: var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color: var(--bs-warning-text-emphasis);--bs-alert-bg: var(--bs-warning-bg-subtle);--bs-alert-border-color: var(--bs-warning-border-subtle);--bs-alert-link-color: var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color: var(--bs-danger-text-emphasis);--bs-alert-bg: var(--bs-danger-bg-subtle);--bs-alert-border-color: var(--bs-danger-border-subtle);--bs-alert-link-color: var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color: var(--bs-light-text-emphasis);--bs-alert-bg: var(--bs-light-bg-subtle);--bs-alert-border-color: var(--bs-light-border-subtle);--bs-alert-link-color: var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color: var(--bs-dark-text-emphasis);--bs-alert-bg: var(--bs-dark-bg-subtle);--bs-alert-border-color: var(--bs-dark-border-subtle);--bs-alert-link-color: var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:.5rem}}.progress,.progress-stacked{--bs-progress-height: 0.5rem;--bs-progress-font-size:0.75rem;--bs-progress-bg: #e9ecef;--bs-progress-border-radius: 0.375rem;--bs-progress-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-progress-bar-color: #fff;--bs-progress-bar-bg: #2780e3;--bs-progress-bar-transition: width 0.6s ease;display:flex;display:-webkit-flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg)}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media(prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color: #373a3c;--bs-list-group-bg: #fff;--bs-list-group-border-color: #e1e1e2;--bs-list-group-border-width: 1px;--bs-list-group-border-radius: 0.375rem;--bs-list-group-item-padding-x: 1rem;--bs-list-group-item-padding-y: 0.5rem;--bs-list-group-action-color: rgba(55, 58, 60, 0.75);--bs-list-group-action-hover-color: #000;--bs-list-group-action-hover-bg: #f8f9fa;--bs-list-group-action-active-color: #373a3c;--bs-list-group-action-active-bg: #e9ecef;--bs-list-group-disabled-color: rgba(55, 58, 60, 0.75);--bs-list-group-disabled-bg: #fff;--bs-list-group-active-color: #fff;--bs-list-group-active-bg: #2780e3;--bs-list-group-active-border-color: #2780e3;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1*var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{--bs-list-group-color: var(--bs-default-text-emphasis);--bs-list-group-bg: var(--bs-default-bg-subtle);--bs-list-group-border-color: var(--bs-default-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-default-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-default-border-subtle);--bs-list-group-active-color: var(--bs-default-bg-subtle);--bs-list-group-active-bg: var(--bs-default-text-emphasis);--bs-list-group-active-border-color: var(--bs-default-text-emphasis)}.list-group-item-primary{--bs-list-group-color: var(--bs-primary-text-emphasis);--bs-list-group-bg: var(--bs-primary-bg-subtle);--bs-list-group-border-color: var(--bs-primary-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-primary-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-primary-border-subtle);--bs-list-group-active-color: var(--bs-primary-bg-subtle);--bs-list-group-active-bg: var(--bs-primary-text-emphasis);--bs-list-group-active-border-color: var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color: var(--bs-secondary-text-emphasis);--bs-list-group-bg: var(--bs-secondary-bg-subtle);--bs-list-group-border-color: var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-secondary-border-subtle);--bs-list-group-active-color: var(--bs-secondary-bg-subtle);--bs-list-group-active-bg: var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color: var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color: var(--bs-success-text-emphasis);--bs-list-group-bg: var(--bs-success-bg-subtle);--bs-list-group-border-color: var(--bs-success-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-success-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-success-border-subtle);--bs-list-group-active-color: var(--bs-success-bg-subtle);--bs-list-group-active-bg: var(--bs-success-text-emphasis);--bs-list-group-active-border-color: var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color: var(--bs-info-text-emphasis);--bs-list-group-bg: var(--bs-info-bg-subtle);--bs-list-group-border-color: var(--bs-info-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-info-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-info-border-subtle);--bs-list-group-active-color: var(--bs-info-bg-subtle);--bs-list-group-active-bg: var(--bs-info-text-emphasis);--bs-list-group-active-border-color: var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color: var(--bs-warning-text-emphasis);--bs-list-group-bg: var(--bs-warning-bg-subtle);--bs-list-group-border-color: var(--bs-warning-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-warning-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-warning-border-subtle);--bs-list-group-active-color: var(--bs-warning-bg-subtle);--bs-list-group-active-bg: var(--bs-warning-text-emphasis);--bs-list-group-active-border-color: var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color: var(--bs-danger-text-emphasis);--bs-list-group-bg: var(--bs-danger-bg-subtle);--bs-list-group-border-color: var(--bs-danger-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-danger-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-danger-border-subtle);--bs-list-group-active-color: var(--bs-danger-bg-subtle);--bs-list-group-active-bg: var(--bs-danger-text-emphasis);--bs-list-group-active-border-color: var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color: var(--bs-light-text-emphasis);--bs-list-group-bg: var(--bs-light-bg-subtle);--bs-list-group-border-color: var(--bs-light-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-light-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-light-border-subtle);--bs-list-group-active-color: var(--bs-light-bg-subtle);--bs-list-group-active-bg: var(--bs-light-text-emphasis);--bs-list-group-active-border-color: var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color: var(--bs-dark-text-emphasis);--bs-list-group-bg: var(--bs-dark-bg-subtle);--bs-list-group-border-color: var(--bs-dark-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-dark-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-dark-border-subtle);--bs-list-group-active-color: var(--bs-dark-bg-subtle);--bs-list-group-active-bg: var(--bs-dark-text-emphasis);--bs-list-group-active-border-color: var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color: #000;--bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");--bs-btn-close-opacity: 0.5;--bs-btn-close-hover-opacity: 0.75;--bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(39, 128, 227, 0.25);--bs-btn-close-focus-opacity: 1;--bs-btn-close-disabled-opacity: 0.25;--bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%);box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:rgba(0,0,0,0) var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{filter:var(--bs-btn-close-white-filter)}[data-bs-theme=dark] .btn-close{filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex: 1090;--bs-toast-padding-x: 0.75rem;--bs-toast-padding-y: 0.5rem;--bs-toast-spacing: 1.5rem;--bs-toast-max-width: 350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg: rgba(255, 255, 255, 0.85);--bs-toast-border-width: 1px;--bs-toast-border-color: rgba(0, 0, 0, 0.175);--bs-toast-border-radius: 0.375rem;--bs-toast-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-toast-header-color: rgba(55, 58, 60, 0.75);--bs-toast-header-bg: rgba(255, 255, 255, 0.85);--bs-toast-header-border-color: rgba(0, 0, 0, 0.175);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex: 1090;position:absolute;z-index:var(--bs-toast-zindex);width:max-content;width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:-o-max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color)}.toast-header .btn-close{margin-right:calc(-0.5*var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex: 1055;--bs-modal-width: 500px;--bs-modal-padding: 1rem;--bs-modal-margin: 0.5rem;--bs-modal-color: ;--bs-modal-bg: #fff;--bs-modal-border-color: rgba(0, 0, 0, 0.175);--bs-modal-border-width: 1px;--bs-modal-border-radius: 0.5rem;--bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-modal-inner-border-radius: calc(0.5rem - 1px);--bs-modal-header-padding-x: 1rem;--bs-modal-header-padding-y: 1rem;--bs-modal-header-padding: 1rem 1rem;--bs-modal-header-border-color: #e1e1e2;--bs-modal-header-border-width: 1px;--bs-modal-title-line-height: 1.5;--bs-modal-footer-gap: 0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color: #e1e1e2;--bs-modal-footer-border-width: 1px;position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media(prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin)*2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;min-height:calc(100% - var(--bs-modal-margin)*2)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);outline:0}.modal-backdrop{--bs-backdrop-zindex: 1050;--bs-backdrop-bg: #000;--bs-backdrop-opacity: 0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y)*.5) calc(var(--bs-modal-header-padding-x)*.5);margin:calc(-0.5*var(--bs-modal-header-padding-y)) calc(-0.5*var(--bs-modal-header-padding-x)) calc(-0.5*var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap)*.5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap)*.5)}@media(min-width: 576px){.modal{--bs-modal-margin: 1.75rem;--bs-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width: 300px}}@media(min-width: 992px){.modal-lg,.modal-xl{--bs-modal-width: 800px}}@media(min-width: 1200px){.modal-xl{--bs-modal-width: 1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0}.modal-fullscreen .modal-body{overflow-y:auto}@media(max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media(max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media(max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media(max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media(max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex: 1080;--bs-tooltip-max-width: 200px;--bs-tooltip-padding-x: 0.5rem;--bs-tooltip-padding-y: 0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color: #fff;--bs-tooltip-bg: #000;--bs-tooltip-border-radius: 0.375rem;--bs-tooltip-opacity: 0.9;--bs-tooltip-arrow-width: 0.8rem;--bs-tooltip-arrow-height: 0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:calc(-1*var(--bs-tooltip-arrow-height))}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width)*.5) 0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:calc(-1*var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width)*.5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width)*.5) 0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:calc(-1*var(--bs-tooltip-arrow-height))}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width)*.5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:calc(-1*var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width)*.5) 0 calc(var(--bs-tooltip-arrow-width)*.5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg)}.popover{--bs-popover-zindex: 1070;--bs-popover-max-width: 276px;--bs-popover-font-size:0.875rem;--bs-popover-bg: #fff;--bs-popover-border-width: 1px;--bs-popover-border-color: rgba(0, 0, 0, 0.175);--bs-popover-border-radius: 0.5rem;--bs-popover-inner-border-radius: calc(0.5rem - 1px);--bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-popover-header-padding-x: 1rem;--bs-popover-header-padding-y: 0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color: inherit;--bs-popover-header-bg: #e9ecef;--bs-popover-body-padding-x: 1rem;--bs-popover-body-padding-y: 1rem;--bs-popover-body-color: #373a3c;--bs-popover-arrow-width: 1rem;--bs-popover-arrow-height: 0.5rem;--bs-popover-arrow-border: var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:rgba(0,0,0,0);border-style:solid;border-width:0}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width)*.5) 0}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{border-width:calc(var(--bs-popover-arrow-width)*.5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width)*.5) 0}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{border-width:0 calc(var(--bs-popover-arrow-width)*.5) var(--bs-popover-arrow-height)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-0.5*var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{border-width:calc(var(--bs-popover-arrow-width)*.5) 0 calc(var(--bs-popover-arrow-width)*.5) var(--bs-popover-arrow-height)}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y;-webkit-touch-action:pan-y;-moz-touch-action:pan-y;-ms-touch-action:pan-y;-o-touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:center;-webkit-justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;-webkit-flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid rgba(0,0,0,0);border-bottom:10px solid rgba(0,0,0,0);opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme=dark] .carousel .carousel-control-prev-icon,[data-bs-theme=dark] .carousel .carousel-control-next-icon,[data-bs-theme=dark].carousel .carousel-control-prev-icon,[data-bs-theme=dark].carousel .carousel-control-next-icon{filter:invert(1) grayscale(100)}[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target],[data-bs-theme=dark].carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme=dark] .carousel .carousel-caption,[data-bs-theme=dark].carousel .carousel-caption{color:#000}.spinner-grow,.spinner-border{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{--bs-spinner-width: 2rem;--bs-spinner-height: 2rem;--bs-spinner-vertical-align: -0.125em;--bs-spinner-border-width: 0.25em;--bs-spinner-animation-speed: 0.75s;--bs-spinner-animation-name: spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:rgba(0,0,0,0)}.spinner-border-sm{--bs-spinner-width: 1rem;--bs-spinner-height: 1rem;--bs-spinner-border-width: 0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width: 2rem;--bs-spinner-height: 2rem;--bs-spinner-vertical-align: -0.125em;--bs-spinner-animation-speed: 0.75s;--bs-spinner-animation-name: spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width: 1rem;--bs-spinner-height: 1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed: 1.5s}}.offcanvas,.offcanvas-xxl,.offcanvas-xl,.offcanvas-lg,.offcanvas-md,.offcanvas-sm{--bs-offcanvas-zindex: 1045;--bs-offcanvas-width: 400px;--bs-offcanvas-height: 30vh;--bs-offcanvas-padding-x: 1rem;--bs-offcanvas-padding-y: 1rem;--bs-offcanvas-color: #373a3c;--bs-offcanvas-bg: #fff;--bs-offcanvas-border-width: 1px;--bs-offcanvas-border-color: rgba(0, 0, 0, 0.175);--bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-offcanvas-transition: transform 0.3s ease-in-out;--bs-offcanvas-title-line-height: 1.5}@media(max-width: 575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 575.98px)and (prefers-reduced-motion: reduce){.offcanvas-sm{transition:none}}@media(max-width: 575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.showing,.offcanvas-sm.show:not(.hiding){transform:none}.offcanvas-sm.showing,.offcanvas-sm.hiding,.offcanvas-sm.show{visibility:visible}}@media(min-width: 576px){.offcanvas-sm{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 767.98px)and (prefers-reduced-motion: reduce){.offcanvas-md{transition:none}}@media(max-width: 767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.showing,.offcanvas-md.show:not(.hiding){transform:none}.offcanvas-md.showing,.offcanvas-md.hiding,.offcanvas-md.show{visibility:visible}}@media(min-width: 768px){.offcanvas-md{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 991.98px)and (prefers-reduced-motion: reduce){.offcanvas-lg{transition:none}}@media(max-width: 991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.showing,.offcanvas-lg.show:not(.hiding){transform:none}.offcanvas-lg.showing,.offcanvas-lg.hiding,.offcanvas-lg.show{visibility:visible}}@media(min-width: 992px){.offcanvas-lg{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 1199.98px)and (prefers-reduced-motion: reduce){.offcanvas-xl{transition:none}}@media(max-width: 1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.showing,.offcanvas-xl.show:not(.hiding){transform:none}.offcanvas-xl.showing,.offcanvas-xl.hiding,.offcanvas-xl.show{visibility:visible}}@media(min-width: 1200px){.offcanvas-xl{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 1399.98px)and (prefers-reduced-motion: reduce){.offcanvas-xxl{transition:none}}@media(max-width: 1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.showing,.offcanvas-xxl.show:not(.hiding){transform:none}.offcanvas-xxl.showing,.offcanvas-xxl.hiding,.offcanvas-xxl.show{visibility:visible}}@media(min-width: 1400px){.offcanvas-xxl{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.showing,.offcanvas.show:not(.hiding){transform:none}.offcanvas.showing,.offcanvas.hiding,.offcanvas.show{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y)*.5) calc(var(--bs-offcanvas-padding-x)*.5);margin-top:calc(-0.5*var(--bs-offcanvas-padding-y));margin-right:calc(-0.5*var(--bs-offcanvas-padding-x));margin-bottom:calc(-0.5*var(--bs-offcanvas-padding-y))}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);-webkit-mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);mask-size:200% 100%;-webkit-mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{mask-position:-200% 0%;-webkit-mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-default{color:#fff !important;background-color:RGBA(var(--bs-default-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-primary{color:#fff !important;background-color:RGBA(var(--bs-primary-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-secondary{color:#fff !important;background-color:RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-success{color:#fff !important;background-color:RGBA(var(--bs-success-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-info{color:#fff !important;background-color:RGBA(var(--bs-info-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-warning{color:#fff !important;background-color:RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-danger{color:#fff !important;background-color:RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-light{color:#000 !important;background-color:RGBA(var(--bs-light-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-dark{color:#fff !important;background-color:RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity, 1)) !important}.link-default{color:RGBA(var(--bs-default-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-default-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-default:hover,.link-default:focus{color:RGBA(44, 46, 48, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(44, 46, 48, var(--bs-link-underline-opacity, 1)) !important}.link-primary{color:RGBA(var(--bs-primary-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-primary:hover,.link-primary:focus{color:RGBA(31, 102, 182, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(31, 102, 182, var(--bs-link-underline-opacity, 1)) !important}.link-secondary{color:RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-secondary:hover,.link-secondary:focus{color:RGBA(44, 46, 48, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(44, 46, 48, var(--bs-link-underline-opacity, 1)) !important}.link-success{color:RGBA(var(--bs-success-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-success:hover,.link-success:focus{color:RGBA(50, 146, 19, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(50, 146, 19, var(--bs-link-underline-opacity, 1)) !important}.link-info{color:RGBA(var(--bs-info-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-info:hover,.link-info:focus{color:RGBA(122, 67, 150, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(122, 67, 150, var(--bs-link-underline-opacity, 1)) !important}.link-warning{color:RGBA(var(--bs-warning-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-warning:hover,.link-warning:focus{color:RGBA(204, 94, 19, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(204, 94, 19, var(--bs-link-underline-opacity, 1)) !important}.link-danger{color:RGBA(var(--bs-danger-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-danger:hover,.link-danger:focus{color:RGBA(204, 0, 46, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(204, 0, 46, var(--bs-link-underline-opacity, 1)) !important}.link-light{color:RGBA(var(--bs-light-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-light:hover,.link-light:focus{color:RGBA(249, 250, 251, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(249, 250, 251, var(--bs-link-underline-opacity, 1)) !important}.link-dark{color:RGBA(var(--bs-dark-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-dark:hover,.link-dark:focus{color:RGBA(44, 46, 48, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(44, 46, 48, var(--bs-link-underline-opacity, 1)) !important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-body-emphasis:hover,.link-body-emphasis:focus{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 0.75)) !important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-align-items:center;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5));text-underline-offset:.25em;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;-webkit-flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media(prefers-reduced-motion: reduce){.icon-link>.bi{transition:none}}.icon-link-hover:hover>.bi,.icon-link-hover:focus-visible>.bi{transform:var(--bs-icon-link-transform, translate3d(0.25em, 0, 0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: 75%}.ratio-16x9{--bs-aspect-ratio: 56.25%}.ratio-21x9{--bs-aspect-ratio: 42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}.sticky-bottom{position:sticky;bottom:0;z-index:1020}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;align-items:center;-webkit-align-items:center;align-self:stretch;-webkit-align-self:stretch}.vstack{display:flex;display:-webkit-flex;flex:1 1 auto;-webkit-flex:1 1 auto;flex-direction:column;-webkit-flex-direction:column;align-self:stretch;-webkit-align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.visually-hidden:not(caption),.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption){position:absolute !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;-webkit-align-self:stretch;width:1px;min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.object-fit-contain{object-fit:contain !important}.object-fit-cover{object-fit:cover !important}.object-fit-fill{object-fit:fill !important}.object-fit-scale{object-fit:scale-down !important}.object-fit-none{object-fit:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.overflow-x-auto{overflow-x:auto !important}.overflow-x-hidden{overflow-x:hidden !important}.overflow-x-visible{overflow-x:visible !important}.overflow-x-scroll{overflow-x:scroll !important}.overflow-y-auto{overflow-y:auto !important}.overflow-y-hidden{overflow-y:hidden !important}.overflow-y-visible{overflow-y:visible !important}.overflow-y-scroll{overflow-y:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-inline-grid{display:inline-grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.focus-ring-default{--bs-focus-ring-color: rgba(var(--bs-default-rgb), var(--bs-focus-ring-opacity))}.focus-ring-primary{--bs-focus-ring-color: rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color: rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color: rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color: rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color: rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color: rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color: rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color: rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-0{border:0 !important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-top-0{border-top:0 !important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-start-0{border-left:0 !important}.border-default{--bs-border-opacity: 1;border-color:rgba(var(--bs-default-rgb), var(--bs-border-opacity)) !important}.border-primary{--bs-border-opacity: 1;border-color:rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important}.border-secondary{--bs-border-opacity: 1;border-color:rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important}.border-success{--bs-border-opacity: 1;border-color:rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important}.border-info{--bs-border-opacity: 1;border-color:rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important}.border-warning{--bs-border-opacity: 1;border-color:rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important}.border-danger{--bs-border-opacity: 1;border-color:rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important}.border-light{--bs-border-opacity: 1;border-color:rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important}.border-dark{--bs-border-opacity: 1;border-color:rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important}.border-black{--bs-border-opacity: 1;border-color:rgba(var(--bs-black-rgb), var(--bs-border-opacity)) !important}.border-white{--bs-border-opacity: 1;border-color:rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle) !important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle) !important}.border-success-subtle{border-color:var(--bs-success-border-subtle) !important}.border-info-subtle{border-color:var(--bs-info-border-subtle) !important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle) !important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle) !important}.border-light-subtle{border-color:var(--bs-light-border-subtle) !important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle) !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.border-opacity-10{--bs-border-opacity: 0.1}.border-opacity-25{--bs-border-opacity: 0.25}.border-opacity-50{--bs-border-opacity: 0.5}.border-opacity-75{--bs-border-opacity: 0.75}.border-opacity-100{--bs-border-opacity: 1}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.row-gap-0{row-gap:0 !important}.row-gap-1{row-gap:.25rem !important}.row-gap-2{row-gap:.5rem !important}.row-gap-3{row-gap:1rem !important}.row-gap-4{row-gap:1.5rem !important}.row-gap-5{row-gap:3rem !important}.column-gap-0{column-gap:0 !important}.column-gap-1{column-gap:.25rem !important}.column-gap-2{column-gap:.5rem !important}.column-gap-3{column-gap:1rem !important}.column-gap-4{column-gap:1.5rem !important}.column-gap-5{column-gap:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.325rem + 0.9vw) !important}.fs-2{font-size:calc(1.29rem + 0.48vw) !important}.fs-3{font-size:calc(1.27rem + 0.24vw) !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1.1rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-lighter{font-weight:lighter !important}.fw-light{font-weight:300 !important}.fw-normal{font-weight:400 !important}.fw-medium{font-weight:500 !important}.fw-semibold{font-weight:600 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-default{--bs-text-opacity: 1;color:rgba(var(--bs-default-rgb), var(--bs-text-opacity)) !important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:var(--bs-secondary-color) !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,.5) !important}.text-body-secondary{--bs-text-opacity: 1;color:var(--bs-secondary-color) !important}.text-body-tertiary{--bs-text-opacity: 1;color:var(--bs-tertiary-color) !important}.text-body-emphasis{--bs-text-opacity: 1;color:var(--bs-emphasis-color) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: 0.25}.text-opacity-50{--bs-text-opacity: 0.5}.text-opacity-75{--bs-text-opacity: 0.75}.text-opacity-100{--bs-text-opacity: 1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis) !important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis) !important}.text-success-emphasis{color:var(--bs-success-text-emphasis) !important}.text-info-emphasis{color:var(--bs-info-text-emphasis) !important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis) !important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis) !important}.text-light-emphasis{color:var(--bs-light-text-emphasis) !important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis) !important}.link-opacity-10{--bs-link-opacity: 0.1}.link-opacity-10-hover:hover{--bs-link-opacity: 0.1}.link-opacity-25{--bs-link-opacity: 0.25}.link-opacity-25-hover:hover{--bs-link-opacity: 0.25}.link-opacity-50{--bs-link-opacity: 0.5}.link-opacity-50-hover:hover{--bs-link-opacity: 0.5}.link-opacity-75{--bs-link-opacity: 0.75}.link-opacity-75-hover:hover{--bs-link-opacity: 0.75}.link-opacity-100{--bs-link-opacity: 1}.link-opacity-100-hover:hover{--bs-link-opacity: 1}.link-offset-1{text-underline-offset:.125em !important}.link-offset-1-hover:hover{text-underline-offset:.125em !important}.link-offset-2{text-underline-offset:.25em !important}.link-offset-2-hover:hover{text-underline-offset:.25em !important}.link-offset-3{text-underline-offset:.375em !important}.link-offset-3-hover:hover{text-underline-offset:.375em !important}.link-underline-default{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-default-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-primary{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-secondary{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-success{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-info{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-warning{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-danger{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-light{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-dark{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important}.link-underline{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-underline-opacity-0{--bs-link-underline-opacity: 0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity: 0}.link-underline-opacity-10{--bs-link-underline-opacity: 0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity: 0.1}.link-underline-opacity-25{--bs-link-underline-opacity: 0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity: 0.25}.link-underline-opacity-50{--bs-link-underline-opacity: 0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity: 0.5}.link-underline-opacity-75{--bs-link-underline-opacity: 0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity: 0.75}.link-underline-opacity-100{--bs-link-underline-opacity: 1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity: 1}.bg-default{--bs-bg-opacity: 1;background-color:rgba(var(--bs-default-rgb), var(--bs-bg-opacity)) !important}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:rgba(0,0,0,0) !important}.bg-body-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity)) !important}.bg-body-tertiary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity)) !important}.bg-opacity-10{--bs-bg-opacity: 0.1}.bg-opacity-25{--bs-bg-opacity: 0.25}.bg-opacity-50{--bs-bg-opacity: 0.5}.bg-opacity-75{--bs-bg-opacity: 0.75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle) !important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle) !important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle) !important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle) !important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle) !important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle) !important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle) !important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle) !important}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:var(--bs-border-radius) !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:var(--bs-border-radius-sm) !important}.rounded-2{border-radius:var(--bs-border-radius) !important}.rounded-3{border-radius:var(--bs-border-radius-lg) !important}.rounded-4{border-radius:var(--bs-border-radius-xl) !important}.rounded-5{border-radius:var(--bs-border-radius-xxl) !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:var(--bs-border-radius-pill) !important}.rounded-top{border-top-left-radius:var(--bs-border-radius) !important;border-top-right-radius:var(--bs-border-radius) !important}.rounded-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm) !important;border-top-right-radius:var(--bs-border-radius-sm) !important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius) !important;border-top-right-radius:var(--bs-border-radius) !important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg) !important;border-top-right-radius:var(--bs-border-radius-lg) !important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl) !important;border-top-right-radius:var(--bs-border-radius-xl) !important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl) !important;border-top-right-radius:var(--bs-border-radius-xxl) !important}.rounded-top-circle{border-top-left-radius:50% !important;border-top-right-radius:50% !important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill) !important;border-top-right-radius:var(--bs-border-radius-pill) !important}.rounded-end{border-top-right-radius:var(--bs-border-radius) !important;border-bottom-right-radius:var(--bs-border-radius) !important}.rounded-end-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm) !important;border-bottom-right-radius:var(--bs-border-radius-sm) !important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius) !important;border-bottom-right-radius:var(--bs-border-radius) !important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg) !important;border-bottom-right-radius:var(--bs-border-radius-lg) !important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl) !important;border-bottom-right-radius:var(--bs-border-radius-xl) !important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl) !important;border-bottom-right-radius:var(--bs-border-radius-xxl) !important}.rounded-end-circle{border-top-right-radius:50% !important;border-bottom-right-radius:50% !important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill) !important;border-bottom-right-radius:var(--bs-border-radius-pill) !important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius) !important;border-bottom-left-radius:var(--bs-border-radius) !important}.rounded-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm) !important;border-bottom-left-radius:var(--bs-border-radius-sm) !important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius) !important;border-bottom-left-radius:var(--bs-border-radius) !important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg) !important;border-bottom-left-radius:var(--bs-border-radius-lg) !important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl) !important;border-bottom-left-radius:var(--bs-border-radius-xl) !important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl) !important;border-bottom-left-radius:var(--bs-border-radius-xxl) !important}.rounded-bottom-circle{border-bottom-right-radius:50% !important;border-bottom-left-radius:50% !important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill) !important;border-bottom-left-radius:var(--bs-border-radius-pill) !important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius) !important;border-top-left-radius:var(--bs-border-radius) !important}.rounded-start-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm) !important;border-top-left-radius:var(--bs-border-radius-sm) !important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius) !important;border-top-left-radius:var(--bs-border-radius) !important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg) !important;border-top-left-radius:var(--bs-border-radius-lg) !important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl) !important;border-top-left-radius:var(--bs-border-radius-xl) !important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl) !important;border-top-left-radius:var(--bs-border-radius-xxl) !important}.rounded-start-circle{border-bottom-left-radius:50% !important;border-top-left-radius:50% !important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill) !important;border-top-left-radius:var(--bs-border-radius-pill) !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}.z-n1{z-index:-1 !important}.z-0{z-index:0 !important}.z-1{z-index:1 !important}.z-2{z-index:2 !important}.z-3{z-index:3 !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.object-fit-sm-contain{object-fit:contain !important}.object-fit-sm-cover{object-fit:cover !important}.object-fit-sm-fill{object-fit:fill !important}.object-fit-sm-scale{object-fit:scale-down !important}.object-fit-sm-none{object-fit:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-inline-grid{display:inline-grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.row-gap-sm-0{row-gap:0 !important}.row-gap-sm-1{row-gap:.25rem !important}.row-gap-sm-2{row-gap:.5rem !important}.row-gap-sm-3{row-gap:1rem !important}.row-gap-sm-4{row-gap:1.5rem !important}.row-gap-sm-5{row-gap:3rem !important}.column-gap-sm-0{column-gap:0 !important}.column-gap-sm-1{column-gap:.25rem !important}.column-gap-sm-2{column-gap:.5rem !important}.column-gap-sm-3{column-gap:1rem !important}.column-gap-sm-4{column-gap:1.5rem !important}.column-gap-sm-5{column-gap:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.object-fit-md-contain{object-fit:contain !important}.object-fit-md-cover{object-fit:cover !important}.object-fit-md-fill{object-fit:fill !important}.object-fit-md-scale{object-fit:scale-down !important}.object-fit-md-none{object-fit:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-inline-grid{display:inline-grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.row-gap-md-0{row-gap:0 !important}.row-gap-md-1{row-gap:.25rem !important}.row-gap-md-2{row-gap:.5rem !important}.row-gap-md-3{row-gap:1rem !important}.row-gap-md-4{row-gap:1.5rem !important}.row-gap-md-5{row-gap:3rem !important}.column-gap-md-0{column-gap:0 !important}.column-gap-md-1{column-gap:.25rem !important}.column-gap-md-2{column-gap:.5rem !important}.column-gap-md-3{column-gap:1rem !important}.column-gap-md-4{column-gap:1.5rem !important}.column-gap-md-5{column-gap:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media(min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.object-fit-lg-contain{object-fit:contain !important}.object-fit-lg-cover{object-fit:cover !important}.object-fit-lg-fill{object-fit:fill !important}.object-fit-lg-scale{object-fit:scale-down !important}.object-fit-lg-none{object-fit:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-inline-grid{display:inline-grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.row-gap-lg-0{row-gap:0 !important}.row-gap-lg-1{row-gap:.25rem !important}.row-gap-lg-2{row-gap:.5rem !important}.row-gap-lg-3{row-gap:1rem !important}.row-gap-lg-4{row-gap:1.5rem !important}.row-gap-lg-5{row-gap:3rem !important}.column-gap-lg-0{column-gap:0 !important}.column-gap-lg-1{column-gap:.25rem !important}.column-gap-lg-2{column-gap:.5rem !important}.column-gap-lg-3{column-gap:1rem !important}.column-gap-lg-4{column-gap:1.5rem !important}.column-gap-lg-5{column-gap:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media(min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.object-fit-xl-contain{object-fit:contain !important}.object-fit-xl-cover{object-fit:cover !important}.object-fit-xl-fill{object-fit:fill !important}.object-fit-xl-scale{object-fit:scale-down !important}.object-fit-xl-none{object-fit:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-inline-grid{display:inline-grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.row-gap-xl-0{row-gap:0 !important}.row-gap-xl-1{row-gap:.25rem !important}.row-gap-xl-2{row-gap:.5rem !important}.row-gap-xl-3{row-gap:1rem !important}.row-gap-xl-4{row-gap:1.5rem !important}.row-gap-xl-5{row-gap:3rem !important}.column-gap-xl-0{column-gap:0 !important}.column-gap-xl-1{column-gap:.25rem !important}.column-gap-xl-2{column-gap:.5rem !important}.column-gap-xl-3{column-gap:1rem !important}.column-gap-xl-4{column-gap:1.5rem !important}.column-gap-xl-5{column-gap:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media(min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.object-fit-xxl-contain{object-fit:contain !important}.object-fit-xxl-cover{object-fit:cover !important}.object-fit-xxl-fill{object-fit:fill !important}.object-fit-xxl-scale{object-fit:scale-down !important}.object-fit-xxl-none{object-fit:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-inline-grid{display:inline-grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.row-gap-xxl-0{row-gap:0 !important}.row-gap-xxl-1{row-gap:.25rem !important}.row-gap-xxl-2{row-gap:.5rem !important}.row-gap-xxl-3{row-gap:1rem !important}.row-gap-xxl-4{row-gap:1.5rem !important}.row-gap-xxl-5{row-gap:3rem !important}.column-gap-xxl-0{column-gap:0 !important}.column-gap-xxl-1{column-gap:.25rem !important}.column-gap-xxl-2{column-gap:.5rem !important}.column-gap-xxl-3{column-gap:1rem !important}.column-gap-xxl-4{column-gap:1.5rem !important}.column-gap-xxl-5{column-gap:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#fff}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#fff}.bg-warning{color:#fff}.bg-danger{color:#fff}.bg-light{color:#000}.bg-dark{color:#fff}@media(min-width: 1200px){.fs-1{font-size:2rem !important}.fs-2{font-size:1.65rem !important}.fs-3{font-size:1.45rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-inline-grid{display:inline-grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}:root{--bslib-spacer: 1rem;--bslib-mb-spacer: var(--bslib-spacer, 1rem)}.bslib-mb-spacing{margin-bottom:var(--bslib-mb-spacer)}.bslib-gap-spacing{gap:var(--bslib-mb-spacer)}.bslib-gap-spacing>.bslib-mb-spacing,.bslib-gap-spacing>.form-group,.bslib-gap-spacing>p,.bslib-gap-spacing>pre{margin-bottom:0}.html-fill-container>.html-fill-item.bslib-mb-spacing{margin-bottom:0}.tab-content>.tab-pane.html-fill-container{display:none}.tab-content>.active.html-fill-container{display:flex}.tab-content.html-fill-container{padding:0}.bg-blue{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #2780e3;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-indigo{--bslib-color-fg: #6610f2;color:var(--bslib-color-fg)}.bg-purple{--bslib-color-bg: #613d7c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-purple{--bslib-color-fg: #613d7c;color:var(--bslib-color-fg)}.bg-pink{--bslib-color-bg: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #ff0039;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #f0ad4e;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-orange{--bslib-color-fg: #f0ad4e;color:var(--bslib-color-fg)}.bg-yellow{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #ff7518;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #3fb618;color:var(--bslib-color-fg)}.bg-teal{--bslib-color-bg: #20c997;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-teal{--bslib-color-fg: #20c997;color:var(--bslib-color-fg)}.bg-cyan{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #9954bb;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #373a3c}.bg-default{--bslib-color-bg: #373a3c;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #2780e3}.bg-primary{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #373a3c}.bg-secondary{--bslib-color-bg: #373a3c;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #3fb618}.bg-success{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #9954bb}.bg-info{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #ff7518}.bg-warning{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #ff0039}.bg-danger{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #f8f9fa}.bg-light{--bslib-color-bg: #f8f9fa;--bslib-color-fg: #000}.text-dark{--bslib-color-fg: #373a3c}.bg-dark{--bslib-color-bg: #373a3c;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4053e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4053e9;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3e65ba;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3e65ba;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7466c0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7466c0;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d4d9f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #7d4d9f;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #7792a7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #7792a7;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #7d7c92;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #7d7c92;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #319692;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #319692;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #249dc5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #249dc5;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #556ed3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #556ed3;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4d3dec;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4d3dec;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--bslib-color-bg: #6422c3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #6422c3;color:#fff}.bg-gradient-indigo-pink{--bslib-color-fg: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #a30aa8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a30aa8;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9d4fb0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9d4fb0;color:#fff}.bg-gradient-indigo-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a3389b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a3389b;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #56529b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #56529b;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--bslib-color-bg: #4a5ace;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #4a5ace;color:#fff}.bg-gradient-indigo-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #7a2bdc;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #7a2bdc;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4a58a5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4a58a5;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #632bab;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #632bab;color:#fff}.bg-gradient-purple-pink{--bslib-color-fg: #fff;--bslib-color-bg: #973d82;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #973d82;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #a02561;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a02561;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9a6a6a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9a6a6a;color:#fff}.bg-gradient-purple-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a05354;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a05354;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #536d54;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #536d54;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--bslib-color-bg: #477587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #477587;color:#fff}.bg-gradient-purple-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #774695;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #774695;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #9b58af;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #9b58af;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b23e86;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b23e86;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #f1256b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f1256b;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #eb6a73;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #eb6a73;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f1545e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f1545e;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #a46e5e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a46e5e;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #c8479f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #c8479f;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a9337d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a9337d;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c20683;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c20683;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c01854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c01854;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f6195a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f6195a;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f94541;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f94541;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ff2f2c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #ff2f2c;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #b2492c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b2492c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6505f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6505f;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d6226d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d6226d;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a09b8a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a09b8a;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b96e90;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b96e90;color:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b78060;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b78060;color:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ed8167;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ed8167;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f66846;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f66846;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #000;--bslib-color-bg: #f69738;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f69738;color:#000}.bg-gradient-orange-green{--bslib-color-fg: #000;--bslib-color-bg: #a9b138;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a9b138;color:#000}.bg-gradient-orange-teal{--bslib-color-fg: #000;--bslib-color-bg: #9db86b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9db86b;color:#000}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #cd897a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #cd897a;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a97969;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a97969;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c24d6f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c24d6f;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c05f40;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c05f40;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f65f46;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f65f46;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ff4625;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #ff4625;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #000;--bslib-color-bg: #f98b2e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f98b2e;color:#000}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #b28f18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b28f18;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6974b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6974b;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d66859;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d66859;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #35a069;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #35a069;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4f746f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4f746f;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d8640;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #4d8640;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #838646;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #838646;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #8c6d25;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #8c6d25;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #000;--bslib-color-bg: #86b22e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #86b22e;color:#000}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #8c9c18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #8c9c18;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #000;--bslib-color-bg: #33be4b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #33be4b;color:#000}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #638f59;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #638f59;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #23acb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #23acb5;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #3c7fbb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #3c7fbb;color:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3a918c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3a918c;color:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #797971;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #797971;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #000;--bslib-color-bg: #73be7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #73be7a;color:#000}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #79a764;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #79a764;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #000;--bslib-color-bg: #2cc164;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #2cc164;color:#000}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #509aa5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #509aa5;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #6b66cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #6b66cb;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #8539d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #8539d1;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #834ba2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #834ba2;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #b94ba8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #b94ba8;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #c23287;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #c23287;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #bc788f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #bc788f;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #c2617a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #c2617a;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #757b7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #757b7a;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #6983ad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #6983ad;color:#fff}.tab-content>.tab-pane.html-fill-container{display:none}.tab-content>.active.html-fill-container{display:flex}.tab-content.html-fill-container{padding:0}:root{--bslib-spacer: 1rem;--bslib-mb-spacer: var(--bslib-spacer, 1rem)}.bslib-mb-spacing{margin-bottom:var(--bslib-mb-spacer)}.bslib-gap-spacing{gap:var(--bslib-mb-spacer)}.bslib-gap-spacing>.bslib-mb-spacing,.bslib-gap-spacing>.form-group,.bslib-gap-spacing>p,.bslib-gap-spacing>pre{margin-bottom:0}.html-fill-container>.html-fill-item.bslib-mb-spacing{margin-bottom:0}.bg-blue{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #2780e3;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-indigo{--bslib-color-fg: #6610f2;color:var(--bslib-color-fg)}.bg-purple{--bslib-color-bg: #613d7c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-purple{--bslib-color-fg: #613d7c;color:var(--bslib-color-fg)}.bg-pink{--bslib-color-bg: #e83e8c;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #e83e8c;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #ff0039;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #f0ad4e;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-orange{--bslib-color-fg: #f0ad4e;color:var(--bslib-color-fg)}.bg-yellow{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #ff7518;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #3fb618;color:var(--bslib-color-fg)}.bg-teal{--bslib-color-bg: #20c997;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-teal{--bslib-color-fg: #20c997;color:var(--bslib-color-fg)}.bg-cyan{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #9954bb;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #373a3c}.bg-default{--bslib-color-bg: #373a3c;--bslib-color-fg: #fff}.text-primary{--bslib-color-fg: #2780e3}.bg-primary{--bslib-color-bg: #2780e3;--bslib-color-fg: #fff}.text-secondary{--bslib-color-fg: #373a3c}.bg-secondary{--bslib-color-bg: #373a3c;--bslib-color-fg: #fff}.text-success{--bslib-color-fg: #3fb618}.bg-success{--bslib-color-bg: #3fb618;--bslib-color-fg: #fff}.text-info{--bslib-color-fg: #9954bb}.bg-info{--bslib-color-bg: #9954bb;--bslib-color-fg: #fff}.text-warning{--bslib-color-fg: #ff7518}.bg-warning{--bslib-color-bg: #ff7518;--bslib-color-fg: #fff}.text-danger{--bslib-color-fg: #ff0039}.bg-danger{--bslib-color-bg: #ff0039;--bslib-color-fg: #fff}.text-light{--bslib-color-fg: #f8f9fa}.bg-light{--bslib-color-bg: #f8f9fa;--bslib-color-fg: #000}.text-dark{--bslib-color-fg: #373a3c}.bg-dark{--bslib-color-bg: #373a3c;--bslib-color-fg: #fff}.bg-gradient-blue-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4053e9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4053e9;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3e65ba;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3e65ba;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #fff;--bslib-color-bg: #7466c0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #7466c0;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #fff;--bslib-color-bg: #7d4d9f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #7d4d9f;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #fff;--bslib-color-bg: #7792a7;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #7792a7;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #7d7c92;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #7d7c92;color:#fff}.bg-gradient-blue-green{--bslib-color-fg: #fff;--bslib-color-bg: #319692;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #319692;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #fff;--bslib-color-bg: #249dc5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #249dc5;color:#fff}.bg-gradient-blue-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #556ed3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #2780e3 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #556ed3;color:#fff}.bg-gradient-indigo-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4d3dec;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4d3dec;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #fff;--bslib-color-bg: #6422c3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #6422c3;color:#fff}.bg-gradient-indigo-pink{--bslib-color-fg: #fff;--bslib-color-bg: #9a22c9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #9a22c9;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #fff;--bslib-color-bg: #a30aa8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a30aa8;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9d4fb0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9d4fb0;color:#fff}.bg-gradient-indigo-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a3389b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a3389b;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #fff;--bslib-color-bg: #56529b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #56529b;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #fff;--bslib-color-bg: #4a5ace;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #4a5ace;color:#fff}.bg-gradient-indigo-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #7a2bdc;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #7a2bdc;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #fff;--bslib-color-bg: #4a58a5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #4a58a5;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #632bab;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #632bab;color:#fff}.bg-gradient-purple-pink{--bslib-color-fg: #fff;--bslib-color-bg: #973d82;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #973d82;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #fff;--bslib-color-bg: #a02561;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #a02561;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #fff;--bslib-color-bg: #9a6a6a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #9a6a6a;color:#fff}.bg-gradient-purple-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #a05354;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #a05354;color:#fff}.bg-gradient-purple-green{--bslib-color-fg: #fff;--bslib-color-bg: #536d54;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #536d54;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #fff;--bslib-color-bg: #477587;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #477587;color:#fff}.bg-gradient-purple-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #774695;background:linear-gradient(var(--bg-gradient-deg, 140deg), #613d7c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #774695;color:#fff}.bg-gradient-pink-blue{--bslib-color-fg: #fff;--bslib-color-bg: #9b58af;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #9b58af;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b42cb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b42cb5;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b23e86;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b23e86;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #fff;--bslib-color-bg: #f1256b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f1256b;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #fff;--bslib-color-bg: #eb6a73;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #eb6a73;color:#fff}.bg-gradient-pink-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #f1545e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f1545e;color:#fff}.bg-gradient-pink-green{--bslib-color-fg: #fff;--bslib-color-bg: #a46e5e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a46e5e;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #fff;--bslib-color-bg: #987690;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #987690;color:#fff}.bg-gradient-pink-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #c8479f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #e83e8c var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #c8479f;color:#fff}.bg-gradient-red-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a9337d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a9337d;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c20683;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c20683;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c01854;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c01854;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f6195a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f6195a;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #fff;--bslib-color-bg: #f94541;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f94541;color:#fff}.bg-gradient-red-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #ff2f2c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #ff2f2c;color:#fff}.bg-gradient-red-green{--bslib-color-fg: #fff;--bslib-color-bg: #b2492c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b2492c;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6505f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6505f;color:#fff}.bg-gradient-red-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d6226d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff0039 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d6226d;color:#fff}.bg-gradient-orange-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a09b8a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a09b8a;color:#fff}.bg-gradient-orange-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #b96e90;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #b96e90;color:#fff}.bg-gradient-orange-purple{--bslib-color-fg: #fff;--bslib-color-bg: #b78060;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #b78060;color:#fff}.bg-gradient-orange-pink{--bslib-color-fg: #fff;--bslib-color-bg: #ed8167;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #ed8167;color:#fff}.bg-gradient-orange-red{--bslib-color-fg: #fff;--bslib-color-bg: #f66846;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #f66846;color:#fff}.bg-gradient-orange-yellow{--bslib-color-fg: #000;--bslib-color-bg: #f69738;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #f69738;color:#000}.bg-gradient-orange-green{--bslib-color-fg: #000;--bslib-color-bg: #a9b138;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #a9b138;color:#000}.bg-gradient-orange-teal{--bslib-color-fg: #000;--bslib-color-bg: #9db86b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #9db86b;color:#000}.bg-gradient-orange-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #cd897a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #f0ad4e var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #cd897a;color:#fff}.bg-gradient-yellow-blue{--bslib-color-fg: #fff;--bslib-color-bg: #a97969;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #a97969;color:#fff}.bg-gradient-yellow-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #c24d6f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c24d6f;color:#fff}.bg-gradient-yellow-purple{--bslib-color-fg: #fff;--bslib-color-bg: #c05f40;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #c05f40;color:#fff}.bg-gradient-yellow-pink{--bslib-color-fg: #fff;--bslib-color-bg: #f65f46;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #f65f46;color:#fff}.bg-gradient-yellow-red{--bslib-color-fg: #fff;--bslib-color-bg: #ff4625;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #ff4625;color:#fff}.bg-gradient-yellow-orange{--bslib-color-fg: #000;--bslib-color-bg: #f98b2e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #f98b2e;color:#000}.bg-gradient-yellow-green{--bslib-color-fg: #fff;--bslib-color-bg: #b28f18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #b28f18;color:#fff}.bg-gradient-yellow-teal{--bslib-color-fg: #fff;--bslib-color-bg: #a6974b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6974b;color:#fff}.bg-gradient-yellow-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #d66859;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ff7518 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #d66859;color:#fff}.bg-gradient-green-blue{--bslib-color-fg: #fff;--bslib-color-bg: #35a069;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #35a069;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #4f746f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #4f746f;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #fff;--bslib-color-bg: #4d8640;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #4d8640;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #fff;--bslib-color-bg: #838646;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #838646;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #fff;--bslib-color-bg: #8c6d25;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #8c6d25;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #000;--bslib-color-bg: #86b22e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #86b22e;color:#000}.bg-gradient-green-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #8c9c18;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #8c9c18;color:#fff}.bg-gradient-green-teal{--bslib-color-fg: #000;--bslib-color-bg: #33be4b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #33be4b;color:#000}.bg-gradient-green-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #638f59;background:linear-gradient(var(--bg-gradient-deg, 140deg), #3fb618 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #638f59;color:#fff}.bg-gradient-teal-blue{--bslib-color-fg: #fff;--bslib-color-bg: #23acb5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #23acb5;color:#fff}.bg-gradient-teal-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #3c7fbb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #3c7fbb;color:#fff}.bg-gradient-teal-purple{--bslib-color-fg: #fff;--bslib-color-bg: #3a918c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #3a918c;color:#fff}.bg-gradient-teal-pink{--bslib-color-fg: #fff;--bslib-color-bg: #709193;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #709193;color:#fff}.bg-gradient-teal-red{--bslib-color-fg: #fff;--bslib-color-bg: #797971;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #797971;color:#fff}.bg-gradient-teal-orange{--bslib-color-fg: #000;--bslib-color-bg: #73be7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #73be7a;color:#000}.bg-gradient-teal-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #79a764;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #79a764;color:#fff}.bg-gradient-teal-green{--bslib-color-fg: #000;--bslib-color-bg: #2cc164;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #2cc164;color:#000}.bg-gradient-teal-cyan{--bslib-color-fg: #fff;--bslib-color-bg: #509aa5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #9954bb var(--bg-gradient-end, 180%)) #509aa5;color:#fff}.bg-gradient-cyan-blue{--bslib-color-fg: #fff;--bslib-color-bg: #6b66cb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #2780e3 var(--bg-gradient-end, 180%)) #6b66cb;color:#fff}.bg-gradient-cyan-indigo{--bslib-color-fg: #fff;--bslib-color-bg: #8539d1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #8539d1;color:#fff}.bg-gradient-cyan-purple{--bslib-color-fg: #fff;--bslib-color-bg: #834ba2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #613d7c var(--bg-gradient-end, 180%)) #834ba2;color:#fff}.bg-gradient-cyan-pink{--bslib-color-fg: #fff;--bslib-color-bg: #b94ba8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #e83e8c var(--bg-gradient-end, 180%)) #b94ba8;color:#fff}.bg-gradient-cyan-red{--bslib-color-fg: #fff;--bslib-color-bg: #c23287;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff0039 var(--bg-gradient-end, 180%)) #c23287;color:#fff}.bg-gradient-cyan-orange{--bslib-color-fg: #fff;--bslib-color-bg: #bc788f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #f0ad4e var(--bg-gradient-end, 180%)) #bc788f;color:#fff}.bg-gradient-cyan-yellow{--bslib-color-fg: #fff;--bslib-color-bg: #c2617a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #ff7518 var(--bg-gradient-end, 180%)) #c2617a;color:#fff}.bg-gradient-cyan-green{--bslib-color-fg: #fff;--bslib-color-bg: #757b7a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #3fb618 var(--bg-gradient-end, 180%)) #757b7a;color:#fff}.bg-gradient-cyan-teal{--bslib-color-fg: #fff;--bslib-color-bg: #6983ad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #9954bb var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #6983ad;color:#fff}:root{--bslib-value-box-shadow: none;--bslib-value-box-border-width-auto-yes: var(--bslib-value-box-border-width-baseline);--bslib-value-box-border-width-auto-no: 0;--bslib-value-box-border-width-baseline: 1px}.bslib-value-box{border-width:var(--bslib-value-box-border-width-auto-no, var(--bslib-value-box-border-width-baseline));container-name:bslib-value-box;container-type:inline-size}.bslib-value-box.card{box-shadow:var(--bslib-value-box-shadow)}.bslib-value-box.border-auto{border-width:var(--bslib-value-box-border-width-auto-yes, var(--bslib-value-box-border-width-baseline))}.bslib-value-box.default{--bslib-value-box-bg-default: var(--bs-card-bg, #fff);--bslib-value-box-border-color-default: var(--bs-card-border-color, rgba(0, 0, 0, 0.175));color:var(--bslib-value-box-color);background-color:var(--bslib-value-box-bg, var(--bslib-value-box-bg-default));border-color:var(--bslib-value-box-border-color, var(--bslib-value-box-border-color-default))}.bslib-value-box .value-box-grid{display:grid;grid-template-areas:"left right";align-items:center;overflow:hidden}.bslib-value-box .value-box-showcase{height:100%;max-height:var(---bslib-value-box-showcase-max-h, 100%)}.bslib-value-box .value-box-showcase,.bslib-value-box .value-box-showcase>.html-fill-item{width:100%}.bslib-value-box[data-full-screen=true] .value-box-showcase{max-height:var(---bslib-value-box-showcase-max-h-fs, 100%)}@media screen and (min-width: 575.98px){@container bslib-value-box (max-width: 300px){.bslib-value-box:not(.showcase-bottom) .value-box-grid{grid-template-columns:1fr !important;grid-template-rows:auto auto;grid-template-areas:"top" "bottom"}.bslib-value-box:not(.showcase-bottom) .value-box-grid .value-box-showcase{grid-area:top !important}.bslib-value-box:not(.showcase-bottom) .value-box-grid .value-box-area{grid-area:bottom !important;justify-content:end}}}.bslib-value-box .value-box-area{justify-content:center;padding:1.5rem 1rem;font-size:.9rem;font-weight:500}.bslib-value-box .value-box-area *{margin-bottom:0;margin-top:0}.bslib-value-box .value-box-title{font-size:1rem;margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2}.bslib-value-box .value-box-title:empty::after{content:" "}.bslib-value-box .value-box-value{font-size:calc(1.29rem + 0.48vw);margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2}@media(min-width: 1200px){.bslib-value-box .value-box-value{font-size:1.65rem}}.bslib-value-box .value-box-value:empty::after{content:" "}.bslib-value-box .value-box-showcase{align-items:center;justify-content:center;margin-top:auto;margin-bottom:auto;padding:1rem}.bslib-value-box .value-box-showcase .bi,.bslib-value-box .value-box-showcase .fa,.bslib-value-box .value-box-showcase .fab,.bslib-value-box .value-box-showcase .fas,.bslib-value-box .value-box-showcase .far{opacity:.85;min-width:50px;max-width:125%}.bslib-value-box .value-box-showcase .bi,.bslib-value-box .value-box-showcase .fa,.bslib-value-box .value-box-showcase .fab,.bslib-value-box .value-box-showcase .fas,.bslib-value-box .value-box-showcase .far{font-size:4rem}.bslib-value-box.showcase-top-right .value-box-grid{grid-template-columns:1fr var(---bslib-value-box-showcase-w, 50%)}.bslib-value-box.showcase-top-right .value-box-grid .value-box-showcase{grid-area:right;margin-left:auto;align-self:start;align-items:end;padding-left:0;padding-bottom:0}.bslib-value-box.showcase-top-right .value-box-grid .value-box-area{grid-area:left;align-self:end}.bslib-value-box.showcase-top-right[data-full-screen=true] .value-box-grid{grid-template-columns:auto var(---bslib-value-box-showcase-w-fs, 1fr)}.bslib-value-box.showcase-top-right[data-full-screen=true] .value-box-grid>div{align-self:center}.bslib-value-box.showcase-top-right:not([data-full-screen=true]) .value-box-showcase{margin-top:0}@container bslib-value-box (max-width: 300px){.bslib-value-box.showcase-top-right:not([data-full-screen=true]) .value-box-grid .value-box-showcase{padding-left:1rem}}.bslib-value-box.showcase-left-center .value-box-grid{grid-template-columns:var(---bslib-value-box-showcase-w, 30%) auto}.bslib-value-box.showcase-left-center[data-full-screen=true] .value-box-grid{grid-template-columns:var(---bslib-value-box-showcase-w-fs, 1fr) auto}.bslib-value-box.showcase-left-center:not([data-fill-screen=true]) .value-box-grid .value-box-showcase{grid-area:left}.bslib-value-box.showcase-left-center:not([data-fill-screen=true]) .value-box-grid .value-box-area{grid-area:right}.bslib-value-box.showcase-bottom .value-box-grid{grid-template-columns:1fr;grid-template-rows:1fr var(---bslib-value-box-showcase-h, auto);grid-template-areas:"top" "bottom";overflow:hidden}.bslib-value-box.showcase-bottom .value-box-grid .value-box-showcase{grid-area:bottom;padding:0;margin:0}.bslib-value-box.showcase-bottom .value-box-grid .value-box-area{grid-area:top}.bslib-value-box.showcase-bottom[data-full-screen=true] .value-box-grid{grid-template-rows:1fr var(---bslib-value-box-showcase-h-fs, 2fr)}.bslib-value-box.showcase-bottom[data-full-screen=true] .value-box-grid .value-box-showcase{padding:1rem}[data-bs-theme=dark] .bslib-value-box{--bslib-value-box-shadow: 0 0.5rem 1rem rgb(0 0 0 / 50%)}@media(min-width: 576px){.nav:not(.nav-hidden){display:flex !important;display:-webkit-flex !important}.nav:not(.nav-hidden):not(.nav-stacked):not(.flex-column){float:none !important}.nav:not(.nav-hidden):not(.nav-stacked):not(.flex-column)>.bslib-nav-spacer{margin-left:auto !important}.nav:not(.nav-hidden):not(.nav-stacked):not(.flex-column)>.form-inline{margin-top:auto;margin-bottom:auto}.nav:not(.nav-hidden).nav-stacked{flex-direction:column;-webkit-flex-direction:column;height:100%}.nav:not(.nav-hidden).nav-stacked>.bslib-nav-spacer{margin-top:auto !important}}.bslib-card{overflow:auto}.bslib-card .card-body+.card-body{padding-top:0}.bslib-card .card-body{overflow:auto}.bslib-card .card-body p{margin-top:0}.bslib-card .card-body p:last-child{margin-bottom:0}.bslib-card .card-body{max-height:var(--bslib-card-body-max-height, none)}.bslib-card[data-full-screen=true]>.card-body{max-height:var(--bslib-card-body-max-height-full-screen, none)}.bslib-card .card-header .form-group{margin-bottom:0}.bslib-card .card-header .selectize-control{margin-bottom:0}.bslib-card .card-header .selectize-control .item{margin-right:1.15rem}.bslib-card .card-footer{margin-top:auto}.bslib-card .bslib-navs-card-title{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center}.bslib-card .bslib-navs-card-title .nav{margin-left:auto}.bslib-card .bslib-sidebar-layout:not([data-bslib-sidebar-border=true]){border:none}.bslib-card .bslib-sidebar-layout:not([data-bslib-sidebar-border-radius=true]){border-top-left-radius:0;border-top-right-radius:0}[data-full-screen=true]{position:fixed;inset:3.5rem 1rem 1rem;height:auto !important;max-height:none !important;width:auto !important;z-index:1070}.bslib-full-screen-enter{display:none;position:absolute;bottom:var(--bslib-full-screen-enter-bottom, 0.2rem);right:var(--bslib-full-screen-enter-right, 0);top:var(--bslib-full-screen-enter-top);left:var(--bslib-full-screen-enter-left);color:var(--bslib-color-fg, var(--bs-card-color));background-color:var(--bslib-color-bg, var(--bs-card-bg, var(--bs-body-bg)));border:var(--bs-card-border-width) solid var(--bslib-color-fg, var(--bs-card-border-color));box-shadow:0 2px 4px rgba(0,0,0,.15);margin:.2rem .4rem;padding:.55rem !important;font-size:.8rem;cursor:pointer;opacity:.7;z-index:1070}.bslib-full-screen-enter:hover{opacity:1}.card[data-full-screen=false]:hover>*>.bslib-full-screen-enter{display:block}.bslib-has-full-screen .card:hover>*>.bslib-full-screen-enter{display:none}@media(max-width: 575.98px){.bslib-full-screen-enter{display:none !important}}.bslib-full-screen-exit{position:relative;top:1.35rem;font-size:.9rem;cursor:pointer;text-decoration:none;display:flex;float:right;margin-right:2.15rem;align-items:center;color:rgba(var(--bs-body-bg-rgb), 0.8)}.bslib-full-screen-exit:hover{color:rgba(var(--bs-body-bg-rgb), 1)}.bslib-full-screen-exit svg{margin-left:.5rem;font-size:1.5rem}#bslib-full-screen-overlay{position:fixed;inset:0;background-color:rgba(var(--bs-body-color-rgb), 0.6);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);z-index:1069;animation:bslib-full-screen-overlay-enter 400ms cubic-bezier(0.6, 0.02, 0.65, 1) forwards}@keyframes bslib-full-screen-overlay-enter{0%{opacity:0}100%{opacity:1}}.bslib-grid{display:grid !important;gap:var(--bslib-spacer, 1rem);height:var(--bslib-grid-height)}.bslib-grid.grid{grid-template-columns:repeat(var(--bs-columns, 12), minmax(0, 1fr));grid-template-rows:unset;grid-auto-rows:var(--bslib-grid--row-heights);--bslib-grid--row-heights--xs: unset;--bslib-grid--row-heights--sm: unset;--bslib-grid--row-heights--md: unset;--bslib-grid--row-heights--lg: unset;--bslib-grid--row-heights--xl: unset;--bslib-grid--row-heights--xxl: unset}.bslib-grid.grid.bslib-grid--row-heights--xs{--bslib-grid--row-heights: var(--bslib-grid--row-heights--xs)}@media(min-width: 576px){.bslib-grid.grid.bslib-grid--row-heights--sm{--bslib-grid--row-heights: var(--bslib-grid--row-heights--sm)}}@media(min-width: 768px){.bslib-grid.grid.bslib-grid--row-heights--md{--bslib-grid--row-heights: var(--bslib-grid--row-heights--md)}}@media(min-width: 992px){.bslib-grid.grid.bslib-grid--row-heights--lg{--bslib-grid--row-heights: var(--bslib-grid--row-heights--lg)}}@media(min-width: 1200px){.bslib-grid.grid.bslib-grid--row-heights--xl{--bslib-grid--row-heights: var(--bslib-grid--row-heights--xl)}}@media(min-width: 1400px){.bslib-grid.grid.bslib-grid--row-heights--xxl{--bslib-grid--row-heights: var(--bslib-grid--row-heights--xxl)}}.bslib-grid>*>.shiny-input-container{width:100%}.bslib-grid-item{grid-column:auto/span 1}@media(max-width: 767.98px){.bslib-grid-item{grid-column:1/-1}}@media(max-width: 575.98px){.bslib-grid{grid-template-columns:1fr !important;height:var(--bslib-grid-height-mobile)}.bslib-grid.grid{height:unset !important;grid-auto-rows:var(--bslib-grid--row-heights--xs, auto)}}.accordion .accordion-header{font-size:calc(1.29rem + 0.48vw);margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2;color:var(--bs-heading-color);margin-bottom:0}@media(min-width: 1200px){.accordion .accordion-header{font-size:1.65rem}}.accordion .accordion-icon:not(:empty){margin-right:.75rem;display:flex}.accordion .accordion-button:not(.collapsed){box-shadow:none}.accordion .accordion-button:not(.collapsed):focus{box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.navbar+.container-fluid:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-sm:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-md:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-lg:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-xl:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-xxl:has(>.tab-content>.tab-pane.active.html-fill-container){padding-left:0;padding-right:0}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container{padding:var(--bslib-spacer, 1rem);gap:var(--bslib-spacer, 1rem)}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child){padding:0}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]){border-left:none;border-right:none;border-bottom:none}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]){border-radius:0}.navbar+div>.bslib-sidebar-layout{border-top:var(--bslib-sidebar-border)}html{height:100%}.bslib-page-fill{width:100%;height:100%;margin:0;padding:var(--bslib-spacer, 1rem);gap:var(--bslib-spacer, 1rem)}@media(max-width: 575.98px){.bslib-page-fill{height:var(--bslib-page-fill-mobile-height, auto)}}:root{--bslib-page-sidebar-title-bg: #f8f9fa;--bslib-page-sidebar-title-color: #000}.bslib-page-title{background-color:var(--bslib-page-sidebar-title-bg);color:var(--bslib-page-sidebar-title-color);font-size:1.25rem;font-weight:300;padding:var(--bslib-spacer, 1rem);padding-left:1.5rem;margin-bottom:0;border-bottom:1px solid #e1e1e2}.bslib-sidebar-layout{--bslib-sidebar-transition-duration: 500ms;--bslib-sidebar-transition-easing-x: cubic-bezier(0.8, 0.78, 0.22, 1.07);--bslib-sidebar-border: var(--bs-card-border-width, 1px) solid var(--bs-card-border-color, rgba(0, 0, 0, 0.175));--bslib-sidebar-border-radius: var(--bs-border-radius);--bslib-sidebar-vert-border: var(--bs-card-border-width, 1px) solid var(--bs-card-border-color, rgba(0, 0, 0, 0.175));--bslib-sidebar-bg: rgba(var(--bs-emphasis-color-rgb, 0, 0, 0), 0.05);--bslib-sidebar-fg: var(--bs-emphasis-color, black);--bslib-sidebar-main-fg: var(--bs-card-color, var(--bs-body-color));--bslib-sidebar-main-bg: var(--bs-card-bg, var(--bs-body-bg));--bslib-sidebar-toggle-bg: rgba(var(--bs-emphasis-color-rgb, 0, 0, 0), 0.1);--bslib-sidebar-padding: calc(var(--bslib-spacer) * 1.5);--bslib-sidebar-icon-size: var(--bslib-spacer, 1rem);--bslib-sidebar-icon-button-size: calc(var(--bslib-sidebar-icon-size, 1rem) * 2);--bslib-sidebar-padding-icon: calc(var(--bslib-sidebar-icon-button-size, 2rem) * 1.5);--bslib-collapse-toggle-border-radius: var(--bs-border-radius, 0.375rem);--bslib-collapse-toggle-transform: 0deg;--bslib-sidebar-toggle-transition-easing: cubic-bezier(1, 0, 0, 1);--bslib-collapse-toggle-right-transform: 180deg;--bslib-sidebar-column-main: minmax(0, 1fr);display:grid !important;grid-template-columns:min(100% - var(--bslib-sidebar-icon-size),var(--bslib-sidebar-width, 250px)) var(--bslib-sidebar-column-main);position:relative;transition:grid-template-columns ease-in-out var(--bslib-sidebar-transition-duration);border:var(--bslib-sidebar-border);border-radius:var(--bslib-sidebar-border-radius)}@media(prefers-reduced-motion: reduce){.bslib-sidebar-layout{transition:none}}.bslib-sidebar-layout[data-bslib-sidebar-border=false]{border:none}.bslib-sidebar-layout[data-bslib-sidebar-border-radius=false]{border-radius:initial}.bslib-sidebar-layout>.main,.bslib-sidebar-layout>.sidebar{grid-row:1/2;border-radius:inherit;overflow:auto}.bslib-sidebar-layout>.main{grid-column:2/3;border-top-left-radius:0;border-bottom-left-radius:0;padding:var(--bslib-sidebar-padding);transition:padding var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration);color:var(--bslib-sidebar-main-fg);background-color:var(--bslib-sidebar-main-bg)}.bslib-sidebar-layout>.sidebar{grid-column:1/2;width:100%;height:100%;border-right:var(--bslib-sidebar-vert-border);border-top-right-radius:0;border-bottom-right-radius:0;color:var(--bslib-sidebar-fg);background-color:var(--bslib-sidebar-bg);backdrop-filter:blur(5px)}.bslib-sidebar-layout>.sidebar>.sidebar-content{display:flex;flex-direction:column;gap:var(--bslib-spacer, 1rem);padding:var(--bslib-sidebar-padding);padding-top:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout>.sidebar>.sidebar-content>:last-child:not(.sidebar-title){margin-bottom:0}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion{margin-left:calc(-1*var(--bslib-sidebar-padding));margin-right:calc(-1*var(--bslib-sidebar-padding))}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:last-child{margin-bottom:calc(-1*var(--bslib-sidebar-padding))}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:not(:last-child){margin-bottom:1rem}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion .accordion-body{display:flex;flex-direction:column}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:not(:first-child) .accordion-item:first-child{border-top:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:not(:last-child) .accordion-item:last-child{border-bottom:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.bslib-sidebar-layout>.sidebar>.sidebar-content.has-accordion>.sidebar-title{border-bottom:none;padding-bottom:0}.bslib-sidebar-layout>.sidebar .shiny-input-container{width:100%}.bslib-sidebar-layout[data-bslib-sidebar-open=always]>.sidebar>.sidebar-content{padding-top:var(--bslib-sidebar-padding)}.bslib-sidebar-layout>.collapse-toggle{grid-row:1/2;grid-column:1/2;display:inline-flex;align-items:center;position:absolute;right:calc(var(--bslib-sidebar-icon-size));top:calc(var(--bslib-sidebar-icon-size, 1rem)/2);border:none;border-radius:var(--bslib-collapse-toggle-border-radius);height:var(--bslib-sidebar-icon-button-size, 2rem);width:var(--bslib-sidebar-icon-button-size, 2rem);display:flex;align-items:center;justify-content:center;padding:0;color:var(--bslib-sidebar-fg);background-color:unset;transition:color var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),top var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),right var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),left var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration)}.bslib-sidebar-layout>.collapse-toggle:hover{background-color:var(--bslib-sidebar-toggle-bg)}.bslib-sidebar-layout>.collapse-toggle>.collapse-icon{opacity:.8;width:var(--bslib-sidebar-icon-size);height:var(--bslib-sidebar-icon-size);transform:rotateY(var(--bslib-collapse-toggle-transform));transition:transform var(--bslib-sidebar-toggle-transition-easing) var(--bslib-sidebar-transition-duration)}.bslib-sidebar-layout>.collapse-toggle:hover>.collapse-icon{opacity:1}.bslib-sidebar-layout .sidebar-title{font-size:1.25rem;line-height:1.25;margin-top:0;margin-bottom:1rem;padding-bottom:1rem;border-bottom:var(--bslib-sidebar-border)}.bslib-sidebar-layout.sidebar-right{grid-template-columns:var(--bslib-sidebar-column-main) min(100% - var(--bslib-sidebar-icon-size),var(--bslib-sidebar-width, 250px))}.bslib-sidebar-layout.sidebar-right>.main{grid-column:1/2;border-top-right-radius:0;border-bottom-right-radius:0;border-top-left-radius:inherit;border-bottom-left-radius:inherit}.bslib-sidebar-layout.sidebar-right>.sidebar{grid-column:2/3;border-right:none;border-left:var(--bslib-sidebar-vert-border);border-top-left-radius:0;border-bottom-left-radius:0}.bslib-sidebar-layout.sidebar-right>.collapse-toggle{grid-column:2/3;left:var(--bslib-sidebar-icon-size);right:unset;border:var(--bslib-collapse-toggle-border)}.bslib-sidebar-layout.sidebar-right>.collapse-toggle>.collapse-icon{transform:rotateY(var(--bslib-collapse-toggle-right-transform))}.bslib-sidebar-layout.sidebar-collapsed{--bslib-collapse-toggle-transform: 180deg;--bslib-collapse-toggle-right-transform: 0deg;--bslib-sidebar-vert-border: none;grid-template-columns:0 minmax(0, 1fr)}.bslib-sidebar-layout.sidebar-collapsed.sidebar-right{grid-template-columns:minmax(0, 1fr) 0}.bslib-sidebar-layout.sidebar-collapsed:not(.transitioning)>.sidebar>*{display:none}.bslib-sidebar-layout.sidebar-collapsed>.main{border-radius:inherit}.bslib-sidebar-layout.sidebar-collapsed:not(.sidebar-right)>.main{padding-left:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout.sidebar-collapsed.sidebar-right>.main{padding-right:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout.sidebar-collapsed>.collapse-toggle{color:var(--bslib-sidebar-main-fg);top:calc(var(--bslib-sidebar-overlap-counter, 0)*(var(--bslib-sidebar-icon-size) + var(--bslib-sidebar-padding)) + var(--bslib-sidebar-icon-size, 1rem)/2);right:calc(-2.5*var(--bslib-sidebar-icon-size) - var(--bs-card-border-width, 1px))}.bslib-sidebar-layout.sidebar-collapsed.sidebar-right>.collapse-toggle{left:calc(-2.5*var(--bslib-sidebar-icon-size) - var(--bs-card-border-width, 1px));right:unset}@media(min-width: 576px){.bslib-sidebar-layout.transitioning>.sidebar>.sidebar-content{display:none}}@media(max-width: 575.98px){.bslib-sidebar-layout[data-bslib-sidebar-open=desktop]{--bslib-sidebar-js-init-collapsed: true}.bslib-sidebar-layout>.sidebar,.bslib-sidebar-layout.sidebar-right>.sidebar{border:none}.bslib-sidebar-layout>.main,.bslib-sidebar-layout.sidebar-right>.main{grid-column:1/3}.bslib-sidebar-layout[data-bslib-sidebar-open=always]{display:block !important}.bslib-sidebar-layout[data-bslib-sidebar-open=always]>.sidebar{max-height:var(--bslib-sidebar-max-height-mobile);overflow-y:auto;border-top:var(--bslib-sidebar-vert-border)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]){grid-template-columns:100% 0}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]):not(.sidebar-collapsed)>.sidebar{z-index:1}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]):not(.sidebar-collapsed)>.collapse-toggle{z-index:1}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-right{grid-template-columns:0 100%}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-collapsed{grid-template-columns:0 100%}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-collapsed.sidebar-right{grid-template-columns:100% 0}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]):not(.sidebar-right)>.main{padding-left:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-right>.main{padding-right:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always])>.main{opacity:0;transition:opacity var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-collapsed>.main{opacity:1}}.html-fill-container{display:flex;flex-direction:column;min-height:0;min-width:0}.html-fill-container>.html-fill-item{flex:1 1 auto;min-height:0;min-width:0}.html-fill-container>:not(.html-fill-item){flex:0 0 auto}.tippy-box[data-theme~=quarto]{background-color:#fff;border:solid 1px #e1e1e2;border-radius:.375rem;color:#373a3c;font-size:.875rem}.tippy-box[data-theme~=quarto]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=quarto]>.tippy-arrow:after,.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=quarto]>.tippy-arrow:after{border-color:rgba(0,0,0,0);border-style:solid}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-6px}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-6px}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-6px}.tippy-box[data-placement^=left]>.tippy-arrow:before{right:-6px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:after{border-top-color:#e1e1e2;border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff;bottom:16px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:#e1e1e2;border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:15px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:after{border-left-color:#e1e1e2;border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff;right:16px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:#e1e1e2}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow{fill:#373a3c}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px}.top-right{position:absolute;top:1em;right:1em}.visually-hidden{border:0;clip:rect(0 0 0 0);height:auto;margin:0;overflow:hidden;padding:0;position:absolute;width:1px;white-space:nowrap}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}figure.figure{display:block}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p,.quarto-figure-left>figure>div{text-align:left}.quarto-figure-center>figure>p,.quarto-figure-center>figure>div{text-align:center}.quarto-figure-right>figure>p,.quarto-figure-right>figure>div{text-align:right}.quarto-figure>figure>div.cell-annotation,.quarto-figure>figure>div code{text-align:left}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption.quarto-float-caption-bottom{margin-bottom:.5em}figure>figcaption.quarto-float-caption-top{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link{position:absolute;top:.6em;right:.5em}div[id^=tbl-]>.anchorjs-link{position:absolute;top:.7em;right:.3em}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,.h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,.h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,.h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,.h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1,#title-block-header .quarto-title-block>div>.h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}@media(min-width: 992px){#title-block-header .quarto-title-block>div>button{margin-top:5px}}tr.header>th>p:last-of-type{margin-bottom:0px}table,table.table{margin-top:.5rem;margin-bottom:.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}figure.quarto-float-tbl figcaption.quarto-float-caption-top{margin-top:.5rem;margin-bottom:.25rem;text-align:center}figure.quarto-float-tbl figcaption.quarto-float-caption-bottom{padding-top:.25rem;margin-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:rgba(55,58,60,.75)}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}dd code:not(.sourceCode),p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.footnote-back{margin-left:.2em}.tippy-content{overflow-x:auto}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x),.knitsql-table:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}a{text-underline-offset:3px}.callout pre.sourceCode{padding-left:0}div.ansi-escaped-output{font-family:monospace;display:block}/*!
+*
+* ansi colors from IPython notebook's
+*
+* we also add `bright-[color]-` synonyms for the `-[color]-intense` classes since
+* that seems to be what ansi_up emits
+*
+*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-black,.ansi-bright-black-fg{color:#282c36}.ansi-black-intense-black,.ansi-bright-black-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-red,.ansi-bright-red-fg{color:#b22b31}.ansi-red-intense-red,.ansi-bright-red-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-green,.ansi-bright-green-fg{color:#007427}.ansi-green-intense-green,.ansi-bright-green-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-yellow,.ansi-bright-yellow-fg{color:#b27d12}.ansi-yellow-intense-yellow,.ansi-bright-yellow-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-blue,.ansi-bright-blue-fg{color:#0065ca}.ansi-blue-intense-blue,.ansi-bright-blue-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-magenta,.ansi-bright-magenta-fg{color:#a03196}.ansi-magenta-intense-magenta,.ansi-bright-magenta-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-cyan,.ansi-bright-cyan-fg{color:#258f8f}.ansi-cyan-intense-cyan,.ansi-bright-cyan-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-white,.ansi-bright-white-fg{color:#a1a6b2}.ansi-white-intense-white,.ansi-bright-white-bg{background-color:#a1a6b2}.ansi-default-inverse-fg{color:#fff}.ansi-default-inverse-bg{background-color:#000}.ansi-bold{font-weight:bold}.ansi-underline{text-decoration:underline}:root{--quarto-body-bg: #fff;--quarto-body-color: #373a3c;--quarto-text-muted: rgba(55, 58, 60, 0.75);--quarto-border-color: #e1e1e2;--quarto-border-width: 1px}table.gt_table{color:var(--quarto-body-color);font-size:1em;width:100%;background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_column_spanner_outer{color:var(--quarto-body-color);background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_col_heading{color:var(--quarto-body-color);font-weight:bold;background-color:rgba(0,0,0,0)}table.gt_table thead.gt_col_headings{border-bottom:1px solid currentColor;border-top-width:inherit;border-top-color:var(--quarto-border-color)}table.gt_table thead.gt_col_headings:not(:first-child){border-top-width:1px;border-top-color:var(--quarto-border-color)}table.gt_table td.gt_row{border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-width:0px}table.gt_table tbody.gt_table_body{border-top-width:1px;border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-color:currentColor}div.columns{display:initial;gap:initial}div.column{display:inline-block;overflow-x:initial;vertical-align:top;width:50%}.code-annotation-tip-content{word-wrap:break-word}.code-annotation-container-hidden{display:none !important}dl.code-annotation-container-grid{display:grid;grid-template-columns:min-content auto}dl.code-annotation-container-grid dt{grid-column:1}dl.code-annotation-container-grid dd{grid-column:2}pre.sourceCode.code-annotation-code{padding-right:0}code.sourceCode .code-annotation-anchor{z-index:100;position:relative;float:right;background-color:rgba(0,0,0,0)}input[type=checkbox]{margin-right:.5ch}:root{--mermaid-bg-color: #fff;--mermaid-edge-color: #373a3c;--mermaid-node-fg-color: #373a3c;--mermaid-fg-color: #373a3c;--mermaid-fg-color--lighter: #4f5457;--mermaid-fg-color--lightest: #686d71;--mermaid-font-family: Source Sans Pro, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;--mermaid-label-bg-color: #fff;--mermaid-label-fg-color: #2780e3;--mermaid-node-bg-color: rgba(39, 128, 227, 0.1);--mermaid-node-fg-color: #373a3c}@media print{:root{font-size:11pt}#quarto-sidebar,#TOC,.nav-page{display:none}.page-columns .content{grid-column-start:page-start}.fixed-top{position:relative}.panel-caption,.figure-caption,figcaption{color:#666}}.code-copy-button{position:absolute;top:0;right:0;border:0;margin-top:5px;margin-right:5px;background-color:rgba(0,0,0,0);z-index:3}.code-copy-button:focus{outline:none}.code-copy-button-tooltip{font-size:.75em}pre.sourceCode:hover>.code-copy-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}pre.sourceCode:hover>.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button-checked:hover>.bi::before{background-image:url('data:image/svg+xml,')}main ol ol,main ul ul,main ol ul,main ul ol{margin-bottom:1em}ul>li:not(:has(>p))>ul,ol>li:not(:has(>p))>ul,ul>li:not(:has(>p))>ol,ol>li:not(:has(>p))>ol{margin-bottom:0}ul>li:not(:has(>p))>ul>li:has(>p),ol>li:not(:has(>p))>ul>li:has(>p),ul>li:not(:has(>p))>ol>li:has(>p),ol>li:not(:has(>p))>ol>li:has(>p){margin-top:1rem}body{margin:0}main.page-columns>header>h1.title,main.page-columns>header>.title.h1{margin-bottom:0}@media(min-width: 992px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] 35px [page-end-inset page-end] 5fr [screen-end-inset] 1.5em}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] minmax(50px, 100px) [page-end] 1fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1000px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1000px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1000px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 150px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 991.98px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(1250px - 3em)) [body-content-end body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(1000px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 767.98px){body .page-columns,body.fullcontent:not(.floating):not(.docked) .page-columns,body.slimcontent:not(.floating):not(.docked) .page-columns,body.docked .page-columns,body.docked.slimcontent .page-columns,body.docked.fullcontent .page-columns,body.floating .page-columns,body.floating.slimcontent .page-columns,body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}nav[role=doc-toc]{display:none}}body,.page-row-navigation{grid-template-rows:[page-top] max-content [contents-top] max-content [contents-bottom] max-content [page-bottom]}.page-rows-contents{grid-template-rows:[content-top] minmax(max-content, 1fr) [content-bottom] minmax(60px, max-content) [page-bottom]}.page-full{grid-column:screen-start/screen-end !important}.page-columns>*{grid-column:body-content-start/body-content-end}.page-columns.column-page>*{grid-column:page-start/page-end}.page-columns.column-page-left .page-columns.page-full>*,.page-columns.column-page-left>*{grid-column:page-start/body-content-end}.page-columns.column-page-right .page-columns.page-full>*,.page-columns.column-page-right>*{grid-column:body-content-start/page-end}.page-rows{grid-auto-rows:auto}.header{grid-column:screen-start/screen-end;grid-row:page-top/contents-top}#quarto-content{padding:0;grid-column:screen-start/screen-end;grid-row:contents-top/contents-bottom}body.floating .sidebar.sidebar-navigation{grid-column:page-start/body-start;grid-row:content-top/page-bottom}body.docked .sidebar.sidebar-navigation{grid-column:screen-start/body-start;grid-row:content-top/page-bottom}.sidebar.toc-left{grid-column:page-start/body-start;grid-row:content-top/page-bottom}.sidebar.margin-sidebar{grid-column:body-end/page-end;grid-row:content-top/page-bottom}.page-columns .content{grid-column:body-content-start/body-content-end;grid-row:content-top/content-bottom;align-content:flex-start}.page-columns .page-navigation{grid-column:body-content-start/body-content-end;grid-row:content-bottom/page-bottom}.page-columns .footer{grid-column:screen-start/screen-end;grid-row:contents-bottom/page-bottom}.page-columns .column-body{grid-column:body-content-start/body-content-end}.page-columns .column-body-fullbleed{grid-column:body-start/body-end}.page-columns .column-body-outset{grid-column:body-start-outset/body-end-outset;z-index:998;opacity:.999}.page-columns .column-body-outset table{background:#fff}.page-columns .column-body-outset-left{grid-column:body-start-outset/body-content-end;z-index:998;opacity:.999}.page-columns .column-body-outset-left table{background:#fff}.page-columns .column-body-outset-right{grid-column:body-content-start/body-end-outset;z-index:998;opacity:.999}.page-columns .column-body-outset-right table{background:#fff}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;opacity:.999}.page-columns .column-page table{background:#fff}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;opacity:.999}.page-columns .column-page-inset table{background:#fff}.page-columns .column-page-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;opacity:.999}.page-columns .column-page-inset-left table{background:#fff}.page-columns .column-page-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;opacity:.999}.page-columns .column-page-inset-right figcaption table{background:#fff}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-page-left table{background:#fff}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;opacity:.999}.page-columns .column-page-right figcaption table{background:#fff}#quarto-content.page-columns #quarto-margin-sidebar,#quarto-content.page-columns #quarto-sidebar{z-index:1}@media(max-width: 991.98px){#quarto-content.page-columns #quarto-margin-sidebar.collapse,#quarto-content.page-columns #quarto-sidebar.collapse,#quarto-content.page-columns #quarto-margin-sidebar.collapsing,#quarto-content.page-columns #quarto-sidebar.collapsing{z-index:1055}}#quarto-content.page-columns main.column-page,#quarto-content.page-columns main.column-page-right,#quarto-content.page-columns main.column-page-left{z-index:0}.page-columns .column-screen-inset{grid-column:screen-start-inset/screen-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:screen-start-inset/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/screen-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;opacity:.999}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;opacity:.999}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#f8f9fa;z-index:998;opacity:.999;margin-bottom:1em}.zindex-content{z-index:998;opacity:.999}.zindex-modal{z-index:1055;opacity:.999}.zindex-over-content{z-index:999;opacity:.999}img.img-fluid.column-screen,img.img-fluid.column-screen-inset-shaded,img.img-fluid.column-screen-inset,img.img-fluid.column-screen-inset-left,img.img-fluid.column-screen-inset-right,img.img-fluid.column-screen-left,img.img-fluid.column-screen-right{width:100%}@media(min-width: 992px){.margin-caption,div.aside,aside:not(.footnotes):not(.sidebar),.column-margin{grid-column:body-end/page-end !important;z-index:998}.column-sidebar{grid-column:page-start/body-start !important;z-index:998}.column-leftmargin{grid-column:screen-start-inset/body-start !important;z-index:998}.no-row-height{height:1em;overflow:visible}}@media(max-width: 991.98px){.margin-caption,div.aside,aside:not(.footnotes):not(.sidebar),.column-margin{grid-column:body-end/page-end !important;z-index:998}.no-row-height{height:1em;overflow:visible}.page-columns.page-full{overflow:visible}.page-columns.toc-left .margin-caption,.page-columns.toc-left div.aside,.page-columns.toc-left aside:not(.footnotes):not(.sidebar),.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;opacity:.999}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside:not(.footnotes):not(.sidebar),.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;opacity:.999}.no-row-height{height:initial;overflow:initial}#quarto-margin-sidebar{display:none}#quarto-sidebar-toc-left{display:none}.hidden-sm{display:none}}.panel-grid{display:grid;grid-template-rows:repeat(1, 1fr);grid-template-columns:repeat(24, 1fr);gap:1em}.panel-grid .g-col-1{grid-column:auto/span 1}.panel-grid .g-col-2{grid-column:auto/span 2}.panel-grid .g-col-3{grid-column:auto/span 3}.panel-grid .g-col-4{grid-column:auto/span 4}.panel-grid .g-col-5{grid-column:auto/span 5}.panel-grid .g-col-6{grid-column:auto/span 6}.panel-grid .g-col-7{grid-column:auto/span 7}.panel-grid .g-col-8{grid-column:auto/span 8}.panel-grid .g-col-9{grid-column:auto/span 9}.panel-grid .g-col-10{grid-column:auto/span 10}.panel-grid .g-col-11{grid-column:auto/span 11}.panel-grid .g-col-12{grid-column:auto/span 12}.panel-grid .g-col-13{grid-column:auto/span 13}.panel-grid .g-col-14{grid-column:auto/span 14}.panel-grid .g-col-15{grid-column:auto/span 15}.panel-grid .g-col-16{grid-column:auto/span 16}.panel-grid .g-col-17{grid-column:auto/span 17}.panel-grid .g-col-18{grid-column:auto/span 18}.panel-grid .g-col-19{grid-column:auto/span 19}.panel-grid .g-col-20{grid-column:auto/span 20}.panel-grid .g-col-21{grid-column:auto/span 21}.panel-grid .g-col-22{grid-column:auto/span 22}.panel-grid .g-col-23{grid-column:auto/span 23}.panel-grid .g-col-24{grid-column:auto/span 24}.panel-grid .g-start-1{grid-column-start:1}.panel-grid .g-start-2{grid-column-start:2}.panel-grid .g-start-3{grid-column-start:3}.panel-grid .g-start-4{grid-column-start:4}.panel-grid .g-start-5{grid-column-start:5}.panel-grid .g-start-6{grid-column-start:6}.panel-grid .g-start-7{grid-column-start:7}.panel-grid .g-start-8{grid-column-start:8}.panel-grid .g-start-9{grid-column-start:9}.panel-grid .g-start-10{grid-column-start:10}.panel-grid .g-start-11{grid-column-start:11}.panel-grid .g-start-12{grid-column-start:12}.panel-grid .g-start-13{grid-column-start:13}.panel-grid .g-start-14{grid-column-start:14}.panel-grid .g-start-15{grid-column-start:15}.panel-grid .g-start-16{grid-column-start:16}.panel-grid .g-start-17{grid-column-start:17}.panel-grid .g-start-18{grid-column-start:18}.panel-grid .g-start-19{grid-column-start:19}.panel-grid .g-start-20{grid-column-start:20}.panel-grid .g-start-21{grid-column-start:21}.panel-grid .g-start-22{grid-column-start:22}.panel-grid .g-start-23{grid-column-start:23}@media(min-width: 576px){.panel-grid .g-col-sm-1{grid-column:auto/span 1}.panel-grid .g-col-sm-2{grid-column:auto/span 2}.panel-grid .g-col-sm-3{grid-column:auto/span 3}.panel-grid .g-col-sm-4{grid-column:auto/span 4}.panel-grid .g-col-sm-5{grid-column:auto/span 5}.panel-grid .g-col-sm-6{grid-column:auto/span 6}.panel-grid .g-col-sm-7{grid-column:auto/span 7}.panel-grid .g-col-sm-8{grid-column:auto/span 8}.panel-grid .g-col-sm-9{grid-column:auto/span 9}.panel-grid .g-col-sm-10{grid-column:auto/span 10}.panel-grid .g-col-sm-11{grid-column:auto/span 11}.panel-grid .g-col-sm-12{grid-column:auto/span 12}.panel-grid .g-col-sm-13{grid-column:auto/span 13}.panel-grid .g-col-sm-14{grid-column:auto/span 14}.panel-grid .g-col-sm-15{grid-column:auto/span 15}.panel-grid .g-col-sm-16{grid-column:auto/span 16}.panel-grid .g-col-sm-17{grid-column:auto/span 17}.panel-grid .g-col-sm-18{grid-column:auto/span 18}.panel-grid .g-col-sm-19{grid-column:auto/span 19}.panel-grid .g-col-sm-20{grid-column:auto/span 20}.panel-grid .g-col-sm-21{grid-column:auto/span 21}.panel-grid .g-col-sm-22{grid-column:auto/span 22}.panel-grid .g-col-sm-23{grid-column:auto/span 23}.panel-grid .g-col-sm-24{grid-column:auto/span 24}.panel-grid .g-start-sm-1{grid-column-start:1}.panel-grid .g-start-sm-2{grid-column-start:2}.panel-grid .g-start-sm-3{grid-column-start:3}.panel-grid .g-start-sm-4{grid-column-start:4}.panel-grid .g-start-sm-5{grid-column-start:5}.panel-grid .g-start-sm-6{grid-column-start:6}.panel-grid .g-start-sm-7{grid-column-start:7}.panel-grid .g-start-sm-8{grid-column-start:8}.panel-grid .g-start-sm-9{grid-column-start:9}.panel-grid .g-start-sm-10{grid-column-start:10}.panel-grid .g-start-sm-11{grid-column-start:11}.panel-grid .g-start-sm-12{grid-column-start:12}.panel-grid .g-start-sm-13{grid-column-start:13}.panel-grid .g-start-sm-14{grid-column-start:14}.panel-grid .g-start-sm-15{grid-column-start:15}.panel-grid .g-start-sm-16{grid-column-start:16}.panel-grid .g-start-sm-17{grid-column-start:17}.panel-grid .g-start-sm-18{grid-column-start:18}.panel-grid .g-start-sm-19{grid-column-start:19}.panel-grid .g-start-sm-20{grid-column-start:20}.panel-grid .g-start-sm-21{grid-column-start:21}.panel-grid .g-start-sm-22{grid-column-start:22}.panel-grid .g-start-sm-23{grid-column-start:23}}@media(min-width: 768px){.panel-grid .g-col-md-1{grid-column:auto/span 1}.panel-grid .g-col-md-2{grid-column:auto/span 2}.panel-grid .g-col-md-3{grid-column:auto/span 3}.panel-grid .g-col-md-4{grid-column:auto/span 4}.panel-grid .g-col-md-5{grid-column:auto/span 5}.panel-grid .g-col-md-6{grid-column:auto/span 6}.panel-grid .g-col-md-7{grid-column:auto/span 7}.panel-grid .g-col-md-8{grid-column:auto/span 8}.panel-grid .g-col-md-9{grid-column:auto/span 9}.panel-grid .g-col-md-10{grid-column:auto/span 10}.panel-grid .g-col-md-11{grid-column:auto/span 11}.panel-grid .g-col-md-12{grid-column:auto/span 12}.panel-grid .g-col-md-13{grid-column:auto/span 13}.panel-grid .g-col-md-14{grid-column:auto/span 14}.panel-grid .g-col-md-15{grid-column:auto/span 15}.panel-grid .g-col-md-16{grid-column:auto/span 16}.panel-grid .g-col-md-17{grid-column:auto/span 17}.panel-grid .g-col-md-18{grid-column:auto/span 18}.panel-grid .g-col-md-19{grid-column:auto/span 19}.panel-grid .g-col-md-20{grid-column:auto/span 20}.panel-grid .g-col-md-21{grid-column:auto/span 21}.panel-grid .g-col-md-22{grid-column:auto/span 22}.panel-grid .g-col-md-23{grid-column:auto/span 23}.panel-grid .g-col-md-24{grid-column:auto/span 24}.panel-grid .g-start-md-1{grid-column-start:1}.panel-grid .g-start-md-2{grid-column-start:2}.panel-grid .g-start-md-3{grid-column-start:3}.panel-grid .g-start-md-4{grid-column-start:4}.panel-grid .g-start-md-5{grid-column-start:5}.panel-grid .g-start-md-6{grid-column-start:6}.panel-grid .g-start-md-7{grid-column-start:7}.panel-grid .g-start-md-8{grid-column-start:8}.panel-grid .g-start-md-9{grid-column-start:9}.panel-grid .g-start-md-10{grid-column-start:10}.panel-grid .g-start-md-11{grid-column-start:11}.panel-grid .g-start-md-12{grid-column-start:12}.panel-grid .g-start-md-13{grid-column-start:13}.panel-grid .g-start-md-14{grid-column-start:14}.panel-grid .g-start-md-15{grid-column-start:15}.panel-grid .g-start-md-16{grid-column-start:16}.panel-grid .g-start-md-17{grid-column-start:17}.panel-grid .g-start-md-18{grid-column-start:18}.panel-grid .g-start-md-19{grid-column-start:19}.panel-grid .g-start-md-20{grid-column-start:20}.panel-grid .g-start-md-21{grid-column-start:21}.panel-grid .g-start-md-22{grid-column-start:22}.panel-grid .g-start-md-23{grid-column-start:23}}@media(min-width: 992px){.panel-grid .g-col-lg-1{grid-column:auto/span 1}.panel-grid .g-col-lg-2{grid-column:auto/span 2}.panel-grid .g-col-lg-3{grid-column:auto/span 3}.panel-grid .g-col-lg-4{grid-column:auto/span 4}.panel-grid .g-col-lg-5{grid-column:auto/span 5}.panel-grid .g-col-lg-6{grid-column:auto/span 6}.panel-grid .g-col-lg-7{grid-column:auto/span 7}.panel-grid .g-col-lg-8{grid-column:auto/span 8}.panel-grid .g-col-lg-9{grid-column:auto/span 9}.panel-grid .g-col-lg-10{grid-column:auto/span 10}.panel-grid .g-col-lg-11{grid-column:auto/span 11}.panel-grid .g-col-lg-12{grid-column:auto/span 12}.panel-grid .g-col-lg-13{grid-column:auto/span 13}.panel-grid .g-col-lg-14{grid-column:auto/span 14}.panel-grid .g-col-lg-15{grid-column:auto/span 15}.panel-grid .g-col-lg-16{grid-column:auto/span 16}.panel-grid .g-col-lg-17{grid-column:auto/span 17}.panel-grid .g-col-lg-18{grid-column:auto/span 18}.panel-grid .g-col-lg-19{grid-column:auto/span 19}.panel-grid .g-col-lg-20{grid-column:auto/span 20}.panel-grid .g-col-lg-21{grid-column:auto/span 21}.panel-grid .g-col-lg-22{grid-column:auto/span 22}.panel-grid .g-col-lg-23{grid-column:auto/span 23}.panel-grid .g-col-lg-24{grid-column:auto/span 24}.panel-grid .g-start-lg-1{grid-column-start:1}.panel-grid .g-start-lg-2{grid-column-start:2}.panel-grid .g-start-lg-3{grid-column-start:3}.panel-grid .g-start-lg-4{grid-column-start:4}.panel-grid .g-start-lg-5{grid-column-start:5}.panel-grid .g-start-lg-6{grid-column-start:6}.panel-grid .g-start-lg-7{grid-column-start:7}.panel-grid .g-start-lg-8{grid-column-start:8}.panel-grid .g-start-lg-9{grid-column-start:9}.panel-grid .g-start-lg-10{grid-column-start:10}.panel-grid .g-start-lg-11{grid-column-start:11}.panel-grid .g-start-lg-12{grid-column-start:12}.panel-grid .g-start-lg-13{grid-column-start:13}.panel-grid .g-start-lg-14{grid-column-start:14}.panel-grid .g-start-lg-15{grid-column-start:15}.panel-grid .g-start-lg-16{grid-column-start:16}.panel-grid .g-start-lg-17{grid-column-start:17}.panel-grid .g-start-lg-18{grid-column-start:18}.panel-grid .g-start-lg-19{grid-column-start:19}.panel-grid .g-start-lg-20{grid-column-start:20}.panel-grid .g-start-lg-21{grid-column-start:21}.panel-grid .g-start-lg-22{grid-column-start:22}.panel-grid .g-start-lg-23{grid-column-start:23}}@media(min-width: 1200px){.panel-grid .g-col-xl-1{grid-column:auto/span 1}.panel-grid .g-col-xl-2{grid-column:auto/span 2}.panel-grid .g-col-xl-3{grid-column:auto/span 3}.panel-grid .g-col-xl-4{grid-column:auto/span 4}.panel-grid .g-col-xl-5{grid-column:auto/span 5}.panel-grid .g-col-xl-6{grid-column:auto/span 6}.panel-grid .g-col-xl-7{grid-column:auto/span 7}.panel-grid .g-col-xl-8{grid-column:auto/span 8}.panel-grid .g-col-xl-9{grid-column:auto/span 9}.panel-grid .g-col-xl-10{grid-column:auto/span 10}.panel-grid .g-col-xl-11{grid-column:auto/span 11}.panel-grid .g-col-xl-12{grid-column:auto/span 12}.panel-grid .g-col-xl-13{grid-column:auto/span 13}.panel-grid .g-col-xl-14{grid-column:auto/span 14}.panel-grid .g-col-xl-15{grid-column:auto/span 15}.panel-grid .g-col-xl-16{grid-column:auto/span 16}.panel-grid .g-col-xl-17{grid-column:auto/span 17}.panel-grid .g-col-xl-18{grid-column:auto/span 18}.panel-grid .g-col-xl-19{grid-column:auto/span 19}.panel-grid .g-col-xl-20{grid-column:auto/span 20}.panel-grid .g-col-xl-21{grid-column:auto/span 21}.panel-grid .g-col-xl-22{grid-column:auto/span 22}.panel-grid .g-col-xl-23{grid-column:auto/span 23}.panel-grid .g-col-xl-24{grid-column:auto/span 24}.panel-grid .g-start-xl-1{grid-column-start:1}.panel-grid .g-start-xl-2{grid-column-start:2}.panel-grid .g-start-xl-3{grid-column-start:3}.panel-grid .g-start-xl-4{grid-column-start:4}.panel-grid .g-start-xl-5{grid-column-start:5}.panel-grid .g-start-xl-6{grid-column-start:6}.panel-grid .g-start-xl-7{grid-column-start:7}.panel-grid .g-start-xl-8{grid-column-start:8}.panel-grid .g-start-xl-9{grid-column-start:9}.panel-grid .g-start-xl-10{grid-column-start:10}.panel-grid .g-start-xl-11{grid-column-start:11}.panel-grid .g-start-xl-12{grid-column-start:12}.panel-grid .g-start-xl-13{grid-column-start:13}.panel-grid .g-start-xl-14{grid-column-start:14}.panel-grid .g-start-xl-15{grid-column-start:15}.panel-grid .g-start-xl-16{grid-column-start:16}.panel-grid .g-start-xl-17{grid-column-start:17}.panel-grid .g-start-xl-18{grid-column-start:18}.panel-grid .g-start-xl-19{grid-column-start:19}.panel-grid .g-start-xl-20{grid-column-start:20}.panel-grid .g-start-xl-21{grid-column-start:21}.panel-grid .g-start-xl-22{grid-column-start:22}.panel-grid .g-start-xl-23{grid-column-start:23}}@media(min-width: 1400px){.panel-grid .g-col-xxl-1{grid-column:auto/span 1}.panel-grid .g-col-xxl-2{grid-column:auto/span 2}.panel-grid .g-col-xxl-3{grid-column:auto/span 3}.panel-grid .g-col-xxl-4{grid-column:auto/span 4}.panel-grid .g-col-xxl-5{grid-column:auto/span 5}.panel-grid .g-col-xxl-6{grid-column:auto/span 6}.panel-grid .g-col-xxl-7{grid-column:auto/span 7}.panel-grid .g-col-xxl-8{grid-column:auto/span 8}.panel-grid .g-col-xxl-9{grid-column:auto/span 9}.panel-grid .g-col-xxl-10{grid-column:auto/span 10}.panel-grid .g-col-xxl-11{grid-column:auto/span 11}.panel-grid .g-col-xxl-12{grid-column:auto/span 12}.panel-grid .g-col-xxl-13{grid-column:auto/span 13}.panel-grid .g-col-xxl-14{grid-column:auto/span 14}.panel-grid .g-col-xxl-15{grid-column:auto/span 15}.panel-grid .g-col-xxl-16{grid-column:auto/span 16}.panel-grid .g-col-xxl-17{grid-column:auto/span 17}.panel-grid .g-col-xxl-18{grid-column:auto/span 18}.panel-grid .g-col-xxl-19{grid-column:auto/span 19}.panel-grid .g-col-xxl-20{grid-column:auto/span 20}.panel-grid .g-col-xxl-21{grid-column:auto/span 21}.panel-grid .g-col-xxl-22{grid-column:auto/span 22}.panel-grid .g-col-xxl-23{grid-column:auto/span 23}.panel-grid .g-col-xxl-24{grid-column:auto/span 24}.panel-grid .g-start-xxl-1{grid-column-start:1}.panel-grid .g-start-xxl-2{grid-column-start:2}.panel-grid .g-start-xxl-3{grid-column-start:3}.panel-grid .g-start-xxl-4{grid-column-start:4}.panel-grid .g-start-xxl-5{grid-column-start:5}.panel-grid .g-start-xxl-6{grid-column-start:6}.panel-grid .g-start-xxl-7{grid-column-start:7}.panel-grid .g-start-xxl-8{grid-column-start:8}.panel-grid .g-start-xxl-9{grid-column-start:9}.panel-grid .g-start-xxl-10{grid-column-start:10}.panel-grid .g-start-xxl-11{grid-column-start:11}.panel-grid .g-start-xxl-12{grid-column-start:12}.panel-grid .g-start-xxl-13{grid-column-start:13}.panel-grid .g-start-xxl-14{grid-column-start:14}.panel-grid .g-start-xxl-15{grid-column-start:15}.panel-grid .g-start-xxl-16{grid-column-start:16}.panel-grid .g-start-xxl-17{grid-column-start:17}.panel-grid .g-start-xxl-18{grid-column-start:18}.panel-grid .g-start-xxl-19{grid-column-start:19}.panel-grid .g-start-xxl-20{grid-column-start:20}.panel-grid .g-start-xxl-21{grid-column-start:21}.panel-grid .g-start-xxl-22{grid-column-start:22}.panel-grid .g-start-xxl-23{grid-column-start:23}}main{margin-top:1em;margin-bottom:1em}h1,.h1,h2,.h2{color:inherit;margin-top:2rem;margin-bottom:1rem;font-weight:600}h1.title,.title.h1{margin-top:0}main.content>section:first-of-type>h2:first-child,main.content>section:first-of-type>.h2:first-child{margin-top:0}h2,.h2{border-bottom:1px solid #e1e1e2;padding-bottom:.5rem}h3,.h3{font-weight:600}h3,.h3,h4,.h4{opacity:.9;margin-top:1.5rem}h5,.h5,h6,.h6{opacity:.9}.header-section-number{color:#747a7f}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,.figure-caption,.subfigure-caption,.table-caption,figcaption,caption{font-size:.9rem;color:#747a7f}.quarto-layout-cell[data-ref-parent] caption{color:#747a7f}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#747a7f;font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse):first-child{padding-bottom:.5em;display:block}.column-margin.column-container>*:not(.collapse):not(:first-child){padding-top:.5em;padding-bottom:.5em;display:block}.column-margin.column-container>*.collapse:not(.show){display:none}@media(min-width: 768px){.column-margin.column-container .callout-margin-content:first-child{margin-top:4.5em}.column-margin.column-container .callout-margin-content-simple:first-child{margin-top:3.5em}}.margin-caption>*{padding-top:.5em;padding-bottom:.5em}@media(max-width: 767.98px){.quarto-layout-row{flex-direction:column}}.nav-tabs .nav-item{margin-top:1px;cursor:pointer}.tab-content{margin-top:0px;border-left:#e1e1e2 1px solid;border-right:#e1e1e2 1px solid;border-bottom:#e1e1e2 1px solid;margin-left:0;padding:1em;margin-bottom:1em}@media(max-width: 767.98px){.layout-sidebar{margin-left:0;margin-right:0}}.panel-sidebar,.panel-sidebar .form-control,.panel-input,.panel-input .form-control,.selectize-dropdown{font-size:.9rem}.panel-sidebar .form-control,.panel-input .form-control{padding-top:.1rem}.tab-pane div.sourceCode{margin-top:0px}.tab-pane>p{padding-top:0}.tab-pane>p:nth-child(1){padding-top:0}.tab-pane>p:last-child{margin-bottom:0}.tab-pane>pre:last-child{margin-bottom:0}.tab-content>.tab-pane:not(.active){display:none !important}div.sourceCode{background-color:rgba(233,236,239,.65);border:1px solid rgba(233,236,239,.65)}pre.sourceCode{background-color:rgba(0,0,0,0)}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:#747a7f}.blockquote h1:first-child,.blockquote .h1:first-child,.blockquote h2:first-child,.blockquote .h2:first-child,.blockquote h3:first-child,.blockquote .h3:first-child,.blockquote h4:first-child,.blockquote .h4:first-child,.blockquote h5:first-child,.blockquote .h5:first-child{margin-top:0}pre{background-color:initial;padding:initial;border:initial}p pre code:not(.sourceCode),li pre code:not(.sourceCode),pre code:not(.sourceCode){background-color:initial}p code:not(.sourceCode),li code:not(.sourceCode),td code:not(.sourceCode){background-color:#f8f9fa;padding:.2em}nav p code:not(.sourceCode),nav li code:not(.sourceCode),nav td code:not(.sourceCode){background-color:rgba(0,0,0,0);padding:0}td code:not(.sourceCode){white-space:pre-wrap}#quarto-embedded-source-code-modal>.modal-dialog{max-width:1000px;padding-left:1.75rem;padding-right:1.75rem}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body{padding:0}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body div.sourceCode{margin:0;padding:.2rem .2rem;border-radius:0px;border:none}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-header{padding:.7rem}.code-tools-button{font-size:1rem;padding:.15rem .15rem;margin-left:5px;color:rgba(55,58,60,.75);background-color:rgba(0,0,0,0);transition:initial;cursor:pointer}.code-tools-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.sidebar{will-change:top;transition:top 200ms linear;position:sticky;overflow-y:auto;padding-top:1.2em;max-height:100vh}.sidebar.toc-left,.sidebar.margin-sidebar{top:0px;padding-top:1em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}figure .quarto-notebook-link{margin-top:.5em}.quarto-notebook-link{font-size:.75em;color:rgba(55,58,60,.75);margin-bottom:1em;text-decoration:none;display:block}.quarto-notebook-link:hover{text-decoration:underline;color:#2761e3}.quarto-notebook-link::before{display:inline-block;height:.75rem;width:.75rem;margin-bottom:0em;margin-right:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}.toc-actions i.bi,.quarto-code-links i.bi,.quarto-other-links i.bi,.quarto-alternate-notebooks i.bi,.quarto-alternate-formats i.bi{margin-right:.4em;font-size:.8rem}.quarto-other-links-text-target .quarto-code-links i.bi,.quarto-other-links-text-target .quarto-other-links i.bi{margin-right:.2em}.quarto-other-formats-text-target .quarto-alternate-formats i.bi{margin-right:.1em}.toc-actions i.bi.empty,.quarto-code-links i.bi.empty,.quarto-other-links i.bi.empty,.quarto-alternate-notebooks i.bi.empty,.quarto-alternate-formats i.bi.empty{padding-left:1em}.quarto-notebook h2,.quarto-notebook .h2{border-bottom:none}.quarto-notebook .cell-container{display:flex}.quarto-notebook .cell-container .cell{flex-grow:4}.quarto-notebook .cell-container .cell-decorator{padding-top:1.5em;padding-right:1em;text-align:right}.quarto-notebook .cell-container.code-fold .cell-decorator{padding-top:3em}.quarto-notebook .cell-code code{white-space:pre-wrap}.quarto-notebook .cell .cell-output-stderr pre code,.quarto-notebook .cell .cell-output-stdout pre code{white-space:pre-wrap;overflow-wrap:anywhere}.toc-actions,.quarto-alternate-formats,.quarto-other-links,.quarto-code-links,.quarto-alternate-notebooks{padding-left:0em}.sidebar .toc-actions a,.sidebar .quarto-alternate-formats a,.sidebar .quarto-other-links a,.sidebar .quarto-code-links a,.sidebar .quarto-alternate-notebooks a,.sidebar nav[role=doc-toc] a{text-decoration:none}.sidebar .toc-actions a:hover,.sidebar .quarto-other-links a:hover,.sidebar .quarto-code-links a:hover,.sidebar .quarto-alternate-formats a:hover,.sidebar .quarto-alternate-notebooks a:hover{color:#2761e3}.sidebar .toc-actions h2,.sidebar .toc-actions .h2,.sidebar .quarto-code-links h2,.sidebar .quarto-code-links .h2,.sidebar .quarto-other-links h2,.sidebar .quarto-other-links .h2,.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2,.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-weight:500;margin-bottom:.2rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar .toc-actions>h2,.sidebar .toc-actions>.h2,.sidebar .quarto-code-links>h2,.sidebar .quarto-code-links>.h2,.sidebar .quarto-other-links>h2,.sidebar .quarto-other-links>.h2,.sidebar .quarto-alternate-notebooks>h2,.sidebar .quarto-alternate-notebooks>.h2,.sidebar .quarto-alternate-formats>h2,.sidebar .quarto-alternate-formats>.h2{font-size:.8rem}.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-size:.875rem}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #e9ecef;padding-left:.6rem}.sidebar .toc-actions h2>ul a,.sidebar .toc-actions .h2>ul a,.sidebar .quarto-code-links h2>ul a,.sidebar .quarto-code-links .h2>ul a,.sidebar .quarto-other-links h2>ul a,.sidebar .quarto-other-links .h2>ul a,.sidebar .quarto-alternate-notebooks h2>ul a,.sidebar .quarto-alternate-notebooks .h2>ul a,.sidebar .quarto-alternate-formats h2>ul a,.sidebar .quarto-alternate-formats .h2>ul a{border-left:none;padding-left:.6rem}.sidebar .toc-actions ul a:empty,.sidebar .quarto-code-links ul a:empty,.sidebar .quarto-other-links ul a:empty,.sidebar .quarto-alternate-notebooks ul a:empty,.sidebar .quarto-alternate-formats ul a:empty,.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar .toc-actions ul,.sidebar .quarto-code-links ul,.sidebar .quarto-other-links ul,.sidebar .quarto-alternate-notebooks ul,.sidebar .quarto-alternate-formats ul{padding-left:0;list-style:none}.sidebar nav[role=doc-toc] ul{list-style:none;padding-left:0;list-style:none}.sidebar nav[role=doc-toc]>ul{margin-left:.45em}.quarto-margin-sidebar nav[role=doc-toc]{padding-left:.5em}.sidebar .toc-actions>ul,.sidebar .quarto-code-links>ul,.sidebar .quarto-other-links>ul,.sidebar .quarto-alternate-notebooks>ul,.sidebar .quarto-alternate-formats>ul{font-size:.8rem}.sidebar nav[role=doc-toc]>ul{font-size:.875rem}.sidebar .toc-actions ul li a,.sidebar .quarto-code-links ul li a,.sidebar .quarto-other-links ul li a,.sidebar .quarto-alternate-notebooks ul li a,.sidebar .quarto-alternate-formats ul li a,.sidebar nav[role=doc-toc]>ul li a{line-height:1.1rem;padding-bottom:.2rem;padding-top:.2rem;color:inherit}.sidebar nav[role=doc-toc] ul>li>ul>li>a{padding-left:1.2em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>a{padding-left:2.4em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>a{padding-left:3.6em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:4.8em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:6em}.sidebar nav[role=doc-toc] ul>li>a.active,.sidebar nav[role=doc-toc] ul>li>ul>li>a.active{border-left:1px solid #2761e3;color:#2761e3 !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#2761e3 !important}kbd,.kbd{color:#373a3c;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#e1e1e2}.quarto-appendix-contents div.hanging-indent{margin-left:0em}.quarto-appendix-contents div.hanging-indent div.csl-entry{margin-left:1em;text-indent:-1em}.citation a,.footnote-ref{text-decoration:none}.footnotes ol{padding-left:1em}.tippy-content>*{margin-bottom:.7em}.tippy-content>*:last-child{margin-bottom:0}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.375rem;overflow-wrap:break-word}.callout .callout-title-container{overflow-wrap:anywhere}.callout.callout-style-simple{padding:.4em .7em;border-left:5px solid;border-right:1px solid #e1e1e2;border-top:1px solid #e1e1e2;border-bottom:1px solid #e1e1e2}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #e1e1e2;border-top:1px solid #e1e1e2;border-bottom:1px solid #e1e1e2}.callout .callout-body-container{flex-grow:1}.callout.callout-style-simple .callout-body{font-size:.9rem;font-weight:400}.callout.callout-style-default .callout-body{font-size:.9rem;font-weight:400}.callout:not(.no-icon).callout-titled.callout-style-simple .callout-body{padding-left:1.6em}.callout.callout-titled>.callout-header{padding-top:.2em;margin-bottom:-0.2em}.callout.callout-style-simple>div.callout-header{border-bottom:none;font-size:.9rem;font-weight:600;opacity:75%}.callout.callout-style-default>div.callout-header{border-bottom:none;font-weight:600;opacity:85%;font-size:.9rem;padding-left:.5em;padding-right:.5em}.callout.callout-style-default .callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default .callout-body>:first-child{padding-top:.5rem;margin-top:0}.callout>div.callout-header[data-bs-toggle=collapse]{cursor:pointer}.callout.callout-style-default .callout-header[aria-expanded=false],.callout.callout-style-default .callout-header[aria-expanded=true]{padding-top:0px;margin-bottom:0px;align-items:center}.callout.callout-titled .callout-body>:last-child:not(.sourceCode),.callout.callout-titled .callout-body>div>:last-child:not(.sourceCode){padding-bottom:.5rem;margin-bottom:0}.callout:not(.callout-titled) .callout-body>:first-child,.callout:not(.callout-titled) .callout-body>div>:first-child{margin-top:.25rem}.callout:not(.callout-titled) .callout-body>:last-child,.callout:not(.callout-titled) .callout-body>div>:last-child{margin-bottom:.2rem}.callout.callout-style-simple .callout-icon::before,.callout.callout-style-simple .callout-toggle::before{height:1rem;width:1rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.callout.callout-style-default .callout-icon::before,.callout.callout-style-default .callout-toggle::before{height:.9rem;width:.9rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:.9rem .9rem}.callout.callout-style-default .callout-toggle::before{margin-top:5px}.callout .callout-btn-toggle .callout-toggle::before{transition:transform .2s linear}.callout .callout-header[aria-expanded=false] .callout-toggle::before{transform:rotate(-90deg)}.callout .callout-header[aria-expanded=true] .callout-toggle::before{transform:none}.callout.callout-style-simple:not(.no-icon) div.callout-icon-container{padding-top:.2em;padding-right:.55em}.callout.callout-style-default:not(.no-icon) div.callout-icon-container{padding-top:.1em;padding-right:.35em}.callout.callout-style-default:not(.no-icon) div.callout-title-container{margin-top:-1px}.callout.callout-style-default.callout-caution:not(.no-icon) div.callout-icon-container{padding-top:.3em;padding-right:.35em}.callout>.callout-body>.callout-icon-container>.no-icon,.callout>.callout-header>.callout-icon-container>.no-icon{display:none}div.callout.callout{border-left-color:rgba(55,58,60,.75)}div.callout.callout-style-default>.callout-header{background-color:rgba(55,58,60,.75)}div.callout-note.callout{border-left-color:#2780e3}div.callout-note.callout-style-default>.callout-header{background-color:#e9f2fc}div.callout-note:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-tip.callout{border-left-color:#3fb618}div.callout-tip.callout-style-default>.callout-header{background-color:#ecf8e8}div.callout-tip:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-warning.callout{border-left-color:#ff7518}div.callout-warning.callout-style-default>.callout-header{background-color:#fff1e8}div.callout-warning:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-caution.callout{border-left-color:#f0ad4e}div.callout-caution.callout-style-default>.callout-header{background-color:#fef7ed}div.callout-caution:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-important.callout{border-left-color:#ff0039}div.callout-important.callout-style-default>.callout-header{background-color:#ffe6eb}div.callout-important:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important .callout-toggle::before{background-image:url('data:image/svg+xml,')}.quarto-toggle-container{display:flex;align-items:center}.quarto-reader-toggle .bi::before,.quarto-color-scheme-toggle .bi::before{display:inline-block;height:1rem;width:1rem;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.sidebar-navigation{padding-left:20px}.navbar{background-color:#f8f9fa;color:#545555}.navbar .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.quarto-sidebar-toggle{border-color:#e1e1e2;border-bottom-left-radius:.375rem;border-bottom-right-radius:.375rem;border-style:solid;border-width:1px;overflow:hidden;border-top-width:0px;padding-top:0px !important}.quarto-sidebar-toggle-title{cursor:pointer;padding-bottom:2px;margin-left:.25em;text-align:center;font-weight:400;font-size:.775em}#quarto-content .quarto-sidebar-toggle{background:#fafafa}#quarto-content .quarto-sidebar-toggle-title{color:#373a3c}.quarto-sidebar-toggle-icon{color:#e1e1e2;margin-right:.5em;float:right;transition:transform .2s ease}.quarto-sidebar-toggle-icon::before{padding-top:5px}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-icon{transform:rotate(-180deg)}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-title{border-bottom:solid #e1e1e2 1px}.quarto-sidebar-toggle-contents{background-color:#fff;padding-right:10px;padding-left:10px;margin-top:0px !important;transition:max-height .5s ease}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-contents{padding-top:1em;padding-bottom:10px}@media(max-width: 767.98px){.sidebar-menu-container{padding-bottom:5em}}.quarto-sidebar-toggle:not(.expanded) .quarto-sidebar-toggle-contents{padding-top:0px !important;padding-bottom:0px}nav[role=doc-toc]{z-index:1020}#quarto-sidebar>*,nav[role=doc-toc]>*{transition:opacity .1s ease,border .1s ease}#quarto-sidebar.slow>*,nav[role=doc-toc].slow>*{transition:opacity .4s ease,border .4s ease}.quarto-color-scheme-toggle:not(.alternate).top-right .bi::before{background-image:url('data:image/svg+xml,')}.quarto-color-scheme-toggle.alternate.top-right .bi::before{background-image:url('data:image/svg+xml,')}#quarto-appendix.default{border-top:1px solid #e1e1e2}#quarto-appendix.default{background-color:#fff;padding-top:1.5em;margin-top:2em;z-index:998}#quarto-appendix.default .quarto-appendix-heading{margin-top:0;line-height:1.4em;font-weight:600;opacity:.9;border-bottom:none;margin-bottom:0}#quarto-appendix.default .footnotes ol,#quarto-appendix.default .footnotes ol li>p:last-of-type,#quarto-appendix.default .quarto-appendix-contents>p:last-of-type{margin-bottom:0}#quarto-appendix.default .footnotes ol{margin-left:.5em}#quarto-appendix.default .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #e1e1e2;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-bibtex code.sourceCode{white-space:pre-wrap}#quarto-appendix.default .quarto-appendix-citeas{font-size:.9em;padding:1em;border:solid 1px #e1e1e2;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-heading{font-size:1em !important}#quarto-appendix.default *[role=doc-endnotes]>ol,#quarto-appendix.default .quarto-appendix-contents>*:not(h2):not(.h2){font-size:.9em}#quarto-appendix.default section{padding-bottom:1.5em}#quarto-appendix.default section *[role=doc-endnotes],#quarto-appendix.default section>*:not(a){opacity:.9;word-wrap:break-word}.btn.btn-quarto,div.cell-output-display .btn-quarto{--bs-btn-color: #cbcccc;--bs-btn-bg: #373a3c;--bs-btn-border-color: #373a3c;--bs-btn-hover-color: #cbcccc;--bs-btn-hover-bg: #555859;--bs-btn-hover-border-color: #4b4e50;--bs-btn-focus-shadow-rgb: 77, 80, 82;--bs-btn-active-color: #fff;--bs-btn-active-bg: #5f6163;--bs-btn-active-border-color: #4b4e50;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #fff;--bs-btn-disabled-bg: #373a3c;--bs-btn-disabled-border-color: #373a3c}nav.quarto-secondary-nav.color-navbar{background-color:#f8f9fa;color:#545555}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#545555}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner{margin-bottom:0;padding-bottom:1em}body.nav-sidebar #title-block-header{margin-block-end:0}}p.subtitle{margin-top:.25em;margin-bottom:.5em}code a:any-link{color:inherit;text-decoration-color:#868e96}/*! light */div.observablehq table thead tr th{background-color:var(--bs-body-bg)}input,button,select,optgroup,textarea{background-color:var(--bs-body-bg)}.code-annotated .code-copy-button{margin-right:1.25em;margin-top:0;padding-bottom:0;padding-top:3px}.code-annotation-gutter-bg{background-color:#fff}.code-annotation-gutter{background-color:rgba(233,236,239,.65)}.code-annotation-gutter,.code-annotation-gutter-bg{height:100%;width:calc(20px + .5em);position:absolute;top:0;right:0}dl.code-annotation-container-grid dt{margin-right:1em;margin-top:.25rem}dl.code-annotation-container-grid dt{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;color:#4f5457;border:solid #4f5457 1px;border-radius:50%;height:22px;width:22px;line-height:22px;font-size:11px;text-align:center;vertical-align:middle;text-decoration:none}dl.code-annotation-container-grid dt[data-target-cell]{cursor:pointer}dl.code-annotation-container-grid dt[data-target-cell].code-annotation-active{color:#fff;border:solid #aaa 1px;background-color:#aaa}pre.code-annotation-code{padding-top:0;padding-bottom:0}pre.code-annotation-code code{z-index:3}#code-annotation-line-highlight-gutter{width:100%;border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}#code-annotation-line-highlight{margin-left:-4em;width:calc(100% + 4em);border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}code.sourceCode .code-annotation-anchor.code-annotation-active{background-color:var(--quarto-hl-normal-color, #aaaaaa);border:solid var(--quarto-hl-normal-color, #aaaaaa) 1px;color:#e9ecef;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;color:var(--quarto-hl-co-color);border:solid var(--quarto-hl-co-color) 1px;border-radius:50%;height:18px;width:18px;font-size:9px;margin-top:2px}code.sourceCode button.code-annotation-anchor{padding:2px;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none}code.sourceCode a.code-annotation-anchor{line-height:18px;text-align:center;vertical-align:middle;cursor:default;text-decoration:none}@media print{.page-columns .column-screen-inset{grid-column:page-start-inset/page-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;opacity:.999}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;opacity:.999}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#f8f9fa;z-index:998;opacity:.999;margin-bottom:1em}}.quarto-video{margin-bottom:1em}.table{border-top:1px solid #d7d8d8;border-bottom:1px solid #d7d8d8}.table>thead{border-top-width:0;border-bottom:1px solid #9b9d9e}.table a{word-break:break-word}.table>:not(caption)>*>*{background-color:unset;color:unset}#quarto-document-content .crosstalk-input .checkbox input[type=checkbox],#quarto-document-content .crosstalk-input .checkbox-inline input[type=checkbox]{position:unset;margin-top:unset;margin-left:unset}#quarto-document-content .row{margin-left:unset;margin-right:unset}.quarto-xref{white-space:nowrap}#quarto-draft-alert{margin-top:0px;margin-bottom:0px;padding:.3em;text-align:center;font-size:.9em}#quarto-draft-alert i{margin-right:.3em}#quarto-back-to-top{z-index:1000}pre{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:0.875em;font-weight:400}pre code{font-family:inherit;font-size:inherit;font-weight:inherit}code{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:0.875em;font-weight:400}a{background-color:rgba(0,0,0,0);font-weight:400;text-decoration:underline}a.external:after{content:"";background-image:url('data:image/svg+xml,');background-size:contain;background-repeat:no-repeat;background-position:center center;margin-left:.2em;padding-right:.75em}div.sourceCode code a.external:after{content:none}a.external:after:hover{cursor:pointer}.quarto-ext-icon{display:inline-block;font-size:.75em;padding-left:.3em}.code-with-filename .code-with-filename-file{margin-bottom:0;padding-bottom:2px;padding-top:2px;padding-left:.7em;border:var(--quarto-border-width) solid var(--quarto-border-color);border-radius:var(--quarto-border-radius);border-bottom:0;border-bottom-left-radius:0%;border-bottom-right-radius:0%}.code-with-filename div.sourceCode,.reveal .code-with-filename div.sourceCode{margin-top:0;border-top-left-radius:0%;border-top-right-radius:0%}.code-with-filename .code-with-filename-file pre{margin-bottom:0}.code-with-filename .code-with-filename-file{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.quarto-title-banner{margin-bottom:1em;color:#545555;background:#f8f9fa}.quarto-title-banner a{color:#545555}.quarto-title-banner h1,.quarto-title-banner .h1,.quarto-title-banner h2,.quarto-title-banner .h2{color:#545555}.quarto-title-banner .code-tools-button{color:#878888}.quarto-title-banner .code-tools-button:hover{color:#545555}.quarto-title-banner .code-tools-button>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .quarto-title .title{font-weight:600}.quarto-title-banner .quarto-categories{margin-top:.75em}@media(min-width: 992px){.quarto-title-banner{padding-top:2.5em;padding-bottom:2.5em}}@media(max-width: 991.98px){.quarto-title-banner{padding-top:1em;padding-bottom:1em}}@media(max-width: 767.98px){body.hypothesis-enabled #title-block-header>*{padding-right:20px}}main.quarto-banner-title-block>section:first-child>h2,main.quarto-banner-title-block>section:first-child>.h2,main.quarto-banner-title-block>section:first-child>h3,main.quarto-banner-title-block>section:first-child>.h3,main.quarto-banner-title-block>section:first-child>h4,main.quarto-banner-title-block>section:first-child>.h4{margin-top:0}.quarto-title .quarto-categories{display:flex;flex-wrap:wrap;row-gap:.5em;column-gap:.4em;padding-bottom:.5em;margin-top:.75em}.quarto-title .quarto-categories .quarto-category{padding:.25em .75em;font-size:.65em;text-transform:uppercase;border:solid 1px;border-radius:.375rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}.quarto-title-meta-container{display:grid;grid-template-columns:1fr auto}.quarto-title-meta-column-end{display:flex;flex-direction:column;padding-left:1em}.quarto-title-meta-column-end a .bi{margin-right:.3em}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:repeat(2, 1fr);grid-column-gap:1em}#title-block-header.quarto-title-block.default .quarto-title .title{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-author-orcid img{margin-top:-0.2em;height:.8em;width:.8em}#title-block-header.quarto-title-block.default .quarto-title-author-email{opacity:.7}#title-block-header.quarto-title-block.default .quarto-description p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p,#title-block-header.quarto-title-block.default .quarto-title-authors p,#title-block-header.quarto-title-block.default .quarto-title-affiliations p{margin-bottom:.1em}#title-block-header.quarto-title-block.default .quarto-title-meta-heading{text-transform:uppercase;margin-top:1em;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-contents{font-size:.9em}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.1em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#title-block-header.quarto-title-block.default .keywords,#title-block-header.quarto-title-block.default .description,#title-block-header.quarto-title-block.default .abstract{margin-top:0}#title-block-header.quarto-title-block.default .keywords>p,#title-block-header.quarto-title-block.default .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#title-block-header.quarto-title-block.default .keywords>p:last-of-type,#title-block-header.quarto-title-block.default .description>p:last-of-type,#title-block-header.quarto-title-block.default .abstract>p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .keywords .block-title,#title-block-header.quarto-title-block.default .description .block-title,#title-block-header.quarto-title-block.default .abstract .block-title{margin-top:1em;text-transform:uppercase;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-author{display:grid;grid-template-columns:minmax(max-content, 1fr) 1fr;grid-column-gap:1em}.quarto-title-tools-only{display:flex;justify-content:right}body{-webkit-font-smoothing:antialiased}.badge.bg-light{color:#373a3c}.progress .progress-bar{font-size:8px;line-height:8px}:root{--quarto-scss-export-white: #fff;--quarto-scss-export-gray-100: #f8f9fa;--quarto-scss-export-gray-200: #e9ecef;--quarto-scss-export-gray-300: #dee2e6;--quarto-scss-export-gray-400: #ced4da;--quarto-scss-export-gray-500: #adb5bd;--quarto-scss-export-gray-600: #868e96;--quarto-scss-export-gray-700: #495057;--quarto-scss-export-gray-800: #373a3c;--quarto-scss-export-gray-900: #212529;--quarto-scss-export-black: #000;--quarto-scss-export-blue: #2780e3;--quarto-scss-export-indigo: #6610f2;--quarto-scss-export-purple: #613d7c;--quarto-scss-export-pink: #e83e8c;--quarto-scss-export-red: #ff0039;--quarto-scss-export-orange: #f0ad4e;--quarto-scss-export-yellow: #ff7518;--quarto-scss-export-green: #3fb618;--quarto-scss-export-teal: #20c997;--quarto-scss-export-cyan: #9954bb;--quarto-scss-export-primary: #2780e3;--quarto-scss-export-secondary: #373a3c;--quarto-scss-export-success: #3fb618;--quarto-scss-export-info: #9954bb;--quarto-scss-export-warning: #ff7518;--quarto-scss-export-danger: #ff0039;--quarto-scss-export-light: #f8f9fa;--quarto-scss-export-dark: #373a3c;--quarto-scss-export-body-color: #373a3c;--quarto-scss-export-title-banner-color: ;--quarto-scss-export-title-banner-bg: ;--quarto-scss-export-btn-code-copy-color: #5E5E5E;--quarto-scss-export-btn-code-copy-color-active: #4758AB;--quarto-scss-export-sidebar-bg: #fff;--quarto-scss-export-link-color: #2761e3;--quarto-scss-export-link-color-bg: transparent;--quarto-scss-export-code-color: #7d12ba;--quarto-scss-export-code-bg: #f8f9fa;--quarto-scss-export-toc-color: #2761e3;--quarto-scss-export-toc-active-border: #2761e3;--quarto-scss-export-toc-inactive-border: #e9ecef;--quarto-scss-export-navbar-default: #2780e3;--quarto-scss-export-navbar-hl-override: false;--quarto-scss-export-navbar-bg: #f8f9fa;--quarto-scss-export-btn-bg: #373a3c;--quarto-scss-export-btn-fg: #cbcccc;--quarto-scss-export-body-contrast-bg: #fff;--quarto-scss-export-body-contrast-color: #373a3c;--quarto-scss-export-navbar-fg: #545555;--quarto-scss-export-navbar-hl: #1f4eb6;--quarto-scss-export-navbar-brand: #545555;--quarto-scss-export-navbar-brand-hl: #1f4eb6;--quarto-scss-export-navbar-toggler-border-color: rgba(84, 85, 85, 0);--quarto-scss-export-navbar-hover-color: rgba(31, 78, 182, 0.8);--quarto-scss-export-navbar-disabled-color: rgba(84, 85, 85, 0.75);--quarto-scss-export-sidebar-fg: #595959;--quarto-scss-export-sidebar-hl: ;--quarto-scss-export-title-block-color: #373a3c;--quarto-scss-export-title-block-contast-color: #fff;--quarto-scss-export-footer-bg: #fff;--quarto-scss-export-footer-fg: #757575;--quarto-scss-export-popover-bg: #fff;--quarto-scss-export-input-bg: #fff;--quarto-scss-export-input-border-color: #e1e1e2;--quarto-scss-export-code-annotation-higlight-color: rgba(170, 170, 170, 0.2666666667);--quarto-scss-export-code-annotation-higlight-bg: rgba(170, 170, 170, 0.1333333333);--quarto-scss-export-table-group-separator-color: #9b9d9e;--quarto-scss-export-table-group-separator-color-lighter: #d7d8d8;--quarto-scss-export-link-decoration: underline;--quarto-scss-export-border-color: #e1e1e2;--quarto-scss-export-table-border-color: #e1e1e2;--quarto-scss-export-color-contrast-dark: #000;--quarto-scss-export-color-contrast-light: #fff;--quarto-scss-export-blue-100: #d4e6f9;--quarto-scss-export-blue-200: #a9ccf4;--quarto-scss-export-blue-300: #7db3ee;--quarto-scss-export-blue-400: #5299e9;--quarto-scss-export-blue-500: #2780e3;--quarto-scss-export-blue-600: #1f66b6;--quarto-scss-export-blue-700: #174d88;--quarto-scss-export-blue-800: #10335b;--quarto-scss-export-blue-900: #081a2d;--quarto-scss-export-indigo-100: #e0cffc;--quarto-scss-export-indigo-200: #c29ffa;--quarto-scss-export-indigo-300: #a370f7;--quarto-scss-export-indigo-400: #8540f5;--quarto-scss-export-indigo-500: #6610f2;--quarto-scss-export-indigo-600: #520dc2;--quarto-scss-export-indigo-700: #3d0a91;--quarto-scss-export-indigo-800: #290661;--quarto-scss-export-indigo-900: #140330;--quarto-scss-export-purple-100: #dfd8e5;--quarto-scss-export-purple-200: #c0b1cb;--quarto-scss-export-purple-300: #a08bb0;--quarto-scss-export-purple-400: #816496;--quarto-scss-export-purple-500: #613d7c;--quarto-scss-export-purple-600: #4e3163;--quarto-scss-export-purple-700: #3a254a;--quarto-scss-export-purple-800: #271832;--quarto-scss-export-purple-900: #130c19;--quarto-scss-export-pink-100: #fad8e8;--quarto-scss-export-pink-200: #f6b2d1;--quarto-scss-export-pink-300: #f18bba;--quarto-scss-export-pink-400: #ed65a3;--quarto-scss-export-pink-500: #e83e8c;--quarto-scss-export-pink-600: #ba3270;--quarto-scss-export-pink-700: #8b2554;--quarto-scss-export-pink-800: #5d1938;--quarto-scss-export-pink-900: #2e0c1c;--quarto-scss-export-red-100: #ffccd7;--quarto-scss-export-red-200: #ff99b0;--quarto-scss-export-red-300: #ff6688;--quarto-scss-export-red-400: #ff3361;--quarto-scss-export-red-500: #ff0039;--quarto-scss-export-red-600: #cc002e;--quarto-scss-export-red-700: #990022;--quarto-scss-export-red-800: #660017;--quarto-scss-export-red-900: #33000b;--quarto-scss-export-orange-100: #fcefdc;--quarto-scss-export-orange-200: #f9deb8;--quarto-scss-export-orange-300: #f6ce95;--quarto-scss-export-orange-400: #f3bd71;--quarto-scss-export-orange-500: #f0ad4e;--quarto-scss-export-orange-600: #c08a3e;--quarto-scss-export-orange-700: #90682f;--quarto-scss-export-orange-800: #60451f;--quarto-scss-export-orange-900: #302310;--quarto-scss-export-yellow-100: #ffe3d1;--quarto-scss-export-yellow-200: #ffc8a3;--quarto-scss-export-yellow-300: #ffac74;--quarto-scss-export-yellow-400: #ff9146;--quarto-scss-export-yellow-500: #ff7518;--quarto-scss-export-yellow-600: #cc5e13;--quarto-scss-export-yellow-700: #99460e;--quarto-scss-export-yellow-800: #662f0a;--quarto-scss-export-yellow-900: #331705;--quarto-scss-export-green-100: #d9f0d1;--quarto-scss-export-green-200: #b2e2a3;--quarto-scss-export-green-300: #8cd374;--quarto-scss-export-green-400: #65c546;--quarto-scss-export-green-500: #3fb618;--quarto-scss-export-green-600: #329213;--quarto-scss-export-green-700: #266d0e;--quarto-scss-export-green-800: #19490a;--quarto-scss-export-green-900: #0d2405;--quarto-scss-export-teal-100: #d2f4ea;--quarto-scss-export-teal-200: #a6e9d5;--quarto-scss-export-teal-300: #79dfc1;--quarto-scss-export-teal-400: #4dd4ac;--quarto-scss-export-teal-500: #20c997;--quarto-scss-export-teal-600: #1aa179;--quarto-scss-export-teal-700: #13795b;--quarto-scss-export-teal-800: #0d503c;--quarto-scss-export-teal-900: #06281e;--quarto-scss-export-cyan-100: #ebddf1;--quarto-scss-export-cyan-200: #d6bbe4;--quarto-scss-export-cyan-300: #c298d6;--quarto-scss-export-cyan-400: #ad76c9;--quarto-scss-export-cyan-500: #9954bb;--quarto-scss-export-cyan-600: #7a4396;--quarto-scss-export-cyan-700: #5c3270;--quarto-scss-export-cyan-800: #3d224b;--quarto-scss-export-cyan-900: #1f1125;--quarto-scss-export-default: #373a3c;--quarto-scss-export-primary-text-emphasis: #10335b;--quarto-scss-export-secondary-text-emphasis: #161718;--quarto-scss-export-success-text-emphasis: #19490a;--quarto-scss-export-info-text-emphasis: #3d224b;--quarto-scss-export-warning-text-emphasis: #662f0a;--quarto-scss-export-danger-text-emphasis: #660017;--quarto-scss-export-light-text-emphasis: #495057;--quarto-scss-export-dark-text-emphasis: #495057;--quarto-scss-export-primary-bg-subtle: #d4e6f9;--quarto-scss-export-secondary-bg-subtle: #d7d8d8;--quarto-scss-export-success-bg-subtle: #d9f0d1;--quarto-scss-export-info-bg-subtle: #ebddf1;--quarto-scss-export-warning-bg-subtle: #ffe3d1;--quarto-scss-export-danger-bg-subtle: #ffccd7;--quarto-scss-export-light-bg-subtle: #fcfcfd;--quarto-scss-export-dark-bg-subtle: #ced4da;--quarto-scss-export-primary-border-subtle: #a9ccf4;--quarto-scss-export-secondary-border-subtle: #afb0b1;--quarto-scss-export-success-border-subtle: #b2e2a3;--quarto-scss-export-info-border-subtle: #d6bbe4;--quarto-scss-export-warning-border-subtle: #ffc8a3;--quarto-scss-export-danger-border-subtle: #ff99b0;--quarto-scss-export-light-border-subtle: #e9ecef;--quarto-scss-export-dark-border-subtle: #adb5bd;--quarto-scss-export-body-text-align: ;--quarto-scss-export-body-bg: #fff;--quarto-scss-export-body-secondary-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-body-secondary-bg: #e9ecef;--quarto-scss-export-body-tertiary-color: rgba(55, 58, 60, 0.5);--quarto-scss-export-body-tertiary-bg: #f8f9fa;--quarto-scss-export-body-emphasis-color: #000;--quarto-scss-export-link-hover-color: #1f4eb6;--quarto-scss-export-link-hover-decoration: ;--quarto-scss-export-border-color-translucent: rgba(0, 0, 0, 0.175);--quarto-scss-export-component-active-bg: #2780e3;--quarto-scss-export-component-active-color: #fff;--quarto-scss-export-focus-ring-color: rgba(39, 128, 227, 0.25);--quarto-scss-export-headings-font-family: ;--quarto-scss-export-headings-font-style: ;--quarto-scss-export-display-font-family: ;--quarto-scss-export-display-font-style: ;--quarto-scss-export-text-muted: rgba(55, 58, 60, 0.75);--quarto-scss-export-blockquote-footer-color: #868e96;--quarto-scss-export-blockquote-border-color: #e9ecef;--quarto-scss-export-hr-bg-color: ;--quarto-scss-export-hr-height: ;--quarto-scss-export-hr-border-color: ;--quarto-scss-export-legend-font-weight: ;--quarto-scss-export-mark-bg: #ffe3d1;--quarto-scss-export-table-color: #373a3c;--quarto-scss-export-table-bg: #fff;--quarto-scss-export-table-accent-bg: transparent;--quarto-scss-export-table-th-font-weight: ;--quarto-scss-export-table-striped-color: #373a3c;--quarto-scss-export-table-striped-bg: rgba(0, 0, 0, 0.05);--quarto-scss-export-table-active-color: #373a3c;--quarto-scss-export-table-active-bg: rgba(0, 0, 0, 0.1);--quarto-scss-export-table-hover-color: #373a3c;--quarto-scss-export-table-hover-bg: rgba(0, 0, 0, 0.075);--quarto-scss-export-table-caption-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-input-btn-font-family: ;--quarto-scss-export-input-btn-focus-color: rgba(39, 128, 227, 0.25);--quarto-scss-export-btn-color: #373a3c;--quarto-scss-export-btn-font-family: ;--quarto-scss-export-btn-white-space: ;--quarto-scss-export-btn-link-color: #2761e3;--quarto-scss-export-btn-link-hover-color: #1f4eb6;--quarto-scss-export-btn-link-disabled-color: #868e96;--quarto-scss-export-form-text-font-style: ;--quarto-scss-export-form-text-font-weight: ;--quarto-scss-export-form-text-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-form-label-font-size: ;--quarto-scss-export-form-label-font-style: ;--quarto-scss-export-form-label-font-weight: ;--quarto-scss-export-form-label-color: ;--quarto-scss-export-input-font-family: ;--quarto-scss-export-input-disabled-color: ;--quarto-scss-export-input-disabled-bg: #e9ecef;--quarto-scss-export-input-disabled-border-color: ;--quarto-scss-export-input-color: #373a3c;--quarto-scss-export-input-focus-bg: #fff;--quarto-scss-export-input-focus-border-color: #93c0f1;--quarto-scss-export-input-focus-color: #373a3c;--quarto-scss-export-input-placeholder-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-input-plaintext-color: #373a3c;--quarto-scss-export-form-check-label-color: ;--quarto-scss-export-form-check-transition: ;--quarto-scss-export-form-check-input-bg: #fff;--quarto-scss-export-form-check-input-focus-border: #93c0f1;--quarto-scss-export-form-check-input-checked-color: #fff;--quarto-scss-export-form-check-input-checked-bg-color: #2780e3;--quarto-scss-export-form-check-input-checked-border-color: #2780e3;--quarto-scss-export-form-check-input-indeterminate-color: #fff;--quarto-scss-export-form-check-input-indeterminate-bg-color: #2780e3;--quarto-scss-export-form-check-input-indeterminate-border-color: #2780e3;--quarto-scss-export-form-switch-color: rgba(0, 0, 0, 0.25);--quarto-scss-export-form-switch-focus-color: #93c0f1;--quarto-scss-export-form-switch-checked-color: #fff;--quarto-scss-export-input-group-addon-color: #373a3c;--quarto-scss-export-input-group-addon-bg: #f8f9fa;--quarto-scss-export-input-group-addon-border-color: #e1e1e2;--quarto-scss-export-form-select-font-family: ;--quarto-scss-export-form-select-color: #373a3c;--quarto-scss-export-form-select-bg: #fff;--quarto-scss-export-form-select-disabled-color: ;--quarto-scss-export-form-select-disabled-bg: #e9ecef;--quarto-scss-export-form-select-disabled-border-color: ;--quarto-scss-export-form-select-indicator-color: #373a3c;--quarto-scss-export-form-select-border-color: #e1e1e2;--quarto-scss-export-form-select-focus-border-color: #93c0f1;--quarto-scss-export-form-range-track-bg: #f8f9fa;--quarto-scss-export-form-range-thumb-bg: #2780e3;--quarto-scss-export-form-range-thumb-active-bg: #bed9f7;--quarto-scss-export-form-range-thumb-disabled-bg: rgba(55, 58, 60, 0.75);--quarto-scss-export-form-file-button-color: #373a3c;--quarto-scss-export-form-file-button-bg: #f8f9fa;--quarto-scss-export-form-file-button-hover-bg: #e9ecef;--quarto-scss-export-form-floating-label-disabled-color: #868e96;--quarto-scss-export-form-feedback-font-style: ;--quarto-scss-export-form-feedback-valid-color: #3fb618;--quarto-scss-export-form-feedback-invalid-color: #ff0039;--quarto-scss-export-form-feedback-icon-valid-color: #3fb618;--quarto-scss-export-form-feedback-icon-invalid-color: #ff0039;--quarto-scss-export-form-valid-color: #3fb618;--quarto-scss-export-form-valid-border-color: #3fb618;--quarto-scss-export-form-invalid-color: #ff0039;--quarto-scss-export-form-invalid-border-color: #ff0039;--quarto-scss-export-nav-link-font-size: ;--quarto-scss-export-nav-link-font-weight: ;--quarto-scss-export-nav-link-color: #2761e3;--quarto-scss-export-nav-link-hover-color: #1f4eb6;--quarto-scss-export-nav-link-disabled-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-nav-tabs-border-color: #e1e1e2;--quarto-scss-export-nav-tabs-link-hover-border-color: #e9ecef #e9ecef #e1e1e2;--quarto-scss-export-nav-tabs-link-active-color: #000;--quarto-scss-export-nav-tabs-link-active-bg: #fff;--quarto-scss-export-nav-pills-link-active-bg: #2780e3;--quarto-scss-export-nav-pills-link-active-color: #fff;--quarto-scss-export-nav-underline-link-active-color: #000;--quarto-scss-export-navbar-padding-x: ;--quarto-scss-export-navbar-light-contrast: #000;--quarto-scss-export-navbar-dark-contrast: #000;--quarto-scss-export-navbar-light-icon-color: rgba(0, 0, 0, 0.75);--quarto-scss-export-navbar-dark-icon-color: rgba(0, 0, 0, 0.75);--quarto-scss-export-dropdown-color: #373a3c;--quarto-scss-export-dropdown-bg: #fff;--quarto-scss-export-dropdown-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-dropdown-divider-bg: rgba(0, 0, 0, 0.175);--quarto-scss-export-dropdown-link-color: #373a3c;--quarto-scss-export-dropdown-link-hover-color: #373a3c;--quarto-scss-export-dropdown-link-hover-bg: #f8f9fa;--quarto-scss-export-dropdown-link-active-bg: #2780e3;--quarto-scss-export-dropdown-link-active-color: #fff;--quarto-scss-export-dropdown-link-disabled-color: rgba(55, 58, 60, 0.5);--quarto-scss-export-dropdown-header-color: #868e96;--quarto-scss-export-dropdown-dark-color: #dee2e6;--quarto-scss-export-dropdown-dark-bg: #373a3c;--quarto-scss-export-dropdown-dark-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-dropdown-dark-divider-bg: rgba(0, 0, 0, 0.175);--quarto-scss-export-dropdown-dark-box-shadow: ;--quarto-scss-export-dropdown-dark-link-color: #dee2e6;--quarto-scss-export-dropdown-dark-link-hover-color: #fff;--quarto-scss-export-dropdown-dark-link-hover-bg: rgba(255, 255, 255, 0.15);--quarto-scss-export-dropdown-dark-link-active-color: #fff;--quarto-scss-export-dropdown-dark-link-active-bg: #2780e3;--quarto-scss-export-dropdown-dark-link-disabled-color: #adb5bd;--quarto-scss-export-dropdown-dark-header-color: #adb5bd;--quarto-scss-export-pagination-color: #2761e3;--quarto-scss-export-pagination-bg: #fff;--quarto-scss-export-pagination-border-color: #e1e1e2;--quarto-scss-export-pagination-focus-color: #1f4eb6;--quarto-scss-export-pagination-focus-bg: #e9ecef;--quarto-scss-export-pagination-hover-color: #1f4eb6;--quarto-scss-export-pagination-hover-bg: #f8f9fa;--quarto-scss-export-pagination-hover-border-color: #e1e1e2;--quarto-scss-export-pagination-active-color: #fff;--quarto-scss-export-pagination-active-bg: #2780e3;--quarto-scss-export-pagination-active-border-color: #2780e3;--quarto-scss-export-pagination-disabled-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-pagination-disabled-bg: #e9ecef;--quarto-scss-export-pagination-disabled-border-color: #e1e1e2;--quarto-scss-export-card-title-color: ;--quarto-scss-export-card-subtitle-color: ;--quarto-scss-export-card-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-card-box-shadow: ;--quarto-scss-export-card-cap-bg: rgba(55, 58, 60, 0.03);--quarto-scss-export-card-cap-color: ;--quarto-scss-export-card-height: ;--quarto-scss-export-card-color: ;--quarto-scss-export-card-bg: #fff;--quarto-scss-export-accordion-color: #373a3c;--quarto-scss-export-accordion-bg: #fff;--quarto-scss-export-accordion-border-color: #e1e1e2;--quarto-scss-export-accordion-button-color: #373a3c;--quarto-scss-export-accordion-button-bg: #fff;--quarto-scss-export-accordion-button-active-bg: #d4e6f9;--quarto-scss-export-accordion-button-active-color: #10335b;--quarto-scss-export-accordion-button-focus-border-color: #93c0f1;--quarto-scss-export-accordion-icon-color: #373a3c;--quarto-scss-export-accordion-icon-active-color: #10335b;--quarto-scss-export-tooltip-color: #fff;--quarto-scss-export-tooltip-bg: #000;--quarto-scss-export-tooltip-margin: ;--quarto-scss-export-tooltip-arrow-color: ;--quarto-scss-export-form-feedback-tooltip-line-height: ;--quarto-scss-export-popover-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-popover-header-bg: #e9ecef;--quarto-scss-export-popover-body-color: #373a3c;--quarto-scss-export-popover-arrow-color: #fff;--quarto-scss-export-popover-arrow-outer-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-toast-color: ;--quarto-scss-export-toast-background-color: rgba(255, 255, 255, 0.85);--quarto-scss-export-toast-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-toast-header-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-toast-header-background-color: rgba(255, 255, 255, 0.85);--quarto-scss-export-toast-header-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-badge-color: #fff;--quarto-scss-export-modal-content-color: ;--quarto-scss-export-modal-content-bg: #fff;--quarto-scss-export-modal-content-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-modal-backdrop-bg: #000;--quarto-scss-export-modal-header-border-color: #e1e1e2;--quarto-scss-export-modal-footer-bg: ;--quarto-scss-export-modal-footer-border-color: #e1e1e2;--quarto-scss-export-progress-bg: #e9ecef;--quarto-scss-export-progress-bar-color: #fff;--quarto-scss-export-progress-bar-bg: #2780e3;--quarto-scss-export-list-group-color: #373a3c;--quarto-scss-export-list-group-bg: #fff;--quarto-scss-export-list-group-border-color: #e1e1e2;--quarto-scss-export-list-group-hover-bg: #f8f9fa;--quarto-scss-export-list-group-active-bg: #2780e3;--quarto-scss-export-list-group-active-color: #fff;--quarto-scss-export-list-group-active-border-color: #2780e3;--quarto-scss-export-list-group-disabled-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-list-group-disabled-bg: #fff;--quarto-scss-export-list-group-action-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-list-group-action-hover-color: #000;--quarto-scss-export-list-group-action-active-color: #373a3c;--quarto-scss-export-list-group-action-active-bg: #e9ecef;--quarto-scss-export-thumbnail-bg: #fff;--quarto-scss-export-thumbnail-border-color: #e1e1e2;--quarto-scss-export-figure-caption-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-breadcrumb-font-size: ;--quarto-scss-export-breadcrumb-bg: ;--quarto-scss-export-breadcrumb-divider-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-breadcrumb-active-color: rgba(55, 58, 60, 0.75);--quarto-scss-export-breadcrumb-border-radius: ;--quarto-scss-export-carousel-control-color: #fff;--quarto-scss-export-carousel-indicator-active-bg: #fff;--quarto-scss-export-carousel-caption-color: #fff;--quarto-scss-export-carousel-dark-indicator-active-bg: #000;--quarto-scss-export-carousel-dark-caption-color: #000;--quarto-scss-export-btn-close-color: #000;--quarto-scss-export-offcanvas-border-color: rgba(0, 0, 0, 0.175);--quarto-scss-export-offcanvas-bg-color: #fff;--quarto-scss-export-offcanvas-color: #373a3c;--quarto-scss-export-offcanvas-backdrop-bg: #000;--quarto-scss-export-code-color-dark: white;--quarto-scss-export-kbd-color: #fff;--quarto-scss-export-kbd-bg: #373a3c;--quarto-scss-export-nested-kbd-font-weight: ;--quarto-scss-export-pre-bg: #f8f9fa;--quarto-scss-export-pre-color: #000;--quarto-scss-export-bslib-page-sidebar-title-bg: #f8f9fa;--quarto-scss-export-bslib-page-sidebar-title-color: #000;--quarto-scss-export-bslib-sidebar-bg: rgba(var(--bs-emphasis-color-rgb, 0, 0, 0), 0.05);--quarto-scss-export-bslib-sidebar-toggle-bg: rgba(var(--bs-emphasis-color-rgb, 0, 0, 0), 0.1);--quarto-scss-export-mermaid-bg-color: #fff;--quarto-scss-export-mermaid-edge-color: #373a3c;--quarto-scss-export-mermaid-node-fg-color: #373a3c;--quarto-scss-export-mermaid-fg-color: #373a3c;--quarto-scss-export-mermaid-fg-color--lighter: #4f5457;--quarto-scss-export-mermaid-fg-color--lightest: #686d71;--quarto-scss-export-mermaid-label-bg-color: #fff;--quarto-scss-export-mermaid-label-fg-color: #2780e3;--quarto-scss-export-mermaid-node-bg-color: rgba(39, 128, 227, 0.1);--quarto-scss-export-code-block-border-left-color: #e1e1e2;--quarto-scss-export-callout-color-note: #2780e3;--quarto-scss-export-callout-color-tip: #3fb618;--quarto-scss-export-callout-color-important: #ff0039;--quarto-scss-export-callout-color-caution: #f0ad4e;--quarto-scss-export-callout-color-warning: #ff7518}
\ No newline at end of file
diff --git a/public/notebooks/music-recommendation_files/libs/bootstrap/bootstrap-icons.css b/public/notebooks/music-recommendation_files/libs/bootstrap/bootstrap-icons.css
new file mode 100644
index 0000000..285e444
--- /dev/null
+++ b/public/notebooks/music-recommendation_files/libs/bootstrap/bootstrap-icons.css
@@ -0,0 +1,2078 @@
+/*!
+ * Bootstrap Icons v1.11.1 (https://icons.getbootstrap.com/)
+ * Copyright 2019-2023 The Bootstrap Authors
+ * Licensed under MIT (https://github.com/twbs/icons/blob/main/LICENSE)
+ */
+
+@font-face {
+ font-display: block;
+ font-family: "bootstrap-icons";
+ src:
+url("./bootstrap-icons.woff?2820a3852bdb9a5832199cc61cec4e65") format("woff");
+}
+
+.bi::before,
+[class^="bi-"]::before,
+[class*=" bi-"]::before {
+ display: inline-block;
+ font-family: bootstrap-icons !important;
+ font-style: normal;
+ font-weight: normal !important;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+ vertical-align: -.125em;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.bi-123::before { content: "\f67f"; }
+.bi-alarm-fill::before { content: "\f101"; }
+.bi-alarm::before { content: "\f102"; }
+.bi-align-bottom::before { content: "\f103"; }
+.bi-align-center::before { content: "\f104"; }
+.bi-align-end::before { content: "\f105"; }
+.bi-align-middle::before { content: "\f106"; }
+.bi-align-start::before { content: "\f107"; }
+.bi-align-top::before { content: "\f108"; }
+.bi-alt::before { content: "\f109"; }
+.bi-app-indicator::before { content: "\f10a"; }
+.bi-app::before { content: "\f10b"; }
+.bi-archive-fill::before { content: "\f10c"; }
+.bi-archive::before { content: "\f10d"; }
+.bi-arrow-90deg-down::before { content: "\f10e"; }
+.bi-arrow-90deg-left::before { content: "\f10f"; }
+.bi-arrow-90deg-right::before { content: "\f110"; }
+.bi-arrow-90deg-up::before { content: "\f111"; }
+.bi-arrow-bar-down::before { content: "\f112"; }
+.bi-arrow-bar-left::before { content: "\f113"; }
+.bi-arrow-bar-right::before { content: "\f114"; }
+.bi-arrow-bar-up::before { content: "\f115"; }
+.bi-arrow-clockwise::before { content: "\f116"; }
+.bi-arrow-counterclockwise::before { content: "\f117"; }
+.bi-arrow-down-circle-fill::before { content: "\f118"; }
+.bi-arrow-down-circle::before { content: "\f119"; }
+.bi-arrow-down-left-circle-fill::before { content: "\f11a"; }
+.bi-arrow-down-left-circle::before { content: "\f11b"; }
+.bi-arrow-down-left-square-fill::before { content: "\f11c"; }
+.bi-arrow-down-left-square::before { content: "\f11d"; }
+.bi-arrow-down-left::before { content: "\f11e"; }
+.bi-arrow-down-right-circle-fill::before { content: "\f11f"; }
+.bi-arrow-down-right-circle::before { content: "\f120"; }
+.bi-arrow-down-right-square-fill::before { content: "\f121"; }
+.bi-arrow-down-right-square::before { content: "\f122"; }
+.bi-arrow-down-right::before { content: "\f123"; }
+.bi-arrow-down-short::before { content: "\f124"; }
+.bi-arrow-down-square-fill::before { content: "\f125"; }
+.bi-arrow-down-square::before { content: "\f126"; }
+.bi-arrow-down-up::before { content: "\f127"; }
+.bi-arrow-down::before { content: "\f128"; }
+.bi-arrow-left-circle-fill::before { content: "\f129"; }
+.bi-arrow-left-circle::before { content: "\f12a"; }
+.bi-arrow-left-right::before { content: "\f12b"; }
+.bi-arrow-left-short::before { content: "\f12c"; }
+.bi-arrow-left-square-fill::before { content: "\f12d"; }
+.bi-arrow-left-square::before { content: "\f12e"; }
+.bi-arrow-left::before { content: "\f12f"; }
+.bi-arrow-repeat::before { content: "\f130"; }
+.bi-arrow-return-left::before { content: "\f131"; }
+.bi-arrow-return-right::before { content: "\f132"; }
+.bi-arrow-right-circle-fill::before { content: "\f133"; }
+.bi-arrow-right-circle::before { content: "\f134"; }
+.bi-arrow-right-short::before { content: "\f135"; }
+.bi-arrow-right-square-fill::before { content: "\f136"; }
+.bi-arrow-right-square::before { content: "\f137"; }
+.bi-arrow-right::before { content: "\f138"; }
+.bi-arrow-up-circle-fill::before { content: "\f139"; }
+.bi-arrow-up-circle::before { content: "\f13a"; }
+.bi-arrow-up-left-circle-fill::before { content: "\f13b"; }
+.bi-arrow-up-left-circle::before { content: "\f13c"; }
+.bi-arrow-up-left-square-fill::before { content: "\f13d"; }
+.bi-arrow-up-left-square::before { content: "\f13e"; }
+.bi-arrow-up-left::before { content: "\f13f"; }
+.bi-arrow-up-right-circle-fill::before { content: "\f140"; }
+.bi-arrow-up-right-circle::before { content: "\f141"; }
+.bi-arrow-up-right-square-fill::before { content: "\f142"; }
+.bi-arrow-up-right-square::before { content: "\f143"; }
+.bi-arrow-up-right::before { content: "\f144"; }
+.bi-arrow-up-short::before { content: "\f145"; }
+.bi-arrow-up-square-fill::before { content: "\f146"; }
+.bi-arrow-up-square::before { content: "\f147"; }
+.bi-arrow-up::before { content: "\f148"; }
+.bi-arrows-angle-contract::before { content: "\f149"; }
+.bi-arrows-angle-expand::before { content: "\f14a"; }
+.bi-arrows-collapse::before { content: "\f14b"; }
+.bi-arrows-expand::before { content: "\f14c"; }
+.bi-arrows-fullscreen::before { content: "\f14d"; }
+.bi-arrows-move::before { content: "\f14e"; }
+.bi-aspect-ratio-fill::before { content: "\f14f"; }
+.bi-aspect-ratio::before { content: "\f150"; }
+.bi-asterisk::before { content: "\f151"; }
+.bi-at::before { content: "\f152"; }
+.bi-award-fill::before { content: "\f153"; }
+.bi-award::before { content: "\f154"; }
+.bi-back::before { content: "\f155"; }
+.bi-backspace-fill::before { content: "\f156"; }
+.bi-backspace-reverse-fill::before { content: "\f157"; }
+.bi-backspace-reverse::before { content: "\f158"; }
+.bi-backspace::before { content: "\f159"; }
+.bi-badge-3d-fill::before { content: "\f15a"; }
+.bi-badge-3d::before { content: "\f15b"; }
+.bi-badge-4k-fill::before { content: "\f15c"; }
+.bi-badge-4k::before { content: "\f15d"; }
+.bi-badge-8k-fill::before { content: "\f15e"; }
+.bi-badge-8k::before { content: "\f15f"; }
+.bi-badge-ad-fill::before { content: "\f160"; }
+.bi-badge-ad::before { content: "\f161"; }
+.bi-badge-ar-fill::before { content: "\f162"; }
+.bi-badge-ar::before { content: "\f163"; }
+.bi-badge-cc-fill::before { content: "\f164"; }
+.bi-badge-cc::before { content: "\f165"; }
+.bi-badge-hd-fill::before { content: "\f166"; }
+.bi-badge-hd::before { content: "\f167"; }
+.bi-badge-tm-fill::before { content: "\f168"; }
+.bi-badge-tm::before { content: "\f169"; }
+.bi-badge-vo-fill::before { content: "\f16a"; }
+.bi-badge-vo::before { content: "\f16b"; }
+.bi-badge-vr-fill::before { content: "\f16c"; }
+.bi-badge-vr::before { content: "\f16d"; }
+.bi-badge-wc-fill::before { content: "\f16e"; }
+.bi-badge-wc::before { content: "\f16f"; }
+.bi-bag-check-fill::before { content: "\f170"; }
+.bi-bag-check::before { content: "\f171"; }
+.bi-bag-dash-fill::before { content: "\f172"; }
+.bi-bag-dash::before { content: "\f173"; }
+.bi-bag-fill::before { content: "\f174"; }
+.bi-bag-plus-fill::before { content: "\f175"; }
+.bi-bag-plus::before { content: "\f176"; }
+.bi-bag-x-fill::before { content: "\f177"; }
+.bi-bag-x::before { content: "\f178"; }
+.bi-bag::before { content: "\f179"; }
+.bi-bar-chart-fill::before { content: "\f17a"; }
+.bi-bar-chart-line-fill::before { content: "\f17b"; }
+.bi-bar-chart-line::before { content: "\f17c"; }
+.bi-bar-chart-steps::before { content: "\f17d"; }
+.bi-bar-chart::before { content: "\f17e"; }
+.bi-basket-fill::before { content: "\f17f"; }
+.bi-basket::before { content: "\f180"; }
+.bi-basket2-fill::before { content: "\f181"; }
+.bi-basket2::before { content: "\f182"; }
+.bi-basket3-fill::before { content: "\f183"; }
+.bi-basket3::before { content: "\f184"; }
+.bi-battery-charging::before { content: "\f185"; }
+.bi-battery-full::before { content: "\f186"; }
+.bi-battery-half::before { content: "\f187"; }
+.bi-battery::before { content: "\f188"; }
+.bi-bell-fill::before { content: "\f189"; }
+.bi-bell::before { content: "\f18a"; }
+.bi-bezier::before { content: "\f18b"; }
+.bi-bezier2::before { content: "\f18c"; }
+.bi-bicycle::before { content: "\f18d"; }
+.bi-binoculars-fill::before { content: "\f18e"; }
+.bi-binoculars::before { content: "\f18f"; }
+.bi-blockquote-left::before { content: "\f190"; }
+.bi-blockquote-right::before { content: "\f191"; }
+.bi-book-fill::before { content: "\f192"; }
+.bi-book-half::before { content: "\f193"; }
+.bi-book::before { content: "\f194"; }
+.bi-bookmark-check-fill::before { content: "\f195"; }
+.bi-bookmark-check::before { content: "\f196"; }
+.bi-bookmark-dash-fill::before { content: "\f197"; }
+.bi-bookmark-dash::before { content: "\f198"; }
+.bi-bookmark-fill::before { content: "\f199"; }
+.bi-bookmark-heart-fill::before { content: "\f19a"; }
+.bi-bookmark-heart::before { content: "\f19b"; }
+.bi-bookmark-plus-fill::before { content: "\f19c"; }
+.bi-bookmark-plus::before { content: "\f19d"; }
+.bi-bookmark-star-fill::before { content: "\f19e"; }
+.bi-bookmark-star::before { content: "\f19f"; }
+.bi-bookmark-x-fill::before { content: "\f1a0"; }
+.bi-bookmark-x::before { content: "\f1a1"; }
+.bi-bookmark::before { content: "\f1a2"; }
+.bi-bookmarks-fill::before { content: "\f1a3"; }
+.bi-bookmarks::before { content: "\f1a4"; }
+.bi-bookshelf::before { content: "\f1a5"; }
+.bi-bootstrap-fill::before { content: "\f1a6"; }
+.bi-bootstrap-reboot::before { content: "\f1a7"; }
+.bi-bootstrap::before { content: "\f1a8"; }
+.bi-border-all::before { content: "\f1a9"; }
+.bi-border-bottom::before { content: "\f1aa"; }
+.bi-border-center::before { content: "\f1ab"; }
+.bi-border-inner::before { content: "\f1ac"; }
+.bi-border-left::before { content: "\f1ad"; }
+.bi-border-middle::before { content: "\f1ae"; }
+.bi-border-outer::before { content: "\f1af"; }
+.bi-border-right::before { content: "\f1b0"; }
+.bi-border-style::before { content: "\f1b1"; }
+.bi-border-top::before { content: "\f1b2"; }
+.bi-border-width::before { content: "\f1b3"; }
+.bi-border::before { content: "\f1b4"; }
+.bi-bounding-box-circles::before { content: "\f1b5"; }
+.bi-bounding-box::before { content: "\f1b6"; }
+.bi-box-arrow-down-left::before { content: "\f1b7"; }
+.bi-box-arrow-down-right::before { content: "\f1b8"; }
+.bi-box-arrow-down::before { content: "\f1b9"; }
+.bi-box-arrow-in-down-left::before { content: "\f1ba"; }
+.bi-box-arrow-in-down-right::before { content: "\f1bb"; }
+.bi-box-arrow-in-down::before { content: "\f1bc"; }
+.bi-box-arrow-in-left::before { content: "\f1bd"; }
+.bi-box-arrow-in-right::before { content: "\f1be"; }
+.bi-box-arrow-in-up-left::before { content: "\f1bf"; }
+.bi-box-arrow-in-up-right::before { content: "\f1c0"; }
+.bi-box-arrow-in-up::before { content: "\f1c1"; }
+.bi-box-arrow-left::before { content: "\f1c2"; }
+.bi-box-arrow-right::before { content: "\f1c3"; }
+.bi-box-arrow-up-left::before { content: "\f1c4"; }
+.bi-box-arrow-up-right::before { content: "\f1c5"; }
+.bi-box-arrow-up::before { content: "\f1c6"; }
+.bi-box-seam::before { content: "\f1c7"; }
+.bi-box::before { content: "\f1c8"; }
+.bi-braces::before { content: "\f1c9"; }
+.bi-bricks::before { content: "\f1ca"; }
+.bi-briefcase-fill::before { content: "\f1cb"; }
+.bi-briefcase::before { content: "\f1cc"; }
+.bi-brightness-alt-high-fill::before { content: "\f1cd"; }
+.bi-brightness-alt-high::before { content: "\f1ce"; }
+.bi-brightness-alt-low-fill::before { content: "\f1cf"; }
+.bi-brightness-alt-low::before { content: "\f1d0"; }
+.bi-brightness-high-fill::before { content: "\f1d1"; }
+.bi-brightness-high::before { content: "\f1d2"; }
+.bi-brightness-low-fill::before { content: "\f1d3"; }
+.bi-brightness-low::before { content: "\f1d4"; }
+.bi-broadcast-pin::before { content: "\f1d5"; }
+.bi-broadcast::before { content: "\f1d6"; }
+.bi-brush-fill::before { content: "\f1d7"; }
+.bi-brush::before { content: "\f1d8"; }
+.bi-bucket-fill::before { content: "\f1d9"; }
+.bi-bucket::before { content: "\f1da"; }
+.bi-bug-fill::before { content: "\f1db"; }
+.bi-bug::before { content: "\f1dc"; }
+.bi-building::before { content: "\f1dd"; }
+.bi-bullseye::before { content: "\f1de"; }
+.bi-calculator-fill::before { content: "\f1df"; }
+.bi-calculator::before { content: "\f1e0"; }
+.bi-calendar-check-fill::before { content: "\f1e1"; }
+.bi-calendar-check::before { content: "\f1e2"; }
+.bi-calendar-date-fill::before { content: "\f1e3"; }
+.bi-calendar-date::before { content: "\f1e4"; }
+.bi-calendar-day-fill::before { content: "\f1e5"; }
+.bi-calendar-day::before { content: "\f1e6"; }
+.bi-calendar-event-fill::before { content: "\f1e7"; }
+.bi-calendar-event::before { content: "\f1e8"; }
+.bi-calendar-fill::before { content: "\f1e9"; }
+.bi-calendar-minus-fill::before { content: "\f1ea"; }
+.bi-calendar-minus::before { content: "\f1eb"; }
+.bi-calendar-month-fill::before { content: "\f1ec"; }
+.bi-calendar-month::before { content: "\f1ed"; }
+.bi-calendar-plus-fill::before { content: "\f1ee"; }
+.bi-calendar-plus::before { content: "\f1ef"; }
+.bi-calendar-range-fill::before { content: "\f1f0"; }
+.bi-calendar-range::before { content: "\f1f1"; }
+.bi-calendar-week-fill::before { content: "\f1f2"; }
+.bi-calendar-week::before { content: "\f1f3"; }
+.bi-calendar-x-fill::before { content: "\f1f4"; }
+.bi-calendar-x::before { content: "\f1f5"; }
+.bi-calendar::before { content: "\f1f6"; }
+.bi-calendar2-check-fill::before { content: "\f1f7"; }
+.bi-calendar2-check::before { content: "\f1f8"; }
+.bi-calendar2-date-fill::before { content: "\f1f9"; }
+.bi-calendar2-date::before { content: "\f1fa"; }
+.bi-calendar2-day-fill::before { content: "\f1fb"; }
+.bi-calendar2-day::before { content: "\f1fc"; }
+.bi-calendar2-event-fill::before { content: "\f1fd"; }
+.bi-calendar2-event::before { content: "\f1fe"; }
+.bi-calendar2-fill::before { content: "\f1ff"; }
+.bi-calendar2-minus-fill::before { content: "\f200"; }
+.bi-calendar2-minus::before { content: "\f201"; }
+.bi-calendar2-month-fill::before { content: "\f202"; }
+.bi-calendar2-month::before { content: "\f203"; }
+.bi-calendar2-plus-fill::before { content: "\f204"; }
+.bi-calendar2-plus::before { content: "\f205"; }
+.bi-calendar2-range-fill::before { content: "\f206"; }
+.bi-calendar2-range::before { content: "\f207"; }
+.bi-calendar2-week-fill::before { content: "\f208"; }
+.bi-calendar2-week::before { content: "\f209"; }
+.bi-calendar2-x-fill::before { content: "\f20a"; }
+.bi-calendar2-x::before { content: "\f20b"; }
+.bi-calendar2::before { content: "\f20c"; }
+.bi-calendar3-event-fill::before { content: "\f20d"; }
+.bi-calendar3-event::before { content: "\f20e"; }
+.bi-calendar3-fill::before { content: "\f20f"; }
+.bi-calendar3-range-fill::before { content: "\f210"; }
+.bi-calendar3-range::before { content: "\f211"; }
+.bi-calendar3-week-fill::before { content: "\f212"; }
+.bi-calendar3-week::before { content: "\f213"; }
+.bi-calendar3::before { content: "\f214"; }
+.bi-calendar4-event::before { content: "\f215"; }
+.bi-calendar4-range::before { content: "\f216"; }
+.bi-calendar4-week::before { content: "\f217"; }
+.bi-calendar4::before { content: "\f218"; }
+.bi-camera-fill::before { content: "\f219"; }
+.bi-camera-reels-fill::before { content: "\f21a"; }
+.bi-camera-reels::before { content: "\f21b"; }
+.bi-camera-video-fill::before { content: "\f21c"; }
+.bi-camera-video-off-fill::before { content: "\f21d"; }
+.bi-camera-video-off::before { content: "\f21e"; }
+.bi-camera-video::before { content: "\f21f"; }
+.bi-camera::before { content: "\f220"; }
+.bi-camera2::before { content: "\f221"; }
+.bi-capslock-fill::before { content: "\f222"; }
+.bi-capslock::before { content: "\f223"; }
+.bi-card-checklist::before { content: "\f224"; }
+.bi-card-heading::before { content: "\f225"; }
+.bi-card-image::before { content: "\f226"; }
+.bi-card-list::before { content: "\f227"; }
+.bi-card-text::before { content: "\f228"; }
+.bi-caret-down-fill::before { content: "\f229"; }
+.bi-caret-down-square-fill::before { content: "\f22a"; }
+.bi-caret-down-square::before { content: "\f22b"; }
+.bi-caret-down::before { content: "\f22c"; }
+.bi-caret-left-fill::before { content: "\f22d"; }
+.bi-caret-left-square-fill::before { content: "\f22e"; }
+.bi-caret-left-square::before { content: "\f22f"; }
+.bi-caret-left::before { content: "\f230"; }
+.bi-caret-right-fill::before { content: "\f231"; }
+.bi-caret-right-square-fill::before { content: "\f232"; }
+.bi-caret-right-square::before { content: "\f233"; }
+.bi-caret-right::before { content: "\f234"; }
+.bi-caret-up-fill::before { content: "\f235"; }
+.bi-caret-up-square-fill::before { content: "\f236"; }
+.bi-caret-up-square::before { content: "\f237"; }
+.bi-caret-up::before { content: "\f238"; }
+.bi-cart-check-fill::before { content: "\f239"; }
+.bi-cart-check::before { content: "\f23a"; }
+.bi-cart-dash-fill::before { content: "\f23b"; }
+.bi-cart-dash::before { content: "\f23c"; }
+.bi-cart-fill::before { content: "\f23d"; }
+.bi-cart-plus-fill::before { content: "\f23e"; }
+.bi-cart-plus::before { content: "\f23f"; }
+.bi-cart-x-fill::before { content: "\f240"; }
+.bi-cart-x::before { content: "\f241"; }
+.bi-cart::before { content: "\f242"; }
+.bi-cart2::before { content: "\f243"; }
+.bi-cart3::before { content: "\f244"; }
+.bi-cart4::before { content: "\f245"; }
+.bi-cash-stack::before { content: "\f246"; }
+.bi-cash::before { content: "\f247"; }
+.bi-cast::before { content: "\f248"; }
+.bi-chat-dots-fill::before { content: "\f249"; }
+.bi-chat-dots::before { content: "\f24a"; }
+.bi-chat-fill::before { content: "\f24b"; }
+.bi-chat-left-dots-fill::before { content: "\f24c"; }
+.bi-chat-left-dots::before { content: "\f24d"; }
+.bi-chat-left-fill::before { content: "\f24e"; }
+.bi-chat-left-quote-fill::before { content: "\f24f"; }
+.bi-chat-left-quote::before { content: "\f250"; }
+.bi-chat-left-text-fill::before { content: "\f251"; }
+.bi-chat-left-text::before { content: "\f252"; }
+.bi-chat-left::before { content: "\f253"; }
+.bi-chat-quote-fill::before { content: "\f254"; }
+.bi-chat-quote::before { content: "\f255"; }
+.bi-chat-right-dots-fill::before { content: "\f256"; }
+.bi-chat-right-dots::before { content: "\f257"; }
+.bi-chat-right-fill::before { content: "\f258"; }
+.bi-chat-right-quote-fill::before { content: "\f259"; }
+.bi-chat-right-quote::before { content: "\f25a"; }
+.bi-chat-right-text-fill::before { content: "\f25b"; }
+.bi-chat-right-text::before { content: "\f25c"; }
+.bi-chat-right::before { content: "\f25d"; }
+.bi-chat-square-dots-fill::before { content: "\f25e"; }
+.bi-chat-square-dots::before { content: "\f25f"; }
+.bi-chat-square-fill::before { content: "\f260"; }
+.bi-chat-square-quote-fill::before { content: "\f261"; }
+.bi-chat-square-quote::before { content: "\f262"; }
+.bi-chat-square-text-fill::before { content: "\f263"; }
+.bi-chat-square-text::before { content: "\f264"; }
+.bi-chat-square::before { content: "\f265"; }
+.bi-chat-text-fill::before { content: "\f266"; }
+.bi-chat-text::before { content: "\f267"; }
+.bi-chat::before { content: "\f268"; }
+.bi-check-all::before { content: "\f269"; }
+.bi-check-circle-fill::before { content: "\f26a"; }
+.bi-check-circle::before { content: "\f26b"; }
+.bi-check-square-fill::before { content: "\f26c"; }
+.bi-check-square::before { content: "\f26d"; }
+.bi-check::before { content: "\f26e"; }
+.bi-check2-all::before { content: "\f26f"; }
+.bi-check2-circle::before { content: "\f270"; }
+.bi-check2-square::before { content: "\f271"; }
+.bi-check2::before { content: "\f272"; }
+.bi-chevron-bar-contract::before { content: "\f273"; }
+.bi-chevron-bar-down::before { content: "\f274"; }
+.bi-chevron-bar-expand::before { content: "\f275"; }
+.bi-chevron-bar-left::before { content: "\f276"; }
+.bi-chevron-bar-right::before { content: "\f277"; }
+.bi-chevron-bar-up::before { content: "\f278"; }
+.bi-chevron-compact-down::before { content: "\f279"; }
+.bi-chevron-compact-left::before { content: "\f27a"; }
+.bi-chevron-compact-right::before { content: "\f27b"; }
+.bi-chevron-compact-up::before { content: "\f27c"; }
+.bi-chevron-contract::before { content: "\f27d"; }
+.bi-chevron-double-down::before { content: "\f27e"; }
+.bi-chevron-double-left::before { content: "\f27f"; }
+.bi-chevron-double-right::before { content: "\f280"; }
+.bi-chevron-double-up::before { content: "\f281"; }
+.bi-chevron-down::before { content: "\f282"; }
+.bi-chevron-expand::before { content: "\f283"; }
+.bi-chevron-left::before { content: "\f284"; }
+.bi-chevron-right::before { content: "\f285"; }
+.bi-chevron-up::before { content: "\f286"; }
+.bi-circle-fill::before { content: "\f287"; }
+.bi-circle-half::before { content: "\f288"; }
+.bi-circle-square::before { content: "\f289"; }
+.bi-circle::before { content: "\f28a"; }
+.bi-clipboard-check::before { content: "\f28b"; }
+.bi-clipboard-data::before { content: "\f28c"; }
+.bi-clipboard-minus::before { content: "\f28d"; }
+.bi-clipboard-plus::before { content: "\f28e"; }
+.bi-clipboard-x::before { content: "\f28f"; }
+.bi-clipboard::before { content: "\f290"; }
+.bi-clock-fill::before { content: "\f291"; }
+.bi-clock-history::before { content: "\f292"; }
+.bi-clock::before { content: "\f293"; }
+.bi-cloud-arrow-down-fill::before { content: "\f294"; }
+.bi-cloud-arrow-down::before { content: "\f295"; }
+.bi-cloud-arrow-up-fill::before { content: "\f296"; }
+.bi-cloud-arrow-up::before { content: "\f297"; }
+.bi-cloud-check-fill::before { content: "\f298"; }
+.bi-cloud-check::before { content: "\f299"; }
+.bi-cloud-download-fill::before { content: "\f29a"; }
+.bi-cloud-download::before { content: "\f29b"; }
+.bi-cloud-drizzle-fill::before { content: "\f29c"; }
+.bi-cloud-drizzle::before { content: "\f29d"; }
+.bi-cloud-fill::before { content: "\f29e"; }
+.bi-cloud-fog-fill::before { content: "\f29f"; }
+.bi-cloud-fog::before { content: "\f2a0"; }
+.bi-cloud-fog2-fill::before { content: "\f2a1"; }
+.bi-cloud-fog2::before { content: "\f2a2"; }
+.bi-cloud-hail-fill::before { content: "\f2a3"; }
+.bi-cloud-hail::before { content: "\f2a4"; }
+.bi-cloud-haze-fill::before { content: "\f2a6"; }
+.bi-cloud-haze::before { content: "\f2a7"; }
+.bi-cloud-haze2-fill::before { content: "\f2a8"; }
+.bi-cloud-lightning-fill::before { content: "\f2a9"; }
+.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; }
+.bi-cloud-lightning-rain::before { content: "\f2ab"; }
+.bi-cloud-lightning::before { content: "\f2ac"; }
+.bi-cloud-minus-fill::before { content: "\f2ad"; }
+.bi-cloud-minus::before { content: "\f2ae"; }
+.bi-cloud-moon-fill::before { content: "\f2af"; }
+.bi-cloud-moon::before { content: "\f2b0"; }
+.bi-cloud-plus-fill::before { content: "\f2b1"; }
+.bi-cloud-plus::before { content: "\f2b2"; }
+.bi-cloud-rain-fill::before { content: "\f2b3"; }
+.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; }
+.bi-cloud-rain-heavy::before { content: "\f2b5"; }
+.bi-cloud-rain::before { content: "\f2b6"; }
+.bi-cloud-slash-fill::before { content: "\f2b7"; }
+.bi-cloud-slash::before { content: "\f2b8"; }
+.bi-cloud-sleet-fill::before { content: "\f2b9"; }
+.bi-cloud-sleet::before { content: "\f2ba"; }
+.bi-cloud-snow-fill::before { content: "\f2bb"; }
+.bi-cloud-snow::before { content: "\f2bc"; }
+.bi-cloud-sun-fill::before { content: "\f2bd"; }
+.bi-cloud-sun::before { content: "\f2be"; }
+.bi-cloud-upload-fill::before { content: "\f2bf"; }
+.bi-cloud-upload::before { content: "\f2c0"; }
+.bi-cloud::before { content: "\f2c1"; }
+.bi-clouds-fill::before { content: "\f2c2"; }
+.bi-clouds::before { content: "\f2c3"; }
+.bi-cloudy-fill::before { content: "\f2c4"; }
+.bi-cloudy::before { content: "\f2c5"; }
+.bi-code-slash::before { content: "\f2c6"; }
+.bi-code-square::before { content: "\f2c7"; }
+.bi-code::before { content: "\f2c8"; }
+.bi-collection-fill::before { content: "\f2c9"; }
+.bi-collection-play-fill::before { content: "\f2ca"; }
+.bi-collection-play::before { content: "\f2cb"; }
+.bi-collection::before { content: "\f2cc"; }
+.bi-columns-gap::before { content: "\f2cd"; }
+.bi-columns::before { content: "\f2ce"; }
+.bi-command::before { content: "\f2cf"; }
+.bi-compass-fill::before { content: "\f2d0"; }
+.bi-compass::before { content: "\f2d1"; }
+.bi-cone-striped::before { content: "\f2d2"; }
+.bi-cone::before { content: "\f2d3"; }
+.bi-controller::before { content: "\f2d4"; }
+.bi-cpu-fill::before { content: "\f2d5"; }
+.bi-cpu::before { content: "\f2d6"; }
+.bi-credit-card-2-back-fill::before { content: "\f2d7"; }
+.bi-credit-card-2-back::before { content: "\f2d8"; }
+.bi-credit-card-2-front-fill::before { content: "\f2d9"; }
+.bi-credit-card-2-front::before { content: "\f2da"; }
+.bi-credit-card-fill::before { content: "\f2db"; }
+.bi-credit-card::before { content: "\f2dc"; }
+.bi-crop::before { content: "\f2dd"; }
+.bi-cup-fill::before { content: "\f2de"; }
+.bi-cup-straw::before { content: "\f2df"; }
+.bi-cup::before { content: "\f2e0"; }
+.bi-cursor-fill::before { content: "\f2e1"; }
+.bi-cursor-text::before { content: "\f2e2"; }
+.bi-cursor::before { content: "\f2e3"; }
+.bi-dash-circle-dotted::before { content: "\f2e4"; }
+.bi-dash-circle-fill::before { content: "\f2e5"; }
+.bi-dash-circle::before { content: "\f2e6"; }
+.bi-dash-square-dotted::before { content: "\f2e7"; }
+.bi-dash-square-fill::before { content: "\f2e8"; }
+.bi-dash-square::before { content: "\f2e9"; }
+.bi-dash::before { content: "\f2ea"; }
+.bi-diagram-2-fill::before { content: "\f2eb"; }
+.bi-diagram-2::before { content: "\f2ec"; }
+.bi-diagram-3-fill::before { content: "\f2ed"; }
+.bi-diagram-3::before { content: "\f2ee"; }
+.bi-diamond-fill::before { content: "\f2ef"; }
+.bi-diamond-half::before { content: "\f2f0"; }
+.bi-diamond::before { content: "\f2f1"; }
+.bi-dice-1-fill::before { content: "\f2f2"; }
+.bi-dice-1::before { content: "\f2f3"; }
+.bi-dice-2-fill::before { content: "\f2f4"; }
+.bi-dice-2::before { content: "\f2f5"; }
+.bi-dice-3-fill::before { content: "\f2f6"; }
+.bi-dice-3::before { content: "\f2f7"; }
+.bi-dice-4-fill::before { content: "\f2f8"; }
+.bi-dice-4::before { content: "\f2f9"; }
+.bi-dice-5-fill::before { content: "\f2fa"; }
+.bi-dice-5::before { content: "\f2fb"; }
+.bi-dice-6-fill::before { content: "\f2fc"; }
+.bi-dice-6::before { content: "\f2fd"; }
+.bi-disc-fill::before { content: "\f2fe"; }
+.bi-disc::before { content: "\f2ff"; }
+.bi-discord::before { content: "\f300"; }
+.bi-display-fill::before { content: "\f301"; }
+.bi-display::before { content: "\f302"; }
+.bi-distribute-horizontal::before { content: "\f303"; }
+.bi-distribute-vertical::before { content: "\f304"; }
+.bi-door-closed-fill::before { content: "\f305"; }
+.bi-door-closed::before { content: "\f306"; }
+.bi-door-open-fill::before { content: "\f307"; }
+.bi-door-open::before { content: "\f308"; }
+.bi-dot::before { content: "\f309"; }
+.bi-download::before { content: "\f30a"; }
+.bi-droplet-fill::before { content: "\f30b"; }
+.bi-droplet-half::before { content: "\f30c"; }
+.bi-droplet::before { content: "\f30d"; }
+.bi-earbuds::before { content: "\f30e"; }
+.bi-easel-fill::before { content: "\f30f"; }
+.bi-easel::before { content: "\f310"; }
+.bi-egg-fill::before { content: "\f311"; }
+.bi-egg-fried::before { content: "\f312"; }
+.bi-egg::before { content: "\f313"; }
+.bi-eject-fill::before { content: "\f314"; }
+.bi-eject::before { content: "\f315"; }
+.bi-emoji-angry-fill::before { content: "\f316"; }
+.bi-emoji-angry::before { content: "\f317"; }
+.bi-emoji-dizzy-fill::before { content: "\f318"; }
+.bi-emoji-dizzy::before { content: "\f319"; }
+.bi-emoji-expressionless-fill::before { content: "\f31a"; }
+.bi-emoji-expressionless::before { content: "\f31b"; }
+.bi-emoji-frown-fill::before { content: "\f31c"; }
+.bi-emoji-frown::before { content: "\f31d"; }
+.bi-emoji-heart-eyes-fill::before { content: "\f31e"; }
+.bi-emoji-heart-eyes::before { content: "\f31f"; }
+.bi-emoji-laughing-fill::before { content: "\f320"; }
+.bi-emoji-laughing::before { content: "\f321"; }
+.bi-emoji-neutral-fill::before { content: "\f322"; }
+.bi-emoji-neutral::before { content: "\f323"; }
+.bi-emoji-smile-fill::before { content: "\f324"; }
+.bi-emoji-smile-upside-down-fill::before { content: "\f325"; }
+.bi-emoji-smile-upside-down::before { content: "\f326"; }
+.bi-emoji-smile::before { content: "\f327"; }
+.bi-emoji-sunglasses-fill::before { content: "\f328"; }
+.bi-emoji-sunglasses::before { content: "\f329"; }
+.bi-emoji-wink-fill::before { content: "\f32a"; }
+.bi-emoji-wink::before { content: "\f32b"; }
+.bi-envelope-fill::before { content: "\f32c"; }
+.bi-envelope-open-fill::before { content: "\f32d"; }
+.bi-envelope-open::before { content: "\f32e"; }
+.bi-envelope::before { content: "\f32f"; }
+.bi-eraser-fill::before { content: "\f330"; }
+.bi-eraser::before { content: "\f331"; }
+.bi-exclamation-circle-fill::before { content: "\f332"; }
+.bi-exclamation-circle::before { content: "\f333"; }
+.bi-exclamation-diamond-fill::before { content: "\f334"; }
+.bi-exclamation-diamond::before { content: "\f335"; }
+.bi-exclamation-octagon-fill::before { content: "\f336"; }
+.bi-exclamation-octagon::before { content: "\f337"; }
+.bi-exclamation-square-fill::before { content: "\f338"; }
+.bi-exclamation-square::before { content: "\f339"; }
+.bi-exclamation-triangle-fill::before { content: "\f33a"; }
+.bi-exclamation-triangle::before { content: "\f33b"; }
+.bi-exclamation::before { content: "\f33c"; }
+.bi-exclude::before { content: "\f33d"; }
+.bi-eye-fill::before { content: "\f33e"; }
+.bi-eye-slash-fill::before { content: "\f33f"; }
+.bi-eye-slash::before { content: "\f340"; }
+.bi-eye::before { content: "\f341"; }
+.bi-eyedropper::before { content: "\f342"; }
+.bi-eyeglasses::before { content: "\f343"; }
+.bi-facebook::before { content: "\f344"; }
+.bi-file-arrow-down-fill::before { content: "\f345"; }
+.bi-file-arrow-down::before { content: "\f346"; }
+.bi-file-arrow-up-fill::before { content: "\f347"; }
+.bi-file-arrow-up::before { content: "\f348"; }
+.bi-file-bar-graph-fill::before { content: "\f349"; }
+.bi-file-bar-graph::before { content: "\f34a"; }
+.bi-file-binary-fill::before { content: "\f34b"; }
+.bi-file-binary::before { content: "\f34c"; }
+.bi-file-break-fill::before { content: "\f34d"; }
+.bi-file-break::before { content: "\f34e"; }
+.bi-file-check-fill::before { content: "\f34f"; }
+.bi-file-check::before { content: "\f350"; }
+.bi-file-code-fill::before { content: "\f351"; }
+.bi-file-code::before { content: "\f352"; }
+.bi-file-diff-fill::before { content: "\f353"; }
+.bi-file-diff::before { content: "\f354"; }
+.bi-file-earmark-arrow-down-fill::before { content: "\f355"; }
+.bi-file-earmark-arrow-down::before { content: "\f356"; }
+.bi-file-earmark-arrow-up-fill::before { content: "\f357"; }
+.bi-file-earmark-arrow-up::before { content: "\f358"; }
+.bi-file-earmark-bar-graph-fill::before { content: "\f359"; }
+.bi-file-earmark-bar-graph::before { content: "\f35a"; }
+.bi-file-earmark-binary-fill::before { content: "\f35b"; }
+.bi-file-earmark-binary::before { content: "\f35c"; }
+.bi-file-earmark-break-fill::before { content: "\f35d"; }
+.bi-file-earmark-break::before { content: "\f35e"; }
+.bi-file-earmark-check-fill::before { content: "\f35f"; }
+.bi-file-earmark-check::before { content: "\f360"; }
+.bi-file-earmark-code-fill::before { content: "\f361"; }
+.bi-file-earmark-code::before { content: "\f362"; }
+.bi-file-earmark-diff-fill::before { content: "\f363"; }
+.bi-file-earmark-diff::before { content: "\f364"; }
+.bi-file-earmark-easel-fill::before { content: "\f365"; }
+.bi-file-earmark-easel::before { content: "\f366"; }
+.bi-file-earmark-excel-fill::before { content: "\f367"; }
+.bi-file-earmark-excel::before { content: "\f368"; }
+.bi-file-earmark-fill::before { content: "\f369"; }
+.bi-file-earmark-font-fill::before { content: "\f36a"; }
+.bi-file-earmark-font::before { content: "\f36b"; }
+.bi-file-earmark-image-fill::before { content: "\f36c"; }
+.bi-file-earmark-image::before { content: "\f36d"; }
+.bi-file-earmark-lock-fill::before { content: "\f36e"; }
+.bi-file-earmark-lock::before { content: "\f36f"; }
+.bi-file-earmark-lock2-fill::before { content: "\f370"; }
+.bi-file-earmark-lock2::before { content: "\f371"; }
+.bi-file-earmark-medical-fill::before { content: "\f372"; }
+.bi-file-earmark-medical::before { content: "\f373"; }
+.bi-file-earmark-minus-fill::before { content: "\f374"; }
+.bi-file-earmark-minus::before { content: "\f375"; }
+.bi-file-earmark-music-fill::before { content: "\f376"; }
+.bi-file-earmark-music::before { content: "\f377"; }
+.bi-file-earmark-person-fill::before { content: "\f378"; }
+.bi-file-earmark-person::before { content: "\f379"; }
+.bi-file-earmark-play-fill::before { content: "\f37a"; }
+.bi-file-earmark-play::before { content: "\f37b"; }
+.bi-file-earmark-plus-fill::before { content: "\f37c"; }
+.bi-file-earmark-plus::before { content: "\f37d"; }
+.bi-file-earmark-post-fill::before { content: "\f37e"; }
+.bi-file-earmark-post::before { content: "\f37f"; }
+.bi-file-earmark-ppt-fill::before { content: "\f380"; }
+.bi-file-earmark-ppt::before { content: "\f381"; }
+.bi-file-earmark-richtext-fill::before { content: "\f382"; }
+.bi-file-earmark-richtext::before { content: "\f383"; }
+.bi-file-earmark-ruled-fill::before { content: "\f384"; }
+.bi-file-earmark-ruled::before { content: "\f385"; }
+.bi-file-earmark-slides-fill::before { content: "\f386"; }
+.bi-file-earmark-slides::before { content: "\f387"; }
+.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; }
+.bi-file-earmark-spreadsheet::before { content: "\f389"; }
+.bi-file-earmark-text-fill::before { content: "\f38a"; }
+.bi-file-earmark-text::before { content: "\f38b"; }
+.bi-file-earmark-word-fill::before { content: "\f38c"; }
+.bi-file-earmark-word::before { content: "\f38d"; }
+.bi-file-earmark-x-fill::before { content: "\f38e"; }
+.bi-file-earmark-x::before { content: "\f38f"; }
+.bi-file-earmark-zip-fill::before { content: "\f390"; }
+.bi-file-earmark-zip::before { content: "\f391"; }
+.bi-file-earmark::before { content: "\f392"; }
+.bi-file-easel-fill::before { content: "\f393"; }
+.bi-file-easel::before { content: "\f394"; }
+.bi-file-excel-fill::before { content: "\f395"; }
+.bi-file-excel::before { content: "\f396"; }
+.bi-file-fill::before { content: "\f397"; }
+.bi-file-font-fill::before { content: "\f398"; }
+.bi-file-font::before { content: "\f399"; }
+.bi-file-image-fill::before { content: "\f39a"; }
+.bi-file-image::before { content: "\f39b"; }
+.bi-file-lock-fill::before { content: "\f39c"; }
+.bi-file-lock::before { content: "\f39d"; }
+.bi-file-lock2-fill::before { content: "\f39e"; }
+.bi-file-lock2::before { content: "\f39f"; }
+.bi-file-medical-fill::before { content: "\f3a0"; }
+.bi-file-medical::before { content: "\f3a1"; }
+.bi-file-minus-fill::before { content: "\f3a2"; }
+.bi-file-minus::before { content: "\f3a3"; }
+.bi-file-music-fill::before { content: "\f3a4"; }
+.bi-file-music::before { content: "\f3a5"; }
+.bi-file-person-fill::before { content: "\f3a6"; }
+.bi-file-person::before { content: "\f3a7"; }
+.bi-file-play-fill::before { content: "\f3a8"; }
+.bi-file-play::before { content: "\f3a9"; }
+.bi-file-plus-fill::before { content: "\f3aa"; }
+.bi-file-plus::before { content: "\f3ab"; }
+.bi-file-post-fill::before { content: "\f3ac"; }
+.bi-file-post::before { content: "\f3ad"; }
+.bi-file-ppt-fill::before { content: "\f3ae"; }
+.bi-file-ppt::before { content: "\f3af"; }
+.bi-file-richtext-fill::before { content: "\f3b0"; }
+.bi-file-richtext::before { content: "\f3b1"; }
+.bi-file-ruled-fill::before { content: "\f3b2"; }
+.bi-file-ruled::before { content: "\f3b3"; }
+.bi-file-slides-fill::before { content: "\f3b4"; }
+.bi-file-slides::before { content: "\f3b5"; }
+.bi-file-spreadsheet-fill::before { content: "\f3b6"; }
+.bi-file-spreadsheet::before { content: "\f3b7"; }
+.bi-file-text-fill::before { content: "\f3b8"; }
+.bi-file-text::before { content: "\f3b9"; }
+.bi-file-word-fill::before { content: "\f3ba"; }
+.bi-file-word::before { content: "\f3bb"; }
+.bi-file-x-fill::before { content: "\f3bc"; }
+.bi-file-x::before { content: "\f3bd"; }
+.bi-file-zip-fill::before { content: "\f3be"; }
+.bi-file-zip::before { content: "\f3bf"; }
+.bi-file::before { content: "\f3c0"; }
+.bi-files-alt::before { content: "\f3c1"; }
+.bi-files::before { content: "\f3c2"; }
+.bi-film::before { content: "\f3c3"; }
+.bi-filter-circle-fill::before { content: "\f3c4"; }
+.bi-filter-circle::before { content: "\f3c5"; }
+.bi-filter-left::before { content: "\f3c6"; }
+.bi-filter-right::before { content: "\f3c7"; }
+.bi-filter-square-fill::before { content: "\f3c8"; }
+.bi-filter-square::before { content: "\f3c9"; }
+.bi-filter::before { content: "\f3ca"; }
+.bi-flag-fill::before { content: "\f3cb"; }
+.bi-flag::before { content: "\f3cc"; }
+.bi-flower1::before { content: "\f3cd"; }
+.bi-flower2::before { content: "\f3ce"; }
+.bi-flower3::before { content: "\f3cf"; }
+.bi-folder-check::before { content: "\f3d0"; }
+.bi-folder-fill::before { content: "\f3d1"; }
+.bi-folder-minus::before { content: "\f3d2"; }
+.bi-folder-plus::before { content: "\f3d3"; }
+.bi-folder-symlink-fill::before { content: "\f3d4"; }
+.bi-folder-symlink::before { content: "\f3d5"; }
+.bi-folder-x::before { content: "\f3d6"; }
+.bi-folder::before { content: "\f3d7"; }
+.bi-folder2-open::before { content: "\f3d8"; }
+.bi-folder2::before { content: "\f3d9"; }
+.bi-fonts::before { content: "\f3da"; }
+.bi-forward-fill::before { content: "\f3db"; }
+.bi-forward::before { content: "\f3dc"; }
+.bi-front::before { content: "\f3dd"; }
+.bi-fullscreen-exit::before { content: "\f3de"; }
+.bi-fullscreen::before { content: "\f3df"; }
+.bi-funnel-fill::before { content: "\f3e0"; }
+.bi-funnel::before { content: "\f3e1"; }
+.bi-gear-fill::before { content: "\f3e2"; }
+.bi-gear-wide-connected::before { content: "\f3e3"; }
+.bi-gear-wide::before { content: "\f3e4"; }
+.bi-gear::before { content: "\f3e5"; }
+.bi-gem::before { content: "\f3e6"; }
+.bi-geo-alt-fill::before { content: "\f3e7"; }
+.bi-geo-alt::before { content: "\f3e8"; }
+.bi-geo-fill::before { content: "\f3e9"; }
+.bi-geo::before { content: "\f3ea"; }
+.bi-gift-fill::before { content: "\f3eb"; }
+.bi-gift::before { content: "\f3ec"; }
+.bi-github::before { content: "\f3ed"; }
+.bi-globe::before { content: "\f3ee"; }
+.bi-globe2::before { content: "\f3ef"; }
+.bi-google::before { content: "\f3f0"; }
+.bi-graph-down::before { content: "\f3f1"; }
+.bi-graph-up::before { content: "\f3f2"; }
+.bi-grid-1x2-fill::before { content: "\f3f3"; }
+.bi-grid-1x2::before { content: "\f3f4"; }
+.bi-grid-3x2-gap-fill::before { content: "\f3f5"; }
+.bi-grid-3x2-gap::before { content: "\f3f6"; }
+.bi-grid-3x2::before { content: "\f3f7"; }
+.bi-grid-3x3-gap-fill::before { content: "\f3f8"; }
+.bi-grid-3x3-gap::before { content: "\f3f9"; }
+.bi-grid-3x3::before { content: "\f3fa"; }
+.bi-grid-fill::before { content: "\f3fb"; }
+.bi-grid::before { content: "\f3fc"; }
+.bi-grip-horizontal::before { content: "\f3fd"; }
+.bi-grip-vertical::before { content: "\f3fe"; }
+.bi-hammer::before { content: "\f3ff"; }
+.bi-hand-index-fill::before { content: "\f400"; }
+.bi-hand-index-thumb-fill::before { content: "\f401"; }
+.bi-hand-index-thumb::before { content: "\f402"; }
+.bi-hand-index::before { content: "\f403"; }
+.bi-hand-thumbs-down-fill::before { content: "\f404"; }
+.bi-hand-thumbs-down::before { content: "\f405"; }
+.bi-hand-thumbs-up-fill::before { content: "\f406"; }
+.bi-hand-thumbs-up::before { content: "\f407"; }
+.bi-handbag-fill::before { content: "\f408"; }
+.bi-handbag::before { content: "\f409"; }
+.bi-hash::before { content: "\f40a"; }
+.bi-hdd-fill::before { content: "\f40b"; }
+.bi-hdd-network-fill::before { content: "\f40c"; }
+.bi-hdd-network::before { content: "\f40d"; }
+.bi-hdd-rack-fill::before { content: "\f40e"; }
+.bi-hdd-rack::before { content: "\f40f"; }
+.bi-hdd-stack-fill::before { content: "\f410"; }
+.bi-hdd-stack::before { content: "\f411"; }
+.bi-hdd::before { content: "\f412"; }
+.bi-headphones::before { content: "\f413"; }
+.bi-headset::before { content: "\f414"; }
+.bi-heart-fill::before { content: "\f415"; }
+.bi-heart-half::before { content: "\f416"; }
+.bi-heart::before { content: "\f417"; }
+.bi-heptagon-fill::before { content: "\f418"; }
+.bi-heptagon-half::before { content: "\f419"; }
+.bi-heptagon::before { content: "\f41a"; }
+.bi-hexagon-fill::before { content: "\f41b"; }
+.bi-hexagon-half::before { content: "\f41c"; }
+.bi-hexagon::before { content: "\f41d"; }
+.bi-hourglass-bottom::before { content: "\f41e"; }
+.bi-hourglass-split::before { content: "\f41f"; }
+.bi-hourglass-top::before { content: "\f420"; }
+.bi-hourglass::before { content: "\f421"; }
+.bi-house-door-fill::before { content: "\f422"; }
+.bi-house-door::before { content: "\f423"; }
+.bi-house-fill::before { content: "\f424"; }
+.bi-house::before { content: "\f425"; }
+.bi-hr::before { content: "\f426"; }
+.bi-hurricane::before { content: "\f427"; }
+.bi-image-alt::before { content: "\f428"; }
+.bi-image-fill::before { content: "\f429"; }
+.bi-image::before { content: "\f42a"; }
+.bi-images::before { content: "\f42b"; }
+.bi-inbox-fill::before { content: "\f42c"; }
+.bi-inbox::before { content: "\f42d"; }
+.bi-inboxes-fill::before { content: "\f42e"; }
+.bi-inboxes::before { content: "\f42f"; }
+.bi-info-circle-fill::before { content: "\f430"; }
+.bi-info-circle::before { content: "\f431"; }
+.bi-info-square-fill::before { content: "\f432"; }
+.bi-info-square::before { content: "\f433"; }
+.bi-info::before { content: "\f434"; }
+.bi-input-cursor-text::before { content: "\f435"; }
+.bi-input-cursor::before { content: "\f436"; }
+.bi-instagram::before { content: "\f437"; }
+.bi-intersect::before { content: "\f438"; }
+.bi-journal-album::before { content: "\f439"; }
+.bi-journal-arrow-down::before { content: "\f43a"; }
+.bi-journal-arrow-up::before { content: "\f43b"; }
+.bi-journal-bookmark-fill::before { content: "\f43c"; }
+.bi-journal-bookmark::before { content: "\f43d"; }
+.bi-journal-check::before { content: "\f43e"; }
+.bi-journal-code::before { content: "\f43f"; }
+.bi-journal-medical::before { content: "\f440"; }
+.bi-journal-minus::before { content: "\f441"; }
+.bi-journal-plus::before { content: "\f442"; }
+.bi-journal-richtext::before { content: "\f443"; }
+.bi-journal-text::before { content: "\f444"; }
+.bi-journal-x::before { content: "\f445"; }
+.bi-journal::before { content: "\f446"; }
+.bi-journals::before { content: "\f447"; }
+.bi-joystick::before { content: "\f448"; }
+.bi-justify-left::before { content: "\f449"; }
+.bi-justify-right::before { content: "\f44a"; }
+.bi-justify::before { content: "\f44b"; }
+.bi-kanban-fill::before { content: "\f44c"; }
+.bi-kanban::before { content: "\f44d"; }
+.bi-key-fill::before { content: "\f44e"; }
+.bi-key::before { content: "\f44f"; }
+.bi-keyboard-fill::before { content: "\f450"; }
+.bi-keyboard::before { content: "\f451"; }
+.bi-ladder::before { content: "\f452"; }
+.bi-lamp-fill::before { content: "\f453"; }
+.bi-lamp::before { content: "\f454"; }
+.bi-laptop-fill::before { content: "\f455"; }
+.bi-laptop::before { content: "\f456"; }
+.bi-layer-backward::before { content: "\f457"; }
+.bi-layer-forward::before { content: "\f458"; }
+.bi-layers-fill::before { content: "\f459"; }
+.bi-layers-half::before { content: "\f45a"; }
+.bi-layers::before { content: "\f45b"; }
+.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; }
+.bi-layout-sidebar-inset::before { content: "\f45d"; }
+.bi-layout-sidebar-reverse::before { content: "\f45e"; }
+.bi-layout-sidebar::before { content: "\f45f"; }
+.bi-layout-split::before { content: "\f460"; }
+.bi-layout-text-sidebar-reverse::before { content: "\f461"; }
+.bi-layout-text-sidebar::before { content: "\f462"; }
+.bi-layout-text-window-reverse::before { content: "\f463"; }
+.bi-layout-text-window::before { content: "\f464"; }
+.bi-layout-three-columns::before { content: "\f465"; }
+.bi-layout-wtf::before { content: "\f466"; }
+.bi-life-preserver::before { content: "\f467"; }
+.bi-lightbulb-fill::before { content: "\f468"; }
+.bi-lightbulb-off-fill::before { content: "\f469"; }
+.bi-lightbulb-off::before { content: "\f46a"; }
+.bi-lightbulb::before { content: "\f46b"; }
+.bi-lightning-charge-fill::before { content: "\f46c"; }
+.bi-lightning-charge::before { content: "\f46d"; }
+.bi-lightning-fill::before { content: "\f46e"; }
+.bi-lightning::before { content: "\f46f"; }
+.bi-link-45deg::before { content: "\f470"; }
+.bi-link::before { content: "\f471"; }
+.bi-linkedin::before { content: "\f472"; }
+.bi-list-check::before { content: "\f473"; }
+.bi-list-nested::before { content: "\f474"; }
+.bi-list-ol::before { content: "\f475"; }
+.bi-list-stars::before { content: "\f476"; }
+.bi-list-task::before { content: "\f477"; }
+.bi-list-ul::before { content: "\f478"; }
+.bi-list::before { content: "\f479"; }
+.bi-lock-fill::before { content: "\f47a"; }
+.bi-lock::before { content: "\f47b"; }
+.bi-mailbox::before { content: "\f47c"; }
+.bi-mailbox2::before { content: "\f47d"; }
+.bi-map-fill::before { content: "\f47e"; }
+.bi-map::before { content: "\f47f"; }
+.bi-markdown-fill::before { content: "\f480"; }
+.bi-markdown::before { content: "\f481"; }
+.bi-mask::before { content: "\f482"; }
+.bi-megaphone-fill::before { content: "\f483"; }
+.bi-megaphone::before { content: "\f484"; }
+.bi-menu-app-fill::before { content: "\f485"; }
+.bi-menu-app::before { content: "\f486"; }
+.bi-menu-button-fill::before { content: "\f487"; }
+.bi-menu-button-wide-fill::before { content: "\f488"; }
+.bi-menu-button-wide::before { content: "\f489"; }
+.bi-menu-button::before { content: "\f48a"; }
+.bi-menu-down::before { content: "\f48b"; }
+.bi-menu-up::before { content: "\f48c"; }
+.bi-mic-fill::before { content: "\f48d"; }
+.bi-mic-mute-fill::before { content: "\f48e"; }
+.bi-mic-mute::before { content: "\f48f"; }
+.bi-mic::before { content: "\f490"; }
+.bi-minecart-loaded::before { content: "\f491"; }
+.bi-minecart::before { content: "\f492"; }
+.bi-moisture::before { content: "\f493"; }
+.bi-moon-fill::before { content: "\f494"; }
+.bi-moon-stars-fill::before { content: "\f495"; }
+.bi-moon-stars::before { content: "\f496"; }
+.bi-moon::before { content: "\f497"; }
+.bi-mouse-fill::before { content: "\f498"; }
+.bi-mouse::before { content: "\f499"; }
+.bi-mouse2-fill::before { content: "\f49a"; }
+.bi-mouse2::before { content: "\f49b"; }
+.bi-mouse3-fill::before { content: "\f49c"; }
+.bi-mouse3::before { content: "\f49d"; }
+.bi-music-note-beamed::before { content: "\f49e"; }
+.bi-music-note-list::before { content: "\f49f"; }
+.bi-music-note::before { content: "\f4a0"; }
+.bi-music-player-fill::before { content: "\f4a1"; }
+.bi-music-player::before { content: "\f4a2"; }
+.bi-newspaper::before { content: "\f4a3"; }
+.bi-node-minus-fill::before { content: "\f4a4"; }
+.bi-node-minus::before { content: "\f4a5"; }
+.bi-node-plus-fill::before { content: "\f4a6"; }
+.bi-node-plus::before { content: "\f4a7"; }
+.bi-nut-fill::before { content: "\f4a8"; }
+.bi-nut::before { content: "\f4a9"; }
+.bi-octagon-fill::before { content: "\f4aa"; }
+.bi-octagon-half::before { content: "\f4ab"; }
+.bi-octagon::before { content: "\f4ac"; }
+.bi-option::before { content: "\f4ad"; }
+.bi-outlet::before { content: "\f4ae"; }
+.bi-paint-bucket::before { content: "\f4af"; }
+.bi-palette-fill::before { content: "\f4b0"; }
+.bi-palette::before { content: "\f4b1"; }
+.bi-palette2::before { content: "\f4b2"; }
+.bi-paperclip::before { content: "\f4b3"; }
+.bi-paragraph::before { content: "\f4b4"; }
+.bi-patch-check-fill::before { content: "\f4b5"; }
+.bi-patch-check::before { content: "\f4b6"; }
+.bi-patch-exclamation-fill::before { content: "\f4b7"; }
+.bi-patch-exclamation::before { content: "\f4b8"; }
+.bi-patch-minus-fill::before { content: "\f4b9"; }
+.bi-patch-minus::before { content: "\f4ba"; }
+.bi-patch-plus-fill::before { content: "\f4bb"; }
+.bi-patch-plus::before { content: "\f4bc"; }
+.bi-patch-question-fill::before { content: "\f4bd"; }
+.bi-patch-question::before { content: "\f4be"; }
+.bi-pause-btn-fill::before { content: "\f4bf"; }
+.bi-pause-btn::before { content: "\f4c0"; }
+.bi-pause-circle-fill::before { content: "\f4c1"; }
+.bi-pause-circle::before { content: "\f4c2"; }
+.bi-pause-fill::before { content: "\f4c3"; }
+.bi-pause::before { content: "\f4c4"; }
+.bi-peace-fill::before { content: "\f4c5"; }
+.bi-peace::before { content: "\f4c6"; }
+.bi-pen-fill::before { content: "\f4c7"; }
+.bi-pen::before { content: "\f4c8"; }
+.bi-pencil-fill::before { content: "\f4c9"; }
+.bi-pencil-square::before { content: "\f4ca"; }
+.bi-pencil::before { content: "\f4cb"; }
+.bi-pentagon-fill::before { content: "\f4cc"; }
+.bi-pentagon-half::before { content: "\f4cd"; }
+.bi-pentagon::before { content: "\f4ce"; }
+.bi-people-fill::before { content: "\f4cf"; }
+.bi-people::before { content: "\f4d0"; }
+.bi-percent::before { content: "\f4d1"; }
+.bi-person-badge-fill::before { content: "\f4d2"; }
+.bi-person-badge::before { content: "\f4d3"; }
+.bi-person-bounding-box::before { content: "\f4d4"; }
+.bi-person-check-fill::before { content: "\f4d5"; }
+.bi-person-check::before { content: "\f4d6"; }
+.bi-person-circle::before { content: "\f4d7"; }
+.bi-person-dash-fill::before { content: "\f4d8"; }
+.bi-person-dash::before { content: "\f4d9"; }
+.bi-person-fill::before { content: "\f4da"; }
+.bi-person-lines-fill::before { content: "\f4db"; }
+.bi-person-plus-fill::before { content: "\f4dc"; }
+.bi-person-plus::before { content: "\f4dd"; }
+.bi-person-square::before { content: "\f4de"; }
+.bi-person-x-fill::before { content: "\f4df"; }
+.bi-person-x::before { content: "\f4e0"; }
+.bi-person::before { content: "\f4e1"; }
+.bi-phone-fill::before { content: "\f4e2"; }
+.bi-phone-landscape-fill::before { content: "\f4e3"; }
+.bi-phone-landscape::before { content: "\f4e4"; }
+.bi-phone-vibrate-fill::before { content: "\f4e5"; }
+.bi-phone-vibrate::before { content: "\f4e6"; }
+.bi-phone::before { content: "\f4e7"; }
+.bi-pie-chart-fill::before { content: "\f4e8"; }
+.bi-pie-chart::before { content: "\f4e9"; }
+.bi-pin-angle-fill::before { content: "\f4ea"; }
+.bi-pin-angle::before { content: "\f4eb"; }
+.bi-pin-fill::before { content: "\f4ec"; }
+.bi-pin::before { content: "\f4ed"; }
+.bi-pip-fill::before { content: "\f4ee"; }
+.bi-pip::before { content: "\f4ef"; }
+.bi-play-btn-fill::before { content: "\f4f0"; }
+.bi-play-btn::before { content: "\f4f1"; }
+.bi-play-circle-fill::before { content: "\f4f2"; }
+.bi-play-circle::before { content: "\f4f3"; }
+.bi-play-fill::before { content: "\f4f4"; }
+.bi-play::before { content: "\f4f5"; }
+.bi-plug-fill::before { content: "\f4f6"; }
+.bi-plug::before { content: "\f4f7"; }
+.bi-plus-circle-dotted::before { content: "\f4f8"; }
+.bi-plus-circle-fill::before { content: "\f4f9"; }
+.bi-plus-circle::before { content: "\f4fa"; }
+.bi-plus-square-dotted::before { content: "\f4fb"; }
+.bi-plus-square-fill::before { content: "\f4fc"; }
+.bi-plus-square::before { content: "\f4fd"; }
+.bi-plus::before { content: "\f4fe"; }
+.bi-power::before { content: "\f4ff"; }
+.bi-printer-fill::before { content: "\f500"; }
+.bi-printer::before { content: "\f501"; }
+.bi-puzzle-fill::before { content: "\f502"; }
+.bi-puzzle::before { content: "\f503"; }
+.bi-question-circle-fill::before { content: "\f504"; }
+.bi-question-circle::before { content: "\f505"; }
+.bi-question-diamond-fill::before { content: "\f506"; }
+.bi-question-diamond::before { content: "\f507"; }
+.bi-question-octagon-fill::before { content: "\f508"; }
+.bi-question-octagon::before { content: "\f509"; }
+.bi-question-square-fill::before { content: "\f50a"; }
+.bi-question-square::before { content: "\f50b"; }
+.bi-question::before { content: "\f50c"; }
+.bi-rainbow::before { content: "\f50d"; }
+.bi-receipt-cutoff::before { content: "\f50e"; }
+.bi-receipt::before { content: "\f50f"; }
+.bi-reception-0::before { content: "\f510"; }
+.bi-reception-1::before { content: "\f511"; }
+.bi-reception-2::before { content: "\f512"; }
+.bi-reception-3::before { content: "\f513"; }
+.bi-reception-4::before { content: "\f514"; }
+.bi-record-btn-fill::before { content: "\f515"; }
+.bi-record-btn::before { content: "\f516"; }
+.bi-record-circle-fill::before { content: "\f517"; }
+.bi-record-circle::before { content: "\f518"; }
+.bi-record-fill::before { content: "\f519"; }
+.bi-record::before { content: "\f51a"; }
+.bi-record2-fill::before { content: "\f51b"; }
+.bi-record2::before { content: "\f51c"; }
+.bi-reply-all-fill::before { content: "\f51d"; }
+.bi-reply-all::before { content: "\f51e"; }
+.bi-reply-fill::before { content: "\f51f"; }
+.bi-reply::before { content: "\f520"; }
+.bi-rss-fill::before { content: "\f521"; }
+.bi-rss::before { content: "\f522"; }
+.bi-rulers::before { content: "\f523"; }
+.bi-save-fill::before { content: "\f524"; }
+.bi-save::before { content: "\f525"; }
+.bi-save2-fill::before { content: "\f526"; }
+.bi-save2::before { content: "\f527"; }
+.bi-scissors::before { content: "\f528"; }
+.bi-screwdriver::before { content: "\f529"; }
+.bi-search::before { content: "\f52a"; }
+.bi-segmented-nav::before { content: "\f52b"; }
+.bi-server::before { content: "\f52c"; }
+.bi-share-fill::before { content: "\f52d"; }
+.bi-share::before { content: "\f52e"; }
+.bi-shield-check::before { content: "\f52f"; }
+.bi-shield-exclamation::before { content: "\f530"; }
+.bi-shield-fill-check::before { content: "\f531"; }
+.bi-shield-fill-exclamation::before { content: "\f532"; }
+.bi-shield-fill-minus::before { content: "\f533"; }
+.bi-shield-fill-plus::before { content: "\f534"; }
+.bi-shield-fill-x::before { content: "\f535"; }
+.bi-shield-fill::before { content: "\f536"; }
+.bi-shield-lock-fill::before { content: "\f537"; }
+.bi-shield-lock::before { content: "\f538"; }
+.bi-shield-minus::before { content: "\f539"; }
+.bi-shield-plus::before { content: "\f53a"; }
+.bi-shield-shaded::before { content: "\f53b"; }
+.bi-shield-slash-fill::before { content: "\f53c"; }
+.bi-shield-slash::before { content: "\f53d"; }
+.bi-shield-x::before { content: "\f53e"; }
+.bi-shield::before { content: "\f53f"; }
+.bi-shift-fill::before { content: "\f540"; }
+.bi-shift::before { content: "\f541"; }
+.bi-shop-window::before { content: "\f542"; }
+.bi-shop::before { content: "\f543"; }
+.bi-shuffle::before { content: "\f544"; }
+.bi-signpost-2-fill::before { content: "\f545"; }
+.bi-signpost-2::before { content: "\f546"; }
+.bi-signpost-fill::before { content: "\f547"; }
+.bi-signpost-split-fill::before { content: "\f548"; }
+.bi-signpost-split::before { content: "\f549"; }
+.bi-signpost::before { content: "\f54a"; }
+.bi-sim-fill::before { content: "\f54b"; }
+.bi-sim::before { content: "\f54c"; }
+.bi-skip-backward-btn-fill::before { content: "\f54d"; }
+.bi-skip-backward-btn::before { content: "\f54e"; }
+.bi-skip-backward-circle-fill::before { content: "\f54f"; }
+.bi-skip-backward-circle::before { content: "\f550"; }
+.bi-skip-backward-fill::before { content: "\f551"; }
+.bi-skip-backward::before { content: "\f552"; }
+.bi-skip-end-btn-fill::before { content: "\f553"; }
+.bi-skip-end-btn::before { content: "\f554"; }
+.bi-skip-end-circle-fill::before { content: "\f555"; }
+.bi-skip-end-circle::before { content: "\f556"; }
+.bi-skip-end-fill::before { content: "\f557"; }
+.bi-skip-end::before { content: "\f558"; }
+.bi-skip-forward-btn-fill::before { content: "\f559"; }
+.bi-skip-forward-btn::before { content: "\f55a"; }
+.bi-skip-forward-circle-fill::before { content: "\f55b"; }
+.bi-skip-forward-circle::before { content: "\f55c"; }
+.bi-skip-forward-fill::before { content: "\f55d"; }
+.bi-skip-forward::before { content: "\f55e"; }
+.bi-skip-start-btn-fill::before { content: "\f55f"; }
+.bi-skip-start-btn::before { content: "\f560"; }
+.bi-skip-start-circle-fill::before { content: "\f561"; }
+.bi-skip-start-circle::before { content: "\f562"; }
+.bi-skip-start-fill::before { content: "\f563"; }
+.bi-skip-start::before { content: "\f564"; }
+.bi-slack::before { content: "\f565"; }
+.bi-slash-circle-fill::before { content: "\f566"; }
+.bi-slash-circle::before { content: "\f567"; }
+.bi-slash-square-fill::before { content: "\f568"; }
+.bi-slash-square::before { content: "\f569"; }
+.bi-slash::before { content: "\f56a"; }
+.bi-sliders::before { content: "\f56b"; }
+.bi-smartwatch::before { content: "\f56c"; }
+.bi-snow::before { content: "\f56d"; }
+.bi-snow2::before { content: "\f56e"; }
+.bi-snow3::before { content: "\f56f"; }
+.bi-sort-alpha-down-alt::before { content: "\f570"; }
+.bi-sort-alpha-down::before { content: "\f571"; }
+.bi-sort-alpha-up-alt::before { content: "\f572"; }
+.bi-sort-alpha-up::before { content: "\f573"; }
+.bi-sort-down-alt::before { content: "\f574"; }
+.bi-sort-down::before { content: "\f575"; }
+.bi-sort-numeric-down-alt::before { content: "\f576"; }
+.bi-sort-numeric-down::before { content: "\f577"; }
+.bi-sort-numeric-up-alt::before { content: "\f578"; }
+.bi-sort-numeric-up::before { content: "\f579"; }
+.bi-sort-up-alt::before { content: "\f57a"; }
+.bi-sort-up::before { content: "\f57b"; }
+.bi-soundwave::before { content: "\f57c"; }
+.bi-speaker-fill::before { content: "\f57d"; }
+.bi-speaker::before { content: "\f57e"; }
+.bi-speedometer::before { content: "\f57f"; }
+.bi-speedometer2::before { content: "\f580"; }
+.bi-spellcheck::before { content: "\f581"; }
+.bi-square-fill::before { content: "\f582"; }
+.bi-square-half::before { content: "\f583"; }
+.bi-square::before { content: "\f584"; }
+.bi-stack::before { content: "\f585"; }
+.bi-star-fill::before { content: "\f586"; }
+.bi-star-half::before { content: "\f587"; }
+.bi-star::before { content: "\f588"; }
+.bi-stars::before { content: "\f589"; }
+.bi-stickies-fill::before { content: "\f58a"; }
+.bi-stickies::before { content: "\f58b"; }
+.bi-sticky-fill::before { content: "\f58c"; }
+.bi-sticky::before { content: "\f58d"; }
+.bi-stop-btn-fill::before { content: "\f58e"; }
+.bi-stop-btn::before { content: "\f58f"; }
+.bi-stop-circle-fill::before { content: "\f590"; }
+.bi-stop-circle::before { content: "\f591"; }
+.bi-stop-fill::before { content: "\f592"; }
+.bi-stop::before { content: "\f593"; }
+.bi-stoplights-fill::before { content: "\f594"; }
+.bi-stoplights::before { content: "\f595"; }
+.bi-stopwatch-fill::before { content: "\f596"; }
+.bi-stopwatch::before { content: "\f597"; }
+.bi-subtract::before { content: "\f598"; }
+.bi-suit-club-fill::before { content: "\f599"; }
+.bi-suit-club::before { content: "\f59a"; }
+.bi-suit-diamond-fill::before { content: "\f59b"; }
+.bi-suit-diamond::before { content: "\f59c"; }
+.bi-suit-heart-fill::before { content: "\f59d"; }
+.bi-suit-heart::before { content: "\f59e"; }
+.bi-suit-spade-fill::before { content: "\f59f"; }
+.bi-suit-spade::before { content: "\f5a0"; }
+.bi-sun-fill::before { content: "\f5a1"; }
+.bi-sun::before { content: "\f5a2"; }
+.bi-sunglasses::before { content: "\f5a3"; }
+.bi-sunrise-fill::before { content: "\f5a4"; }
+.bi-sunrise::before { content: "\f5a5"; }
+.bi-sunset-fill::before { content: "\f5a6"; }
+.bi-sunset::before { content: "\f5a7"; }
+.bi-symmetry-horizontal::before { content: "\f5a8"; }
+.bi-symmetry-vertical::before { content: "\f5a9"; }
+.bi-table::before { content: "\f5aa"; }
+.bi-tablet-fill::before { content: "\f5ab"; }
+.bi-tablet-landscape-fill::before { content: "\f5ac"; }
+.bi-tablet-landscape::before { content: "\f5ad"; }
+.bi-tablet::before { content: "\f5ae"; }
+.bi-tag-fill::before { content: "\f5af"; }
+.bi-tag::before { content: "\f5b0"; }
+.bi-tags-fill::before { content: "\f5b1"; }
+.bi-tags::before { content: "\f5b2"; }
+.bi-telegram::before { content: "\f5b3"; }
+.bi-telephone-fill::before { content: "\f5b4"; }
+.bi-telephone-forward-fill::before { content: "\f5b5"; }
+.bi-telephone-forward::before { content: "\f5b6"; }
+.bi-telephone-inbound-fill::before { content: "\f5b7"; }
+.bi-telephone-inbound::before { content: "\f5b8"; }
+.bi-telephone-minus-fill::before { content: "\f5b9"; }
+.bi-telephone-minus::before { content: "\f5ba"; }
+.bi-telephone-outbound-fill::before { content: "\f5bb"; }
+.bi-telephone-outbound::before { content: "\f5bc"; }
+.bi-telephone-plus-fill::before { content: "\f5bd"; }
+.bi-telephone-plus::before { content: "\f5be"; }
+.bi-telephone-x-fill::before { content: "\f5bf"; }
+.bi-telephone-x::before { content: "\f5c0"; }
+.bi-telephone::before { content: "\f5c1"; }
+.bi-terminal-fill::before { content: "\f5c2"; }
+.bi-terminal::before { content: "\f5c3"; }
+.bi-text-center::before { content: "\f5c4"; }
+.bi-text-indent-left::before { content: "\f5c5"; }
+.bi-text-indent-right::before { content: "\f5c6"; }
+.bi-text-left::before { content: "\f5c7"; }
+.bi-text-paragraph::before { content: "\f5c8"; }
+.bi-text-right::before { content: "\f5c9"; }
+.bi-textarea-resize::before { content: "\f5ca"; }
+.bi-textarea-t::before { content: "\f5cb"; }
+.bi-textarea::before { content: "\f5cc"; }
+.bi-thermometer-half::before { content: "\f5cd"; }
+.bi-thermometer-high::before { content: "\f5ce"; }
+.bi-thermometer-low::before { content: "\f5cf"; }
+.bi-thermometer-snow::before { content: "\f5d0"; }
+.bi-thermometer-sun::before { content: "\f5d1"; }
+.bi-thermometer::before { content: "\f5d2"; }
+.bi-three-dots-vertical::before { content: "\f5d3"; }
+.bi-three-dots::before { content: "\f5d4"; }
+.bi-toggle-off::before { content: "\f5d5"; }
+.bi-toggle-on::before { content: "\f5d6"; }
+.bi-toggle2-off::before { content: "\f5d7"; }
+.bi-toggle2-on::before { content: "\f5d8"; }
+.bi-toggles::before { content: "\f5d9"; }
+.bi-toggles2::before { content: "\f5da"; }
+.bi-tools::before { content: "\f5db"; }
+.bi-tornado::before { content: "\f5dc"; }
+.bi-trash-fill::before { content: "\f5dd"; }
+.bi-trash::before { content: "\f5de"; }
+.bi-trash2-fill::before { content: "\f5df"; }
+.bi-trash2::before { content: "\f5e0"; }
+.bi-tree-fill::before { content: "\f5e1"; }
+.bi-tree::before { content: "\f5e2"; }
+.bi-triangle-fill::before { content: "\f5e3"; }
+.bi-triangle-half::before { content: "\f5e4"; }
+.bi-triangle::before { content: "\f5e5"; }
+.bi-trophy-fill::before { content: "\f5e6"; }
+.bi-trophy::before { content: "\f5e7"; }
+.bi-tropical-storm::before { content: "\f5e8"; }
+.bi-truck-flatbed::before { content: "\f5e9"; }
+.bi-truck::before { content: "\f5ea"; }
+.bi-tsunami::before { content: "\f5eb"; }
+.bi-tv-fill::before { content: "\f5ec"; }
+.bi-tv::before { content: "\f5ed"; }
+.bi-twitch::before { content: "\f5ee"; }
+.bi-twitter::before { content: "\f5ef"; }
+.bi-type-bold::before { content: "\f5f0"; }
+.bi-type-h1::before { content: "\f5f1"; }
+.bi-type-h2::before { content: "\f5f2"; }
+.bi-type-h3::before { content: "\f5f3"; }
+.bi-type-italic::before { content: "\f5f4"; }
+.bi-type-strikethrough::before { content: "\f5f5"; }
+.bi-type-underline::before { content: "\f5f6"; }
+.bi-type::before { content: "\f5f7"; }
+.bi-ui-checks-grid::before { content: "\f5f8"; }
+.bi-ui-checks::before { content: "\f5f9"; }
+.bi-ui-radios-grid::before { content: "\f5fa"; }
+.bi-ui-radios::before { content: "\f5fb"; }
+.bi-umbrella-fill::before { content: "\f5fc"; }
+.bi-umbrella::before { content: "\f5fd"; }
+.bi-union::before { content: "\f5fe"; }
+.bi-unlock-fill::before { content: "\f5ff"; }
+.bi-unlock::before { content: "\f600"; }
+.bi-upc-scan::before { content: "\f601"; }
+.bi-upc::before { content: "\f602"; }
+.bi-upload::before { content: "\f603"; }
+.bi-vector-pen::before { content: "\f604"; }
+.bi-view-list::before { content: "\f605"; }
+.bi-view-stacked::before { content: "\f606"; }
+.bi-vinyl-fill::before { content: "\f607"; }
+.bi-vinyl::before { content: "\f608"; }
+.bi-voicemail::before { content: "\f609"; }
+.bi-volume-down-fill::before { content: "\f60a"; }
+.bi-volume-down::before { content: "\f60b"; }
+.bi-volume-mute-fill::before { content: "\f60c"; }
+.bi-volume-mute::before { content: "\f60d"; }
+.bi-volume-off-fill::before { content: "\f60e"; }
+.bi-volume-off::before { content: "\f60f"; }
+.bi-volume-up-fill::before { content: "\f610"; }
+.bi-volume-up::before { content: "\f611"; }
+.bi-vr::before { content: "\f612"; }
+.bi-wallet-fill::before { content: "\f613"; }
+.bi-wallet::before { content: "\f614"; }
+.bi-wallet2::before { content: "\f615"; }
+.bi-watch::before { content: "\f616"; }
+.bi-water::before { content: "\f617"; }
+.bi-whatsapp::before { content: "\f618"; }
+.bi-wifi-1::before { content: "\f619"; }
+.bi-wifi-2::before { content: "\f61a"; }
+.bi-wifi-off::before { content: "\f61b"; }
+.bi-wifi::before { content: "\f61c"; }
+.bi-wind::before { content: "\f61d"; }
+.bi-window-dock::before { content: "\f61e"; }
+.bi-window-sidebar::before { content: "\f61f"; }
+.bi-window::before { content: "\f620"; }
+.bi-wrench::before { content: "\f621"; }
+.bi-x-circle-fill::before { content: "\f622"; }
+.bi-x-circle::before { content: "\f623"; }
+.bi-x-diamond-fill::before { content: "\f624"; }
+.bi-x-diamond::before { content: "\f625"; }
+.bi-x-octagon-fill::before { content: "\f626"; }
+.bi-x-octagon::before { content: "\f627"; }
+.bi-x-square-fill::before { content: "\f628"; }
+.bi-x-square::before { content: "\f629"; }
+.bi-x::before { content: "\f62a"; }
+.bi-youtube::before { content: "\f62b"; }
+.bi-zoom-in::before { content: "\f62c"; }
+.bi-zoom-out::before { content: "\f62d"; }
+.bi-bank::before { content: "\f62e"; }
+.bi-bank2::before { content: "\f62f"; }
+.bi-bell-slash-fill::before { content: "\f630"; }
+.bi-bell-slash::before { content: "\f631"; }
+.bi-cash-coin::before { content: "\f632"; }
+.bi-check-lg::before { content: "\f633"; }
+.bi-coin::before { content: "\f634"; }
+.bi-currency-bitcoin::before { content: "\f635"; }
+.bi-currency-dollar::before { content: "\f636"; }
+.bi-currency-euro::before { content: "\f637"; }
+.bi-currency-exchange::before { content: "\f638"; }
+.bi-currency-pound::before { content: "\f639"; }
+.bi-currency-yen::before { content: "\f63a"; }
+.bi-dash-lg::before { content: "\f63b"; }
+.bi-exclamation-lg::before { content: "\f63c"; }
+.bi-file-earmark-pdf-fill::before { content: "\f63d"; }
+.bi-file-earmark-pdf::before { content: "\f63e"; }
+.bi-file-pdf-fill::before { content: "\f63f"; }
+.bi-file-pdf::before { content: "\f640"; }
+.bi-gender-ambiguous::before { content: "\f641"; }
+.bi-gender-female::before { content: "\f642"; }
+.bi-gender-male::before { content: "\f643"; }
+.bi-gender-trans::before { content: "\f644"; }
+.bi-headset-vr::before { content: "\f645"; }
+.bi-info-lg::before { content: "\f646"; }
+.bi-mastodon::before { content: "\f647"; }
+.bi-messenger::before { content: "\f648"; }
+.bi-piggy-bank-fill::before { content: "\f649"; }
+.bi-piggy-bank::before { content: "\f64a"; }
+.bi-pin-map-fill::before { content: "\f64b"; }
+.bi-pin-map::before { content: "\f64c"; }
+.bi-plus-lg::before { content: "\f64d"; }
+.bi-question-lg::before { content: "\f64e"; }
+.bi-recycle::before { content: "\f64f"; }
+.bi-reddit::before { content: "\f650"; }
+.bi-safe-fill::before { content: "\f651"; }
+.bi-safe2-fill::before { content: "\f652"; }
+.bi-safe2::before { content: "\f653"; }
+.bi-sd-card-fill::before { content: "\f654"; }
+.bi-sd-card::before { content: "\f655"; }
+.bi-skype::before { content: "\f656"; }
+.bi-slash-lg::before { content: "\f657"; }
+.bi-translate::before { content: "\f658"; }
+.bi-x-lg::before { content: "\f659"; }
+.bi-safe::before { content: "\f65a"; }
+.bi-apple::before { content: "\f65b"; }
+.bi-microsoft::before { content: "\f65d"; }
+.bi-windows::before { content: "\f65e"; }
+.bi-behance::before { content: "\f65c"; }
+.bi-dribbble::before { content: "\f65f"; }
+.bi-line::before { content: "\f660"; }
+.bi-medium::before { content: "\f661"; }
+.bi-paypal::before { content: "\f662"; }
+.bi-pinterest::before { content: "\f663"; }
+.bi-signal::before { content: "\f664"; }
+.bi-snapchat::before { content: "\f665"; }
+.bi-spotify::before { content: "\f666"; }
+.bi-stack-overflow::before { content: "\f667"; }
+.bi-strava::before { content: "\f668"; }
+.bi-wordpress::before { content: "\f669"; }
+.bi-vimeo::before { content: "\f66a"; }
+.bi-activity::before { content: "\f66b"; }
+.bi-easel2-fill::before { content: "\f66c"; }
+.bi-easel2::before { content: "\f66d"; }
+.bi-easel3-fill::before { content: "\f66e"; }
+.bi-easel3::before { content: "\f66f"; }
+.bi-fan::before { content: "\f670"; }
+.bi-fingerprint::before { content: "\f671"; }
+.bi-graph-down-arrow::before { content: "\f672"; }
+.bi-graph-up-arrow::before { content: "\f673"; }
+.bi-hypnotize::before { content: "\f674"; }
+.bi-magic::before { content: "\f675"; }
+.bi-person-rolodex::before { content: "\f676"; }
+.bi-person-video::before { content: "\f677"; }
+.bi-person-video2::before { content: "\f678"; }
+.bi-person-video3::before { content: "\f679"; }
+.bi-person-workspace::before { content: "\f67a"; }
+.bi-radioactive::before { content: "\f67b"; }
+.bi-webcam-fill::before { content: "\f67c"; }
+.bi-webcam::before { content: "\f67d"; }
+.bi-yin-yang::before { content: "\f67e"; }
+.bi-bandaid-fill::before { content: "\f680"; }
+.bi-bandaid::before { content: "\f681"; }
+.bi-bluetooth::before { content: "\f682"; }
+.bi-body-text::before { content: "\f683"; }
+.bi-boombox::before { content: "\f684"; }
+.bi-boxes::before { content: "\f685"; }
+.bi-dpad-fill::before { content: "\f686"; }
+.bi-dpad::before { content: "\f687"; }
+.bi-ear-fill::before { content: "\f688"; }
+.bi-ear::before { content: "\f689"; }
+.bi-envelope-check-fill::before { content: "\f68b"; }
+.bi-envelope-check::before { content: "\f68c"; }
+.bi-envelope-dash-fill::before { content: "\f68e"; }
+.bi-envelope-dash::before { content: "\f68f"; }
+.bi-envelope-exclamation-fill::before { content: "\f691"; }
+.bi-envelope-exclamation::before { content: "\f692"; }
+.bi-envelope-plus-fill::before { content: "\f693"; }
+.bi-envelope-plus::before { content: "\f694"; }
+.bi-envelope-slash-fill::before { content: "\f696"; }
+.bi-envelope-slash::before { content: "\f697"; }
+.bi-envelope-x-fill::before { content: "\f699"; }
+.bi-envelope-x::before { content: "\f69a"; }
+.bi-explicit-fill::before { content: "\f69b"; }
+.bi-explicit::before { content: "\f69c"; }
+.bi-git::before { content: "\f69d"; }
+.bi-infinity::before { content: "\f69e"; }
+.bi-list-columns-reverse::before { content: "\f69f"; }
+.bi-list-columns::before { content: "\f6a0"; }
+.bi-meta::before { content: "\f6a1"; }
+.bi-nintendo-switch::before { content: "\f6a4"; }
+.bi-pc-display-horizontal::before { content: "\f6a5"; }
+.bi-pc-display::before { content: "\f6a6"; }
+.bi-pc-horizontal::before { content: "\f6a7"; }
+.bi-pc::before { content: "\f6a8"; }
+.bi-playstation::before { content: "\f6a9"; }
+.bi-plus-slash-minus::before { content: "\f6aa"; }
+.bi-projector-fill::before { content: "\f6ab"; }
+.bi-projector::before { content: "\f6ac"; }
+.bi-qr-code-scan::before { content: "\f6ad"; }
+.bi-qr-code::before { content: "\f6ae"; }
+.bi-quora::before { content: "\f6af"; }
+.bi-quote::before { content: "\f6b0"; }
+.bi-robot::before { content: "\f6b1"; }
+.bi-send-check-fill::before { content: "\f6b2"; }
+.bi-send-check::before { content: "\f6b3"; }
+.bi-send-dash-fill::before { content: "\f6b4"; }
+.bi-send-dash::before { content: "\f6b5"; }
+.bi-send-exclamation-fill::before { content: "\f6b7"; }
+.bi-send-exclamation::before { content: "\f6b8"; }
+.bi-send-fill::before { content: "\f6b9"; }
+.bi-send-plus-fill::before { content: "\f6ba"; }
+.bi-send-plus::before { content: "\f6bb"; }
+.bi-send-slash-fill::before { content: "\f6bc"; }
+.bi-send-slash::before { content: "\f6bd"; }
+.bi-send-x-fill::before { content: "\f6be"; }
+.bi-send-x::before { content: "\f6bf"; }
+.bi-send::before { content: "\f6c0"; }
+.bi-steam::before { content: "\f6c1"; }
+.bi-terminal-dash::before { content: "\f6c3"; }
+.bi-terminal-plus::before { content: "\f6c4"; }
+.bi-terminal-split::before { content: "\f6c5"; }
+.bi-ticket-detailed-fill::before { content: "\f6c6"; }
+.bi-ticket-detailed::before { content: "\f6c7"; }
+.bi-ticket-fill::before { content: "\f6c8"; }
+.bi-ticket-perforated-fill::before { content: "\f6c9"; }
+.bi-ticket-perforated::before { content: "\f6ca"; }
+.bi-ticket::before { content: "\f6cb"; }
+.bi-tiktok::before { content: "\f6cc"; }
+.bi-window-dash::before { content: "\f6cd"; }
+.bi-window-desktop::before { content: "\f6ce"; }
+.bi-window-fullscreen::before { content: "\f6cf"; }
+.bi-window-plus::before { content: "\f6d0"; }
+.bi-window-split::before { content: "\f6d1"; }
+.bi-window-stack::before { content: "\f6d2"; }
+.bi-window-x::before { content: "\f6d3"; }
+.bi-xbox::before { content: "\f6d4"; }
+.bi-ethernet::before { content: "\f6d5"; }
+.bi-hdmi-fill::before { content: "\f6d6"; }
+.bi-hdmi::before { content: "\f6d7"; }
+.bi-usb-c-fill::before { content: "\f6d8"; }
+.bi-usb-c::before { content: "\f6d9"; }
+.bi-usb-fill::before { content: "\f6da"; }
+.bi-usb-plug-fill::before { content: "\f6db"; }
+.bi-usb-plug::before { content: "\f6dc"; }
+.bi-usb-symbol::before { content: "\f6dd"; }
+.bi-usb::before { content: "\f6de"; }
+.bi-boombox-fill::before { content: "\f6df"; }
+.bi-displayport::before { content: "\f6e1"; }
+.bi-gpu-card::before { content: "\f6e2"; }
+.bi-memory::before { content: "\f6e3"; }
+.bi-modem-fill::before { content: "\f6e4"; }
+.bi-modem::before { content: "\f6e5"; }
+.bi-motherboard-fill::before { content: "\f6e6"; }
+.bi-motherboard::before { content: "\f6e7"; }
+.bi-optical-audio-fill::before { content: "\f6e8"; }
+.bi-optical-audio::before { content: "\f6e9"; }
+.bi-pci-card::before { content: "\f6ea"; }
+.bi-router-fill::before { content: "\f6eb"; }
+.bi-router::before { content: "\f6ec"; }
+.bi-thunderbolt-fill::before { content: "\f6ef"; }
+.bi-thunderbolt::before { content: "\f6f0"; }
+.bi-usb-drive-fill::before { content: "\f6f1"; }
+.bi-usb-drive::before { content: "\f6f2"; }
+.bi-usb-micro-fill::before { content: "\f6f3"; }
+.bi-usb-micro::before { content: "\f6f4"; }
+.bi-usb-mini-fill::before { content: "\f6f5"; }
+.bi-usb-mini::before { content: "\f6f6"; }
+.bi-cloud-haze2::before { content: "\f6f7"; }
+.bi-device-hdd-fill::before { content: "\f6f8"; }
+.bi-device-hdd::before { content: "\f6f9"; }
+.bi-device-ssd-fill::before { content: "\f6fa"; }
+.bi-device-ssd::before { content: "\f6fb"; }
+.bi-displayport-fill::before { content: "\f6fc"; }
+.bi-mortarboard-fill::before { content: "\f6fd"; }
+.bi-mortarboard::before { content: "\f6fe"; }
+.bi-terminal-x::before { content: "\f6ff"; }
+.bi-arrow-through-heart-fill::before { content: "\f700"; }
+.bi-arrow-through-heart::before { content: "\f701"; }
+.bi-badge-sd-fill::before { content: "\f702"; }
+.bi-badge-sd::before { content: "\f703"; }
+.bi-bag-heart-fill::before { content: "\f704"; }
+.bi-bag-heart::before { content: "\f705"; }
+.bi-balloon-fill::before { content: "\f706"; }
+.bi-balloon-heart-fill::before { content: "\f707"; }
+.bi-balloon-heart::before { content: "\f708"; }
+.bi-balloon::before { content: "\f709"; }
+.bi-box2-fill::before { content: "\f70a"; }
+.bi-box2-heart-fill::before { content: "\f70b"; }
+.bi-box2-heart::before { content: "\f70c"; }
+.bi-box2::before { content: "\f70d"; }
+.bi-braces-asterisk::before { content: "\f70e"; }
+.bi-calendar-heart-fill::before { content: "\f70f"; }
+.bi-calendar-heart::before { content: "\f710"; }
+.bi-calendar2-heart-fill::before { content: "\f711"; }
+.bi-calendar2-heart::before { content: "\f712"; }
+.bi-chat-heart-fill::before { content: "\f713"; }
+.bi-chat-heart::before { content: "\f714"; }
+.bi-chat-left-heart-fill::before { content: "\f715"; }
+.bi-chat-left-heart::before { content: "\f716"; }
+.bi-chat-right-heart-fill::before { content: "\f717"; }
+.bi-chat-right-heart::before { content: "\f718"; }
+.bi-chat-square-heart-fill::before { content: "\f719"; }
+.bi-chat-square-heart::before { content: "\f71a"; }
+.bi-clipboard-check-fill::before { content: "\f71b"; }
+.bi-clipboard-data-fill::before { content: "\f71c"; }
+.bi-clipboard-fill::before { content: "\f71d"; }
+.bi-clipboard-heart-fill::before { content: "\f71e"; }
+.bi-clipboard-heart::before { content: "\f71f"; }
+.bi-clipboard-minus-fill::before { content: "\f720"; }
+.bi-clipboard-plus-fill::before { content: "\f721"; }
+.bi-clipboard-pulse::before { content: "\f722"; }
+.bi-clipboard-x-fill::before { content: "\f723"; }
+.bi-clipboard2-check-fill::before { content: "\f724"; }
+.bi-clipboard2-check::before { content: "\f725"; }
+.bi-clipboard2-data-fill::before { content: "\f726"; }
+.bi-clipboard2-data::before { content: "\f727"; }
+.bi-clipboard2-fill::before { content: "\f728"; }
+.bi-clipboard2-heart-fill::before { content: "\f729"; }
+.bi-clipboard2-heart::before { content: "\f72a"; }
+.bi-clipboard2-minus-fill::before { content: "\f72b"; }
+.bi-clipboard2-minus::before { content: "\f72c"; }
+.bi-clipboard2-plus-fill::before { content: "\f72d"; }
+.bi-clipboard2-plus::before { content: "\f72e"; }
+.bi-clipboard2-pulse-fill::before { content: "\f72f"; }
+.bi-clipboard2-pulse::before { content: "\f730"; }
+.bi-clipboard2-x-fill::before { content: "\f731"; }
+.bi-clipboard2-x::before { content: "\f732"; }
+.bi-clipboard2::before { content: "\f733"; }
+.bi-emoji-kiss-fill::before { content: "\f734"; }
+.bi-emoji-kiss::before { content: "\f735"; }
+.bi-envelope-heart-fill::before { content: "\f736"; }
+.bi-envelope-heart::before { content: "\f737"; }
+.bi-envelope-open-heart-fill::before { content: "\f738"; }
+.bi-envelope-open-heart::before { content: "\f739"; }
+.bi-envelope-paper-fill::before { content: "\f73a"; }
+.bi-envelope-paper-heart-fill::before { content: "\f73b"; }
+.bi-envelope-paper-heart::before { content: "\f73c"; }
+.bi-envelope-paper::before { content: "\f73d"; }
+.bi-filetype-aac::before { content: "\f73e"; }
+.bi-filetype-ai::before { content: "\f73f"; }
+.bi-filetype-bmp::before { content: "\f740"; }
+.bi-filetype-cs::before { content: "\f741"; }
+.bi-filetype-css::before { content: "\f742"; }
+.bi-filetype-csv::before { content: "\f743"; }
+.bi-filetype-doc::before { content: "\f744"; }
+.bi-filetype-docx::before { content: "\f745"; }
+.bi-filetype-exe::before { content: "\f746"; }
+.bi-filetype-gif::before { content: "\f747"; }
+.bi-filetype-heic::before { content: "\f748"; }
+.bi-filetype-html::before { content: "\f749"; }
+.bi-filetype-java::before { content: "\f74a"; }
+.bi-filetype-jpg::before { content: "\f74b"; }
+.bi-filetype-js::before { content: "\f74c"; }
+.bi-filetype-jsx::before { content: "\f74d"; }
+.bi-filetype-key::before { content: "\f74e"; }
+.bi-filetype-m4p::before { content: "\f74f"; }
+.bi-filetype-md::before { content: "\f750"; }
+.bi-filetype-mdx::before { content: "\f751"; }
+.bi-filetype-mov::before { content: "\f752"; }
+.bi-filetype-mp3::before { content: "\f753"; }
+.bi-filetype-mp4::before { content: "\f754"; }
+.bi-filetype-otf::before { content: "\f755"; }
+.bi-filetype-pdf::before { content: "\f756"; }
+.bi-filetype-php::before { content: "\f757"; }
+.bi-filetype-png::before { content: "\f758"; }
+.bi-filetype-ppt::before { content: "\f75a"; }
+.bi-filetype-psd::before { content: "\f75b"; }
+.bi-filetype-py::before { content: "\f75c"; }
+.bi-filetype-raw::before { content: "\f75d"; }
+.bi-filetype-rb::before { content: "\f75e"; }
+.bi-filetype-sass::before { content: "\f75f"; }
+.bi-filetype-scss::before { content: "\f760"; }
+.bi-filetype-sh::before { content: "\f761"; }
+.bi-filetype-svg::before { content: "\f762"; }
+.bi-filetype-tiff::before { content: "\f763"; }
+.bi-filetype-tsx::before { content: "\f764"; }
+.bi-filetype-ttf::before { content: "\f765"; }
+.bi-filetype-txt::before { content: "\f766"; }
+.bi-filetype-wav::before { content: "\f767"; }
+.bi-filetype-woff::before { content: "\f768"; }
+.bi-filetype-xls::before { content: "\f76a"; }
+.bi-filetype-xml::before { content: "\f76b"; }
+.bi-filetype-yml::before { content: "\f76c"; }
+.bi-heart-arrow::before { content: "\f76d"; }
+.bi-heart-pulse-fill::before { content: "\f76e"; }
+.bi-heart-pulse::before { content: "\f76f"; }
+.bi-heartbreak-fill::before { content: "\f770"; }
+.bi-heartbreak::before { content: "\f771"; }
+.bi-hearts::before { content: "\f772"; }
+.bi-hospital-fill::before { content: "\f773"; }
+.bi-hospital::before { content: "\f774"; }
+.bi-house-heart-fill::before { content: "\f775"; }
+.bi-house-heart::before { content: "\f776"; }
+.bi-incognito::before { content: "\f777"; }
+.bi-magnet-fill::before { content: "\f778"; }
+.bi-magnet::before { content: "\f779"; }
+.bi-person-heart::before { content: "\f77a"; }
+.bi-person-hearts::before { content: "\f77b"; }
+.bi-phone-flip::before { content: "\f77c"; }
+.bi-plugin::before { content: "\f77d"; }
+.bi-postage-fill::before { content: "\f77e"; }
+.bi-postage-heart-fill::before { content: "\f77f"; }
+.bi-postage-heart::before { content: "\f780"; }
+.bi-postage::before { content: "\f781"; }
+.bi-postcard-fill::before { content: "\f782"; }
+.bi-postcard-heart-fill::before { content: "\f783"; }
+.bi-postcard-heart::before { content: "\f784"; }
+.bi-postcard::before { content: "\f785"; }
+.bi-search-heart-fill::before { content: "\f786"; }
+.bi-search-heart::before { content: "\f787"; }
+.bi-sliders2-vertical::before { content: "\f788"; }
+.bi-sliders2::before { content: "\f789"; }
+.bi-trash3-fill::before { content: "\f78a"; }
+.bi-trash3::before { content: "\f78b"; }
+.bi-valentine::before { content: "\f78c"; }
+.bi-valentine2::before { content: "\f78d"; }
+.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; }
+.bi-wrench-adjustable-circle::before { content: "\f78f"; }
+.bi-wrench-adjustable::before { content: "\f790"; }
+.bi-filetype-json::before { content: "\f791"; }
+.bi-filetype-pptx::before { content: "\f792"; }
+.bi-filetype-xlsx::before { content: "\f793"; }
+.bi-1-circle-fill::before { content: "\f796"; }
+.bi-1-circle::before { content: "\f797"; }
+.bi-1-square-fill::before { content: "\f798"; }
+.bi-1-square::before { content: "\f799"; }
+.bi-2-circle-fill::before { content: "\f79c"; }
+.bi-2-circle::before { content: "\f79d"; }
+.bi-2-square-fill::before { content: "\f79e"; }
+.bi-2-square::before { content: "\f79f"; }
+.bi-3-circle-fill::before { content: "\f7a2"; }
+.bi-3-circle::before { content: "\f7a3"; }
+.bi-3-square-fill::before { content: "\f7a4"; }
+.bi-3-square::before { content: "\f7a5"; }
+.bi-4-circle-fill::before { content: "\f7a8"; }
+.bi-4-circle::before { content: "\f7a9"; }
+.bi-4-square-fill::before { content: "\f7aa"; }
+.bi-4-square::before { content: "\f7ab"; }
+.bi-5-circle-fill::before { content: "\f7ae"; }
+.bi-5-circle::before { content: "\f7af"; }
+.bi-5-square-fill::before { content: "\f7b0"; }
+.bi-5-square::before { content: "\f7b1"; }
+.bi-6-circle-fill::before { content: "\f7b4"; }
+.bi-6-circle::before { content: "\f7b5"; }
+.bi-6-square-fill::before { content: "\f7b6"; }
+.bi-6-square::before { content: "\f7b7"; }
+.bi-7-circle-fill::before { content: "\f7ba"; }
+.bi-7-circle::before { content: "\f7bb"; }
+.bi-7-square-fill::before { content: "\f7bc"; }
+.bi-7-square::before { content: "\f7bd"; }
+.bi-8-circle-fill::before { content: "\f7c0"; }
+.bi-8-circle::before { content: "\f7c1"; }
+.bi-8-square-fill::before { content: "\f7c2"; }
+.bi-8-square::before { content: "\f7c3"; }
+.bi-9-circle-fill::before { content: "\f7c6"; }
+.bi-9-circle::before { content: "\f7c7"; }
+.bi-9-square-fill::before { content: "\f7c8"; }
+.bi-9-square::before { content: "\f7c9"; }
+.bi-airplane-engines-fill::before { content: "\f7ca"; }
+.bi-airplane-engines::before { content: "\f7cb"; }
+.bi-airplane-fill::before { content: "\f7cc"; }
+.bi-airplane::before { content: "\f7cd"; }
+.bi-alexa::before { content: "\f7ce"; }
+.bi-alipay::before { content: "\f7cf"; }
+.bi-android::before { content: "\f7d0"; }
+.bi-android2::before { content: "\f7d1"; }
+.bi-box-fill::before { content: "\f7d2"; }
+.bi-box-seam-fill::before { content: "\f7d3"; }
+.bi-browser-chrome::before { content: "\f7d4"; }
+.bi-browser-edge::before { content: "\f7d5"; }
+.bi-browser-firefox::before { content: "\f7d6"; }
+.bi-browser-safari::before { content: "\f7d7"; }
+.bi-c-circle-fill::before { content: "\f7da"; }
+.bi-c-circle::before { content: "\f7db"; }
+.bi-c-square-fill::before { content: "\f7dc"; }
+.bi-c-square::before { content: "\f7dd"; }
+.bi-capsule-pill::before { content: "\f7de"; }
+.bi-capsule::before { content: "\f7df"; }
+.bi-car-front-fill::before { content: "\f7e0"; }
+.bi-car-front::before { content: "\f7e1"; }
+.bi-cassette-fill::before { content: "\f7e2"; }
+.bi-cassette::before { content: "\f7e3"; }
+.bi-cc-circle-fill::before { content: "\f7e6"; }
+.bi-cc-circle::before { content: "\f7e7"; }
+.bi-cc-square-fill::before { content: "\f7e8"; }
+.bi-cc-square::before { content: "\f7e9"; }
+.bi-cup-hot-fill::before { content: "\f7ea"; }
+.bi-cup-hot::before { content: "\f7eb"; }
+.bi-currency-rupee::before { content: "\f7ec"; }
+.bi-dropbox::before { content: "\f7ed"; }
+.bi-escape::before { content: "\f7ee"; }
+.bi-fast-forward-btn-fill::before { content: "\f7ef"; }
+.bi-fast-forward-btn::before { content: "\f7f0"; }
+.bi-fast-forward-circle-fill::before { content: "\f7f1"; }
+.bi-fast-forward-circle::before { content: "\f7f2"; }
+.bi-fast-forward-fill::before { content: "\f7f3"; }
+.bi-fast-forward::before { content: "\f7f4"; }
+.bi-filetype-sql::before { content: "\f7f5"; }
+.bi-fire::before { content: "\f7f6"; }
+.bi-google-play::before { content: "\f7f7"; }
+.bi-h-circle-fill::before { content: "\f7fa"; }
+.bi-h-circle::before { content: "\f7fb"; }
+.bi-h-square-fill::before { content: "\f7fc"; }
+.bi-h-square::before { content: "\f7fd"; }
+.bi-indent::before { content: "\f7fe"; }
+.bi-lungs-fill::before { content: "\f7ff"; }
+.bi-lungs::before { content: "\f800"; }
+.bi-microsoft-teams::before { content: "\f801"; }
+.bi-p-circle-fill::before { content: "\f804"; }
+.bi-p-circle::before { content: "\f805"; }
+.bi-p-square-fill::before { content: "\f806"; }
+.bi-p-square::before { content: "\f807"; }
+.bi-pass-fill::before { content: "\f808"; }
+.bi-pass::before { content: "\f809"; }
+.bi-prescription::before { content: "\f80a"; }
+.bi-prescription2::before { content: "\f80b"; }
+.bi-r-circle-fill::before { content: "\f80e"; }
+.bi-r-circle::before { content: "\f80f"; }
+.bi-r-square-fill::before { content: "\f810"; }
+.bi-r-square::before { content: "\f811"; }
+.bi-repeat-1::before { content: "\f812"; }
+.bi-repeat::before { content: "\f813"; }
+.bi-rewind-btn-fill::before { content: "\f814"; }
+.bi-rewind-btn::before { content: "\f815"; }
+.bi-rewind-circle-fill::before { content: "\f816"; }
+.bi-rewind-circle::before { content: "\f817"; }
+.bi-rewind-fill::before { content: "\f818"; }
+.bi-rewind::before { content: "\f819"; }
+.bi-train-freight-front-fill::before { content: "\f81a"; }
+.bi-train-freight-front::before { content: "\f81b"; }
+.bi-train-front-fill::before { content: "\f81c"; }
+.bi-train-front::before { content: "\f81d"; }
+.bi-train-lightrail-front-fill::before { content: "\f81e"; }
+.bi-train-lightrail-front::before { content: "\f81f"; }
+.bi-truck-front-fill::before { content: "\f820"; }
+.bi-truck-front::before { content: "\f821"; }
+.bi-ubuntu::before { content: "\f822"; }
+.bi-unindent::before { content: "\f823"; }
+.bi-unity::before { content: "\f824"; }
+.bi-universal-access-circle::before { content: "\f825"; }
+.bi-universal-access::before { content: "\f826"; }
+.bi-virus::before { content: "\f827"; }
+.bi-virus2::before { content: "\f828"; }
+.bi-wechat::before { content: "\f829"; }
+.bi-yelp::before { content: "\f82a"; }
+.bi-sign-stop-fill::before { content: "\f82b"; }
+.bi-sign-stop-lights-fill::before { content: "\f82c"; }
+.bi-sign-stop-lights::before { content: "\f82d"; }
+.bi-sign-stop::before { content: "\f82e"; }
+.bi-sign-turn-left-fill::before { content: "\f82f"; }
+.bi-sign-turn-left::before { content: "\f830"; }
+.bi-sign-turn-right-fill::before { content: "\f831"; }
+.bi-sign-turn-right::before { content: "\f832"; }
+.bi-sign-turn-slight-left-fill::before { content: "\f833"; }
+.bi-sign-turn-slight-left::before { content: "\f834"; }
+.bi-sign-turn-slight-right-fill::before { content: "\f835"; }
+.bi-sign-turn-slight-right::before { content: "\f836"; }
+.bi-sign-yield-fill::before { content: "\f837"; }
+.bi-sign-yield::before { content: "\f838"; }
+.bi-ev-station-fill::before { content: "\f839"; }
+.bi-ev-station::before { content: "\f83a"; }
+.bi-fuel-pump-diesel-fill::before { content: "\f83b"; }
+.bi-fuel-pump-diesel::before { content: "\f83c"; }
+.bi-fuel-pump-fill::before { content: "\f83d"; }
+.bi-fuel-pump::before { content: "\f83e"; }
+.bi-0-circle-fill::before { content: "\f83f"; }
+.bi-0-circle::before { content: "\f840"; }
+.bi-0-square-fill::before { content: "\f841"; }
+.bi-0-square::before { content: "\f842"; }
+.bi-rocket-fill::before { content: "\f843"; }
+.bi-rocket-takeoff-fill::before { content: "\f844"; }
+.bi-rocket-takeoff::before { content: "\f845"; }
+.bi-rocket::before { content: "\f846"; }
+.bi-stripe::before { content: "\f847"; }
+.bi-subscript::before { content: "\f848"; }
+.bi-superscript::before { content: "\f849"; }
+.bi-trello::before { content: "\f84a"; }
+.bi-envelope-at-fill::before { content: "\f84b"; }
+.bi-envelope-at::before { content: "\f84c"; }
+.bi-regex::before { content: "\f84d"; }
+.bi-text-wrap::before { content: "\f84e"; }
+.bi-sign-dead-end-fill::before { content: "\f84f"; }
+.bi-sign-dead-end::before { content: "\f850"; }
+.bi-sign-do-not-enter-fill::before { content: "\f851"; }
+.bi-sign-do-not-enter::before { content: "\f852"; }
+.bi-sign-intersection-fill::before { content: "\f853"; }
+.bi-sign-intersection-side-fill::before { content: "\f854"; }
+.bi-sign-intersection-side::before { content: "\f855"; }
+.bi-sign-intersection-t-fill::before { content: "\f856"; }
+.bi-sign-intersection-t::before { content: "\f857"; }
+.bi-sign-intersection-y-fill::before { content: "\f858"; }
+.bi-sign-intersection-y::before { content: "\f859"; }
+.bi-sign-intersection::before { content: "\f85a"; }
+.bi-sign-merge-left-fill::before { content: "\f85b"; }
+.bi-sign-merge-left::before { content: "\f85c"; }
+.bi-sign-merge-right-fill::before { content: "\f85d"; }
+.bi-sign-merge-right::before { content: "\f85e"; }
+.bi-sign-no-left-turn-fill::before { content: "\f85f"; }
+.bi-sign-no-left-turn::before { content: "\f860"; }
+.bi-sign-no-parking-fill::before { content: "\f861"; }
+.bi-sign-no-parking::before { content: "\f862"; }
+.bi-sign-no-right-turn-fill::before { content: "\f863"; }
+.bi-sign-no-right-turn::before { content: "\f864"; }
+.bi-sign-railroad-fill::before { content: "\f865"; }
+.bi-sign-railroad::before { content: "\f866"; }
+.bi-building-add::before { content: "\f867"; }
+.bi-building-check::before { content: "\f868"; }
+.bi-building-dash::before { content: "\f869"; }
+.bi-building-down::before { content: "\f86a"; }
+.bi-building-exclamation::before { content: "\f86b"; }
+.bi-building-fill-add::before { content: "\f86c"; }
+.bi-building-fill-check::before { content: "\f86d"; }
+.bi-building-fill-dash::before { content: "\f86e"; }
+.bi-building-fill-down::before { content: "\f86f"; }
+.bi-building-fill-exclamation::before { content: "\f870"; }
+.bi-building-fill-gear::before { content: "\f871"; }
+.bi-building-fill-lock::before { content: "\f872"; }
+.bi-building-fill-slash::before { content: "\f873"; }
+.bi-building-fill-up::before { content: "\f874"; }
+.bi-building-fill-x::before { content: "\f875"; }
+.bi-building-fill::before { content: "\f876"; }
+.bi-building-gear::before { content: "\f877"; }
+.bi-building-lock::before { content: "\f878"; }
+.bi-building-slash::before { content: "\f879"; }
+.bi-building-up::before { content: "\f87a"; }
+.bi-building-x::before { content: "\f87b"; }
+.bi-buildings-fill::before { content: "\f87c"; }
+.bi-buildings::before { content: "\f87d"; }
+.bi-bus-front-fill::before { content: "\f87e"; }
+.bi-bus-front::before { content: "\f87f"; }
+.bi-ev-front-fill::before { content: "\f880"; }
+.bi-ev-front::before { content: "\f881"; }
+.bi-globe-americas::before { content: "\f882"; }
+.bi-globe-asia-australia::before { content: "\f883"; }
+.bi-globe-central-south-asia::before { content: "\f884"; }
+.bi-globe-europe-africa::before { content: "\f885"; }
+.bi-house-add-fill::before { content: "\f886"; }
+.bi-house-add::before { content: "\f887"; }
+.bi-house-check-fill::before { content: "\f888"; }
+.bi-house-check::before { content: "\f889"; }
+.bi-house-dash-fill::before { content: "\f88a"; }
+.bi-house-dash::before { content: "\f88b"; }
+.bi-house-down-fill::before { content: "\f88c"; }
+.bi-house-down::before { content: "\f88d"; }
+.bi-house-exclamation-fill::before { content: "\f88e"; }
+.bi-house-exclamation::before { content: "\f88f"; }
+.bi-house-gear-fill::before { content: "\f890"; }
+.bi-house-gear::before { content: "\f891"; }
+.bi-house-lock-fill::before { content: "\f892"; }
+.bi-house-lock::before { content: "\f893"; }
+.bi-house-slash-fill::before { content: "\f894"; }
+.bi-house-slash::before { content: "\f895"; }
+.bi-house-up-fill::before { content: "\f896"; }
+.bi-house-up::before { content: "\f897"; }
+.bi-house-x-fill::before { content: "\f898"; }
+.bi-house-x::before { content: "\f899"; }
+.bi-person-add::before { content: "\f89a"; }
+.bi-person-down::before { content: "\f89b"; }
+.bi-person-exclamation::before { content: "\f89c"; }
+.bi-person-fill-add::before { content: "\f89d"; }
+.bi-person-fill-check::before { content: "\f89e"; }
+.bi-person-fill-dash::before { content: "\f89f"; }
+.bi-person-fill-down::before { content: "\f8a0"; }
+.bi-person-fill-exclamation::before { content: "\f8a1"; }
+.bi-person-fill-gear::before { content: "\f8a2"; }
+.bi-person-fill-lock::before { content: "\f8a3"; }
+.bi-person-fill-slash::before { content: "\f8a4"; }
+.bi-person-fill-up::before { content: "\f8a5"; }
+.bi-person-fill-x::before { content: "\f8a6"; }
+.bi-person-gear::before { content: "\f8a7"; }
+.bi-person-lock::before { content: "\f8a8"; }
+.bi-person-slash::before { content: "\f8a9"; }
+.bi-person-up::before { content: "\f8aa"; }
+.bi-scooter::before { content: "\f8ab"; }
+.bi-taxi-front-fill::before { content: "\f8ac"; }
+.bi-taxi-front::before { content: "\f8ad"; }
+.bi-amd::before { content: "\f8ae"; }
+.bi-database-add::before { content: "\f8af"; }
+.bi-database-check::before { content: "\f8b0"; }
+.bi-database-dash::before { content: "\f8b1"; }
+.bi-database-down::before { content: "\f8b2"; }
+.bi-database-exclamation::before { content: "\f8b3"; }
+.bi-database-fill-add::before { content: "\f8b4"; }
+.bi-database-fill-check::before { content: "\f8b5"; }
+.bi-database-fill-dash::before { content: "\f8b6"; }
+.bi-database-fill-down::before { content: "\f8b7"; }
+.bi-database-fill-exclamation::before { content: "\f8b8"; }
+.bi-database-fill-gear::before { content: "\f8b9"; }
+.bi-database-fill-lock::before { content: "\f8ba"; }
+.bi-database-fill-slash::before { content: "\f8bb"; }
+.bi-database-fill-up::before { content: "\f8bc"; }
+.bi-database-fill-x::before { content: "\f8bd"; }
+.bi-database-fill::before { content: "\f8be"; }
+.bi-database-gear::before { content: "\f8bf"; }
+.bi-database-lock::before { content: "\f8c0"; }
+.bi-database-slash::before { content: "\f8c1"; }
+.bi-database-up::before { content: "\f8c2"; }
+.bi-database-x::before { content: "\f8c3"; }
+.bi-database::before { content: "\f8c4"; }
+.bi-houses-fill::before { content: "\f8c5"; }
+.bi-houses::before { content: "\f8c6"; }
+.bi-nvidia::before { content: "\f8c7"; }
+.bi-person-vcard-fill::before { content: "\f8c8"; }
+.bi-person-vcard::before { content: "\f8c9"; }
+.bi-sina-weibo::before { content: "\f8ca"; }
+.bi-tencent-qq::before { content: "\f8cb"; }
+.bi-wikipedia::before { content: "\f8cc"; }
+.bi-alphabet-uppercase::before { content: "\f2a5"; }
+.bi-alphabet::before { content: "\f68a"; }
+.bi-amazon::before { content: "\f68d"; }
+.bi-arrows-collapse-vertical::before { content: "\f690"; }
+.bi-arrows-expand-vertical::before { content: "\f695"; }
+.bi-arrows-vertical::before { content: "\f698"; }
+.bi-arrows::before { content: "\f6a2"; }
+.bi-ban-fill::before { content: "\f6a3"; }
+.bi-ban::before { content: "\f6b6"; }
+.bi-bing::before { content: "\f6c2"; }
+.bi-cake::before { content: "\f6e0"; }
+.bi-cake2::before { content: "\f6ed"; }
+.bi-cookie::before { content: "\f6ee"; }
+.bi-copy::before { content: "\f759"; }
+.bi-crosshair::before { content: "\f769"; }
+.bi-crosshair2::before { content: "\f794"; }
+.bi-emoji-astonished-fill::before { content: "\f795"; }
+.bi-emoji-astonished::before { content: "\f79a"; }
+.bi-emoji-grimace-fill::before { content: "\f79b"; }
+.bi-emoji-grimace::before { content: "\f7a0"; }
+.bi-emoji-grin-fill::before { content: "\f7a1"; }
+.bi-emoji-grin::before { content: "\f7a6"; }
+.bi-emoji-surprise-fill::before { content: "\f7a7"; }
+.bi-emoji-surprise::before { content: "\f7ac"; }
+.bi-emoji-tear-fill::before { content: "\f7ad"; }
+.bi-emoji-tear::before { content: "\f7b2"; }
+.bi-envelope-arrow-down-fill::before { content: "\f7b3"; }
+.bi-envelope-arrow-down::before { content: "\f7b8"; }
+.bi-envelope-arrow-up-fill::before { content: "\f7b9"; }
+.bi-envelope-arrow-up::before { content: "\f7be"; }
+.bi-feather::before { content: "\f7bf"; }
+.bi-feather2::before { content: "\f7c4"; }
+.bi-floppy-fill::before { content: "\f7c5"; }
+.bi-floppy::before { content: "\f7d8"; }
+.bi-floppy2-fill::before { content: "\f7d9"; }
+.bi-floppy2::before { content: "\f7e4"; }
+.bi-gitlab::before { content: "\f7e5"; }
+.bi-highlighter::before { content: "\f7f8"; }
+.bi-marker-tip::before { content: "\f802"; }
+.bi-nvme-fill::before { content: "\f803"; }
+.bi-nvme::before { content: "\f80c"; }
+.bi-opencollective::before { content: "\f80d"; }
+.bi-pci-card-network::before { content: "\f8cd"; }
+.bi-pci-card-sound::before { content: "\f8ce"; }
+.bi-radar::before { content: "\f8cf"; }
+.bi-send-arrow-down-fill::before { content: "\f8d0"; }
+.bi-send-arrow-down::before { content: "\f8d1"; }
+.bi-send-arrow-up-fill::before { content: "\f8d2"; }
+.bi-send-arrow-up::before { content: "\f8d3"; }
+.bi-sim-slash-fill::before { content: "\f8d4"; }
+.bi-sim-slash::before { content: "\f8d5"; }
+.bi-sourceforge::before { content: "\f8d6"; }
+.bi-substack::before { content: "\f8d7"; }
+.bi-threads-fill::before { content: "\f8d8"; }
+.bi-threads::before { content: "\f8d9"; }
+.bi-transparency::before { content: "\f8da"; }
+.bi-twitter-x::before { content: "\f8db"; }
+.bi-type-h4::before { content: "\f8dc"; }
+.bi-type-h5::before { content: "\f8dd"; }
+.bi-type-h6::before { content: "\f8de"; }
+.bi-backpack-fill::before { content: "\f8df"; }
+.bi-backpack::before { content: "\f8e0"; }
+.bi-backpack2-fill::before { content: "\f8e1"; }
+.bi-backpack2::before { content: "\f8e2"; }
+.bi-backpack3-fill::before { content: "\f8e3"; }
+.bi-backpack3::before { content: "\f8e4"; }
+.bi-backpack4-fill::before { content: "\f8e5"; }
+.bi-backpack4::before { content: "\f8e6"; }
+.bi-brilliance::before { content: "\f8e7"; }
+.bi-cake-fill::before { content: "\f8e8"; }
+.bi-cake2-fill::before { content: "\f8e9"; }
+.bi-duffle-fill::before { content: "\f8ea"; }
+.bi-duffle::before { content: "\f8eb"; }
+.bi-exposure::before { content: "\f8ec"; }
+.bi-gender-neuter::before { content: "\f8ed"; }
+.bi-highlights::before { content: "\f8ee"; }
+.bi-luggage-fill::before { content: "\f8ef"; }
+.bi-luggage::before { content: "\f8f0"; }
+.bi-mailbox-flag::before { content: "\f8f1"; }
+.bi-mailbox2-flag::before { content: "\f8f2"; }
+.bi-noise-reduction::before { content: "\f8f3"; }
+.bi-passport-fill::before { content: "\f8f4"; }
+.bi-passport::before { content: "\f8f5"; }
+.bi-person-arms-up::before { content: "\f8f6"; }
+.bi-person-raised-hand::before { content: "\f8f7"; }
+.bi-person-standing-dress::before { content: "\f8f8"; }
+.bi-person-standing::before { content: "\f8f9"; }
+.bi-person-walking::before { content: "\f8fa"; }
+.bi-person-wheelchair::before { content: "\f8fb"; }
+.bi-shadows::before { content: "\f8fc"; }
+.bi-suitcase-fill::before { content: "\f8fd"; }
+.bi-suitcase-lg-fill::before { content: "\f8fe"; }
+.bi-suitcase-lg::before { content: "\f8ff"; }
+.bi-suitcase::before { content: "\f900"; }
+.bi-suitcase2-fill::before { content: "\f901"; }
+.bi-suitcase2::before { content: "\f902"; }
+.bi-vignette::before { content: "\f903"; }
diff --git a/public/notebooks/music-recommendation_files/libs/bootstrap/bootstrap-icons.woff b/public/notebooks/music-recommendation_files/libs/bootstrap/bootstrap-icons.woff
new file mode 100644
index 0000000..dbeeb05
Binary files /dev/null and b/public/notebooks/music-recommendation_files/libs/bootstrap/bootstrap-icons.woff differ
diff --git a/public/notebooks/music-recommendation_files/libs/bootstrap/bootstrap.min.js b/public/notebooks/music-recommendation_files/libs/bootstrap/bootstrap.min.js
new file mode 100644
index 0000000..e8f21f7
--- /dev/null
+++ b/public/notebooks/music-recommendation_files/libs/bootstrap/bootstrap.min.js
@@ -0,0 +1,7 @@
+/*!
+ * Bootstrap v5.3.1 (https://getbootstrap.com/)
+ * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=I(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return P(s,{delegateTarget:r}),n.oneOff&&N.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return P(n,{delegateTarget:t}),i.oneOff&&N.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function $(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function I(t){return t=t.replace(y,""),T[t]||t}const N={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))$(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(w,"");a&&!e.includes(s)||D(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;D(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==I(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=P(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function P(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function M(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function j(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const F={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${j(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${j(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=M(t.dataset[n])}return e},getDataAttribute:(t,e)=>M(t.getAttribute(`data-bs-${j(e)}`))};class H{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?F.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?F.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],r=o(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(r))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${r}" but expected type "${s}".`)}var i}}class W extends H{constructor(t,i){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(i),e.set(this._element,this.constructor.DATA_KEY,this))}dispose(){e.remove(this._element,this.constructor.DATA_KEY),N.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return e.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.1"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const B=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return n(e)},z={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))},getSelectorFromElement(t){const e=B(t);return e&&z.findOne(e)?e:null},getElementFromSelector(t){const e=B(t);return e?z.findOne(e):null},getMultipleElementsFromSelector(t){const e=B(t);return e?z.find(e):[]}},R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;N.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const s=z.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},q=".bs.alert",V=`close${q}`,K=`closed${q}`;class Q extends W{static get NAME(){return"alert"}close(){if(N.trigger(this._element,V).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),N.trigger(this._element,K),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Q.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(Q,"close"),m(Q);const X='[data-bs-toggle="button"]';class Y extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=Y.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}N.on(document,"click.bs.button.data-api",X,(t=>{t.preventDefault();const e=t.target.closest(X);Y.getOrCreateInstance(e).toggle()})),m(Y);const U=".bs.swipe",G=`touchstart${U}`,J=`touchmove${U}`,Z=`touchend${U}`,tt=`pointerdown${U}`,et=`pointerup${U}`,it={endCallback:null,leftCallback:null,rightCallback:null},nt={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class st extends H{constructor(t,e){super(),this._element=t,t&&st.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return it}static get DefaultType(){return nt}static get NAME(){return"swipe"}dispose(){N.off(this._element,U)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),g(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&g(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(N.on(this._element,tt,(t=>this._start(t))),N.on(this._element,et,(t=>this._end(t))),this._element.classList.add("pointer-event")):(N.on(this._element,G,(t=>this._start(t))),N.on(this._element,J,(t=>this._move(t))),N.on(this._element,Z,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const ot=".bs.carousel",rt=".data-api",at="next",lt="prev",ct="left",ht="right",dt=`slide${ot}`,ut=`slid${ot}`,ft=`keydown${ot}`,pt=`mouseenter${ot}`,mt=`mouseleave${ot}`,gt=`dragstart${ot}`,_t=`load${ot}${rt}`,bt=`click${ot}${rt}`,vt="carousel",yt="active",wt=".active",At=".carousel-item",Et=wt+At,Tt={ArrowLeft:ht,ArrowRight:ct},Ct={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Ot={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class xt extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===vt&&this.cycle()}static get Default(){return Ct}static get DefaultType(){return Ot}static get NAME(){return"carousel"}next(){this._slide(at)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(lt)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?N.one(this._element,ut,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void N.one(this._element,ut,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?at:lt;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&N.on(this._element,ft,(t=>this._keydown(t))),"hover"===this._config.pause&&(N.on(this._element,pt,(()=>this.pause())),N.on(this._element,mt,(()=>this._maybeEnableCycle()))),this._config.touch&&st.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of z.find(".carousel-item img",this._element))N.on(t,gt,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ct)),rightCallback:()=>this._slide(this._directionToOrder(ht)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new st(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Tt[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=z.findOne(wt,this._indicatorsElement);e.classList.remove(yt),e.removeAttribute("aria-current");const i=z.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(yt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===at,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>N.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(dt).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(yt),i.classList.remove(yt,c,l),this._isSliding=!1,r(ut)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return z.findOne(Et,this._element)}_getItems(){return z.find(At,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===ct?lt:at:t===ct?at:lt}_orderToDirection(t){return p()?t===lt?ct:ht:t===lt?ht:ct}static jQueryInterface(t){return this.each((function(){const e=xt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}N.on(document,bt,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=z.getElementFromSelector(this);if(!e||!e.classList.contains(vt))return;t.preventDefault();const i=xt.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===F.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),N.on(window,_t,(()=>{const t=z.find('[data-bs-ride="carousel"]');for(const e of t)xt.getOrCreateInstance(e)})),m(xt);const kt=".bs.collapse",Lt=`show${kt}`,St=`shown${kt}`,Dt=`hide${kt}`,$t=`hidden${kt}`,It=`click${kt}.data-api`,Nt="show",Pt="collapse",Mt="collapsing",jt=`:scope .${Pt} .${Pt}`,Ft='[data-bs-toggle="collapse"]',Ht={parent:null,toggle:!0},Wt={parent:"(null|element)",toggle:"boolean"};class Bt extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=z.find(Ft);for(const t of i){const e=z.getSelectorFromElement(t),i=z.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Ht}static get DefaultType(){return Wt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Bt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(N.trigger(this._element,Lt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Pt),this._element.classList.add(Mt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt,Nt),this._element.style[e]="",N.trigger(this._element,St)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(N.trigger(this._element,Dt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(Mt),this._element.classList.remove(Pt,Nt);for(const t of this._triggerArray){const e=z.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt),N.trigger(this._element,$t)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(Nt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Ft);for(const e of t){const t=z.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=z.find(jt,this._config.parent);return z.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Bt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}N.on(document,It,Ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of z.getMultipleElementsFromSelector(this))Bt.getOrCreateInstance(t,{toggle:!1}).toggle()})),m(Bt);var zt="top",Rt="bottom",qt="right",Vt="left",Kt="auto",Qt=[zt,Rt,qt,Vt],Xt="start",Yt="end",Ut="clippingParents",Gt="viewport",Jt="popper",Zt="reference",te=Qt.reduce((function(t,e){return t.concat([e+"-"+Xt,e+"-"+Yt])}),[]),ee=[].concat(Qt,[Kt]).reduce((function(t,e){return t.concat([e,e+"-"+Xt,e+"-"+Yt])}),[]),ie="beforeRead",ne="read",se="afterRead",oe="beforeMain",re="main",ae="afterMain",le="beforeWrite",ce="write",he="afterWrite",de=[ie,ne,se,oe,re,ae,le,ce,he];function ue(t){return t?(t.nodeName||"").toLowerCase():null}function fe(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function pe(t){return t instanceof fe(t).Element||t instanceof Element}function me(t){return t instanceof fe(t).HTMLElement||t instanceof HTMLElement}function ge(t){return"undefined"!=typeof ShadowRoot&&(t instanceof fe(t).ShadowRoot||t instanceof ShadowRoot)}const _e={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];me(s)&&ue(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});me(n)&&ue(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function be(t){return t.split("-")[0]}var ve=Math.max,ye=Math.min,we=Math.round;function Ae(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ee(){return!/^((?!chrome|android).)*safari/i.test(Ae())}function Te(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&me(t)&&(s=t.offsetWidth>0&&we(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&we(n.height)/t.offsetHeight||1);var r=(pe(t)?fe(t):window).visualViewport,a=!Ee()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function Ce(t){var e=Te(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Oe(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&ge(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function xe(t){return fe(t).getComputedStyle(t)}function ke(t){return["table","td","th"].indexOf(ue(t))>=0}function Le(t){return((pe(t)?t.ownerDocument:t.document)||window.document).documentElement}function Se(t){return"html"===ue(t)?t:t.assignedSlot||t.parentNode||(ge(t)?t.host:null)||Le(t)}function De(t){return me(t)&&"fixed"!==xe(t).position?t.offsetParent:null}function $e(t){for(var e=fe(t),i=De(t);i&&ke(i)&&"static"===xe(i).position;)i=De(i);return i&&("html"===ue(i)||"body"===ue(i)&&"static"===xe(i).position)?e:i||function(t){var e=/firefox/i.test(Ae());if(/Trident/i.test(Ae())&&me(t)&&"fixed"===xe(t).position)return null;var i=Se(t);for(ge(i)&&(i=i.host);me(i)&&["html","body"].indexOf(ue(i))<0;){var n=xe(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Ie(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function Ne(t,e,i){return ve(t,ye(e,i))}function Pe(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function Me(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const je={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=be(i.placement),l=Ie(a),c=[Vt,qt].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return Pe("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:Me(t,Qt))}(s.padding,i),d=Ce(o),u="y"===l?zt:Vt,f="y"===l?Rt:qt,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=$e(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=Ne(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Oe(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Fe(t){return t.split("-")[1]}var He={top:"auto",right:"auto",bottom:"auto",left:"auto"};function We(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,m=void 0===p?0:p,g="function"==typeof h?h({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=Vt,y=zt,w=window;if(c){var A=$e(i),E="clientHeight",T="clientWidth";A===fe(i)&&"static"!==xe(A=Le(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===zt||(s===Vt||s===qt)&&o===Yt)&&(y=Rt,m-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,m*=l?1:-1),s!==Vt&&(s!==zt&&s!==Rt||o!==Yt)||(v=qt,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&He),x=!0===h?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:we(i*s)/s||0,y:we(n*s)/s||0}}({x:f,y:m},fe(i)):{x:f,y:m};return f=x.x,m=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?m+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const Be={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:be(e.placement),variation:Fe(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,We(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,We(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var ze={passive:!0};const Re={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=fe(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,ze)})),a&&l.addEventListener("resize",i.update,ze),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,ze)})),a&&l.removeEventListener("resize",i.update,ze)}},data:{}};var qe={left:"right",right:"left",bottom:"top",top:"bottom"};function Ve(t){return t.replace(/left|right|bottom|top/g,(function(t){return qe[t]}))}var Ke={start:"end",end:"start"};function Qe(t){return t.replace(/start|end/g,(function(t){return Ke[t]}))}function Xe(t){var e=fe(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ye(t){return Te(Le(t)).left+Xe(t).scrollLeft}function Ue(t){var e=xe(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ge(t){return["html","body","#document"].indexOf(ue(t))>=0?t.ownerDocument.body:me(t)&&Ue(t)?t:Ge(Se(t))}function Je(t,e){var i;void 0===e&&(e=[]);var n=Ge(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=fe(n),r=s?[o].concat(o.visualViewport||[],Ue(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Je(Se(r)))}function Ze(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function ti(t,e,i){return e===Gt?Ze(function(t,e){var i=fe(t),n=Le(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ee();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ye(t),y:l}}(t,i)):pe(e)?function(t,e){var i=Te(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):Ze(function(t){var e,i=Le(t),n=Xe(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ve(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ve(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ye(t),l=-n.scrollTop;return"rtl"===xe(s||i).direction&&(a+=ve(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Le(t)))}function ei(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?be(s):null,r=s?Fe(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case zt:e={x:a,y:i.y-n.height};break;case Rt:e={x:a,y:i.y+i.height};break;case qt:e={x:i.x+i.width,y:l};break;case Vt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Ie(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case Xt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Yt:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ii(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Ut:a,c=i.rootBoundary,h=void 0===c?Gt:c,d=i.elementContext,u=void 0===d?Jt:d,f=i.altBoundary,p=void 0!==f&&f,m=i.padding,g=void 0===m?0:m,_=Pe("number"!=typeof g?g:Me(g,Qt)),b=u===Jt?Zt:Jt,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Je(Se(t)),i=["absolute","fixed"].indexOf(xe(t).position)>=0&&me(t)?$e(t):t;return pe(i)?e.filter((function(t){return pe(t)&&Oe(t,i)&&"body"!==ue(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=ti(t,i,n);return e.top=ve(s.top,e.top),e.right=ye(s.right,e.right),e.bottom=ye(s.bottom,e.bottom),e.left=ve(s.left,e.left),e}),ti(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(pe(y)?y:y.contextElement||Le(t.elements.popper),l,h,r),A=Te(t.elements.reference),E=ei({reference:A,element:v,strategy:"absolute",placement:s}),T=Ze(Object.assign({},v,E)),C=u===Jt?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===Jt&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[qt,Rt].indexOf(t)>=0?1:-1,i=[zt,Rt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function ni(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?ee:l,h=Fe(n),d=h?a?te:te.filter((function(t){return Fe(t)===h})):Qt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ii(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[be(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const si={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=be(g),b=l||(_!==g&&p?function(t){if(be(t)===Kt)return[];var e=Ve(t);return[Qe(t),e,Qe(e)]}(g):[Ve(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(be(i)===Kt?ni(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,S=L?"width":"height",D=ii(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),$=L?k?qt:Vt:k?Rt:zt;y[S]>w[S]&&($=Ve($));var I=Ve($),N=[];if(o&&N.push(D[x]<=0),a&&N.push(D[$]<=0,D[I]<=0),N.every((function(t){return t}))){T=O,E=!1;break}A.set(O,N)}if(E)for(var P=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==P(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function oi(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ri(t){return[zt,qt,Rt,Vt].some((function(e){return t[e]>=0}))}const ai={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ii(e,{elementContext:"reference"}),a=ii(e,{altBoundary:!0}),l=oi(r,n),c=oi(a,s,o),h=ri(l),d=ri(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},li={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=ee.reduce((function(t,i){return t[i]=function(t,e,i){var n=be(t),s=[Vt,zt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[Vt,qt].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},ci={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ei({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},hi={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ii(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=be(e.placement),b=Fe(e.placement),v=!b,y=Ie(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,S="y"===y?zt:Vt,D="y"===y?Rt:qt,$="y"===y?"height":"width",I=A[y],N=I+g[S],P=I-g[D],M=f?-T[$]/2:0,j=b===Xt?E[$]:T[$],F=b===Xt?-T[$]:-E[$],H=e.elements.arrow,W=f&&H?Ce(H):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=B[S],R=B[D],q=Ne(0,E[$],W[$]),V=v?E[$]/2-M-q-z-O.mainAxis:j-q-z-O.mainAxis,K=v?-E[$]/2+M+q+R+O.mainAxis:F+q+R+O.mainAxis,Q=e.elements.arrow&&$e(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=I+K-Y,G=Ne(f?ye(N,I+V-Y-X):N,I,f?ve(P,U):P);A[y]=G,k[y]=G-I}if(a){var J,Z="x"===y?zt:Vt,tt="x"===y?Rt:qt,et=A[w],it="y"===w?"height":"width",nt=et+g[Z],st=et-g[tt],ot=-1!==[zt,Vt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=Ne(t,e,i);return n>i?i:n}(at,et,lt):Ne(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function di(t,e,i){void 0===i&&(i=!1);var n,s,o=me(e),r=me(e)&&function(t){var e=t.getBoundingClientRect(),i=we(e.width)/t.offsetWidth||1,n=we(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=Le(e),l=Te(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==ue(e)||Ue(a))&&(c=(n=e)!==fe(n)&&me(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:Xe(n)),me(e)?((h=Te(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ye(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function ui(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var fi={placement:"bottom",modifiers:[],strategy:"absolute"};function pi(){for(var t=arguments.length,e=new Array(t),i=0;iNumber.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(F.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...g(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=z.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Ti,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=z.find(Ni);for(const i of e){const e=qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Ei,Ti].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ii)?this:z.prev(this,Ii)[0]||z.next(this,Ii)[0]||z.findOne(Ii,t.delegateTarget.parentNode),o=qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}N.on(document,Si,Ii,qi.dataApiKeydownHandler),N.on(document,Si,Pi,qi.dataApiKeydownHandler),N.on(document,Li,qi.clearMenus),N.on(document,Di,qi.clearMenus),N.on(document,Li,Ii,(function(t){t.preventDefault(),qi.getOrCreateInstance(this).toggle()})),m(qi);const Vi="backdrop",Ki="show",Qi=`mousedown.bs.${Vi}`,Xi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Yi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Ui extends H{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Xi}static get DefaultType(){return Yi}static get NAME(){return Vi}show(t){if(!this._config.isVisible)return void g(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(Ki),this._emulateAnimation((()=>{g(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Ki),this._emulateAnimation((()=>{this.dispose(),g(t)}))):g(t)}dispose(){this._isAppended&&(N.off(this._element,Qi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),N.on(t,Qi,(()=>{g(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const Gi=".bs.focustrap",Ji=`focusin${Gi}`,Zi=`keydown.tab${Gi}`,tn="backward",en={autofocus:!0,trapElement:null},nn={autofocus:"boolean",trapElement:"element"};class sn extends H{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return en}static get DefaultType(){return nn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),N.off(document,Gi),N.on(document,Ji,(t=>this._handleFocusin(t))),N.on(document,Zi,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,N.off(document,Gi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=z.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===tn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?tn:"forward")}}const on=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",rn=".sticky-top",an="padding-right",ln="margin-right";class cn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,an,(e=>e+t)),this._setElementAttributes(on,an,(e=>e+t)),this._setElementAttributes(rn,ln,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,an),this._resetElementAttributes(on,an),this._resetElementAttributes(rn,ln)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&F.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=F.getDataAttribute(t,e);null!==i?(F.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of z.find(t,this._element))e(i)}}const hn=".bs.modal",dn=`hide${hn}`,un=`hidePrevented${hn}`,fn=`hidden${hn}`,pn=`show${hn}`,mn=`shown${hn}`,gn=`resize${hn}`,_n=`click.dismiss${hn}`,bn=`mousedown.dismiss${hn}`,vn=`keydown.dismiss${hn}`,yn=`click${hn}.data-api`,wn="modal-open",An="show",En="modal-static",Tn={backdrop:!0,focus:!0,keyboard:!0},Cn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class On extends W{constructor(t,e){super(t,e),this._dialog=z.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new cn,this._addEventListeners()}static get Default(){return Tn}static get DefaultType(){return Cn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||N.trigger(this._element,pn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(wn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(N.trigger(this._element,dn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(An),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){N.off(window,hn),N.off(this._dialog,hn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Ui({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=z.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(An),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,N.trigger(this._element,mn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){N.on(this._element,vn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),N.on(window,gn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),N.on(this._element,bn,(t=>{N.one(this._element,_n,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(wn),this._resetAdjustments(),this._scrollBar.reset(),N.trigger(this._element,fn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(N.trigger(this._element,un).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(En)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(En),this._queueCallback((()=>{this._element.classList.remove(En),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=On.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}N.on(document,yn,'[data-bs-toggle="modal"]',(function(t){const e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),N.one(e,pn,(t=>{t.defaultPrevented||N.one(e,fn,(()=>{a(this)&&this.focus()}))}));const i=z.findOne(".modal.show");i&&On.getInstance(i).hide(),On.getOrCreateInstance(e).toggle(this)})),R(On),m(On);const xn=".bs.offcanvas",kn=".data-api",Ln=`load${xn}${kn}`,Sn="show",Dn="showing",$n="hiding",In=".offcanvas.show",Nn=`show${xn}`,Pn=`shown${xn}`,Mn=`hide${xn}`,jn=`hidePrevented${xn}`,Fn=`hidden${xn}`,Hn=`resize${xn}`,Wn=`click${xn}${kn}`,Bn=`keydown.dismiss${xn}`,zn={backdrop:!0,keyboard:!0,scroll:!1},Rn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class qn extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return zn}static get DefaultType(){return Rn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||N.trigger(this._element,Nn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new cn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Dn),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Sn),this._element.classList.remove(Dn),N.trigger(this._element,Pn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(N.trigger(this._element,Mn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add($n),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Sn,$n),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new cn).reset(),N.trigger(this._element,Fn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Ui({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():N.trigger(this._element,jn)}:null})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_addEventListeners(){N.on(this._element,Bn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():N.trigger(this._element,jn))}))}static jQueryInterface(t){return this.each((function(){const e=qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}N.on(document,Wn,'[data-bs-toggle="offcanvas"]',(function(t){const e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;N.one(e,Fn,(()=>{a(this)&&this.focus()}));const i=z.findOne(In);i&&i!==e&&qn.getInstance(i).hide(),qn.getOrCreateInstance(e).toggle(this)})),N.on(window,Ln,(()=>{for(const t of z.find(In))qn.getOrCreateInstance(t).show()})),N.on(window,Hn,(()=>{for(const t of z.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&qn.getOrCreateInstance(t).hide()})),R(qn),m(qn);const Vn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Kn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Qn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Xn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Kn.has(i)||Boolean(Qn.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Yn={allowList:Vn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:""},Un={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Gn={entry:"(string|element|function|null)",selector:"(string|element)"};class Jn extends H{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Yn}static get DefaultType(){return Un}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Gn)}_setContent(t,e,i){const n=z.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Xn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return g(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Zn=new Set(["sanitize","allowList","sanitizeFn"]),ts="fade",es="show",is=".modal",ns="hide.bs.modal",ss="hover",os="focus",rs={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},as={allowList:Vn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ls={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class cs extends W{constructor(t,e){if(void 0===vi)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return as}static get DefaultType(){return ls}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),N.off(this._element.closest(is),ns,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=N.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),N.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.on(t,"mouseover",h);this._queueCallback((()=>{N.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!N.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger[os]=!1,this._activeTrigger[ss]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),N.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ts,es),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ts),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Jn({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ts)}_isShown(){return this.tip&&this.tip.classList.contains(es)}_createPopper(t){const e=g(this._config.placement,[this,t,this._element]),i=rs[e.toUpperCase()];return bi(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return g(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...g(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)N.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ss?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ss?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");N.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?os:ss]=!0,e._enter()})),N.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?os:ss]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},N.on(this._element.closest(is),ns,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=F.getDataAttributes(this._element);for(const t of Object.keys(e))Zn.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=cs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(cs);const hs={...cs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},ds={...cs.DefaultType,content:"(null|string|element|function)"};class us extends cs{static get Default(){return hs}static get DefaultType(){return ds}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=us.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(us);const fs=".bs.scrollspy",ps=`activate${fs}`,ms=`click${fs}`,gs=`load${fs}.data-api`,_s="active",bs="[href]",vs=".nav-link",ys=`${vs}, .nav-item > ${vs}, .list-group-item`,ws={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},As={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Es extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ws}static get DefaultType(){return As}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(N.off(this._config.target,ms),N.on(this._config.target,ms,bs,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=z.find(bs,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=z.findOne(decodeURI(e.hash),this._element);a(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(_s),this._activateParents(t),N.trigger(this._element,ps,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))z.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(_s);else for(const e of z.parents(t,".nav, .list-group"))for(const t of z.prev(e,ys))t.classList.add(_s)}_clearActiveClass(t){t.classList.remove(_s);const e=z.find(`${bs}.${_s}`,t);for(const t of e)t.classList.remove(_s)}static jQueryInterface(t){return this.each((function(){const e=Es.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(window,gs,(()=>{for(const t of z.find('[data-bs-spy="scroll"]'))Es.getOrCreateInstance(t)})),m(Es);const Ts=".bs.tab",Cs=`hide${Ts}`,Os=`hidden${Ts}`,xs=`show${Ts}`,ks=`shown${Ts}`,Ls=`click${Ts}`,Ss=`keydown${Ts}`,Ds=`load${Ts}`,$s="ArrowLeft",Is="ArrowRight",Ns="ArrowUp",Ps="ArrowDown",Ms="Home",js="End",Fs="active",Hs="fade",Ws="show",Bs=":not(.dropdown-toggle)",zs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Rs=`.nav-link${Bs}, .list-group-item${Bs}, [role="tab"]${Bs}, ${zs}`,qs=`.${Fs}[data-bs-toggle="tab"], .${Fs}[data-bs-toggle="pill"], .${Fs}[data-bs-toggle="list"]`;class Vs extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),N.on(this._element,Ss,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?N.trigger(e,Cs,{relatedTarget:t}):null;N.trigger(t,xs,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Fs),this._activate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),N.trigger(t,ks,{relatedTarget:e})):t.classList.add(Ws)}),t,t.classList.contains(Hs)))}_deactivate(t,e){t&&(t.classList.remove(Fs),t.blur(),this._deactivate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),N.trigger(t,Os,{relatedTarget:e})):t.classList.remove(Ws)}),t,t.classList.contains(Hs)))}_keydown(t){if(![$s,Is,Ns,Ps,Ms,js].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!l(t)));let i;if([Ms,js].includes(t.key))i=e[t.key===Ms?0:e.length-1];else{const n=[Is,Ps].includes(t.key);i=b(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Vs.getOrCreateInstance(i).show())}_getChildren(){return z.find(Rs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=z.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=z.findOne(t,i);s&&s.classList.toggle(n,e)};n(".dropdown-toggle",Fs),n(".dropdown-menu",Ws),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Fs)}_getInnerElement(t){return t.matches(Rs)?t:z.findOne(Rs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Vs.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(document,Ls,zs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||Vs.getOrCreateInstance(this).show()})),N.on(window,Ds,(()=>{for(const t of z.find(qs))Vs.getOrCreateInstance(t)})),m(Vs);const Ks=".bs.toast",Qs=`mouseover${Ks}`,Xs=`mouseout${Ks}`,Ys=`focusin${Ks}`,Us=`focusout${Ks}`,Gs=`hide${Ks}`,Js=`hidden${Ks}`,Zs=`show${Ks}`,to=`shown${Ks}`,eo="hide",io="show",no="showing",so={animation:"boolean",autohide:"boolean",delay:"number"},oo={animation:!0,autohide:!0,delay:5e3};class ro extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return oo}static get DefaultType(){return so}static get NAME(){return"toast"}show(){N.trigger(this._element,Zs).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(eo),d(this._element),this._element.classList.add(io,no),this._queueCallback((()=>{this._element.classList.remove(no),N.trigger(this._element,to),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(N.trigger(this._element,Gs).defaultPrevented||(this._element.classList.add(no),this._queueCallback((()=>{this._element.classList.add(eo),this._element.classList.remove(no,io),N.trigger(this._element,Js)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(io),super.dispose()}isShown(){return this._element.classList.contains(io)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){N.on(this._element,Qs,(t=>this._onInteraction(t,!0))),N.on(this._element,Xs,(t=>this._onInteraction(t,!1))),N.on(this._element,Ys,(t=>this._onInteraction(t,!0))),N.on(this._element,Us,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=ro.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(ro),m(ro),{Alert:Q,Button:Y,Carousel:xt,Collapse:Bt,Dropdown:qi,Modal:On,Offcanvas:qn,Popover:us,ScrollSpy:Es,Tab:Vs,Toast:ro,Tooltip:cs}}));
+//# sourceMappingURL=bootstrap.bundle.min.js.map
\ No newline at end of file
diff --git a/public/notebooks/music-recommendation_files/libs/clipboard/clipboard.min.js b/public/notebooks/music-recommendation_files/libs/clipboard/clipboard.min.js
new file mode 100644
index 0000000..1103f81
--- /dev/null
+++ b/public/notebooks/music-recommendation_files/libs/clipboard/clipboard.min.js
@@ -0,0 +1,7 @@
+/*!
+ * clipboard.js v2.0.11
+ * https://clipboardjs.com/
+ *
+ * Licensed MIT © Zeno Rocha
+ */
+!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1.anchorjs-link,.anchorjs-link:focus{opacity:1}",A.sheet.cssRules.length),A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",A.sheet.cssRules.length),A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',A.sheet.cssRules.length)),h=document.querySelectorAll("[id]"),t=[].map.call(h,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}});
+// @license-end
\ No newline at end of file
diff --git a/public/notebooks/music-recommendation_files/libs/quarto-html/popper.min.js b/public/notebooks/music-recommendation_files/libs/quarto-html/popper.min.js
new file mode 100644
index 0000000..e3726d7
--- /dev/null
+++ b/public/notebooks/music-recommendation_files/libs/quarto-html/popper.min.js
@@ -0,0 +1,6 @@
+/**
+ * @popperjs/core v2.11.7 - MIT License
+ */
+
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(){var e=navigator.userAgentData;return null!=e&&e.brands&&Array.isArray(e.brands)?e.brands.map((function(e){return e.brand+"/"+e.version})).join(" "):navigator.userAgent}function c(){return!/^((?!chrome|android).)*safari/i.test(f())}function p(e,o,i){void 0===o&&(o=!1),void 0===i&&(i=!1);var a=e.getBoundingClientRect(),f=1,p=1;o&&r(e)&&(f=e.offsetWidth>0&&s(a.width)/e.offsetWidth||1,p=e.offsetHeight>0&&s(a.height)/e.offsetHeight||1);var u=(n(e)?t(e):window).visualViewport,l=!c()&&i,d=(a.left+(l&&u?u.offsetLeft:0))/f,h=(a.top+(l&&u?u.offsetTop:0))/p,m=a.width/f,v=a.height/p;return{width:m,height:v,top:h,right:d+m,bottom:h+v,left:d,x:d,y:h}}function u(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function l(e){return e?(e.nodeName||"").toLowerCase():null}function d(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function h(e){return p(d(e)).left+u(e).scrollLeft}function m(e){return t(e).getComputedStyle(e)}function v(e){var t=m(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function y(e,n,o){void 0===o&&(o=!1);var i,a,f=r(n),c=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),m=d(n),y=p(e,c,o),g={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(f||!f&&!o)&&(("body"!==l(n)||v(m))&&(g=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:u(i)),r(n)?((b=p(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):m&&(b.x=h(m))),{x:y.left+g.scrollLeft-b.x,y:y.top+g.scrollTop-b.y,width:y.width,height:y.height}}function g(e){var t=p(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function b(e){return"html"===l(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||d(e)}function x(e){return["html","body","#document"].indexOf(l(e))>=0?e.ownerDocument.body:r(e)&&v(e)?e:x(b(e))}function w(e,n){var r;void 0===n&&(n=[]);var o=x(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],v(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(w(b(s)))}function O(e){return["table","td","th"].indexOf(l(e))>=0}function j(e){return r(e)&&"fixed"!==m(e).position?e.offsetParent:null}function E(e){for(var n=t(e),i=j(e);i&&O(i)&&"static"===m(i).position;)i=j(i);return i&&("html"===l(i)||"body"===l(i)&&"static"===m(i).position)?n:i||function(e){var t=/firefox/i.test(f());if(/Trident/i.test(f())&&r(e)&&"fixed"===m(e).position)return null;var n=b(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(l(n))<0;){var i=m(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var D="top",A="bottom",L="right",P="left",M="auto",k=[D,A,L,P],W="start",B="end",H="viewport",T="popper",R=k.reduce((function(e,t){return e.concat([t+"-"+W,t+"-"+B])}),[]),S=[].concat(k,[M]).reduce((function(e,t){return e.concat([t,t+"-"+W,t+"-"+B])}),[]),V=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function q(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function N(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function I(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function _(e,r,o){return r===H?I(function(e,n){var r=t(e),o=d(e),i=r.visualViewport,a=o.clientWidth,s=o.clientHeight,f=0,p=0;if(i){a=i.width,s=i.height;var u=c();(u||!u&&"fixed"===n)&&(f=i.offsetLeft,p=i.offsetTop)}return{width:a,height:s,x:f+h(e),y:p}}(e,o)):n(r)?function(e,t){var n=p(e,!1,"fixed"===t);return n.top=n.top+e.clientTop,n.left=n.left+e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}(r,o):I(function(e){var t,n=d(e),r=u(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+h(e),c=-r.scrollTop;return"rtl"===m(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:c}}(d(e)))}function F(e,t,o,s){var f="clippingParents"===t?function(e){var t=w(b(e)),o=["absolute","fixed"].indexOf(m(e).position)>=0&&r(e)?E(e):e;return n(o)?t.filter((function(e){return n(e)&&N(e,o)&&"body"!==l(e)})):[]}(e):[].concat(t),c=[].concat(f,[o]),p=c[0],u=c.reduce((function(t,n){var r=_(e,n,s);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),_(e,p,s));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function U(e){return e.split("-")[1]}function z(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function X(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?U(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case D:t={x:s,y:n.y-r.height};break;case A:t={x:s,y:n.y+n.height};break;case L:t={x:n.x+n.width,y:f};break;case P:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?z(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case W:t[c]=t[c]-(n[p]/2-r[p]/2);break;case B:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function Y(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function G(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function J(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.strategy,s=void 0===a?e.strategy:a,f=r.boundary,c=void 0===f?"clippingParents":f,u=r.rootBoundary,l=void 0===u?H:u,h=r.elementContext,m=void 0===h?T:h,v=r.altBoundary,y=void 0!==v&&v,g=r.padding,b=void 0===g?0:g,x=Y("number"!=typeof b?b:G(b,k)),w=m===T?"reference":T,O=e.rects.popper,j=e.elements[y?w:m],E=F(n(j)?j:j.contextElement||d(e.elements.popper),c,l,s),P=p(e.elements.reference),M=X({reference:P,element:O,strategy:"absolute",placement:i}),W=I(Object.assign({},O,M)),B=m===T?W:P,R={top:E.top-B.top+x.top,bottom:B.bottom-E.bottom+x.bottom,left:E.left-B.left+x.left,right:B.right-E.right+x.right},S=e.modifiersData.offset;if(m===T&&S){var V=S[i];Object.keys(R).forEach((function(e){var t=[L,A].indexOf(e)>=0?1:-1,n=[D,A].indexOf(e)>=0?"y":"x";R[e]+=V[n]*t}))}return R}var K={placement:"bottom",modifiers:[],strategy:"absolute"};function Q(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[P,L].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},se={left:"right",right:"left",bottom:"top",top:"bottom"};function fe(e){return e.replace(/left|right|bottom|top/g,(function(e){return se[e]}))}var ce={start:"end",end:"start"};function pe(e){return e.replace(/start|end/g,(function(e){return ce[e]}))}function ue(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?S:f,p=U(r),u=p?s?R:R.filter((function(e){return U(e)===p})):k,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=J(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var le={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,y=C(v),g=f||(y===v||!h?[fe(v)]:function(e){if(C(e)===M)return[];var t=fe(e);return[pe(e),t,pe(t)]}(v)),b=[v].concat(g).reduce((function(e,n){return e.concat(C(n)===M?ue(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,j=!0,E=b[0],k=0;k=0,S=R?"width":"height",V=J(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),q=R?T?L:P:T?A:D;x[S]>w[S]&&(q=fe(q));var N=fe(q),I=[];if(i&&I.push(V[H]<=0),s&&I.push(V[q]<=0,V[N]<=0),I.every((function(e){return e}))){E=B,j=!1;break}O.set(B,I)}if(j)for(var _=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return E=t,"break"},F=h?3:1;F>0;F--){if("break"===_(F))break}t.placement!==E&&(t.modifiersData[r]._skip=!0,t.placement=E,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function de(e,t,n){return i(e,a(t,n))}var he={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,v=n.tetherOffset,y=void 0===v?0:v,b=J(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=U(t.placement),O=!w,j=z(x),M="x"===j?"y":"x",k=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,V={x:0,y:0};if(k){if(s){var q,N="y"===j?D:P,I="y"===j?A:L,_="y"===j?"height":"width",F=k[j],X=F+b[N],Y=F-b[I],G=m?-H[_]/2:0,K=w===W?B[_]:H[_],Q=w===W?-H[_]:-B[_],Z=t.elements.arrow,$=m&&Z?g(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=de(0,B[_],$[_]),oe=O?B[_]/2-G-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=O?-B[_]/2+G+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&E(t.elements.arrow),se=ae?"y"===j?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(q=null==S?void 0:S[j])?q:0,ce=F+ie-fe,pe=de(m?a(X,F+oe-fe-se):X,F,m?i(Y,ce):Y);k[j]=pe,V[j]=pe-F}if(c){var ue,le="x"===j?D:P,he="x"===j?A:L,me=k[M],ve="y"===M?"height":"width",ye=me+b[le],ge=me-b[he],be=-1!==[D,P].indexOf(x),xe=null!=(ue=null==S?void 0:S[M])?ue:0,we=be?ye:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ge,je=m&&be?function(e,t,n){var r=de(e,t,n);return r>n?n:r}(we,me,Oe):de(m?we:ye,me,m?Oe:ge);k[M]=je,V[M]=je-me}t.modifiersData[r]=V}},requiresIfExists:["offset"]};var me={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=z(s),c=[P,L].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return Y("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:G(e,k))}(o.padding,n),u=g(i),l="y"===f?D:P,d="y"===f?A:L,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],v=E(i),y=v?"y"===f?v.clientHeight||0:v.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],O=y/2-u[c]/2+b,j=de(x,O,w),M=f;n.modifiersData[r]=((t={})[M]=j,t.centerOffset=j-O,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&N(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ve(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function ye(e){return[D,L,A,P].some((function(t){return e[t]>=0}))}var ge={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=J(t,{elementContext:"reference"}),s=J(t,{altBoundary:!0}),f=ve(a,r),c=ve(s,o,i),p=ye(f),u=ye(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},be=Z({defaultModifiers:[ee,te,oe,ie]}),xe=[ee,te,oe,ie,ae,le,he,me,ge],we=Z({defaultModifiers:xe});e.applyStyles=ie,e.arrow=me,e.computeStyles=oe,e.createPopper=we,e.createPopperLite=be,e.defaultModifiers=xe,e.detectOverflow=J,e.eventListeners=ee,e.flip=le,e.hide=ge,e.offset=ae,e.popperGenerator=Z,e.popperOffsets=te,e.preventOverflow=he,Object.defineProperty(e,"__esModule",{value:!0})}));
+
diff --git a/public/notebooks/music-recommendation_files/libs/quarto-html/quarto-syntax-highlighting-2f5df379a58b258e96c21c0638c20c03.css b/public/notebooks/music-recommendation_files/libs/quarto-html/quarto-syntax-highlighting-2f5df379a58b258e96c21c0638c20c03.css
new file mode 100644
index 0000000..48bb62a
--- /dev/null
+++ b/public/notebooks/music-recommendation_files/libs/quarto-html/quarto-syntax-highlighting-2f5df379a58b258e96c21c0638c20c03.css
@@ -0,0 +1,205 @@
+/* quarto syntax highlight colors */
+:root {
+ --quarto-hl-ot-color: #003B4F;
+ --quarto-hl-at-color: #657422;
+ --quarto-hl-ss-color: #20794D;
+ --quarto-hl-an-color: #5E5E5E;
+ --quarto-hl-fu-color: #4758AB;
+ --quarto-hl-st-color: #20794D;
+ --quarto-hl-cf-color: #003B4F;
+ --quarto-hl-op-color: #5E5E5E;
+ --quarto-hl-er-color: #AD0000;
+ --quarto-hl-bn-color: #AD0000;
+ --quarto-hl-al-color: #AD0000;
+ --quarto-hl-va-color: #111111;
+ --quarto-hl-bu-color: inherit;
+ --quarto-hl-ex-color: inherit;
+ --quarto-hl-pp-color: #AD0000;
+ --quarto-hl-in-color: #5E5E5E;
+ --quarto-hl-vs-color: #20794D;
+ --quarto-hl-wa-color: #5E5E5E;
+ --quarto-hl-do-color: #5E5E5E;
+ --quarto-hl-im-color: #00769E;
+ --quarto-hl-ch-color: #20794D;
+ --quarto-hl-dt-color: #AD0000;
+ --quarto-hl-fl-color: #AD0000;
+ --quarto-hl-co-color: #5E5E5E;
+ --quarto-hl-cv-color: #5E5E5E;
+ --quarto-hl-cn-color: #8f5902;
+ --quarto-hl-sc-color: #5E5E5E;
+ --quarto-hl-dv-color: #AD0000;
+ --quarto-hl-kw-color: #003B4F;
+}
+
+/* other quarto variables */
+:root {
+ --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+}
+
+pre > code.sourceCode > span {
+ color: #003B4F;
+}
+
+code span {
+ color: #003B4F;
+}
+
+code.sourceCode > span {
+ color: #003B4F;
+}
+
+div.sourceCode,
+div.sourceCode pre.sourceCode {
+ color: #003B4F;
+}
+
+code span.ot {
+ color: #003B4F;
+ font-style: inherit;
+}
+
+code span.at {
+ color: #657422;
+ font-style: inherit;
+}
+
+code span.ss {
+ color: #20794D;
+ font-style: inherit;
+}
+
+code span.an {
+ color: #5E5E5E;
+ font-style: inherit;
+}
+
+code span.fu {
+ color: #4758AB;
+ font-style: inherit;
+}
+
+code span.st {
+ color: #20794D;
+ font-style: inherit;
+}
+
+code span.cf {
+ color: #003B4F;
+ font-weight: bold;
+ font-style: inherit;
+}
+
+code span.op {
+ color: #5E5E5E;
+ font-style: inherit;
+}
+
+code span.er {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.bn {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.al {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.va {
+ color: #111111;
+ font-style: inherit;
+}
+
+code span.bu {
+ font-style: inherit;
+}
+
+code span.ex {
+ font-style: inherit;
+}
+
+code span.pp {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.in {
+ color: #5E5E5E;
+ font-style: inherit;
+}
+
+code span.vs {
+ color: #20794D;
+ font-style: inherit;
+}
+
+code span.wa {
+ color: #5E5E5E;
+ font-style: italic;
+}
+
+code span.do {
+ color: #5E5E5E;
+ font-style: italic;
+}
+
+code span.im {
+ color: #00769E;
+ font-style: inherit;
+}
+
+code span.ch {
+ color: #20794D;
+ font-style: inherit;
+}
+
+code span.dt {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.fl {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.co {
+ color: #5E5E5E;
+ font-style: inherit;
+}
+
+code span.cv {
+ color: #5E5E5E;
+ font-style: italic;
+}
+
+code span.cn {
+ color: #8f5902;
+ font-style: inherit;
+}
+
+code span.sc {
+ color: #5E5E5E;
+ font-style: inherit;
+}
+
+code span.dv {
+ color: #AD0000;
+ font-style: inherit;
+}
+
+code span.kw {
+ color: #003B4F;
+ font-weight: bold;
+ font-style: inherit;
+}
+
+.prevent-inlining {
+ content: "";
+}
+
+/*# sourceMappingURL=35eb38a806ee71ea6d2563be2308c832.css.map */
diff --git a/public/notebooks/music-recommendation_files/libs/quarto-html/quarto.js b/public/notebooks/music-recommendation_files/libs/quarto-html/quarto.js
new file mode 100644
index 0000000..3c24c29
--- /dev/null
+++ b/public/notebooks/music-recommendation_files/libs/quarto-html/quarto.js
@@ -0,0 +1,911 @@
+const sectionChanged = new CustomEvent("quarto-sectionChanged", {
+ detail: {},
+ bubbles: true,
+ cancelable: false,
+ composed: false,
+});
+
+const layoutMarginEls = () => {
+ // Find any conflicting margin elements and add margins to the
+ // top to prevent overlap
+ const marginChildren = window.document.querySelectorAll(
+ ".column-margin.column-container > *, .margin-caption, .aside"
+ );
+
+ let lastBottom = 0;
+ for (const marginChild of marginChildren) {
+ if (marginChild.offsetParent !== null) {
+ // clear the top margin so we recompute it
+ marginChild.style.marginTop = null;
+ const top = marginChild.getBoundingClientRect().top + window.scrollY;
+ if (top < lastBottom) {
+ const marginChildStyle = window.getComputedStyle(marginChild);
+ const marginBottom = parseFloat(marginChildStyle["marginBottom"]);
+ const margin = lastBottom - top + marginBottom;
+ marginChild.style.marginTop = `${margin}px`;
+ }
+ const styles = window.getComputedStyle(marginChild);
+ const marginTop = parseFloat(styles["marginTop"]);
+ lastBottom = top + marginChild.getBoundingClientRect().height + marginTop;
+ }
+ }
+};
+
+window.document.addEventListener("DOMContentLoaded", function (_event) {
+ // Recompute the position of margin elements anytime the body size changes
+ if (window.ResizeObserver) {
+ const resizeObserver = new window.ResizeObserver(
+ throttle(() => {
+ layoutMarginEls();
+ if (
+ window.document.body.getBoundingClientRect().width < 990 &&
+ isReaderMode()
+ ) {
+ quartoToggleReader();
+ }
+ }, 50)
+ );
+ resizeObserver.observe(window.document.body);
+ }
+
+ const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]');
+ const sidebarEl = window.document.getElementById("quarto-sidebar");
+ const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left");
+ const marginSidebarEl = window.document.getElementById(
+ "quarto-margin-sidebar"
+ );
+ // function to determine whether the element has a previous sibling that is active
+ const prevSiblingIsActiveLink = (el) => {
+ const sibling = el.previousElementSibling;
+ if (sibling && sibling.tagName === "A") {
+ return sibling.classList.contains("active");
+ } else {
+ return false;
+ }
+ };
+
+ // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior)
+ function fireSlideEnter(e) {
+ const event = window.document.createEvent("Event");
+ event.initEvent("slideenter", true, true);
+ window.document.dispatchEvent(event);
+ }
+ const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]');
+ tabs.forEach((tab) => {
+ tab.addEventListener("shown.bs.tab", fireSlideEnter);
+ });
+
+ // fire slideEnter for tabby tab activations (for htmlwidget resize behavior)
+ document.addEventListener("tabby", fireSlideEnter, false);
+
+ // Track scrolling and mark TOC links as active
+ // get table of contents and sidebar (bail if we don't have at least one)
+ const tocLinks = tocEl
+ ? [...tocEl.querySelectorAll("a[data-scroll-target]")]
+ : [];
+ const makeActive = (link) => tocLinks[link].classList.add("active");
+ const removeActive = (link) => tocLinks[link].classList.remove("active");
+ const removeAllActive = () =>
+ [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link));
+
+ // activate the anchor for a section associated with this TOC entry
+ tocLinks.forEach((link) => {
+ link.addEventListener("click", () => {
+ if (link.href.indexOf("#") !== -1) {
+ const anchor = link.href.split("#")[1];
+ const heading = window.document.querySelector(
+ `[data-anchor-id="${anchor}"]`
+ );
+ if (heading) {
+ // Add the class
+ heading.classList.add("reveal-anchorjs-link");
+
+ // function to show the anchor
+ const handleMouseout = () => {
+ heading.classList.remove("reveal-anchorjs-link");
+ heading.removeEventListener("mouseout", handleMouseout);
+ };
+
+ // add a function to clear the anchor when the user mouses out of it
+ heading.addEventListener("mouseout", handleMouseout);
+ }
+ }
+ });
+ });
+
+ const sections = tocLinks.map((link) => {
+ const target = link.getAttribute("data-scroll-target");
+ if (target.startsWith("#")) {
+ return window.document.getElementById(decodeURI(`${target.slice(1)}`));
+ } else {
+ return window.document.querySelector(decodeURI(`${target}`));
+ }
+ });
+
+ const sectionMargin = 200;
+ let currentActive = 0;
+ // track whether we've initialized state the first time
+ let init = false;
+
+ const updateActiveLink = () => {
+ // The index from bottom to top (e.g. reversed list)
+ let sectionIndex = -1;
+ if (
+ window.innerHeight + window.pageYOffset >=
+ window.document.body.offsetHeight
+ ) {
+ // This is the no-scroll case where last section should be the active one
+ sectionIndex = 0;
+ } else {
+ // This finds the last section visible on screen that should be made active
+ sectionIndex = [...sections].reverse().findIndex((section) => {
+ if (section) {
+ return window.pageYOffset >= section.offsetTop - sectionMargin;
+ } else {
+ return false;
+ }
+ });
+ }
+ if (sectionIndex > -1) {
+ const current = sections.length - sectionIndex - 1;
+ if (current !== currentActive) {
+ removeAllActive();
+ currentActive = current;
+ makeActive(current);
+ if (init) {
+ window.dispatchEvent(sectionChanged);
+ }
+ init = true;
+ }
+ }
+ };
+
+ const inHiddenRegion = (top, bottom, hiddenRegions) => {
+ for (const region of hiddenRegions) {
+ if (top <= region.bottom && bottom >= region.top) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ const categorySelector = "header.quarto-title-block .quarto-category";
+ const activateCategories = (href) => {
+ // Find any categories
+ // Surround them with a link pointing back to:
+ // #category=Authoring
+ try {
+ const categoryEls = window.document.querySelectorAll(categorySelector);
+ for (const categoryEl of categoryEls) {
+ const categoryText = categoryEl.textContent;
+ if (categoryText) {
+ const link = `${href}#category=${encodeURIComponent(categoryText)}`;
+ const linkEl = window.document.createElement("a");
+ linkEl.setAttribute("href", link);
+ for (const child of categoryEl.childNodes) {
+ linkEl.append(child);
+ }
+ categoryEl.appendChild(linkEl);
+ }
+ }
+ } catch {
+ // Ignore errors
+ }
+ };
+ function hasTitleCategories() {
+ return window.document.querySelector(categorySelector) !== null;
+ }
+
+ function offsetRelativeUrl(url) {
+ const offset = getMeta("quarto:offset");
+ return offset ? offset + url : url;
+ }
+
+ function offsetAbsoluteUrl(url) {
+ const offset = getMeta("quarto:offset");
+ const baseUrl = new URL(offset, window.location);
+
+ const projRelativeUrl = url.replace(baseUrl, "");
+ if (projRelativeUrl.startsWith("/")) {
+ return projRelativeUrl;
+ } else {
+ return "/" + projRelativeUrl;
+ }
+ }
+
+ // read a meta tag value
+ function getMeta(metaName) {
+ const metas = window.document.getElementsByTagName("meta");
+ for (let i = 0; i < metas.length; i++) {
+ if (metas[i].getAttribute("name") === metaName) {
+ return metas[i].getAttribute("content");
+ }
+ }
+ return "";
+ }
+
+ async function findAndActivateCategories() {
+ // Categories search with listing only use path without query
+ const currentPagePath = offsetAbsoluteUrl(
+ window.location.origin + window.location.pathname
+ );
+ const response = await fetch(offsetRelativeUrl("listings.json"));
+ if (response.status == 200) {
+ return response.json().then(function (listingPaths) {
+ const listingHrefs = [];
+ for (const listingPath of listingPaths) {
+ const pathWithoutLeadingSlash = listingPath.listing.substring(1);
+ for (const item of listingPath.items) {
+ if (
+ item === currentPagePath ||
+ item === currentPagePath + "index.html"
+ ) {
+ // Resolve this path against the offset to be sure
+ // we already are using the correct path to the listing
+ // (this adjusts the listing urls to be rooted against
+ // whatever root the page is actually running against)
+ const relative = offsetRelativeUrl(pathWithoutLeadingSlash);
+ const baseUrl = window.location;
+ const resolvedPath = new URL(relative, baseUrl);
+ listingHrefs.push(resolvedPath.pathname);
+ break;
+ }
+ }
+ }
+
+ // Look up the tree for a nearby linting and use that if we find one
+ const nearestListing = findNearestParentListing(
+ offsetAbsoluteUrl(window.location.pathname),
+ listingHrefs
+ );
+ if (nearestListing) {
+ activateCategories(nearestListing);
+ } else {
+ // See if the referrer is a listing page for this item
+ const referredRelativePath = offsetAbsoluteUrl(document.referrer);
+ const referrerListing = listingHrefs.find((listingHref) => {
+ const isListingReferrer =
+ listingHref === referredRelativePath ||
+ listingHref === referredRelativePath + "index.html";
+ return isListingReferrer;
+ });
+
+ if (referrerListing) {
+ // Try to use the referrer if possible
+ activateCategories(referrerListing);
+ } else if (listingHrefs.length > 0) {
+ // Otherwise, just fall back to the first listing
+ activateCategories(listingHrefs[0]);
+ }
+ }
+ });
+ }
+ }
+ if (hasTitleCategories()) {
+ findAndActivateCategories();
+ }
+
+ const findNearestParentListing = (href, listingHrefs) => {
+ if (!href || !listingHrefs) {
+ return undefined;
+ }
+ // Look up the tree for a nearby linting and use that if we find one
+ const relativeParts = href.substring(1).split("/");
+ while (relativeParts.length > 0) {
+ const path = relativeParts.join("/");
+ for (const listingHref of listingHrefs) {
+ if (listingHref.startsWith(path)) {
+ return listingHref;
+ }
+ }
+ relativeParts.pop();
+ }
+
+ return undefined;
+ };
+
+ const manageSidebarVisiblity = (el, placeholderDescriptor) => {
+ let isVisible = true;
+ let elRect;
+
+ return (hiddenRegions) => {
+ if (el === null) {
+ return;
+ }
+
+ // Find the last element of the TOC
+ const lastChildEl = el.lastElementChild;
+
+ if (lastChildEl) {
+ // Converts the sidebar to a menu
+ const convertToMenu = () => {
+ for (const child of el.children) {
+ child.style.opacity = 0;
+ child.style.overflow = "hidden";
+ child.style.pointerEvents = "none";
+ }
+
+ nexttick(() => {
+ const toggleContainer = window.document.createElement("div");
+ toggleContainer.style.width = "100%";
+ toggleContainer.classList.add("zindex-over-content");
+ toggleContainer.classList.add("quarto-sidebar-toggle");
+ toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom
+ toggleContainer.id = placeholderDescriptor.id;
+ toggleContainer.style.position = "fixed";
+
+ const toggleIcon = window.document.createElement("i");
+ toggleIcon.classList.add("quarto-sidebar-toggle-icon");
+ toggleIcon.classList.add("bi");
+ toggleIcon.classList.add("bi-caret-down-fill");
+
+ const toggleTitle = window.document.createElement("div");
+ const titleEl = window.document.body.querySelector(
+ placeholderDescriptor.titleSelector
+ );
+ if (titleEl) {
+ toggleTitle.append(
+ titleEl.textContent || titleEl.innerText,
+ toggleIcon
+ );
+ }
+ toggleTitle.classList.add("zindex-over-content");
+ toggleTitle.classList.add("quarto-sidebar-toggle-title");
+ toggleContainer.append(toggleTitle);
+
+ const toggleContents = window.document.createElement("div");
+ toggleContents.classList = el.classList;
+ toggleContents.classList.add("zindex-over-content");
+ toggleContents.classList.add("quarto-sidebar-toggle-contents");
+ for (const child of el.children) {
+ if (child.id === "toc-title") {
+ continue;
+ }
+
+ const clone = child.cloneNode(true);
+ clone.style.opacity = 1;
+ clone.style.pointerEvents = null;
+ clone.style.display = null;
+ toggleContents.append(clone);
+ }
+ toggleContents.style.height = "0px";
+ const positionToggle = () => {
+ // position the element (top left of parent, same width as parent)
+ if (!elRect) {
+ elRect = el.getBoundingClientRect();
+ }
+ toggleContainer.style.left = `${elRect.left}px`;
+ toggleContainer.style.top = `${elRect.top}px`;
+ toggleContainer.style.width = `${elRect.width}px`;
+ };
+ positionToggle();
+
+ toggleContainer.append(toggleContents);
+ el.parentElement.prepend(toggleContainer);
+
+ // Process clicks
+ let tocShowing = false;
+ // Allow the caller to control whether this is dismissed
+ // when it is clicked (e.g. sidebar navigation supports
+ // opening and closing the nav tree, so don't dismiss on click)
+ const clickEl = placeholderDescriptor.dismissOnClick
+ ? toggleContainer
+ : toggleTitle;
+
+ const closeToggle = () => {
+ if (tocShowing) {
+ toggleContainer.classList.remove("expanded");
+ toggleContents.style.height = "0px";
+ tocShowing = false;
+ }
+ };
+
+ // Get rid of any expanded toggle if the user scrolls
+ window.document.addEventListener(
+ "scroll",
+ throttle(() => {
+ closeToggle();
+ }, 50)
+ );
+
+ // Handle positioning of the toggle
+ window.addEventListener(
+ "resize",
+ throttle(() => {
+ elRect = undefined;
+ positionToggle();
+ }, 50)
+ );
+
+ window.addEventListener("quarto-hrChanged", () => {
+ elRect = undefined;
+ });
+
+ // Process the click
+ clickEl.onclick = () => {
+ if (!tocShowing) {
+ toggleContainer.classList.add("expanded");
+ toggleContents.style.height = null;
+ tocShowing = true;
+ } else {
+ closeToggle();
+ }
+ };
+ });
+ };
+
+ // Converts a sidebar from a menu back to a sidebar
+ const convertToSidebar = () => {
+ for (const child of el.children) {
+ child.style.opacity = 1;
+ child.style.overflow = null;
+ child.style.pointerEvents = null;
+ }
+
+ const placeholderEl = window.document.getElementById(
+ placeholderDescriptor.id
+ );
+ if (placeholderEl) {
+ placeholderEl.remove();
+ }
+
+ el.classList.remove("rollup");
+ };
+
+ if (isReaderMode()) {
+ convertToMenu();
+ isVisible = false;
+ } else {
+ // Find the top and bottom o the element that is being managed
+ const elTop = el.offsetTop;
+ const elBottom =
+ elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight;
+
+ if (!isVisible) {
+ // If the element is current not visible reveal if there are
+ // no conflicts with overlay regions
+ if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) {
+ convertToSidebar();
+ isVisible = true;
+ }
+ } else {
+ // If the element is visible, hide it if it conflicts with overlay regions
+ // and insert a placeholder toggle (or if we're in reader mode)
+ if (inHiddenRegion(elTop, elBottom, hiddenRegions)) {
+ convertToMenu();
+ isVisible = false;
+ }
+ }
+ }
+ }
+ };
+ };
+
+ const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]');
+ for (const tabEl of tabEls) {
+ const id = tabEl.getAttribute("data-bs-target");
+ if (id) {
+ const columnEl = document.querySelector(
+ `${id} .column-margin, .tabset-margin-content`
+ );
+ if (columnEl)
+ tabEl.addEventListener("shown.bs.tab", function (event) {
+ const el = event.srcElement;
+ if (el) {
+ const visibleCls = `${el.id}-margin-content`;
+ // walk up until we find a parent tabset
+ let panelTabsetEl = el.parentElement;
+ while (panelTabsetEl) {
+ if (panelTabsetEl.classList.contains("panel-tabset")) {
+ break;
+ }
+ panelTabsetEl = panelTabsetEl.parentElement;
+ }
+
+ if (panelTabsetEl) {
+ const prevSib = panelTabsetEl.previousElementSibling;
+ if (
+ prevSib &&
+ prevSib.classList.contains("tabset-margin-container")
+ ) {
+ const childNodes = prevSib.querySelectorAll(
+ ".tabset-margin-content"
+ );
+ for (const childEl of childNodes) {
+ if (childEl.classList.contains(visibleCls)) {
+ childEl.classList.remove("collapse");
+ } else {
+ childEl.classList.add("collapse");
+ }
+ }
+ }
+ }
+ }
+
+ layoutMarginEls();
+ });
+ }
+ }
+
+ // Manage the visibility of the toc and the sidebar
+ const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, {
+ id: "quarto-toc-toggle",
+ titleSelector: "#toc-title",
+ dismissOnClick: true,
+ });
+ const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, {
+ id: "quarto-sidebarnav-toggle",
+ titleSelector: ".title",
+ dismissOnClick: false,
+ });
+ let tocLeftScrollVisibility;
+ if (leftTocEl) {
+ tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, {
+ id: "quarto-lefttoc-toggle",
+ titleSelector: "#toc-title",
+ dismissOnClick: true,
+ });
+ }
+
+ // Find the first element that uses formatting in special columns
+ const conflictingEls = window.document.body.querySelectorAll(
+ '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]'
+ );
+
+ // Filter all the possibly conflicting elements into ones
+ // the do conflict on the left or ride side
+ const arrConflictingEls = Array.from(conflictingEls);
+ const leftSideConflictEls = arrConflictingEls.filter((el) => {
+ if (el.tagName === "ASIDE") {
+ return false;
+ }
+ return Array.from(el.classList).find((className) => {
+ return (
+ className !== "column-body" &&
+ className.startsWith("column-") &&
+ !className.endsWith("right") &&
+ !className.endsWith("container") &&
+ className !== "column-margin"
+ );
+ });
+ });
+ const rightSideConflictEls = arrConflictingEls.filter((el) => {
+ if (el.tagName === "ASIDE") {
+ return true;
+ }
+
+ const hasMarginCaption = Array.from(el.classList).find((className) => {
+ return className == "margin-caption";
+ });
+ if (hasMarginCaption) {
+ return true;
+ }
+
+ return Array.from(el.classList).find((className) => {
+ return (
+ className !== "column-body" &&
+ !className.endsWith("container") &&
+ className.startsWith("column-") &&
+ !className.endsWith("left")
+ );
+ });
+ });
+
+ const kOverlapPaddingSize = 10;
+ function toRegions(els) {
+ return els.map((el) => {
+ const boundRect = el.getBoundingClientRect();
+ const top =
+ boundRect.top +
+ document.documentElement.scrollTop -
+ kOverlapPaddingSize;
+ return {
+ top,
+ bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize,
+ };
+ });
+ }
+
+ let hasObserved = false;
+ const visibleItemObserver = (els) => {
+ let visibleElements = [...els];
+ const intersectionObserver = new IntersectionObserver(
+ (entries, _observer) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ if (visibleElements.indexOf(entry.target) === -1) {
+ visibleElements.push(entry.target);
+ }
+ } else {
+ visibleElements = visibleElements.filter((visibleEntry) => {
+ return visibleEntry !== entry;
+ });
+ }
+ });
+
+ if (!hasObserved) {
+ hideOverlappedSidebars();
+ }
+ hasObserved = true;
+ },
+ {}
+ );
+ els.forEach((el) => {
+ intersectionObserver.observe(el);
+ });
+
+ return {
+ getVisibleEntries: () => {
+ return visibleElements;
+ },
+ };
+ };
+
+ const rightElementObserver = visibleItemObserver(rightSideConflictEls);
+ const leftElementObserver = visibleItemObserver(leftSideConflictEls);
+
+ const hideOverlappedSidebars = () => {
+ marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries()));
+ sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries()));
+ if (tocLeftScrollVisibility) {
+ tocLeftScrollVisibility(
+ toRegions(leftElementObserver.getVisibleEntries())
+ );
+ }
+ };
+
+ window.quartoToggleReader = () => {
+ // Applies a slow class (or removes it)
+ // to update the transition speed
+ const slowTransition = (slow) => {
+ const manageTransition = (id, slow) => {
+ const el = document.getElementById(id);
+ if (el) {
+ if (slow) {
+ el.classList.add("slow");
+ } else {
+ el.classList.remove("slow");
+ }
+ }
+ };
+
+ manageTransition("TOC", slow);
+ manageTransition("quarto-sidebar", slow);
+ };
+ const readerMode = !isReaderMode();
+ setReaderModeValue(readerMode);
+
+ // If we're entering reader mode, slow the transition
+ if (readerMode) {
+ slowTransition(readerMode);
+ }
+ highlightReaderToggle(readerMode);
+ hideOverlappedSidebars();
+
+ // If we're exiting reader mode, restore the non-slow transition
+ if (!readerMode) {
+ slowTransition(!readerMode);
+ }
+ };
+
+ const highlightReaderToggle = (readerMode) => {
+ const els = document.querySelectorAll(".quarto-reader-toggle");
+ if (els) {
+ els.forEach((el) => {
+ if (readerMode) {
+ el.classList.add("reader");
+ } else {
+ el.classList.remove("reader");
+ }
+ });
+ }
+ };
+
+ const setReaderModeValue = (val) => {
+ if (window.location.protocol !== "file:") {
+ window.localStorage.setItem("quarto-reader-mode", val);
+ } else {
+ localReaderMode = val;
+ }
+ };
+
+ const isReaderMode = () => {
+ if (window.location.protocol !== "file:") {
+ return window.localStorage.getItem("quarto-reader-mode") === "true";
+ } else {
+ return localReaderMode;
+ }
+ };
+ let localReaderMode = null;
+
+ const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded");
+ const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1;
+
+ // Walk the TOC and collapse/expand nodes
+ // Nodes are expanded if:
+ // - they are top level
+ // - they have children that are 'active' links
+ // - they are directly below an link that is 'active'
+ const walk = (el, depth) => {
+ // Tick depth when we enter a UL
+ if (el.tagName === "UL") {
+ depth = depth + 1;
+ }
+
+ // It this is active link
+ let isActiveNode = false;
+ if (el.tagName === "A" && el.classList.contains("active")) {
+ isActiveNode = true;
+ }
+
+ // See if there is an active child to this element
+ let hasActiveChild = false;
+ for (child of el.children) {
+ hasActiveChild = walk(child, depth) || hasActiveChild;
+ }
+
+ // Process the collapse state if this is an UL
+ if (el.tagName === "UL") {
+ if (tocOpenDepth === -1 && depth > 1) {
+ // toc-expand: false
+ el.classList.add("collapse");
+ } else if (
+ depth <= tocOpenDepth ||
+ hasActiveChild ||
+ prevSiblingIsActiveLink(el)
+ ) {
+ el.classList.remove("collapse");
+ } else {
+ el.classList.add("collapse");
+ }
+
+ // untick depth when we leave a UL
+ depth = depth - 1;
+ }
+ return hasActiveChild || isActiveNode;
+ };
+
+ // walk the TOC and expand / collapse any items that should be shown
+ if (tocEl) {
+ updateActiveLink();
+ walk(tocEl, 0);
+ }
+
+ // Throttle the scroll event and walk peridiocally
+ window.document.addEventListener(
+ "scroll",
+ throttle(() => {
+ if (tocEl) {
+ updateActiveLink();
+ walk(tocEl, 0);
+ }
+ if (!isReaderMode()) {
+ hideOverlappedSidebars();
+ }
+ }, 5)
+ );
+ window.addEventListener(
+ "resize",
+ throttle(() => {
+ if (tocEl) {
+ updateActiveLink();
+ walk(tocEl, 0);
+ }
+ if (!isReaderMode()) {
+ hideOverlappedSidebars();
+ }
+ }, 10)
+ );
+ hideOverlappedSidebars();
+ highlightReaderToggle(isReaderMode());
+});
+
+// grouped tabsets
+window.addEventListener("pageshow", (_event) => {
+ function getTabSettings() {
+ const data = localStorage.getItem("quarto-persistent-tabsets-data");
+ if (!data) {
+ localStorage.setItem("quarto-persistent-tabsets-data", "{}");
+ return {};
+ }
+ if (data) {
+ return JSON.parse(data);
+ }
+ }
+
+ function setTabSettings(data) {
+ localStorage.setItem(
+ "quarto-persistent-tabsets-data",
+ JSON.stringify(data)
+ );
+ }
+
+ function setTabState(groupName, groupValue) {
+ const data = getTabSettings();
+ data[groupName] = groupValue;
+ setTabSettings(data);
+ }
+
+ function toggleTab(tab, active) {
+ const tabPanelId = tab.getAttribute("aria-controls");
+ const tabPanel = document.getElementById(tabPanelId);
+ if (active) {
+ tab.classList.add("active");
+ tabPanel.classList.add("active");
+ } else {
+ tab.classList.remove("active");
+ tabPanel.classList.remove("active");
+ }
+ }
+
+ function toggleAll(selectedGroup, selectorsToSync) {
+ for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) {
+ const active = selectedGroup === thisGroup;
+ for (const tab of tabs) {
+ toggleTab(tab, active);
+ }
+ }
+ }
+
+ function findSelectorsToSyncByLanguage() {
+ const result = {};
+ const tabs = Array.from(
+ document.querySelectorAll(`div[data-group] a[id^='tabset-']`)
+ );
+ for (const item of tabs) {
+ const div = item.parentElement.parentElement.parentElement;
+ const group = div.getAttribute("data-group");
+ if (!result[group]) {
+ result[group] = {};
+ }
+ const selectorsToSync = result[group];
+ const value = item.innerHTML;
+ if (!selectorsToSync[value]) {
+ selectorsToSync[value] = [];
+ }
+ selectorsToSync[value].push(item);
+ }
+ return result;
+ }
+
+ function setupSelectorSync() {
+ const selectorsToSync = findSelectorsToSyncByLanguage();
+ Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => {
+ Object.entries(tabSetsByValue).forEach(([value, items]) => {
+ items.forEach((item) => {
+ item.addEventListener("click", (_event) => {
+ setTabState(group, value);
+ toggleAll(value, selectorsToSync[group]);
+ });
+ });
+ });
+ });
+ return selectorsToSync;
+ }
+
+ const selectorsToSync = setupSelectorSync();
+ for (const [group, selectedName] of Object.entries(getTabSettings())) {
+ const selectors = selectorsToSync[group];
+ // it's possible that stale state gives us empty selections, so we explicitly check here.
+ if (selectors) {
+ toggleAll(selectedName, selectors);
+ }
+ }
+});
+
+function throttle(func, wait) {
+ let waiting = false;
+ return function () {
+ if (!waiting) {
+ func.apply(this, arguments);
+ waiting = true;
+ setTimeout(function () {
+ waiting = false;
+ }, wait);
+ }
+ };
+}
+
+function nexttick(func) {
+ return setTimeout(func, 0);
+}
diff --git a/public/notebooks/music-recommendation_files/libs/quarto-html/tippy.css b/public/notebooks/music-recommendation_files/libs/quarto-html/tippy.css
new file mode 100644
index 0000000..e6ae635
--- /dev/null
+++ b/public/notebooks/music-recommendation_files/libs/quarto-html/tippy.css
@@ -0,0 +1 @@
+.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}
\ No newline at end of file
diff --git a/public/notebooks/music-recommendation_files/libs/quarto-html/tippy.umd.min.js b/public/notebooks/music-recommendation_files/libs/quarto-html/tippy.umd.min.js
new file mode 100644
index 0000000..ca292be
--- /dev/null
+++ b/public/notebooks/music-recommendation_files/libs/quarto-html/tippy.umd.min.js
@@ -0,0 +1,2 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F}));
+
diff --git a/public/notebooks/styles/toc-scrollbar-hide.html b/public/notebooks/styles/toc-scrollbar-hide.html
new file mode 100644
index 0000000..92fe299
--- /dev/null
+++ b/public/notebooks/styles/toc-scrollbar-hide.html
@@ -0,0 +1,48 @@
+
+
+
\ No newline at end of file
diff --git a/public/notebooks/styles/toc-scrollbar-thin.html b/public/notebooks/styles/toc-scrollbar-thin.html
new file mode 100644
index 0000000..1fc4d6f
--- /dev/null
+++ b/public/notebooks/styles/toc-scrollbar-thin.html
@@ -0,0 +1,34 @@
+
+
+
\ No newline at end of file
diff --git a/styles/globals.css b/styles/globals.css
index c958ecb..6a82d7a 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -245,3 +245,41 @@ a {
opacity: 0.5;
}
}
+
+
+/* Force center the thumbnails track */
+.yarl__thumbnails_track {
+ justify-content: center !important;
+ }
+
+ /* Optional: smooth transition when switching */
+ .yarl__thumbnails_thumbnail {
+ transition: transform 0.3s ease;
+ }
+
+ /* Optional: outline active thumb */
+ .yarl__thumbnails_thumbnail_active {
+ outline: 2px solid #6366f1; /* or any color you prefer */
+ outline-offset: 2px;
+ }
+
+
+ html {
+ scroll-behavior: smooth;
+ transition: background-color 0.4s ease, color 0.4s ease;
+ }
+
+ .typography {
+ @apply text-gray-800 dark:text-gray-300 leading-relaxed text-lg sm:text-xl tracking-tight;
+ }
+
+
+ .typography-modern {
+ @apply text-gray-900 dark:text-gray-100 text-base sm:text-lg leading-[1.8] tracking-normal;
+ }
+
+
+ p {
+ overflow-wrap: break-word;
+ word-break: break-word;
+ }
\ No newline at end of file
diff --git a/tailwind.config.js b/tailwind.config.js
index 1476eb4..71cd6e5 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,37 +1,52 @@
const colors = require('tailwindcss/colors');
module.exports = {
- content: [
- './pages/**/*.{js,ts,jsx,tsx}',
- './components/**/*.{js,ts,jsx,tsx}',
- ],
- darkMode: 'class',
- theme: {
- extend: {
- colors: {
- // Light colors
- 'primary-light': '#F7F8FC',
- 'secondary-light': '#FFFFFF',
- 'ternary-light': '#f6f7f8',
+ content: [
+ './pages/**/*.{js,ts,jsx,tsx}',
+ './components/**/*.{js,ts,jsx,tsx}',
+ './content/**/*.{md,mdx}', // Include MDX content folder
+ ],
+ darkMode: 'class',
+ theme: {
+ extend: {
+ fontFamily: {
+ serif: ['var(--font-playfair)', 'Georgia', 'serif'],
+ sans: ['GeneralSans-Variable', 'sans-serif'],
+ },
+ colors: {
+ // Light colors
+ 'primary-light': '#F7F8FC',
+ 'secondary-light': '#FFFFFF',
+ 'ternary-light': '#f6f7f8',
- // Dark colors
- 'primary-dark': '#0D2438',
- 'secondary-dark': '#102D44',
- 'ternary-dark': '#1E3851',
+ // Dark colors
+ 'primary-dark': '#0D2438',
+ 'secondary-dark': '#102D44',
+ 'ternary-dark': '#1E3851',
+ 'gray-75': '#f8fafb', // Subtle gray between gray-50 and #f3f4f6,
- // Extended v3 color
- gray: colors.neutral,
- },
- container: {
- padding: {
- DEFAULT: '1rem',
- sm: '2rem',
- lg: '5rem',
- xl: '6rem',
- '2xl': '8rem',
- },
- },
- },
- },
- plugins: [require('@tailwindcss/forms')],
+ // Tailwind's gray neutral scale
+ gray: colors.neutral,
+ },
+ keyframes: {
+ fadeInUp: {
+ '0%': { opacity: 0, transform: 'translateY(20px)' },
+ '100%': { opacity: 1, transform: 'translateY(0)' },
+ },
+ },
+ animation: {
+ fadeInUp: 'fadeInUp 1s ease-out forwards',
+ },
+ container: {
+ padding: {
+ DEFAULT: '1rem',
+ sm: '2rem',
+ lg: '5rem',
+ xl: '6rem',
+ '2xl': '8rem',
+ },
+ },
+ },
+ },
+ plugins: [require('@tailwindcss/forms')],
};
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..bd2eb14
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "noEmit": true,
+ "incremental": true,
+ "module": "esnext",
+ "esModuleInterop": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve"
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx"
+, "components/ui/Callout.jsx", "components/TableOfContents.jsx" ],
+ "exclude": [
+ "node_modules"
+ ]
+}