diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/index.html b/index.html new file mode 100644 index 0000000..882f33a --- /dev/null +++ b/index.html @@ -0,0 +1,692 @@ + + + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ + + +
+

Overview

+

These materials provide a practical guide on analysing viral amplicon sequencing data for genomic surveillance, with a specific focus on SARS-CoV-2. While centered on SARS-CoV-2, the concepts and pipelines explored here are applicable to various viruses. The content includes the analysis of data from clinical isolates and wastewater samples. For clinical isolates, we illustrate how to create consensus sequences for upload to databases like GISAID and for downstream applications such as variant annotation and phylogeny. Wastewater sample analysis includes estimating variant and mutation frequencies. For both applications we will use a standardized bioinformatic pipeline compatible with both Illumina and Nanopore data. The materials cover assigning sequences to lineages, identifying variants of interest and creating visualizations to effectively communicate findings. Throughout, you will acquire foundational bioinformatic skills, including Unix command line usage and scripting for reproducible analyses.

+
+
+
+ +
+
+Learning Objectives +
+
+
+
    +
  • Recognise the uses of genomic surveillance to inform public health actions during a pandemic.
  • +
  • Assemble high-quality SARS-CoV-2 genome sequences starting with raw sequencing data from clinical isolates.
  • +
  • Assign consensus sequences to lineages and identify variants of interest/concern.
  • +
  • Capture high-quality metadata, recognising its impact on downstream analyses.
  • +
  • Construct phylogenetic trees to contextualise new samples in a set of background samples.
  • +
  • Estimate variant frequencies from mixed wastewater samples.
  • +
  • Produce visualisations to communicate your findings and help inform public health action.
  • +
+
+
+
+

Target Audience

+

These materials are aimed at life scientists and molecular lab technicians interested in the bioinformatic analysis of viral genomic data. In particular, it will benefit those working in SARS-CoV-2 sequencing facilities, such as public health labs.

+
+
+

Prerequisites

+

We assume no prior bioinformatics experience or experience with the tools introduced in this course. An elementary knowledge of molecular and viral biology is assumed (concepts such as: DNA, RNA, PCR, primers, SNPs).

+
+
+

Citation

+ +

Please cite these materials if:

+
    +
  • You adapted or used any of them in your own teaching.
  • +
  • These materials were useful for your research work. For example, you can cite us in the methods section of your paper: “We carried our analyses based on the recommendations in Tavares et al (2022).”.
  • +
+

You can reference these materials as:

+
+

Tavares H, Salehe B, Kumar A, Castle M & UKHSA New Variant Assessment Platform Team (2022) “cambiotraining/sars-cov-2-genomics: Introduction to Sars-CoV-2 Genomics”, https://cambiotraining.github.io/sars-cov-2-genomics

+
+

Or, in BibTeX format:

+
@Misc{,
+  author = {Tavares, Hugo and Salehe, Bajuna and Kumar, Ankit and Castle, Matt and UKHSA New Variant Assessment Platform Team},
+  title = {cambiotraining/sars-cov-2-genomics: Introduction to Sars-CoV-2 Genomics},
+  month = {March},
+  year = {2022},
+  url = {https://cambiotraining.github.io/sars-cov-2-genomics},
+}
+

Please make sure to include a link to the materials in the citation. (we will add a DOI in due time)

+

The contributing members from University of Cambridge Bioinformatics Training Facility team are:

+
    +
  • Matt Castle, Bioinformatics Training Manager
  • +
  • Hugo Tavares, Senior Teaching Associate
  • +
  • Bajuna Salehe, Teaching Associate
  • +
  • Ankit Kumar, Teaching Assistant
  • +
+

The UKHSA’s NVAP Team members that supported these materials are:

+
    +
  • Dr Leena Inamdar, NVAP Programme Lead and Global Health Lead
  • +
  • Dr Babak Afrough, Senior Project Manager
  • +
  • Aude Wilhelm, Senior Epidemiology Scientist
  • +
  • Richard Myers, Data Analytics Surveillance Head Bioinformatician
  • +
  • Sam Sims, Bioinformatician
  • +
  • Kate Edington, Bioinformatician
  • +
  • Constantina Laou, Specialist Lab Advisor
  • +
+
+
+

Acknowledgements

+

These materials have been developed as a collaboration between the Bioinformatics Training Facility at the University of Cambridge and the New Variant Assessment Platform (NVAP) program from the UK Health Security Agency.

+

Our partners also include COG Train. We also thank the wider community for publicly sharing training resources, including:

+ + + +
+
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/01-intro/01-surveillance.html b/materials/01-intro/01-surveillance.html new file mode 100644 index 0000000..448c737 --- /dev/null +++ b/materials/01-intro/01-surveillance.html @@ -0,0 +1,1051 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

1  SARS-CoV-2 genomic surveillance

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Learning Objectives +
+
+
+
    +
  • Enumerate some examples of how the genomic surveillance of SARS-CoV-2 has impacted public health decisions during the ongoing pandemic.
  • +
  • Describe what a variant of concern (VOC) is and how it differs from a variant of interest (VOI).
  • +
  • Describe the sources of information that can be obtained from GISAID, Pango, Nextstrain and the WHO nomenclature systems.
  • +
  • Contrast different sequencing protocols (e.g. amplicon, metagenomic) and technologies (e.g. Illumina and Nanopore) commonly used for SARS-CoV-2 sequencing, including the pros and cons of each.
  • +
  • Understand the steps involved in the widespread ARTIC protocol and the differences between its versions.
  • +
  • List key metadata fields needed with each sample to make best use of the data and recognise some limitations related to privacy.
  • +
+
+
+ +
+

1.1 What is SARS-CoV-2?

+

SARS-CoV-2 (Severe acute respiratory syndrome coronavirus 2) is a betacoronavirus resposible for the COVID-19 disease and caused a global outbreak leading to an ongoing pandemic. The initial spread of this virus started in the city of Wuhan, in China. Despite early efforts to contain its spread in China (through several lockdowns in the country), the virus spread to other provinces within China and, eventually, to other countries across the world. This led to the World Health Organisation (WHO) declaring a public health emergency on 30 January 2020 and then a pandemic on 11 March 2020.

+

SARS-CoV-2 is an RNA virus, composed of single-stranded RNA. The first SARS-CoV-2 genome was published in January 2020 and is approximately 30Kb long. It encodes several proteins including the so-called Spike protein (or ‘S’ for short), which is used by the virus to interact and eventually enter human cells and cause infection. This interaction happens by the binding of the S protein to the ACE2 protein receptor found in human cells.

+
+
+

+
Genome structure of SARS-CoV-2. Source: Rahimi, Mirzazadeh & Tavakolpour 2021
+
+
+

The genome sequence of SARS-CoV-2 has been a huge contributor to our ability to manage current pandemic. Namely:

+
    +
  • It allowed the development of a vaccine to target the S protein.
  • +
  • It allowed the development of diagnostic tests for positive cases (lateral flow test and PCR-based test).
  • +
  • It allowed the development of protocols for whole-genome sequencing of the virus from positive human samples.
  • +
+

This last point is the focus of our workshop, and we will spend some time looking at how to analyse these data.

+

You can watch the video below, created by the Maastrich University, if you want to learn more about the lifecycle of the SARS-CoV-2 virus and the role that its different proteins play in this context:

+

+ +

+ +
+
+

1.2 Genomic Surveillance

+

The number of SARS-CoV-2 genomes sequenced is the largest of a pathogen ever done. As such, it has enabled us to track how the virus evolves and spreads both at a local and global scale at an unprecendented resolution. This allowed the identification of mutations that affect characteristics of the virus, such as its transmissibility or severity of the disease, and testing new strains of the virus to understand whether they are effectively neutralised by current or future vaccines.

+

One of the main applications of SARS-CoV-2 sequencing is to infer relationships between the different circulating forms of the virus. This is done by comparing these sequences and building phylogenetic trees that reflect their sequence similarities. From these, it is possible to identify particular clusters of similar sequences that spread faster than expected, and may therefore be associated with mutations that increase the virus’ fitness.

+
+
+

+
Example of global phylogeny from the Nextstrain public server. Colours show clusters of similar sequences. (Screenshot taken Feb 2022)
+
+
+

For example, one of the first mutations identified from these sequencing efforts was A23403G (an adenine was replaced by a guanine in position 23,403 of the genome), which caused an aminoacid substitution in the S protein that increased virus infectivity and transmissibility. Since then, many new forms of the virus have been identified as being of particular concern for public health, and have been classified by the WHO as Variants of Concern. These have been named based on letters of the Greek alphabet, and have been a crucial way to inform public health policy and containment measures around the world.

+

In addition to the WHO variant nomenclature, there are three main projects that have been instrumental in grouping SARS-CoV-2 sequences into similar groups:

+
    +
  • The GISAID nomenclature classifies sequences based on key mutations that define particular groups of sequences in the global phylogeny. It also complements its classification by borrowing information from the Pango nomenclature system.
  • +
  • The Pango nomenclature is based on the phylogeny of SARS-CoV-2 and defined as groups of sequences that share a common ancestor and a distinctive sequence feature (for example, all share the same single nucleotide change). This nomenclature is also sometimes referred as “Rambaut et al. 2020” after the respective publication.
  • +
  • The Nextstrain nomenclature is slightly more informal than the above, its main purpose being to facilitate public health discussions. Despite this, it is still informed by the phylogenetic placement of sequences in a tree and therefore has a large overlap with the Pango nomenclature.
  • +
+

We will learn more about how sequences are classified into lineages and variants of concern in the section about Lineage Assignment.

+
+
+
+ +
+
+What is the difference: strain, lineage, clade, variant? +
+
+
+

These terms are sometimes used interchangeably in informal conversations about the different forms of SARS-CoV-2. For our purposes, these are the definitions we will use:

+

A strain is a group of viruses that are sufficiently diverged from others, so that they are quite distinct at a sequence level as well as in their biological properties. SARS-CoV-2 is still considered to be a single strain. Examples of other coronavirus strains are SARS-CoV and MERS-CoV.

+

The terms lineage and clade are somewhat similar, in that both represent groups of similar sequences, inferred from a phylogenetic analysis and sharing a common ancestor. Their main difference (at least in the current SARS-CoV-2 nomenclature systems) is the level of resolution. Clades tend to be more broadly defined and therefore include more sequences within it. They are useful to discuss long-term trends during a pandemic. Lineages have a finer resolution, being useful to identify patterns related to a recent outbreak.

+

Since a phylogenetic tree is inherently hierarchical, there isn’t always a clear-cut way of defining where one lineage/clade starts and another ends. That is why lineage classification is a partially manual process, involving human curation by experts.

+

Finally, the term variant is usually used to refer to WHO’s variants of interest or variants of concern (e.g. the Alpha, Delta and Omicron variants). Variants are distinct from each other by the combination of all sequence changes in their genomes. The term “variant” can be ambiguous when used in the field of bioinformatics, and we return to this in the section about Lineage Assigment

+
+
+
+
+

1.3 The GISAID Database

+

The widespread use of genome sequences would have not been possible without the ability to centrally collect these sequences and make them available to researchers and public health professionals. The main repository used to deposit SARS-CoV-2 sequences is the database managed by the GISAID Initiative. This allows sharing the sequencing data as well as metadata associated with it (such as dates of collection, geo-location, patient information, etc.). For example, the outbreak.info website that we just used in the exercise above directly pulls data from GISAID to update its reports on a daily basis.

+

At the end of this course you too will be able to contribute to this database, by producing full genome sequences assembled from sequencing data.

+
+
+
+ +
+
+Create a GISAID account +
+
+
+

Go to the GISAID registration page and create an account, so you can gain access to the data stored in GISAID as well as the ability to submit your own sequences in the future.

+
+
+
+
+

1.4 SARS-CoV-2 Sequencing

+

Routine SARS-CoV-2 sequencing is done with a method generally referred to as amplicon sequencing. This method relies on amplifying the genetic material using polymerase chain reaction (PCR) with a panel of primers designed against the known SARS-CoV-2 genome.

+

One of the most popular methods for preparing virus RNA for sequencing is amplicon sequencing. This method consists of amplifying the genetic material by PCR using a panel of primers that covers the entire genome of the target virus. This is required, since the starting amount of viral genetic material in a sample is typically very low (most of the material will belong to the host - in this case, human RNA).

+

The most popular protocol for amplicon sequencing has been developed by the ARTIC network, whose aim is to develop standardised protocols for viral sample processing. The group has designed and tested a panel of primers that work well to amplify the SARS-CoV-2 genome in a mostly unbiased way. This is a challenging task, as the protocol involves pooling hundreds of primers together in a single reaction! Also, as the virus mutates in the population, primers that used to work in the original template genome may no longer work in the new variants circulating in the population (“primer erosion”). Therefore, the ARTIC primers have gone through several versions, which are updated and optimised to work on the most common circulating lineages. These are called “primer schemes” and are made publicly available in a public repository.

+
+
+

+
Schematic of the ARTIC protocol. The RNA is reverse-transcribed to cDNA, then two pools of primers (that cover the entire SARS-CoV-2 genome) are used to PCR-amplify the material. This material is used to prepare sequencing libraries for the relevant platform that will be used downstream (Illumina or Nanopore). Source: Gohl et al. 2020
+
+
+

Besides amplicon sequencing, other methods can also be used to obtain SARS-Cov-2 genomes:

+
    +
  • Metagenomic RNA sequencing was the method used to assemble the first SARS-CoV-2 genome and one of the first sequences in Cambodia. This method consists of extracting viral RNA (using commercially available kits) followed by high-throughput RNA-seq. The resulting sequences are then compared with nucleotide databases of known organisms, and viral sequences selected for de-novo assembly. This approach is suitable when the virus sequence is unknown, but requires a high sequencing depth, increasing its costs.

  • +
  • Sequence capture protocols are also available, whereby the samples are enriched for the target virus by using a panel of probes against the SARS-CoV-2 genome, followed by sequencing and reference-based assembly. This approach is more similar to amplicon sequencing, as it works by enriching the sample for the known virus.

  • +
+
+
+

+
Simplified diagram of the main steps involved in metagenomic and amplicon sequencing approaches. In amplicon sequencing the cDNA material is first PCR-amplified with virus-specific primers to enrich the sample, followed by sequencing and downstream bioinformatic analysis. With the metagenomic approach the mixed cDNA material is sequenced and the resulting sequences are bioinformatically separated between known sequences (from other organisms) and unknown sequences. The unknown sequences can then be de-novo assembled into a new genome.
+
+
+

Despite these alternative methods, amplicon sequencing remains one of the most popular methods for large-scale viral surveillance due to its low cost and high-throughput. The data generated from this method will be the focus of this course.

+
+
+
+ +
+
+Illumina or Nanopore? +
+
+
+

Both of these sequencing platforms can be used to sequence amplicon samples.

+

Nanopore platforms allow sequencing 96 samples at a time, are readily available as portable devices, and have fast run times. This gives them great flexibility, making them an excellent solution for rapidly building sequencing capacity in a lab.

+

By comparison, Illumina platforms give higher throughput, are cheaper to run per sample and have lower error rates. However, they require substantial upfront cost to setup and equip in the lab and take longer to run.

+
+
+
+
+
+ +
+
+Alternative Amplicon Sequencing Protocols +
+
+
+

Although the ARTIC protocol is one of the most popular used in routine SARS-CoV-2 sequencing, there are alternative sets of primers and protocols available.

+

For example, Thermo Fisher has the Ion AmpliSeq SARS-CoV-2 kit, designed to work with Ion Torrent sequencing platforms.

+

An alternative protocol that has been developed by the community is the midnight protocol. This protocol consists of amplifying larger PCR fragments, thus requiring fewer primer pairs than the ARTIC protocol. This leads to a lower complexity in the multiplex PCR reaction and fewer chances of PCR dropout due to mis-priming againts new variants. However, because it uses longer PCR fragments, it can only be used with long-read sequencing (Nanopore) and not short-read sequencing (Illumina).

+

The Coronavirus Method Development Community on the protocols.io platform is a good source of alternative sequencing protocols for SARS-CoV-2.

+
+
+
+
+

1.5 Sample Collection

+

There are two big considerations when collecting samples for sequencing:

+
    +
  • Is there enough viral material in the sample (viral load)?
  • +
  • Did I collect all the necessary information about each sample (metadata)?
  • +
+
+

1.5.1 Viral Load

+

When collecting patient samples for sequencing, it is important to quantify the viral load in the sample. This is usually done by quantitative RT-PCR (RT-qPCR), whereby the amplification of the samples is monitored by measuring a dye that is incorporated during the PCR reaction. This results in amplification curves, which reflect the amount of RNA present in the sample. The more PCR cycles are needed to saturate the signal, the lower the amount of virus in the sample.

+
+
+

+
Overview of RT-qPCR quantification of viral load in a sample. Adapted from Smyrlaki et al. 2020
+
+
+

The result of RT-qPCR is usually expressed as the PCR cycle at which a particular threshold in the amplification curve is reached, the Ct value. Generally, samples with Ct > 30 are not worth sequencing, as their genome completeness is likely going to be low due to the low amounts of starting material.

+
+
+

+
Relationship between RT-qPCR Ct value and resulting genome completeness. Source: Jared Simpson’s talk at CLIMB BIG DATA workshop
+
+
+
+
+

1.5.2 Metadata

+

One important consideration when collecting samples, is to record as much information as possible about each sample. The Public Health Alliance for Genomic Epidemiology (PHA4GE) coalition provides several guidelines and a protocol to aid in metadata collection. There are also essential metadata needed to upload new SARS-CoV-2 genome sequences to the GISAID database (Figure).

+
+
+

+
Snapshot of the GISAID upload template metadata sheet.
+
+
+

Two key pieces of information for genomic surveillance are the date of sample collection and the geographic location of that sample. This information can be used to understand which variants are circulating in an area at any given time.

+

Privacy concerns need to be considered when collecting and storing sensitive data. However, it should be noted that sensitive data can still be collected, even if it is not shared publicly (e.g. via the GISAID database). Such sensitive information may still be useful for the relevant public health authorities, who may use those sensitive information for a finer analysis of the data. For example, epidemiological analysis will require individual-level metadata (“person, place, time”) to be available, in order to track the dynamics of transmission within a community.

+

This is the general advice when it comes to metadata collection: record as much information about each sample as possible!

+
+
+
+

1.6 SARS-CoV-2 Bioinformatics

+

What bioinformatic skills do we need in order to analyse SARS-CoV-2 genome sequencing data? While there are several software tools that have been specifically developed for SARS-CoV-2 analysis (and we will see some of them in this course), there is a set of foundational skills that are applicable to any bioinformatics application:

+
    +
  • The use of the Unix command line. Linux is the most common operating system for computational work and most of the bioinformatic software only runs on it.
  • +
  • Getting familiar with common file formats in bioinformatics. This includes files to store nucleotide sequences, sequence alignments to a reference genome, gene annotations, phylogenetic trees, amongst others.
  • +
  • Understand software tools’ documentation and how to configure different options when running our analyses.
  • +
+

We will turn to these topics in the following sessions.

+
+
+

1.7 Exercises

+
+
+
+ +
+
+WHO Variants +
+
+
+
+
+
+
+
    +
  • Looking through the WHO variants page, can you find what the difference is between a Variant of Interest (VOI) and a Variant of Concern (VOC)?
  • +
  • Can you find the correspondence between the VOCs and their respective lineages in other classification systems (GISAID, Nextstrain and Pango)?
  • +
  • Go to the outbreak.info website and search for a country of your choice (for example, your country of origin or where you live).
  • +
  • How many sequences are available from the last 360 days? (Note: by default only the last 60 days are shown. You can change this in the text box found on the right of the report page.)
  • +
  • What were the most common lineages of the virus in circulation in the last 360 days? Do you notice sharp changes in the frequency of WHO Variants of Concern?
  • +
+
+
+
+
+
+
+
+
+ +
+
+Metadata +
+
+
+
+
+
+
+

Open the folder under “Course_Materials > 01-Unix > metadata”, where you will find several CSV files with information about samples that were sequenced and will be analysed by us later in the course. Double-click to open one of these files (any of them is fine), which should open them on a spreadsheet software (on our training machines it is LibreOffice, but Excel would also work).

+

The column names of these files are based on the PHA4GE nomenclature system.

+
    +
  • Go to the PHA4GE metadata collection protocol and read throught the first steps to find the link to the collection templates.
  • +
  • From that link, go to the Supporting Materials to find the “field mappings” spreadsheet, which matches naming conventions between PHA4GE and other databases such as GISAID.
  • +
  • Based on this information, can you match the column names from our sample information table to the fields required by GISAID?
  • +
  • What do you think is the naming convention for the isolate name? How would you adjust this when uploading the data to GISAID?
  • +
+
+ +
+
+
+
+

The first step of this protocol provides with a link to a GitHub repository containing several template files: https://github.com/pha4ge/SARS-CoV-2-Contextual-Data-Specification.

+

If we follow that link, we will see several spreadsheet files (for Excel or LibreOffice) with templates that can be used to record samples metadata. In the “Supporting Materials” we find a link to “PHA4GE to WHO and sequence repository field mappings”. If we download and open that file we will see a spreadhseet with 4 tabs. Each of the tabs shows the equivalence between the PHA4GE column names and the equivalent names used by other platforms.

+

Looking at the equivalence between the column names in our metadata sheets and the GISAID nomenclature, we can identify only some of them:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Our NameGISAID equivalentComments
sample
sample_collection_dateCollection date
geo_loc_countryLocation
geo_loc_regionLocationIn GISAID the field “Location” is used for “Continent / Country or Territory / Region”
latitude
longitude
sequencing_instrumentSequencing technology
sample_collection_year
sample_collection_month
sample_collection_day
organism
isolateVirus name
sequencing_protocol_name
amplicon_pcr_primer_scheme
sequencing_protocol
+

Looking at this spreadhseet, we can also see this explanation about the “Virus Name” field:

+
+

While the meanings and structures of the GISAID field “Virus name” and the PHA4GE field “isolate” overlap, GISAID requires a slightly different structure for Virus name. e.g. PHA4GE structure: SARS-CoV-2/country/sampleID/2020, GISAID structure: hCov-19/country/sampleID/2020. Change “SARS-CoV-2” to “hCov-19” in the isolate name.

+
+

So, in order to upload our data to GISAID we would need to change the name of the virus accordingly.

+
+
+
+
+
+
+
+
+
+
+
+
+

1.8 Summary

+
+
+
+ +
+
+Key Points +
+
+
+
    +
  • The sequencing of SARS-CoV-2 genomes has allowed the tracking of new variants throughout the pandemic.
  • +
  • The World Health Organisation defines Variants of Concern as SARS-CoV-2 forms with characteristics of public health concern. This includes increased transmissibility, virulence, disease symptoms or vaccine resistance.
  • +
  • GISAID, Pango and Nextstrain are organisations whose work includes the classification of SARS-CoV-2 genomes into groups that may later be classified as variants of concern by WHO.
  • +
  • GISAID also plays a key role as the main database for assembled SARS-CoV-2 genomes, submitted by the community.
  • +
  • Routine SARS-CoV-2 sequencing is usually done by amplicon sequencing (amplifying an infected sample by PCR using primers that cover the entire genome). The most popular protocol has been developed by the ARTIC network.
  • +
  • Other methods of sequencing include metagenomic and sequence capture. However, these methods require more sequencing and therefore are not commonly used for sequencing at a population scale.
  • +
  • Both Illumina and Nanopore platforms can be used for sequencing SARS-CoV-2 amplicon samples.
  • +
  • Metadata collection is essential for interpreting the results of the sequencing. +
      +
    • For genomic surveillance purposes recording of geographic location and date of sampling are crucial.
    • +
    • Other useful information includes details about the sequencing (e.g. sample preparation protocols and sequencing platforms used).
    • +
  • +
  • +
+
+
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/01-intro/02-ngs.html b/materials/01-intro/02-ngs.html new file mode 100644 index 0000000..d1ff3a6 --- /dev/null +++ b/materials/01-intro/02-ngs.html @@ -0,0 +1,1015 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

2  Introduction to NGS

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Learning Objectives +
+
+
+
    +
  • Describe differences between sequencing data produced by Illumina and Nanopore platforms.
  • +
  • Recognise the structure of common file formats in bioinformatics, in particular FASTA and FASTQ files.
  • +
  • Use FastQC to produce a quality report for Illumina sequences.
  • +
  • Use MultiQC to produce a report compiling multiple quality statistics.
  • +
  • Examine quality reports to identify problematic samples.
  • +
+
+
+ +
+

2.1 Next Generation Sequencing

+

The sequencing of genomes has become more routine due to the rapid drop in DNA sequencing costs seen since the development of Next Generation Sequencing (NGS) technologies in 2007. One main feature of these technologies is that they are high-throughput, allowing one to more fully characterise the genetic material in a sample of interest.

+

There are three main technologies in use nowadays, often referred to as 2nd and 3rd generation sequencing:

+
    +
  • Illumina’s sequencing by synthesis (2nd generation)
  • +
  • Oxford Nanopore, shortened ONT (3rd generation)
  • +
  • Pacific Biosciences, shortened PacBio (3rd generation)
  • +
+

The video below from the iBiology team gives a great overview of these technologies.

+

+ +

+
+

2.1.1 Illumina Sequencing

+

Illumina’s technology has become a widely popular method, with many applications to study transcriptomes (RNA-seq), epigenomes (ATAC-seq, BS-seq), DNA-protein interactions (ChIP-seq), chromatin conformation (Hi-C/3C-Seq), population and quantitative genetics (variant detection, GWAS), de-novo genome assembly, amongst many others.

+

An overview of the sequencing procedure is shown in the animation video below. Generally, samples are processed to generate so-called sequencing libraries, where the genetic material (DNA or RNA) is processed to generate fragments of DNA with attached oligo adapters necessary for the sequencing procedure (if the starting material is RNA, it can be converted to DNA by a step of reverse transcription). Each of these DNA molecule is then sequenced from both ends, generating pairs of sequences from each molecule, i.e. paired-end sequencing (single-end sequencing, where the molecule is only sequenced from one end is also possible, although much less common nowadays).

+

This technology is a type of short-read sequencing, because we only obtain short sequences from the original DNA molecules. Typical protocols will generate 2x50bp to 2x250bp sequences (the 2x denotes that we sequence from each end of the molecule).

+

+ +

+

The main advantage of Illumina sequencing is that it produces very high-quality sequence reads (current protocols generate reads with an error rate of less than <1%) at a low cost. However, the fact that we only get relatively short sequences means that there are limitations when it comes to resolving particular problems such as long sequence repeats (e.g. around centromeres or transposon-rich areas of the genome), distinguishing gene isoforms (in RNA-seq), or resolving haplotypes (combinations of variants in each copy of an individual’s diploid genome).

+
+
+

2.1.2 Nanopore Sequencing

+

Nanopore sequencing is a type of long-read sequencing technology. The main advantage of this technology is that it can sequence very long DNA molecules (up to megabase-sized), thus overcoming the main shortcoming of short-read sequencing mentioned above. Another big advantage of this technology is its portability, with some of its devices designed to work via USB plugged to a standard laptop. This makes it an ideal technology to use in situations where it is not possible to equip a dedicated sequencing facility/laboratory (for example, when doing field work).

+
+
+

+
Overview of Nanopore sequencing showing the highly-portable MinION device. The device contains thousands of nanopores embeded in a membrane where current is applied. As individual DNA molecules pass through these nanopores they cause changes in this current, which is detected by sensors and read by a dedicated computer program. Each DNA base causes different changes in the current, allowing the software to convert this signal into base calls.
+
+
+

One of the bigger challenges in effectively using this technology is to produce sequencing libraries that contain high molecular weight, intact, DNA. Another disadvantage is that, compared to Illumina sequencing, the error rates at higher, at around 5%.

+
+
+
+ +
+
+Illumina or Nanopore for SARS-CoV-2 sequencing? +
+
+
+

Both of these platforms have been widely popular for SARS-CoV-2 sequencing. They can both generate data with high-enough quality for the assembly and analysis of SARS-CoV-2 genomes. Mostly, which one you use will depend on what sequencing facilities you have access to.

+

While Illumina provides the cheapest option per sample of the two, it has a higher setup cost, requiring access to the expensive sequencing machines. On the other hand, Nanopore is a very flexible platform, especially its portable MinION devices. They require less up-front cost allowing getting started with sequencing very quickly in a standard molecular biology lab.

+
+
+
+
+
+

2.2 Sequencing Analysis

+

In this section we will demonstrate two common tasks in sequencing data analysis: sequence quality control and mapping to a reference genome. There are many other tasks involved in analysing sequencing data, but looking at these two examples will demonstrate the principles of running bioinformatic programs. We will later see how bioinformaticians can automate more complex analyses in the consensus assembly section.

+

One of the main features in bioinformatic analysis is the use of standard file formats. It allows software developers to create tools that work well with each other. For example, the raw data from Illumina and Nanopore platforms is very different: Illumina generates images; Nanopore generates electrical current signal. However, both platforms come with software that converts those raw data to a standard text-based format called FASTQ.

+
+

2.2.1 FASTQ Files

+

FASTQ files are used to store nucleotide sequences along with a quality score for each nucleotide of the sequence. These files are the typical format obtained from NGS sequencing platforms such as Illumina and Nanopore (after basecalling).

+

The file format is as follows:

+
@SEQ_ID                   <-- SEQUENCE NAME
+AGCGTGTACTGTGCATGTCGATG   <-- SEQUENCE
++                         <-- SEPARATOR
+%%).1***-+*''))**55CCFF   <-- QUALITY SCORES
+

In FASTQ files each sequence is always represented across 4 lines. The quality scores are encoded in a compact form, using a single character. They represent a score that can vary between 0 and 40 (see Illumina’s Quality Score Encoding). The reason single characters are used to encode the quality scores is that it saves space when storing these large files. Software that work on FASTQ files automatically convert these characters into their score, so we don’t have to worry about doing this conversion ourselves.

+

The quality value in common use is called a Phred score and it represents the probability that the respective base is an error. For example, a base with quality 20 has a probability \(10^{-2} = 0.01 = 1\%\) of being an error. A base with quality 30 has \(10^{-3} = 0.001 = 0.1\%\) chance of being an error. Typically, a Phred score threshold of >20 or >30 is used when applying quality filters to sequencing reads.

+

Because FASTQ files tend to be quite large, they are often compressed to save space. The most common compression format is called gzip and uses the extension .gz. To look at a gzip file, we can use the command zcat, which decompresses the file and prints the output as text.

+

For example, we can use the following command to count the number of lines in a compressed FASTQ file:

+
zcat sequences.fq.gz | wc -l
+

If we want to know how many sequences there are in the file, we can divide the result by 4 (since each sequence is always represented across four lines).

+
+
+

2.2.2 FASTQ Quality Control

+

One of the most basic tasks in Illumina sequence analysis is to run a quality control step on the FASTQ files we obtained from the sequencing machine.

+

The program used to assess FASTQ quality is called FastQC. It produces several statistics and graphs for each file in a nice report that can be used to identify any quality issues with our sequences.

+

The basic command to run FastQC is:

+
fastqc --outdir PATH_TO_OUTPUT_DIRECTORY   PATH_TO_SEQUENCES
+

FastQC can process several samples at once, and often we can use the * wildcard to do this. We will see an example of this in the following exercise.

+

Each FastQC HTML report contains a section with a different quality assessment plot. Each of these are explained in the online documentation:

+ +

For example, looking at the “Per base sequence quality” section for one of our samples, we can see a very high quality score, which is typical of Illumina data nowadays.

+
+
+

+
Sequence quality plot from FastQC for one of our samples. The blue line shows the average across all samples. This sample is very high quality as all sequences have quality > 20 across the entire length of the reads.
+
+
+
+
+
+ +
+
+Quality Control Nanopore Reads +
+
+
+

Although FastQC can run its analysis on any FASTQ files, it has mostly been designed for Illumina data. You can still run FastQC on basecalled Nanopore data, but some of the output modules may not be as informative. FastQC can also run on FAST5 files, using the option --nano.

+

You can also use MinIONQC, which takes as input the sequence_summary.txt file, which is a standard output file from the Guppy software used to convert Nanopore electrical signal to sequence calls.

+
+
+
+
+

2.2.3 Read Mapping

+

A common task in processing sequencing reads is to align them to a reference genome, which is typically referred to as read mapping or read alignment. We will continue exemplifying how this works for Illumina data, however the principle is similar for Nanopore data (although the software used is often different, due to the higher error rates and longer reads typical of these platforms).

+

Generally, these are the steps involved in read mapping (figure below):

+
    +
  • Genome Indexing | Because reference genomes can be quite long, most mapping algorithms require that the genome is pre-processed, which is called genome indexing. You can think of a genome index in a similar way to an index at the end of a textbook, which tells you in which pages of the book you can find certain keywords. Similarly, a genome index is used by mapping algorithms to quickly search through its sequence and find a good match with the reads it is trying to align against it. Each mapping software requires its own index, but we only have to generate the genome index once.
  • +
  • Read mapping | This is the actual step of aligning the reads to a reference genome. There are different popular read mapping programs such as bowtie2 or bwa. The input to these programs includes the genome index (from the previous step) and the FASTQ file(s) with reads. The output is an alignment in a file format called SAM (text-based format - takes a lot of space) or BAM (compressed binary format - much smaller file size).
  • +
  • BAM Sorting | The mapping programs output the sequencing reads in a random order (the order in which they were processed). But, for downstream analysis, it is good to sort the reads by their position in the genome, which makes it faster to process the file.
  • +
  • BAM Indexing | This is similar to the genome indexing we mentioned above, but this time creating an index for the alignment file. This index is often required for downstream analysis and for visualising the alignment with programs such as IGV.
  • +
+
+
+

+
Diagram illustrating the steps involved in mapping sequencing reads to a reference genome. Mapping programs allow some differences between the reads and the reference genome (red mutation shown as an example). Before doing the mapping, reads are usually filtered for high-quality and to remove any sequencing adapters. The reference genome is also indexed before running the mapping step. The mapped file (BAM format) can be used in many downstream analyses. See text for more details.
+
+
+

We have already prepared the SARS-CoV-2 genome index for the bowtie2 aligner. We have also prepared a shell script with the code to run the three steps above as an example. Let’s look at the content of this file (you can open it with nano scripts/mapping.sh):

+
# mapping
+bowtie2 -x resources/reference/bowtie2/sarscov2 -1 data/reads/ERR6129126_1.fastq.gz -2 data/reads/ERR6129126_2.fastq.gz --threads 5 | samtools sort -o results/bowtie2/ERR6129126.bam -
+
+# index mapped file
+samtools index results/bowtie2/ERR6129126.bam
+
+# obtain some mapping statistics
+samtools stats results/bowtie2/ERR6129126.bam > results/bowtie2/ERR6129126.stats.txt
+

In the first step, mapping, we are using two tools: bowtie2 and samtools. bowtie2 is the mapping program and samtools is a program used to manipulate SAM/BAM alignment files. In this case we used the | pipe to send the output of bowtie2 directly to samtools:

+
    +
  • -x is the prefix of the reference genome index.
  • +
  • -1 is the path to the first read in paired-end sequencing.
  • +
  • -2 is the path to the second read in paired-end sequencing.
  • +
  • --threads 5 indicates we want to use 5 CPUs (or threads) to do parallel processing of the data.
  • +
  • | is the pipe that sends the output from bowtie2 to samtools sort.
  • +
  • -o is the name of the output file. By setting the file extension of this file to .bam, samtools will automatically save the file in the compressed format (which saves a lot of space).
  • +
  • The - symbol at the end of the samtools command indicates that the input is coming from the | pipe.
  • +
+

We also have a step that creates an index file for the BAM file using samtools index. This creates a file with the same name and .bai extension.

+

Finally, the script also contains a step that collects some basic statistics from the alignment, which we save in a text file. We will see how this file can be used to produce a quality control report below.

+
+
+

2.2.4 Visualising BAM Files in IGV

+

One thing that can be useful is to visualise the alignments produced in this way. We can use the program IGV (Integrative Genome Viewer) to do this:

+
    +
  • Open IGV and go to File → Load from file….
  • +
  • In the file browser that opens go to the folder results/bowtie2/ and select the file ERR6129126.bam to open it.
  • +
  • Go back to File → Load from file… and this time load the BED files containing the primer locations. These can be found in resources/primers/artic_primers_pool1.bed and resources/primers/artic_primers_pool2.bed.
  • +
+

There are several ways to search and browse through our alignments, exemplified in the figure below.

+
+
+

+
Screenshot IGV program. The search box at the top can be used to go to a specific region in the format “CHROM:START-END”. In the case of SARS-CoV-2 there is only one “chromosome” called NC_045512.2 (this is the name of the reference genome), so if we wanted to visualise the region between positions 21563 and 25384 (the Spike gene) we would write “NC_045512.2:21563-25384”.
+
+
+
+
+

2.2.5 Quality Reports

+

We’ve seen the example of using the program FastQC to assess the quality of our FASTQ sequencing files. And we have also seen an example of using the program samtools stats to obtain some quality statistics of our read mapping step.

+

When processing multiple samples at once, it can become hard to check all of these quality metrics individually for each sample. This is the problem that the software MultiQC tries to solve. This software automatically scans a directory and looks for files it recognises as containing quality statistics. It then compiles all those statistics in a single report, so that we can more easily look across dozens or even hundreds of samples at once.

+

Here is the command to run MultiQC and compile several quality statistics into a single report:

+
mkdir results/multiqc
+multiqc --outdir results/multiqc/  results/
+

MultiQC generates a report, in this example in results/multiqc/multiqc_report.html. From this report we can get an overview of the quality across all our samples.

+
+
+

+
Snapshot of some of the report sections from MultiQC. In this example we can see the “General Statistics” table and the “Sequence Quality Histograms” plot. One of the samples has lower quality towards the end of the read compared to other samples (red line in the bottom panel).
+
+
+

For example, from the section “General Statistics” we can see that the number of reads varies a lot between samples. Sample ERR5926784 has around 0.1 million reads, which is substantially lower than other samples that have over 1 million reads. This may affect the quality of the consensus assembly that we will do afterwards.

+

From the section “Sequence Quality Histograms”, we can see that one sample in particular - ERR5926784 - has lower quality in the second pair of the read. We can open the original FastQC report and confirm that several sequences even drop below a quality score of 20 (1% change of error). A drop in sequencing quality towards the end of a read can often happen, especially for longer reads. Usually, analysis workflows include a step to remove reads with low quality so these should not affect downstream analysis too badly. However, it’s always good to make a note of potentially problematic samples, and see if they produce lower quality results downstream.

+
+
+
+

2.3 Bioinformatic File Formats

+

Like we said above, bioinformatics uses many standard file formats to store different types of data. We have just seen two of these file formats: FASTQ for sequencing reads and BAM files to store reads mapped to a genome.

+

Another very common file format is the FASTA file, which is the format that our reference genome is stored as. The consensus sequences that we will generate are also stored as FASTA files. We detail this format below, but there are many other formats. Check out our appendix page on File Formats to learn more about them.

+
+

2.3.1 FASTA Files

+

Another very common file that we should consider is the FASTA format. FASTA files are used to store nucleotide or amino acid sequences.

+

The general structure of a FASTA file is illustrated below:

+
>sample01                 <-- NAME OF THE SEQUENCE
+AGCGTGTACTGTGCATGTCGATG   <-- SEQUENCE ITSELF
+

Each sequence is represented by a name, which always starts with the character >, followed by the actual sequence.

+

A FASTA file can contain several sequences, for example:

+
>sample01
+AGCGTGTACTGTGCATGTCGATG
+>sample02
+AGCGTGTACTGTGCATGTCGATG
+

Each sequence can sometimes span multiple lines, and separate sequences can always be identified by the > character. For example, this contains the same sequences as above:

+
>sample01      <-- FIRST SEQUENCE STARTS HERE
+AGCGTGTACTGT
+GCATGTCGATG
+>sample02      <-- SECOND SEQUENCE STARTS HERE
+AGCGTGTACTGT
+GCATGTCGATG
+

To count how many sequences there are in a FASTA file, we can use the following command:

+
grep ">" sequences.fa | wc -l
+

In two steps:

+
    +
  • find the lines containing the character “>”, and then
  • +
  • count the number of lines of the result.
  • +
+

We will see FASTA files several times throughout this course, so it’s important to be familiar with them.

+
+
+
+

2.4 Exercises

+
+
+
+ +
+
+Sequence quality control: FASTQC +
+
+
+
+
+
+
+

In the course materials directory 02-ngs/ we have several FASTQ files that we will use to assemble SARS-CoV-2 genomes. But first, we will run FastQC to check the quality of these files.

+

This is the basic command we could use in our samples:

+
fastqc --outdir results/fastqc data/reads/*.fastq.gz
+
    +
  • Create the output directory for the analysis (results/fastqc). +
    + +Hint + +The command to create directories is mkdir. By default, the mkdir directory only creates one directory at a time. In this case we need to create first the results directory and then the results/fastqc within it. Alternatively, both directories can be created at once using the -p option. +
  • +
  • Modify the fastqc command shown above to add an option to run the analysis using 8 threads in parallel (or CPUs). Check the tool’s help (fastqc --help) to see what the option to do this is called.
  • +
  • Run the command. You will know it is running successfully because it prints progress of its analysis on the screen.
  • +
+
+ +
+
+
+
+

First, we can create a directory to output our results:

+
mkdir -p results/fastqc
+

The -p option ensures that both directories are created in one step. Otherwise, since the parent directory results did not exist, mkdir would throw an error.

+

To check the options available with FastQC we can run fastqc --help to get the complete documentation. As we scroll through the options, we can see the relevant one for running the analysis in parallel:

+
-t --threads    Specifies the number of files which can be processed
+                simultaneously.  Each thread will be allocated 250MB of
+                memory so you shouldn't run more threads than your
+                available memory will cope with, and not more than
+                6 threads on a 32 bit machine
+

Although the documentation is a little technical, this means that if we have multiple CPUs available on our computer, we can set this option to allow multiple files to be processed in parallel. Our training machines have 8 CPUs, so we can run the command as follows:

+
fastqc -t 8 --outdir results/fastqc data/reads/*.fastq.gz
+

The analysis report generated by FastQC is given as a .html file (opens in a web browser). We will go through the details of this below.

+
+
+
+
+
+
+
+
+
+
+
+
+

2.5 Summary

+
+
+
+ +
+
+Key Points +
+
+
+
    +
  • Illumina sequencing produces short reads (50bp - 200bp), typically from both ends of a DNA fragment. It is a comparatively cheap sequencing platform which produces very high-quality sequences.
  • +
  • Nanopore sequencing produces very long reads (typically hundreds of kilobases long). It is comparatively more expensive and has higher error rates. However, it is more flexible with some of its platforms being fully portable.
  • +
  • Sequencing reads are stored in a file format called FASTQ. This file contains both the nucleotide sequence and quality of each base.
  • +
  • The quality of Illumina sequence reads can be assessed using the software FastQC.
  • +
  • One common task in bioinformatics is to align or map reads to a reference genome. This involves: +
      +
    • Creating a genome index - this only needs to be done once.
    • +
    • Mapping the reads to the reference genome (e.g. using bowtie2) - the output is in SAM format.
    • +
    • Sorting the reads in the mapped file (using samtools sort) - the output is in BAM format.
    • +
    • Indexing the BAM alignment file (using samtools index).
    • +
  • +
  • The software MultiQC can be used to generate a single reports that compiles statistics across several samples.
  • +
  • Bioinformatics uses many standard file formats. One of the most common ones is the FASTA format, which is used to store nucleotide or amino acid sequences (no quality information is contained in these files). This is a standard format that assembled genomes are stored as.
  • +
+
+
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/01-intro/03-workflows.html b/materials/01-intro/03-workflows.html new file mode 100644 index 0000000..230f997 --- /dev/null +++ b/materials/01-intro/03-workflows.html @@ -0,0 +1,629 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

3  Bioinformatic workflows

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Warning +
+
+
+

Under development

+
+
+ + + +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/01-intro/images/Ct_coverage_relationship.png b/materials/01-intro/images/Ct_coverage_relationship.png new file mode 100644 index 0000000..b70d12b Binary files /dev/null and b/materials/01-intro/images/Ct_coverage_relationship.png differ diff --git a/materials/01-intro/images/artic_protocol.png b/materials/01-intro/images/artic_protocol.png new file mode 100644 index 0000000..5250977 Binary files /dev/null and b/materials/01-intro/images/artic_protocol.png differ diff --git a/materials/01-intro/images/fastqc_quality.png b/materials/01-intro/images/fastqc_quality.png new file mode 100644 index 0000000..25104c1 Binary files /dev/null and b/materials/01-intro/images/fastqc_quality.png differ diff --git a/materials/01-intro/images/gisaid_metadata.png b/materials/01-intro/images/gisaid_metadata.png new file mode 100644 index 0000000..49b4cfb Binary files /dev/null and b/materials/01-intro/images/gisaid_metadata.png differ diff --git a/materials/01-intro/images/igv_overview.svg b/materials/01-intro/images/igv_overview.svg new file mode 100644 index 0000000..8af4d14 --- /dev/null +++ b/materials/01-intro/images/igv_overview.svg @@ -0,0 +1,2258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + Genome + Chromosome + Region search box + Use the mouse here to zoom-in on a region + + + + Depth of Coverage + Read Alignment + Annotations + SNP + Errors + + + + + + diff --git a/materials/01-intro/images/lineages_example.svg b/materials/01-intro/images/lineages_example.svg new file mode 100644 index 0000000..c503d4c --- /dev/null +++ b/materials/01-intro/images/lineages_example.svg @@ -0,0 +1,4282 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + Date + 2020-May + 2020-Oct + 2021-Mar + 2021-Aug + 2022-Jan + + diff --git a/materials/01-intro/images/metagenomic_vs_amplicon.svg b/materials/01-intro/images/metagenomic_vs_amplicon.svg new file mode 100644 index 0000000..8f2e9f7 --- /dev/null +++ b/materials/01-intro/images/metagenomic_vs_amplicon.svg @@ -0,0 +1,819 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Amplicon Sequencing + Reference-based Assembly + + Metagenomic Sequencing + De-novo Assembly + + Sequencing + + + + + + + + + + + + + + + + + + + + + Sequences matching knownorganisms + New sequences + + + + + + de-novo assembly + + + Separate sequences + New genome + + + + + Mixed Material + PCR with SARS-CoV-2 specific primers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Enriched Sample + + + Sequencing + + + + + + + + + + + + + + + + + + + + + + + Consensus genome + Map reads to reference genome + + + diff --git a/materials/01-intro/images/multiqc.svg b/materials/01-intro/images/multiqc.svg new file mode 100644 index 0000000..50ab9d5 --- /dev/null +++ b/materials/01-intro/images/multiqc.svg @@ -0,0 +1,3435 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/materials/01-intro/images/ngs_mapping.svg b/materials/01-intro/images/ngs_mapping.svg new file mode 100644 index 0000000..9c10e65 --- /dev/null +++ b/materials/01-intro/images/ngs_mapping.svg @@ -0,0 +1,509 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CATGCTAGGGCTAGCTACGTGATCGTACTAGTCGTCTAGA + CATGCTAGGGCTAGCTACGT + GCTACGTGATCGTAATAGTC + CGTAATAGTCGTCTAGA + + + + + Genome (FASTA file) + Reads (FASTQ files) + + Indexing (software-specific) + + + + + + Quality Filtering + + Mapping + + Sort and Index + BAM file + + Visualise (IGV)Collect Statisticsetc... + + diff --git a/materials/01-intro/images/virus-RT-qPCR.png b/materials/01-intro/images/virus-RT-qPCR.png new file mode 100644 index 0000000..166939b Binary files /dev/null and b/materials/01-intro/images/virus-RT-qPCR.png differ diff --git a/materials/02-isolates/01-consensus.html b/materials/02-isolates/01-consensus.html new file mode 100644 index 0000000..88f61cd --- /dev/null +++ b/materials/02-isolates/01-consensus.html @@ -0,0 +1,1127 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

4  Consensus assembly

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Learning Objectives +
+
+
+
    +
  • Recognise what the main steps are in processing raw sequencing data to generate consensus genome sequences, including sequence alignment, primer trimming and consensus generation.
  • +
  • Recognise the differences between Illumina and Nanopore pipelines.
  • +
  • Apply the nf-core/viralrecon Nextflow pipeline to generate a consensus sequence from Illumina and Nanopore data.
  • +
+
+
+ +
+

4.1 SARS-CoV-2 Consensus Assembly

+

As we discussed earlier in the course, the starting material for sequencing SARS-CoV-2 samples from infected patients is PCR-amplified DNA generated with a panel of primers that covers the whole SARS-CoV-2 genome (for example the primers developed by the ARTIC network). This material can then be sequenced using either Illumina or Nanopore platforms.

+

Although different sotware tools are used depending on which kind of sequencing platform was used, the main goal is the same: to align the sequencing reads to the reference genome, and identify any DNA changes (SNPs or Indels) relative to the reference genome (Wuhan-Hu-1). This is called consensus assembly, since we are assembling the genome of our sample from the PCR-amplified fragments and generating a consensus sequence based on changes present in several reads covering a particular position of the genome.

+

The general data processing steps are:

+
    +
  • Filter high-quality sequencing reads.
  • +
  • Map the reads to the Wuhan-Hu-1 reference genome.
  • +
  • Trim the primers from the aligned reads based on the primer location file (BED file).
  • +
  • Perform variant calling (SNPs and indels) to identify changes relative to the reference sequence.
  • +
  • Generate a consensus sequence for the sample based on those variants.
  • +
+
+
+

+
Overview of the consensus assembly procedure from amplicon sequencing reads. In this schematic, each read spans the whole length of a PCR amplicon, which is what is expected from Nanopore reads. With Illumina data, there would be two pairs of reads starting at each end of the PCR amplicon.
+
+
+
+
+
+ +
+
+Primer trimming +
+
+
+

Primer trimming is a key step of the data processing, otherwise SNPs might be missed at the primer sites, on the final consensus sequence. This is because the primer sequence is retained during PCR instead of the original sequence of the sample. Because the PCR amplicons overlap with each other, we can trim the primers from each read and do variant calling after trimming. An example of this is shown in the Figure above.

+ +
+
+
+
+

4.2 Bioinformatic Workflows/Pipelines

+

As can already be seen from the brief description above, bioinformatic analyses always involve multiple steps where data is gathered, cleaned and integrated to give a final set of processed files of interest to the user. These sequences of steps are called a workflow or pipeline. As analyses become more complex, pipelines may include the use of many different software tools, each requiring a specific set of inputs and options to be defined. Furthermore, as we want to chain multiple tools together, the inputs of one tool may be the output of another, which can become challenging to manage.

+

Although it is possible to code such workflows using shell scripts, these often don’t scale well across different users and compute setups. To overcome these limitations, dedicated workflow/pipeline management software packages have been developed to help standardise pipelines and make it easier for the user to process their data.

+

Nextflow

+

Two of the most popular workflow software packages are Snakemake and Nextflow. We will not cover how to develop workflows with these packages, but rather how to use an existing workflow to generate consensus sequences from SARS-CoV-2 data.

+
+

Why Use a Standardised Workflow?

+

These are some of the key advantages of using a standardised workflow for our analysis:

+
    +
  • Fewer errors - because the workflow automates the process of managing input/output files, there are less chances for errors or bugs in the code to occur.
  • +
  • Consistency and reproducibility - analysis ran by different people should result in the same output, regardless of their computational setup.
  • +
  • Software installation - all software dependencies are automatically installed for the user using solutions such as Conda, Docker and Singularity (more about these in a later section of the course).
  • +
  • Scalability - workflows can run on a local desktop or scale up to run on high performance compute clusters.
  • +
  • Checkpoint and resume - if a workflow fails in one of the tasks, it can be resumed at a later time.
  • +
+
+
+
+

4.3 SARS-CoV-2 Pipeline

+

To generate consensus SARS-CoV-2 genomes from these data, we will use a pipeline that was developed by the Nextflow core team called nf-core/viralrecon (which was itself inspired by a previous pipeline from the Connor Lab). Its objective is to harmonise the assembly of SARS-CoV-2 genomes from both Illumina and Nanopore amplicon sequencing data. It can also work with metagenomic data, which we will not cover in this workshop. This pipeline therefore includes different sub-pipelines, which are launched depending on the type of sequence data we have. Watch the video below to learn more about the development of this pipeline.

+

+ +

+

Generally speaking, Nextflow pipelines are run with the command nextflow run PIPELINE_NAME, where “PIPELINE_NAME” is the name of the pipeline. Pipelines are usually shared in a public repository such as GitHub, and nextflow will automatically download the pipeline if it hasn’t been downloaded already to your computer.

+

Let’s test our pipeline by looking at its help documentation:

+
nextflow run nf-core/viralrecon -r 2.6.0 --help
+

The command should print a long list of options available with this pipeline. For pipelines developed by the Nextflow core team you can also consult the documentation available online, which is easier to read: nf-co.re/viralrecon. This page includes many details about the pipeline: which tools are used in different steps of the data processing, how to use the pipeline for different types of data, a detailed documentation of all the options of the pipeline and explanation of the output files generated by it.

+

Below, we give an overview of the pipelines used for Illumina and Nanopore amplicon data.

+
+
+
+ +
+
+Reference Genome and Primer Locations +
+
+
+

The Wuhan-Hu-1 reference genome sequence and the amplicon primer locations (in BED file format) can all be found on the ARTIC Primer Schemes repository. The pipeline we are using takes care of downloading these files for us automatically, however it can be useful to know where to find them, in case you want to use other tools that require these files.

+
+
+
+ +
+
+

The Illumina sub-workflow is based on several standard bioinformatic tools and, importantly, on the iVar software, which was developed for analysing amplicon-based sequencing data.

+
+
+

+
Schematic of the key steps in the Illumina sub-workflow.
+
+
+

To run the pipeline on Illumina data, we use the following general command:

+
nextflow run nf-core/viralrecon 
+  -r 2.6.0 -profile singularity \
+  --platform illumina \
+  --input SAMPLESHEET_CSV \
+  --outdir OUTPUT_DIRECTORY \
+  --protocol amplicon \
+  --genome 'MN908947.3' \
+  --primer_set artic \
+  --primer_set_version PRIMER_VERSION \
+  --skip_assembly --skip_asciigenome \
+  --skip_pangolin --skip_nextclade 
+

One of the key options is --platform illumina, which makes sure that the correct sub-workflow will be used.

+
+ +Click to see more details about this sub-workflow + +

In summary, the steps performed by the Illumina sub-workflow are:

+
    +
  • Adapter trimming - this consists of trimming (or “cutting”) the sequences to remove low-quality bases and any Illumina adapter sequences that are present in the sequences.
  • +
  • Removing human (host) reads - when doing the sequencing it is possible that many reads are still from human material and this step removes them from the rest of the analysis.
  • +
  • Read mapping - aligning (or mapping) the reads to the Wuhan-Hu-1 reference genome. +
      +
    • The software used for mapping is bowtie2.
    • +
    • The software samtools is used to convert the mapped file to BAM (instead of SAM) and sort the reads by coordinate (which is necessary for downstream steps).
    • +
  • +
  • Trim Primers - primers are removed from the aligned reads using ivar trim (using the primer BED file).
  • +
  • Call variants - identify SNPs and indels using ivar variants.
  • +
  • Annotate variants - the called variants are annotated according to their potential effect on the genes/proteins they are located in. For example, if a mutation introduces a new stop codon, or causes a frameshift.
  • +
  • Make consensus - apply the SNPs and indels from the previous step to the reference FASTA file. +
      +
    • There are two tools that can be used in this step: bcftools consensus (default) or ivar consensus (can be set with the option --consensus_caller ivar).
    • +
  • +
  • Lineage assignment - the consensus sequences are assigned to lineages or clades using the pangolin and nextclade programs. These are two of the main lineage/clade nomenclature systems in use. They also identify variants of concern from the consensus sequences.
  • +
  • Quality control - at several steps in the pipeline different tools are used to collect quality metrics and these are compiled into a report using multiqc.
  • +
+
+
+
+

The nanopore sub-workflow is based on the ARTIC bioinformatics protocol and uses several of the tools from the accompanying artic software package.

+

This sub-workflow is similar to the other nanopore sub-workflow, the main difference is the software used for generating a consensus sequence (medaka instead of nanopolish).

+
+
+

+
Schematic of the key steps in the Medaka sub-workflow.
+
+
+

To run our pipeline on basecalled data (FASTQ files), we use the following command:

+
nextflow run nf-core/viralrecon \
+  -r 2.6.0 -profile singularity \
+  --platform nanopore \
+  --input SAMPLESHEET_CSV \
+  --fastq_dir fastq_pass/ \
+  --outdir OUTPUT_DIRECTORY \
+  --protocol amplicon \
+  --genome 'MN908947.3' \
+  --primer_set artic \
+  --primer_set_version PRIMER_VERSION \
+  --artic_minion_caller medaka \
+  --artic_minion_medaka_model MEDAKA_MODEL \
+  --skip_assembly --skip_asciigenome \
+  --skip_pangolin --skip_nextclade 
+

Some of the key options are:

+
    +
  • --platform nanopore makes sure that the correct sub-workflow will be used.
  • +
  • --artic_minion_caller medaka indicates we want to use the medaka program to do the variant/consensus calling (directly from the basecalled FASTQ files, rather than from the raw signal in the FAST5 files).
  • +
  • --artic_minion_medaka_model specifies the model used by the guppy_basecaller software to do the basecalling. The model name follows the structure {pore}_{device}_{caller variant}_{caller version}. See more details about this in the medaka models documentation. Note: for recent versions of Guppy (>6) there is no exact matching model from medaka. The recommendation is to use the model for the latest version available; a list of supported models can be found on the medaka GitHub repository.
  • +
  • --fastq_dir specifies the directory containing the FASTQ files. This directory should contain sub-directories for each barcoded sample following the naming convention barcodeXXXX (where X is a number between 0 and 9). By default, the guppy_basecaller software from Nanopore generates a folder called “fastq_pass” which follows this convention.
  • +
+
+ +Click to see more details about this sub-workflow + +

In summary, the steps performed by the Medaka sub-workflow are:

+
    +
  • Aggregate reads from each sequencing barcode (when multiple files are availble for each barcode)
  • +
  • Run the artic minion tool, which internally does several steps: +
      +
    • Map reads to the reference genome using minimap2 (can be changed to use bwa mem with the option --artic_minion_aligner bwa).
    • +
    • Trim primers from the aligned reads based on the known primer positions in the BED file (using a custom python script called align_trim.py).
    • +
    • Call consensus sequences and SNP/indel variants using medaka consensus and medaka variant: +
        +
      • Positions with less than 20x depth are treated as missing data and converted to the ambiguous base ‘N’. It is not advised to go below this threshold as the models used to call variants do not perform as well.
      • +
    • +
  • +
  • Annotate variants - the called variants are annotated according to their potential effect on the genes/proteins they are located in. For example, if a mutation introduces a new stop codon, or causes a frameshift.
  • +
  • Lineage assignment - the consensus sequences are assigned to lineages or clades using the pangolin and nextclade programs. These are two of the main lineage/clade nomenclature systems in use. They also identify variants of concern from the consensus sequences.
  • +
  • Quality control - at several steps in the pipeline different tools are used to collect quality metrics and these are compiled into a report using multiqc.
  • +
+
+
+
+

The nanopore sub-workflow is based on the ARTIC bioinformatics protocol and uses several of the tools from the accompanying artic software package.

+

This sub-workflow is similar to the other nanopore sub-workflow, the main difference is the software used for generating a consensus sequence (nanopolish instead of medaka).

+
+
+

+
Schematic of the key steps in the Nanopolish sub-workflow. (Under development)
+
+
+
+ +Click to see more details about this sub-workflow + +

In summary, the steps performed by the Nanopolish sub-workflow are:

+
    +
  • Filter reads to ensure they pass minimum read length thresholds: +
      +
    • minimum length 400bp (can be changed with --min_length option)
    • +
    • maximum length 700bp (can be changed with --max_length option)
    • +
  • +
  • Run the artic minion tool, which internally does: +
      +
    • Read alignment to reference genome using minimap2 (can be changed to use bwa mem with the --bwa option).
    • +
    • Trim primers from the aligned reads (based on the known primer positions in the BED file).
    • +
    • Call consensus sequences and variants using nanopolish variants if using signal-level FAST5 files. +
        +
      • Positions with less than 20x depth are assigned the ambiguous base ‘N’. It is not advised to go below this threshold as the models used to call variants do not perform as well.
      • +
    • +
  • +
  • Annotate variants - the called variants are annotated according to their potential effect on the genes/proteins they are located in. For example, if a mutation introduces a new stop codon, or causes a frameshift.
  • +
  • Lineage assignment - the consensus sequences are assigned to lineages or clades using the pangolin and nextclade programs. These are two of the main lineage/clade nomenclature systems in use. They also identify variants of concern from the consensus sequences.
  • +
  • Quality control - at several steps in the pipeline different tools are used to collect quality metrics and these are compiled into a report using multiqc.
  • +
+
+

To run our pipeline on signal-level data (FAST5 files), we use the following command:

+
nextflow run nf-core/viralrecon \
+  --input SAMPLESHEET_CSV \
+  --outdir OUTPUT_DIRECTORY \
+  --protocol amplicon \
+  --genome 'MN908947.3' \
+  --primer_set artic \
+  --primer_set_version PRIMER_VERSION \
+  --skip_assembly \
+  --platform nanopore \
+  --fastq_dir fastq_pass/ \
+  --fast5_dir fast5_pass/ \
+  --sequencing_summary sequencing_summary.txt \
+  -profile conda,singularity,docker
+

Some of the key options are:

+
    +
  • --platform nanopore makes sure that the correct sub-workflow will be used.
  • +
  • --fastq_dir specifies the directory containing the FASTQ files generated by the guppy_basecaller program (this is the standard software from Nanopore that processes the raw signal data from the sequencing device). This directory should contain sub-directories for each barcoded sample following the naming convention barcodeXXXX (where X is a number between 0 and 9). By default, guppy_basecaller generates a folder called “fastq_pass” which follows this convention.
  • +
  • --fast5_dir specifies the directory containing the FAST5 files generated by guppy_basecaller. This directory follows the same naming convention as above and is usually in a folder called “fast5_pass”.
  • +
  • --sequencing_summary is a path to the “sequencing_summary.txt” text file generated by guppy_basecaller.
  • +
+
+
+
+

Apart from the specific options used by each sub-workflow, there are some general options that are used:

+
    +
  • --input specifies a CSV file with details about our samples. The format of this file depends on the specific sub-workflow you are using. See the details in the samplesheet documentation page.
  • +
  • --outdir specifies the output directory to store all our results.
  • +
  • --protocol amplicon sets the pipeline for PCR amplicon data (the other option is --protocol metagenomic, which we do not cover in this course).
  • +
  • --genome 'MN908947.3' this is the standard name of the Wuhan-Hu-1 reference genome.
  • +
  • --primer_set artic at the moment only “artic” primers are available by default. It is possible to use custom primers with the Illumina workflow (see details here).
  • +
  • --primer_set_version the version of the ARTIC primer scheme used. The viralrecon primer config file indicates the available primer shemes are: 1, 2, 3, 4, 4.1, 5.3.2 and also 1200 (the 1200bp amplicon protocol, also known as “midnight”).
  • +
  • --skip_assembly this is used to skip de-novo assembly of the genome. This step is unnecessary in amplicon protocols, which instead rely on mapping reads to the reference genome (reference-based assembly). De-novo assembly is necessary for metagenomic protocols.
  • +
  • --skip_pangolin and --skip_nextclade is used to skip the lineage assignment. The reason is that viralrecon does not use the latest version of the SARS lineage databases, so we skip this step for now, and run it separately in a later step of the analysis.
  • +
+

There are two more generic options we used:

+
    +
  • -r 2.6.0 indicates the version of the pipeline we want to use. It’s always good to check what the latest version is on the viralrecon website. -profile singularity indicates how we want to manage the software required by the pipeline. In our case, we are using a software called Singularity, which creates a “virtual operating system” (called a container) where all the necessary software is run from. This ensures that all of the software is automatically installed and runs on any Linux computer.
  • +
+
+
+
+ +
+
+Conda, Singularity, Docker? +
+
+
+

Generally speaking, workflow management software such as Nextflow or Snakemake support three solutions for managing software dependencies:

+
    +
  • Docker is a software that allows to package a small virtual operating system (or a “container”) containing all the software and data necessary for running an analysis.
  • +
  • Singularity also creates software containers, similarly to Docker. However, it can more easily interface with the user’s filesystem, without the need to have special permissions.
  • +
  • Conda is a package manager, also very popular in bioinformatics. Instead of creating virtual OS containers, Conda instead creates software environments (think of them as directories) where all the software is locally installed, including all its dependencies. The use of individual environments ensures that software packages with incompatible dependencies can still be used within the same pipeline.
  • +
+

Of the three, Singularity is the recommended choice, although Conda is also a good alternative.

+
+
+
+

4.3.1 Running the Workflow

+

Let’s see an example in action by using some example data. If you go to the directory uk_illumina/ in the course materials, you will find several FASTQ files in the data directory. There is also a shell script (scripts/run_illumina_workflow.sh) that contains the commands we will use to run the workflow on these data.

+

Opening the script, we can see the following commands:

+
# create output directory
+mkdir results
+
+# run the workflow
+nextflow run nf-core/viralrecon \
+  -r 2.6.0 -profile singularity \
+  --platform illumina \
+  --input samplesheet.csv \
+  --outdir results/viralrecon \
+  --protocol amplicon \
+  --genome 'MN908947.3' \
+  --primer_set artic \
+  --primer_set_version 3 \
+  --skip_assembly --skip_asciigenome \
+  --skip_pangolin --skip_nextclade
+

It first creates a results directory (to store our output files) and then runs the nextflow command using the Illumina sub-workflow. We could run these commands one at a time by copy/pasting them to the terminal. Or alternatively, we can run the entire script using bash scripts/run_illumina_workflow.sh

+

When you start running the workflow, you will get a list of the workflow steps and their progress. This may take quite a while to run, depending on the computer resources you have available. Once the workflow is complete, you should see a message similar to the following:

+
-[nf-core/viralrecon] 8/8 samples passed Bowtie2 1000 mapped read threshold:
+    280038: GB16
+    2946614: GB28
+    252871: GB09
+    3269412: GB21
+    103742: GB23
+    3016474: GB36
+    ..see pipeline reports for full list
+-
+-[nf-core/viralrecon] Pipeline completed successfully-
+Completed at: 18-May-2022 08:08:25
+Duration    : 1h 13m
+CPU hours   : 8.1
+Succeeded   : 343
+

You should also get several output files in the results folder specified with our nextflow command. We will detail what these files are in the next section.

+
+
+
+ +
+
+Running the pipeline on our training computers +
+
+
+

Our training computers don’t have the high specifications needed for routine bioinformatic analysis, so the Illumina pipeline takes up to 1h to complete.

+

We provide already pre-processed results for 48 samples in the folder uk_illumina/preprocessed, which you can use to follow the next section.

+
+
+
+
+
+

4.4 Exercises

+
+
+
+ +
+
+Running nf-core/viralrecon: ONT data +
+
+
+
+
+
+
+

Go to the course materials directory india_nanopore. This contains Nanopore sequencing data for several samples collected in India. Nanopore data is organised in directories named as barcodeXX (where XX is a number) - this is the standard output from the Guppy software used to do basecalling and generate FASTQ files.

+

The Nanopore Medaka workflow expects to be given as an input a CSV file with two columns: sample name and barcode number. We already provide this file in samplesheet.csv.

+

Your task now is to process these samples using the nf-core/viralrecon pipeline:

+
    +
  • Using nano, open the script found in scripts/run_medaka_workflow.sh.
  • +
  • Fix the code in the script where you see the word “FIXME”: +
      +
    • Output the results to a directory called results/viralrecon/.
    • +
    • The input sample sheet is in the file samplesheet.csv (check the pipeline documentation to review what the format of this samplesheet should be for the Nanopore pipeline).
    • +
    • The FASTQ files are in a folder data/fastq_pass.
    • +
  • +
  • Run the script using bash. This may take ~15 minutes to complete.
  • +
+
+ +
+
+
+
+

We can open the script with Nano using the command:

+
nano scripts/run_medaka_workflow.sh
+

The fixed code is:

+
# create output directory
+mkdir -p results
+
+# run the workflow
+nextflow run nf-core/viralrecon \
+  -r 2.6.0 -profile singularity \
+  --max_cpus 8 --max_memory "24.GB" \
+  --input samplesheet.csv \
+  --outdir results/viralrecon \
+  --platform nanopore \
+  --protocol amplicon \
+  --genome 'MN908947.3' \
+  --primer_set artic \
+  --primer_set_version 3 \
+  --skip_assembly \
+  --fastq_dir data/fastq_pass/ \
+  --artic_minion_caller medaka \
+  --artic_minion_medaka_model r941_min_high_g360
+

What we did to fix the code was:

+
    +
  • Set --input as samplesheet.csv, which is the CSV file with two columns: sample name and Nanopore barcode number. The format of this file is detailed in the nf-core/viralrecon documentation.
  • +
  • Set --outdir as results/viralrecon/, which is the directory where we want to output our results.
  • +
  • Set --fastq_dir as data/fastq_pass, which is the directory containing the FASTQ file folders named as barcodeXX (where XX is a number). This is the standard output from the Guppy software used to do basecalling of Nanopore data to produce FASTQ files.
  • +
+

When we finish editing the file we can hit Ctrl + X to exit Nano. Before it closes it will ask us if we want to save the file and we can type “Y” for yes.

+

To run the script we can do bash scripts/run_medaka_workflow.sh. After the workflow completes, we get a message similar to this one:

+
-[nf-core/viralrecon] Pipeline completed successfully-
+Completed at: 18-May-2022 08:08:25
+Duration    : 13m 22s
+CPU hours   : 1.6
+Succeeded   : 167
+

Note that the exact time that the workflow takes to run may differ from what we show here. The time depends on how many samples you are processing and how big the computer you are using is.

+
+
+
+
+
+
+
+
+
+
+
+
+

4.5 Summary

+
+
+
+ +
+
+Key Points +
+
+
+
    +
  • The main steps to generate SARS-CoV-2 consensus sequences are: filter high-quality reads, map reads to reference genome, trim PCR primers and variant/mutation calling, which is finally used to produce a consensus sequence.
  • +
  • Other steps that can be done include annotating the variants/mutations (what their effects in each gene might be) and assigning sequencences to known lineages/clades.
  • +
  • Nextflow is a software used for building workflows/pipelines involving multiple tools and data processing steps. Using established pipelines helps with automation, reproducibility, consistency of results and reduces the chances of data processing errors.
  • +
  • The nf-core/viralrecon pipeline implements the steps to generate SARS-CoV-2 consensus sequences from Illumina or Nanopore data.
  • +
  • The command nextflow run nf-core/viralrecon is used to run the pipeline, using specific options depending on the data we have.
  • +
+
+
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/02-isolates/02-qc.html b/materials/02-isolates/02-qc.html new file mode 100644 index 0000000..1aca693 --- /dev/null +++ b/materials/02-isolates/02-qc.html @@ -0,0 +1,1061 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

5  Quality Control

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Learning Objectives +
+
+
+
    +
  • Interpret and critically evaluate data quality reports from the assembled sequences and identify sequences suitable for downstream analyses.
  • +
+
+
+
+

5.1 Output Files

+

After running our pipeline, we get several output directories and files. The directories we get depend on which version of the workflow was used (Illumina or Nanopore). The description of the output is detailed in the pipeline documentation. Although there are many output files, most of these contain results that are aggregated in an interactive MultiQC report, which makes their analysis easier. We highlight some of the main files of interest below.

+
+ +
+
+
    +
  • The file multiqc/multiqc_report.html contains a MultiQC quality and analysis report for the consensus assemblies generated by the pipeline.
  • +
  • The folder variants/bowtie2/ contains individual BAM files, which can be visualised with IGV if we want to look at the reads mapped to the reference genome.
  • +
  • The folder variants/ivar/consensus/bcftools contains individual FASTA files (named *.consensus.fa) for each sample’s consensus genome sequence.
  • +
  • The file variants/ivar/variants_long_table.csv contains a table with the aggregated results of all the variants detected in each sample.
  • +
+
+
+
    +
  • The file multiqc/medaka/multiqc_report.html contains a MultiQC quality and analysis report for the consensus assemblies generated by the pipeline.
  • +
  • The folder medaka/ contains: +
      +
    • Individual BAM files (named *.primertrimmed.rg.sorted.bam), which can be visualised with IGV if we want to look at the reads mapped to the reference genome.
    • +
    • Individual FASTA files (named *.consensus.fasta) for each sample’s consensus genome sequence.
    • +
    • A file called variants_long_table.csv with a table of all the variants detected in each sample.
    • +
  • +
+
+
+
+
+
+

5.2 Quality Control

+

The viralrecon pipeline produces many quality control metrics, which are conveniently compiled in an interactive report with MultiQC, as mentioned above. We will not detail here every section of the report (check the pipeline documentation for a full description), but only highlight some of the sections that can be used for a first assessment of the quality of our samples.

+
+

5.2.1 Variant Calling Metrics

+

The first section or the report - Variant Calling Metrics - contains a summary table with several statistics for each sample, including the total number of reads, the number/percentage of reads mapped to the reference genome, the depth of coverage, the number of SNPs (single-nucleotide polymorphisms) and INDELs (insertions/deletions), the number of missense variants (mutations that result in an aminoacid change) and the fraction of ambiguous bases ‘N’ per 100kb.

+

Looking at these basic metrics gives us a good first idea of whether some of the samples have a high fraction of missing bases (marked as N in the FASTA file), leading to a poorer assembly. We can quickly check this by sorting the table by the column named “# Ns per 100kb consensus” (you can convert the values in this column to a percentage by dividing the numbers by 100). There is also the ability to produce simple scatterplots from the data in this table, which can be useful to look at the relationship between the different metrics (see an example in the figure below).

+

This table also contains information about the lineage/clade assigned to each sample by the programs Pangolin and Nextclade. This gives us an idea of which samples may be more similar to each other, and where they fit in the global context of other sequences available publicly. We will talk more about this topic in the lineage assignment section.

+
+
+
+ +
+
+Lineage Versions +
+
+
+

Although the Viralrecon pipeline runs Pangolin and Nextclade to perform lineage assignment, it does not use the latest version of these programs (because lineages evolve so fast, the nomenclature constantly changes). Therefore, this information should mostly be ignored at this stage, and instead we should run our analysis on the most up-to-date versions of these programs. We will detail this in the Lineages and Variants section of the materials.

+
+
+
+
+

+
Snapshot of the “Variant Metrics” section of the viralrecon MultiQC report. Simple scatterplots can be made from the data on this table using the Plot button. For example, at the bottom we show a scatterplot showing the relationship between the median depth of coverage and the number of ambiguous bases ‘N’ per 100kb. For the data in this example, we can see that when the average depth of coverage drops below around 200 reads we start getting higher number of missing bases in the assembly.
+
+
+
+
+
+ +
+
+Terminology Alert! +
+
+
+

The word coverage is sometimes used in an ambiguous way by bioinformaticians. It can mean two things:

+
    +
  • The number of reads aligning to a position of the genome. This is sometimes called sequencing depth or depth of coverage (we will use these terms in the materials to avoid confusion). For example, we may say that the average depth of coverage of the genome is 20x, meaning that on average there are 20 reads aligned to a position of the genome.
  • +
  • The fraction of a genome that has a sufficient number of reads for analysis. For example, if 90% of the genome has a sufficient number of reads to be analysed (for example, at a threshold of 10x), we would say it has 90% coverage (we “covered” 90% of the genome with enough data). In the context of genome assembly, we sometimes use the word “coverage” to refer to the percentage of the consensus genome without ambiguous ‘N’ bases.
  • +
+

In the viralrecon MultiQC report the word “coverage” is used to mean “depth of coverage”.

+
+
+
+
+

5.2.2 Amplicon Depth of Coverage

+

The next section of the report - Amplicon Coverage Heatmap - contains a graphical representation of the average depth of coverage for each PCR amplicon in the ARTIC protocol (i.e. the average number of reads mapped to each amplicon interval). This plot is extremely useful to identify common PCR dropouts occurring across many samples. This may be an indication that our PCR primer scheme is not working for some of the forms of the virus circulating in our population (for example due to mutations that accumulate in the primer site).

+
+
+

+
Example heatmap showing the depth of coverage for a set of samples. The failure of an amplicon to be sequenced in several samples can be seen as a “column” of dark cells in the heatmap.
+
+
+

We can investigate primer dropout in more detail, for example by looking at the BAM files with mapped reads using IGV.

+

From our heatmap (shown in the Figure above) we can see one of the PCR fragments - ncov-2019_83 - seems to have poor amplification across many of our samples. Let’s investigate this in more detail by looking at the alignment file:

+
    +
  • Open IGV and go to File → Load from file….
  • +
  • In the file browser that opens go to the folder results/viralrecon/variants/bowtie2/ and select the file GB36.ivar_trim.sorted.bam to open it.
  • +
  • Go back to File → Load from file… and this time load the BED files containing the primer locations. These can be found in resources/primers/artic_primers_pool1.bed and resources/primers/artic_primers_pool2.bed.
  • +
+

Once you have IGV open, you can navigate to the region where this amplicon is located by searching for the name of one of the primers - “ncov-2019_83_LEFT” - in the search box at the top. By zooming in to the region where this primer is located, we can see there is a mutation right at the start of this primer, which suggests that this may be the reason why this PCR fragment fails to amplify in this sample.

+
+
+

+
Screenshot from the IGV program showing an example of a region with PCR amplicon dropout. This is shown by a lack of reads mapped to this region of the genome. Because the PCR fragments from the ARTIC protocol overlap between the two pools, we can see if there are mutations occurring at the primer sites. In this example, a mutation in the left primer of amplicon 83 suggests this may be the reason for the dropout in this sample.
+
+
+
+
+
+

5.3 Mutation/Variant Analysis

+

One of the output files produced by the pipeline is a table SNP and indel variants detected in our samples saved as a CSV file in variants/ivar/variants_long_table.csv. This table can be useful to investigate if particular mutations are particularly frequent in our samples and what their likely effects are.

+
+
+

+
Variants table output by viralrecon, with a summary of the main columns of interest. Note that this table also includes a column with lineage assignment (not shown in this snapshot). Remember that at this stage we mostly ignore this column, as viralrecon does not use the most up-to-date version of the lineage databases.
+
+
+

The columns in this table are:

+
    +
  • SAMPLE is the name of our sequenced sample.
  • +
  • CHROM is the name of the “chromosome”, which in our case is just the name of the reference genome (“MN908947.3”).
  • +
  • POS is the position of the genome where the mutation occurs.
  • +
  • REF is the reference nucleotide.
  • +
  • ALT is the alternative nucleotide, that is the nucleotide that was observed in our sample.
  • +
  • FILTER indicates whether the SNP passed quality filters from the variant calling software (“PASS”) or whether some other issue was observed (for example “dp” means that the depth of sequencing was unusually high or low).
  • +
  • DP is the total depth of sequencing at this position, meaning how many reads in total were aligned there.
  • +
  • REF_DP is the depth of sequencing of the reference allele, meaning how many reads contained the reference nucleotide.
  • +
  • ALT_DP is the depth of sequencing of the alternative allele, meaning how many reads contained the alternative nucleotide.
  • +
  • AF is the allele frequence of the alternative allele, meaning the proportion of reads that contained the alternative nucleotide (this column is equivalent to ALT_DP/(ALT_DP + REF_DP)).
  • +
  • GENE is the name of the gene in the SARS-CoV-2 annotation.
  • +
  • EFFECT this is the predicted effect of the mutation in the gene. This output comes from the snpeff software and uses The Sequence Ontology nomenclature (follow the link to search for each term).
  • +
  • HGVS_C, HGVS_P and HGVS_P_1LETTER is the DNA or amino acid change using HGVS nomenclature. For example, “c.1112C>T” means a C changed to a T at position 1112 of the genome; and “p.Pro371Leu” would mean that a Proline changed to a Leucine at position 371 of the respective protein.
  • +
  • CALLER is the software used for variant calling.
  • +
  • LINEAGE is the Pangolin lineage that the sample was assigned to. Note that this column should usually be ignored, since viralrecon doesn’t use the latest Pangolin data version by default.
  • +
+

This table can be opened in a spreadsheet program such as Excel to look at particular features. In terms of quality-control, we can filter the table:

+
    +
  • for mutations with intermediate allele frequency (say < 70%) – if a sample has too many such mutations, that could indicate cross-contamination between samples.
  • +
  • for frameshift mutations – these mutations should be rare because they are highly disruptive to the functioning of the virus, and their occurrence is more often due to errors. The presence of these mutations are usually not critical for downstream analysis (such as lineage assignment and phylogenetics), but we should make a note that these mutations may be false-positives in our data.
  • +
+

This variants/mutations table can also be used to explore reasons for amplicon dropout. For example, we can identify how many samples contain the mutation we previously saw in the “ncov-2019_83_LEFT” primer (we saw this was in position 25,003 of the genome). We can do this by sorting our table by the column “POS” (position) and then scrolling down to find the samples with this mutation. We will find that only 3 of the samples contain this mutation, suggesting some other additional causes are leading to the dropout in this PCR fragment.

+

Finally, for the Illumina pipeline (--platform illumina), it is important to note that not all of the mutations on this table are included in the final consensus. Only mutations with allele frequency >75% (AF >= 0.75) and minimum depth of 10 (DP >= 10) are retained in the final consensus. Therefore, to see which mutations are actually present in our consensus sequences, we need to filter this table for these criteria. Again, we note that this only applies to the Illumina pipeline. For the Nanopore pipeline (--platform nanopore) all the mutations included in this table are retained in the consensus sequence.

+
+
+
+ +
+
+ORF1b Annotation Issues +
+
+
+

The annotation of the ORF1b gene had some changes between the first original assembly and newer versions of this annotation. In particular, there is a frameshift that is not considered in the annotation used by most software, and this causes a discrepancy in the results between viralrecon and other tools such as Nextclade (which we will cover in the next section).

+

This issue is under investigation by the developers of viralrecon and may be corrected in future versions of the pipeline.

+

For now, the advice is to ignore the variant effects of ORF1b as these correspond to an older version of the annotated gene.

+
+
+
+
+
+ +
+
+Keep Original Files Intact +
+
+
+

When you analyse the results of a CSV file in a spreadsheet software (such as Excel), it may be a good idea to create a copy of your file to avoid accidentally modifying the original table. For example, if you accidentally sort one column of the table but forget to sort other columns, of if you accidentally filter the results and delete some of the rows from the table, etc.

+

You can save a copy of the file by going to File → Save As…. You may want to save the copy as an Excel file format, to include graphs and colours. (Remember that the CSV format is a plain text file - it does not support graphs or coloured cells.)

+
+
+
+
+

5.4 Cleaning FASTA Files (Optional)

+

To proceed with our analysis, we need a FASTA file containing all of our consensus sequences. However, our viralrecon pipeline outputs separate FASTA files for each sample. We can see this by running (from within the uk_illumina/ directory):

+
ls results/viralrecon/variants/ivar/consensus/bcftools/
+

Also, the workflow modifies our original sample names in the FASTA file, by adding extra information to the sequence name. For example:

+
head -n 1 results/viralrecon/variants/ivar/consensus/bcftools/ERR5728910.consensus.fa
+
>ERR5728910 MN908947.3
+

What we want to do is clean these sample names, so that we end up with:

+
>ERR5728910
+

We also want to make sure to combine all the samples into a single FASTA file.

+

We can the command cat to combine (concatenate) the individual files and the sed command to substitute text and clean our sample names. Let’s do this step by step.

+

First, we can use the * wildcard to combine all the FASTA files with the cat command:

+
cat results/viralrecon/variants/ivar/consensus/bcftools/*.fa
+

Running this command will print all of the sequences on the screen! To see what happened a little better, we could pipe this command to less to browse up-and-down through the file:

+
cat results/viralrecon/variants/ivar/consensus/bcftools/*.fa | less
+

We could also check that we now have all our samples combined, we could pass the results to grep and search for the word >, which in the FASTA format indicates the sequence name:

+
cat results/viralrecon/variants/ivar/consensus/bcftools/*.fa | grep ">" | wc -l
+

This should give us 7 as the result (which makes sense, since we have 7 samples).

+

We can now proceed with cleaning the names of the sequences, by using sed:

+
cat results/viralrecon/variants/ivar/consensus/bcftools/*.fa | sed 's| MN908947.3||' > results/clean_sequences.fa
+

Notice that in this last command we make sure to redirect the result to a new file using >.

+
+
+

5.5 Missing data intervals

+

When we align reads to the reference genome, there may be regions that we were not able to sequence (e.g. due to amplicon dropout, discussed above) and therefore cannot tell what the base sequence in those positions is. In those cases, the viralrecon pipeline includes the missing character ‘N’ in the sequence.

+

As a further quality check, it is useful to check how many contiguous intervals of missing bases we have in each of our assemblies, as well as how long those intervals are. For example, for this small sequence:

+
T A N N N G C T N N A T
+

We have two missing intervals, from positions 3-5 and from positions 9-10.

+

To obtain a list of missing intervals in a sequence, we can use the software seqkit, which is a toolkit of commands to do many operations on FASTA/FASTQ files (check out its documentation). In particular, we can use the following command:

+
seqkit locate -i -P -G -M -r -p "N+" results/clean_sequences.fa
+
seqID  patternName  pattern  strand  start  end
+GB09   N+           N+       +       1      342
+GB09   N+           N+       +       9246   9502
+GB09   N+           N+       +       10738  11331
+GB09   N+           N+       +       21428  21543
+GB09   N+           N+       +       27400  27462
+GB09   N+           N+       +       27618  27644
+GB09   N+           N+       +       29827  29893
+
+... more output omitted...
+

The output is a tabular file specifying the consensus sequence name (first column) and the location of intervals in the genome where the pattern of one or more “N” were found.

+

To see what all the options we used with this command are, see the tool’s documentation.

+
+
+

5.6 Exercises

+
+
+
+ +
+
+Quality control for India (ONT) samples +
+
+
+
+
+
+
+

Now it’s your turn to check the quality of the assembled genomes from our India samples, sequenced using Nanopore. For this exercise, we will use the results produced by viralrecon on 48 pre-processed samples:

+
    +
  1. Use the file explorer to open the folder preprocessed, and then: +
      +
    • Open the file in pipeline_info/execution_report_DATE.html (where “DATE” is the date when the files were processed).
    • +
    • How long did the workflow take to run?
    • +
    • Which step of the pipeline took the longest to run?
    • +
  2. +
  3. Open the MultiQC report found in preprocessed/multiqc/medaka/multiqc_report.html and answer the following questions: +
      +
    • Produce a plot between median depth of coverage (x-axis) and #Ns (y-axis). What do you think is the average coverage needed for <10% of missing data.
    • +
    • Were there any PCR amplicons with a dropout (i.e. very low depth of coverage) across multiple samples?
    • +
    • Do any of these dropout amplicons coincide with gene “S” (Spike protein gene)? Note: you can see the ARTIC PCR primer location from the online repository and the gene locations in the SARS-CoV-2 NCBI genome browser.
    • +
  4. +
  5. Based on any amplicons that you identified in the previous question, find if there are any mutations coinciding with their primer locations. You can use the mutation variant table in preprocessed/medaka/variants_long_table.csv.
  6. +
  7. With the mutation variants table open in a spreadsheet program (LibreOffice on our training machines): +
      +
    • Use the filter button to look for mutations in gene “S” (encodes for the Spike protein).
    • +
    • Identify the samples and positions of mutations of the type “disruptive_inframe_deletion”.
    • +
    • Using IGV, open the BAM file for one of those samples. Note: BAM files are located in preprocessed/medaka/SAMPLE.primertrimmed.rg.sorted.bam (were ‘SAMPLE’ is the sample name).
    • +
    • Go to the location of those mutations. From the read alignment, how confident are you about it? What could you do to confirm it?
    • +
  8. +
+
+ +
+
+
+
+

Question 1

+

We open the file found in preprocessed/pipeline_info/execution_report_2022-05-04_12-41-12.html, which contains information about the pipeline that was run on a set of 48 samples. We can see at the top of the report that it took ~15 minutes to run. This report also gives us information about how long each individual step of the pipeline took the longest to run (and other information such as which used more CPU or RAM memory). For example, in the section “Job Duration” we can see a graph that looks like this:

+

+

This indicates that the step running the artic minion tool takes the longest. This is not surprising as this is the step where most of the work is happening (mapping, primer trimming and making a consensus sequence). You can revise the steps of the workflow in the respective section above.

+
+

Question 2

+

When opening the MultiQC report, we can see the first section contains a table with several statistics about the quality of each sample. We can produce a plot from this table, by pressing the “Plot” button above the table. The plot we were asked for is the following:

+
+
+

+
This plot can be produced by clicking the Plot button on top of the table and then selecting the variables “Coverage median” and “# Ns per 100kb consensus”.
+
+
+

The y-axis of the plot tells us the number of ‘N’ per 100kb of sequence. If we divide those numbers by 100 we get a percentage and from these samples it seems like a median depth of coverage of around 200 reads generates samples with less than 10% of missing bases.

+

From the section of the report “Amplicon coverage heatmap” we can see the average depth of coverage for each PCR amplicon from the ARTIC protocol. There are 3 amplicons in particular that have very low depth of coverage: nCoV-2019_51, nCoV-2019_62 and nCoV-2019_95.

+

We can check the ARTIC V3 primer BED file online to look at the location of these primers, or we could look for it using command-line tools. Here is an example:

+
cat resources/primers/artic_version3_pool*.bed | grep "_51_"
+
MN908947.3      15171   15193   nCoV-2019_51_LEFT       1       +
+MN908947.3      15538   15560   nCoV-2019_51_RIGHT      1       -
+

For this example, we can see the amplicon is between positions 15171 - 15560 of the genome. Looking at the SARS-CoV-2 NCBI genome browser, we can see this coincides with ORF1ab. If we do the same analysis for the other primers, we will see that none of them coincides with gene S.

+
+

Question 3

+

To investigate whether this amplicon dropout is due to mutations in the primer regions we can open the mutations table preprocessed/medaka/variants_long_table.csv. If we sort the table by the column “POS”, we can scroll to the location of each primer pair.

+

We can see that in only one case there is a mutation coinciding one of the primers: in sample “IN33” position 28687 there is a C > T mutation, which coincides with primer nCoV-2019_95_LEFT. However, this only occurs for one of the samples, suggesting the reason for the PCR dropout might be related to other factors.

+
+

Question 4

+

After filtering our table for gene S (Spike gene), we can see only a couple of mutations of type “disruptive_inframe_deletion” in some of the samples at positions 21764 and 21990. To look at the reads with this mutation, we open the BAM file for one of these samples in IGV (for example sample IN22):

+
    +
  • Go to File → Load from file….
  • +
  • In the file browser that opens go to the folder preprocessed/medaka and select the file IN22.primertrimmed.rg.sorted.bam to open it.
  • +
  • In the search box we can type “NC_045512.2:21990” which will zoom-in on the region around the deletion.
  • +
+

From looking at the reads aligned to this position of the genome, we can see several of them containing a 3bp deletion. However, we can also see some reads that do not contain the deletion, and others that seem to be mis-aligned. If we were interested in confirming this mutation, we could do an independent PCR followed by Sanger sequencing, for example.

+

Although this was not part of the question, it is also worth noting that the primer “nCoV-2019_73_LEFT” starts very near this deletion. In the MultiQC report, we can see that this PCR amplicon had very poor amplification in this sample. One possibility is that the deletion interfered with the primer efficiency in this sample.

+

Generally, we don’t need to confirm every single mutation obtained from our analysis. But if we see a mutation occurring many times, then it may be worth further investigation, especially if it is disruptive and in an important gene such as the Spike gene.

+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+Clean FASTA file: ONT data +
+
+
+
+
+
+
+

In this exercise we will create a clean FASTA file for the samples collected in India.

+

In this case, the output FASTA files are in the folder results/viralrecon/medaka and have the file extension .fasta. If we look at one of the files:

+
head -n 1 results/viralrecon/medaka/IN42.consensus.fasta
+
>IN42/ARTIC/medaka MN908947.3
+

We can see that the name has a lot of extra information attached to it. We want to clean the name of the sequences so that the result is:

+
>IN42
+

The following sed command can be used to substitute the text “/ARTIC/medaka MN908947.3” with nothing:

+
sed 's|/ARTIC/medaka MN908947.3||'
+
    +
  • Pipe the tools cat and sed to construct a command that generates a new file called results/clean_sequences.fa containing all the sequences with “clean” sequence names.
  • +
+
+ +
+
+
+
+

The complete code to achieve the desired outcome is:

+
cat results/viralrecon/medaka/*.consensus.fasta | sed 's|/ARTIC/medaka MN908947.3||' > results/clean_sequences.fa
+

Look at our companion Unix course materials for more information about how the sed command works.

+
+
+
+
+
+
+
+
+
+
+
+
+

5.7 Summary

+
+
+
+ +
+
+Key Points +
+
+
+
    +
  • The output of the pipeline includes, among others: +
      +
    • A detailed MultiQC report, including information about genome coverage, the depth of sequencing for each amplicon, as well as other quality metrics that can help to troubleshoot problematic samples.
    • +
    • A table with SNP and indel mutation variants identified in each sample.
    • +
    • Information about SARS-CoV-2 lineages/clades/variants, which is detailed in the next section.
    • +
  • +
+
+
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/02-isolates/03-lineages.html b/materials/02-isolates/03-lineages.html new file mode 100644 index 0000000..f70a3f3 --- /dev/null +++ b/materials/02-isolates/03-lineages.html @@ -0,0 +1,923 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

6  Lineages and variants

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Learning Objectives +
+
+
+
    +
  • Understand variant annotation conventions used by Gisaid, Pango, Nextstrain and WHO and how they relate to each other.
  • +
  • Assign sequences to Pango lineages using pangolin.
  • +
  • Interactively explore mutations in the assembled genomes and their phylogenetic context using Nexstrain’s tools.
  • +
+
+
+ +
+

6.1 SARS-CoV-2 Variants

+

As viruses (or any other organism) evolve, random DNA changes occur in the population, for example due to replication errors. Many of these changes are likely to be neutral, meaning that they do not change the characteristics of the virus in any significant way. Neutral changes tend to drift in the population, increasing or decreasing in frequency in a random way, but most likely end up disappearing due to their low starting frequency in the population.

+

On the other hand, advantageous mutations can occur, which lead to changes in the characteristics of the virus that are beneficial for its spread in the population (e.g. high transmissibility, resistance to immune response or vaccines, etc.). Such beneficial mutations will therefore experience positive selection, potentially leading to their increase in frequency in the population as they spread to more and more hosts.

+

Viruses carrying those advantageous mutations may, over time, aquire other advantageous mutations that further increase their fitness and, therefore, their frequency in the population. One way to visualise this is by looking at a phylogenetic tree showing the relationship between sequences (based on their similarity) and how groups of sequences change over time.

+
+
+

+
Example of global phylogeny from the Nextstrain public server. Colours show different Nextstrain clades. (Screenshot taken Feb 2022)
+
+
+

In the figure above, which shows SARS-CoV-2 samples from across the world, we can see groups of similar sequences rapidly “expanding” at certain points in time. Such groups of sequences, which share a collection of DNA changes, are referred to as SARS-CoV-2 variants (see box below about the ambiguous meaning of this term). In an effort to understand the spread of the virus and monitor situations of increased occurrence of such variants, several groups and institutions have developed a system to classify groups of SARS-CoV-2 sequences as variants of interest and variants of concern.

+

A full explanation and definitions of such variants is given in the World Health Organisation (WHO) variants page The main classification systems currently in use are:

+ +

In practice, there is a big overlap between these different nomenclature systems, with WHO variants having a direct match to Pango lineages and Nextstrain clades. In fact, the different teams work together to try and harmonise the nomenclature used, and thus facilitate the interpretation of sequence analysis.

+

The two most popular systems - Nextclade and Pangolin - have slightly different levels of resolution. Nextclade’s nomenclature system was developed to highlight diversity patterns at a larger scale, allowing discussions of SARS-CoV-2 diversity at a global level and over larger time scales. On the other hand, Pangolin’s nomenclature system is more fine-grained, aiming to follow the dynamics of the pandemic as it unfolds. The two systems are complementary to each other, and our analysis of SARS-CoV-2 sequences should include both tools.

+
+
+
+ +
+
+What is a variant? +
+
+
+

It is important to note that the term variant can be sometimes ambiguous.

+

The term “SARS-CoV-2 variant” usually refers to the WHO definition of variants of concern/interest (e.g. the Alpha, Delta and Omicron variants), which includes sequences containing a collection of several nucleotide changes that characterise that group. According to this definition, we have two variants in the example below (samples 1 & 2 are one variant and samples 3 & 4 another variant).

+

+

However, in bioinformatic sequence analysis, a sequence variant refers to an individual change in the DNA sequence (a SNP or an insertion/deletion). Using this definition, in the example above we have 5 variants: 3 SNPs and 2 indels. In the Consensus Sequence section, we saw that one of our workflow steps was “variant calling”. This was the definition of variant we were using: identifying individual SNPs and/or indels relative to the reference genome, from our sequencing data. This is also reflected in the one of the common file formats used to store SNP/indel information, the VCF file, which means “Variant Call Format”.

+

Sometimes the term “mutation” is used to refer to SNP/indel variants. For example see this definition from the COG consortium.

+

Because of this ambiguity, the terms “lineages” or “clades” are often used instead of “variants” when referring to groups of similar SARS-CoV-2 sequences, because they have a phylogenetic interpretation.

+
+
+
+
+

6.2 Pangolin

+

Pangolin

+

The first tool we will cover is called pangolin and uses the Pango nomenclature system. The main steps performed by this tool are:

+
    +
  • Multiple sequence alignment of our samples against the Wuhan-Hu-1 reference genome, using the minimap2 software.
  • +
  • Assigning our sequences to lineages based on the current global phylogeny. Two methods/software are available: +
      +
    • pangoLEARN (default) uses a pre-trained machine learning model.
    • +
    • UShER uses a more classic parsimony-based method, but highly optimised for working with large numbers of sequences.
    • +
  • +
  • Classifying our sequences according to the WHO nomenclature of variants of interest/concern using the scorpio software.
  • +
+

Although Pangolin can run as part of the nf-core/viralrecon pipeline we used, we recommended to turn this option off. The reason is that the model to classify variants regularly changes over time, as more public sequences become available and the nomenclature rules updated. Therefore, it important to always run the samples through the latest Pangolin version available.

+

Pangolin can be run from the command line, using two steps:

+
    +
  • Updating the data used for lineage/variant classification.
  • +
  • Running the actual lineage assignment step.
  • +
+

On our example data, these would be the commands:

+
# update pangolin data
+pangolin --update-data
+
+# run pangolin
+pangolin --outdir results/pangolin/ --outfile pango_report.csv results/clean_sequences.fa
+
    +
  • The first command downloads the latest version of the lineages and their characteristic mutations from the Pango classification system.
  • +
  • The second command runs the FASTA file of consensus genomes through Pangolin’s classification algorithm. +
      +
    • --outdir is used to define the directory to save the results in.
    • +
    • --outfile is used to give the file a name of our choice.
    • +
  • +
+

The output is a CSV file, with several columns of interest, including WHO variants of concern identified using the Scorpio software. A detailed explanation of the columns of this file is given in the Pangolin documentation page.

+

More information about running Pangolin from the command line can be found in its online documentation.

+
+

6.2.1 Web Application

+

This tool can also be run separately using a web application, which only requires us to provide with a FASTA file of consensus sequences. This may desirable to re-run samples using the latest version of the Pangolin software and SARS-CoV-2 variant databases.

+

The results from the web application can be downloaded as a CSV file, which contains a table similar to the one obtained from our command above (some of the column names are different, but their meaning is the same).

+
+
+
+

6.3 Nextclade

+

Nextstrain

+

Another system of clade assignment is provided by nextclade, which is part of the broader software ecosystem Nextstrain.

+

Nextclade performs similar steps to Pangolin, with some differences in the algorithms that are used:

+
    +
  • Each sequence is aligned with the Wuhan-Hu-1 reference genome using a local alignment algorithm.
  • +
  • Samples are placed in the global phylogeny using a distance-based metric (placing the sequence on the tree where it has the highest similarity with).
  • +
  • Clade assignment is done based on the previous phylogeny placement step.
  • +
+

You can find more details about Nextclade’s methods on its documentation. Nextclade also provides several quality control metrics, which are very useful to identify problematic samples.

+

As we discussed above, the models and clades are regularly updated, so we also skipped this step when we ran the nf-core/viralrecon pipeline. Instead, we can run this tool directly from the command line, by first making sure to download the most up-to-date clade information. Here are the commands:

+
# get nextclade data
+nextclade dataset get --name sars-cov-2 --output-dir resources/nextclade_background_data
+
+# run nextclade
+nextclade run --input-dataset resources/nextclade_background_data/ --output-all results/nextclade results/clean_consensus.fa
+
    +
  • The first command (nextclade dataset get) downloads the latest version of the Nextclade dataset for SARS-CoV-2 (option --name sars-cov-2). We define the directory to store this information with --output-dir.
  • +
  • The next step is to run the actual clade assignment on our data. We use the database from the previous step as --input-dataset, we define a directory to output all the results with --output-all and at the end of the command we give as input our clean FASTA consensus genomes.
  • +
+

More information about running Nextclade from the command line can be found in its online documentation.

+
+

6.3.1 Web Application

+

Nextclade offers an interactive application, which can be used to run its analysis on a FASTA file with sequences:

+
    +
  • Go to nextclade.org.
  • +
  • Click Select a file to browse your computer and upload the FASTA file with the cleaned consensus sequences (results/clean_sequences.fa).
  • +
  • Nextclade will automatically detect your data are from SARS-CoV-2, but if not you can select this organism.
  • +
  • Click Run.
  • +
+

Nextclade will show a progress of its analysis at the top of the page, and the results of several quality control metrics in the main panel (see Figure).

+
+
+

+
Overview of the Nextclade web interface.
+
+
+
+
+
+ +
+
+Nextclade and data privacy +
+
+
+

When using the Nextclade web application, the data does not leave your computer, so privacy concerns are not an issue.

+
+
+
+
+
+

6.4 Exercises

+
+
+
+ +
+
+Nextclade +
+
+
+
+
+
+
+

In this exercise we will work with 48 consensus sequences from the UK, processed with the nf-core/viralrecon pipeline and covered in the previous section.

+

Go to nextclade.org and load the sequences provided in uk_illumina/preprocessed/clean_sequences.fa.

+
    +
  1. Are there any samples that were classified as “bad” quality? If so, what is the main reason?
  2. +
  3. Sort the table by the “Clade” column. Looking at the mutations in gene S on the right, you can see that all sequences classified as “Alpha” have a deletion in positions 21992-21994. Samples classified as “Delta” do not have this deletion and instead have a deletion in positions 22029-22034. However, there is one exception: sample GB39, classified as “Delta” has both deletions. Investigate if this mutation is accurate using IGV: +
      +
    • Open the BAM alignment file for this sample (the alignment file is in results/viralrecon/variants/bowtie2/GB39.ivar_trim.sorted.bam).
    • +
    • Open the BAM alignment file for one of the “Alpha” samples as a comparison.
    • +
    • Open the ARTIC primer files (two BED files found in resources/primers/).
    • +
    • Go to the position where this deletion was identified and investigate if it seems clear from the mapped reads.
    • +
  4. +
+
+ +
+
+
+
+

Question 1

+

After loading the samples to the Nextclade web application, we can see that, generally, these samples have high quality as very few are highlighted by Nextclade’s analysis.

+

Sorting the table by the “QC” column, we can see two samples with bad quality (red) and one with mediocre quality (yellow).

+

There are separate reasons for the two bad quality samples:

+
    +
  • Sample GB16 has low quality due to a high number of missing bases.
  • +
  • Sample GB39 has low quality due to the occurrence of too many “private mutations”. These are mutations that are only found in this sample, compared to other samples present in Nextclade’s background sequence tree. As we don’t usually expect too many new mutations to occur in a new sample (the mutation rate of SARS-CoV-2 is around 2 new mutations per month), the occurrence of too many private mutations could indicate sequencing quality issues.
  • +
+

Question 2

+

Sort the table by the “Clade” column. Looking at the mutations in gene S on the right, you can see that all sequences classified as “Alpha” have a deletion in positions 21992-21994. Samples classified as “Delta” do not have this deletion and instead have a deletion in positions 22029-22034. However, there is one exception: sample GB39, classified as “Delta” has both deletions. Investigate if this mutation is accurate using IGV:

+
    +
  • Open the BAM alignment file for this sample (the alignment file is in preprocessed/variants/bowtie2/GB39.ivar_trim.sorted.bam).
  • +
  • Open the BAM alignment file for one of the “Alpha” samples as a comparison.
  • +
  • Open the ARTIC primer files (two BED files found in resources/primers/).
  • +
  • Go to the position where this deletion was identified and investigate if it seems clear from the mapped reads.
  • +
+

By sorting the table by “Clade”, we can see that although sample GB39 was classified as a Delta variant, it has two deletions in gene S that are present in Alpha variants, as shown in this snapshot (click the image to view a bigger size):

+
+

+
+

As we investigate one of these in more detail from the BAM file (opening it in IGV), we can see that there is an inconsistency between reads coming from the PCR fragment “nCoV-2019_72” and those reads coming from the “nCoV-2019_73” fragment. In some of those reads the deletion is present, but in others it is not. If we look at an Alpha variant sample (for example GB43) we can see that this deletion is present in both cases.

+

+

If we thought this sample was crucial for public health investigation, then this would require further investigation by doing a PCR with new primers and Sanger-sequencing the fragment, for example.

+
+
+
+
+
+
+
+
+
+
+
+
+

6.5 Summary

+
+
+
+ +
+
+Key Points +
+
+
+
    +
  • Groups of similar SARS-CoV-2 sequences are classified into lineages or clades by different groups. The main nomenclature systems in use are Nextstrain, Pangolin and GISAID.
  • +
  • In addition, the World Health Organisation (WHO) classifies some forms of SARS-CoV-2 as variants of concern or variants of interest. These are forms of the virus that have been determined to have a significant public health impact.
  • +
  • Both Pangolin and Nextclade assign consensus sequences to lineages/clades and additionally identify those that correspond to WHO variants of concern. Both of these are run as part of the nf-core/viralrecon pipeline, but can also be run using a web application: +
  • +
  • Besides clade assignment and variant classification, Nextclade provides additional analysis such as identification of mutations and quality control metrics that can be used to identify problematic samples.
  • +
+
+
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/02-isolates/04-phylogeny.html b/materials/02-isolates/04-phylogeny.html new file mode 100644 index 0000000..230f541 --- /dev/null +++ b/materials/02-isolates/04-phylogeny.html @@ -0,0 +1,1160 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

7  Building phylogenetic trees

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Learning Objectives +
+
+
+
    +
  • Understand the basics of how phylogeny trees are constructed using maximum likelihood methods.
  • +
  • Describe what branch lengths represent in typical SARS-CoV-2 phylogenetic tree.
  • +
  • Recognise the limitations and challenges in building phylogenies for SARS-CoV-2.
  • +
  • Use MAFFT to produce a multiple sequence alignment.
  • +
  • Use IQ-Tree for phylogenetic tree inference.
  • +
  • Use TimeTree to obtain time-scaled phylogenetic trees using sample collection date metadata.
  • +
+
+
+ + +
+

7.1 SARS-CoV-2 Phylogeny

+

Global Phylogeny

+

Building phylogenetic trees for SARS-CoV-2 is challenging due to the large number of sequences available, with millions of genomes submited to GISAID to this day. This means that using maximum-likelihood inference tools to build a global SARS-CoV-2 phylogeny is both time-consuming and computationally demanding.

+

However, several researchers have dedicated their time to identifying tools and models suitable for the phylogenetic analysis of this organism. For example, the global phylogenies repository from Rob Lanfear provide with several tips on building phylogenetic trees for this organism. Their trees are regularly updated and available to download from the GISAID website (which requires an account).

+

Global phylogenies are also available from the groups of Russell Corbett-Detig and Yatish Turakhia, who have developed efficient methods and tools for dealing with large phylogenies. These tools include UShER and matUtils, introducing a new and efficient file format for storing phylogenetic trees, mutations and other annotations (such as lineages) called mutation-annotated trees (MAT format). Their phylogenies are updated daily and are publicly available for download. (Note: Course materials covering these tools are still under development.)

+

Two popular tools used for phylogenetic inference via maximum-likelihood are FastTree and IQ-Tree. Generally, when building a tree from a collection of samples you can include the Wuhan-Hu-1 reference sequence as an outgroup to root the tree. Optionally, you can also add a collection of sequences from around the world (and across time) to contextualise your samples in the global diversity.

+

As an input, these programs need a multiple sequence alignment FASTA file, which is where we start our analysis.

+
+
+
+ +
+
+Data for this section +
+
+
+

We will work from the course materials folder called 04-phylogeny, which contains the following files:

+
    +
  • data/uk_consensus.fa and data/india_consensus.fa are consensus sequences from the UK and India, respectively. These are the sequences previously assembled using the nf-core/viralrecon pipeline.
  • +
  • sample_annotation.tsv is a tab-separated values (TSV) file with information about each sample such as the date of collection and lineage/clade they were assiged to from our analysis. We will use this table to annotate our phylogenetic trees. This table can also be opened in a spreadsheet program such as Excel.
  • +
  • resources/reference/sarscov2.fa is the reference genome.
  • +
+
+
+
+
+

7.2 Alignment

+

The first step in building a phylogenetic tree is to produce a multiple sequence alignment from all our consensus sequences. This is the basis for building a phylogenetic tree from the positions that are variable across samples.

+

A widely used multiple sequence alignment software is called MAFFT. For SARS-CoV-2, a reference-based alignment approach is often used, which is suitable for closely-related genomes. MAFF provides this functionality, which is detailed in its documentation.

+

We demonstrate the analysis using the UK samples in our course materials. First, start by creating a directory for the output:

+
mkdir -p results/mafft
+

Then, we run the command to generate a reference-based alignment:

+
mafft --6merpair --maxambiguous 0.2 --addfragments data/uk_consensus.fa resources/reference/sarscov2.fa > results/mafft/uk_alignment.fa
+

The meaning of the options used is:

+
    +
  • --6merpair is a fast method for estimating the distances between sequences, based on the number of short 6bp sequences shared between each pair of sequences. This is less accurate than other options available (like --localpair and --globalpair), but runs much faster in whole genome data like we have.
  • +
  • --maxambiguous 0.2 automatically removes samples with more than 20% ambiguous ‘N’ bases (or any other value of our choice). This is a convenient way to remove samples with poor genome coverage from our analysis.
  • +
  • --addfragments data/consensus_sequences.fa is the FASTA file with the sequences we want to align.
  • +
  • finally, at the end of the command we give the reference genome as the input sequence to align our sequences against.
  • +
+

MAFFT provides several other methods for alignment, with tradeoffs between speed and accuracy. You can look at the full documentation using mafft --man (mafft --help will give a shorter description of the main options). For example, from its documentation we can see that the most precise alignment can be obtained with the options --localpair --maxiterate 1000. However, this is quite slow and may not be feasible for whole genome data of SARS-CoV-2.

+
+

7.2.1 Visualising alignments

+

We can visualise our alignment using the software AliView, which is both lightweight and fast, making it ideal for large alignments. Visualising the alignment can be useful for example to identify regions with missing data (more about this below).

+
+
+

+
Snapshop of an alignment visualised with AliView. In this case we are looking at the end of the alignment of our sequences, which shows a typical high number of missing (‘N’) bases.
+
+
+
+
+
+ +
+
+Other Alignment Strategies +
+
+
+

There are other commonly used alignment tools used for SARS-CoV-2 genomes:

+
    +
  • The minimap2 software has been designed for aligning long sequences to a reference genome. It can therefore be used to align each consensus sequence to the Wuhan-Hu-1 genome. This is the tool internally used by Pangolin.
  • +
  • Nextclade uses an internal alignment algorithm where each consensus sequence is aligned with the reference genome. The alignment produced from this tool can also be used for phylogenetic inference.
  • +
+

It is worth mentioning that when doing reference-based alignment, insertions relative to the reference genome are not considered.

+
+
+
+
+
+

7.3 Tree Inference: IQ-Tree

+

IQ-TREE supports many substitution models, including models with rate heterogeneity across sites.

+

Let’s start by creating an output directory for our results:

+
mkdir -p results/iqtree
+

And then run the program with default options (we set --prefix to ensure output files go to the directory we just created and are named “uk”):

+
iqtree2 -s results/mafft/uk_alignment.fa --prefix results/iqtree/uk
+

Without specifying any options, iqtree2 uses ModelFinder to find the substituion model that maximizes the likelihood of the data, while at the same time taking into account the complexity of each model (using information criteria metrics commonly used to assess statistical models).

+

From the information printed on the console after running the command, we can see that the chosen model for our alignment was “GTR+F+I”, a generalised time reversible (GTR) substitution model. This model requires an estimate of each base frequency in the population of samples, which in this case is estimated by simply counting the frequencies of each base from the alignment (this is indicated by “+F” in the model name). Finally, the model includes rate heterogeneity across sites, allowing for a proportion of invariant sites (indicated by “+I” in the model name). This makes sense, since we know that there are a lot of positions in the genome where there is no variation in our samples.

+

We can look at the output folder (specified with --prefix) where we see several files with the following extension:

+
    +
  • .iqtree - a text file containing a report of the IQ-Tree run, including a representation of the tree in text format.
  • +
  • .treefile - the estimated tree in NEWICK format. We can use this file with other programs, such as FigTree, to visualise our tree.
  • +
  • .log - the log file containing the messages that were also printed on the screen.
  • +
  • .bionj - the initial tree estimated by neighbour joining (NEWICK format).
  • +
  • .mldist - the maximum likelihood distances between every pair of sequences.
  • +
  • ckp.gz - this is a “checkpoint” file, which IQ-Tree uses to resume a run in case it was interrupted (e.g. if you are estimating very large trees and your job fails half-way through).
  • +
  • .model.gz - this is also a “checkpoint” file for the model testing step.
  • +
+

The main files of interest are the report file (.iqtree) and the NEWICK tree file (.treefile).

+
+
+
+ +
+
+Inference of very large trees +
+
+
+

Although running IQ-Tree with default options is fine for most applications, there will be some bottlenecks once the number of samples becomes too large. In particular, the ModelFinder step may be very slow and so it’s best to set a model of our choice based on other people’s work. For example, work by Rob Lanfear suggests that models such as “GTR+G” and “GTR+I” are suitable for SARS-CoV-2. We can specify the model used by iqtree2 by adding the option -m GTR+G, for example.

+

For very large trees (over 10k or 100k samples), using an alternative method to place samples in an existing phylogeny may be more adequate. UShER is a popular tool that can be used to this end. It uses a parsimony-based method, which tends to perform well for SARS-CoV-2 phylogenies.

+
+
+
+
+

7.4 Visualising Trees

+

There are many programs that can be used to visualise phylogenetic trees. In this course we will use FigTree, which has a simple graphical user interface.

+

To open the tree, go to File > Open… and browse to the folder with the IQ-Tree output files. Select the file with .treefile extension and click Open. You will be presented with a visual representation of the tree.

+

We can also import a “tab-separated values” (TSV) file with annotations to add to the tree. For example, we can use our results from Pangolin and Nextclade, as well as other metadata to improve our visualisation (we have prepared a TSV with this information combined, which you could do using Excel or another spreadsheet software).

+
    +
  • Go to File > Import annotations… and open the annotation file.
  • +
  • On the menu on the left, click Tip Labels and under “Display” choose one of the fields of our metadata table. For example, you can display the lineage assigned by Pangolin (“pango_lineage” column of our annotation table).
  • +
+

There are many ways to further configure the tree, including highlighting clades in the tree, and change the labels. See the figure below for an example.

+
+
+

+
Annotated phylogenetic tree obtained with FigTree. We used the lineage of each sample as our tip labels and aligned the labels on the right (check the tickbox at the top of the left menu called “Align tip labels”). We identified two clades in the tree that corresponded to the Alpha and Delta variants, and used the “Highlight” tool to give them different colours. To do this, change the “Selection Mode” at the top to “Clade”, then select the branch at the base of the clade you want to highlight, and press the “Highlight” button on the top to pick a colour.
+
+
+ +
+
+

7.5 Time-scaled Phylogenies

+

The trees that we build from sequence data are scaled using the mutation rate estimated from the sequence alignments. This is useful if we want to know, for example, on average how many mutations separate different branches of the tree.

+

Another way to scale trees is to use time. For viral genome sequences, we usually have information about their date of collection, and this can be used to scale the phylogeny using the date information. The idea is to rescale the trees such that the x-axis of the tree represents a date rather than number of mutations.

+

Two programs that can be used to time-scale trees using date information are called TreeTime and Chronumental. Chronumental was developed for dealing with very large phylogenies (millions of samples), but lacks some of the functionalities provided by TreeTime (such as estimating uncertainty in date estimates, reconstructing past sequences, re-rooting trees, among others). So, if you are working with less than ~50,000 sequences, we recommend using TreeTime, otherwise Chronumental is a suitable alternative.

+

Because we are dealing with a small number of samples, we will show an example of using TreeTime to scale our tree based on our dates:

+
treetime --tree results/iqtree/uk.treefile --dates sample_annotation.tsv --aln results/mafft/uk_alignment.fa --outdir results/treetime/uk
+

After running, this tool produces several output files in the specified folder. The main files of interest are:

+
    +
  • timetree.nexus is the new date-scaled tree. NEXUS is another tree file format, which can store more information about the tree compared to the simpler NEWICK format. This format is also supported by FigTree.
  • +
  • timetree.pdf is a PDF file with the inferred tree, including a time scale on the x-axis. This can be useful for quick visualisation of the tree.
  • +
+

We can visualise this tree in FigTree, by opening the file timetree.nexus. The scale of this new tree now corresponds to years (instead of nucleotide substitution rates). We make make several adjustments to this tree, to make it look how we prefer. For example:

+
    +
  • Label the nodes of the tree with the inferred dates: from the left menu click Node Labels and under “Display” select “date”.
  • +
  • Import the metadata table (sample_annotation.tsv file) and display the results of Nextclade or Pangolin clades/lineages.
  • +
  • Adjust the scale of the tree to be more intuitive. For example, instead of having the unit of the scale in years, we can change it to months. On the left menu, click Time Scale and change “Scale factor” to 12 (twelve months in a year). Then click Scale Bar and change the “Scale range” to 1. The scale now represents 1 month of time, which may be easier to interpret in this case.
  • +
+

Note that there is uncertainty in the estimates of the internal node dates from TimeTree and these should be interpreted with some caution. The inference of the internal nodes will be better the more samples we have, and across a wider range of times. In our specific case we had samples all from a similar period of time, which makes our dating of internal nodes a little poorer than might be desired.

+

More precise tree dating may be achieved by using public sequences across a range of times or by collecting more samples over time. Time-scaled trees of this sort can therefore be useful to infer if there is a recent spread of a new clade in the population.

+
+
+

7.6 Missing Data & Problematic Sites

+

So far, we have been using all of our assembled samples in the phylogenetic analysis. However, we know that some of these have poorer quality (for example the “IN01” sample from India had low genome coverage). Although, generally speaking, sequences with missing data are unlikely to substantially affect the phylogenetic results, their placement in the phylogeny will be more uncertain (since several variable sites may be missing data). Therefore, for phylogenetic analysis, it is best if we remove samples with low sequencing coverage, and instead focus on high-quality samples (e.g. with >80% coverage).

+

A more serious issue affecting phylogenies is the presence of recurrent errors in certain positions of the genome. One of the regions with a higher prevalence of errors is the start and end of the consensus sequence, which also typically contains many missing data (see example in Figure 2). Therefore, it is common to mask the first and last few bases of the alignment, to avoid including spurious variable sites in the analysis.

+
+
+
+ +
+
+Sequence masking +
+
+
+

The term masking is often used to refer to the process of converting sequence bases to the ambiguous character ‘N’. You may come across this term in the documentation of certain tools, for example: “Positions with less than 20x depth of sequencing are masked.”

+

Masks are not limited to depth of sequencing. For example, reference genomes from ENSEMBL are available with masked repeat or low-complexity sequences (e.g. around centromeres, transposon-rich regions, etc.).

+

The term soft masking is also used to refer to cases where, instead of using the ambiguous character ‘N’, sequences are masked with a lowercase. For example:

+
>seq_with_soft_masking
+ACAGACTGACGCTGTcatgtatgtcgacGATAGGCTGATGGCGAGTGACTCGAG
+>seq_with_hard_masking
+ACAGACTGACGCTGTNNNNNNNNNNNNNGATAGGCTGATGGCGAGTGACTCGAG
+
+
+

Additionally, work by Turakhia, de Maio, Thornlow, et al. (2020) has identified several sites that show an unexpected mutation pattern. This includes, for example, mutations that unexpectedly occur multiple times in different parts of the tree (homoplasies) and often coincide with primer binding sites (from amplicon-based protocols) and can even be lab-specific (e.g. due to their protocols and data processing pipelines). The work from this team has led to the creation of a list of problematic sites, which are recommended to be masked before running the phylogenetic analysis.

+
+
+

+
Example of errors in phylogenetic inference due to recurrent sequencing errors. Source: Figure 1 in Turakhia, de Maio, Thornlow et al. (2020)
+
+
+

So, let’s try to improve our alignment by masking the problematic sites, which are provided as a VCF file. This file also includes the first and last positions of the genome as targets for masking (positions 1–55 and 29804–29903, relative to the Wuhan-Hu-1 reference genome MN908947.3). The authors also provide a python script for masking a multiple sequence alignment. We have already downloaded these files to our course materials folder, so we can go ahead and use the script to mask our file:

+
python scripts/mask_alignment_using_vcf.py --mask -v resources/problematic_sites.vcf -i results/mafft/uk_alignment.fa -o results/mafft/uk_alignment_masked.fa
+

If we open the output file with AliView, we can confirm that the positions specified in the VCF file have now been masked with the missing ‘N’ character.

+

We could then use this masked alignment for our tree-inference, just as we did before.

+
+
+
+ +
+
+Using Python Scripts +
+
+
+

Bioinformaticians often write custom scripts for particular tasks. In this example, the authors of the “problematic sites” wrote a Python script that takes as input the FASTA file we want to mask as well as a VCF with the list of sites to be masked.

+

Python scripts are usually run with the python program and often accept options in a similar way to other command-line tools, using the syntax --option (this is not always the case, but most professionally written scripts follow this convention). To see how to use the script we can use the option --help. For our case, we could run:

+
python scripts/mask_alignment_using_vcf.py --help
+
+
+
+
+

7.7 Exercises

+
+
+
+ +
+
+Building phylogenies +
+
+
+
+
+
+
+

So far we have focused our analysis on the samples from the UK. In this exercise you will be able to practice these steps on the samples from India. The steps are similar to what we have done so far, and you can consult the materials in the sections above to go through each exercise.

+

In the following exercises, you can run the commands directly from the command line. But if you feel comfortable with nano, as a bonus, you can try to save the commands in a shell script.

+
    +
  1. Using mafft, produce a multiple-sequence alignment from the India consensus sequences in the file data/india_consensus.fa. Save the output in a file named results/mafft/india_alignment.fa.
  2. +
  3. Using iqtree2, infer a phylogenetic tree from this alignment. Save the output with prefix results/iqtree/india. +
      +
    • What substitution model did IQ-Tree infer as the most likely for the data?
    • +
  4. +
  5. Using FigTree, visualise the inferred tree: +
      +
    • Open the .tree output file.
    • +
    • Import the annotation table sample_annotation.tsv.
    • +
    • Make the tip labels display the Nextclade clade instead of the sample names.
    • +
    • Highlight any clusters of the tree containing WHO variants of concern.
    • +
  6. +
  7. Using timetree, re-scale the phylogeny using dates (the file sample_annotation.tsv can be used as input to timetree along with the previously-created phylogeny and alignments). Output the result to results/treetime/india +
      +
    • Open the .nexus output file in FigTree.
    • +
    • Make the node labels display the date inferred by timetree.
    • +
  8. +
  9. Two samples in the time-scaled tree appear at the root of the tree: IN05 and IN33. But we would have expected the reference sample (MN908947.3) to be the root of the tree, as it was collected in Dec 2019. +
      +
    • Investigate what lineages these samples were assigned to.
    • +
    • Go to https://cov-lineages.org/lineage_list.html and check when those lineages were detected.
    • +
    • Is the collection date metadata for these samples compatible with the lineage information from the Pangolin website? Can you hypothesise what may have happened with these samples?
    • +
  10. +
+
+ +
+
+
+
+

Question 1

+

We run our alignment using the following command:

+
mafft --6merpair --maxambiguous 0.2 --addfragments data/india_consensus.fa resources/reference/sarscov2.fa > results/mafft/india_alignment.fa
+

We could optionally visualise our alignment using AliView to check that the alignment was successful.

+

Question 2

+

We can fit a maximum-likelihood tree using the following command:

+
iqtree2 -s results/mafft/india_alignment.fa --prefix results/iqtree/india
+

This command prints a lot of output on the screen. We can see all this information (and more!) on the output file results/iqtree/india.iqtree, which contains a log of all the analysis done by the program. We could, for example, use the less program to look inside this file. When we do this, we can see the following:

+
ModelFinder
+-----------
+
+Best-fit model according to BIC: GTR+F+I
+

Which indicates that the substitution model used by IQ-Tree was “GTR+F+I”. This is the same model that was determined for the UK samples, and an explanation of this model is given in the materials above.

+

Question 3

+

Using FigTree:

+
    +
  • Go to File > Open… and browse to the folder with the IQ-Tree output files.
  • +
  • Select the file with india.treefile and click Open.
  • +
  • Go to File > Import annotations… and open the annotation file sample_annotation.tsv.
  • +
  • On the menu on the left, click Tip Labels and under “Display” choose the field “nextclade_clade”.
  • +
+

On this tree, there is a small group of samples classified as “20I (Alpha; V1)”. These samples correspond to the Alpha variant of concern. To highlight these:

+
    +
  • Change the “Selection Mode” at the top to “Clade”.
  • +
  • Select the branch corresponding to the base of the group of samples classified as Alpha. This should highlight all those branches.
  • +
  • Click the “Highlight” button at the top and choose a colour.
  • +
+ +

Question 4

+

The command to time-scale the tree is:

+
treetime --tree results/iqtree/india.treefile --dates sample_annotation.tsv --aln results/mafft/india_alignment.fa --outdir results/treetime/india
+

Once complete, we can open the india.nexus tree with FigTree. We can annotate the internal nodes of the tree with the dates inferred by treetime by clicking on the Node Labels menu on the left and selecting “Display” to be “date”.

+ +

Question 5

+

To investigate this strange result, we open the sample_annotation.tsv file in our spreadsheet program. We can see the following information for these two samples:

+
name    date           country  year     month  pango_lineage      nextclade_clade
+IN05    2021-01-21   India    2021   1      A.23.1           19B
+IN33    2021-01-21   India    2021   1      A.23.1           19B
+

Both these samples were assigned to Pango lineage “A.23.1”. From the cov-lineages.org website we can see that these were detected as early as 2020-06-08.

+

However, our samples’ metadata indicate that these samples were collected in January 2021, that is 7 months after these samples were first detected globally.

+

This contractiction explains the strange result in our time-scaled tree. These samples will have mutations that are part of the older lineage “A”, but were annotated to be from 2021. So treetime put them down at the root of the tree.

+

This discrepancy could indicate an issue with the metadata collection, which perhaps is wrong. For example, it could be that these samples were collected months before January 2021, but only sequenced then. And by mistake the date of sequencing was recorded as the date of collection.

+

This is an example how important accurate metadata is for our analysis.

+
+
+
+
+
+
+
+
+
+
+
+
+

7.8 Summary

+
+
+
+ +
+
+Key Points +
+
+
+
    +
  • Methods for phylogenetic inference include parsimony and maximum likelihood. Maximum likelihood methods are preferred because they include more features of the evolutionary process. However, they are computationally more demanding than parsimony-based methods.
  • +
  • To build a phylogenetic tree we need a multiple sequence alignment of the sequences we want to infer a tree from.
  • +
  • In SARS-CoV-2, alignments are usually done against the Wuhan-Hu-1 reference genome.
  • +
  • We can use the software mafft to produce a multiple sequence alignment. The option --addfragments is used to produce an alignment against the reference genome.
  • +
  • The software iqtree2 can be used for inferring trees from an alignment using maximum likelihood. This software supports a wide range of substitution models and a method to identify the model that maximizes the likelihood of the data.
  • +
  • Some of the substituion models that have been used to build global SARS-CoV-2 phylogenies are “GTR+G” and “GTR+I”.
  • +
  • We can time-scale trees using sample collection date information. The program treetime can be used to achieve this. For very large sample sizes (>50,000 samples) the much faster program Chronumental can be used instead.
  • +
  • Before building a phylogeny, we should be careful to mask problematic sites that can lead to misleading placements of samples in the tree. The SARS-CoV-2 Problematic Sites repository provides with an updated list of sites that should be masked.
  • +
+
+
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/02-isolates/images/alignment_aliview.png b/materials/02-isolates/images/alignment_aliview.png new file mode 100644 index 0000000..5b2848e Binary files /dev/null and b/materials/02-isolates/images/alignment_aliview.png differ diff --git a/materials/02-isolates/images/igv_amplicon_dropout.svg b/materials/02-isolates/images/igv_amplicon_dropout.svg new file mode 100644 index 0000000..f831e87 --- /dev/null +++ b/materials/02-isolates/images/igv_amplicon_dropout.svg @@ -0,0 +1,281 @@ + + + + + + + + + + + image/svg+xml + + + + + + + Low depth of sequencing in amplicon 83 + + SNP located in ncov-2019_83_LEFT primer + + + + diff --git a/materials/02-isolates/images/igv_deletion_example.svg b/materials/02-isolates/images/igv_deletion_example.svg new file mode 100644 index 0000000..520ec6d --- /dev/null +++ b/materials/02-isolates/images/igv_deletion_example.svg @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + These reads come from amplicon nCoV-2019_72and have the deletion + These reads come from amplicon nCoV-2019_73and do not have the deletion + + + In this Alpha variant sampleboth sets of reads have the deletion + + + + + diff --git a/materials/02-isolates/images/lineages_example.svg b/materials/02-isolates/images/lineages_example.svg new file mode 100644 index 0000000..c503d4c --- /dev/null +++ b/materials/02-isolates/images/lineages_example.svg @@ -0,0 +1,4282 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + Date + 2020-May + 2020-Oct + 2021-Mar + 2021-Aug + 2022-Jan + + diff --git a/materials/02-isolates/images/nextclade_deletion_example.svg b/materials/02-isolates/images/nextclade_deletion_example.svg new file mode 100644 index 0000000..b609f77 --- /dev/null +++ b/materials/02-isolates/images/nextclade_deletion_example.svg @@ -0,0 +1,3770 @@ + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/materials/02-isolates/images/nextclade_overview.svg b/materials/02-isolates/images/nextclade_overview.svg new file mode 100644 index 0000000..4878e97 --- /dev/null +++ b/materials/02-isolates/images/nextclade_overview.svg @@ -0,0 +1,2919 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Analysis progress + + + + + + + + + Filter Data + Phylogeny + DownloadResults + + + Gene Selector + + + + + Quality Control Metrics + + diff --git a/materials/02-isolates/images/phylogeny_figtree.svg b/materials/02-isolates/images/phylogeny_figtree.svg new file mode 100644 index 0000000..037eeef --- /dev/null +++ b/materials/02-isolates/images/phylogeny_figtree.svg @@ -0,0 +1,3018 @@ + + + + + + image/svg+xml + + + + + + + + + + + 2.0E-4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AY.6 + + + + + + AY.4 + + + + + + B.1.560 + + + + + + B.1 + + + + + + B.1.1.7 + + + + B.1.1.7 + + + + + + B.1.36.29 + + + + + + B.1 + + + + + + B.1.560 + + + + + + B.1.36 + + + + + + B.1.36.8 + + + + + + B.1.36.29 + + + + + + B.1.1.216 + + + + + + AY.5 + + + + + + B.1.36.29 + + + + B.1.36.29 + + + + + + B.1.1.7 + + + + B.1.1.7 + + + + + + B.1.617.2 + + + + + + B.1.1.7 + + + + B.1.1.7 + + + + + + B.1.36.39 + + + + + + B.1.1.7 + + + + + + B.1 + + + + + + AY.4 + + + + + + B.1.36.17 + + + + + + B.1.36.29 + + + + + + B.1.1.7 + + + + B.1.1.7 + + + + B.1.1.7 + + + + + + B.1.560 + + + + + + AY.4 + + + + + + B.1.1.7 + + + + B.1.1.7 + + + + B.1.1.7 + + + + + + None + + + + + + B.1 + + + + + + B.1.1.7 + + + + B.1.1.7 + + + + + + AY.6 + + + + + + AY.4 + + + + + + None + + + + + + B.1.177 + + + + + + B.1.560 + + + + + + B.1.36.29 + + + + + + B.1.36 + + + + + + B.1 + + + + + + AY.5 + + + + + + B.1 + + + + + + B.1.1.7 + + + + + + AY.4 + + + + + + B.1.1.7 + + + + + + AY.120 + + + + + + B.1.1.7 + + + + + + AY.4 + + + + + + B.1.1.7 + + + + + + A.23.1 + + + + + + B.1.36.29 + + + + + + B.1.36 + + + + + + + + + B.1.1.7 + + + + + + B.1.36.8 + + + + + + A + + + + + + B.1 + + + + + + None + + + + + + B.1.560 + + + + + + AY.4 + + + + + + B.1.1.7 + + + + + + B.1.560 + + + + + + B.1.36.29 + + + + + + B.1 + + + + B.1 + + + + + + B.1.1.7 + + + + + + B.1.36 + + + + + + B.1.36.8 + + + + + + AY.120 + + + + + + None + + + + + + B.1 + + + + + + B.1.1.7 + + + + B.1.1.7 + + + + + + AY.120 + + + + + + B.1.1.7 + + + + B.1.1.7 + + + + + + B.1.36.29 + + + + + + B.1.1.7 + + + + + + AY.9 + + + + + + B.1.1.7 + + + + B.1.1.7 + + + + B.1.1.7 + + + + B.1.1.7 + + + + + + A.23.1 + + + + + + AY.4 + + + + + + B.1.1.7 + + + + + + B.1.36.29 + + + + + + B.1 + + + + + + B.1.1.7 + + + + + + Wuhan-Hu-1 + + diff --git a/materials/02-isolates/images/switzerland_variants.svg b/materials/02-isolates/images/switzerland_variants.svg new file mode 100644 index 0000000..346e5d0 --- /dev/null +++ b/materials/02-isolates/images/switzerland_variants.svg @@ -0,0 +1,2141 @@ + + + +PositionReference and alternativeallelesDepth ofsequencingAlternativeallelefrequencyMutation effectThe alternative allele is the one in the consensus sequenceAn insertionA deletionTotalReferenceAlternative diff --git a/materials/02-isolates/images/variants_snps_indels.svg b/materials/02-isolates/images/variants_snps_indels.svg new file mode 100644 index 0000000..f1ff382 --- /dev/null +++ b/materials/02-isolates/images/variants_snps_indels.svg @@ -0,0 +1,376 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + aaatacacaa tggcagacct cgtctatgct ttaaggcatt ttgatgaagg taattgtgacaaatacacaa tggcaaacct cgtctatgct ttaa---att ttgatgaagg taattctgacaaatacacaa tggcaaacct cgtctatgct ttaa---att ttgatgaagg taattctgacaaatactcaa tggcaaacct cgtctatgct ttaaggcatt ttgatg---g taattgtgacaaatactcaa tggcaaacct cgtctatgct ttaaggcatt ttgatg---g taattgtgac + reference + 1 + + 2 + + 3 + + 4 + + 5 + [delta] sample 1 + [delta] sample 2 + [alpha] sample 3 + [alpha] sample 4 + + diff --git a/materials/02-isolates/images/viralrecon_coverage_heatmap.png b/materials/02-isolates/images/viralrecon_coverage_heatmap.png new file mode 100644 index 0000000..93cd1d4 Binary files /dev/null and b/materials/02-isolates/images/viralrecon_coverage_heatmap.png differ diff --git a/materials/02-isolates/images/viralrecon_multiqc_plot.png b/materials/02-isolates/images/viralrecon_multiqc_plot.png new file mode 100644 index 0000000..a1e9fba Binary files /dev/null and b/materials/02-isolates/images/viralrecon_multiqc_plot.png differ diff --git a/materials/02-isolates/images/viralrecon_multiqc_variant_metrics.svg b/materials/02-isolates/images/viralrecon_multiqc_variant_metrics.svg new file mode 100644 index 0000000..3f459c0 --- /dev/null +++ b/materials/02-isolates/images/viralrecon_multiqc_variant_metrics.svg @@ -0,0 +1,4304 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + Create Plot + + + diff --git a/materials/02-isolates/images/viralrecon_pipeline_info_duration.png b/materials/02-isolates/images/viralrecon_pipeline_info_duration.png new file mode 100644 index 0000000..f910a99 Binary files /dev/null and b/materials/02-isolates/images/viralrecon_pipeline_info_duration.png differ diff --git a/materials/02-isolates/images/viralrecon_workflow_illumina.svg b/materials/02-isolates/images/viralrecon_workflow_illumina.svg new file mode 100644 index 0000000..8a202c5 --- /dev/null +++ b/materials/02-isolates/images/viralrecon_workflow_illumina.svg @@ -0,0 +1,1341 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + reads.fq.gz + + Adapter Trimming + + fastp + + Read Mapping + + bowtie2 + + samtools + + Trim Primers + + ivar trim + + Call Variants + + ivar variants + + Make Consensus + + bcftools + + + Prepare Reference Files + + reference.fasta + + + + + + + + primers.bed + + + + + + Input Files + + software + + Workflow Step + + Output Files + + + ivar consensus + or + + Remove Human + + kraken2 + + Lineage Assignment + + pangolin + + nextclade + + QC + + mosdepth + + picard + Variant Report + variants_long_table.csv + + samtools + + Quality Report + + multiqc + + multiqc_report.html + + + + + + + + + + + + Annotate Variants + + snpsift + + snpeff + + + + + diff --git a/materials/02-isolates/images/viralrecon_workflow_medaka.svg b/materials/02-isolates/images/viralrecon_workflow_medaka.svg new file mode 100644 index 0000000..5813c2f --- /dev/null +++ b/materials/02-isolates/images/viralrecon_workflow_medaka.svg @@ -0,0 +1,1286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + reads.fq.gz + + Agregate & Filter Reads + + artic guppyplex + + ARTIC Consensus Pipeline + + artic minion --medaka + + Trim Primers + + align_trim.py + + Read Mapping + + minimap2 + + bwa + + Make Consensus + + medaka consensus + + Call Variants + + medaka variant + + longshot + + Mask Low-depth Positions + + python scripts + + bcftools consensus + + Prepare Reference Files + + reference.fasta + + primers.bed + + + + + + + + + + + + + + + + Input Files + + + + software + + + + Workflow Step + + + + Output Files + + + + Quality Report + + multiqc + + multiqc_report.html + + + + + + + Annotate Variants + + snpsift + + snpeff + + + Lineage Assignment + + pangolin + + nextclade + + + + Variant Report + variants_long_table.csv + + + + diff --git a/materials/02-isolates/images/workflow_overview.svg b/materials/02-isolates/images/workflow_overview.svg new file mode 100644 index 0000000..0adb444 --- /dev/null +++ b/materials/02-isolates/images/workflow_overview.svg @@ -0,0 +1,1721 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + New sequence + Wuhan-Hu-1 Reference Genome + + + Mapping + Consensus + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SNP in a primer site + + Sequencing error + + + + + + + + + + + + + + SNP within anamplicon + + + Wuhan-Hu-1 Reference Genome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SNP will be correctly called after primer trimming + + + + + + + + + + + + + + Primer Trimming + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grey shows the primer site used in PCR amplification + + + + + Mixed Material + PCR with SARS-CoV-2 specific primers + + + + + + + + + + + + + + + + Sequencing + ARTIC protocol + diff --git a/materials/03-case_studies/01-switzerland.html b/materials/03-case_studies/01-switzerland.html new file mode 100644 index 0000000..637396c --- /dev/null +++ b/materials/03-case_studies/01-switzerland.html @@ -0,0 +1,1173 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

8  Switzerland (Nanopore)

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Learning Objectives +
+
+
+

This section demonstrates a start-to-finish analysis of a dataset sequenced on a Nanopore platform, using the concepts and tools covered in previous sections. You can download the data from these links (two versions available):

+
    +
  • Switzerland Case Study - Full Data – this includes data for 65 samples, which gives a more realistic sample size, but can take several hours to run on a small computer.
  • +
  • Switzerland Case Study - Small Version – this includes data for a subset of 10 samples, which is more suitable for training purposes (but the results will look slightly different from the ones shown here).
  • +
+

By the end of this section, you should be able to:

+
    +
  • Prepare all the files necessary to run the consensus pipeline.
  • +
  • Run the viralrecon pipeline to generate FASTA consensus from raw FASTQ files.
  • +
  • Assess and collect several quality metrics for the consensus sequences.
  • +
  • Clean output files, in preparation for other downstream analysis.
  • +
  • Assign sequences to lineages using Nextclade and/or Pangolin.
  • +
  • Contextualise your sequences in other background data and cluster them based on phylogenetic analysis.
  • +
  • Integrate the metadata and results to generate useful visualisations of your data.
  • +
  • Report your analysis.
  • +
+
+
+

We will analyse data from 48 samples collected in Switzerland between Nov 2021 and Jan 2022. The samples were sequenced on a GridION platform using pore version 9.4.1.

+

The final product of our work (and main objective) is to produce a report of the analysis, which you can see here: Switzerland Case Study Report.

+

In summary, the report addresses the following:

+ +

We also produce several essential output files, which would usually be necessary to upload our data to public repositories:

+ +
+

8.1 Pipeline Overview

+

Our analysis starts with FASTQ files, which will be used with the nf-core/viralrecon Nextflow pipeline. This will give us several quality control metrics essential for our downstream analysis and reporting.

+

Critical files output by the pipeline will need to be further processed, including combining our consensus FASTA files and obtaining a list of filtered SNP/indel variants. Using these clean files, we can then proceed to downstream analysis, which includes assigning each sample to the most up-to-date Pango lineage, Nextclade clade and WHO designation. Finally, we can do more advanced analysis, including the idenfication of sample clusters based on phylogenetic analysis, or produce timeseries visualisations of mutations or variants of concern. With all this information together, we will have the necessary pieces to submit our results to public repositories and write reports to inform public health decisions.

+

+
+
+

8.2 Preparing Files

+

Before we start our work, it’s always a good idea to setup our directory structure, so we keep our files organised as the analysis progresses. From the data we are starting with, we already have the following directories:

+
    +
  • data → contains the sequencing data in a sub-directory called fast_pass.
  • +
  • resources → files that were downloaded from public repositories.
  • +
  • scriptsbash and R scripts used to run the analysis.
  • +
+

We create two additional directories:

+
    +
  • report → files and documents that we report to our colleagues or upload to public repositories.
  • +
  • results → results of the analysis.
  • +
+

You can create directories from the command line using the mkdir command:

+
mkdir results
+mkdir report
+
+

8.2.1 Data

+

We start our analysis from FASTQ files generated using the software Guppy v6.1.5 ran in “fast” mode. This software outputs the files to a directory called fastq_pass, with further sub-directories for each sample barcode. This is how it looks like in this case:

+
ls data/fastq_pass
+
barcode01  barcode08  barcode15  barcode22  barcode29  barcode36  barcode43  barcode50  barcode57
+barcode02  barcode09  barcode16  barcode23  barcode30  barcode37  barcode44  barcode51  barcode58
+barcode03  barcode10  barcode17  barcode24  barcode31  barcode38  barcode45  barcode52  barcode59
+barcode04  barcode11  barcode18  barcode25  barcode32  barcode39  barcode46  barcode53  barcode60
+barcode05  barcode12  barcode19  barcode26  barcode33  barcode40  barcode47  barcode54  barcode61
+barcode06  barcode13  barcode20  barcode27  barcode34  barcode41  barcode48  barcode55  barcode62
+barcode07  barcode14  barcode21  barcode28  barcode35  barcode42  barcode49  barcode56  barcode63
+
+
+

8.2.2 Metadata

+

Metadata for these samples is available in the file sample_info.csv. Here is some of the information we have available for these samples:

+
    +
  • sample → the sample ID.
  • +
  • collection_date → the date of collection for the sample in the format YYYY-MM-DD.
  • +
  • country → the country of origin for this sample.
  • +
  • latitude/longitude → coordinates for sample location (optional).
  • +
  • sequencing_instrument → the model for the sequencing instrument used (e.g. NovaSeq 6000, MinION, etc.).
  • +
  • sequencing_protocol_name → the type of protocol used to prepare the samples (e.g. ARTIC).
  • +
  • amplicon_primer_scheme → for amplicon protocols, what version of the primers was used (e.g. V3, V4.1)
  • +
  • Specific columns for Oxford Nanopore data, which are essential for the bioinformatic analysis: +
      +
    • ont_pore → the version of the pores.
    • +
    • ont_guppy_version → the version of the Guppy software used for basecalling.
    • +
    • ont_guppy_mode → the basecalling mode used with Guppy.
    • +
  • +
+
+
+
+

8.3 Consensus Assembly

+
+
+
+ +
+
+Note +
+
+
+

See Section 4.1, if you need to revise how the nf-core/viralrecon pipeline works.

+
+
+

The first step in the bioinformatic analysis is to run the nf-core/viralrecon pipeline.

+
+

8.3.1 Samplesheet

+

But first we need to prepare our input files. For Nanopore data, we need a samplesheet CSV file with two columns, indicating sample name (first column) and the respective barcode number (second column).

+

We produced this table in Excel and saved it as a CSV file. Here are the top few rows of the file:

+
head samplesheet.csv
+
sample,barcode
+CH01,1
+CH02,2
+CH03,3
+CH04,4
+CH05,5
+CH06,6
+CH07,7
+CH08,8
+CH09,9
+CH10,10
+
+
+

8.3.2 Running Viralrecon

+

Now we are ready to run the nf-core/viralrecon pipeline (see Section 4.3 for details). We saved our command in a script (scripts/01-run_viralrecon.sh), which we created with the command line text editor nano. This ensures that our analysis is reproducible and traceable (we can go back to the script to see how the analysis was run).

+

First, we activate our software environment, to ensure Nextflow is available to us:

+
mamba activate nextflow
+

Then, we run our script with:

+
bash scripts/01-run_viralrecon.sh
+

Which will start executing the pipeline.

+

For reference, here is the command included in that script:

+
nextflow run nf-core/viralrecon \
+  -r 2.6.0 -profile singularity \
+  --max_memory '16.GB' --max_cpus 8 \
+  --platform nanopore \
+  --input samplesheet.csv \
+  --fastq_dir data/fastq_pass/ \
+  --outdir results/viralrecon \
+  --protocol amplicon \
+  --genome 'MN908947.3' \
+  --primer_set artic \
+  --primer_set_version 3 \
+  --artic_minion_caller medaka \
+  --artic_minion_medaka_model r941_min_fast_g303 \
+  --skip_assembly --skip_asciigenome \
+  --skip_pangolin --skip_nextclade
+

In this case, we used the medaka model r941_min_fast_g303, because that is the latest one available (even though our Guppy version is 6.1.5). We also restricted our --max_memory and --max_cpus due to the size of our processing computers. If a larger computer was available, we could have used higher values for these parameters.

+
+
+
+ +
+
+The \ in long commands +
+
+
+

In the command above, you will notice several \ at the end of each line. This is indicating that we want to continue writing our command in the next line. Notice that the last line does not include \, because that is the end of the command. This is very useful when commands are very long, because it makes the code more readable.

+
+
+
+
+
+

8.4 Consensus Quality

+
+
+
+ +
+
+Note +
+
+
+

See Section 5.2, if you need to revise how to assess the quality of consensus sequences.

+
+
+
+

8.4.1 General Metrics

+

We used the MultiqQC report to assess the initial quality of our samples. The quality report can be found in results/viralrecon/multiqc/medaka/multiqc_report.html.

+

We paid particular attention to:

+
    +
  • Number of reads mapped to the reference genome.
  • +
  • Median depth of coverage.
  • +
  • Percentage of the genome with missing bases (‘N’).
  • +
  • Number of SNP + Indel variants.
  • +
+
+
+

+
Example MultiQC output, highlighting the main columns with quality information that will be collected for the final report.
+
+
+

We noted that:

+
    +
  • 9 samples had more than 15% missing bases.
  • +
  • All samples had median depth of coverage greater than 20x.
  • +
  • There was some systematic dropout for some amplicons, in particular nCoV-2019_64 had very low amplification in several of the samples. Of note was also nCoV-2019_73, and other neighbouring amplicons.
  • +
+
+
+

+
Amplicon depth of sequencing (or coverage) across samples, from the MultiQC report. Darker regions indicate systematic amplicon dropout, probably due to issues during the PCR amplification.
+
+
+

Besides the MultiQC report, the pipeline also outputs a CSV file with collected summary metrics (equivalent to the first table on the report): results/viralrecon/multiqc/medaka/summary_variants_metrics_mqc.csv. We will use this file later to join this information with our metadata and lineage assignment using the R software (detailed in “Integration & Visualisation” section, below).

+
+
+

8.4.2 Variants

+

We also looked at the table of variants obtained from the pipeline. This is output in results/viralrecon/medaka/variants_long_table.csv. This table can be very useful to keep track of particular mutations that may be increasing over time. Later, we will tidy this table to attach to our reported results (“Integration & Visualisation” section).

+

But for now, we will explore this table to address a few more quality-related questions. We opened this table in Excel to answer the following:

+
    +
  • Where there samples with a high number of intermediate allele frequencies? This could indicate mixed samples due to cross-contamination.
  • +
  • Where there samples with frameshift mutations? These mutations should be rare because they are highly disruptive to the functioning of the virus. So, their occurrence may be due to errors rather than a true mutation and it’s good to make a note of this.
  • +
+

All the variants in our samples had an alternative allele frequency (AF column) greater than 68%, which is a good indication that there were no mixed samples. We did note two samples – CH13 and CH49 – had a frameshift mutation each, in the Spike and ORF1a proteins, respectively. This may be due to a sequencing error, as it is not observed in any other samples. The inclusion of this error may not affect our downstream analysis significantly, but we make a note of these two samples, to check if their lineage assignment and phylogenetic analysis makes sense later on (or whether they appear as outliers, indicating broader sequencing quality issues).

+
+
+

+
Variants table output by viralrecon, with a summary of the main columns of interest. Note that this table also includes a column with lineage assignment (not shown in this snapshot). Remember that at this stage we mostly ignore this column, as viralrecon does not use the most up-to-date version of the lineage databases.
+
+
+
+
+

8.4.3 Clean FASTA

+

The pipeline outputs the consensus sequences in results/viralrecon/medaka/*.consensus.fasta (one file for each sample). For downstream analysis, it is convenient to combine all these sequences into a single file, and also clean the sequence names (to remove some text – “/ARTIC/medaka MN908947.3” –, which is added by the medaka variant caller).

+

We created a new script to clean our consensus FASTA files, which we ran with bash scripts/02-clean_fasta.sh:

+
#!/bin/bash 
+
+# combine and clean FASTA files
+cat results/viralrecon/medaka/*.consensus.fasta | sed 's|/ARTIC/medaka MN908947.3||' > report/consensus.fa
+

This command does two things:

+
    +
  • Combine all our FASTA consensus sequences into a single file (using cat).
  • +
  • Clean the sequence names (using sed).
  • +
+

The output was saved as a new FASTA file: report/consensus.fa.

+
+
+

8.4.4 Missing Intervals

+

As a further quality check, we also generated a table of missing intervals (indicated by the N character in the FASTA sequences). We used the seqkit software to achieve this.

+

First, we activate our software environment:

+
mamba activate seqkit
+

Then, we ran the script bash scripts/03-missing_intervals.sh, which includes the following command:

+
seqkit locate -i -P -G -M -r -p "N+" report/consensus.fa > results/missing_intervals.tsv
+

This software outputs a tab-delimited table, which we saved as results/missing_intervals.tsv. The table looks like this (only the top few rows are shown):

+
seqID  patternName  pattern  strand  start  end
+CH01   N+           N+       +       1      54
+CH01   N+           N+       +       1193   1264
+CH01   N+           N+       +       4143   4322
+CH01   N+           N+       +       6248   6294
+CH01   N+           N+       +       7561   7561
+CH01   N+           N+       +       9243   9311
+CH01   N+           N+       +       10367  10367
+CH01   N+           N+       +       11361  11370
+CH01   N+           N+       +       13599  13613
+

We opened this file missing_intervals.tsv in Excel and quickly calculated the length of each interval. We noted that two samples – CH07 and CH59 – both have a continuous interval of 5130 missing bases between positions 19580 and 24709. This includes part of the ORF1a gene and nearly all of the S (Spike) gene. This may be due to a poor amplification of one of the PCR amplicons and may affect the interpretation of the results for these two samples. We make a note of these samples as being possibly problematic in downstream analysis steps.

+
+
+
+

8.5 Downstream Analyses

+

Based on the clean consensus sequences, we then perform several downstream analysis.

+
+

8.5.1 Lineage Assignment

+
+
+
+ +
+
+Note +
+
+
+

See Section 6.1, if you need to revise how lineage assignment works.

+
+
+

Although the Viralrecon pipeline runs Pangolin and Nextclade, it does not use the latest version of these programs (because lineages evolve so fast, the nomenclature constantly changes). An up-to-date run of both of these tools can be done using each of their web applications:

+ +

However, for automation, reproducibility and traceability purposes, we used the command line versions of these tools, and included their analysis in two scripts.

+
+ +
+
+

For Nextclade, we first activate the software environment:

+
mamba activate nextclade
+

And then we ran the script bash scripts/04-nextclade.sh, which contains the following commands:

+
# get nextclade data
+nextclade dataset get --name sars-cov-2 --output-dir resources/nextclade_background_data
+
+# run nextclade
+nextclade run --input-dataset resources/nextclade_background_data/ --output-all results/nextclade report/consensus.fa
+

The first command downloads the latest version of the Nextclade background data using nextclade dataset. We use that data as input to the second command (nextclade run) to make sure it runs with the most up-to-date lineages.

+
+
+

For Pangolin, we first activate the software environment:

+
mamba activate pangolin
+

And then we ran the script bash/04-pangolin.sh, which contains the following commands:

+
# update pangolin data
+pangolin --update-data
+
+# run pangolin
+pangolin --outdir results/pangolin/ --outfile pango_report.csv report/consensus.fa
+

Similarly to before, we first ran pangolin --update-data to ensure we were using the latest lineages available. We can check the version of the data used with pangolin --all-versions (at the time we ran this we had pangolin-data: 1.23.1).

+
+
+
+

Both of these tools output CSV files, which can be open in Excel for further examination.
+Opening the pangolin results (results/pangolin/pango_report.csv), we noticed that two samples – CH07 and CH59 – failed the QC due to high fraction of missing data. These are the same two samples that had a large gap of missing data in the previous section. Several other samples were classified as “Probable Omicron”, which from the “scorpio_notes” column we can see may be because too many of the expected mutations had missing (ambiguous) bases in those consensus sequences.
+Opening the nextclade results (results/nextclade/nextclade.tsv), we noticed that 23 samples were assigned a QC status of “bad”, mostly due to high percentage of missing data (nextclade uses a stringent threshold of 3000 sites, or ~10%, missing data).

+

Like before, we will do further analysis (and visualisation) of these data using the software R, in the section “Integration & Visualisation”, detailed below.

+
+
+

8.5.2 Phylogeny

+
+
+
+ +
+
+Note +
+
+
+

See Section 7.1, if you need to revise how to build phylogenetic trees.

+
+
+

Although a tool such as Nextclade can place our samples in a global phylogeny context, sometimes it may be convient to build our own phylogenies. This requires three steps:

+
    +
  • Producing a multiple sequence alignment from all consensus sequences.
  • +
  • Tree inference.
  • +
  • Tree visualisation and annotation.
  • +
+

Before our analysis, we first activated our software environment:

+
mamba activate phylo
+

We performed the first two steps with the following script, which we ran with bash scripts/05-phylogeny.sh:

+
# alignment
+mkdir -p results/mafft
+mafft --6merpair --maxambiguous 0.2 --addfragments report/consensus.fa resources/reference/sarscov2.fa > results/mafft/alignment.fa
+
+# tree inference
+mkdir -p results/iqtree
+iqtree2 -s results/mafft/alignment.fa --prefix results/iqtree/consensus
+

The output of iqtree includes a tree file, which can be visualised using FigTree (or online using Microreact). The figure below shows an example of an annotated tree, where we highlight the main VOCs detected. This annotation was done based on the file that we generate in the next section (“Integration & Visualisation”), so those steps would have to be done first.

+

We can see that our samples fall broadly into two large clusters, which correlate with the VOC classification for these samples.

+
+
+

+
Phylogenetic tree for our samples. The top cluster (purple) highlights samples classified as Omicron (BA.1-related) and the bottom cluster (pink) samples classified as Delta (AY lineage, also known as B.1.617.2). The tips of the tree are coloured according to Nextclade’s QC status: green = “good”; blue = “mediocre”; red = “bad”.
+
+
+
+
+

8.5.3 Clustering

+

We identified groups of similar sequences in our data using the software civet (Cluster Investigation and Virus Epidemiology Tool). This software compares our samples with a background dataset of our choice, which givus us more context for our analysis. In this case we are using the example background data that comes with civet. However, in a real-world analysis, it would have been ideal to choose local samples as background data. For example, we could download samples from Switzerland from around the time period of our sample collection, from GISAID following the instructions on the civet documentation (you need an account on GISAID to obtain these data).

+

For this example, we already prepared civet background dataset saved in resources/civet_background_data.

+

Before our analysis, we first activate our software environment:

+
mamba activate civet
+

Then, we ran the script bash scripts/06-civet.sh, which contains the following code:

+
# run civet analysis
+civet \
+  -i sample_info.csv \
+  -f report/consensus.fa \
+  -icol sample \
+  -idate collection_date \
+  -d resources/civet_background_data/ \
+  -o results/civet
+

The result of this analysis includes an interactive HTML report (in results/civet/civet.html). We can see that our samples were grouped into 2 catchments, using this background data. This makes sense from our previous lineage/variant analysis: the two catchments correlate with the two main variants in these data (Omicron and Delta).

+
+
+

+
Example of results from civet report for catchment 2, showing the phylogeny in the context of the samples in the background data (coloured according to their lineages).
+
+
+

Civet also outputs a CSV file (results/civet/master_metadata.csv), which includes the catchment that each sample was assigned to. We will use this CSV file later to integrate this information with other parts of our analysis, in R, detailed in the “Integration & Visualisation” section.

+
+
+
+

8.6 Integration & Visualisation

+

At this point in our analysis, we have several tables with different pieces of information:

+
    +
  • sample_info.csv → the original table with metadata for our samples.
  • +
  • results/viralrecon/multiqc/medaka/summary_variants_metrics_mqc.csv → quality metrics from the MultiQC report generated by the viralrecon pipeline.
  • +
  • results/nextclade/nextclade.tsv → the results from Nextclade.
  • +
  • results/pangolin/pango_report.csv → the results from Pangolin.
  • +
  • results/civet/master_metadata.csv → the results from the civet analysis, namely the catchment (or cluster) that each of our samples was grouped into.
  • +
+

To consolidate our analysis, we tidied and integrated the information from across these different files, into a single table using the software R. The script used to do this is in scripts/07-data_integration.R. Because this is an R script, we opened it in RStudio to execute the code.

+

The output of our script is a new tab-delimited table, which we saved in report/consensus_metrics.tsv, and contains the following columns:

+
    +
  • sample → sample ID.
  • +
  • collection_date → date of collection day.
  • +
  • collection_week → date of collection week (useful for summarising/visualising counts per-week).
  • +
  • country → country of origin.
  • +
  • latitude/longitude → latitude and longitude of collection.
  • +
  • n_mapped_reads → number of mapped reads.
  • +
  • median_depth → median depth.
  • +
  • pct_missing → percentage of missing data.
  • +
  • pct_coverage → percentage of coverage.
  • +
  • n_variants → number of SNP + indel variants detected.
  • +
  • nextclade → nextclade clade.
  • +
  • qc_status → QC status as determined by Nextclade (“bad”, “mediocre”, “good”).
  • +
  • lineagePangolin lineage.
  • +
  • who_variant → variant of concern designation.
  • +
  • catchment → catchment group from Civet.
  • +
+

This table, which aggregates information from many of the tools we used, was then used to produce different visualisations of our analysis. These visualisations were also done using the R software (scripts/08-visualisation.R), and integrated into a report, shown below.

+ +
+
+

8.7 Bonus: Full Workflow

+

Although we have ran each of the steps of our analysis individually (each in their own script), now that we have everything working, we could integrate all these steps into a single “master” script:

+
#!/bin/bash
+
+# make mamba activate command available
+eval "$(conda shell.bash hook)"
+source $(mamba info --base)/etc/profile.d/mamba.sh
+
+# make report directory
+mkdir -p report
+
+mamba activate nextflow
+
+# run viralrecon
+nextflow run nf-core/viralrecon \
+  -r 2.6.0 -profile singularity \
+  --max_memory '16.GB' --max_cpus 8 \
+  --platform nanopore \
+  --input samplesheet.csv \
+  --fastq_dir data/fastq_pass/ \
+  --outdir results/viralrecon \
+  --protocol amplicon \
+  --genome 'MN908947.3' \
+  --primer_set artic \
+  --primer_set_version 3 \
+  --artic_minion_caller medaka \
+  --artic_minion_medaka_model r941_min_fast_g303 \
+  --skip_assembly --skip_asciigenome \
+  --skip_pangolin --skip_nextclade
+
+# combine and clean FASTA files
+cat results/viralrecon/medaka/*.consensus.fasta | sed 's|/ARTIC/medaka MN908947.3||' > report/consensus.fa
+
+mamba activate seqkit
+
+# create missing bases TSV file
+seqkit locate -i -P -G -M -r -p "N+" report/consensus.fa > results/missing_intervals.tsv
+
+mamba activate nextclade
+
+# get nextclade data
+nextclade dataset get --name sars-cov-2 --output-dir resources/nextclade_background_data
+
+# run nextclade
+nextclade run --input-dataset resources/nextclade_background_data/ --output-all results/nextclade report/consensus.fa
+
+mamba activate pangolin
+
+# update pangolin data
+pangolin --update-data
+
+# run pangolin
+pangolin --outdir results/pangolin/ --outfile pango_report.csv report/consensus.fa
+
+mamba activate phylo
+
+# alignment
+mkdir -p results/mafft
+mafft --6merpair --maxambiguous 0.2 --addfragments report/consensus.fa resources/reference/sarscov2.fa > results/mafft/alignment.fa
+
+# tree inference
+mkdir -p results/iqtree
+iqtree2 -s results/mafft/alignment.fa --prefix results/iqtree/consensus
+
+# data integration and cleaning
+Rscript scripts/07-data_integration.R
+

Notice that we included the R script that does the data cleaning here, using the Rscript program that allows to execute an R script from the command-line.

+

Having this “master” script, we could run all these steps from start-to-finish with a single command, which can be very useful if you want to fully automate your analysis across multiple runs.

+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/03-case_studies/02-southafrica.html b/materials/03-case_studies/02-southafrica.html new file mode 100644 index 0000000..80a1132 --- /dev/null +++ b/materials/03-case_studies/02-southafrica.html @@ -0,0 +1,1211 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

9  South Africa (Illumina)

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Learning Objectives +
+
+
+

This section demonstrates a start-to-finish analysis of a dataset sequenced on an Illumina platform, using the concepts and tools covered in previous sections. You can download the data from these links (two versions available):

+
    +
  • South Africa Case Study - Full Data – this includes data for 24 samples, which gives a more realistic sample size, but can take a few hours to run on a small computer.
  • +
  • South Africa Case Study - Small Version – this includes data for a subset of 8 samples, which is more suitable for training purposes (but the results will look slightly different from the ones shown here).
  • +
+

By the end of this section, you should be able to:

+
    +
  • Prepare all the files necessary to run the consensus pipeline.
  • +
  • Run the viralrecon pipeline to generate FASTA consensus from raw FASTQ files.
  • +
  • Assess and collect several quality metrics for the consensus sequences.
  • +
  • Clean output files, in preparation for other downstream analysis.
  • +
  • Assign sequences to lineages using Nextclade and/or Pangolin.
  • +
  • Contextualise your sequences in other background data and cluster them based on phylogenetic analysis.
  • +
  • Integrate the metadata and results to generate useful visualisations of your data.
  • +
  • Report your analysis.
  • +
+
+
+

We will analyse data from 24 samples collected in South Africa between Nov-Dec 2021. The samples were sequenced on an Illumina MiSeq platform.

+

The final product of our work (and main objective) is to produce a report of the analysis, which you can see here: South Africa Case Study Report.

+

In summary, the report addresses the following:

+ +

We also produce several essential output files, which would usually be necessary to upload our data to public repositories:

+ +
+

9.1 Pipeline Overview

+

Our analysis starts with FASTQ files, which will be used with the nf-core/viralrecon Nextflow pipeline. This will give us several quality control metrics essential for our downstream analysis and reporting.

+

Critical files output by the pipeline will need to be further processed, including combining our consensus FASTA files and obtaining a list of filtered SNP/indel variants. Using these clean files, we can then proceed to downstream analysis, which includes assigning each sample to the most up-to-date Pango lineage, Nextclade clade and WHO designation. Finally, we can do more advanced analysis, including the idenfication of sample clusters based on phylogenetic analysis, or produce timeseries visualisations of mutations or variants of concern. With all this information together, we will have the necessary pieces to submit our results to public repositories and write reports to inform public health decisions.

+

+
+
+

9.2 Preparing Files

+

Before we start our work, it’s always a good idea to setup our directory structure, so we keep our files organised as the analysis progresses. From the data we are starting with, we already have the following directories:

+
    +
  • data → contains the sequencing data in a sub-directory called reads.
  • +
  • resources → files that were downloaded from public repositories.
  • +
  • scriptsbash and R scripts used to run the analysis.
  • +
+

We create two additional directories:

+
    +
  • report → files and documents that we report to our colleagues or upload to public repositories.
  • +
  • results → results of the analysis.
  • +
+

You can create directories from the command line using the mkdir command:

+
mkdir results
+mkdir report
+
+

9.2.1 Data

+

We start our analysis from FASTQ files generated by the Illumina sequencer. As this is paired-end sequencing, we have two files per sample (with suffix _1 and _2):

+
ls data/reads
+
SRR17051908_1.fastq.gz  SRR17051953_1.fastq.gz  SRR17461700_1.fastq.gz  SRR17712594_1.fastq.gz
+SRR17051908_2.fastq.gz  SRR17051953_2.fastq.gz  SRR17461700_2.fastq.gz  SRR17712594_2.fastq.gz
+SRR17051916_1.fastq.gz  SRR17054503_1.fastq.gz  SRR17461712_1.fastq.gz  SRR17712607_1.fastq.gz
+SRR17051916_2.fastq.gz  SRR17054503_2.fastq.gz  SRR17461712_2.fastq.gz  SRR17712607_2.fastq.gz
+SRR17051923_1.fastq.gz  SRR17088917_1.fastq.gz  SRR17701832_1.fastq.gz  SRR17712711_1.fastq.gz
+SRR17051923_2.fastq.gz  SRR17088917_2.fastq.gz  SRR17701832_2.fastq.gz  SRR17712711_2.fastq.gz
+SRR17051932_1.fastq.gz  SRR17088924_1.fastq.gz  SRR17701841_1.fastq.gz  SRR17712779_1.fastq.gz
+SRR17051932_2.fastq.gz  SRR17088924_2.fastq.gz  SRR17701841_2.fastq.gz  SRR17712779_2.fastq.gz
+SRR17051935_1.fastq.gz  SRR17088928_1.fastq.gz  SRR17701890_1.fastq.gz  SRR17712994_1.fastq.gz
+SRR17051935_2.fastq.gz  SRR17088928_2.fastq.gz  SRR17701890_2.fastq.gz  SRR17712994_2.fastq.gz
+SRR17051951_1.fastq.gz  SRR17088930_1.fastq.gz  SRR17712442_1.fastq.gz  SRR17712997_1.fastq.gz
+SRR17051951_2.fastq.gz  SRR17088930_2.fastq.gz  SRR17712442_2.fastq.gz  SRR17712997_2.fastq.gz
+
+
+

9.2.2 Metadata

+

Metadata for these samples is available in the file sample_info.csv. Here is some of the information we have available for these samples:

+
    +
  • sample → the sample ID.
  • +
  • collection_date → the date of collection for the sample in the format YYYY-MM-DD.
  • +
  • country → the country of origin for this sample.
  • +
  • geo_loc_region → the region within the country where the sample was collected.
  • +
  • latitude/longitude → coordinates for sample location (in this case we’re only given a single coordinate for the whole country - in a real setting you may want to collect a precise location).
  • +
  • sequencing_instrument → the model for the sequencing instrument used (e.g. NovaSeq 6000, MinION, etc.).
  • +
  • sequencing_protocol_name → the type of protocol used to prepare the samples (e.g. ARTIC).
  • +
  • amplicon_primer_scheme → for amplicon protocols, what version of the primers was used (e.g. V3, V4.1)
  • +
+
+
+
+

9.3 Consensus Assembly

+
+
+
+ +
+
+Note +
+
+
+

See Section 4.1, if you need to revise how the nf-core/viralrecon pipeline works.

+
+
+

The first step in the bioinformatic analysis is to run the nf-core/viralrecon pipeline. But first we need to prepare our input files.

+
+

9.3.1 Samplesheet

+

For Illumina data, we need a samplesheet CSV file with three columns, indicating sample name (first column) and the respective FASTQ file paths for read 1 (second column) and read 2 (third column).

+

Because our FASTQ file names are not very user-friendly, we used some command-line tricks to help us produce this table:

+
# list read 1 files and save output in a temporary file
+ls data/reads/*_1.fastq.gz > read1_filenames.txt
+
+# list read 2 files and save output in a temporary file
+ls data/reads/*_2.fastq.gz > read2_filenames.txt
+
+# initiate a file with column names
+echo "fastq_1,fastq_2" > samplesheet.csv
+
+# paste the two temporary files together, using comma as a delimiter
+paste -d "," read1_filenames.txt read2_filenames.txt >> samplesheet.csv
+
+# remove the two temporary files
+rm read1_filenames.txt read2_filenames.txt
+

These commands resulted in creating a file called samplesheet.csv, which contains the following:

+
head samplesheet.csv
+
fastq_1,fastq_2
+data/reads/SRR17051908_1.fastq.gz,data/reads/SRR17051908_2.fastq.gz
+data/reads/SRR17051916_1.fastq.gz,data/reads/SRR17051916_2.fastq.gz
+data/reads/SRR17051923_1.fastq.gz,data/reads/SRR17051923_2.fastq.gz
+data/reads/SRR17051932_1.fastq.gz,data/reads/SRR17051932_2.fastq.gz
+data/reads/SRR17051935_1.fastq.gz,data/reads/SRR17051935_2.fastq.gz
+data/reads/SRR17051951_1.fastq.gz,data/reads/SRR17051951_2.fastq.gz
+data/reads/SRR17051953_1.fastq.gz,data/reads/SRR17051953_2.fastq.gz
+data/reads/SRR17054503_1.fastq.gz,data/reads/SRR17054503_2.fastq.gz
+data/reads/SRR17088917_1.fastq.gz,data/reads/SRR17088917_2.fastq.gz
+

So, we programmatically created the last two columns of our file. We then opened this CSV file in Excel to add another column “sample” where we included our sample names and saved the file again as a CSV format. Here are the top few rows of the final file:

+
head samplesheet.csv
+
sample,fastq_1,fastq_2
+ZA01,data/reads/SRR17051908_1.fastq.gz,data/reads/SRR17051908_2.fastq.gz
+ZA02,data/reads/SRR17051923_1.fastq.gz,data/reads/SRR17051923_2.fastq.gz
+ZA03,data/reads/SRR17051916_1.fastq.gz,data/reads/SRR17051916_2.fastq.gz
+ZA04,data/reads/SRR17051953_1.fastq.gz,data/reads/SRR17051953_2.fastq.gz
+ZA05,data/reads/SRR17051951_1.fastq.gz,data/reads/SRR17051951_2.fastq.gz
+ZA06,data/reads/SRR17051935_1.fastq.gz,data/reads/SRR17051935_2.fastq.gz
+ZA07,data/reads/SRR17051932_1.fastq.gz,data/reads/SRR17051932_2.fastq.gz
+ZA08,data/reads/SRR17054503_1.fastq.gz,data/reads/SRR17054503_2.fastq.gz
+ZA09,data/reads/SRR17088930_1.fastq.gz,data/reads/SRR17088930_2.fastq.gz
+
+
+

9.3.2 Running Viralrecon

+

Now we are ready to run the nf-core/viralrecon pipeline (see Section 4.3 for details). We saved our command in a script (scripts/01-run_viralrecon.sh), which we created with the command line text editor nano. This ensures that our analysis is reproducible and traceable (we can go back to the script to see how the analysis was run).

+

First, we activate our software environment, to ensure Nextflow is available to us:

+
mamba activate nextflow
+

Then, we run our script with:

+
bash scripts/01-run_viralrecon.sh
+

Which will start executing the pipeline.

+

For reference, here is the command included in that script:

+
nextflow run nf-core/viralrecon \
+  -r 2.6.0 -profile singularity \
+  --max_memory '15.GB' --max_cpus 8 \
+  --platform illumina \
+  --input samplesheet.csv \
+  --outdir results/viralrecon \
+  --protocol amplicon \
+  --genome 'MN908947.3' \
+  --primer_set artic \
+  --primer_set_version 3 \
+  --skip_assembly --skip_asciigenome \
+  --skip_pangolin --skip_nextclade
+
+
+
+ +
+
+The \ in long commands +
+
+
+

In the command above, you will notice several \ at the end of each line. This is indicating that we want to continue writing our command in the next line. Notice that the last line does not include \, because that is the end of the command. This is very useful when commands are very long, because it makes the code more readable.

+
+
+

After running the pipeline, we notice the following message:

+
-[nf-core/viralrecon] 2 samples skipped since they failed Bowtie2 1000 mapped read threshold:
+    201: ZA09
+    176: ZA12
+

This indicates that two samples - ZA09 and ZA12 - had very few reads aligned to the SARS-CoV-2 genome. Could the reason be that they had very few reads to start with? We can quickly investigate this hypothesis by counting the number of lines in the FASTQ files from these samples:

+
# FASTQ file for ZA09
+zcat data/reads/SRR17088930_1.fastq.gz | wc -l
+
698044
+

Since each sequence takes 4 lines in a FASTQ format, this indicates that this sample had 698044/4 = 124511 reads. That’s certainly more than 1000 reads, so the reason the sample failed must be something else. We will investigate this further in the next section.

+
+
+
+

9.4 Consensus Quality

+
+
+
+ +
+
+Note +
+
+
+

See Section 5.2, if you need to revise how to assess the quality of consensus sequences.

+
+
+
+

9.4.1 General Metrics

+

We used the MultiqQC report to assess the initial quality of our samples. The quality report can be found in results/viralrecon/multiqc/multiqc_report.html.

+

We paid particular attention to:

+
    +
  • Number of reads mapped to the reference genome.
  • +
  • Median depth of coverage.
  • +
  • Percentage of the genome with missing bases (‘N’).
  • +
  • Number of SNP + Indel variants.
  • +
+
+
+

+
Example MultiQC output, highlighting the main columns with quality information that will be collected for the final report. Note that some columns have been ommited here for clarity.
+
+
+

We noted that:

+
    +
  • 2 samples - ZA09 and ZA12 - completely failed. This was already noted after we ran the pipeline, above. The reason seems to be because these samples have a very low percentage of non-human reads (less than 2%), indicating that the sequenced material was mostly human. Because of this, there were not enough reads to assemble a genome.
  • +
  • 2 other samples - ZA10 and ZA23 - had > 90% missing bases, also indicating a very poor assembly. The reason was the same as above, both samples had low % of non-human reads.
  • +
  • 1 samples - ZA14 - had ~39% missing bases. In this case the reason was not a low number of mapped reads. For example, sample ZA13 had a very similar median depth of coverage (121 vs 161 in ZA14) and even fewer mapped reads (24k vs 60k in ZA14). But ZA13 only had 2% missing bases. However, upon inspection of the amplicon heatmap, we detected that several amplicons were not properly amplified in ZA14 compared to ZA13. Therefore, the reason for high % missing bases in ZA14 was due to low amplification efficiency in this sample.
  • +
  • There was some systematic dropout for some amplicons, in particular nCoV-2019_64 had very low amplification in several of the samples. Of note was also nCoV-2019_73, and other neighbouring amplicons.
  • +
+
+
+

+
Amplicon depth of sequencing (or coverage) across samples, from the MultiQC report. Darker regions indicate systematic amplicon dropout, probably due to issues during the PCR amplification.
+
+
+

Besides the MultiQC report, the pipeline also outputs a CSV file with collected summary metrics (equivalent to the first table on the report): results/viralrecon/multiqc/summary_variants_metrics_mqc.csv. We will use this file later to join this information with our metadata and lineage assignment using the R software (detailed in “Integration & Visualisation” section, below).

+
+
+

9.4.2 Variants

+

We also looked at the table of variants obtained from the pipeline. This is output in results/viralrecon/variants/ivar/variants_long_table.csv. This table can be very useful to keep track of particular mutations that may be increasing over time. Later, we will tidy this table to attach to our reported results (“Integration & Visualisation” section).

+

But for now, we will explore this table to address a few more quality-related questions. We opened this table in Excel to answer the following:

+
    +
  • Where there samples with a high number of intermediate allele frequencies? This could indicate mixed samples due to cross-contamination.
    +
  • +
  • Where there samples with frameshift mutations? These mutations should be rare because they are highly disruptive to the functioning of the virus. So, their occurrence may be due to errors rather than a true mutation and it’s good to make a note of this.
  • +
+

By manual inspection of this table (and using the “filter” feature in Excel), we found 74 variants with alternative allele frequency less than 75%. These were spread across samples, all samples having less than 10 such low-frequency mutations, and most samples having less than 5. Sample ZA23 - which we had previously highlighted has having a high % missing bases (39%) - had 8 low-frequency mutations out of a total of 48 mutations in this sample (~16%), suggesting further quality issues in this sample.

+
+
+

+
Variants table output by viralrecon, with a summary of the main columns of interest. Note that this table also includes a column with lineage assignment (not shown in this snapshot). Remember that at this stage we mostly ignore this column, as viralrecon does not use the most up-to-date version of the lineage databases.
+
+
+
+
+
+

9.5 Clean FASTA

+

The pipeline outputs the consensus sequences in results/viralrecon/variants/ivar/consensus/bcftools/*.consensus.fa (one file for each sample). For downstream analysis, it is convenient to combine all these sequences into a single file, and also clean the sequence names (to remove some text – ” MN908947.3” –, which is added by the bcftools variant caller).

+

We created a new script to clean our consensus FASTA files, which we ran with bash scripts/02-clean_fasta.sh:

+ +
# combine and clean FASTA files
+cat results/viralrecon/variants/ivar/consensus/bcftools/*.consensus.fa | sed 's| MN908947.3||' > report/consensus.fa
+

This command does two things:

+
    +
  • Combine all our FASTA consensus sequences into a single file (using cat).
  • +
  • Clean the sequence names (using sed).
  • +
+

The output was saved as a new FASTA file: report/consensus.fa.

+ +
+

9.5.1 Missing Intervals

+

As a further quality check, we also generated a table of missing intervals (indicated by the N character in the FASTA sequences). We used the seqkit software to achieve this.

+

First, we activate our software environment:

+
mamba activate seqkit
+

Then, we ran the script bash scripts/03-missing_intervals.sh, which includes the following command:

+
seqkit locate -i -P -G -M -r -p "N+" report/consensus.fa > results/missing_intervals.tsv
+

This software outputs a tab-delimited table, which we saved as results/missing_intervals.tsv. The table looks like this (only the top few rows are shown):

+
seqID  patternName  pattern  strand  start  end
+ZA01   N+           N+       +       1      54
+ZA01   N+           N+       +       22771  22926
+ZA01   N+           N+       +       23603  23835
+ZA01   N+           N+       +       26948  26948
+ZA01   N+           N+       +       26968  27137
+ZA01   N+           N+       +       29801  29867
+ZA02   N+           N+       +       1      54
+ZA02   N+           N+       +       22771  22921
+ZA02   N+           N+       +       23603  23835
+

We opened this file missing_intervals.tsv in Excel and quickly calculated the length of each interval. We noted that two samples - ZA10 and ZA23 - both have a continuous interval of over 18kb missing bases, which is not surprising as we had already identified these samples has having >90% missing data. We make a note of these samples as being possibly problematic in downstream analysis steps.

+
+
+
+

9.6 Downstream Analyses

+

Based on the clean consensus sequences, we then perform several downstream analysis.

+
+

9.6.1 Lineage Assignment

+
+
+
+ +
+
+Note +
+
+
+

See Section 6.1, if you need to revise how lineage assignment works.

+
+
+

Although the Viralrecon pipeline runs Pangolin and Nextclade, it does not use the latest version of these programs (because lineages evolve so fast, the nomenclature constantly changes). An up-to-date run of both of these tools can be done using each of their web applications:

+ +

However, for automation, reproducibility and traceability purposes, we used the command line versions of these tools, and included their analysis in two scripts.

+
+ +
+
+

For Nextclade, we first activate the software environment:

+
mamba activate nextclade
+

And then we ran the script bash scripts/04-nextclade.sh, which contains the following commands:

+
# get nextclade data
+nextclade dataset get --name sars-cov-2 --output-dir resources/nextclade_background_data
+
+# run nextclade
+nextclade run --input-dataset resources/nextclade_background_data/ --output-all results/nextclade report/consensus.fa
+

The first command downloads the latest version of the Nextclade background data using nextclade dataset. We use that data as input to the second command (nextclade run) to make sure it runs with the most up-to-date lineages.

+
+
+

For Pangolin, we first activate the software environment:

+
mamba activate pangolin
+

And then we ran the script bash/04-pangolin.sh, which contains the following commands:

+
# update pangolin data
+pangolin --update-data
+
+# run pangolin
+pangolin --outdir results/pangolin/ --outfile pango_report.csv report/consensus.fa
+

Similarly to before, we first ran pangolin --update-data to ensure we were using the latest lineages available. We can check the version of the data used with pangolin --all-versions (at the time we ran this we had pangolin-data: 1.23.1).

+
+
+
+

Both of these tools output CSV files, which can be open in Excel for further examination.
+Opening the pangolin results (results/pangolin/pango_report.csv), we noticed that the two problematic samples – ZA10 and ZA23 – failed the QC due to high fraction of missing data. The other samples all seemed to have been assigned to “Omicron” variant with a high support.

+

Opening the nextclade results (results/nextclade/nextclade.tsv), we noticed that the two problematic samples were classified as “recombinant”! We know from our quality control that we should not trust this assessment, and that most likely these are bad quality samples, not true recombinant lineages. Nextclade is more relaxed in assigning samples to lineages, so we should always check the QC status as well. We will notice that both of these samples were assigned QC status “bad”, due to their high percentage of missing data (nextclade uses a stringent threshold of 3000 sites, or ~10%, missing data). Two other samples had “bad” QC status. Sample ZA14 due to high % of missing data, and sample ZA18 from a mixture of a high number of private mutations and the presence of a frameshift mutation in ORF1b.

+

Like before, we will do further analysis (and visualisation) of these data using the software R, in the section “Integration & Visualisation”, detailed below.

+
+
+

9.6.2 Phylogeny

+
+
+
+ +
+
+Note +
+
+
+

See Section 7.1, if you need to revise how to build phylogenetic trees.

+
+
+

Although a tool such as Nextclade can place our samples in a global phylogeny context, sometimes it may be convient to build our own phylogenies. This requires three steps:

+
    +
  • Producing a multiple sequence alignment from all consensus sequences.
  • +
  • Tree inference.
  • +
  • Tree visualisation and annotation.
  • +
+

Before our analysis, we first activated our software environment:

+
mamba activate phylo
+

We performed the first two steps with the following script, which we ran with bash scripts/05-phylogeny.sh:

+
# alignment
+mkdir -p results/mafft
+mafft --6merpair --maxambiguous 0.2 --addfragments report/consensus.fa resources/reference/sarscov2.fa > results/mafft/alignment.fa
+
+# tree inference
+mkdir -p results/iqtree
+iqtree2 -s results/mafft/alignment.fa --prefix results/iqtree/consensus
+

The output of iqtree includes a tree file, which can be visualised using FigTree (or online using Microreact). The figure below shows our tree, which shows all our samples fall mostly clustering together. This makes sense, as all our samples were classified as “Omicron (BA.1-related)”.

+

It is worth noting that the samples ZA10, ZA14 and ZA23 are not included in this phylogeny, as they contained >20% missing data (we used that threshold with MAFFT alignment, option --maxambiguous 0.2). Also, note that sample ZA18, which Nextclade had identified as “bad” QC (due to excess private mutations) also appears slightly separated from the other samples in the tree (the sample in red on the tree). The sample still clusters well with the rest, suggesting we can probably trust that it is indeed an Omicron variant, however the excess of private mutations (which may be due to sequencing errors) is making it stand apart from the others in the phylogeny.

+
+
+

+
Phylogenetic tree for our samples. All samples were classified as lineage BA.1 (and some of its sub-lineages), corresponding to the Omicron VOC. The tips of the tree are coloured according to Nextclade’s QC status: green = “good”; blue = “mediocre”; red = “bad”.
+
+
+
+
+

9.6.3 Clustering

+

We identified groups of similar sequences in our data using the software civet (Cluster Investigation and Virus Epidemiology Tool). This software compares our samples with a background dataset of our choice, which givus us more context for our analysis. In this case we are using the example background data that comes with civet. However, in a real-world analysis, it would have been ideal to choose local samples as background data. For example, we could download samples from South Africa from around the time period of our sample collection, from GISAID following the instructions on the civet documentation (you need an account on GISAID to obtain these data).

+

For this example, we already prepared civet background dataset saved in resources/civet_background_data.

+

Before our analysis, we first activate our software environment:

+
mamba activate civet
+

Then, we ran the script bash scripts/06-civet.sh, which contains the following code:

+
# run civet analysis
+civet \
+  -i sample_info.csv \
+  -f report/consensus.fa \
+  -icol sample \
+  -idate collection_date \
+  -d resources/civet_background_data/ \
+  -o results/civet
+

The result of this analysis includes an interactive HTML report (in results/civet/civet.html). We can see that our samples were grouped into a single catchment. This makes sense from our previous lineage/variant analysis: all our samples were classfied as Omicron VOC. We can see that our samples seem to be more diverged from the Australian BA.1 sample present in the background data, with new SNPs in our samples creating a longer branch in the tree.

+
+
+

+
Results from civet report for catchment 1, showing the phylogeny in the context of a BA.1 Australian sample from the background data.
+
+
+

Civet also outputs a CSV file (results/civet/master_metadata.csv), which includes the catchment that each sample was assigned to. We will use this CSV file later to integrate this information with other parts of our analysis, in R, detailed in the “Integration & Visualisation” section.

+
+
+
+

9.7 Integration & Visualisation

+

At this point in our analysis, we have several tables with different pieces of information:

+
    +
  • sample_info.csv → the original table with metadata for our samples.
  • +
  • results/viralrecon/multiqc/medaka/summary_variants_metrics_mqc.csv → quality metrics from the MultiQC report generated by the viralrecon pipeline.
  • +
  • results/nextclade/nextclade.tsv → the results from Nextclade.
  • +
  • results/pangolin/pango_report.csv → the results from Pangolin.
  • +
  • results/civet/master_metadata.csv → the results from the civet analysis, namely the catchment (or cluster) that each of our samples was grouped into.
  • +
+

To consolidate our analysis, we tidied and integrated the information from across these different files, into a single table using the software R. The script used to do this is in scripts/07-data_integration.R. Because this is an R script, we opened it in RStudio to execute the code.

+

The output of our script is a new tab-delimited table, which we saved in report/consensus_metrics.tsv, and contains the following columns:

+
    +
  • sample → sample ID.
  • +
  • collection_date → date of collection day.
  • +
  • collection_week → date of collection week (useful for summarising/visualising counts per-week).
  • +
  • country → country of origin.
  • +
  • latitude/longitude → latitude and longitude of collection.
  • +
  • n_mapped_reads → number of mapped reads.
  • +
  • median_depth → median depth.
  • +
  • pct_missing → percentage of missing data.
  • +
  • pct_coverage → percentage of coverage.
  • +
  • n_variants → number of SNP + indel variants detected.
  • +
  • nextclade → nextclade clade.
  • +
  • qc_status → QC status as determined by Nextclade (“bad”, “mediocre”, “good”).
  • +
  • lineagePangolin lineage.
  • +
  • who_variant → variant of concern designation.
  • +
  • catchment → catchment group from Civet.
  • +
+

This table, which aggregates information from many of the tools we used, was then used to produce different visualisations of our analysis. These visualisations were also done using the R software (scripts/08-visualisation.R), and integrated into a report, shown below.

+ +
+
+

9.8 Bonus: Full Workflow

+

Although we have ran each of the steps of our analysis individually (each in their own script), now that we have everything working, we could integrate all these steps into a single “master” script:

+
#!/bin/bash
+
+# make mamba activate command available
+eval "$(conda shell.bash hook)"
+source $(mamba info --base)/etc/profile.d/mamba.sh
+
+# make report directory
+mkdir -p report
+
+mamba activate nextflow
+# run viralrecon
+nextflow run nf-core/viralrecon \
+  -r 2.6.0 -profile singularity \
+  --max_memory '15.GB' --max_cpus 8 \
+  --platform illumina \
+  --input samplesheet.csv \
+  --outdir results/viralrecon \
+  --protocol amplicon \
+  --genome 'MN908947.3' \
+  --primer_set artic \
+  --primer_set_version 3 \
+  --skip_assembly --skip_asciigenome \
+  --skip_pangolin --skip_nextclade
+
+# combine and clean FASTA files
+cat results/viralrecon/variants/ivar/consensus/bcftools/*.consensus.fa | sed 's| MN908947.3||' > report/consensus.fa
+
+mamba activate seqkit
+# create missing bases TSV file
+seqkit locate -i -P -G -M -r -p "N+" report/consensus.fa > results/missing_intervals.tsv
+
+mamba activate nextclade
+# get nextclade data
+nextclade dataset get --name sars-cov-2 --output-dir resources/nextclade_background_data
+
+# run nextclade
+nextclade run --input-dataset resources/nextclade_background_data/ --output-all results/nextclade report/consensus.fa
+
+mamba activate pangolin
+# update pangolin data
+pangolin --update-data
+
+# run pangolin
+pangolin --outdir results/pangolin/ --outfile pango_report.csv report/consensus.fa
+
+mamba activate phylo
+# alignment
+mkdir -p results/mafft
+mafft --6merpair --maxambiguous 0.2 --addfragments report/consensus.fa resources/reference/sarscov2.fa > results/mafft/alignment.fa
+
+# tree inference
+mkdir -p results/iqtree
+iqtree2 -s results/mafft/alignment.fa --prefix results/iqtree/consensus
+
+# data integration and cleaning
+Rscript scripts/07-data_integration.R
+

Notice that we included the R script that does the data cleaning here, using the Rscript program that allows to execute an R script from the command-line.

+

Having this “master” script, we could run all these steps from start-to-finish with a single command, which can be very useful if you want to fully automate your analysis across multiple runs.

+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/03-case_studies/03-eqa.html b/materials/03-case_studies/03-eqa.html new file mode 100644 index 0000000..475bc65 --- /dev/null +++ b/materials/03-case_studies/03-eqa.html @@ -0,0 +1,1719 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

10  EQA (Exercise)

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Learning Objectives +
+
+
+

This section is an extended self-paced practice applying the concepts covered in previous sections.

+

By the end of this practice, you should be able to:

+
    +
  • Prepare all the files necessary to run the consensus pipeline.
  • +
  • Run the viralrecon pipeline to generate FASTA consensus from raw FASTQ files.
  • +
  • Assess and collect several quality metrics for the consensus sequences.
  • +
  • Clean output files, in preparation for other downstream analysis.
  • +
  • Assign sequences to lineages.
  • +
  • Contextualise your sequences in other background data and cluster them based on phylogenetic analysis.
  • +
  • Integrate the metadata and analysis results to generate timeseries visualisations of your data.
  • +
+
+
+

GenQA logo

+

External Quality Assessment (EQA) is a procedure that allows laboratories to assess the quality of the data they produce, including its analysis. For genomic analysis of SARS-CoV-2, a standard panel of samples has been developed by the GenQA consortium, which is part of UK NEQAS, a non-profit that designs EQA protocols for a range of applications. GenQA’s panel of samples includes lab-cultured SARS-CoV-2 samples of known origin, enabling laboratories to assess whether their sequencing and bioinformatic pipelines correctly identify expected mutations and lineage assignments for each of the samples in the panel.

+
+ +
+
+

EQA programs such as this one help to provide assurance of the diagnostic testing results obtained by a lab. One of the key things to consider is that, for reliable assessment, samples should be processed in the same way as patient samples. Since these samples are for quality assessment, it may be tempting to assign the samples to more experienced staff, include extra checks, increase sequencing, etc. However, doing that would give a false impression of the performance of your own lab.

+

Evaluating the results of EQA sample processing is critical for labs to understand where their procedures can be improved. This may include staff training, purchase of new equipment, or updating the reagents and kits used for sample processing.

+

EQA panels are often updated, to reflect any new and emerging pathogens, allowing the labs to assess the suitability of the protocols used to detect them. It also helps raise awareness of any atypical variants of pathogens that may exist in the environment.

+
+
+
+

In this case study, we are going to analyse samples from the GenQA panel, to helps us assess the quality of our bioinformatic analysis and extract key pieces of information to be reported. Two panels are available, which include the following variants:

+
+ +
+
+
    +
  • 1x Alpha
  • +
  • 1x Beta
  • +
  • 1x Delta
  • +
  • 2x Gamma
  • +
  • 1x Omicron
  • +
+
+
+
    +
  • 2x Omicron BA.1
  • +
  • 1x Omicron BA.2
  • +
  • 2x Delta
  • +
  • 1x Gamma
  • +
+
+
+
+

From the sequencing data analysis, we will address the following:

+ +

We should also produce several essential output files, which would usually be necessary to upload our data to public repositories:

+ +
+

10.1 Pipeline Overview

+

We will start our analysis with FASTQ files produced by our sequencing platforms (Illumina and Nanopore are considered here). These FASTQ files will be used with the nf-core/viralrecon Nextflow pipeline, allowing us to automate the generation of consensus sequences and produce several quality control metrics essential for our downstream analysis and reporting.

+

Critical files output by the pipeline will need to be further processed, including combining and cleaning our consensus FASTA files. Using these clean files, we can then proceed to downstream analysis, which includes assigning each sample to the most up-to-date Pango lineage, Nextclade clade and WHO designation. Finally, we can do more advanced analysis, including the idenfication of sample clusters based on phylogenetic analysis, or produce timeseries visualisations of variants of concern. With all this information together, we will have the necessary pieces to submit our results to public repositories and write reports to inform public health decisions.

+

+
+
+

10.2 Preparing Files

+

Before we start our work, it’s always a good idea to setup our directory structure, so we keep our files organised as the analysis progresses. There is no standard way to do this, but here are some suggested directories:

+
    +
  • data → contains the sequencing data for the particular run or project you are working on. Data may sometimes be stored in a separate server, in which case you may not need to create this directory. Generally, you should leave the original data unmodified, in case something goes wrong during your analysis, you can always re-run it from the start.
  • +
  • results → to save results of the analysis.
  • +
  • scripts → to save scripts used to run the analysis. You should always try to save all the commands you run in a script, this way you will have a record of what was done to produce the results, and it makes your life easier if you want to run the analysis again on a new set of data.
  • +
  • report → to save the final files and documents that we report to our colleagues or upload to public repositories.
  • +
  • resources → files that you download from the internet could be saved here. For example, the reference genome sequence, background data used for phylogenetics, etc. Sometimes you may share these resources across multiple projects, in which case you could have this folder somewhere else that you can access across multiple projects.
  • +
+

On our computers, we have a directory in ~/Documents/eqa_workshop, where we will do all our analysis. We already include the following:

+
    +
  • data → with the results of the EQA sample sequencing.
  • +
  • resources → where we include the SARS-CoV-2 reference genome, and some background datasets that will be used with some of the tools we will cover.
  • +
  • scripts → where we include some scripts that we will use towards the end of the workshop. You should also create several scripts during the workshop, which you will save here.
  • +
  • sample_info.csv → a table with some metadata for our samples.
  • +
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Your first task is to create two new directories in the project folder called report and results. You can do this either using the file explorer or from the command line (using the mkdir command).

+
+
+
+
+
+
+

10.2.1 Data

+

Regardless of which platform you used to sequence your samples, the analysis starts with FASTQ files (if you need a reminder of what a FASTQ file is, look at the Introduction to NGS section). However, the organisation of these files is slightly different depending on the platform, and is detailed below.

+
+ +
+
+

Typically, Nanopore data is converted to FASTQ format using the program Guppy. This software outputs the files to a directory called fastq_pass. Within this directory, it creates further sub-directories for each sample barcode, which are named barcodeXX (XX is the barcode number). Finally, within each barcode directory there is one (or sometimes more) FASTQ files corresponding to that sample.

+

You can look at the files you have available from the command line using:

+
ls data/fastq_pass
+
+
+

The Illumina files come as compressed FASTQ files (.fq.gz format) and there’s two files per sample, corresponding to read 1 and read 2. This is indicated by the file name suffix:

+
    +
  • *_1.fq.gz for read 1
  • +
  • *_2.fq.gz for read 2
  • +
+

You can look at the files you have available from the command line using:

+
ls data/
+
+
+
+
+
+

10.2.2 Metadata

+

A critical step in any analysis is to make sure that our samples have all the relevant metadata associated with them. This is important to make sense of our results and produce informative reports at the end. There are many types of information that can be collected from each sample (revise the Genomic Surveillance > Metadata section of the materials to learn more about this). For effective genomic surveillance, we need at the very minimum three pieces of information:

+
    +
  • When: date when the sample was collected (not when it was sequenced!).
  • +
  • Where: the location where the sample was collected (not where it was sequenced!).
  • +
  • How: how the sample was sequenced (sequencing platform and protocol used).
  • +
+

Of course, this is the minimum metadata we need for a useful analysis. However, the more information you collect about each sample, the more questions you can ask from your data – so always remember to record as much information as possible for each sample.

+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Note: If you are using our pre-sequenced data, you can skip this exercise.

+

We already provide some basic metadata for these samples in the file sample_info.csv:

+
    +
  • sample → the sample ID.
  • +
  • collection_date → the date of collection for the sample in the format YYYY-MM-DD.
  • +
  • country → the country of origin for this sample.
  • +
  • expected_lineage and expected_voc → because we are using the EQA panel, we know what lineage and variant-of-concern (VOC) each sample belongs to. We will use this later to assess the quality of our analysis.
  • +
+

There is however some information missing from this table, which you may have available at this point. Open this file in Excel and create the following columns:

+
    +
  • ct → Ct value from qPCR viral load quantification.
  • +
  • sequencing_instrument → the model for the sequencing instrument used (e.g. NovaSeq 6000, MinION, etc.).
  • +
  • sequencing_protocol_name → the type of protocol used to prepare the samples (e.g. ARTIC).
  • +
  • amplicon_primer_scheme → for amplicon protocols, what version of the primers was used (e.g. V3, V4.1)
  • +
  • Specific columns for Oxford Nanopore data, which are essential for the bioinformatic analysis: +
      +
    • ont_nanopore → the version of the pores used (e.g. 9.4.1 or 10.4.1).
    • +
    • ont_guppy_version → the version of the Guppy software used for basecalling.
    • +
    • ont_guppy_mode → the basecalling mode used with Guppy (usually “fast”, “high”, “sup” or “hac”).
    • +
  • +
+

This will ensure that in the future people have sufficient information to re-run the analysis on your data.

+
+
+
+
+
+
+
+
+ +
+
+Dates in Spreadsheet Programs +
+
+
+

Note that programs such as Excel often convert date columns to their own format, and this can cause problems when analysing data later on. For example, GISAID wants dates in the format YYYY-MM-DD, but by default Excel displays dates as DD/MM/YYYY.
+You can change how Excel displays dates by highlighting the date column, right-clicking and selecting Format cells, then select “Date” and pick the format that matches YYYY-MM-DD. However, every time you open the CSV file, Excel annoyingly converts it back to its default format!

+

To make sure no date information is lost due to Excel’s behaviour, it’s a good idea to store information about year, month and day in separate columns (stored just as regular numbers).

+
+
+
+
+
+

10.3 Consensus Assembly

+

At this point we are ready to start our analysis with the first step: generating a consensus genome for our samples. We will use a standardised pipeline called viralrecon, which automates most of this process for us, helping us be more efficient and reproducible in our analysis.

+
+
+
+ +
+
+Note +
+
+
+

See Section 4.1, if you need to revise how the nf-core/viralrecon pipeline works.

+
+
+
+

10.3.1 Samplesheet

+

The first step in this process is to prepare a CSV file with information about our sequencing files, which will be used as the input to the viralrecon pipeline.
+The pipeline’s documentation gives details about the format of this samplesheet, depending on whether you are working with Illumina or Nanopore data.

+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Note: If you are using our pre-sequenced data, you can skip this exercise.

+

Using Excel, produce the input samplesheet for nf-core/viralrecon, making sure that you save it as a CSV file (FileSave As… and choose “CSV” as the file format).

+

Note: make sure that the sample names you use in this samplesheet match those in the metadata sample_info.csv file (pay attention to things like spaces, uppercase/lowercase, etc. – make sure the names used match exactly).

+

If you are working with Illumina data, you should check the tip below.

+
+ +Illumina samplesheet: saving time with the command line! + +

You can save some time (and a lot of typing!) in making the Illumina samplesheet using the command line to get a list of file paths. For example, if your files are saved in a folder called data, you could do:

+
# list read 1 files and save output in a temporary file
+ls data/*_1.fq.gz > read1_filenames.txt
+
+# list read 2 files and save output in a temporary file
+ls data/*_2.fq.gz > read2_filenames.txt
+
+# initiate a file with column names
+echo "fastq_1,fastq_2" > samplesheet.csv
+
+# paste the two temporary files together, using comma as a delimiter
+paste -d "," read1_filenames.txt read2_filenames.txt >> samplesheet.csv
+
+# remove the two temporary files
+rm read1_filenames.txt read2_filenames.txt
+

Now, you can open this file in Excel and continue editing it to add a new column of sample names.

+
+
+
+
+
+
+
+
+

10.3.2 Running Viralrecon

+

The next step in our analysis is to run the nf-core/viralrecon pipeline. The way the command is structured depends on which kind of data we are working with. There are many options that can be used to customise the pipeline, but typical commands are shown below for each platform.

+
+ +
+
+
nextflow run nf-core/viralrecon \
+  -r 2.6.0 -profile singularity \
+  --max_memory '15.GB' --max_cpus 4 \
+  --platform nanopore \
+  --input SAMPLESHEET_CSV \
+  --fastq_dir data/fastq_pass/ \
+  --outdir results/viralrecon \
+  --protocol amplicon \
+  --genome 'MN908947.3' \
+  --primer_set artic \
+  --primer_set_version PRIMER_VERSION \
+  --artic_minion_caller medaka \
+  --artic_minion_medaka_model MEDAKA_MODEL \
+  --skip_assembly --skip_asciigenome \
+  --skip_pangolin --skip_nextclade
+

You need to check which model the medaka software should use to process the data. You need three pieces of information to determine this:

+
    +
  • The version of the nanopores used (usually 9.4.1 or 10.4.1).
  • +
  • The sequencing device model (MinION, GridION or PromethION).
  • +
  • The mode used in the Guppy software for basecalling (“fast”, “high”, “sup” or “hac”).
  • +
  • The version of the Guppy software.
  • +
+

Once you have these pieces of information, you can see how the model is specified based on the model files available on the medaka GitHub repository. For example, if you used a flowcell with chemistry 9.4.1, sequenced on a MinION using the “fast” algorithm on Guppy version 5.0.7, the model used should be r941_min_fast_g507.
+Note that in some cases there is no model for recent versions of Guppy, in which case you use the version for the latest version available. In our example, if our version of Guppy was 6.1.5 we would use the same model above, since that’s the most recent one available.

+
+
+
nextflow run nf-core/viralrecon \
+  -r 2.6.0 -profile singularity \
+  --max_memory '15.GB' --max_cpus 4 \
+  --platform illumina \
+  --input SAMPLESHEET_CSV \
+  --outdir results/viralrecon \
+  --protocol amplicon \
+  --genome 'MN908947.3' \
+  --primer_set artic \
+  --primer_set_version PRIMER_VERSION \
+  --skip_assembly --skip_asciigenome \
+  --skip_pangolin --skip_nextclade
+
+
+
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Your next task is to run the pipeline on your data. However, rather than run the command directly from the command line, let’s save it in a shell script – for reproducibility and as a form of documenting our analysis.

+
    +
  • First, open the README.txt file found in your folder, which has some details about your samples, including the amplicon primer scheme used.
  • +
  • Using a text editor, create a new shell script and save it in scripts/01-run_viralrecon.sh. You can either use the command-line text editor nano or use Gedit, which comes installed with Ubuntu.
  • +
  • Copy the command shown above (either Illumina or Nanopore, depending on your data) to your new script.
  • +
  • Adjust the code, to use the correct input samplesheet file, primer scheme and, in the case of ONT, the medaka model. For information about primer scheme options see the “Amplicon Primer Schemes” appendix.
  • +
  • Activate the software environment to use Nextflow: mamba activate nextflow.
  • +
  • Save the script and run it from the command line using bash scripts/01-run_viralrecon.sh.
  • +
+

If you need a reminder of how to work with shell scripts, revise the Shell Scripts section of the accompanying Unix materials.

+
+
+
+
+
+
+
+
+ +
+
+Maximum Memory and CPUs +
+
+
+

In our Nextflow command above we have set --max_memory '15.GB' --max_cpus 8 to limit the resources used in the analysis. This is suitable for the computers we are using in this workshop. However, make sure to set these options to the maximum resources available on the computer where you process your data.

+
+
+
+
+
+

10.4 Consensus Quality

+

Once your workflow is complete, it’s time to assess the quality of the assembly.

+
+
+
+ +
+
+Note +
+
+
+

See Section 5.2, if you need to revise how to assess the quality of consensus sequences.

+
+
+
+

10.4.1 Coverage

+

At this stage we want to identify issues such as:

+
    +
  • Any samples which have critically low coverage. There is no defined threshold, but samples with less than 85% coverage should be considered carefully.
  • +
  • Any problematic regions that systematically did not get amplified (amplicon dropout).
  • +
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

To assess the quality of our assemblies, we can use the MultiQC report generated by the pipeline, which compiles several pieces of information about our samples. If you need a reminder of where to find this file, consult the “Consensus assembly” section of the materials, or the Viralrecon output documentation.

+

Open the quality report and try to answer the following questions:

+
    +
  • Were there any samples with more than 15% missing bases (’N’s)?
  • +
  • Were there any samples with a median depth of coverage <20x?
  • +
  • Were there any problematic amplicons with low depth of coverage across multiple samples?
  • +
+

Make a note of any samples that you think are problematic. You can discuss with your colleagues and compare your results/conclusions to see if you reach similar conclusions.

+
+
+
+
+
+
+
+

10.4.2 Variants

+

The viralrecon pipeline outputs a table with information about SNP/Indel variants as a CSV file named variants_long_table.csv. It is important to inspect the results of this file, to identify any mutations with severe effects on annotated proteins, or identify samples with an abnormal high number of “mixed” bases.

+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Open the variants_long_table.csv file and answer the following questions. If you need a reminder of where to find this file, consult the “Consensus assembly” section of the materials, or the Viralrecon output documentation.

+
    +
  • How many variants have allele frequency < 75%?
  • +
  • Does any of the samples have a particularly high number of these low-frequency variants, compared to other samples? (This could indicate cross-contamination of samples)
  • +
  • Investigate if there are any samples with frameshift mutations. These mutations should be rare because they are highly disruptive to the functioning of the virus. So, their occurrence may be due to errors rather than a true mutation and it’s good to make a note of this.
  • +
+

If you need a reminder of the meaning of the columns in this file, consult the Consensus > Mutation/Variant Analysis section of the materials.

+

Make a note of any samples that you think may be problematic, either because they have a high number of low-frequency variants or because they have frameshift mutations.

+

(Optional)
+If you identify samples with frameshift mutations, open the BAM file of the sample using the software IGV. Go to the position where the mutation is located, and try to see if there is evidence that this mutation is an error (for example, if it’s near an homopolymer).

+
+
+
+
+
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

The samples we are using in this analysis come from a standard EQA panel. As such, we already know what lineages we expect to find in these samples.

+
    +
  • Open IGV and load one (or more) of the alignment BAM files into it. If you need a reminder of where to find this file, consult the “Consensus assembly” section of the materials, or the Viralrecon output documentation.
  • +
  • Open your metadata table (sample_info.csv) and check which lineages your loaded samples belong to.
  • +
  • Do a web search to find what mutations characterise those lineages.
  • +
  • On IGV, go to the location of some of the expected mutations, and see if you can see it in the respective samples.
  • +
+
+
+
+
+
+
+
+

10.4.3 Clean FASTA

+

The viralrecon pipeline outputs each of our consensus sequences as individual FASTA files for each sample (look at the FASTA Files section if you need a reminder of what these files are). However, by default, the sample names in this file have extra information added to them, which makes some downstream analysis less friendly (because the names will be too long and complicated).

+

To clean up these files, we will do two things:

+
    +
  • Combine all the individual FASTA files together.
  • +
  • Remove the extra text from the sequence names.
  • +
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

We will use the programs cat (concatenate) and sed (text replacement) to clean our FASTA files. Consider the commands given below:

+
+ +
+
+
cat <INPUT> | sed 's|/ARTIC/medaka MN908947.3||' > report/consensus.fa
+
+
+
cat <INPUT> | sed 's| MN908947.3||' > report/consensus.fa
+
+
+
+

The sed command shown is used to remove the extra pieces of text added by the pipeline to each sample name. This is a slightly more advanced program to use, so we already give you the code. If you want to learn more about it, check the “Text Replacement” section of the accompanying Unix materials.

+

Copy the command above to a new shell script called scripts/02-clean_fasta.sh. Fix the code by replacing <INPUT> with the path to all the FASTA files generated by the pipeline (remember you can use the * wildcard).
+If you need a reminder of where to find the FASTA consensus files from viralrecon, consult the “Consensus assembly” section of the materials, or the Viralrecon output documentation.

+

Once you fixed the script, run it with bash and check that the output file is generated.

+

Bonus:
+Check that you have all expected sequences in your FASTA file using grep to find the lines of the file with > (which is used to indicate sample names).

+
+
+
+
+
+
+
+

10.4.4 Missing Intervals

+

When we generate our consensus assembly, there are regions for which we did not have enough information (e.g. due to amplicon dropout) or where there were mixed bases. These regions are missing data, and are denoted as ‘N’ in our sequences. Although we already determined the percentage of each sequence that is missing from the MultiQC report, we don’t have the intervals of missing data, which can be a useful quality metric to have. In particular, we may want to know what is the largest continuous stretch of missing data in each sample.

+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

We will use the software tool seqkit to find intervals where the ‘N’ character occurs. This tool has several functions available, one of which is called locate.

+

Here is the command that would be used to locate intervals containing one or more ‘N’ characters:

+
seqkit locate -i -P -G -M -r -p "N+" report/consensus.fa
+

The meaning of the options is detailed in seqkit’s documentation.

+
    +
  • Create a new shell script called scripts/03-missing_intervals.sh.
  • +
  • Copy the command shown above to the script and modify it to redirect the output (using >) to a file called results/missing_intervals.tsv.
  • +
  • Activate the software environment: mamba activate seqkit.
  • +
  • Run the script you created using bash.
  • +
+
+
+
+
+
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Open the file you created in the previous step (results/missing_intervals.tsv) in a spreadsheet program. Create a new column with the length of each interval (end - start + 1).

+

Note if any missing intervals are larger than 1Kb, and whether they overlap with the Spike gene. (Note: you can use the Sars-CoV-2 genome browser to help you see the location of the annotated genes.)

+
+
+
+
+
+
+
+
+

10.5 Downstream Analyses

+

Now that we have cleaned our FASTA file, we can use it for several downstream analysis. We will focus on these:

+
    +
  • Lineage assignment: identify if our samples come from known lineages from the Pangolin consortium.
  • +
  • Phylogeny: produce an annotated phylogenetic tree of our samples.
  • +
  • Clustering: assess how many clusters of sequences we have, based on a phylogenetic analysis.
  • +
  • Integration & Visualisation: cross-reference different results tables and produce visualisations of how variants changed over time.
  • +
+
+

10.5.1 Lineage Assignment

+
+
+
+ +
+
+Note +
+
+
+

See Section 6.1, if you need to revise how lineage assignment works.

+
+
+

Although the Viralrecon pipeline can run Pangolin and Nextclade, it does not use the latest version of these programs (because lineages evolve so fast, the nomenclature constantly changes). Although it is possible to configure viralrecon to use more recent versions of these tools, it requires more advanced use of configuration files with the pipeline.

+

Alternatively, we can run our consensus sequences through the latest versions of Nextclade and Pangolin. There are two ways to do this: using their respective web applications, or their command-line versions. For this task, we recommend that you use the command line tools (this will ensure our downstream analysis works well), but we also provide the option to use the web apps for your reference.

+
+ +
+
+

To run the command-line version of these tools, there are two steps:

+
    +
  • Update the datasets of each software (to ensure they are using the latest lineage/clade nomenclature available).
  • +
  • Run the actual analysis on your samples.
  • +
+

We will use the exercises below to see what the commands to achieve this are.

+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

We will start doing the Nextclade analysis.

+
    +
  • Create a new script file called scripts/04-nextclade.sh and copy this code into it:

    +
    # update nextclade data
    +nextclade dataset get --name sars-cov-2 --output-dir resources/nextclade_background_data
    +
    +# run nextclade
    +nextclade run --input-dataset resources/nextclade_background_data/ --output-all results/nextclade/ <INPUT>
  • +
  • Fix the code, replacing <INPUT> with the path to your consensus sequence file. Save the file.

  • +
  • Activate the software environment: mamba activate nextclade.

  • +
  • Run your script using bash.

  • +
  • Once the analysis completes, open the file results/nextclade/nextclade.tsv in Excel and see what problems your samples may have (in particular those classified as “bad” quality).

  • +
+
+
+
+
+
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

For the Pangolin analysis:

+
    +
  • Create a new script file called scripts/04-pangolin.sh and copy this code into it:

    +
    # update pangolin data
    +<DATA_UPDATE_COMMAND>
    +
    +# run pangolin
    +pangolin --outdir results/pangolin/ --outfile pango_report.csv <INPUT>
  • +
  • Fix the code:

    +
      +
    • Replace <INPUT> with the path to your consensus sequence file.
    • +
    • Replace <DATA_UPDATE_COMMAND> with the pangolin command used to update its data. Check the documentation of the tool with pangolin --help to see if you can find what this option is called. Alternatively, look at the documentation online.
    • +
  • +
  • Save the file.

  • +
  • Activate the software environment: mamba activate pangolin.

  • +
  • Run your script using bash.

  • +
  • Once the analysis completes, open the file results/pangolin/pango_report.csv in Excel and see if there were any samples for which the analysis failed. If there were any failed samples, check if they match the report from Nextclade.

  • +
+
+
+
+
+
+
+
+

These exercises are optional - during the workshop please use the command line version of the tools.

+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Running Nextclade

+

Go to clades.nextstrain.org and run Nextclade on the clean FASTA file you created earlier (report/consensus.fa).
+If you need a reminder about this tool, see the “Lineages and variants” section of the materials.

+

Once the analysis completes, pay particular attention to the quality control column, to see what problems your samples may have (in particular those classified as “bad” quality).

+

Then:

+
    +
  • Create a new folder in your project directory: results/nextclade.
  • +
  • Use the “download” button (top-right) and download the file nextclade.tsv (tab-delimited file), which contains the results of the analysis. Important: please download the TSV file (not the CSV file, as it uses ; to separate columns, and will not work later with the “Data Integration” section).
  • +
  • Save this file in the results/nextclade folder you created.
  • +
+
+
+
+
+
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Running Pangolin

+

Go to pangolin.cog-uk.io and run Pangolin on the clean FASTA file you created earlier (report/consensus.fa).
+If you need a reminder about this tool, see the “Lineages and variants” section of the materials.

+

Once the analysis completes, pay particular attention to any samples that failed. If there were any failed samples, check if they match the report from Nextclade.

+

Then:

+
    +
  • Create a new folder in your project directory: results/pangolin.
  • +
  • Use the “download” button (the “arrow down” symbol on the results table) and download the file results.csv. Rename this file to report.csv (important: make sure to rename the file like this, otherwise it will not work later with the “Data Integration” section).
  • +
  • Save this file in the results/pangolin folder you created.
  • +
+
+
+
+
+
+
+
+
+
+
+

10.5.2 Phylogeny

+
+
+
+ +
+
+Note +
+
+
+

See Section 7.1, if you need to revise how to build phylogenetic trees.

+
+
+

Although tools such as Nextclade and civet can place our samples in a phylogeny, sometimes it may be convenient to build our own phylogenies. This requires three steps:

+
    +
  • Producing a multiple sequence alignment from all consensus sequences.
  • +
  • Tree inference.
  • +
  • Tree visualisation.
  • +
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+
    +
  • Start by creating two directories to store the output of our analysis: results/mafft and results/iqtree.
  • +
  • Activate the software environment: mamba activate phylo (this environment includes both mafft and iqtree).
  • +
  • To make our analysis more interesting, we will combine our sequences with sequences from previous workshops. Use the cat program to concatenate your sequences (report/consensus.fa) with the sequences from our collaborators (resources/eqa_collaborators/eqa_consensus.fa). Using >, save the output in a new file results/mafft/unaligned_consensus.fa.
  • +
  • Perform a multiple sequence alignment of the combined consensus sequences using the program mafft (see Section 7.2 for how to use this program). Save the output in a file called results/mafft/aligned_consensus.fa.
  • +
  • Infer a phylogenetic tree using the iqtree2 program (see Section 7.3).
  • +
  • Once you have both of these commands working, make sure to save them in a new shell script (as a record of your analysis). Save the script as scripts/05-phylogeny.sh.
  • +
  • Visualise the tree using FigTree (see Section 7.4).
  • +
+

What substitution model was chosen as the best for your data by IQ-Tree?

+

Do your samples group with samples from previous workshops in the way that is expected, or are there any outliers?

+

Are there any obvious differences between sequencing technologies?

+
+
+
+
+
+
+
+

10.5.3 Clustering (Optional Section)

+ +

The software civet (Cluster Investigation and Virus Epidemiology Tool) can be used to produce a report on the phylogenetic relationship between viral samples, using a background dataset to contextualise them (e.g. a global or regional sample of sequences) as well as metadata information to enable the identification of emerging clusters of samples.

+

Civet works in several stages, in summary:

+
    +
  • Each input sequence is assigned to a “catchment” group. These are groups of samples that occur at a given genetic distance (number of SNP differences) from each other.
  • +
  • Within each catchment group, a phylogeny is built (using the iqtree2 software with the HKY substitution model).
  • +
  • The phylogeny is annotated with user-input metadata (e.g. location, collection date, etc.). The results are displayed in an interactive HTML report.
  • +
+

One of the critical pieces of information needed by civet is the background data, to which the input samples will be compared with. These background data could be anything the user wants, but typically it’s a good idea to use samples from the local geographic area from where the samples were collected. For example, a national centre may choose to have their background data composed of all the samples that have been sequenced in the country so far. Or perhaps all the samples in its country and other neighbouring countries.

+

One way to obtain background data is to download it from GISAID. An example of how to do this is detailed in the civet documentation. We have already downloaded the data from GISAID, so we can go straight to running our civet analysis.

+ +
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+
    +
  • Create a new script in scripts/06-civet.sh, with the following command, adjusted to fit your files:

    +
    civet -i <PATH_TO_YOUR_SAMPLE_METADATA> \
    +  -f <PATH_TO_YOUR_CONSENSUS_FASTA> \
    +  -icol <COLUMN_NAME_FOR_YOUR_SAMPLE_IDS> \
    +  -idate <COLUMN_NAME_FOR_YOUR_COLLECTION_DATE> \
    +  -d <PATH_TO_CIVET_BACKGROUND_DATA> \
    +  -o results/civet
  • +
  • Activate the software environment: mamba activate civet.

  • +
  • Once your script is ready, run it with bash.

  • +
  • After the analysis completes, open the HTML output file in results/civet and examine into how many catchments your samples were grouped into.

  • +
+
+
+
+
+
+
+
+
+

10.6 Integration & Visualisation

+

At this point in our analysis, we have several tables with different pieces of information:

+
    +
  • sample_info.csv → the original table with metadata for our samples.
  • +
  • results/viralrecon/multiqc/medaka/summary_variants_metrics_mqc.csv → quality metrics from the MultiQC report generated by the viralrecon pipeline.
  • +
  • results/nextclade/nextclade.tsv → the results from Nextclade.
  • +
  • results/pangolin/pango_report.csv → the results from Pangolin.
  • +
  • (optional) results/civet/master_metadata.csv → the results from the civet analysis, namely the catchment (or cluster) that each of our samples was grouped into.
  • +
+

Each of these tables stores different pieces of information, and it would be great if we could integrate them together, to facilitate their interpration and generate some visualisations.

+

We will demonstrate how this analysis can be done using the R software, which is a popular programming language used for data analysis and visualisation. Check the Quick R Intro appendix for the basics of how to work with R and RStudio.

+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Data Integration

+

From RStudio, open the script in scripts/07-data_integration.R. Run the code in the script, going line by line (remember in RStudio you can run code from the script panel using Ctrl + Enter). As you run the code check the tables that are created (in your “Environment” panel on the top-right) and see if they were correctly imported.

+

Once you reach the end of the script, you should have two tab-delimited files named report/consensus_metrics.tsv and report/variants.tsv. Open both files in Excel to check their content and confirm they contain information from across the multiple tables.

+
+
+
+
+
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Data Visualisation

+

After integrating the data, it’s time to produce some visualisations. From RStudio, open the script in scripts/08-visualisation.R. Run the code in the script, going line by line.

+

As you run the code, several plots will be created. You can export these plots from within RStudio using the “Export” button on the plotting panel – these will be useful when you write your report later.

+
+
+
+
+
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Annotating Phylogenetic Tree

+

Using FigTree, import two annotation files (File > Import Annotations…):

+
    +
  • report/consensus_metrics.tsv, which was created in the Data Integration exercise.
  • +
  • resources/eqa_collaborators/metadata.tsv, which has lineage assignment for EQA samples sequenced by other labs.
  • +
+

After importing both files, annotate your phylogenetic tree to display the lineages assigned to each sample as the tip labels. See Section 7.4, if you need a reminder of how to annotate trees using FigTree.

+
+
+
+
+
+
+
+

10.7 EQA Panels

+

The analysis we have done so far is applicable to any new sequences that you want to process. However, we have also been using a pre-defined panel of samples to be used as part of an External Quality Assessment (EQA) process. This allows us to assess whether our bioinformatic analysis identified all the expected mutations in these samples, as well as assigned them to the correct lineages.

+

To do this, requires to compare the mutations we detected in our EQA samples to the expected mutations provided to us. From this, we will be able to calculate True Positives (TP), False Positives (FP) and False Negatives (FN), and calculate two performance metrics:

+
    +
  • Precision = TP / (TP + FP) → what fraction of the mutations we detected are true?
  • +
  • Sensitivity = TP / (TP + FN) → what fraction of all true mutations did we detect?
  • +
+

There can be cases where one of these metrics is high and the other one lower. For example, if you have a high-coverage genome (say >95%) but lots of sequencing errors, you may have a high sensitivity (you manage to detect all true mutations), but low precision (you also detect lots of false positives, due to sequencing errors). Conversely, if you have a low-coverage genome (say <50%) but very high-quality sequencing, you may have low sensitivity (you missed lots of true mutations, because of missing data), but high precision (those mutations that you did manage to identify were all true, you didn’t have many false positives).

+

+

If you are submiting your samples to GenQA’s platform, they will also provide with a general accuracy score called F-score. This is calculated as the harmonic mean of precision and sensitivity:

+

\(F_{score} = \frac{2 \times Precision \times Sensitivity}{Precision + Sensitivity}\)

+

A high F-score is indicative of both high precision and sensitivity, whereas a lower score indicates that at least one of those metrics are low.

+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

From RStudio, open the script in scripts/09-eqa_validation.R. Run the code in the script, going line by line.

+

This script will save a new tab-delimited file in report/eqa_performance_metrics.tsv. Open this file in Excel and check what the precision and sensitivity of your analysis was. Are you happy with the results?

+
+
+
+
+
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

(Optional)

+

While you run the R script, you will have an object called all_mutations in your environment (check the top-right panel). From RStudio, click on the name of this object to open it in the table viewer.

+

Look at mutations that were not detected in your samples, but were present in the expected mutations (i.e. false negatives). Open the BAM file for one of those samples in IGV and navigate to the position where that mutation should occur. Investigate what the reason may have been for missing that mutation.

+

If you need a reminder of where to find the BAM file, consult the Consensus > Output Files section of the materials, or the Viralrecon output documentation.

+
+
+
+
+
+
+
+

10.8 Reporting

+

Finally, we have come to the end of our analysis. So, all that is left is to report our results in the most informative way to our colleagues and decision-makers. You may already have established reporting templates in your institution, and if that’s the case, you should use those. Alternatively, we will look at a suggested template, based on the reports done by the UK Health Security Agency (UKHSA).

+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Open our shared report template document and download it as a Word document (FileDownloadMicrosoft Word (.docx)). Save the file in report/YYYY-MM-DD_analysis_report.docx (replace YYYY-MM-DD by today’s date).

+

Open the file and complete it with the information you collected throughout your analysis. You should be able to get all this information from the files in your report/ directory.

+
+
+
+
+
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/03-case_studies/images/analysis_overview.png b/materials/03-case_studies/images/analysis_overview.png new file mode 100644 index 0000000..81f4df9 Binary files /dev/null and b/materials/03-case_studies/images/analysis_overview.png differ diff --git a/materials/03-case_studies/images/civet_catchment_example.png b/materials/03-case_studies/images/civet_catchment_example.png new file mode 100644 index 0000000..efb3056 Binary files /dev/null and b/materials/03-case_studies/images/civet_catchment_example.png differ diff --git a/materials/03-case_studies/images/precision_sensitivity.svg b/materials/03-case_studies/images/precision_sensitivity.svg new file mode 100644 index 0000000..f6f4792 --- /dev/null +++ b/materials/03-case_studies/images/precision_sensitivity.svg @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + High Coverage +Low Error Rate + Low Coverage +High Error Rate + Low Coverage +Low Error Rate + Good Coverage +High Error Rate + + Precision + Sensitivity + + + + + + + + + + + diff --git a/materials/03-case_studies/images/southafrica_amplicon_heatmap.svg b/materials/03-case_studies/images/southafrica_amplicon_heatmap.svg new file mode 100644 index 0000000..a22eb7a --- /dev/null +++ b/materials/03-case_studies/images/southafrica_amplicon_heatmap.svg @@ -0,0 +1,1473 @@ + + + +Example of amplicon dropoutacross samplesZA10 and ZA23had very few non-human readsZA14 had lots of failed amplicons despite similar number of reads to ZA13 diff --git a/materials/03-case_studies/images/southafrica_civet.png b/materials/03-case_studies/images/southafrica_civet.png new file mode 100644 index 0000000..2a0d676 Binary files /dev/null and b/materials/03-case_studies/images/southafrica_civet.png differ diff --git a/materials/03-case_studies/images/southafrica_multiqc_metrics.png b/materials/03-case_studies/images/southafrica_multiqc_metrics.png new file mode 100644 index 0000000..7b8531c Binary files /dev/null and b/materials/03-case_studies/images/southafrica_multiqc_metrics.png differ diff --git a/materials/03-case_studies/images/southafrica_phylogeny.png b/materials/03-case_studies/images/southafrica_phylogeny.png new file mode 100644 index 0000000..9091069 Binary files /dev/null and b/materials/03-case_studies/images/southafrica_phylogeny.png differ diff --git a/materials/03-case_studies/images/switzerland_amplicon_heatmap.png b/materials/03-case_studies/images/switzerland_amplicon_heatmap.png new file mode 100644 index 0000000..f44f293 Binary files /dev/null and b/materials/03-case_studies/images/switzerland_amplicon_heatmap.png differ diff --git a/materials/03-case_studies/images/switzerland_multiqc_metrics.svg b/materials/03-case_studies/images/switzerland_multiqc_metrics.svg new file mode 100644 index 0000000..82ab681 --- /dev/null +++ b/materials/03-case_studies/images/switzerland_multiqc_metrics.svg @@ -0,0 +1,3459 @@ + + + +Depth of CoverageSNPs + Indels% Missing Bases(divide by 1000)MappedReadsIgnore Lineages(viralrecon does not use most up-to-date version of the databases) diff --git a/materials/03-case_studies/images/switzerland_phylogeny.png b/materials/03-case_studies/images/switzerland_phylogeny.png new file mode 100644 index 0000000..b5ee482 Binary files /dev/null and b/materials/03-case_studies/images/switzerland_phylogeny.png differ diff --git a/materials/03-case_studies/images/switzerland_variants.svg b/materials/03-case_studies/images/switzerland_variants.svg new file mode 100644 index 0000000..346e5d0 --- /dev/null +++ b/materials/03-case_studies/images/switzerland_variants.svg @@ -0,0 +1,2141 @@ + + + +PositionReference and alternativeallelesDepth ofsequencingAlternativeallelefrequencyMutation effectThe alternative allele is the one in the consensus sequenceAn insertionA deletionTotalReferenceAlternative diff --git a/materials/04-wastewater/00-ww_temp.html b/materials/04-wastewater/00-ww_temp.html new file mode 100644 index 0000000..f7acb2d --- /dev/null +++ b/materials/04-wastewater/00-ww_temp.html @@ -0,0 +1,629 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

11  Wastewater surveillance

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Caution +
+
+
+

Under development

+
+
+ + + +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/05-software/01-managing_software.html b/materials/05-software/01-managing_software.html new file mode 100644 index 0000000..acbe537 --- /dev/null +++ b/materials/05-software/01-managing_software.html @@ -0,0 +1,821 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

12  Managing Bioinformatics Software

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Learning objectives +
+
+
+
    +
  • List some of the reasons why Linux is an essential operating system for bioinformatic analysis.
  • +
  • Distinguish between software containers and software environments.
  • +
  • Use package managers to install software globally and locally.
  • +
  • Recognise the advantage of using software containers for bioinformatic pipelines.
  • +
+
+
+
+

12.1 Why Linux?

+

Most computer users either use Windows or macOS as their operating system. However, in bioinformatics the Linux operating system is the most popular.

+

There are different reasons to prefer Linux, for example:

+
    +
  • Most bioinformatics software is only available for this operating system.
  • +
  • Generally more secure.
  • +
  • Free and open source.
  • +
  • The operating system of choice on high performance compute (HPC) cluster environments.
  • +
  • It has flexible built-in command line tools to manipulate large data (cat, grep, sed, and the | pipe for chaining commands).
  • +
+

Because Linux is an open source project, there are many different types of Linux operating systems available. Generally, we recommend using Ubuntu, as it’s a well-supported and widely used distribution.

+

The first step for running bioinformatics software therefore is to ensure that you have access to Ubuntu Linux on your machine. We give instructions for this in Section 13.1.

+
+
+

12.2 Package Managers

+

Once you have Linux available, there is the question of how to install software, especially when using the command line. Most modern Linux operating systems have a default package manager available. A package manager allows the user to install (or remove, or upgrade) software with a single command. The package manager takes care of automatically downloading and installing the software we want, as well as any dependencies it requires.

+
+
+

+
Diagram illustrating how package managers work. Image source.
+
+
+
+

12.2.1 Ubuntu apt

+

On Ubuntu, the default package manager is called apt. This software can be used to do system-wide updates, upgrades and installation of software packages. There are several commands available:

+
    +
  • apt update updates the database of available packages, to check the latest versions available. This command doesn’t actually install anything.
  • +
  • apt upgrade upgrades all the packages to their latest version. This command upgrades existing packages, and installs new ones if required as dependencies.
  • +
  • apt install is used to install a single software of your choice.
  • +
+

For example, let’s say we wanted to install the latest version of java (Java is used for many applications, including IGV, which we introduced earlier). We could run the following commands:

+
sudo apt update               # make sure the package database is up-to-date
+sudo apt upgrade              # upgrade existing software to their latest version
+sudo apt install default-jre  # install java
+

The sudo command at the beginning indicates that we want to run the apt command with administrator privilege, which will require you to input your password. This is necessary, because the apt command does a system installation of the software, so it can only be done if the user trying to do the installation has those permissions. With the install command, we give the name of the software we want to install. In this case, default-jre will install the default Java runtime environment.

+

The apt package manager is extremely useful to install core system software, however most bioinformatics software are not available from apt. Also, as it requires administrator (sudo) permissions, it is not always possible to install software with apt (for example, if you are using a HPC cluster). This is why we turn to alternative package managers such as Conda.

+
+
+

12.2.2 Debian Packages

+

Sometimes, software is not available through the apt repositories, but instead distributed as a file. Ubuntu uses the Debian package format. To install Debian packages, you can use the dpkg command.

+

For example, the RStudio application for Linux is distributed as a .deb file. After you download it, you can install it as follows:

+
sudo dpkg -i rstudio-2023.12.0-369-amd64.deb
+

As with apt this is a system installation and so requires admin privileges (sudo).

+
+
+

12.2.3 Conda/Mamba Package Manager

+

Often you may want to use software packages that are not available on the apt repositories. A popular alternative in bioinformatics is to use the package manager Mamba, which is a successor to another package manager called Conda.

+

Conda and Mamba are package managers commonly used in data science, scientific computing, and bioinformatics. Conda, originally developed by Anaconda, is a package manager and environment manager that simplifies the creation, distribution, and management of software environments containing different packages and dependencies. It is known for its cross-platform compatibility and ease of use. Mamba is a more recent and high-performance alternative to Conda. While it maintains compatibility with Conda’s package and environment management capabilities, Mamba is designed for faster dependency resolution and installation, making it a better choice nowadays.

+

One of the strengths of using Mamba to manage your software is that you can have different versions of your software installed alongside each other, organised in environments. Organising software packages into environments is extremely useful, as it allows to have a reproducible set of software versions that you can use and reuse in your projects.

+

For example, imagine you are working on two projects with different software requirements:

+
    +
  • Project A: requires Python 3.7, NumPy 1.15, and scikit-learn 0.20.
  • +
  • Project B: requires Python 3.9, the latest version of NumPy, and TensorFlow 2.0.
  • +
+

If you don’t use environments, you would need to install and maintain these packages globally on your system. This can lead to several issues:

+
    +
  • Version conflicts: different projects may require different versions of the same library. For example, Project A might not be compatible with the latest NumPy, while Project B needs it.
  • +
  • Dependency chaos: as your projects grow, you might install numerous packages, and they could interfere with each other, causing unexpected errors or instability.
  • +
  • Difficulty collaborating: sharing your code with colleagues or collaborators becomes complex because they may have different versions of packages installed, leading to compatibility issues.
  • +
+
+
+

+
Illustration of Conda/Mamba environments. Each environment is isolated from the others (effectively in its own folder), so different versions of the packages can be installed for distinct projects or parts of a long analysis pipeline.
+
+
+

Environments allow you to create isolated, self-contained environments for each project, addressing these issues:

+
    +
  • Isolation: you can create a separate environment for each project using tools like Conda/Mamba. This ensures that the dependencies for one project don’t affect another.
  • +
  • Version control: you can specify the exact versions of libraries and packages required for each project within its environment. This eliminates version conflicts and ensures reproducibility.
  • +
  • Ease of collaboration: sharing your code and environment file makes it easy for collaborators to replicate your environment and run your project without worrying about conflicts.
  • +
  • Simplified maintenance: if you need to update a library for one project, it won’t impact others. You can manage environments separately, making maintenance more straightforward.
  • +
+

Another advantage of using Mamba is that the software is installed locally (by default in your home directory), without the need for admin (sudo) permissions.

+

You can search for available packages from the anaconda.org website. Packages are organised into “channels”, which represent communities that develop and maintain the installation “recipes” for each software. The most popular channels for bioinformatics and data analysis are “bioconda” and “conda-forge”.

+

There are three main commands to use with Mamba:

+
    +
  • mamba create -n ENVIRONMENT-NAME: this command creates a new software environment, which can be named as you want. Usually people name their environments to either match the name of the main package they are installating there (e.g. an environment called pangolin if it’s to install the Pangolin software). Or, if you are installing several packages in the same environment, then you can name it as a topic (e.g. an environment called rnaseq if it contains several packages for RNA-seq data analysis).
  • +
  • mamba install -n ENVIRONMENT-NAME NAME-OF-PACKAGE: this command installs the desired package in the specified environment.
  • +
  • mamba activate ENVIRONMENT-NAME: this command “activates” the environment, which means the software installed there becomes available from the terminal.
  • +
+

Let’s see a concrete example. If we wanted to install packages for phylogenetic analysis, we could do:

+
# create an environment named "phylo"
+mamba create -n phylo
+ 
+# install some software in that environment
+mamba install -n phylo  iqtree mafft
+

If we run the command:

+
mamba env list
+

We will get a list of environments we created, and “phylo” should be listed there. If we want to use the software we installed in that environment, then we can activate it:

+
mamba activate phylo
+

And usually this changes your terminal to have the word (phylo) at the start of your prompt.

+
+
+
+

12.3 Software Containers

+

Software containerization is a way to package software and its dependencies in a single file. A software container can be thought of as a very small virtual machine, with everything needed to run that software stored inside that file. For this reason, software containerization solutions, such as Docker and Singularity, are widely used in bioinformatics.

+

Software containers ensure reproducibility, allowing the same analysis to run on different systems. They can run on a local computer or on a high-performance computing cluster, producing the same result. The software within a container is isolated from other software, addressing the issue of incompatible dependencies between tools (similarly to Mamba environments).

+

As we already saw, analysis pipelines can be very complex, using many tools, each with their own dependencies. Therefore, worflow managers such as Nextflow use software containers to run their analysis (both Singularity and Docker are supported). This is what we’ve been doing throughout these materials, when running the nf-core/viralrecon pipeline, where we used the option -profile singularity. With this option, Nextflow will download the necessary software containers to run each step of the pipeline, which are available in public repositories online.

+

To use Singularity and Docker containers, the respective programs have to be installed, which we detail in our software setup page.

+
+
+
+ +
+
+Package managers or containers? +
+
+
+

As we’ve seen, the Mamba package manager and the containerisation solutions such as Docker and Singularity are trying to achieve similar things: enabling the reproducible installation of software and its dependencies in an isolated environment. So, why use one or the other?

+

Mamba is more user-friendly, allowing you to easily install packages of your choice. However, Mamba often works less well for more complex environments and can become extremely inneficient for environments with too many packages and conflicting versions. The recommendation is to keep your Mamba environments relatively small and atomic (e.g. an environment for each software package or for small sets of related packages).

+

Singularity and Docker, on the other hand, allow for more complex environments. However, to create your own container requires more advanced knowledge and a steep learning curve. Fortunately, there are many existing containers available for bioinformatics, which Nextflow uses in its pipelines.

+
+
+
+
+

12.4 Summary

+
+
+
+ +
+
+Key Points +
+
+
+
    +
  • Linux is used in bioinformatics due to its open-source nature, powerful command-line interface, and compatibility with bioinformatics tools.
  • +
  • Package managers such as apt enable global software installations on Linux systems, simplifying the process of obtaining and managing software at the system level.
  • +
  • Local package managers such as mamba allow users to install and manage software within user-specific environments, avoiding conflicts with system-level packages.
  • +
  • Software containers, such as Docker and Singularity, encapsulate entire environments in a file and can be used to manage more complex software enviroments.
  • +
  • Bioinformatics workflow managers such as Nextflow can make use of software containers to run complex bioinformatic pipelines.
  • +
+
+
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/05-software/03-software_setup.html b/materials/05-software/03-software_setup.html new file mode 100644 index 0000000..cc39777 --- /dev/null +++ b/materials/05-software/03-software_setup.html @@ -0,0 +1,866 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

13  Software setup

+
+ + + +
+ + + + +
+ + +
+ +

If you are attending one of our workshops, we will provide a virtual training environment with all of the required software and data pre-installed. If you want to setup your own computer to run the analysis demonstrated on this course, you can follow the instructions below.

+
+
+
+ +
+
+Hardware specifications +
+
+
+

The viralrecon pipeline that we cover in this workshop can run on a regular desktop (e.g. with 4 CPUs and 16GB RAM). However, if you are processing hundreds of samples, it may take several hours or even days.

+

For general bioinformatic applications, we recommend investing in a high-performance desktop workstation. The exact specifications depend on the application, but as a minimum at least 32 threads and 64GB RAM.

+
+
+
+

13.1 Install Linux

+
+ +
+
+

The recommendation for bioinformatic analysis is to have a dedicated computer running a Linux distribution. The kind of distribution you choose is not critical, but we recommend Ubuntu if you are unsure.

+

You can follow the installation tutorial on the Ubuntu webpage.

+
+
+
+ +
+
+Warning +
+
+
+

Installing Ubuntu on the computer will remove any other operating system you had previously installed, and can lead to data loss.

+
+
+
+
+

The Windows Subsystem for Linux (WSL2) runs a compiled version of Ubuntu natively on Windows.

+

There are detailed instructions on how to install WSL on the Microsoft documentation page. But briefly:

+
    +
  • Click the Windows key and search for Windows PowerShell, right-click on the app and choose Run as administrator.
  • +
  • Answer “Yes” when it asks if you want the App to make changes on your computer.
  • +
  • A terminal will open; run the command: wsl --install.
    +Progress bars will show while installing “Virtual Machine Platform”, “Windows Subsystem for Linux” and finally “Ubuntu” (this process can take a long time). +
      +
    • Note: it has happened to us in the past that the terminal freezes at the step of installing “Ubuntu”. If it is frozen for ~1h at that step, press Ctrl + C and hopefully you will get a message saying “Ubuntu installed successfully”.
    • +
  • +
  • After installation completes, restart your computer.
  • +
  • After restart, a terminal window will open asking you to create a username and password.
    +If it doesn’t, click the Windows key and search for Ubuntu, click on the App and it should open a new terminal. +
      +
    • You can use the same username and password that you have on Windows, or a different one - it’s your choice. Spaces and other special characters are not allowed for your Ubuntu username.
    • +
    • Note: when you type your password nothing seems to be happening as the cursor doesn’t move. However, the terminal is recording your password as you type. You will be asked to type the new password again to confirm it, so you can always try again if you get it wrong the first time.
    • +
  • +
+

You should now have access to a Ubuntu Linux terminal. This behaves very much like a regular Ubuntu server.

+
+

Configuring WSL2

+

After installation, it is useful to create shortcuts to your files on Windows. Your main C:\ drive is located in /mnt/c/ and other drives will be equally available based on their letter. To create shortcuts to commonly-used directories you use symbolic links. Here are some commands to automatically create shortcuts to your Windows “Documents”, “Desktop” and “Downloads” folders (copy/paste these commands on the terminal):

+
ln -s $(wslpath $(powershell.exe '[environment]::getfolderpath("MyDocuments")' | tr -d '\r')) ~/Documents
+ln -s $(wslpath $(powershell.exe '[environment]::getfolderpath("Desktop")' | tr -d '\r')) ~/Desktop
+ln -s $(wslpath $(powershell.exe '[environment]::getfolderpath("UserProfile")' | tr -d '\r'))/Downloads ~/Downloads
+

You may also want to configure the Windows terminal to automatically open WSL2 (instead of the default Windows Command Prompt or Powershell):

+
    +
  • Search for and open the “ Terminal” application.
  • +
  • Click on the down arrow in the toolbar.
  • +
  • Click on “ Settings”.
  • +
  • Under “Default Profile” select “ Ubuntu”.
  • +
+
+
+
+

Another way to run Linux within Windows (or macOS) is to install a Virtual Machine. However, this is mostly suitable for practicing and not suitable for real data analysis.

+

Details for installing Ubuntu on VirtualBox is given on this page. Make sure to do these things, while you are setting it up:

+
    +
  • In Step 2 “Create a user profile”: make sure to tick the Guest Additions option.
  • +
  • In Step 2 “Define the Virtual Machine’s resources”: +
      +
    • Assign at least 4 CPUs and 16000MB of RAM. At the very minimum you need 2 CPUs to run an Ubuntu VM.
    • +
    • Set at least 100GB as disk size, more if you have it available (note, this will not take 100GB of space on your computer, but it will allow using up to a maximum of that value, which is useful as we are working with sequencing data).
    • +
  • +
+

Once the installation completes, login to the Ubuntu Virtual machine, open a terminal and do the following:

+
    +
  • Run su command.
  • +
  • Enter your user password. Your terminal should change to start with root@
  • +
  • Type the command: usermod -a -G sudo YOUR-USERNAME-HERE.
  • +
  • Close the terminal and restart the virtual machine.
  • +
+

These commands will add your newly created user to the “sudo” (admin) group.

+
+
+
+

After making a fresh install of Ubuntu, open a terminal and run the following commands to update your system and install some essential packages:

+
sudo apt update && sudo apt upgrade -y && sudo apt autoremove -y
+sudo apt install -y git
+sudo apt install -y default-jre
+
+
+

13.2 Conda

+

We recommend using the Conda package manager to install your software. In particular, the newest implementation called Mamba.

+

To install Mamba, run the following commands from the terminal:

+
wget "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"
+bash Miniforge3-$(uname)-$(uname -m).sh -b -p $HOME/miniforge3
+rm Miniforge3-$(uname)-$(uname -m).sh
+$HOME/miniforge3/bin/mamba init
+

Restart your terminal (or open a new one) and confirm that your shell now starts with the word (base). Then run the following commands:

+

Restart your terminal (or open a new one) and confirm that your shell now starts with the word (base). Then run the following commands:

+
conda config --add channels defaults
+conda config --add channels bioconda
+conda config --add channels conda-forge
+conda config --set remote_read_timeout_secs 1000
+
+
+

13.3 Nextflow

+

We recommend that you install Nextflow within a conda environment. You can do that with the following command:

+
mamba create -n nextflow -y nextflow
+

When you want to use Nextflow make sure to activate this software environment by running mamba activate nextflow.

+

Also run the following command to create a configuration file to setup Nextflow correctly (make sure to copy all the code from top to bottom):

+
mkdir -p $HOME/.nextflow
+echo "
+conda {
+  conda.enabled = true
+  singularity.enabled = false
+  docker.enabled = false
+  useMamba = true
+  createTimeout = '4 h'
+  cacheDir = '$HOME/.nextflow-conda-cache/'
+}
+singularity {
+  singularity.enabled = true
+  conda.enabled = false
+  docker.enabled = false
+  pullTimeout = '4 h'
+  cacheDir = '$HOME/.nextflow-singularity-cache/'
+}
+docker {
+  docker.enabled = true
+  singularity.enabled = false
+  conda.enabled = false
+}
+" >> $HOME/.nextflow/config
+
+
+

13.4 Bioinformatics Software

+

Due to conflicts in software versions required by different packages, we install them in separate environments:

+
mamba create -n seqkit -y seqkit
+mamba create -n pangolin -y pangolin
+mamba create -n nextclade -y nextclade
+mamba create -n civet -y civet
+mamba create -n phylo -y mafft iqtree treetime figtree
+

In addition to these packages, you can also install AliView (which unfortunately is not available through Conda):

+
wget https://ormbunkar.se/aliview/downloads/linux/linux-version-1.28/aliview.tgz
+tar -xzvf aliview.tgz
+rm aliview.tgz
+echo "alias aliview='java -jar $HOME/aliview/aliview.jar'" >> $HOME/.bashrc
+

Finally, you can install IGV by downloading the installer from their website.

+
+
+

13.5 Software Image Containers

+
+

13.5.1 Singularity

+

We recommend that you install Singularity and use the -profile singularity option when running Nextflow pipelines. On Ubuntu/WSL2, you can install Singularity using the following commands:

+
sudo apt install -y runc cryptsetup-bin uidmap
+wget -O singularity.deb https://github.com/sylabs/singularity/releases/download/v4.0.2/singularity-ce_4.0.2-$(lsb_release -cs)_amd64.deb
+sudo dpkg -i singularity.deb
+rm singularity.deb
+

If you have a different Linux distribution, you can find more detailed instructions on the Singularity documentation page.

+

If you have issues running Nextflow pipelines with Singularity, then you can follow the instructions below for Docker instead.

+
+
+

13.5.2 Docker

+
+ +
+
+

When using WSL2 on Windows, running Nextflow pipelines with -profile singularity sometimes doesn’t work.

+

As an alternative you can instead use Docker, which is another software containerisation solution. To set this up, you can follow the full instructions given on the Microsoft Documentation: Get started with Docker remote containers on WSL 2.

+

We briefly summarise the instructions here (but check that page for details and images):

+
    +
  • Download Docker for Windows.
  • +
  • Run the installer and install accepting default options.
  • +
  • Restart the computer.
  • +
  • Open Docker and go to Settings > General to tick “Use the WSL 2 based engine”.
  • +
  • Go to Settings > Resources > WSL Integration to enable your Ubuntu WSL installation.
  • +
+

Once you have Docker set and installed, you can use -profile docker when running your Nextflow command.

+
+
+

For Linux, here are the installation instructions:

+
sudo apt install curl
+curl -fsSL https://get.docker.com -o get-docker.sh
+sudo sh ./get-docker.sh
+sudo groupadd docker
+sudo usermod -aG docker $USER
+

After the last step, you will need to restart your computer. From now on, you can use -profile docker when you run Nextflow

+
+
+
+ + +
+
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/05-software/images/conda_environments.svg b/materials/05-software/images/conda_environments.svg new file mode 100644 index 0000000..783838d --- /dev/null +++ b/materials/05-software/images/conda_environments.svg @@ -0,0 +1,201 @@ + + + + + + + + + + image/svg+xml + + + + + + + ComputerEnvironment + + + + Conda Environments + Environment 1 + Environment 2 + python 3.9 + python 3.7NumPy 1.15scikit-learn 0.20 + python 3.11numpy 1.26Tensorflow 2.13 + + diff --git a/materials/appendices/file_formats.html b/materials/appendices/file_formats.html new file mode 100644 index 0000000..0f8a810 --- /dev/null +++ b/materials/appendices/file_formats.html @@ -0,0 +1,729 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Common file formats

+
+ + + +
+ + + + +
+ + +
+ +

This page lists some common file formats used in Bioinformatics (listed alphabetically). The heading of each file links to a page with more details about each format.

+

Generally, files can be classified into two categories: text files and binary files.

+ +

Very often, text files may be compressed to save storage space. A common compression format used in bioinformatics is gzip with has extension .gz. Many bioinformatic tools support compressed files. For example, FASTQ files (used to store NGS sequencing data) are often compressed with format .fq.gz.

+
+

BAM (“Binary Alignment Map”)

+
    +
  • Binary file.
  • +
  • Same as a SAM file but compressed in binary form.
  • +
  • File extensions: .bam
  • +
+
+
+

BED (“Browser Extensible Data”)

+
    +
  • Text file.
  • +
  • Stores coordinates of genomic regions.
  • +
  • File extension: .bed
  • +
+
+
+

CSV (“Comma Separated Values”)

+
    +
  • Text file.
  • +
  • Stores tabular data in a text file. (also see TSV format)
  • +
  • File extensions: .csv
  • +
+

These files can be opened with spreadsheet programs (such as Microsoft Excel). They can also be created from spreadsheet programs by going to File > Save As… and select “CSV (Comma delimited)” as the file format.

+
+
+

FAST5

+
    +
  • Binary file. More specifically, this is a Hierarchical Data Format (HDF5) file.
  • +
  • Used by Nanopore platforms to store the called sequences (in FASTQ format) as well as the raw electrical signal data from the pore.
  • +
  • File extensions: .fast5
  • +
+
+
+

FASTA

+
    +
  • Text file.
  • +
  • Stores nucleotide or amino acid sequences.
  • +
  • File extensions: .fa or .fas or .fasta
  • +
+
+
+

FASTQ

+
    +
  • Text file, but often compressed with gzip.
  • +
  • Stores sequences and their quality scores.
  • +
  • File extensions: .fq or .fastq (compressed as .fq.gz or .fastq.gz)
  • +
+
+
+

GFF (“General Feature Format”)

+
    +
  • Text file.
  • +
  • Stores gene coordinates and other features.
  • +
  • File extension: .gff
  • +
+
+
+

NEWICK

+
    +
  • Text file.
  • +
  • Stores phylogenetic trees including nodes names and edge lengths.
  • +
  • File extensions: .tree or .treefile
  • +
+
+
+

SAM (“Sequence Alignment Map”)

+
    +
  • Text file.
  • +
  • Stores sequences aligned to a reference genome. (also see BAM format)
  • +
  • File extensions: .sam
  • +
+
+
+

TSV (“Tab-Separated Values”)

+
    +
  • Text file.
  • +
  • Stores tabular data in a text file. (also see CSV format)
  • +
  • File extensions: .tsv or .txt
  • +
+

These files can be opened with spreadsheet programs (such as Microsoft Excel). They can also be created from spreadsheet programs by going to File > Save As… and select “Text (Tab delimited)” as the file format.

+
+
+

VCF (“Variant Calling Format”)

+
    +
  • Text file but often compressed with gzip.
  • +
  • Stores SNP/Indel variants
  • +
  • File extension: .vcf (or compressed as .vcf.gz)
  • +
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/appendices/images/rstudio_data_viewer.svg b/materials/appendices/images/rstudio_data_viewer.svg new file mode 100644 index 0000000..49e0e71 --- /dev/null +++ b/materials/appendices/images/rstudio_data_viewer.svg @@ -0,0 +1,1878 @@ + + + +Clicking the table name in the environment opens a data viewerClicking the blue arrow shows the column names diff --git a/materials/appendices/images/rstudio_panels.png b/materials/appendices/images/rstudio_panels.png new file mode 100644 index 0000000..377612e Binary files /dev/null and b/materials/appendices/images/rstudio_panels.png differ diff --git a/materials/appendices/images/rstudio_setup.png b/materials/appendices/images/rstudio_setup.png new file mode 100644 index 0000000..c55dae2 Binary files /dev/null and b/materials/appendices/images/rstudio_setup.png differ diff --git a/materials/appendices/quick_r.html b/materials/appendices/quick_r.html new file mode 100644 index 0000000..d4b1c3b --- /dev/null +++ b/materials/appendices/quick_r.html @@ -0,0 +1,1450 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

R fundamentals

+
+ + + +
+ + + + +
+ + +
+ +

This section gives a (very) quick and brief introduction to R and RStudio. It will not teach you the details of how to program in R, but it should serve as a basis to start you on that journey. R can be used for a range of data analysis applications, including statistics, machine learning, plotting, image analysis, bioinformatics, etc. R is also a programming language, which makes it very flexible. With this flexibility, comes a somewhat steeper learning curve, compared to point-and-click programs. However, in the long run it can really help you improve as a data analyst.

+

These materials will not teach you the details of how to write your own R programs (we won’t have time for these), but to make you familiar with some of its applications and how to use RStudio to run your code. It’s only the start of your journey into R programming.

+

If you want to learn more about this topic, we recommend:

+ +
+

RStudio

+

R is the software and programming language itself, but the R interface is quite basic and not very user-friendly. Fortunately, there is a companion piece of software called RStudio, which makes working with R a little bit easier.

+

There are 4 basic panels in RStudio (see image below):

+
    +
  • The script panel is essentially a text editor, where we can write code and save it as a text file, which in this case, because it contains R code, we call it an R script (but remember, a script is just a text file with some code in it. We’ve been creating shell scripts that run on the command line, here we have some R scripts, which contain R code)
  • +
  • The console panel is where the code actually runs, or is executed. This is equivalent to the terminal, on the command line. If we want to execute a line of code, then we need to run it on the console. +
      +
    • One nice feature of RStudio is that we can edit our code on the script panel and then run a line of code from the script on the console - it’s like copy/pasting that line of code from the script to the console. This makes working interactively with RStudio much easier, because we can edit our code in the script and run it as we go along.
    • +
  • +
  • On the top-right we have the Environment panel, which shows us objects that we create, that store information such as tables of data that we read into R.
  • +
  • Finally, on the bottom-right there are a few tabs: a file browser (allowing us to see files in our computer), a plot display tab (for plots we generate) and a help tab to look at documentation.
  • +
+

+
+

Setting RStudio

+

Before we start working with RStudio, it’s a good idea to change one of its default options. Go to ToolsGlobal Options… and change the following:

+

+

This will tell RStudio to NOT automatically save and load things that we have done in the past. You may think this is a helpful thing, but actually it’s very inconvenient, because if you are doing multiple analysis, it might get very very confusing what the objects that you created are! So, it’s always best to start R with a fresh session, and setting these options makes sure we do this.

+
+
+

Starting a Project

+

R has a concept called working directory, which is the location on your computer where it is working from (looking for files and folders). You can think of it as the folder that you cd into if you were working on the command-line.

+

The easiest way to ensure that R is using the correct working directory for our analysis, is to create an R project. In RStudio: FileNew Project…Existing Directory and then click the Browse… button to navigate to the folder where your project files are located.

+

This will create an .Rproj file on your project folder. Next time you want to work on your analysis, you can simply double-click on this file, and it will open RStudio, with the correct working directory already set for you.

+
+
+
+

R Basics

+

This section introduces some of the basic concepts in the R programming language.

+
+

Packages/Libraries

+

R has several extensions to the basic functionality called “packages” or “libraries”. A popular library for data manipulation is called tidyverse, which we are using in this course. Each time we start a new R session, we need to load the libraries we want to use:

+
+
library(tidyverse)
+
+

If you get an error like Error in library(tidyverse) : there is no package called 'tidyverse', that means that you didn’t install the package. To install packages you can run:

+
+
install.packages("tidyverse")
+
+

You only need to do this the first time you want to use a library. Once it’s installed, you don’t need to run this command again (unless you want to update the library to its latest version – often a good idea!).

+
+
+

Create objects

+

Create objects (something that contains a value) with <-. For example, the following creates an object called x containing a single number:

+
+
x <- 53.341
+
+

We can print the content of the object by typing its name:

+
+
x
+
+
[1] 53.341
+
+
+
+
+

Functions

+

Most of the tasks we can achieve in R are done through the use of functions. We can think of functions as mini-programs that take an input and give an output.

+

Functions are easy to identify, because they are always followed by parenthesis. Inside the parenthesis we include the inputs to the function.

+
+
round(x)   # round the the value of x
+
+
[1] 53
+
+
+

Functions have options that can change their behaviour. Separate options using a comma:

+
+
round(x, digits = 1) # round to one decimal point
+
+
[1] 53.3
+
+
+
+
+

Vector

+

A vector is the most basic type of object in R. It is a collection of values, which are all of the same type, for example numeric, character or logical (TRUE/FALSE).

+
+
x_chr <- c("dog", "cat", "goldfish")   # character vector
+x_num <- c(1, 5, 23.3, 55.2)           # numeric vector
+x_log <- c(TRUE, TRUE, FALSE, TRUE)    # logical vector
+
+

Access values inside the vector with []:

+
+
x_chr[2]        # the second value
+
+
[1] "cat"
+
+
x_chr[c(2, 3)]  # the second and third values
+
+
[1] "cat"      "goldfish"
+
+
+
+
+

Conditions

+

In many situations (for example to filter rows in a table), it’s useful to evaluate a set of conditions. We can create logical vectors using conditions:

+
+
x_num
+
+
[1]  1.0  5.0 23.3 55.2
+
+
# is x_num greater than 20?
+x_num > 20
+
+
[1] FALSE FALSE  TRUE  TRUE
+
+
# is x_num equal to 5?
+x_num == 5
+
+
[1] FALSE  TRUE FALSE FALSE
+
+
# is x_num contained the vector on the right?
+x_num %in% c(20, 30, 1)
+
+
[1]  TRUE FALSE FALSE FALSE
+
+
+

Combine conditions with & (AND) and | (OR):

+
+
x_num
+
+
[1]  1.0  5.0 23.3 55.2
+
+
# is x_num greater than or equal to 10 AND smaller than or equal to 30?
+x_num >= 10 & x_num <= 30
+
+
[1] FALSE FALSE  TRUE FALSE
+
+
# is x_num smaller than 10 OR greater than 30?
+x_num < 10 | x_num > 30
+
+
[1]  TRUE  TRUE FALSE  TRUE
+
+
+

To set the filtering conditions, several relational operators can be used:

+
    +
  • == is equal to
  • +
  • != is different from
  • +
  • %in% is contained in
  • +
  • > is greater than
  • +
  • >= is greater than or equal to
  • +
  • < is less than
  • +
  • <= is less than or equal to
  • +
+

It is also possible to combine several conditions together using the following logical operators:

+
    +
  • & AND
  • +
  • | OR
  • +
+
+
+

Missing Values

+

Sometimes we have missing values in our data, which are encoded as NA:

+
+
y <- c(23, 44, NA, 212)
+
+

We need to ensure these are dealt with properly

+
+
# returns NA
+mean(y)
+
+
[1] NA
+
+
# removes NA and then calculates mean
+mean(y, na.rm = TRUE)
+
+
[1] 93
+
+
+

The is.na() function is important to deal with missing values:

+
+
y
+
+
[1]  23  44  NA 212
+
+
# create a logical that is true if value is missing
+is.na(y)
+
+
[1] FALSE FALSE  TRUE FALSE
+
+
# Negate that expression using !
+!is.na(y)
+
+
[1]  TRUE  TRUE FALSE  TRUE
+
+
+
+
+

Tables: data.frame/tibble

+

Tables in R are called data.frame. The tidyverse package has its own version of a data.frame called a tibble. For the most part they are basically equivalent, but the tibble object has a nicer printing function to display our data on the console.

+

As an example for working with tables in R, let’s read a TSV (tab-delimited) file that contains intervals of missing information in 5 SARS-CoV-2 consensus sequences (this data comes from the Switzerland case study). To import a TSV file into R as a data.frame we can use the function read_tsv() (for a CSV file we would use read_csv()):

+
+
missing_intervals <- read_tsv("missing_intervals.tsv")
+
+
Rows: 132 Columns: 6
+── Column specification ────────────────────────────────────────────────────────
+Delimiter: "\t"
+chr (4): seqID, patternName, pattern, strand
+dbl (2): start, end
+
+ℹ Use `spec()` to retrieve the full column specification for this data.
+ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
+
+
+

When we read the table in, we get a message informing us of the column types found. In this case we have character columns containing text (indicated by chr) and numeric columns (indicated by dbl, which refers to the double-precission floating point format that computers use to store numbers).

+

To see the content of the table you can type the name of the object:

+
+
missing_intervals
+
+
# A tibble: 132 × 6
+   seqID patternName pattern strand start   end
+   <chr> <chr>       <chr>   <chr>  <dbl> <dbl>
+ 1 CH01  N+          N+      +          1    54
+ 2 CH01  N+          N+      +       1193  1264
+ 3 CH01  N+          N+      +       4143  4322
+ 4 CH01  N+          N+      +       6248  6294
+ 5 CH01  N+          N+      +       7561  7561
+ 6 CH01  N+          N+      +       9243  9311
+ 7 CH01  N+          N+      +      10367 10367
+ 8 CH01  N+          N+      +      11361 11370
+ 9 CH01  N+          N+      +      13599 13613
+10 CH01  N+          N+      +      16699 16758
+# ℹ 122 more rows
+
+
+

Sometimes a more convenient way is to click the name of the table on the environment, which opens a new tab to preview your data.

+
+
+

+
Data viewer in RStudio.
+
+
+
+
+
+

Data Manipulation

+

Most of the work you will do in R is with tables of data (data.frame/tibble objects). There are several ways to manipulate tables in R, but we will give a quick overview of the functionality available through the tidyverse collection of packages.

+
+

Basic “verbs”

+

There’s a set of basic functions that can be thought of as “data manipulation verbs”. They are:

+
    +
  • mutate() → add a new column of modify an existing one.
  • +
  • select() → select columns from the table.
  • +
  • filter() → subset the rows from the table that fullfill a certain logical condition.
  • +
+

Here are some examples of each:

+
+
# create a new column with the missing interval lengths
+mutate(missing_intervals, 
+       length = (end - start) + 1)
+
+
# A tibble: 132 × 7
+   seqID patternName pattern strand start   end length
+   <chr> <chr>       <chr>   <chr>  <dbl> <dbl>  <dbl>
+ 1 CH01  N+          N+      +          1    54     54
+ 2 CH01  N+          N+      +       1193  1264     72
+ 3 CH01  N+          N+      +       4143  4322    180
+ 4 CH01  N+          N+      +       6248  6294     47
+ 5 CH01  N+          N+      +       7561  7561      1
+ 6 CH01  N+          N+      +       9243  9311     69
+ 7 CH01  N+          N+      +      10367 10367      1
+ 8 CH01  N+          N+      +      11361 11370     10
+ 9 CH01  N+          N+      +      13599 13613     15
+10 CH01  N+          N+      +      16699 16758     60
+# ℹ 122 more rows
+
+
# select only a few columns of the table
+select(missing_intervals,
+       seqID, start, end)
+
+
# A tibble: 132 × 3
+   seqID start   end
+   <chr> <dbl> <dbl>
+ 1 CH01      1    54
+ 2 CH01   1193  1264
+ 3 CH01   4143  4322
+ 4 CH01   6248  6294
+ 5 CH01   7561  7561
+ 6 CH01   9243  9311
+ 7 CH01  10367 10367
+ 8 CH01  11361 11370
+ 9 CH01  13599 13613
+10 CH01  16699 16758
+# ℹ 122 more rows
+
+
# subset the table to include only intervals within the Spike protein
+filter(missing_intervals,
+       start > 21563 & end < 25384)
+
+
# A tibble: 15 × 6
+   seqID patternName pattern strand start   end
+   <chr> <chr>       <chr>   <chr>  <dbl> <dbl>
+ 1 CH01  N+          N+      +      21621 21670
+ 2 CH01  N+          N+      +      23480 23507
+ 3 CH02  N+          N+      +      21620 21670
+ 4 CH02  N+          N+      +      23480 23508
+ 5 CH03  N+          N+      +      21621 21670
+ 6 CH03  N+          N+      +      23480 23507
+ 7 CH04  N+          N+      +      21620 21670
+ 8 CH04  N+          N+      +      22240 22240
+ 9 CH04  N+          N+      +      23480 23509
+10 CH04  N+          N+      +      24062 24395
+11 CH05  N+          N+      +      21620 22530
+12 CH05  N+          N+      +      23489 24709
+13 CH06  N+          N+      +      21605 22515
+14 CH06  N+          N+      +      23474 23525
+15 CH06  N+          N+      +      23721 24694
+
+
+
+
+

Pipes

+

We can chain multiple commands together using pipes (similarly to pipes in Unix). In R the pipe is represented by |> (or %>%). The way the pipe works is that the output of one function is sent as the input to the next function.

+

Taking the examples from the previous section, we could chain all those commands like this:

+
+
missing_intervals |> 
+  mutate(length = (end - start) + 1) |> 
+  select(seqID, start, end, length)
+
+
# A tibble: 132 × 4
+   seqID start   end length
+   <chr> <dbl> <dbl>  <dbl>
+ 1 CH01      1    54     54
+ 2 CH01   1193  1264     72
+ 3 CH01   4143  4322    180
+ 4 CH01   6248  6294     47
+ 5 CH01   7561  7561      1
+ 6 CH01   9243  9311     69
+ 7 CH01  10367 10367      1
+ 8 CH01  11361 11370     10
+ 9 CH01  13599 13613     15
+10 CH01  16699 16758     60
+# ℹ 122 more rows
+
+
+

If you want to update the missing_intervals table, then you need to use <- at the beggining of the chain of pipes:

+
+
missing_intervals <- missing_intervals |> 
+  mutate(length = (end - start) + 1) |> 
+  select(seqID, start, end, length)
+
+
+
+

Grouped Summaries

+

We can calculate summaries of the data (e.g. mean, standard deviation, maximum, minimum) per group (e.g. per sample) using a pair of functions together. For example:

+
+
# mean and maximum interval length per sample
+missing_intervals |> 
+  # for each sample
+  group_by(seqID) |> 
+  # calculate summary statistics
+  summarise(max_length = max(length),
+            min_length = min(length),
+            mean_length = mean(length))
+
+
# A tibble: 7 × 4
+  seqID max_length min_length mean_length
+  <chr>      <dbl>      <dbl>       <dbl>
+1 CH01         238          1        57  
+2 CH02         274          2        66  
+3 CH03         279         10        70.4
+4 CH04        1011          1       178. 
+5 CH05        1221         11       200  
+6 CH06         974          1       166. 
+7 CH07        5130          1       343. 
+
+
+

As before, if we wanted to save this result in a new object, we would need to use <-:

+
+
intervals_summary <- missing_intervals |> 
+  # for each sample
+  group_by(seqID) |> 
+  # calculate summary statistics
+  summarise(max_length = max(length),
+            min_length = min(length),
+            mean_length = mean(length)) |> 
+  # and rename the seqID column
+  rename(sample = seqID)
+
+

Notice in this case we also renamed the column called seqID to be named sample instead (this will be useful for the exercise later on).

+

Another useful function is count(), which counts how many times the values in a column appear on a data frame. For example, if we wanted to know how many missing intervals each sample had, we could do it like this:

+
+
missing_intervals |> 
+  count(seqID)
+
+
# A tibble: 7 × 2
+  seqID     n
+  <chr> <int>
+1 CH01     16
+2 CH02     19
+3 CH03     16
+4 CH04     21
+5 CH05     15
+6 CH06     16
+7 CH07     29
+
+
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Modify the following code:

+
+
missing_intervals |> 
+  count(seqID)
+
+

To also:

+
    +
  • rename the column seqID to be named sample instead.
  • +
  • save the output in an object called intervals_count.
  • +
+
+ +
+
+
+
+

To rename the column, we could use the rename() function:

+
+
missing_intervals |> 
+  count(seqID) |> 
+  rename(sample = seqID)
+
+
# A tibble: 7 × 2
+  sample     n
+  <chr>  <int>
+1 CH01      16
+2 CH02      19
+3 CH03      16
+4 CH04      21
+5 CH05      15
+6 CH06      16
+7 CH07      29
+
+
+

And to save this output to a new object, we need to use <-:

+
+
intervals_count <- missing_intervals |> 
+  count(seqID) |> 
+  rename(sample = seqID)
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Joining Tables

+

We can join multiple tables together based on a common identifier. There are different types of join operations, depending on what we want to achieve.

+

Take these two tables as an example (these tables come pre-loaded with tidyverse):

+
+
band_members
+
+
# A tibble: 3 × 2
+  name  band   
+  <chr> <chr>  
+1 Mick  Stones 
+2 John  Beatles
+3 Paul  Beatles
+
+
band_instruments
+
+
# A tibble: 3 × 2
+  name  plays 
+  <chr> <chr> 
+1 John  guitar
+2 Paul  bass  
+3 Keith guitar
+
+
+

Here are some different ways we can join these tables:

+
+
# keep all records from both tables
+full_join(band_members, band_instruments, by = "name")
+
+
# A tibble: 4 × 3
+  name  band    plays 
+  <chr> <chr>   <chr> 
+1 Mick  Stones  <NA>  
+2 John  Beatles guitar
+3 Paul  Beatles bass  
+4 Keith <NA>    guitar
+
+
# keep all records from the first table
+left_join(band_members, band_instruments, by = "name")
+
+
# A tibble: 3 × 3
+  name  band    plays 
+  <chr> <chr>   <chr> 
+1 Mick  Stones  <NA>  
+2 John  Beatles guitar
+3 Paul  Beatles bass  
+
+
# keep all records from the second table
+right_join(band_members, band_instruments, by = "name")
+
+
# A tibble: 3 × 3
+  name  band    plays 
+  <chr> <chr>   <chr> 
+1 John  Beatles guitar
+2 Paul  Beatles bass  
+3 Keith <NA>    guitar
+
+
# keep only the records occurring in both tables
+inner_join(band_members, band_instruments, by = "name")
+
+
# A tibble: 2 × 3
+  name  band    plays 
+  <chr> <chr>   <chr> 
+1 John  Beatles guitar
+2 Paul  Beatles bass  
+
+
+

In each case, if there was no match between the two tables, the cells are filled with missing values NA.

+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

In this exercise we will join the intervals_summary table we created earlier, with the metadata table that contains information about our samples.

+
    +
  1. Read the your metadata table sample_info.csv into R and save it as an object called sample_info. +
    + +Hint + +Remember that you can use the read_csv() function to read CSV files into R. +
  2. +
  3. Join the sample_info table that you just imported with the intervals_summary table we created earlier. Save the output to the intervals_summary table (this will update the table). +
    + +You can use the left_join() function to achieve this, using the “sample” column as the identifier column used to join the two tables. + +
  4. +
+
+ +
+
+
+
+

Answer 1

+

To read the metadata CSV file, we use the read_csv() function, and use <- to save the output in an object called sample_info:

+
+
sample_info <- read_csv("sample_info.csv")
+
+
Rows: 7 Columns: 4
+── Column specification ────────────────────────────────────────────────────────
+Delimiter: ","
+chr  (3): sample, country, sequencing_instrument
+date (1): collection_date
+
+ℹ Use `spec()` to retrieve the full column specification for this data.
+ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
+
+
+

We can see the content of our file by typing its name in the console. Here is the file in our example data (yours might look different):

+
+
sample_info
+
+
# A tibble: 7 × 4
+  sample collection_date country     sequencing_instrument
+  <chr>  <date>          <chr>       <chr>                
+1 CH01   2021-11-09      Switzerland GridION              
+2 CH04   2021-12-20      Switzerland GridION              
+3 CH05   2021-12-20      Switzerland GridION              
+4 CH06   2021-12-20      Switzerland GridION              
+5 CH07   2021-12-20      Switzerland GridION              
+6 CH02   2021-12-21      Switzerland GridION              
+7 CH03   2021-12-22      Switzerland GridION              
+
+
+

Answer 2

+

To join the two tables together, we can use one of the *_join() functions. In this case it doesn’t matter which function we use, because both tables have the same sample IDs. But, for example, let’s say we only wanted to retain the samples that are in common across both tables. In that case, we would use inner_join():

+
+
intervals_summary <- inner_join(intervals_summary, sample_info, by = "sample")
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Data Visualisation

+

For this section, we will use another table, which contains some of the metrics that we can collect from our consensus pipeline:

+
+
# read the table
+qc_metrics <- read_tsv("consensus_metrics.tsv")
+
+
Rows: 7 Columns: 6
+── Column specification ────────────────────────────────────────────────────────
+Delimiter: "\t"
+chr (3): sample, qc_status, lineage
+dbl (3): n_mapped_reads, median_depth, pct_missing
+
+ℹ Use `spec()` to retrieve the full column specification for this data.
+ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
+
+
# preview the table
+qc_metrics
+
+
# A tibble: 7 × 6
+  sample n_mapped_reads median_depth pct_missing qc_status lineage
+  <chr>           <dbl>        <dbl>       <dbl> <chr>     <chr>  
+1 CH01            43900          248        3.05 good      BA.1   
+2 CH02            42841          159        4.20 good      BA.1   
+3 CH03            40079          160        3.77 good      BA.1   
+4 CH04            50902          164       12.5  bad       BA.1   
+5 CH05            32020          133       10.0  bad       BA.1   
+6 CH06            46277          177        8.87 mediocre  BA.1.1 
+7 CH07            15867           41       33.2  bad       None   
+
+
+

We can build plots from our tables using the ggplot2 package (which is also part of the tidyverse).

+

To build a ggplot, we usually need at least three things:

+
    +
  • The data frame we want to use for the plot (the data).
  • +
  • The columns we want to visualise as our x-axis, y-axis, and colours (these are called aesthetics).
  • +
  • The type of shape that we want to plot (these are called geometries).
  • +
+

For example, let’s try to make a plot to show the relationship between total number of counts and the median depth of sequencing in these samples:

+
+
ggplot(data = qc_metrics, aes(x = n_mapped_reads, y = median_depth))
+
+

+
+
+

When we do this, we simply get an empty plot, with x and y axis, but nothing drawn on it. To draw something on the plot, we add (literally with +) geometries to our plot. In this case, we can use the geom_point() geometry, which draws “points” on the plot:

+
+
ggplot(data = qc_metrics, aes(x = n_mapped_reads, y = median_depth)) +
+  geom_point()
+
+

+
+
+

There are many geometries available with ggplot:

+
    +
  • geom_point() draws points.
  • +
  • geom_boxplot() draws a boxplot.
  • +
  • geom_histogram() draws a histogram.
  • +
  • geom_col() draws a barplot (this one is named a little strangely, but “col” means it draws “columns” or bars).
  • +
+

We can further modify the look to the plot by adding other aesthetics such as the colour of the points. For example, let’s say we wanted to colour our points according to their “QC Status”:

+
+
ggplot(data = qc_metrics, 
+       aes(x = n_mapped_reads, y = median_depth, colour = qc_status)) +
+  geom_point()
+
+

+
+
+

Finally, we may sometimes want to change the labels of our plot. In that case, we can add the labs() function to our plotting code:

+
+
ggplot(data = qc_metrics, 
+       aes(x = n_mapped_reads, y = median_depth, colour = qc_status)) +
+  geom_point() +
+  labs(x = "Number Mapped Reads", 
+       y = "Median Sequencing Depth", 
+       colour = "Nextclade QC Status")
+
+

+
+
+
+
+
+ +
+
+Exercise +
+
+
+
+
+
+
+

Modify the plot we just did, to show the relationship between median depth of sequencing (x-axis) and percentage of missing bases (y-axis). Colour the points according to the lineage column.

+
+ +
+
+
+
+

Here is the modified code:

+
+
ggplot(data = qc_metrics, 
+       aes(x = median_depth, y = pct_missing, colour = lineage)) +
+  geom_point() +
+  labs(x = "Median Sequencing Depth", 
+       y = "% Missing Bases", 
+       colour = "Pango Lineage")
+
+

+
+
+

The things we have changed were the aesthetics and labels.

+
+
+
+
+
+
+
+
+
+
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/appendices/quick_r_files/figure-html/unnamed-chunk-32-1.png b/materials/appendices/quick_r_files/figure-html/unnamed-chunk-32-1.png new file mode 100644 index 0000000..0453b35 Binary files /dev/null and b/materials/appendices/quick_r_files/figure-html/unnamed-chunk-32-1.png differ diff --git a/materials/appendices/quick_r_files/figure-html/unnamed-chunk-33-1.png b/materials/appendices/quick_r_files/figure-html/unnamed-chunk-33-1.png new file mode 100644 index 0000000..3b78047 Binary files /dev/null and b/materials/appendices/quick_r_files/figure-html/unnamed-chunk-33-1.png differ diff --git a/materials/appendices/quick_r_files/figure-html/unnamed-chunk-34-1.png b/materials/appendices/quick_r_files/figure-html/unnamed-chunk-34-1.png new file mode 100644 index 0000000..86f6612 Binary files /dev/null and b/materials/appendices/quick_r_files/figure-html/unnamed-chunk-34-1.png differ diff --git a/materials/appendices/quick_r_files/figure-html/unnamed-chunk-35-1.png b/materials/appendices/quick_r_files/figure-html/unnamed-chunk-35-1.png new file mode 100644 index 0000000..b7550d1 Binary files /dev/null and b/materials/appendices/quick_r_files/figure-html/unnamed-chunk-35-1.png differ diff --git a/materials/appendices/quick_r_files/figure-html/unnamed-chunk-36-1.png b/materials/appendices/quick_r_files/figure-html/unnamed-chunk-36-1.png new file mode 100644 index 0000000..84570e0 Binary files /dev/null and b/materials/appendices/quick_r_files/figure-html/unnamed-chunk-36-1.png differ diff --git a/materials/appendices/tools_and_resources.html b/materials/appendices/tools_and_resources.html new file mode 100644 index 0000000..591330f --- /dev/null +++ b/materials/appendices/tools_and_resources.html @@ -0,0 +1,850 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Tools and Resources

+
+ + + +
+ + + + +
+ + +
+ +

This page summarises several additional resources and software applications useful for SARS surveillance.

+
+

Amplicon Primer Schemes

+

For amplicon sequencing, there are several protocols and commercial kits available. We try to summarise some of the common ones below.

+
+

ARTIC

+
+ +
+
+

ARTIC primer scheme version 5.3.2 (link).

+

To analyse with nf-core/viralrecon, add these options to the command:

+
--genome 'MN908947.3' \
+--primer_set artic \
+--primer_set_version 5.3.2 \
+--schema_ignore_params 'genomes,primer_set_version'
+

Or, alternatively, you can use the direct links to the FASTA/BED files:

+
--fasta 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V5.3.2/SARS-CoV-2.reference.fasta' \
+--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \
+--primer_bed 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V5.3.2/SARS-CoV-2.scheme.bed'
+
+
+

ARTIC primer scheme version 4.1 (link).

+

To analyse with nf-core/viralrecon, add these options to the command:

+
--genome 'MN908947.3' \
+--primer_set artic \
+--primer_set_version 4.1
+

Or, alternatively, you can use the direct links to the FASTA/BED files:

+
--fasta 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V4.1/SARS-CoV-2.reference.fasta' \
+--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \
+--primer_bed 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V4.1/SARS-CoV-2.scheme.bed'
+
+
+

ARTIC primer scheme version 4 (link).

+

To analyse with nf-core/viralrecon, add these options to the command:

+
--genome 'MN908947.3' \
+--primer_set artic \
+--primer_set_version 4
+

Or, alternatively, you can use the direct links to the FASTA/BED files:

+
--fasta 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V4/SARS-CoV-2.reference.fasta' \
+--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \
+--primer_bed 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V4/SARS-CoV-2.scheme.bed'
+
+
+

ARTIC primer scheme version 3 (link).

+

To analyse with nf-core/viralrecon, add these options to the command:

+
--genome 'MN908947.3' \
+--primer_set artic \
+--primer_set_version 3
+

Or, alternatively, you can use the direct links to the FASTA/BED files:

+
--fasta 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V3/nCoV-2019.reference.fasta' \
+--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \
+--primer_bed 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V3/nCoV-2019.primer.bed'
+
+
+

ARTIC primer scheme version 2 (link).

+

To analyse with nf-core/viralrecon, add these options to the command:

+
--genome 'MN908947.3' \
+--primer_set artic \
+--primer_set_version 2
+

Or, alternatively, you can use the direct links to the FASTA/BED files:

+
--fasta 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V2/nCoV-2019.reference.fasta' \
+--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \
+--primer_bed 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V2/nCoV-2019.primer.bed'
+
+
+

ARTIC primer scheme version 1 (link).

+

To analyse with nf-core/viralrecon, add these options to the command:

+
--genome 'MN908947.3' \
+--primer_set artic \
+--primer_set_version 1
+

Or, alternatively, you can use the direct links to the FASTA/BED files:

+
--fasta 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V1/nCoV-2019.reference.fasta' \
+--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \
+--primer_bed 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V1/nCoV-2019.primer.bed'
+
+
+

Primers for the “Midnight” protocol, also known as “1200” as it produces fragments that are ~1200bp long. This primer scheme is optimised for ONT platforms (link).

+

To analyse with nf-core/viralrecon, add these options to the command:

+
--genome 'MN908947.3' \
+--primer_set artic \
+--primer_set_version 1200
+

Or, alternatively, you can use the direct links to the FASTA/BED files:

+
--fasta 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/primer_schemes/artic/nCoV-2019/V1200/nCoV-2019.reference.fasta' \
+--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \
+--primer_bed 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/primer_schemes/artic/nCoV-2019/V1200/nCoV-2019.bed'
+
+
+
+
+
+

NEB VarSkip

+
+ +
+
+

NEB’s VarSkip kit, primer version 1a (link).

+

For analysis with nf-core/viralrecon the direct link to the FASTA and BED files can be given:

+
--fasta 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V1a/NEB_VarSkip.reference.fasta' \
+--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \
+--primer_bed 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V1a/NEB_VarSkip.scheme.bed'
+
+
+

NEB’s VarSkip kit, primer version 2a (link).

+

For analysis with nf-core/viralrecon the direct link to the FASTA and BED files can be given:

+
--fasta 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V2a/NEB_VarSkip.reference.fasta' \
+--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \
+--primer_bed 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V2a/NEB_VarSkip.scheme.bed'
+
+
+

NEB’s VarSkip kit, primer version 2b (link).

+

For analysis with nf-core/viralrecon the direct link to the FASTA and BED files can be given:

+
--fasta 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V2b/NEB_VarSkip.reference.fasta' \
+--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \
+--primer_bed 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V2b/NEB_VarSkip.scheme.bed'
+
+
+

NEB’s VarSkip kit, primer version 1a, long primers (link).

+

For analysis with nf-core/viralrecon the direct link to the FASTA and BED files can be given:

+
--fasta 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V1a-long/NEB_VarSkip.reference.fasta' \
+--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \
+--primer_bed 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V1a-long/NEB_VarSkip.scheme.bed'
+
+
+
+
+
+

Atoplex

+

ATOPlex kit.

+

For analysis with nf-core/viralrecon the direct link to the FASTA and BED files can be given:

+
--fasta 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/NC_045512.2/GCF_009858895.2_ASM985889v3_genomic.200409.fna.gz' \
+--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/NC_045512.2/GCF_009858895.2_ASM985889v3_genomic.200409.gff.gz' \
+--primer_bed 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/NC_045512.2/amplicon/nCoV-2019.atoplex.V1.bed'
+
+
+

Illumina COVIDseq

+
+ +
+
+

The original COVIDseq kit uses ARTIC V3 primers.

+

See section above for nf-core/viralrecon options.

+
+
+

The COVIDseq RUO version uses ARTIC V4 primers.

+

See section above for nf-core/viralrecon options.

+
+
+
+
+
+

SWIFT / xGEN

+

The “xGen SARS-CoV-2 Amplicon Panels”, previously called “SWIFT Normalase”, is commercialised by IDT. Unfortunately IDT does not make their BED files available publicly. If you purchase a kit from IDT, the company should provide you with the correct BED file to process your samples.

+

NOTE: in the future we will provide a link to a BED file with approximate coordinates, inferred bioinformatically. This can be useful if re-analysing public data.

+
+
+
+

Data Resources

+ +
+
+

Web Tools

+
    +
  • Taxonium - a tool for large tree visualisation.
  • +
+
+
+

Other Courses

+ + + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/materials/appendices/unix_cheatsheet.html b/materials/appendices/unix_cheatsheet.html new file mode 100644 index 0000000..9fcac29 --- /dev/null +++ b/materials/appendices/unix_cheatsheet.html @@ -0,0 +1,928 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Unix cheat sheet

+
+ + + +
+ + + + +
+ + +
+ +

This document gives a brief summary of useful Unix commands. Anything within {} indicates a user-provided input.

+
+
+
+ +
+
+Note +
+
+
+

If you are on Windows, you can install the Windows Subsystem for Linux following our instructions.

+
+
+
+

Documentation and Help

+ ++++ + + + + + + + + + + + + + + +
man {command}manual page for the program
whatis {command}short description of the program
{command} --helpmany programs use the --help flag to print documentation
+
+
+

Listing files

+ + + + + + + + + + + + + + + + + + + +
lslist files in the current directory
ls {path}list files in the specified path
ls -l {path}list files in long format (more information)
ls -a {path}list all files (including hidden files)
+
+
+

Change Directories

+ ++++ + + + + + + + + + + + + + + + + + + +
cd {path}change to the specified directory
cd or cd ~change to the home directory
cd ..move back one directory
pwdprint working directory. Shows the full path of where you are at the moment (useful if you are lost)
+
+
+

Make or Remove Directories

+ ++++ + + + + + + + + + + + + + + +
mkdir {dirname}create a directory with specified name
rmdir {dirname}remove a directory (only works if the directory is empty)
rm -r {dirname}remove the directory and all it’s contents (use with care)
+
+
+

Copy, Move and Remove Files

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
cp {source/path/file1} {target/path/}copy “file1” to another directory keeping its name
cp {source/path/file1} {target/path/file2}copy “file1” to another directory naming it “file2”
cp {file1} {file2}make a copy of “file1” in the same directory with a new name “file2”
mv {source/path/file1} {target/path/}move “file1” to another directory keeping its name
mv {source/path/file1} {target/path/file2}move “file1” to another directory renaming it as “file2”
mv {file1} {file2}is equivalent to renaming a file
rm {filename}remove a file
+
+
+

View Text Files

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
less {file}view and scroll through a text file
head {file}print the first 10 lines of a file
head -n {N} {file}print the first N lines of a file
tail {file}print the last 10 lines of a file
tail -n {N} {file}print the last N lines of a file
head -n {N} {file} | tail -n 1print the Nth line of a file
cat {file}print the whole content of the file
cat {file1} {file2} {...} {fileN}concatenate files and print the result
zcat {file} and zless {file}like cat and less but for compressed files (.zip or .gz)
+
+
+

Find Patterns

+

Finding (and replacing) patterns in text is a very powerful feature of several command line programs. The patterns are specified using regular expressions (shortened as regex), which are not covered in this document. See this Regular Expressions Cheat Sheet for a comprehensive overview.

+ ++++ + + + + + + +
grep {regex} {file}print the lines of the file that have a match with the regular expression pattern
+
+
+

Wildcards

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +
*match any number of characters
?match any character only once
Examples
ls sample*list all files that start with the word “sample”
ls *.txtlist all the files with .txt extension
cp * {another/directory}copy all the files in the current directory to a different directory
+
+
+

Redirect Output

+ ++++ + + + + + + + + + + +
{command} > {file}redirect output to a file (overwrites if the file exists)
{command} >> {file}append output to a file (creates a new file if it does not already exist)
+
+
+

Combining Commands with | Pipes

+ ++++ + + + + + + + + + + + + + + + + + + + + + + +
<command1> | <command2>the output of “command1” is passed as input to “command2”
Examples
ls | wc -lcount the number of files in a directory
cat {file1} {file2} | lessconcatenate files and view them with less
cat {file} | grep "{pattern}" | wc -lcount how many lines in the file have a match with “pattern”
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/search.json b/search.json new file mode 100644 index 0000000..49f0901 --- /dev/null +++ b/search.json @@ -0,0 +1,758 @@ +[ + { + "objectID": "index.html", + "href": "index.html", + "title": "Overview", + "section": "", + "text": "Overview\nThese materials provide a practical guide on analysing viral amplicon sequencing data for genomic surveillance, with a specific focus on SARS-CoV-2. While centered on SARS-CoV-2, the concepts and pipelines explored here are applicable to various viruses. The content includes the analysis of data from clinical isolates and wastewater samples. For clinical isolates, we illustrate how to create consensus sequences for upload to databases like GISAID and for downstream applications such as variant annotation and phylogeny. Wastewater sample analysis includes estimating variant and mutation frequencies. For both applications we will use a standardized bioinformatic pipeline compatible with both Illumina and Nanopore data. The materials cover assigning sequences to lineages, identifying variants of interest and creating visualizations to effectively communicate findings. Throughout, you will acquire foundational bioinformatic skills, including Unix command line usage and scripting for reproducible analyses." + }, + { + "objectID": "index.html#citation", + "href": "index.html#citation", + "title": "Overview", + "section": "Citation", + "text": "Citation\n\nPlease cite these materials if:\n\nYou adapted or used any of them in your own teaching.\nThese materials were useful for your research work. For example, you can cite us in the methods section of your paper: “We carried our analyses based on the recommendations in Tavares et al (2022).”.\n\nYou can reference these materials as:\n\nTavares H, Salehe B, Kumar A, Castle M & UKHSA New Variant Assessment Platform Team (2022) “cambiotraining/sars-cov-2-genomics: Introduction to Sars-CoV-2 Genomics”, https://cambiotraining.github.io/sars-cov-2-genomics\n\nOr, in BibTeX format:\n@Misc{,\n author = {Tavares, Hugo and Salehe, Bajuna and Kumar, Ankit and Castle, Matt and UKHSA New Variant Assessment Platform Team},\n title = {cambiotraining/sars-cov-2-genomics: Introduction to Sars-CoV-2 Genomics},\n month = {March},\n year = {2022},\n url = {https://cambiotraining.github.io/sars-cov-2-genomics},\n}\nPlease make sure to include a link to the materials in the citation. (we will add a DOI in due time)\nThe contributing members from University of Cambridge Bioinformatics Training Facility team are:\n\nMatt Castle, Bioinformatics Training Manager\nHugo Tavares, Senior Teaching Associate\nBajuna Salehe, Teaching Associate\nAnkit Kumar, Teaching Assistant\n\nThe UKHSA’s NVAP Team members that supported these materials are:\n\nDr Leena Inamdar, NVAP Programme Lead and Global Health Lead\nDr Babak Afrough, Senior Project Manager\nAude Wilhelm, Senior Epidemiology Scientist\nRichard Myers, Data Analytics Surveillance Head Bioinformatician\nSam Sims, Bioinformatician\nKate Edington, Bioinformatician\nConstantina Laou, Specialist Lab Advisor" + }, + { + "objectID": "index.html#acknowledgements", + "href": "index.html#acknowledgements", + "title": "Overview", + "section": "Acknowledgements", + "text": "Acknowledgements\nThese materials have been developed as a collaboration between the Bioinformatics Training Facility at the University of Cambridge and the New Variant Assessment Platform (NVAP) program from the UK Health Security Agency.\nOur partners also include COG Train. We also thank the wider community for publicly sharing training resources, including:\n\nThe workshop video series from CLIMB BIG DATA.\nThe Carpentries project, in particular for their Unix Shell lesson, which we adapted for this workshop." + }, + { + "objectID": "setup.html#software", + "href": "setup.html#software", + "title": "Data & Software", + "section": "Software", + "text": "Software\nThe software installation for this course is quite complex and detailed in a separate section." + }, + { + "objectID": "setup.html#data", + "href": "setup.html#data", + "title": "Data & Software", + "section": "Data", + "text": "Data\nDifferent datasets are used throughout these materials. This page provides links to download each dataset, with a brief description for each of them. We have split the data between sequencing platforms." + }, + { + "objectID": "setup.html#illumina", + "href": "setup.html#illumina", + "title": "Data & Software", + "section": "Illumina", + "text": "Illumina\n\nUK Sequences\nThese samples were downloaded from SRA and include samples collected in the UK. These data are used in the following sections of the materials: Consensus Assembly, Lineages and Variants and Building Phylogenies.\n\n Download (zip file)\n\n\n\nClick here to see details of how the data was downloaded from public repositories\n\nWe obtained these data from the SRA repository, using the fastq-dump command as follows:\nfastq-dump --split-3 --gzip ERR5728910 ERR5728911 ERR5728913 ERR5742126 ERR5742297 ERR5742457 ERR5742549 ERR5742553 ERR5761182 ERR5761193 ERR5765358 ERR5770082 ERR5855061 ERR5855065 ERR5855555 ERR5914874 ERR5921129 ERR5921248 ERR5921268 ERR5921355 ERR5921612 ERR5925864 ERR5926784 ERR5932087 ERR5932097 ERR5932290 ERR5932412 ERR5932418 ERR5932680 ERR5932985 ERR5933082 ERR5933090 ERR6106244 ERR6083647 ERR6085882 ERR6086247 ERR6104816 ERR6105221 ERR6105244 ERR6105337 ERR6105341 ERR6106514 ERR6106801 ERR6107074 ERR6128896 ERR6128978 ERR6129122 ERR6129126\n\n\n\n\nSouth Africa\nThese samples were downloaded from SRA and include samples collected between Nov and Dec 2021 in South Africa. These data are used in the worked example South Africa (Illumina).\nWe provide two versions of the data: the full data includes 24 samples (more realistic sample size), whereas the small version includes only 8 samples (quicker processing time for practising).\n\n Full data - 24 samples (zip file)\n Small data - 8 samples (zip file)\n\n\n\nClick here to see details of how the data was downloaded from public repositories\n\nWe obtained these data from the SRA repository, using the fastq-dump command as follows:\nfastq-dump --split-3 --gzip SRR17051908 SRR17051923 SRR17051916 SRR17051953 SRR17051951 SRR17051935 SRR17051932 SRR17054503 SRR17088930 SRR17088928 SRR17088924 SRR17088917 SRR17461712 SRR17461700 SRR17712997 SRR17712994 SRR17712779 SRR17701841 SRR17712711 SRR17701832 SRR17701890 SRR17712607 SRR17712594 SRR17712442 SRR17712435 SRR17712343 SRR17712341 SRR17712321 SRR17712313 SRR17712312 SRR17712387 SRR17970983 SRR17973983 SRR17973948 SRR17973937 SRR17973974 SRR17974004 SRR17974001 SRR17974000 SRR17973999 SRR17973998 SRR17973997 SRR17973996 SRR17973995 SRR17973992 SRR17973991 SRR17973989 SRR17973988\n\n\n\n\nEQA panels\nThe standard panels for External Quality Assessment provided by NEQAS were sequenced on an Illumina platform by UKHSA and used to generate four sets of data for training purposes. These data can be used for the extended exercise: EQA (Exercise).\nWe provide a link to each of the four datasets below (the datasets are very similar to each other):\n\n Dataset 1 (zip file)\n Dataset 2 (zip file)\n Dataset 3 (zip file)\n Dataset 4 (zip file)" + }, + { + "objectID": "setup.html#nanopore", + "href": "setup.html#nanopore", + "title": "Data & Software", + "section": "Nanopore", + "text": "Nanopore\n\nIndia\nThese samples were downloaded from SRA and include samples collected in India and sequenced by the National Institute of Mental Health and Neurosciences. These data are used for the exercises in the following sections of the materials: Consensus Assembly, Lineages and Variants and Building Phylogenies.\n\n Download (zip file)\n\nPlatform: MinION\nNanopore chemistry: not specified (we assumed 9.4.1)\nPrimer set: not specified (we assumed ARTIC primers v3)\nGuppy version: not specified (we assumed 3.6 or higher)\nBasecalling mode: not specified (we assumed “high”)\n\n\nNote that several details needed to run the medaka pipeline were not provided in the public repository. We arbitrarily picked parameters as detailed above, but this is for demonstration purposes only.\n\n\nClick here to see details of how the data was downloaded from public repositories\n\nWe obtained these data from the SRA repository, using the fastq-dump command as follows:\nfastq-dump --split-3 --gzip SRR14494107 SRR14493634 SRR14493632 SRR14493631 SRR14493707 SRR14493705 SRR14493730 SRR14493729 SRR14493728 SRR14493727 SRR14493726 SRR14493725 SRR14493724 SRR14493723 SRR14493722 SRR14493721 SRR14493719 SRR14493718 SRR14493717 SRR14493716 SRR14493715 SRR14493714 SRR14493713 SRR14493712 SRR14493711 SRR14494106 SRR14494095 SRR14494092 SRR14494091 SRR14494090 SRR14494089 SRR14494088 SRR14494087 SRR14494086 SRR14494105 SRR14494104 SRR14494103 SRR14494102 SRR14494101 SRR14494100 SRR14493626 SRR14494099 SRR14494098 SRR14494097 SRR14494096 SRR14494094 SRR14494093\n\n\n\n\nSwitzerland\nThese samples were downloaded from SRA and include samples collected between Nov 2021 and Jan 2022 in Switzerland and sequenced by the Institute for Infectious Diseases, University of Bern. These data are used in the worked example Switzerland (Nanopore).\nWe provide two versions of the data: the full data includes 65 samples (more realistic sample size), whereas the small version includes only 10 samples (quicker processing time for practising).\n\n Full data - 65 samples (zip file)\n Small data - 10 samples (zip file)\n\nPlatform: GridION\nNanopore chemistry: not specified (we assumed 9.4.1)\nPrimer set: ARTIC primers v3\nGuppy version: not specified (we assumed 3.6 or higher)\nBasecalling mode: not specified (we assumed “fast”)\n\n\nNote that several details needed to run the medaka pipeline were not provided in the public repository. We arbitrarily picked parameters as detailed above, but this is for demonstration purposes only.\n\n\nClick here to see details of how the data was downloaded from public repositories\n\nWe obtained these data from the SRA repository, using the fastq-dump command as follows:\nfastq-dump --split-3 --gzip ERR8971298 ERR8961150 ERR8961147 ERR8961133 ERR8961130 ERR8961129 ERR8961128 ERR8961124 ERR8961123 ERR8961116 ERR8961115 ERR8961114 ERR8961112 ERR8961110 ERR8961065 ERR8961062 ERR8961333 ERR8959962 ERR8959961 ERR8959960 ERR8959959 ERR8959958 ERR8959957 ERR8959956 ERR8959955 ERR8959953 ERR8959952 ERR8959950 ERR8959949 ERR8959948 ERR8959946 ERR8959945 ERR8959943 ERR8959942 ERR8959940 ERR8959939 ERR8959938 ERR8959937 ERR8959936 ERR8959934 ERR8959933 ERR8959931 ERR8959927 ERR8959926 ERR8959925 ERR8959912 ERR8959911 ERR8959905 ERR8959901 ERR8959892 ERR8960229 ERR8960215 ERR8960216 ERR8960217 ERR8960218 ERR8960219 ERR8960220 ERR8960221 ERR8960223 ERR8959343 ERR8959341 ERR8959330 ERR8959327\n\n\n\n\nEQA panels\nThe standard panels for External Quality Assessment provided by NEQAS were sequenced on an ONT platform by TODO. These data can be used for the extended exercise: EQA (Exercise).\nWe provide a link to datasets corresponding to two different runs:\n\n Dataset 1 (zip file)\n\nPlatform: PromethION\nNanopore chemistry: 9.4.1\nPrimer set: midnight\nGuppy version: 6.6\nBasecalling mode: “fast”\n\n Dataset 2 (zip file)\n\nPlatform: GridION\nNanopore chemistry: 9.4.1\nPrimer set: 4.1\nGuppy version: 6.6\nBasecalling mode: “high”\n\n\nWe thank the collaborating institutions for sharing their trainining data with us." + }, + { + "objectID": "materials/01-intro/01-surveillance.html#what-is-sars-cov-2", + "href": "materials/01-intro/01-surveillance.html#what-is-sars-cov-2", + "title": "1  SARS-CoV-2 genomic surveillance", + "section": "1.1 What is SARS-CoV-2?", + "text": "1.1 What is SARS-CoV-2?\nSARS-CoV-2 (Severe acute respiratory syndrome coronavirus 2) is a betacoronavirus resposible for the COVID-19 disease and caused a global outbreak leading to an ongoing pandemic. The initial spread of this virus started in the city of Wuhan, in China. Despite early efforts to contain its spread in China (through several lockdowns in the country), the virus spread to other provinces within China and, eventually, to other countries across the world. This led to the World Health Organisation (WHO) declaring a public health emergency on 30 January 2020 and then a pandemic on 11 March 2020.\nSARS-CoV-2 is an RNA virus, composed of single-stranded RNA. The first SARS-CoV-2 genome was published in January 2020 and is approximately 30Kb long. It encodes several proteins including the so-called Spike protein (or ‘S’ for short), which is used by the virus to interact and eventually enter human cells and cause infection. This interaction happens by the binding of the S protein to the ACE2 protein receptor found in human cells.\n\n\n\nGenome structure of SARS-CoV-2. Source: Rahimi, Mirzazadeh & Tavakolpour 2021\n\n\nThe genome sequence of SARS-CoV-2 has been a huge contributor to our ability to manage current pandemic. Namely:\n\nIt allowed the development of a vaccine to target the S protein.\nIt allowed the development of diagnostic tests for positive cases (lateral flow test and PCR-based test).\nIt allowed the development of protocols for whole-genome sequencing of the virus from positive human samples.\n\nThis last point is the focus of our workshop, and we will spend some time looking at how to analyse these data.\nYou can watch the video below, created by the Maastrich University, if you want to learn more about the lifecycle of the SARS-CoV-2 virus and the role that its different proteins play in this context:" + }, + { + "objectID": "materials/01-intro/01-surveillance.html#genomic-surveillance", + "href": "materials/01-intro/01-surveillance.html#genomic-surveillance", + "title": "1  SARS-CoV-2 genomic surveillance", + "section": "1.2 Genomic Surveillance", + "text": "1.2 Genomic Surveillance\nThe number of SARS-CoV-2 genomes sequenced is the largest of a pathogen ever done. As such, it has enabled us to track how the virus evolves and spreads both at a local and global scale at an unprecendented resolution. This allowed the identification of mutations that affect characteristics of the virus, such as its transmissibility or severity of the disease, and testing new strains of the virus to understand whether they are effectively neutralised by current or future vaccines.\nOne of the main applications of SARS-CoV-2 sequencing is to infer relationships between the different circulating forms of the virus. This is done by comparing these sequences and building phylogenetic trees that reflect their sequence similarities. From these, it is possible to identify particular clusters of similar sequences that spread faster than expected, and may therefore be associated with mutations that increase the virus’ fitness.\n\n\n\nExample of global phylogeny from the Nextstrain public server. Colours show clusters of similar sequences. (Screenshot taken Feb 2022)\n\n\nFor example, one of the first mutations identified from these sequencing efforts was A23403G (an adenine was replaced by a guanine in position 23,403 of the genome), which caused an aminoacid substitution in the S protein that increased virus infectivity and transmissibility. Since then, many new forms of the virus have been identified as being of particular concern for public health, and have been classified by the WHO as Variants of Concern. These have been named based on letters of the Greek alphabet, and have been a crucial way to inform public health policy and containment measures around the world.\nIn addition to the WHO variant nomenclature, there are three main projects that have been instrumental in grouping SARS-CoV-2 sequences into similar groups:\n\nThe GISAID nomenclature classifies sequences based on key mutations that define particular groups of sequences in the global phylogeny. It also complements its classification by borrowing information from the Pango nomenclature system.\nThe Pango nomenclature is based on the phylogeny of SARS-CoV-2 and defined as groups of sequences that share a common ancestor and a distinctive sequence feature (for example, all share the same single nucleotide change). This nomenclature is also sometimes referred as “Rambaut et al. 2020” after the respective publication.\nThe Nextstrain nomenclature is slightly more informal than the above, its main purpose being to facilitate public health discussions. Despite this, it is still informed by the phylogenetic placement of sequences in a tree and therefore has a large overlap with the Pango nomenclature.\n\nWe will learn more about how sequences are classified into lineages and variants of concern in the section about Lineage Assignment.\n\n\n\n\n\n\nWhat is the difference: strain, lineage, clade, variant?\n\n\n\nThese terms are sometimes used interchangeably in informal conversations about the different forms of SARS-CoV-2. For our purposes, these are the definitions we will use:\nA strain is a group of viruses that are sufficiently diverged from others, so that they are quite distinct at a sequence level as well as in their biological properties. SARS-CoV-2 is still considered to be a single strain. Examples of other coronavirus strains are SARS-CoV and MERS-CoV.\nThe terms lineage and clade are somewhat similar, in that both represent groups of similar sequences, inferred from a phylogenetic analysis and sharing a common ancestor. Their main difference (at least in the current SARS-CoV-2 nomenclature systems) is the level of resolution. Clades tend to be more broadly defined and therefore include more sequences within it. They are useful to discuss long-term trends during a pandemic. Lineages have a finer resolution, being useful to identify patterns related to a recent outbreak.\nSince a phylogenetic tree is inherently hierarchical, there isn’t always a clear-cut way of defining where one lineage/clade starts and another ends. That is why lineage classification is a partially manual process, involving human curation by experts.\nFinally, the term variant is usually used to refer to WHO’s variants of interest or variants of concern (e.g. the Alpha, Delta and Omicron variants). Variants are distinct from each other by the combination of all sequence changes in their genomes. The term “variant” can be ambiguous when used in the field of bioinformatics, and we return to this in the section about Lineage Assigment" + }, + { + "objectID": "materials/01-intro/01-surveillance.html#the-gisaid-database", + "href": "materials/01-intro/01-surveillance.html#the-gisaid-database", + "title": "1  SARS-CoV-2 genomic surveillance", + "section": "1.3 The GISAID Database", + "text": "1.3 The GISAID Database\nThe widespread use of genome sequences would have not been possible without the ability to centrally collect these sequences and make them available to researchers and public health professionals. The main repository used to deposit SARS-CoV-2 sequences is the database managed by the GISAID Initiative. This allows sharing the sequencing data as well as metadata associated with it (such as dates of collection, geo-location, patient information, etc.). For example, the outbreak.info website that we just used in the exercise above directly pulls data from GISAID to update its reports on a daily basis.\nAt the end of this course you too will be able to contribute to this database, by producing full genome sequences assembled from sequencing data.\n\n\n\n\n\n\nCreate a GISAID account\n\n\n\nGo to the GISAID registration page and create an account, so you can gain access to the data stored in GISAID as well as the ability to submit your own sequences in the future." + }, + { + "objectID": "materials/01-intro/01-surveillance.html#sars-cov-2-sequencing", + "href": "materials/01-intro/01-surveillance.html#sars-cov-2-sequencing", + "title": "1  SARS-CoV-2 genomic surveillance", + "section": "1.4 SARS-CoV-2 Sequencing", + "text": "1.4 SARS-CoV-2 Sequencing\nRoutine SARS-CoV-2 sequencing is done with a method generally referred to as amplicon sequencing. This method relies on amplifying the genetic material using polymerase chain reaction (PCR) with a panel of primers designed against the known SARS-CoV-2 genome.\nOne of the most popular methods for preparing virus RNA for sequencing is amplicon sequencing. This method consists of amplifying the genetic material by PCR using a panel of primers that covers the entire genome of the target virus. This is required, since the starting amount of viral genetic material in a sample is typically very low (most of the material will belong to the host - in this case, human RNA).\nThe most popular protocol for amplicon sequencing has been developed by the ARTIC network, whose aim is to develop standardised protocols for viral sample processing. The group has designed and tested a panel of primers that work well to amplify the SARS-CoV-2 genome in a mostly unbiased way. This is a challenging task, as the protocol involves pooling hundreds of primers together in a single reaction! Also, as the virus mutates in the population, primers that used to work in the original template genome may no longer work in the new variants circulating in the population (“primer erosion”). Therefore, the ARTIC primers have gone through several versions, which are updated and optimised to work on the most common circulating lineages. These are called “primer schemes” and are made publicly available in a public repository.\n\n\n\nSchematic of the ARTIC protocol. The RNA is reverse-transcribed to cDNA, then two pools of primers (that cover the entire SARS-CoV-2 genome) are used to PCR-amplify the material. This material is used to prepare sequencing libraries for the relevant platform that will be used downstream (Illumina or Nanopore). Source: Gohl et al. 2020\n\n\nBesides amplicon sequencing, other methods can also be used to obtain SARS-Cov-2 genomes:\n\nMetagenomic RNA sequencing was the method used to assemble the first SARS-CoV-2 genome and one of the first sequences in Cambodia. This method consists of extracting viral RNA (using commercially available kits) followed by high-throughput RNA-seq. The resulting sequences are then compared with nucleotide databases of known organisms, and viral sequences selected for de-novo assembly. This approach is suitable when the virus sequence is unknown, but requires a high sequencing depth, increasing its costs.\nSequence capture protocols are also available, whereby the samples are enriched for the target virus by using a panel of probes against the SARS-CoV-2 genome, followed by sequencing and reference-based assembly. This approach is more similar to amplicon sequencing, as it works by enriching the sample for the known virus.\n\n\n\n\nSimplified diagram of the main steps involved in metagenomic and amplicon sequencing approaches. In amplicon sequencing the cDNA material is first PCR-amplified with virus-specific primers to enrich the sample, followed by sequencing and downstream bioinformatic analysis. With the metagenomic approach the mixed cDNA material is sequenced and the resulting sequences are bioinformatically separated between known sequences (from other organisms) and unknown sequences. The unknown sequences can then be de-novo assembled into a new genome.\n\n\nDespite these alternative methods, amplicon sequencing remains one of the most popular methods for large-scale viral surveillance due to its low cost and high-throughput. The data generated from this method will be the focus of this course.\n\n\n\n\n\n\nIllumina or Nanopore?\n\n\n\nBoth of these sequencing platforms can be used to sequence amplicon samples.\nNanopore platforms allow sequencing 96 samples at a time, are readily available as portable devices, and have fast run times. This gives them great flexibility, making them an excellent solution for rapidly building sequencing capacity in a lab.\nBy comparison, Illumina platforms give higher throughput, are cheaper to run per sample and have lower error rates. However, they require substantial upfront cost to setup and equip in the lab and take longer to run.\n\n\n\n\n\n\n\n\nAlternative Amplicon Sequencing Protocols\n\n\n\nAlthough the ARTIC protocol is one of the most popular used in routine SARS-CoV-2 sequencing, there are alternative sets of primers and protocols available.\nFor example, Thermo Fisher has the Ion AmpliSeq SARS-CoV-2 kit, designed to work with Ion Torrent sequencing platforms.\nAn alternative protocol that has been developed by the community is the midnight protocol. This protocol consists of amplifying larger PCR fragments, thus requiring fewer primer pairs than the ARTIC protocol. This leads to a lower complexity in the multiplex PCR reaction and fewer chances of PCR dropout due to mis-priming againts new variants. However, because it uses longer PCR fragments, it can only be used with long-read sequencing (Nanopore) and not short-read sequencing (Illumina).\nThe Coronavirus Method Development Community on the protocols.io platform is a good source of alternative sequencing protocols for SARS-CoV-2." + }, + { + "objectID": "materials/01-intro/01-surveillance.html#sample-collection", + "href": "materials/01-intro/01-surveillance.html#sample-collection", + "title": "1  SARS-CoV-2 genomic surveillance", + "section": "1.5 Sample Collection", + "text": "1.5 Sample Collection\nThere are two big considerations when collecting samples for sequencing:\n\nIs there enough viral material in the sample (viral load)?\nDid I collect all the necessary information about each sample (metadata)?\n\n\n1.5.1 Viral Load\nWhen collecting patient samples for sequencing, it is important to quantify the viral load in the sample. This is usually done by quantitative RT-PCR (RT-qPCR), whereby the amplification of the samples is monitored by measuring a dye that is incorporated during the PCR reaction. This results in amplification curves, which reflect the amount of RNA present in the sample. The more PCR cycles are needed to saturate the signal, the lower the amount of virus in the sample.\n\n\n\nOverview of RT-qPCR quantification of viral load in a sample. Adapted from Smyrlaki et al. 2020\n\n\nThe result of RT-qPCR is usually expressed as the PCR cycle at which a particular threshold in the amplification curve is reached, the Ct value. Generally, samples with Ct > 30 are not worth sequencing, as their genome completeness is likely going to be low due to the low amounts of starting material.\n\n\n\nRelationship between RT-qPCR Ct value and resulting genome completeness. Source: Jared Simpson’s talk at CLIMB BIG DATA workshop\n\n\n\n\n1.5.2 Metadata\nOne important consideration when collecting samples, is to record as much information as possible about each sample. The Public Health Alliance for Genomic Epidemiology (PHA4GE) coalition provides several guidelines and a protocol to aid in metadata collection. There are also essential metadata needed to upload new SARS-CoV-2 genome sequences to the GISAID database (Figure).\n\n\n\nSnapshot of the GISAID upload template metadata sheet.\n\n\nTwo key pieces of information for genomic surveillance are the date of sample collection and the geographic location of that sample. This information can be used to understand which variants are circulating in an area at any given time.\nPrivacy concerns need to be considered when collecting and storing sensitive data. However, it should be noted that sensitive data can still be collected, even if it is not shared publicly (e.g. via the GISAID database). Such sensitive information may still be useful for the relevant public health authorities, who may use those sensitive information for a finer analysis of the data. For example, epidemiological analysis will require individual-level metadata (“person, place, time”) to be available, in order to track the dynamics of transmission within a community.\nThis is the general advice when it comes to metadata collection: record as much information about each sample as possible!" + }, + { + "objectID": "materials/01-intro/01-surveillance.html#sars-cov-2-bioinformatics", + "href": "materials/01-intro/01-surveillance.html#sars-cov-2-bioinformatics", + "title": "1  SARS-CoV-2 genomic surveillance", + "section": "1.6 SARS-CoV-2 Bioinformatics", + "text": "1.6 SARS-CoV-2 Bioinformatics\nWhat bioinformatic skills do we need in order to analyse SARS-CoV-2 genome sequencing data? While there are several software tools that have been specifically developed for SARS-CoV-2 analysis (and we will see some of them in this course), there is a set of foundational skills that are applicable to any bioinformatics application:\n\nThe use of the Unix command line. Linux is the most common operating system for computational work and most of the bioinformatic software only runs on it.\nGetting familiar with common file formats in bioinformatics. This includes files to store nucleotide sequences, sequence alignments to a reference genome, gene annotations, phylogenetic trees, amongst others.\nUnderstand software tools’ documentation and how to configure different options when running our analyses.\n\nWe will turn to these topics in the following sessions." + }, + { + "objectID": "materials/01-intro/01-surveillance.html#exercises", + "href": "materials/01-intro/01-surveillance.html#exercises", + "title": "1  SARS-CoV-2 genomic surveillance", + "section": "1.7 Exercises", + "text": "1.7 Exercises\n\n\n\n\n\n\nWHO Variants\n\n\n\n\n\n\n\n\nLooking through the WHO variants page, can you find what the difference is between a Variant of Interest (VOI) and a Variant of Concern (VOC)?\nCan you find the correspondence between the VOCs and their respective lineages in other classification systems (GISAID, Nextstrain and Pango)?\nGo to the outbreak.info website and search for a country of your choice (for example, your country of origin or where you live).\nHow many sequences are available from the last 360 days? (Note: by default only the last 60 days are shown. You can change this in the text box found on the right of the report page.)\nWhat were the most common lineages of the virus in circulation in the last 360 days? Do you notice sharp changes in the frequency of WHO Variants of Concern?\n\n\n\n\n\n\n\n\n\n\n\n\nMetadata\n\n\n\n\n\n\n\nOpen the folder under “Course_Materials > 01-Unix > metadata”, where you will find several CSV files with information about samples that were sequenced and will be analysed by us later in the course. Double-click to open one of these files (any of them is fine), which should open them on a spreadsheet software (on our training machines it is LibreOffice, but Excel would also work).\nThe column names of these files are based on the PHA4GE nomenclature system.\n\nGo to the PHA4GE metadata collection protocol and read throught the first steps to find the link to the collection templates.\nFrom that link, go to the Supporting Materials to find the “field mappings” spreadsheet, which matches naming conventions between PHA4GE and other databases such as GISAID.\nBased on this information, can you match the column names from our sample information table to the fields required by GISAID?\nWhat do you think is the naming convention for the isolate name? How would you adjust this when uploading the data to GISAID?\n\n\n\n\n\n\n\nAnswer\n\n\n\n\n\n\n\nThe first step of this protocol provides with a link to a GitHub repository containing several template files: https://github.com/pha4ge/SARS-CoV-2-Contextual-Data-Specification.\nIf we follow that link, we will see several spreadsheet files (for Excel or LibreOffice) with templates that can be used to record samples metadata. In the “Supporting Materials” we find a link to “PHA4GE to WHO and sequence repository field mappings”. If we download and open that file we will see a spreadhseet with 4 tabs. Each of the tabs shows the equivalence between the PHA4GE column names and the equivalent names used by other platforms.\nLooking at the equivalence between the column names in our metadata sheets and the GISAID nomenclature, we can identify only some of them:\n\n\n\n\n\n\n\n\nOur Name\nGISAID equivalent\nComments\n\n\n\n\nsample\n\n\n\n\nsample_collection_date\nCollection date\n\n\n\ngeo_loc_country\nLocation\n\n\n\ngeo_loc_region\nLocation\nIn GISAID the field “Location” is used for “Continent / Country or Territory / Region”\n\n\nlatitude\n\n\n\n\nlongitude\n\n\n\n\nsequencing_instrument\nSequencing technology\n\n\n\nsample_collection_year\n\n\n\n\nsample_collection_month\n\n\n\n\nsample_collection_day\n\n\n\n\norganism\n\n\n\n\nisolate\nVirus name\n\n\n\nsequencing_protocol_name\n\n\n\n\namplicon_pcr_primer_scheme\n\n\n\n\nsequencing_protocol\n\n\n\n\n\nLooking at this spreadhseet, we can also see this explanation about the “Virus Name” field:\n\nWhile the meanings and structures of the GISAID field “Virus name” and the PHA4GE field “isolate” overlap, GISAID requires a slightly different structure for Virus name. e.g. PHA4GE structure: SARS-CoV-2/country/sampleID/2020, GISAID structure: hCov-19/country/sampleID/2020. Change “SARS-CoV-2” to “hCov-19” in the isolate name.\n\nSo, in order to upload our data to GISAID we would need to change the name of the virus accordingly." + }, + { + "objectID": "materials/01-intro/01-surveillance.html#summary", + "href": "materials/01-intro/01-surveillance.html#summary", + "title": "1  SARS-CoV-2 genomic surveillance", + "section": "1.8 Summary", + "text": "1.8 Summary\n\n\n\n\n\n\nKey Points\n\n\n\n\nThe sequencing of SARS-CoV-2 genomes has allowed the tracking of new variants throughout the pandemic.\nThe World Health Organisation defines Variants of Concern as SARS-CoV-2 forms with characteristics of public health concern. This includes increased transmissibility, virulence, disease symptoms or vaccine resistance.\nGISAID, Pango and Nextstrain are organisations whose work includes the classification of SARS-CoV-2 genomes into groups that may later be classified as variants of concern by WHO.\nGISAID also plays a key role as the main database for assembled SARS-CoV-2 genomes, submitted by the community.\nRoutine SARS-CoV-2 sequencing is usually done by amplicon sequencing (amplifying an infected sample by PCR using primers that cover the entire genome). The most popular protocol has been developed by the ARTIC network.\nOther methods of sequencing include metagenomic and sequence capture. However, these methods require more sequencing and therefore are not commonly used for sequencing at a population scale.\nBoth Illumina and Nanopore platforms can be used for sequencing SARS-CoV-2 amplicon samples.\nMetadata collection is essential for interpreting the results of the sequencing.\n\nFor genomic surveillance purposes recording of geographic location and date of sampling are crucial.\nOther useful information includes details about the sequencing (e.g. sample preparation protocols and sequencing platforms used)." + }, + { + "objectID": "materials/01-intro/02-ngs.html#next-generation-sequencing", + "href": "materials/01-intro/02-ngs.html#next-generation-sequencing", + "title": "2  Introduction to NGS", + "section": "2.1 Next Generation Sequencing", + "text": "2.1 Next Generation Sequencing\nThe sequencing of genomes has become more routine due to the rapid drop in DNA sequencing costs seen since the development of Next Generation Sequencing (NGS) technologies in 2007. One main feature of these technologies is that they are high-throughput, allowing one to more fully characterise the genetic material in a sample of interest.\nThere are three main technologies in use nowadays, often referred to as 2nd and 3rd generation sequencing:\n\nIllumina’s sequencing by synthesis (2nd generation)\nOxford Nanopore, shortened ONT (3rd generation)\nPacific Biosciences, shortened PacBio (3rd generation)\n\nThe video below from the iBiology team gives a great overview of these technologies.\n\n\n\n\n\n2.1.1 Illumina Sequencing\nIllumina’s technology has become a widely popular method, with many applications to study transcriptomes (RNA-seq), epigenomes (ATAC-seq, BS-seq), DNA-protein interactions (ChIP-seq), chromatin conformation (Hi-C/3C-Seq), population and quantitative genetics (variant detection, GWAS), de-novo genome assembly, amongst many others.\nAn overview of the sequencing procedure is shown in the animation video below. Generally, samples are processed to generate so-called sequencing libraries, where the genetic material (DNA or RNA) is processed to generate fragments of DNA with attached oligo adapters necessary for the sequencing procedure (if the starting material is RNA, it can be converted to DNA by a step of reverse transcription). Each of these DNA molecule is then sequenced from both ends, generating pairs of sequences from each molecule, i.e. paired-end sequencing (single-end sequencing, where the molecule is only sequenced from one end is also possible, although much less common nowadays).\nThis technology is a type of short-read sequencing, because we only obtain short sequences from the original DNA molecules. Typical protocols will generate 2x50bp to 2x250bp sequences (the 2x denotes that we sequence from each end of the molecule).\n\n\n\n\nThe main advantage of Illumina sequencing is that it produces very high-quality sequence reads (current protocols generate reads with an error rate of less than <1%) at a low cost. However, the fact that we only get relatively short sequences means that there are limitations when it comes to resolving particular problems such as long sequence repeats (e.g. around centromeres or transposon-rich areas of the genome), distinguishing gene isoforms (in RNA-seq), or resolving haplotypes (combinations of variants in each copy of an individual’s diploid genome).\n\n\n2.1.2 Nanopore Sequencing\nNanopore sequencing is a type of long-read sequencing technology. The main advantage of this technology is that it can sequence very long DNA molecules (up to megabase-sized), thus overcoming the main shortcoming of short-read sequencing mentioned above. Another big advantage of this technology is its portability, with some of its devices designed to work via USB plugged to a standard laptop. This makes it an ideal technology to use in situations where it is not possible to equip a dedicated sequencing facility/laboratory (for example, when doing field work).\n\n\n\nOverview of Nanopore sequencing showing the highly-portable MinION device. The device contains thousands of nanopores embeded in a membrane where current is applied. As individual DNA molecules pass through these nanopores they cause changes in this current, which is detected by sensors and read by a dedicated computer program. Each DNA base causes different changes in the current, allowing the software to convert this signal into base calls.\n\n\nOne of the bigger challenges in effectively using this technology is to produce sequencing libraries that contain high molecular weight, intact, DNA. Another disadvantage is that, compared to Illumina sequencing, the error rates at higher, at around 5%.\n\n\n\n\n\n\nIllumina or Nanopore for SARS-CoV-2 sequencing?\n\n\n\nBoth of these platforms have been widely popular for SARS-CoV-2 sequencing. They can both generate data with high-enough quality for the assembly and analysis of SARS-CoV-2 genomes. Mostly, which one you use will depend on what sequencing facilities you have access to.\nWhile Illumina provides the cheapest option per sample of the two, it has a higher setup cost, requiring access to the expensive sequencing machines. On the other hand, Nanopore is a very flexible platform, especially its portable MinION devices. They require less up-front cost allowing getting started with sequencing very quickly in a standard molecular biology lab." + }, + { + "objectID": "materials/01-intro/02-ngs.html#sequencing-analysis", + "href": "materials/01-intro/02-ngs.html#sequencing-analysis", + "title": "2  Introduction to NGS", + "section": "2.2 Sequencing Analysis", + "text": "2.2 Sequencing Analysis\nIn this section we will demonstrate two common tasks in sequencing data analysis: sequence quality control and mapping to a reference genome. There are many other tasks involved in analysing sequencing data, but looking at these two examples will demonstrate the principles of running bioinformatic programs. We will later see how bioinformaticians can automate more complex analyses in the consensus assembly section.\nOne of the main features in bioinformatic analysis is the use of standard file formats. It allows software developers to create tools that work well with each other. For example, the raw data from Illumina and Nanopore platforms is very different: Illumina generates images; Nanopore generates electrical current signal. However, both platforms come with software that converts those raw data to a standard text-based format called FASTQ.\n\n2.2.1 FASTQ Files\nFASTQ files are used to store nucleotide sequences along with a quality score for each nucleotide of the sequence. These files are the typical format obtained from NGS sequencing platforms such as Illumina and Nanopore (after basecalling).\nThe file format is as follows:\n@SEQ_ID <-- SEQUENCE NAME\nAGCGTGTACTGTGCATGTCGATG <-- SEQUENCE\n+ <-- SEPARATOR\n%%).1***-+*''))**55CCFF <-- QUALITY SCORES\nIn FASTQ files each sequence is always represented across 4 lines. The quality scores are encoded in a compact form, using a single character. They represent a score that can vary between 0 and 40 (see Illumina’s Quality Score Encoding). The reason single characters are used to encode the quality scores is that it saves space when storing these large files. Software that work on FASTQ files automatically convert these characters into their score, so we don’t have to worry about doing this conversion ourselves.\nThe quality value in common use is called a Phred score and it represents the probability that the respective base is an error. For example, a base with quality 20 has a probability \\(10^{-2} = 0.01 = 1\\%\\) of being an error. A base with quality 30 has \\(10^{-3} = 0.001 = 0.1\\%\\) chance of being an error. Typically, a Phred score threshold of >20 or >30 is used when applying quality filters to sequencing reads.\nBecause FASTQ files tend to be quite large, they are often compressed to save space. The most common compression format is called gzip and uses the extension .gz. To look at a gzip file, we can use the command zcat, which decompresses the file and prints the output as text.\nFor example, we can use the following command to count the number of lines in a compressed FASTQ file:\nzcat sequences.fq.gz | wc -l\nIf we want to know how many sequences there are in the file, we can divide the result by 4 (since each sequence is always represented across four lines).\n\n\n2.2.2 FASTQ Quality Control\nOne of the most basic tasks in Illumina sequence analysis is to run a quality control step on the FASTQ files we obtained from the sequencing machine.\nThe program used to assess FASTQ quality is called FastQC. It produces several statistics and graphs for each file in a nice report that can be used to identify any quality issues with our sequences.\nThe basic command to run FastQC is:\nfastqc --outdir PATH_TO_OUTPUT_DIRECTORY PATH_TO_SEQUENCES\nFastQC can process several samples at once, and often we can use the * wildcard to do this. We will see an example of this in the following exercise.\nEach FastQC HTML report contains a section with a different quality assessment plot. Each of these are explained in the online documentation:\n\nBasic statistics\nPer base sequence quality\nPer sequence quality scores\nPer base sequence content\nPer sequence GC content\nPer base N content\nSequence length distribution\nSequence duplication levels\nOverrepresented sequences\nAdapter content\nPer tile sequence quality\n\nFor example, looking at the “Per base sequence quality” section for one of our samples, we can see a very high quality score, which is typical of Illumina data nowadays.\n\n\n\nSequence quality plot from FastQC for one of our samples. The blue line shows the average across all samples. This sample is very high quality as all sequences have quality > 20 across the entire length of the reads.\n\n\n\n\n\n\n\n\nQuality Control Nanopore Reads\n\n\n\nAlthough FastQC can run its analysis on any FASTQ files, it has mostly been designed for Illumina data. You can still run FastQC on basecalled Nanopore data, but some of the output modules may not be as informative. FastQC can also run on FAST5 files, using the option --nano.\nYou can also use MinIONQC, which takes as input the sequence_summary.txt file, which is a standard output file from the Guppy software used to convert Nanopore electrical signal to sequence calls.\n\n\n\n\n2.2.3 Read Mapping\nA common task in processing sequencing reads is to align them to a reference genome, which is typically referred to as read mapping or read alignment. We will continue exemplifying how this works for Illumina data, however the principle is similar for Nanopore data (although the software used is often different, due to the higher error rates and longer reads typical of these platforms).\nGenerally, these are the steps involved in read mapping (figure below):\n\nGenome Indexing | Because reference genomes can be quite long, most mapping algorithms require that the genome is pre-processed, which is called genome indexing. You can think of a genome index in a similar way to an index at the end of a textbook, which tells you in which pages of the book you can find certain keywords. Similarly, a genome index is used by mapping algorithms to quickly search through its sequence and find a good match with the reads it is trying to align against it. Each mapping software requires its own index, but we only have to generate the genome index once.\nRead mapping | This is the actual step of aligning the reads to a reference genome. There are different popular read mapping programs such as bowtie2 or bwa. The input to these programs includes the genome index (from the previous step) and the FASTQ file(s) with reads. The output is an alignment in a file format called SAM (text-based format - takes a lot of space) or BAM (compressed binary format - much smaller file size).\nBAM Sorting | The mapping programs output the sequencing reads in a random order (the order in which they were processed). But, for downstream analysis, it is good to sort the reads by their position in the genome, which makes it faster to process the file.\nBAM Indexing | This is similar to the genome indexing we mentioned above, but this time creating an index for the alignment file. This index is often required for downstream analysis and for visualising the alignment with programs such as IGV.\n\n\n\n\nDiagram illustrating the steps involved in mapping sequencing reads to a reference genome. Mapping programs allow some differences between the reads and the reference genome (red mutation shown as an example). Before doing the mapping, reads are usually filtered for high-quality and to remove any sequencing adapters. The reference genome is also indexed before running the mapping step. The mapped file (BAM format) can be used in many downstream analyses. See text for more details.\n\n\nWe have already prepared the SARS-CoV-2 genome index for the bowtie2 aligner. We have also prepared a shell script with the code to run the three steps above as an example. Let’s look at the content of this file (you can open it with nano scripts/mapping.sh):\n# mapping\nbowtie2 -x resources/reference/bowtie2/sarscov2 -1 data/reads/ERR6129126_1.fastq.gz -2 data/reads/ERR6129126_2.fastq.gz --threads 5 | samtools sort -o results/bowtie2/ERR6129126.bam -\n\n# index mapped file\nsamtools index results/bowtie2/ERR6129126.bam\n\n# obtain some mapping statistics\nsamtools stats results/bowtie2/ERR6129126.bam > results/bowtie2/ERR6129126.stats.txt\nIn the first step, mapping, we are using two tools: bowtie2 and samtools. bowtie2 is the mapping program and samtools is a program used to manipulate SAM/BAM alignment files. In this case we used the | pipe to send the output of bowtie2 directly to samtools:\n\n-x is the prefix of the reference genome index.\n-1 is the path to the first read in paired-end sequencing.\n-2 is the path to the second read in paired-end sequencing.\n--threads 5 indicates we want to use 5 CPUs (or threads) to do parallel processing of the data.\n| is the pipe that sends the output from bowtie2 to samtools sort.\n-o is the name of the output file. By setting the file extension of this file to .bam, samtools will automatically save the file in the compressed format (which saves a lot of space).\nThe - symbol at the end of the samtools command indicates that the input is coming from the | pipe.\n\nWe also have a step that creates an index file for the BAM file using samtools index. This creates a file with the same name and .bai extension.\nFinally, the script also contains a step that collects some basic statistics from the alignment, which we save in a text file. We will see how this file can be used to produce a quality control report below.\n\n\n2.2.4 Visualising BAM Files in IGV\nOne thing that can be useful is to visualise the alignments produced in this way. We can use the program IGV (Integrative Genome Viewer) to do this:\n\nOpen IGV and go to File → Load from file….\nIn the file browser that opens go to the folder results/bowtie2/ and select the file ERR6129126.bam to open it.\nGo back to File → Load from file… and this time load the BED files containing the primer locations. These can be found in resources/primers/artic_primers_pool1.bed and resources/primers/artic_primers_pool2.bed.\n\nThere are several ways to search and browse through our alignments, exemplified in the figure below.\n\n\n\nScreenshot IGV program. The search box at the top can be used to go to a specific region in the format “CHROM:START-END”. In the case of SARS-CoV-2 there is only one “chromosome” called NC_045512.2 (this is the name of the reference genome), so if we wanted to visualise the region between positions 21563 and 25384 (the Spike gene) we would write “NC_045512.2:21563-25384”.\n\n\n\n\n2.2.5 Quality Reports\nWe’ve seen the example of using the program FastQC to assess the quality of our FASTQ sequencing files. And we have also seen an example of using the program samtools stats to obtain some quality statistics of our read mapping step.\nWhen processing multiple samples at once, it can become hard to check all of these quality metrics individually for each sample. This is the problem that the software MultiQC tries to solve. This software automatically scans a directory and looks for files it recognises as containing quality statistics. It then compiles all those statistics in a single report, so that we can more easily look across dozens or even hundreds of samples at once.\nHere is the command to run MultiQC and compile several quality statistics into a single report:\nmkdir results/multiqc\nmultiqc --outdir results/multiqc/ results/\nMultiQC generates a report, in this example in results/multiqc/multiqc_report.html. From this report we can get an overview of the quality across all our samples.\n\n\n\nSnapshot of some of the report sections from MultiQC. In this example we can see the “General Statistics” table and the “Sequence Quality Histograms” plot. One of the samples has lower quality towards the end of the read compared to other samples (red line in the bottom panel).\n\n\nFor example, from the section “General Statistics” we can see that the number of reads varies a lot between samples. Sample ERR5926784 has around 0.1 million reads, which is substantially lower than other samples that have over 1 million reads. This may affect the quality of the consensus assembly that we will do afterwards.\nFrom the section “Sequence Quality Histograms”, we can see that one sample in particular - ERR5926784 - has lower quality in the second pair of the read. We can open the original FastQC report and confirm that several sequences even drop below a quality score of 20 (1% change of error). A drop in sequencing quality towards the end of a read can often happen, especially for longer reads. Usually, analysis workflows include a step to remove reads with low quality so these should not affect downstream analysis too badly. However, it’s always good to make a note of potentially problematic samples, and see if they produce lower quality results downstream." + }, + { + "objectID": "materials/01-intro/02-ngs.html#bioinformatic-file-formats", + "href": "materials/01-intro/02-ngs.html#bioinformatic-file-formats", + "title": "2  Introduction to NGS", + "section": "2.3 Bioinformatic File Formats", + "text": "2.3 Bioinformatic File Formats\nLike we said above, bioinformatics uses many standard file formats to store different types of data. We have just seen two of these file formats: FASTQ for sequencing reads and BAM files to store reads mapped to a genome.\nAnother very common file format is the FASTA file, which is the format that our reference genome is stored as. The consensus sequences that we will generate are also stored as FASTA files. We detail this format below, but there are many other formats. Check out our appendix page on File Formats to learn more about them.\n\n2.3.1 FASTA Files\nAnother very common file that we should consider is the FASTA format. FASTA files are used to store nucleotide or amino acid sequences.\nThe general structure of a FASTA file is illustrated below:\n>sample01 <-- NAME OF THE SEQUENCE\nAGCGTGTACTGTGCATGTCGATG <-- SEQUENCE ITSELF\nEach sequence is represented by a name, which always starts with the character >, followed by the actual sequence.\nA FASTA file can contain several sequences, for example:\n>sample01\nAGCGTGTACTGTGCATGTCGATG\n>sample02\nAGCGTGTACTGTGCATGTCGATG\nEach sequence can sometimes span multiple lines, and separate sequences can always be identified by the > character. For example, this contains the same sequences as above:\n>sample01 <-- FIRST SEQUENCE STARTS HERE\nAGCGTGTACTGT\nGCATGTCGATG\n>sample02 <-- SECOND SEQUENCE STARTS HERE\nAGCGTGTACTGT\nGCATGTCGATG\nTo count how many sequences there are in a FASTA file, we can use the following command:\ngrep \">\" sequences.fa | wc -l\nIn two steps:\n\nfind the lines containing the character “>”, and then\ncount the number of lines of the result.\n\nWe will see FASTA files several times throughout this course, so it’s important to be familiar with them." + }, + { + "objectID": "materials/01-intro/02-ngs.html#exercises", + "href": "materials/01-intro/02-ngs.html#exercises", + "title": "2  Introduction to NGS", + "section": "2.4 Exercises", + "text": "2.4 Exercises\n\n\n\n\n\n\nSequence quality control: FASTQC\n\n\n\n\n\n\n\nIn the course materials directory 02-ngs/ we have several FASTQ files that we will use to assemble SARS-CoV-2 genomes. But first, we will run FastQC to check the quality of these files.\nThis is the basic command we could use in our samples:\nfastqc --outdir results/fastqc data/reads/*.fastq.gz\n\nCreate the output directory for the analysis (results/fastqc).\n\n\nHint\n\nThe command to create directories is mkdir. By default, the mkdir directory only creates one directory at a time. In this case we need to create first the results directory and then the results/fastqc within it. Alternatively, both directories can be created at once using the -p option.\n\nModify the fastqc command shown above to add an option to run the analysis using 8 threads in parallel (or CPUs). Check the tool’s help (fastqc --help) to see what the option to do this is called.\nRun the command. You will know it is running successfully because it prints progress of its analysis on the screen.\n\n\n\n\n\n\n\nAnswer\n\n\n\n\n\n\n\nFirst, we can create a directory to output our results:\nmkdir -p results/fastqc\nThe -p option ensures that both directories are created in one step. Otherwise, since the parent directory results did not exist, mkdir would throw an error.\nTo check the options available with FastQC we can run fastqc --help to get the complete documentation. As we scroll through the options, we can see the relevant one for running the analysis in parallel:\n-t --threads Specifies the number of files which can be processed\n simultaneously. Each thread will be allocated 250MB of\n memory so you shouldn't run more threads than your\n available memory will cope with, and not more than\n 6 threads on a 32 bit machine\nAlthough the documentation is a little technical, this means that if we have multiple CPUs available on our computer, we can set this option to allow multiple files to be processed in parallel. Our training machines have 8 CPUs, so we can run the command as follows:\nfastqc -t 8 --outdir results/fastqc data/reads/*.fastq.gz\nThe analysis report generated by FastQC is given as a .html file (opens in a web browser). We will go through the details of this below." + }, + { + "objectID": "materials/01-intro/02-ngs.html#summary", + "href": "materials/01-intro/02-ngs.html#summary", + "title": "2  Introduction to NGS", + "section": "2.5 Summary", + "text": "2.5 Summary\n\n\n\n\n\n\nKey Points\n\n\n\n\nIllumina sequencing produces short reads (50bp - 200bp), typically from both ends of a DNA fragment. It is a comparatively cheap sequencing platform which produces very high-quality sequences.\nNanopore sequencing produces very long reads (typically hundreds of kilobases long). It is comparatively more expensive and has higher error rates. However, it is more flexible with some of its platforms being fully portable.\nSequencing reads are stored in a file format called FASTQ. This file contains both the nucleotide sequence and quality of each base.\nThe quality of Illumina sequence reads can be assessed using the software FastQC.\nOne common task in bioinformatics is to align or map reads to a reference genome. This involves:\n\nCreating a genome index - this only needs to be done once.\nMapping the reads to the reference genome (e.g. using bowtie2) - the output is in SAM format.\nSorting the reads in the mapped file (using samtools sort) - the output is in BAM format.\nIndexing the BAM alignment file (using samtools index).\n\nThe software MultiQC can be used to generate a single reports that compiles statistics across several samples.\nBioinformatics uses many standard file formats. One of the most common ones is the FASTA format, which is used to store nucleotide or amino acid sequences (no quality information is contained in these files). This is a standard format that assembled genomes are stored as." + }, + { + "objectID": "materials/01-intro/03-workflows.html", + "href": "materials/01-intro/03-workflows.html", + "title": "3  Bioinformatic workflows", + "section": "", + "text": "Warning\n\n\n\nUnder development" + }, + { + "objectID": "materials/02-isolates/01-consensus.html#sec-consensus", + "href": "materials/02-isolates/01-consensus.html#sec-consensus", + "title": "4  Consensus assembly", + "section": "4.1 SARS-CoV-2 Consensus Assembly", + "text": "4.1 SARS-CoV-2 Consensus Assembly\nAs we discussed earlier in the course, the starting material for sequencing SARS-CoV-2 samples from infected patients is PCR-amplified DNA generated with a panel of primers that covers the whole SARS-CoV-2 genome (for example the primers developed by the ARTIC network). This material can then be sequenced using either Illumina or Nanopore platforms.\nAlthough different sotware tools are used depending on which kind of sequencing platform was used, the main goal is the same: to align the sequencing reads to the reference genome, and identify any DNA changes (SNPs or Indels) relative to the reference genome (Wuhan-Hu-1). This is called consensus assembly, since we are assembling the genome of our sample from the PCR-amplified fragments and generating a consensus sequence based on changes present in several reads covering a particular position of the genome.\nThe general data processing steps are:\n\nFilter high-quality sequencing reads.\nMap the reads to the Wuhan-Hu-1 reference genome.\nTrim the primers from the aligned reads based on the primer location file (BED file).\nPerform variant calling (SNPs and indels) to identify changes relative to the reference sequence.\nGenerate a consensus sequence for the sample based on those variants.\n\n\n\n\nOverview of the consensus assembly procedure from amplicon sequencing reads. In this schematic, each read spans the whole length of a PCR amplicon, which is what is expected from Nanopore reads. With Illumina data, there would be two pairs of reads starting at each end of the PCR amplicon.\n\n\n\n\n\n\n\n\nPrimer trimming\n\n\n\nPrimer trimming is a key step of the data processing, otherwise SNPs might be missed at the primer sites, on the final consensus sequence. This is because the primer sequence is retained during PCR instead of the original sequence of the sample. Because the PCR amplicons overlap with each other, we can trim the primers from each read and do variant calling after trimming. An example of this is shown in the Figure above." + }, + { + "objectID": "materials/02-isolates/01-consensus.html#bioinformatic-workflowspipelines", + "href": "materials/02-isolates/01-consensus.html#bioinformatic-workflowspipelines", + "title": "4  Consensus assembly", + "section": "4.2 Bioinformatic Workflows/Pipelines", + "text": "4.2 Bioinformatic Workflows/Pipelines\nAs can already be seen from the brief description above, bioinformatic analyses always involve multiple steps where data is gathered, cleaned and integrated to give a final set of processed files of interest to the user. These sequences of steps are called a workflow or pipeline. As analyses become more complex, pipelines may include the use of many different software tools, each requiring a specific set of inputs and options to be defined. Furthermore, as we want to chain multiple tools together, the inputs of one tool may be the output of another, which can become challenging to manage.\nAlthough it is possible to code such workflows using shell scripts, these often don’t scale well across different users and compute setups. To overcome these limitations, dedicated workflow/pipeline management software packages have been developed to help standardise pipelines and make it easier for the user to process their data.\n\nTwo of the most popular workflow software packages are Snakemake and Nextflow. We will not cover how to develop workflows with these packages, but rather how to use an existing workflow to generate consensus sequences from SARS-CoV-2 data.\n\nWhy Use a Standardised Workflow?\nThese are some of the key advantages of using a standardised workflow for our analysis:\n\nFewer errors - because the workflow automates the process of managing input/output files, there are less chances for errors or bugs in the code to occur.\nConsistency and reproducibility - analysis ran by different people should result in the same output, regardless of their computational setup.\nSoftware installation - all software dependencies are automatically installed for the user using solutions such as Conda, Docker and Singularity (more about these in a later section of the course).\nScalability - workflows can run on a local desktop or scale up to run on high performance compute clusters.\nCheckpoint and resume - if a workflow fails in one of the tasks, it can be resumed at a later time." + }, + { + "objectID": "materials/02-isolates/01-consensus.html#sec-viralrecon", + "href": "materials/02-isolates/01-consensus.html#sec-viralrecon", + "title": "4  Consensus assembly", + "section": "4.3 SARS-CoV-2 Pipeline", + "text": "4.3 SARS-CoV-2 Pipeline\nTo generate consensus SARS-CoV-2 genomes from these data, we will use a pipeline that was developed by the Nextflow core team called nf-core/viralrecon (which was itself inspired by a previous pipeline from the Connor Lab). Its objective is to harmonise the assembly of SARS-CoV-2 genomes from both Illumina and Nanopore amplicon sequencing data. It can also work with metagenomic data, which we will not cover in this workshop. This pipeline therefore includes different sub-pipelines, which are launched depending on the type of sequence data we have. Watch the video below to learn more about the development of this pipeline.\n\n\n\n\nGenerally speaking, Nextflow pipelines are run with the command nextflow run PIPELINE_NAME, where “PIPELINE_NAME” is the name of the pipeline. Pipelines are usually shared in a public repository such as GitHub, and nextflow will automatically download the pipeline if it hasn’t been downloaded already to your computer.\nLet’s test our pipeline by looking at its help documentation:\nnextflow run nf-core/viralrecon -r 2.6.0 --help\nThe command should print a long list of options available with this pipeline. For pipelines developed by the Nextflow core team you can also consult the documentation available online, which is easier to read: nf-co.re/viralrecon. This page includes many details about the pipeline: which tools are used in different steps of the data processing, how to use the pipeline for different types of data, a detailed documentation of all the options of the pipeline and explanation of the output files generated by it.\nBelow, we give an overview of the pipelines used for Illumina and Nanopore amplicon data.\n\n\n\n\n\n\nReference Genome and Primer Locations\n\n\n\nThe Wuhan-Hu-1 reference genome sequence and the amplicon primer locations (in BED file format) can all be found on the ARTIC Primer Schemes repository. The pipeline we are using takes care of downloading these files for us automatically, however it can be useful to know where to find them, in case you want to use other tools that require these files.\n\n\n\nIlluminaNanopore (FASTQ)Nanopore (FAST5)\n\n\nThe Illumina sub-workflow is based on several standard bioinformatic tools and, importantly, on the iVar software, which was developed for analysing amplicon-based sequencing data.\n\n\n\nSchematic of the key steps in the Illumina sub-workflow.\n\n\nTo run the pipeline on Illumina data, we use the following general command:\nnextflow run nf-core/viralrecon \n -r 2.6.0 -profile singularity \\\n --platform illumina \\\n --input SAMPLESHEET_CSV \\\n --outdir OUTPUT_DIRECTORY \\\n --protocol amplicon \\\n --genome 'MN908947.3' \\\n --primer_set artic \\\n --primer_set_version PRIMER_VERSION \\\n --skip_assembly --skip_asciigenome \\\n --skip_pangolin --skip_nextclade \nOne of the key options is --platform illumina, which makes sure that the correct sub-workflow will be used.\n\n\nClick to see more details about this sub-workflow\n\nIn summary, the steps performed by the Illumina sub-workflow are:\n\nAdapter trimming - this consists of trimming (or “cutting”) the sequences to remove low-quality bases and any Illumina adapter sequences that are present in the sequences.\nRemoving human (host) reads - when doing the sequencing it is possible that many reads are still from human material and this step removes them from the rest of the analysis.\nRead mapping - aligning (or mapping) the reads to the Wuhan-Hu-1 reference genome.\n\nThe software used for mapping is bowtie2.\nThe software samtools is used to convert the mapped file to BAM (instead of SAM) and sort the reads by coordinate (which is necessary for downstream steps).\n\nTrim Primers - primers are removed from the aligned reads using ivar trim (using the primer BED file).\nCall variants - identify SNPs and indels using ivar variants.\nAnnotate variants - the called variants are annotated according to their potential effect on the genes/proteins they are located in. For example, if a mutation introduces a new stop codon, or causes a frameshift.\nMake consensus - apply the SNPs and indels from the previous step to the reference FASTA file.\n\nThere are two tools that can be used in this step: bcftools consensus (default) or ivar consensus (can be set with the option --consensus_caller ivar).\n\nLineage assignment - the consensus sequences are assigned to lineages or clades using the pangolin and nextclade programs. These are two of the main lineage/clade nomenclature systems in use. They also identify variants of concern from the consensus sequences.\nQuality control - at several steps in the pipeline different tools are used to collect quality metrics and these are compiled into a report using multiqc.\n\n\n\n\nThe nanopore sub-workflow is based on the ARTIC bioinformatics protocol and uses several of the tools from the accompanying artic software package.\nThis sub-workflow is similar to the other nanopore sub-workflow, the main difference is the software used for generating a consensus sequence (medaka instead of nanopolish).\n\n\n\nSchematic of the key steps in the Medaka sub-workflow.\n\n\nTo run our pipeline on basecalled data (FASTQ files), we use the following command:\nnextflow run nf-core/viralrecon \\\n -r 2.6.0 -profile singularity \\\n --platform nanopore \\\n --input SAMPLESHEET_CSV \\\n --fastq_dir fastq_pass/ \\\n --outdir OUTPUT_DIRECTORY \\\n --protocol amplicon \\\n --genome 'MN908947.3' \\\n --primer_set artic \\\n --primer_set_version PRIMER_VERSION \\\n --artic_minion_caller medaka \\\n --artic_minion_medaka_model MEDAKA_MODEL \\\n --skip_assembly --skip_asciigenome \\\n --skip_pangolin --skip_nextclade \nSome of the key options are:\n\n--platform nanopore makes sure that the correct sub-workflow will be used.\n--artic_minion_caller medaka indicates we want to use the medaka program to do the variant/consensus calling (directly from the basecalled FASTQ files, rather than from the raw signal in the FAST5 files).\n--artic_minion_medaka_model specifies the model used by the guppy_basecaller software to do the basecalling. The model name follows the structure {pore}_{device}_{caller variant}_{caller version}. See more details about this in the medaka models documentation. Note: for recent versions of Guppy (>6) there is no exact matching model from medaka. The recommendation is to use the model for the latest version available; a list of supported models can be found on the medaka GitHub repository.\n--fastq_dir specifies the directory containing the FASTQ files. This directory should contain sub-directories for each barcoded sample following the naming convention barcodeXXXX (where X is a number between 0 and 9). By default, the guppy_basecaller software from Nanopore generates a folder called “fastq_pass” which follows this convention.\n\n\n\nClick to see more details about this sub-workflow\n\nIn summary, the steps performed by the Medaka sub-workflow are:\n\nAggregate reads from each sequencing barcode (when multiple files are availble for each barcode)\nRun the artic minion tool, which internally does several steps:\n\nMap reads to the reference genome using minimap2 (can be changed to use bwa mem with the option --artic_minion_aligner bwa).\nTrim primers from the aligned reads based on the known primer positions in the BED file (using a custom python script called align_trim.py).\nCall consensus sequences and SNP/indel variants using medaka consensus and medaka variant:\n\nPositions with less than 20x depth are treated as missing data and converted to the ambiguous base ‘N’. It is not advised to go below this threshold as the models used to call variants do not perform as well.\n\n\nAnnotate variants - the called variants are annotated according to their potential effect on the genes/proteins they are located in. For example, if a mutation introduces a new stop codon, or causes a frameshift.\nLineage assignment - the consensus sequences are assigned to lineages or clades using the pangolin and nextclade programs. These are two of the main lineage/clade nomenclature systems in use. They also identify variants of concern from the consensus sequences.\nQuality control - at several steps in the pipeline different tools are used to collect quality metrics and these are compiled into a report using multiqc.\n\n\n\n\nThe nanopore sub-workflow is based on the ARTIC bioinformatics protocol and uses several of the tools from the accompanying artic software package.\nThis sub-workflow is similar to the other nanopore sub-workflow, the main difference is the software used for generating a consensus sequence (nanopolish instead of medaka).\n\n\n\nSchematic of the key steps in the Nanopolish sub-workflow. (Under development)\n\n\n\n\nClick to see more details about this sub-workflow\n\nIn summary, the steps performed by the Nanopolish sub-workflow are:\n\nFilter reads to ensure they pass minimum read length thresholds:\n\nminimum length 400bp (can be changed with --min_length option)\nmaximum length 700bp (can be changed with --max_length option)\n\nRun the artic minion tool, which internally does:\n\nRead alignment to reference genome using minimap2 (can be changed to use bwa mem with the --bwa option).\nTrim primers from the aligned reads (based on the known primer positions in the BED file).\nCall consensus sequences and variants using nanopolish variants if using signal-level FAST5 files.\n\nPositions with less than 20x depth are assigned the ambiguous base ‘N’. It is not advised to go below this threshold as the models used to call variants do not perform as well.\n\n\nAnnotate variants - the called variants are annotated according to their potential effect on the genes/proteins they are located in. For example, if a mutation introduces a new stop codon, or causes a frameshift.\nLineage assignment - the consensus sequences are assigned to lineages or clades using the pangolin and nextclade programs. These are two of the main lineage/clade nomenclature systems in use. They also identify variants of concern from the consensus sequences.\nQuality control - at several steps in the pipeline different tools are used to collect quality metrics and these are compiled into a report using multiqc.\n\n\nTo run our pipeline on signal-level data (FAST5 files), we use the following command:\nnextflow run nf-core/viralrecon \\\n --input SAMPLESHEET_CSV \\\n --outdir OUTPUT_DIRECTORY \\\n --protocol amplicon \\\n --genome 'MN908947.3' \\\n --primer_set artic \\\n --primer_set_version PRIMER_VERSION \\\n --skip_assembly \\\n --platform nanopore \\\n --fastq_dir fastq_pass/ \\\n --fast5_dir fast5_pass/ \\\n --sequencing_summary sequencing_summary.txt \\\n -profile conda,singularity,docker\nSome of the key options are:\n\n--platform nanopore makes sure that the correct sub-workflow will be used.\n--fastq_dir specifies the directory containing the FASTQ files generated by the guppy_basecaller program (this is the standard software from Nanopore that processes the raw signal data from the sequencing device). This directory should contain sub-directories for each barcoded sample following the naming convention barcodeXXXX (where X is a number between 0 and 9). By default, guppy_basecaller generates a folder called “fastq_pass” which follows this convention.\n--fast5_dir specifies the directory containing the FAST5 files generated by guppy_basecaller. This directory follows the same naming convention as above and is usually in a folder called “fast5_pass”.\n--sequencing_summary is a path to the “sequencing_summary.txt” text file generated by guppy_basecaller.\n\n\n\n\nApart from the specific options used by each sub-workflow, there are some general options that are used:\n\n--input specifies a CSV file with details about our samples. The format of this file depends on the specific sub-workflow you are using. See the details in the samplesheet documentation page.\n--outdir specifies the output directory to store all our results.\n--protocol amplicon sets the pipeline for PCR amplicon data (the other option is --protocol metagenomic, which we do not cover in this course).\n--genome 'MN908947.3' this is the standard name of the Wuhan-Hu-1 reference genome.\n--primer_set artic at the moment only “artic” primers are available by default. It is possible to use custom primers with the Illumina workflow (see details here).\n--primer_set_version the version of the ARTIC primer scheme used. The viralrecon primer config file indicates the available primer shemes are: 1, 2, 3, 4, 4.1, 5.3.2 and also 1200 (the 1200bp amplicon protocol, also known as “midnight”).\n--skip_assembly this is used to skip de-novo assembly of the genome. This step is unnecessary in amplicon protocols, which instead rely on mapping reads to the reference genome (reference-based assembly). De-novo assembly is necessary for metagenomic protocols.\n--skip_pangolin and --skip_nextclade is used to skip the lineage assignment. The reason is that viralrecon does not use the latest version of the SARS lineage databases, so we skip this step for now, and run it separately in a later step of the analysis.\n\nThere are two more generic options we used:\n\n-r 2.6.0 indicates the version of the pipeline we want to use. It’s always good to check what the latest version is on the viralrecon website. -profile singularity indicates how we want to manage the software required by the pipeline. In our case, we are using a software called Singularity, which creates a “virtual operating system” (called a container) where all the necessary software is run from. This ensures that all of the software is automatically installed and runs on any Linux computer.\n\n\n\n\n\n\n\nConda, Singularity, Docker?\n\n\n\nGenerally speaking, workflow management software such as Nextflow or Snakemake support three solutions for managing software dependencies:\n\nDocker is a software that allows to package a small virtual operating system (or a “container”) containing all the software and data necessary for running an analysis.\nSingularity also creates software containers, similarly to Docker. However, it can more easily interface with the user’s filesystem, without the need to have special permissions.\nConda is a package manager, also very popular in bioinformatics. Instead of creating virtual OS containers, Conda instead creates software environments (think of them as directories) where all the software is locally installed, including all its dependencies. The use of individual environments ensures that software packages with incompatible dependencies can still be used within the same pipeline.\n\nOf the three, Singularity is the recommended choice, although Conda is also a good alternative.\n\n\n\n4.3.1 Running the Workflow\nLet’s see an example in action by using some example data. If you go to the directory uk_illumina/ in the course materials, you will find several FASTQ files in the data directory. There is also a shell script (scripts/run_illumina_workflow.sh) that contains the commands we will use to run the workflow on these data.\nOpening the script, we can see the following commands:\n# create output directory\nmkdir results\n\n# run the workflow\nnextflow run nf-core/viralrecon \\\n -r 2.6.0 -profile singularity \\\n --platform illumina \\\n --input samplesheet.csv \\\n --outdir results/viralrecon \\\n --protocol amplicon \\\n --genome 'MN908947.3' \\\n --primer_set artic \\\n --primer_set_version 3 \\\n --skip_assembly --skip_asciigenome \\\n --skip_pangolin --skip_nextclade\nIt first creates a results directory (to store our output files) and then runs the nextflow command using the Illumina sub-workflow. We could run these commands one at a time by copy/pasting them to the terminal. Or alternatively, we can run the entire script using bash scripts/run_illumina_workflow.sh\nWhen you start running the workflow, you will get a list of the workflow steps and their progress. This may take quite a while to run, depending on the computer resources you have available. Once the workflow is complete, you should see a message similar to the following:\n-[nf-core/viralrecon] 8/8 samples passed Bowtie2 1000 mapped read threshold:\n 280038: GB16\n 2946614: GB28\n 252871: GB09\n 3269412: GB21\n 103742: GB23\n 3016474: GB36\n ..see pipeline reports for full list\n-\n-[nf-core/viralrecon] Pipeline completed successfully-\nCompleted at: 18-May-2022 08:08:25\nDuration : 1h 13m\nCPU hours : 8.1\nSucceeded : 343\nYou should also get several output files in the results folder specified with our nextflow command. We will detail what these files are in the next section.\n\n\n\n\n\n\nRunning the pipeline on our training computers\n\n\n\nOur training computers don’t have the high specifications needed for routine bioinformatic analysis, so the Illumina pipeline takes up to 1h to complete.\nWe provide already pre-processed results for 48 samples in the folder uk_illumina/preprocessed, which you can use to follow the next section." + }, + { + "objectID": "materials/02-isolates/01-consensus.html#exercises", + "href": "materials/02-isolates/01-consensus.html#exercises", + "title": "4  Consensus assembly", + "section": "4.4 Exercises", + "text": "4.4 Exercises\n\n\n\n\n\n\nRunning nf-core/viralrecon: ONT data\n\n\n\n\n\n\n\nGo to the course materials directory india_nanopore. This contains Nanopore sequencing data for several samples collected in India. Nanopore data is organised in directories named as barcodeXX (where XX is a number) - this is the standard output from the Guppy software used to do basecalling and generate FASTQ files.\nThe Nanopore Medaka workflow expects to be given as an input a CSV file with two columns: sample name and barcode number. We already provide this file in samplesheet.csv.\nYour task now is to process these samples using the nf-core/viralrecon pipeline:\n\nUsing nano, open the script found in scripts/run_medaka_workflow.sh.\nFix the code in the script where you see the word “FIXME”:\n\nOutput the results to a directory called results/viralrecon/.\nThe input sample sheet is in the file samplesheet.csv (check the pipeline documentation to review what the format of this samplesheet should be for the Nanopore pipeline).\nThe FASTQ files are in a folder data/fastq_pass.\n\nRun the script using bash. This may take ~15 minutes to complete.\n\n\n\n\n\n\n\nAnswer\n\n\n\n\n\n\n\nWe can open the script with Nano using the command:\nnano scripts/run_medaka_workflow.sh\nThe fixed code is:\n# create output directory\nmkdir -p results\n\n# run the workflow\nnextflow run nf-core/viralrecon \\\n -r 2.6.0 -profile singularity \\\n --max_cpus 8 --max_memory \"24.GB\" \\\n --input samplesheet.csv \\\n --outdir results/viralrecon \\\n --platform nanopore \\\n --protocol amplicon \\\n --genome 'MN908947.3' \\\n --primer_set artic \\\n --primer_set_version 3 \\\n --skip_assembly \\\n --fastq_dir data/fastq_pass/ \\\n --artic_minion_caller medaka \\\n --artic_minion_medaka_model r941_min_high_g360\nWhat we did to fix the code was:\n\nSet --input as samplesheet.csv, which is the CSV file with two columns: sample name and Nanopore barcode number. The format of this file is detailed in the nf-core/viralrecon documentation.\nSet --outdir as results/viralrecon/, which is the directory where we want to output our results.\nSet --fastq_dir as data/fastq_pass, which is the directory containing the FASTQ file folders named as barcodeXX (where XX is a number). This is the standard output from the Guppy software used to do basecalling of Nanopore data to produce FASTQ files.\n\nWhen we finish editing the file we can hit Ctrl + X to exit Nano. Before it closes it will ask us if we want to save the file and we can type “Y” for yes.\nTo run the script we can do bash scripts/run_medaka_workflow.sh. After the workflow completes, we get a message similar to this one:\n-[nf-core/viralrecon] Pipeline completed successfully-\nCompleted at: 18-May-2022 08:08:25\nDuration : 13m 22s\nCPU hours : 1.6\nSucceeded : 167\nNote that the exact time that the workflow takes to run may differ from what we show here. The time depends on how many samples you are processing and how big the computer you are using is." + }, + { + "objectID": "materials/02-isolates/01-consensus.html#summary", + "href": "materials/02-isolates/01-consensus.html#summary", + "title": "4  Consensus assembly", + "section": "4.5 Summary", + "text": "4.5 Summary\n\n\n\n\n\n\nKey Points\n\n\n\n\nThe main steps to generate SARS-CoV-2 consensus sequences are: filter high-quality reads, map reads to reference genome, trim PCR primers and variant/mutation calling, which is finally used to produce a consensus sequence.\nOther steps that can be done include annotating the variants/mutations (what their effects in each gene might be) and assigning sequencences to known lineages/clades.\nNextflow is a software used for building workflows/pipelines involving multiple tools and data processing steps. Using established pipelines helps with automation, reproducibility, consistency of results and reduces the chances of data processing errors.\nThe nf-core/viralrecon pipeline implements the steps to generate SARS-CoV-2 consensus sequences from Illumina or Nanopore data.\nThe command nextflow run nf-core/viralrecon is used to run the pipeline, using specific options depending on the data we have." + }, + { + "objectID": "materials/02-isolates/02-qc.html#output-files", + "href": "materials/02-isolates/02-qc.html#output-files", + "title": "5  Quality Control", + "section": "5.1 Output Files", + "text": "5.1 Output Files\nAfter running our pipeline, we get several output directories and files. The directories we get depend on which version of the workflow was used (Illumina or Nanopore). The description of the output is detailed in the pipeline documentation. Although there are many output files, most of these contain results that are aggregated in an interactive MultiQC report, which makes their analysis easier. We highlight some of the main files of interest below.\n\nIlluminaNanopore (FASTQ)\n\n\n\nThe file multiqc/multiqc_report.html contains a MultiQC quality and analysis report for the consensus assemblies generated by the pipeline.\nThe folder variants/bowtie2/ contains individual BAM files, which can be visualised with IGV if we want to look at the reads mapped to the reference genome.\nThe folder variants/ivar/consensus/bcftools contains individual FASTA files (named *.consensus.fa) for each sample’s consensus genome sequence.\nThe file variants/ivar/variants_long_table.csv contains a table with the aggregated results of all the variants detected in each sample.\n\n\n\n\nThe file multiqc/medaka/multiqc_report.html contains a MultiQC quality and analysis report for the consensus assemblies generated by the pipeline.\nThe folder medaka/ contains:\n\nIndividual BAM files (named *.primertrimmed.rg.sorted.bam), which can be visualised with IGV if we want to look at the reads mapped to the reference genome.\nIndividual FASTA files (named *.consensus.fasta) for each sample’s consensus genome sequence.\nA file called variants_long_table.csv with a table of all the variants detected in each sample." + }, + { + "objectID": "materials/02-isolates/02-qc.html#sec-consensus-qc", + "href": "materials/02-isolates/02-qc.html#sec-consensus-qc", + "title": "5  Quality Control", + "section": "5.2 Quality Control", + "text": "5.2 Quality Control\nThe viralrecon pipeline produces many quality control metrics, which are conveniently compiled in an interactive report with MultiQC, as mentioned above. We will not detail here every section of the report (check the pipeline documentation for a full description), but only highlight some of the sections that can be used for a first assessment of the quality of our samples.\n\n5.2.1 Variant Calling Metrics\nThe first section or the report - Variant Calling Metrics - contains a summary table with several statistics for each sample, including the total number of reads, the number/percentage of reads mapped to the reference genome, the depth of coverage, the number of SNPs (single-nucleotide polymorphisms) and INDELs (insertions/deletions), the number of missense variants (mutations that result in an aminoacid change) and the fraction of ambiguous bases ‘N’ per 100kb.\nLooking at these basic metrics gives us a good first idea of whether some of the samples have a high fraction of missing bases (marked as N in the FASTA file), leading to a poorer assembly. We can quickly check this by sorting the table by the column named “# Ns per 100kb consensus” (you can convert the values in this column to a percentage by dividing the numbers by 100). There is also the ability to produce simple scatterplots from the data in this table, which can be useful to look at the relationship between the different metrics (see an example in the figure below).\nThis table also contains information about the lineage/clade assigned to each sample by the programs Pangolin and Nextclade. This gives us an idea of which samples may be more similar to each other, and where they fit in the global context of other sequences available publicly. We will talk more about this topic in the lineage assignment section.\n\n\n\n\n\n\nLineage Versions\n\n\n\nAlthough the Viralrecon pipeline runs Pangolin and Nextclade to perform lineage assignment, it does not use the latest version of these programs (because lineages evolve so fast, the nomenclature constantly changes). Therefore, this information should mostly be ignored at this stage, and instead we should run our analysis on the most up-to-date versions of these programs. We will detail this in the Lineages and Variants section of the materials.\n\n\n\n\n\nSnapshot of the “Variant Metrics” section of the viralrecon MultiQC report. Simple scatterplots can be made from the data on this table using the Plot button. For example, at the bottom we show a scatterplot showing the relationship between the median depth of coverage and the number of ambiguous bases ‘N’ per 100kb. For the data in this example, we can see that when the average depth of coverage drops below around 200 reads we start getting higher number of missing bases in the assembly.\n\n\n\n\n\n\n\n\nTerminology Alert!\n\n\n\nThe word coverage is sometimes used in an ambiguous way by bioinformaticians. It can mean two things:\n\nThe number of reads aligning to a position of the genome. This is sometimes called sequencing depth or depth of coverage (we will use these terms in the materials to avoid confusion). For example, we may say that the average depth of coverage of the genome is 20x, meaning that on average there are 20 reads aligned to a position of the genome.\nThe fraction of a genome that has a sufficient number of reads for analysis. For example, if 90% of the genome has a sufficient number of reads to be analysed (for example, at a threshold of 10x), we would say it has 90% coverage (we “covered” 90% of the genome with enough data). In the context of genome assembly, we sometimes use the word “coverage” to refer to the percentage of the consensus genome without ambiguous ‘N’ bases.\n\nIn the viralrecon MultiQC report the word “coverage” is used to mean “depth of coverage”.\n\n\n\n\n5.2.2 Amplicon Depth of Coverage\nThe next section of the report - Amplicon Coverage Heatmap - contains a graphical representation of the average depth of coverage for each PCR amplicon in the ARTIC protocol (i.e. the average number of reads mapped to each amplicon interval). This plot is extremely useful to identify common PCR dropouts occurring across many samples. This may be an indication that our PCR primer scheme is not working for some of the forms of the virus circulating in our population (for example due to mutations that accumulate in the primer site).\n\n\n\nExample heatmap showing the depth of coverage for a set of samples. The failure of an amplicon to be sequenced in several samples can be seen as a “column” of dark cells in the heatmap.\n\n\nWe can investigate primer dropout in more detail, for example by looking at the BAM files with mapped reads using IGV.\nFrom our heatmap (shown in the Figure above) we can see one of the PCR fragments - ncov-2019_83 - seems to have poor amplification across many of our samples. Let’s investigate this in more detail by looking at the alignment file:\n\nOpen IGV and go to File → Load from file….\nIn the file browser that opens go to the folder results/viralrecon/variants/bowtie2/ and select the file GB36.ivar_trim.sorted.bam to open it.\nGo back to File → Load from file… and this time load the BED files containing the primer locations. These can be found in resources/primers/artic_primers_pool1.bed and resources/primers/artic_primers_pool2.bed.\n\nOnce you have IGV open, you can navigate to the region where this amplicon is located by searching for the name of one of the primers - “ncov-2019_83_LEFT” - in the search box at the top. By zooming in to the region where this primer is located, we can see there is a mutation right at the start of this primer, which suggests that this may be the reason why this PCR fragment fails to amplify in this sample.\n\n\n\nScreenshot from the IGV program showing an example of a region with PCR amplicon dropout. This is shown by a lack of reads mapped to this region of the genome. Because the PCR fragments from the ARTIC protocol overlap between the two pools, we can see if there are mutations occurring at the primer sites. In this example, a mutation in the left primer of amplicon 83 suggests this may be the reason for the dropout in this sample." + }, + { + "objectID": "materials/02-isolates/02-qc.html#mutationvariant-analysis", + "href": "materials/02-isolates/02-qc.html#mutationvariant-analysis", + "title": "5  Quality Control", + "section": "5.3 Mutation/Variant Analysis", + "text": "5.3 Mutation/Variant Analysis\nOne of the output files produced by the pipeline is a table SNP and indel variants detected in our samples saved as a CSV file in variants/ivar/variants_long_table.csv. This table can be useful to investigate if particular mutations are particularly frequent in our samples and what their likely effects are.\n\n\n\nVariants table output by viralrecon, with a summary of the main columns of interest. Note that this table also includes a column with lineage assignment (not shown in this snapshot). Remember that at this stage we mostly ignore this column, as viralrecon does not use the most up-to-date version of the lineage databases.\n\n\nThe columns in this table are:\n\nSAMPLE is the name of our sequenced sample.\nCHROM is the name of the “chromosome”, which in our case is just the name of the reference genome (“MN908947.3”).\nPOS is the position of the genome where the mutation occurs.\nREF is the reference nucleotide.\nALT is the alternative nucleotide, that is the nucleotide that was observed in our sample.\nFILTER indicates whether the SNP passed quality filters from the variant calling software (“PASS”) or whether some other issue was observed (for example “dp” means that the depth of sequencing was unusually high or low).\nDP is the total depth of sequencing at this position, meaning how many reads in total were aligned there.\nREF_DP is the depth of sequencing of the reference allele, meaning how many reads contained the reference nucleotide.\nALT_DP is the depth of sequencing of the alternative allele, meaning how many reads contained the alternative nucleotide.\nAF is the allele frequence of the alternative allele, meaning the proportion of reads that contained the alternative nucleotide (this column is equivalent to ALT_DP/(ALT_DP + REF_DP)).\nGENE is the name of the gene in the SARS-CoV-2 annotation.\nEFFECT this is the predicted effect of the mutation in the gene. This output comes from the snpeff software and uses The Sequence Ontology nomenclature (follow the link to search for each term).\nHGVS_C, HGVS_P and HGVS_P_1LETTER is the DNA or amino acid change using HGVS nomenclature. For example, “c.1112C>T” means a C changed to a T at position 1112 of the genome; and “p.Pro371Leu” would mean that a Proline changed to a Leucine at position 371 of the respective protein.\nCALLER is the software used for variant calling.\nLINEAGE is the Pangolin lineage that the sample was assigned to. Note that this column should usually be ignored, since viralrecon doesn’t use the latest Pangolin data version by default.\n\nThis table can be opened in a spreadsheet program such as Excel to look at particular features. In terms of quality-control, we can filter the table:\n\nfor mutations with intermediate allele frequency (say < 70%) – if a sample has too many such mutations, that could indicate cross-contamination between samples.\nfor frameshift mutations – these mutations should be rare because they are highly disruptive to the functioning of the virus, and their occurrence is more often due to errors. The presence of these mutations are usually not critical for downstream analysis (such as lineage assignment and phylogenetics), but we should make a note that these mutations may be false-positives in our data.\n\nThis variants/mutations table can also be used to explore reasons for amplicon dropout. For example, we can identify how many samples contain the mutation we previously saw in the “ncov-2019_83_LEFT” primer (we saw this was in position 25,003 of the genome). We can do this by sorting our table by the column “POS” (position) and then scrolling down to find the samples with this mutation. We will find that only 3 of the samples contain this mutation, suggesting some other additional causes are leading to the dropout in this PCR fragment.\nFinally, for the Illumina pipeline (--platform illumina), it is important to note that not all of the mutations on this table are included in the final consensus. Only mutations with allele frequency >75% (AF >= 0.75) and minimum depth of 10 (DP >= 10) are retained in the final consensus. Therefore, to see which mutations are actually present in our consensus sequences, we need to filter this table for these criteria. Again, we note that this only applies to the Illumina pipeline. For the Nanopore pipeline (--platform nanopore) all the mutations included in this table are retained in the consensus sequence.\n\n\n\n\n\n\nORF1b Annotation Issues\n\n\n\nThe annotation of the ORF1b gene had some changes between the first original assembly and newer versions of this annotation. In particular, there is a frameshift that is not considered in the annotation used by most software, and this causes a discrepancy in the results between viralrecon and other tools such as Nextclade (which we will cover in the next section).\nThis issue is under investigation by the developers of viralrecon and may be corrected in future versions of the pipeline.\nFor now, the advice is to ignore the variant effects of ORF1b as these correspond to an older version of the annotated gene.\n\n\n\n\n\n\n\n\nKeep Original Files Intact\n\n\n\nWhen you analyse the results of a CSV file in a spreadsheet software (such as Excel), it may be a good idea to create a copy of your file to avoid accidentally modifying the original table. For example, if you accidentally sort one column of the table but forget to sort other columns, of if you accidentally filter the results and delete some of the rows from the table, etc.\nYou can save a copy of the file by going to File → Save As…. You may want to save the copy as an Excel file format, to include graphs and colours. (Remember that the CSV format is a plain text file - it does not support graphs or coloured cells.)" + }, + { + "objectID": "materials/02-isolates/02-qc.html#cleaning-fasta-files-optional", + "href": "materials/02-isolates/02-qc.html#cleaning-fasta-files-optional", + "title": "5  Quality Control", + "section": "5.4 Cleaning FASTA Files (Optional)", + "text": "5.4 Cleaning FASTA Files (Optional)\nTo proceed with our analysis, we need a FASTA file containing all of our consensus sequences. However, our viralrecon pipeline outputs separate FASTA files for each sample. We can see this by running (from within the uk_illumina/ directory):\nls results/viralrecon/variants/ivar/consensus/bcftools/\nAlso, the workflow modifies our original sample names in the FASTA file, by adding extra information to the sequence name. For example:\nhead -n 1 results/viralrecon/variants/ivar/consensus/bcftools/ERR5728910.consensus.fa\n>ERR5728910 MN908947.3\nWhat we want to do is clean these sample names, so that we end up with:\n>ERR5728910\nWe also want to make sure to combine all the samples into a single FASTA file.\nWe can the command cat to combine (concatenate) the individual files and the sed command to substitute text and clean our sample names. Let’s do this step by step.\nFirst, we can use the * wildcard to combine all the FASTA files with the cat command:\ncat results/viralrecon/variants/ivar/consensus/bcftools/*.fa\nRunning this command will print all of the sequences on the screen! To see what happened a little better, we could pipe this command to less to browse up-and-down through the file:\ncat results/viralrecon/variants/ivar/consensus/bcftools/*.fa | less\nWe could also check that we now have all our samples combined, we could pass the results to grep and search for the word >, which in the FASTA format indicates the sequence name:\ncat results/viralrecon/variants/ivar/consensus/bcftools/*.fa | grep \">\" | wc -l\nThis should give us 7 as the result (which makes sense, since we have 7 samples).\nWe can now proceed with cleaning the names of the sequences, by using sed:\ncat results/viralrecon/variants/ivar/consensus/bcftools/*.fa | sed 's| MN908947.3||' > results/clean_sequences.fa\nNotice that in this last command we make sure to redirect the result to a new file using >." + }, + { + "objectID": "materials/02-isolates/02-qc.html#missing-data-intervals", + "href": "materials/02-isolates/02-qc.html#missing-data-intervals", + "title": "5  Quality Control", + "section": "5.5 Missing data intervals", + "text": "5.5 Missing data intervals\nWhen we align reads to the reference genome, there may be regions that we were not able to sequence (e.g. due to amplicon dropout, discussed above) and therefore cannot tell what the base sequence in those positions is. In those cases, the viralrecon pipeline includes the missing character ‘N’ in the sequence.\nAs a further quality check, it is useful to check how many contiguous intervals of missing bases we have in each of our assemblies, as well as how long those intervals are. For example, for this small sequence:\nT A N N N G C T N N A T\nWe have two missing intervals, from positions 3-5 and from positions 9-10.\nTo obtain a list of missing intervals in a sequence, we can use the software seqkit, which is a toolkit of commands to do many operations on FASTA/FASTQ files (check out its documentation). In particular, we can use the following command:\nseqkit locate -i -P -G -M -r -p \"N+\" results/clean_sequences.fa\nseqID patternName pattern strand start end\nGB09 N+ N+ + 1 342\nGB09 N+ N+ + 9246 9502\nGB09 N+ N+ + 10738 11331\nGB09 N+ N+ + 21428 21543\nGB09 N+ N+ + 27400 27462\nGB09 N+ N+ + 27618 27644\nGB09 N+ N+ + 29827 29893\n\n... more output omitted...\nThe output is a tabular file specifying the consensus sequence name (first column) and the location of intervals in the genome where the pattern of one or more “N” were found.\nTo see what all the options we used with this command are, see the tool’s documentation." + }, + { + "objectID": "materials/02-isolates/02-qc.html#exercises", + "href": "materials/02-isolates/02-qc.html#exercises", + "title": "5  Quality Control", + "section": "5.6 Exercises", + "text": "5.6 Exercises\n\n\n\n\n\n\nQuality control for India (ONT) samples\n\n\n\n\n\n\n\nNow it’s your turn to check the quality of the assembled genomes from our India samples, sequenced using Nanopore. For this exercise, we will use the results produced by viralrecon on 48 pre-processed samples:\n\nUse the file explorer to open the folder preprocessed, and then:\n\nOpen the file in pipeline_info/execution_report_DATE.html (where “DATE” is the date when the files were processed).\nHow long did the workflow take to run?\nWhich step of the pipeline took the longest to run?\n\nOpen the MultiQC report found in preprocessed/multiqc/medaka/multiqc_report.html and answer the following questions:\n\nProduce a plot between median depth of coverage (x-axis) and #Ns (y-axis). What do you think is the average coverage needed for <10% of missing data.\nWere there any PCR amplicons with a dropout (i.e. very low depth of coverage) across multiple samples?\nDo any of these dropout amplicons coincide with gene “S” (Spike protein gene)? Note: you can see the ARTIC PCR primer location from the online repository and the gene locations in the SARS-CoV-2 NCBI genome browser.\n\nBased on any amplicons that you identified in the previous question, find if there are any mutations coinciding with their primer locations. You can use the mutation variant table in preprocessed/medaka/variants_long_table.csv.\nWith the mutation variants table open in a spreadsheet program (LibreOffice on our training machines):\n\nUse the filter button to look for mutations in gene “S” (encodes for the Spike protein).\nIdentify the samples and positions of mutations of the type “disruptive_inframe_deletion”.\nUsing IGV, open the BAM file for one of those samples. Note: BAM files are located in preprocessed/medaka/SAMPLE.primertrimmed.rg.sorted.bam (were ‘SAMPLE’ is the sample name).\nGo to the location of those mutations. From the read alignment, how confident are you about it? What could you do to confirm it?\n\n\n\n\n\n\n\n\nAnswer\n\n\n\n\n\n\n\nQuestion 1\nWe open the file found in preprocessed/pipeline_info/execution_report_2022-05-04_12-41-12.html, which contains information about the pipeline that was run on a set of 48 samples. We can see at the top of the report that it took ~15 minutes to run. This report also gives us information about how long each individual step of the pipeline took the longest to run (and other information such as which used more CPU or RAM memory). For example, in the section “Job Duration” we can see a graph that looks like this:\n\nThis indicates that the step running the artic minion tool takes the longest. This is not surprising as this is the step where most of the work is happening (mapping, primer trimming and making a consensus sequence). You can revise the steps of the workflow in the respective section above.\n\nQuestion 2\nWhen opening the MultiQC report, we can see the first section contains a table with several statistics about the quality of each sample. We can produce a plot from this table, by pressing the “Plot” button above the table. The plot we were asked for is the following:\n\n\n\nThis plot can be produced by clicking the Plot button on top of the table and then selecting the variables “Coverage median” and “# Ns per 100kb consensus”.\n\n\nThe y-axis of the plot tells us the number of ‘N’ per 100kb of sequence. If we divide those numbers by 100 we get a percentage and from these samples it seems like a median depth of coverage of around 200 reads generates samples with less than 10% of missing bases.\nFrom the section of the report “Amplicon coverage heatmap” we can see the average depth of coverage for each PCR amplicon from the ARTIC protocol. There are 3 amplicons in particular that have very low depth of coverage: nCoV-2019_51, nCoV-2019_62 and nCoV-2019_95.\nWe can check the ARTIC V3 primer BED file online to look at the location of these primers, or we could look for it using command-line tools. Here is an example:\ncat resources/primers/artic_version3_pool*.bed | grep \"_51_\"\nMN908947.3 15171 15193 nCoV-2019_51_LEFT 1 +\nMN908947.3 15538 15560 nCoV-2019_51_RIGHT 1 -\nFor this example, we can see the amplicon is between positions 15171 - 15560 of the genome. Looking at the SARS-CoV-2 NCBI genome browser, we can see this coincides with ORF1ab. If we do the same analysis for the other primers, we will see that none of them coincides with gene S.\n\nQuestion 3\nTo investigate whether this amplicon dropout is due to mutations in the primer regions we can open the mutations table preprocessed/medaka/variants_long_table.csv. If we sort the table by the column “POS”, we can scroll to the location of each primer pair.\nWe can see that in only one case there is a mutation coinciding one of the primers: in sample “IN33” position 28687 there is a C > T mutation, which coincides with primer nCoV-2019_95_LEFT. However, this only occurs for one of the samples, suggesting the reason for the PCR dropout might be related to other factors.\n\nQuestion 4\nAfter filtering our table for gene S (Spike gene), we can see only a couple of mutations of type “disruptive_inframe_deletion” in some of the samples at positions 21764 and 21990. To look at the reads with this mutation, we open the BAM file for one of these samples in IGV (for example sample IN22):\n\nGo to File → Load from file….\nIn the file browser that opens go to the folder preprocessed/medaka and select the file IN22.primertrimmed.rg.sorted.bam to open it.\nIn the search box we can type “NC_045512.2:21990” which will zoom-in on the region around the deletion.\n\nFrom looking at the reads aligned to this position of the genome, we can see several of them containing a 3bp deletion. However, we can also see some reads that do not contain the deletion, and others that seem to be mis-aligned. If we were interested in confirming this mutation, we could do an independent PCR followed by Sanger sequencing, for example.\nAlthough this was not part of the question, it is also worth noting that the primer “nCoV-2019_73_LEFT” starts very near this deletion. In the MultiQC report, we can see that this PCR amplicon had very poor amplification in this sample. One possibility is that the deletion interfered with the primer efficiency in this sample.\nGenerally, we don’t need to confirm every single mutation obtained from our analysis. But if we see a mutation occurring many times, then it may be worth further investigation, especially if it is disruptive and in an important gene such as the Spike gene.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nClean FASTA file: ONT data\n\n\n\n\n\n\n\nIn this exercise we will create a clean FASTA file for the samples collected in India.\nIn this case, the output FASTA files are in the folder results/viralrecon/medaka and have the file extension .fasta. If we look at one of the files:\nhead -n 1 results/viralrecon/medaka/IN42.consensus.fasta\n>IN42/ARTIC/medaka MN908947.3\nWe can see that the name has a lot of extra information attached to it. We want to clean the name of the sequences so that the result is:\n>IN42\nThe following sed command can be used to substitute the text “/ARTIC/medaka MN908947.3” with nothing:\nsed 's|/ARTIC/medaka MN908947.3||'\n\nPipe the tools cat and sed to construct a command that generates a new file called results/clean_sequences.fa containing all the sequences with “clean” sequence names.\n\n\n\n\n\n\n\nAnswer\n\n\n\n\n\n\n\nThe complete code to achieve the desired outcome is:\ncat results/viralrecon/medaka/*.consensus.fasta | sed 's|/ARTIC/medaka MN908947.3||' > results/clean_sequences.fa\nLook at our companion Unix course materials for more information about how the sed command works." + }, + { + "objectID": "materials/02-isolates/02-qc.html#summary", + "href": "materials/02-isolates/02-qc.html#summary", + "title": "5  Quality Control", + "section": "5.7 Summary", + "text": "5.7 Summary\n\n\n\n\n\n\nKey Points\n\n\n\n\nThe output of the pipeline includes, among others:\n\nA detailed MultiQC report, including information about genome coverage, the depth of sequencing for each amplicon, as well as other quality metrics that can help to troubleshoot problematic samples.\nA table with SNP and indel mutation variants identified in each sample.\nInformation about SARS-CoV-2 lineages/clades/variants, which is detailed in the next section." + }, + { + "objectID": "materials/02-isolates/03-lineages.html#sec-lineages", + "href": "materials/02-isolates/03-lineages.html#sec-lineages", + "title": "6  Lineages and variants", + "section": "6.1 SARS-CoV-2 Variants", + "text": "6.1 SARS-CoV-2 Variants\nAs viruses (or any other organism) evolve, random DNA changes occur in the population, for example due to replication errors. Many of these changes are likely to be neutral, meaning that they do not change the characteristics of the virus in any significant way. Neutral changes tend to drift in the population, increasing or decreasing in frequency in a random way, but most likely end up disappearing due to their low starting frequency in the population.\nOn the other hand, advantageous mutations can occur, which lead to changes in the characteristics of the virus that are beneficial for its spread in the population (e.g. high transmissibility, resistance to immune response or vaccines, etc.). Such beneficial mutations will therefore experience positive selection, potentially leading to their increase in frequency in the population as they spread to more and more hosts.\nViruses carrying those advantageous mutations may, over time, aquire other advantageous mutations that further increase their fitness and, therefore, their frequency in the population. One way to visualise this is by looking at a phylogenetic tree showing the relationship between sequences (based on their similarity) and how groups of sequences change over time.\n\n\n\nExample of global phylogeny from the Nextstrain public server. Colours show different Nextstrain clades. (Screenshot taken Feb 2022)\n\n\nIn the figure above, which shows SARS-CoV-2 samples from across the world, we can see groups of similar sequences rapidly “expanding” at certain points in time. Such groups of sequences, which share a collection of DNA changes, are referred to as SARS-CoV-2 variants (see box below about the ambiguous meaning of this term). In an effort to understand the spread of the virus and monitor situations of increased occurrence of such variants, several groups and institutions have developed a system to classify groups of SARS-CoV-2 sequences as variants of interest and variants of concern.\nA full explanation and definitions of such variants is given in the World Health Organisation (WHO) variants page The main classification systems currently in use are:\n\nGISAID clades\nNextstrain clades\nPango lineages\nWorld Health Organisation (WHO) variants\n\nIn practice, there is a big overlap between these different nomenclature systems, with WHO variants having a direct match to Pango lineages and Nextstrain clades. In fact, the different teams work together to try and harmonise the nomenclature used, and thus facilitate the interpretation of sequence analysis.\nThe two most popular systems - Nextclade and Pangolin - have slightly different levels of resolution. Nextclade’s nomenclature system was developed to highlight diversity patterns at a larger scale, allowing discussions of SARS-CoV-2 diversity at a global level and over larger time scales. On the other hand, Pangolin’s nomenclature system is more fine-grained, aiming to follow the dynamics of the pandemic as it unfolds. The two systems are complementary to each other, and our analysis of SARS-CoV-2 sequences should include both tools.\n\n\n\n\n\n\nWhat is a variant?\n\n\n\nIt is important to note that the term variant can be sometimes ambiguous.\nThe term “SARS-CoV-2 variant” usually refers to the WHO definition of variants of concern/interest (e.g. the Alpha, Delta and Omicron variants), which includes sequences containing a collection of several nucleotide changes that characterise that group. According to this definition, we have two variants in the example below (samples 1 & 2 are one variant and samples 3 & 4 another variant).\n\nHowever, in bioinformatic sequence analysis, a sequence variant refers to an individual change in the DNA sequence (a SNP or an insertion/deletion). Using this definition, in the example above we have 5 variants: 3 SNPs and 2 indels. In the Consensus Sequence section, we saw that one of our workflow steps was “variant calling”. This was the definition of variant we were using: identifying individual SNPs and/or indels relative to the reference genome, from our sequencing data. This is also reflected in the one of the common file formats used to store SNP/indel information, the VCF file, which means “Variant Call Format”.\nSometimes the term “mutation” is used to refer to SNP/indel variants. For example see this definition from the COG consortium.\nBecause of this ambiguity, the terms “lineages” or “clades” are often used instead of “variants” when referring to groups of similar SARS-CoV-2 sequences, because they have a phylogenetic interpretation." + }, + { + "objectID": "materials/02-isolates/03-lineages.html#pangolin", + "href": "materials/02-isolates/03-lineages.html#pangolin", + "title": "6  Lineages and variants", + "section": "6.2 Pangolin", + "text": "6.2 Pangolin\n\nThe first tool we will cover is called pangolin and uses the Pango nomenclature system. The main steps performed by this tool are:\n\nMultiple sequence alignment of our samples against the Wuhan-Hu-1 reference genome, using the minimap2 software.\nAssigning our sequences to lineages based on the current global phylogeny. Two methods/software are available:\n\npangoLEARN (default) uses a pre-trained machine learning model.\nUShER uses a more classic parsimony-based method, but highly optimised for working with large numbers of sequences.\n\nClassifying our sequences according to the WHO nomenclature of variants of interest/concern using the scorpio software.\n\nAlthough Pangolin can run as part of the nf-core/viralrecon pipeline we used, we recommended to turn this option off. The reason is that the model to classify variants regularly changes over time, as more public sequences become available and the nomenclature rules updated. Therefore, it important to always run the samples through the latest Pangolin version available.\nPangolin can be run from the command line, using two steps:\n\nUpdating the data used for lineage/variant classification.\nRunning the actual lineage assignment step.\n\nOn our example data, these would be the commands:\n# update pangolin data\npangolin --update-data\n\n# run pangolin\npangolin --outdir results/pangolin/ --outfile pango_report.csv results/clean_sequences.fa\n\nThe first command downloads the latest version of the lineages and their characteristic mutations from the Pango classification system.\nThe second command runs the FASTA file of consensus genomes through Pangolin’s classification algorithm.\n\n--outdir is used to define the directory to save the results in.\n--outfile is used to give the file a name of our choice.\n\n\nThe output is a CSV file, with several columns of interest, including WHO variants of concern identified using the Scorpio software. A detailed explanation of the columns of this file is given in the Pangolin documentation page.\nMore information about running Pangolin from the command line can be found in its online documentation.\n\n6.2.1 Web Application\nThis tool can also be run separately using a web application, which only requires us to provide with a FASTA file of consensus sequences. This may desirable to re-run samples using the latest version of the Pangolin software and SARS-CoV-2 variant databases.\nThe results from the web application can be downloaded as a CSV file, which contains a table similar to the one obtained from our command above (some of the column names are different, but their meaning is the same)." + }, + { + "objectID": "materials/02-isolates/03-lineages.html#nextclade", + "href": "materials/02-isolates/03-lineages.html#nextclade", + "title": "6  Lineages and variants", + "section": "6.3 Nextclade", + "text": "6.3 Nextclade\n\nAnother system of clade assignment is provided by nextclade, which is part of the broader software ecosystem Nextstrain.\nNextclade performs similar steps to Pangolin, with some differences in the algorithms that are used:\n\nEach sequence is aligned with the Wuhan-Hu-1 reference genome using a local alignment algorithm.\nSamples are placed in the global phylogeny using a distance-based metric (placing the sequence on the tree where it has the highest similarity with).\nClade assignment is done based on the previous phylogeny placement step.\n\nYou can find more details about Nextclade’s methods on its documentation. Nextclade also provides several quality control metrics, which are very useful to identify problematic samples.\nAs we discussed above, the models and clades are regularly updated, so we also skipped this step when we ran the nf-core/viralrecon pipeline. Instead, we can run this tool directly from the command line, by first making sure to download the most up-to-date clade information. Here are the commands:\n# get nextclade data\nnextclade dataset get --name sars-cov-2 --output-dir resources/nextclade_background_data\n\n# run nextclade\nnextclade run --input-dataset resources/nextclade_background_data/ --output-all results/nextclade results/clean_consensus.fa\n\nThe first command (nextclade dataset get) downloads the latest version of the Nextclade dataset for SARS-CoV-2 (option --name sars-cov-2). We define the directory to store this information with --output-dir.\nThe next step is to run the actual clade assignment on our data. We use the database from the previous step as --input-dataset, we define a directory to output all the results with --output-all and at the end of the command we give as input our clean FASTA consensus genomes.\n\nMore information about running Nextclade from the command line can be found in its online documentation.\n\n6.3.1 Web Application\nNextclade offers an interactive application, which can be used to run its analysis on a FASTA file with sequences:\n\nGo to nextclade.org.\nClick Select a file to browse your computer and upload the FASTA file with the cleaned consensus sequences (results/clean_sequences.fa).\nNextclade will automatically detect your data are from SARS-CoV-2, but if not you can select this organism.\nClick Run.\n\nNextclade will show a progress of its analysis at the top of the page, and the results of several quality control metrics in the main panel (see Figure).\n\n\n\nOverview of the Nextclade web interface.\n\n\n\n\n\n\n\n\nNextclade and data privacy\n\n\n\nWhen using the Nextclade web application, the data does not leave your computer, so privacy concerns are not an issue." + }, + { + "objectID": "materials/02-isolates/03-lineages.html#exercises", + "href": "materials/02-isolates/03-lineages.html#exercises", + "title": "6  Lineages and variants", + "section": "6.4 Exercises", + "text": "6.4 Exercises\n\n\n\n\n\n\nNextclade\n\n\n\n\n\n\n\nIn this exercise we will work with 48 consensus sequences from the UK, processed with the nf-core/viralrecon pipeline and covered in the previous section.\nGo to nextclade.org and load the sequences provided in uk_illumina/preprocessed/clean_sequences.fa.\n\nAre there any samples that were classified as “bad” quality? If so, what is the main reason?\nSort the table by the “Clade” column. Looking at the mutations in gene S on the right, you can see that all sequences classified as “Alpha” have a deletion in positions 21992-21994. Samples classified as “Delta” do not have this deletion and instead have a deletion in positions 22029-22034. However, there is one exception: sample GB39, classified as “Delta” has both deletions. Investigate if this mutation is accurate using IGV:\n\nOpen the BAM alignment file for this sample (the alignment file is in results/viralrecon/variants/bowtie2/GB39.ivar_trim.sorted.bam).\nOpen the BAM alignment file for one of the “Alpha” samples as a comparison.\nOpen the ARTIC primer files (two BED files found in resources/primers/).\nGo to the position where this deletion was identified and investigate if it seems clear from the mapped reads.\n\n\n\n\n\n\n\n\nAnswer\n\n\n\n\n\n\n\nQuestion 1\nAfter loading the samples to the Nextclade web application, we can see that, generally, these samples have high quality as very few are highlighted by Nextclade’s analysis.\nSorting the table by the “QC” column, we can see two samples with bad quality (red) and one with mediocre quality (yellow).\nThere are separate reasons for the two bad quality samples:\n\nSample GB16 has low quality due to a high number of missing bases.\nSample GB39 has low quality due to the occurrence of too many “private mutations”. These are mutations that are only found in this sample, compared to other samples present in Nextclade’s background sequence tree. As we don’t usually expect too many new mutations to occur in a new sample (the mutation rate of SARS-CoV-2 is around 2 new mutations per month), the occurrence of too many private mutations could indicate sequencing quality issues.\n\nQuestion 2\nSort the table by the “Clade” column. Looking at the mutations in gene S on the right, you can see that all sequences classified as “Alpha” have a deletion in positions 21992-21994. Samples classified as “Delta” do not have this deletion and instead have a deletion in positions 22029-22034. However, there is one exception: sample GB39, classified as “Delta” has both deletions. Investigate if this mutation is accurate using IGV:\n\nOpen the BAM alignment file for this sample (the alignment file is in preprocessed/variants/bowtie2/GB39.ivar_trim.sorted.bam).\nOpen the BAM alignment file for one of the “Alpha” samples as a comparison.\nOpen the ARTIC primer files (two BED files found in resources/primers/).\nGo to the position where this deletion was identified and investigate if it seems clear from the mapped reads.\n\nBy sorting the table by “Clade”, we can see that although sample GB39 was classified as a Delta variant, it has two deletions in gene S that are present in Alpha variants, as shown in this snapshot (click the image to view a bigger size):\n\n \n\nAs we investigate one of these in more detail from the BAM file (opening it in IGV), we can see that there is an inconsistency between reads coming from the PCR fragment “nCoV-2019_72” and those reads coming from the “nCoV-2019_73” fragment. In some of those reads the deletion is present, but in others it is not. If we look at an Alpha variant sample (for example GB43) we can see that this deletion is present in both cases.\n\nIf we thought this sample was crucial for public health investigation, then this would require further investigation by doing a PCR with new primers and Sanger-sequencing the fragment, for example." + }, + { + "objectID": "materials/02-isolates/03-lineages.html#summary", + "href": "materials/02-isolates/03-lineages.html#summary", + "title": "6  Lineages and variants", + "section": "6.5 Summary", + "text": "6.5 Summary\n\n\n\n\n\n\nKey Points\n\n\n\n\nGroups of similar SARS-CoV-2 sequences are classified into lineages or clades by different groups. The main nomenclature systems in use are Nextstrain, Pangolin and GISAID.\nIn addition, the World Health Organisation (WHO) classifies some forms of SARS-CoV-2 as variants of concern or variants of interest. These are forms of the virus that have been determined to have a significant public health impact.\nBoth Pangolin and Nextclade assign consensus sequences to lineages/clades and additionally identify those that correspond to WHO variants of concern. Both of these are run as part of the nf-core/viralrecon pipeline, but can also be run using a web application:\n\nPangolin web application\nNextclade web application\n\nBesides clade assignment and variant classification, Nextclade provides additional analysis such as identification of mutations and quality control metrics that can be used to identify problematic samples." + }, + { + "objectID": "materials/02-isolates/04-phylogeny.html#sec-phylogeny", + "href": "materials/02-isolates/04-phylogeny.html#sec-phylogeny", + "title": "7  Building phylogenetic trees", + "section": "7.1 SARS-CoV-2 Phylogeny", + "text": "7.1 SARS-CoV-2 Phylogeny\n\nBuilding phylogenetic trees for SARS-CoV-2 is challenging due to the large number of sequences available, with millions of genomes submited to GISAID to this day. This means that using maximum-likelihood inference tools to build a global SARS-CoV-2 phylogeny is both time-consuming and computationally demanding.\nHowever, several researchers have dedicated their time to identifying tools and models suitable for the phylogenetic analysis of this organism. For example, the global phylogenies repository from Rob Lanfear provide with several tips on building phylogenetic trees for this organism. Their trees are regularly updated and available to download from the GISAID website (which requires an account).\nGlobal phylogenies are also available from the groups of Russell Corbett-Detig and Yatish Turakhia, who have developed efficient methods and tools for dealing with large phylogenies. These tools include UShER and matUtils, introducing a new and efficient file format for storing phylogenetic trees, mutations and other annotations (such as lineages) called mutation-annotated trees (MAT format). Their phylogenies are updated daily and are publicly available for download. (Note: Course materials covering these tools are still under development.)\nTwo popular tools used for phylogenetic inference via maximum-likelihood are FastTree and IQ-Tree. Generally, when building a tree from a collection of samples you can include the Wuhan-Hu-1 reference sequence as an outgroup to root the tree. Optionally, you can also add a collection of sequences from around the world (and across time) to contextualise your samples in the global diversity.\nAs an input, these programs need a multiple sequence alignment FASTA file, which is where we start our analysis.\n\n\n\n\n\n\nData for this section\n\n\n\nWe will work from the course materials folder called 04-phylogeny, which contains the following files:\n\ndata/uk_consensus.fa and data/india_consensus.fa are consensus sequences from the UK and India, respectively. These are the sequences previously assembled using the nf-core/viralrecon pipeline.\nsample_annotation.tsv is a tab-separated values (TSV) file with information about each sample such as the date of collection and lineage/clade they were assiged to from our analysis. We will use this table to annotate our phylogenetic trees. This table can also be opened in a spreadsheet program such as Excel.\nresources/reference/sarscov2.fa is the reference genome." + }, + { + "objectID": "materials/02-isolates/04-phylogeny.html#sec-mafft", + "href": "materials/02-isolates/04-phylogeny.html#sec-mafft", + "title": "7  Building phylogenetic trees", + "section": "7.2 Alignment", + "text": "7.2 Alignment\nThe first step in building a phylogenetic tree is to produce a multiple sequence alignment from all our consensus sequences. This is the basis for building a phylogenetic tree from the positions that are variable across samples.\nA widely used multiple sequence alignment software is called MAFFT. For SARS-CoV-2, a reference-based alignment approach is often used, which is suitable for closely-related genomes. MAFF provides this functionality, which is detailed in its documentation.\nWe demonstrate the analysis using the UK samples in our course materials. First, start by creating a directory for the output:\nmkdir -p results/mafft\nThen, we run the command to generate a reference-based alignment:\nmafft --6merpair --maxambiguous 0.2 --addfragments data/uk_consensus.fa resources/reference/sarscov2.fa > results/mafft/uk_alignment.fa\nThe meaning of the options used is:\n\n--6merpair is a fast method for estimating the distances between sequences, based on the number of short 6bp sequences shared between each pair of sequences. This is less accurate than other options available (like --localpair and --globalpair), but runs much faster in whole genome data like we have.\n--maxambiguous 0.2 automatically removes samples with more than 20% ambiguous ‘N’ bases (or any other value of our choice). This is a convenient way to remove samples with poor genome coverage from our analysis.\n--addfragments data/consensus_sequences.fa is the FASTA file with the sequences we want to align.\nfinally, at the end of the command we give the reference genome as the input sequence to align our sequences against.\n\nMAFFT provides several other methods for alignment, with tradeoffs between speed and accuracy. You can look at the full documentation using mafft --man (mafft --help will give a shorter description of the main options). For example, from its documentation we can see that the most precise alignment can be obtained with the options --localpair --maxiterate 1000. However, this is quite slow and may not be feasible for whole genome data of SARS-CoV-2.\n\n7.2.1 Visualising alignments\nWe can visualise our alignment using the software AliView, which is both lightweight and fast, making it ideal for large alignments. Visualising the alignment can be useful for example to identify regions with missing data (more about this below).\n\n\n\nSnapshop of an alignment visualised with AliView. In this case we are looking at the end of the alignment of our sequences, which shows a typical high number of missing (‘N’) bases.\n\n\n\n\n\n\n\n\nOther Alignment Strategies\n\n\n\nThere are other commonly used alignment tools used for SARS-CoV-2 genomes:\n\nThe minimap2 software has been designed for aligning long sequences to a reference genome. It can therefore be used to align each consensus sequence to the Wuhan-Hu-1 genome. This is the tool internally used by Pangolin.\nNextclade uses an internal alignment algorithm where each consensus sequence is aligned with the reference genome. The alignment produced from this tool can also be used for phylogenetic inference.\n\nIt is worth mentioning that when doing reference-based alignment, insertions relative to the reference genome are not considered." + }, + { + "objectID": "materials/02-isolates/04-phylogeny.html#sec-iqtree", + "href": "materials/02-isolates/04-phylogeny.html#sec-iqtree", + "title": "7  Building phylogenetic trees", + "section": "7.3 Tree Inference: IQ-Tree", + "text": "7.3 Tree Inference: IQ-Tree\nIQ-TREE supports many substitution models, including models with rate heterogeneity across sites.\nLet’s start by creating an output directory for our results:\nmkdir -p results/iqtree\nAnd then run the program with default options (we set --prefix to ensure output files go to the directory we just created and are named “uk”):\niqtree2 -s results/mafft/uk_alignment.fa --prefix results/iqtree/uk\nWithout specifying any options, iqtree2 uses ModelFinder to find the substituion model that maximizes the likelihood of the data, while at the same time taking into account the complexity of each model (using information criteria metrics commonly used to assess statistical models).\nFrom the information printed on the console after running the command, we can see that the chosen model for our alignment was “GTR+F+I”, a generalised time reversible (GTR) substitution model. This model requires an estimate of each base frequency in the population of samples, which in this case is estimated by simply counting the frequencies of each base from the alignment (this is indicated by “+F” in the model name). Finally, the model includes rate heterogeneity across sites, allowing for a proportion of invariant sites (indicated by “+I” in the model name). This makes sense, since we know that there are a lot of positions in the genome where there is no variation in our samples.\nWe can look at the output folder (specified with --prefix) where we see several files with the following extension:\n\n.iqtree - a text file containing a report of the IQ-Tree run, including a representation of the tree in text format.\n.treefile - the estimated tree in NEWICK format. We can use this file with other programs, such as FigTree, to visualise our tree.\n.log - the log file containing the messages that were also printed on the screen.\n.bionj - the initial tree estimated by neighbour joining (NEWICK format).\n.mldist - the maximum likelihood distances between every pair of sequences.\nckp.gz - this is a “checkpoint” file, which IQ-Tree uses to resume a run in case it was interrupted (e.g. if you are estimating very large trees and your job fails half-way through).\n.model.gz - this is also a “checkpoint” file for the model testing step.\n\nThe main files of interest are the report file (.iqtree) and the NEWICK tree file (.treefile).\n\n\n\n\n\n\nInference of very large trees\n\n\n\nAlthough running IQ-Tree with default options is fine for most applications, there will be some bottlenecks once the number of samples becomes too large. In particular, the ModelFinder step may be very slow and so it’s best to set a model of our choice based on other people’s work. For example, work by Rob Lanfear suggests that models such as “GTR+G” and “GTR+I” are suitable for SARS-CoV-2. We can specify the model used by iqtree2 by adding the option -m GTR+G, for example.\nFor very large trees (over 10k or 100k samples), using an alternative method to place samples in an existing phylogeny may be more adequate. UShER is a popular tool that can be used to this end. It uses a parsimony-based method, which tends to perform well for SARS-CoV-2 phylogenies." + }, + { + "objectID": "materials/02-isolates/04-phylogeny.html#sec-figtree", + "href": "materials/02-isolates/04-phylogeny.html#sec-figtree", + "title": "7  Building phylogenetic trees", + "section": "7.4 Visualising Trees", + "text": "7.4 Visualising Trees\nThere are many programs that can be used to visualise phylogenetic trees. In this course we will use FigTree, which has a simple graphical user interface.\nTo open the tree, go to File > Open… and browse to the folder with the IQ-Tree output files. Select the file with .treefile extension and click Open. You will be presented with a visual representation of the tree.\nWe can also import a “tab-separated values” (TSV) file with annotations to add to the tree. For example, we can use our results from Pangolin and Nextclade, as well as other metadata to improve our visualisation (we have prepared a TSV with this information combined, which you could do using Excel or another spreadsheet software).\n\nGo to File > Import annotations… and open the annotation file.\nOn the menu on the left, click Tip Labels and under “Display” choose one of the fields of our metadata table. For example, you can display the lineage assigned by Pangolin (“pango_lineage” column of our annotation table).\n\nThere are many ways to further configure the tree, including highlighting clades in the tree, and change the labels. See the figure below for an example.\n\n\n\nAnnotated phylogenetic tree obtained with FigTree. We used the lineage of each sample as our tip labels and aligned the labels on the right (check the tickbox at the top of the left menu called “Align tip labels”). We identified two clades in the tree that corresponded to the Alpha and Delta variants, and used the “Highlight” tool to give them different colours. To do this, change the “Selection Mode” at the top to “Clade”, then select the branch at the base of the clade you want to highlight, and press the “Highlight” button on the top to pick a colour." + }, + { + "objectID": "materials/02-isolates/04-phylogeny.html#time-scaled-phylogenies", + "href": "materials/02-isolates/04-phylogeny.html#time-scaled-phylogenies", + "title": "7  Building phylogenetic trees", + "section": "7.5 Time-scaled Phylogenies", + "text": "7.5 Time-scaled Phylogenies\nThe trees that we build from sequence data are scaled using the mutation rate estimated from the sequence alignments. This is useful if we want to know, for example, on average how many mutations separate different branches of the tree.\nAnother way to scale trees is to use time. For viral genome sequences, we usually have information about their date of collection, and this can be used to scale the phylogeny using the date information. The idea is to rescale the trees such that the x-axis of the tree represents a date rather than number of mutations.\nTwo programs that can be used to time-scale trees using date information are called TreeTime and Chronumental. Chronumental was developed for dealing with very large phylogenies (millions of samples), but lacks some of the functionalities provided by TreeTime (such as estimating uncertainty in date estimates, reconstructing past sequences, re-rooting trees, among others). So, if you are working with less than ~50,000 sequences, we recommend using TreeTime, otherwise Chronumental is a suitable alternative.\nBecause we are dealing with a small number of samples, we will show an example of using TreeTime to scale our tree based on our dates:\ntreetime --tree results/iqtree/uk.treefile --dates sample_annotation.tsv --aln results/mafft/uk_alignment.fa --outdir results/treetime/uk\nAfter running, this tool produces several output files in the specified folder. The main files of interest are:\n\ntimetree.nexus is the new date-scaled tree. NEXUS is another tree file format, which can store more information about the tree compared to the simpler NEWICK format. This format is also supported by FigTree.\ntimetree.pdf is a PDF file with the inferred tree, including a time scale on the x-axis. This can be useful for quick visualisation of the tree.\n\nWe can visualise this tree in FigTree, by opening the file timetree.nexus. The scale of this new tree now corresponds to years (instead of nucleotide substitution rates). We make make several adjustments to this tree, to make it look how we prefer. For example:\n\nLabel the nodes of the tree with the inferred dates: from the left menu click Node Labels and under “Display” select “date”.\nImport the metadata table (sample_annotation.tsv file) and display the results of Nextclade or Pangolin clades/lineages.\nAdjust the scale of the tree to be more intuitive. For example, instead of having the unit of the scale in years, we can change it to months. On the left menu, click Time Scale and change “Scale factor” to 12 (twelve months in a year). Then click Scale Bar and change the “Scale range” to 1. The scale now represents 1 month of time, which may be easier to interpret in this case.\n\nNote that there is uncertainty in the estimates of the internal node dates from TimeTree and these should be interpreted with some caution. The inference of the internal nodes will be better the more samples we have, and across a wider range of times. In our specific case we had samples all from a similar period of time, which makes our dating of internal nodes a little poorer than might be desired.\nMore precise tree dating may be achieved by using public sequences across a range of times or by collecting more samples over time. Time-scaled trees of this sort can therefore be useful to infer if there is a recent spread of a new clade in the population." + }, + { + "objectID": "materials/02-isolates/04-phylogeny.html#missing-data-problematic-sites", + "href": "materials/02-isolates/04-phylogeny.html#missing-data-problematic-sites", + "title": "7  Building phylogenetic trees", + "section": "7.6 Missing Data & Problematic Sites", + "text": "7.6 Missing Data & Problematic Sites\nSo far, we have been using all of our assembled samples in the phylogenetic analysis. However, we know that some of these have poorer quality (for example the “IN01” sample from India had low genome coverage). Although, generally speaking, sequences with missing data are unlikely to substantially affect the phylogenetic results, their placement in the phylogeny will be more uncertain (since several variable sites may be missing data). Therefore, for phylogenetic analysis, it is best if we remove samples with low sequencing coverage, and instead focus on high-quality samples (e.g. with >80% coverage).\nA more serious issue affecting phylogenies is the presence of recurrent errors in certain positions of the genome. One of the regions with a higher prevalence of errors is the start and end of the consensus sequence, which also typically contains many missing data (see example in Figure 2). Therefore, it is common to mask the first and last few bases of the alignment, to avoid including spurious variable sites in the analysis.\n\n\n\n\n\n\nSequence masking\n\n\n\nThe term masking is often used to refer to the process of converting sequence bases to the ambiguous character ‘N’. You may come across this term in the documentation of certain tools, for example: “Positions with less than 20x depth of sequencing are masked.”\nMasks are not limited to depth of sequencing. For example, reference genomes from ENSEMBL are available with masked repeat or low-complexity sequences (e.g. around centromeres, transposon-rich regions, etc.).\nThe term soft masking is also used to refer to cases where, instead of using the ambiguous character ‘N’, sequences are masked with a lowercase. For example:\n>seq_with_soft_masking\nACAGACTGACGCTGTcatgtatgtcgacGATAGGCTGATGGCGAGTGACTCGAG\n>seq_with_hard_masking\nACAGACTGACGCTGTNNNNNNNNNNNNNGATAGGCTGATGGCGAGTGACTCGAG\n\n\nAdditionally, work by Turakhia, de Maio, Thornlow, et al. (2020) has identified several sites that show an unexpected mutation pattern. This includes, for example, mutations that unexpectedly occur multiple times in different parts of the tree (homoplasies) and often coincide with primer binding sites (from amplicon-based protocols) and can even be lab-specific (e.g. due to their protocols and data processing pipelines). The work from this team has led to the creation of a list of problematic sites, which are recommended to be masked before running the phylogenetic analysis.\n\n\n\nExample of errors in phylogenetic inference due to recurrent sequencing errors. Source: Figure 1 in Turakhia, de Maio, Thornlow et al. (2020)\n\n\nSo, let’s try to improve our alignment by masking the problematic sites, which are provided as a VCF file. This file also includes the first and last positions of the genome as targets for masking (positions 1–55 and 29804–29903, relative to the Wuhan-Hu-1 reference genome MN908947.3). The authors also provide a python script for masking a multiple sequence alignment. We have already downloaded these files to our course materials folder, so we can go ahead and use the script to mask our file:\npython scripts/mask_alignment_using_vcf.py --mask -v resources/problematic_sites.vcf -i results/mafft/uk_alignment.fa -o results/mafft/uk_alignment_masked.fa\nIf we open the output file with AliView, we can confirm that the positions specified in the VCF file have now been masked with the missing ‘N’ character.\nWe could then use this masked alignment for our tree-inference, just as we did before.\n\n\n\n\n\n\nUsing Python Scripts\n\n\n\nBioinformaticians often write custom scripts for particular tasks. In this example, the authors of the “problematic sites” wrote a Python script that takes as input the FASTA file we want to mask as well as a VCF with the list of sites to be masked.\nPython scripts are usually run with the python program and often accept options in a similar way to other command-line tools, using the syntax --option (this is not always the case, but most professionally written scripts follow this convention). To see how to use the script we can use the option --help. For our case, we could run:\npython scripts/mask_alignment_using_vcf.py --help" + }, + { + "objectID": "materials/02-isolates/04-phylogeny.html#exercises", + "href": "materials/02-isolates/04-phylogeny.html#exercises", + "title": "7  Building phylogenetic trees", + "section": "7.7 Exercises", + "text": "7.7 Exercises\n\n\n\n\n\n\nBuilding phylogenies\n\n\n\n\n\n\n\nSo far we have focused our analysis on the samples from the UK. In this exercise you will be able to practice these steps on the samples from India. The steps are similar to what we have done so far, and you can consult the materials in the sections above to go through each exercise.\nIn the following exercises, you can run the commands directly from the command line. But if you feel comfortable with nano, as a bonus, you can try to save the commands in a shell script.\n\nUsing mafft, produce a multiple-sequence alignment from the India consensus sequences in the file data/india_consensus.fa. Save the output in a file named results/mafft/india_alignment.fa.\nUsing iqtree2, infer a phylogenetic tree from this alignment. Save the output with prefix results/iqtree/india.\n\nWhat substitution model did IQ-Tree infer as the most likely for the data?\n\nUsing FigTree, visualise the inferred tree:\n\nOpen the .tree output file.\nImport the annotation table sample_annotation.tsv.\nMake the tip labels display the Nextclade clade instead of the sample names.\nHighlight any clusters of the tree containing WHO variants of concern.\n\nUsing timetree, re-scale the phylogeny using dates (the file sample_annotation.tsv can be used as input to timetree along with the previously-created phylogeny and alignments). Output the result to results/treetime/india\n\nOpen the .nexus output file in FigTree.\nMake the node labels display the date inferred by timetree.\n\nTwo samples in the time-scaled tree appear at the root of the tree: IN05 and IN33. But we would have expected the reference sample (MN908947.3) to be the root of the tree, as it was collected in Dec 2019.\n\nInvestigate what lineages these samples were assigned to.\nGo to https://cov-lineages.org/lineage_list.html and check when those lineages were detected.\nIs the collection date metadata for these samples compatible with the lineage information from the Pangolin website? Can you hypothesise what may have happened with these samples?\n\n\n\n\n\n\n\n\nAnswer\n\n\n\n\n\n\n\nQuestion 1\nWe run our alignment using the following command:\nmafft --6merpair --maxambiguous 0.2 --addfragments data/india_consensus.fa resources/reference/sarscov2.fa > results/mafft/india_alignment.fa\nWe could optionally visualise our alignment using AliView to check that the alignment was successful.\nQuestion 2\nWe can fit a maximum-likelihood tree using the following command:\niqtree2 -s results/mafft/india_alignment.fa --prefix results/iqtree/india\nThis command prints a lot of output on the screen. We can see all this information (and more!) on the output file results/iqtree/india.iqtree, which contains a log of all the analysis done by the program. We could, for example, use the less program to look inside this file. When we do this, we can see the following:\nModelFinder\n-----------\n\nBest-fit model according to BIC: GTR+F+I\nWhich indicates that the substitution model used by IQ-Tree was “GTR+F+I”. This is the same model that was determined for the UK samples, and an explanation of this model is given in the materials above.\nQuestion 3\nUsing FigTree:\n\nGo to File > Open… and browse to the folder with the IQ-Tree output files.\nSelect the file with india.treefile and click Open.\nGo to File > Import annotations… and open the annotation file sample_annotation.tsv.\nOn the menu on the left, click Tip Labels and under “Display” choose the field “nextclade_clade”.\n\nOn this tree, there is a small group of samples classified as “20I (Alpha; V1)”. These samples correspond to the Alpha variant of concern. To highlight these:\n\nChange the “Selection Mode” at the top to “Clade”.\nSelect the branch corresponding to the base of the group of samples classified as Alpha. This should highlight all those branches.\nClick the “Highlight” button at the top and choose a colour.\n\n\nQuestion 4\nThe command to time-scale the tree is:\ntreetime --tree results/iqtree/india.treefile --dates sample_annotation.tsv --aln results/mafft/india_alignment.fa --outdir results/treetime/india\nOnce complete, we can open the india.nexus tree with FigTree. We can annotate the internal nodes of the tree with the dates inferred by treetime by clicking on the Node Labels menu on the left and selecting “Display” to be “date”.\n\nQuestion 5\nTo investigate this strange result, we open the sample_annotation.tsv file in our spreadsheet program. We can see the following information for these two samples:\nname date country year month pango_lineage nextclade_clade\nIN05 2021-01-21 India 2021 1 A.23.1 19B\nIN33 2021-01-21 India 2021 1 A.23.1 19B\nBoth these samples were assigned to Pango lineage “A.23.1”. From the cov-lineages.org website we can see that these were detected as early as 2020-06-08.\nHowever, our samples’ metadata indicate that these samples were collected in January 2021, that is 7 months after these samples were first detected globally.\nThis contractiction explains the strange result in our time-scaled tree. These samples will have mutations that are part of the older lineage “A”, but were annotated to be from 2021. So treetime put them down at the root of the tree.\nThis discrepancy could indicate an issue with the metadata collection, which perhaps is wrong. For example, it could be that these samples were collected months before January 2021, but only sequenced then. And by mistake the date of sequencing was recorded as the date of collection.\nThis is an example how important accurate metadata is for our analysis." + }, + { + "objectID": "materials/02-isolates/04-phylogeny.html#summary", + "href": "materials/02-isolates/04-phylogeny.html#summary", + "title": "7  Building phylogenetic trees", + "section": "7.8 Summary", + "text": "7.8 Summary\n\n\n\n\n\n\nKey Points\n\n\n\n\nMethods for phylogenetic inference include parsimony and maximum likelihood. Maximum likelihood methods are preferred because they include more features of the evolutionary process. However, they are computationally more demanding than parsimony-based methods.\nTo build a phylogenetic tree we need a multiple sequence alignment of the sequences we want to infer a tree from.\nIn SARS-CoV-2, alignments are usually done against the Wuhan-Hu-1 reference genome.\nWe can use the software mafft to produce a multiple sequence alignment. The option --addfragments is used to produce an alignment against the reference genome.\nThe software iqtree2 can be used for inferring trees from an alignment using maximum likelihood. This software supports a wide range of substitution models and a method to identify the model that maximizes the likelihood of the data.\nSome of the substituion models that have been used to build global SARS-CoV-2 phylogenies are “GTR+G” and “GTR+I”.\nWe can time-scale trees using sample collection date information. The program treetime can be used to achieve this. For very large sample sizes (>50,000 samples) the much faster program Chronumental can be used instead.\nBefore building a phylogeny, we should be careful to mask problematic sites that can lead to misleading placements of samples in the tree. The SARS-CoV-2 Problematic Sites repository provides with an updated list of sites that should be masked." + }, + { + "objectID": "materials/03-case_studies/01-switzerland.html#pipeline-overview", + "href": "materials/03-case_studies/01-switzerland.html#pipeline-overview", + "title": "8  Switzerland (Nanopore)", + "section": "8.1 Pipeline Overview", + "text": "8.1 Pipeline Overview\nOur analysis starts with FASTQ files, which will be used with the nf-core/viralrecon Nextflow pipeline. This will give us several quality control metrics essential for our downstream analysis and reporting.\nCritical files output by the pipeline will need to be further processed, including combining our consensus FASTA files and obtaining a list of filtered SNP/indel variants. Using these clean files, we can then proceed to downstream analysis, which includes assigning each sample to the most up-to-date Pango lineage, Nextclade clade and WHO designation. Finally, we can do more advanced analysis, including the idenfication of sample clusters based on phylogenetic analysis, or produce timeseries visualisations of mutations or variants of concern. With all this information together, we will have the necessary pieces to submit our results to public repositories and write reports to inform public health decisions." + }, + { + "objectID": "materials/03-case_studies/01-switzerland.html#preparing-files", + "href": "materials/03-case_studies/01-switzerland.html#preparing-files", + "title": "8  Switzerland (Nanopore)", + "section": "8.2 Preparing Files", + "text": "8.2 Preparing Files\nBefore we start our work, it’s always a good idea to setup our directory structure, so we keep our files organised as the analysis progresses. From the data we are starting with, we already have the following directories:\n\ndata → contains the sequencing data in a sub-directory called fast_pass.\nresources → files that were downloaded from public repositories.\nscripts → bash and R scripts used to run the analysis.\n\nWe create two additional directories:\n\nreport → files and documents that we report to our colleagues or upload to public repositories.\nresults → results of the analysis.\n\nYou can create directories from the command line using the mkdir command:\nmkdir results\nmkdir report\n\n8.2.1 Data\nWe start our analysis from FASTQ files generated using the software Guppy v6.1.5 ran in “fast” mode. This software outputs the files to a directory called fastq_pass, with further sub-directories for each sample barcode. This is how it looks like in this case:\nls data/fastq_pass\nbarcode01 barcode08 barcode15 barcode22 barcode29 barcode36 barcode43 barcode50 barcode57\nbarcode02 barcode09 barcode16 barcode23 barcode30 barcode37 barcode44 barcode51 barcode58\nbarcode03 barcode10 barcode17 barcode24 barcode31 barcode38 barcode45 barcode52 barcode59\nbarcode04 barcode11 barcode18 barcode25 barcode32 barcode39 barcode46 barcode53 barcode60\nbarcode05 barcode12 barcode19 barcode26 barcode33 barcode40 barcode47 barcode54 barcode61\nbarcode06 barcode13 barcode20 barcode27 barcode34 barcode41 barcode48 barcode55 barcode62\nbarcode07 barcode14 barcode21 barcode28 barcode35 barcode42 barcode49 barcode56 barcode63\n\n\n8.2.2 Metadata\nMetadata for these samples is available in the file sample_info.csv. Here is some of the information we have available for these samples:\n\nsample → the sample ID.\ncollection_date → the date of collection for the sample in the format YYYY-MM-DD.\ncountry → the country of origin for this sample.\nlatitude/longitude → coordinates for sample location (optional).\nsequencing_instrument → the model for the sequencing instrument used (e.g. NovaSeq 6000, MinION, etc.).\nsequencing_protocol_name → the type of protocol used to prepare the samples (e.g. ARTIC).\namplicon_primer_scheme → for amplicon protocols, what version of the primers was used (e.g. V3, V4.1)\nSpecific columns for Oxford Nanopore data, which are essential for the bioinformatic analysis:\n\nont_pore → the version of the pores.\nont_guppy_version → the version of the Guppy software used for basecalling.\nont_guppy_mode → the basecalling mode used with Guppy." + }, + { + "objectID": "materials/03-case_studies/01-switzerland.html#consensus-assembly", + "href": "materials/03-case_studies/01-switzerland.html#consensus-assembly", + "title": "8  Switzerland (Nanopore)", + "section": "8.3 Consensus Assembly", + "text": "8.3 Consensus Assembly\n\n\n\n\n\n\nNote\n\n\n\nSee Section 4.1, if you need to revise how the nf-core/viralrecon pipeline works.\n\n\nThe first step in the bioinformatic analysis is to run the nf-core/viralrecon pipeline.\n\n8.3.1 Samplesheet\nBut first we need to prepare our input files. For Nanopore data, we need a samplesheet CSV file with two columns, indicating sample name (first column) and the respective barcode number (second column).\nWe produced this table in Excel and saved it as a CSV file. Here are the top few rows of the file:\nhead samplesheet.csv\nsample,barcode\nCH01,1\nCH02,2\nCH03,3\nCH04,4\nCH05,5\nCH06,6\nCH07,7\nCH08,8\nCH09,9\nCH10,10\n\n\n8.3.2 Running Viralrecon\nNow we are ready to run the nf-core/viralrecon pipeline (see Section 4.3 for details). We saved our command in a script (scripts/01-run_viralrecon.sh), which we created with the command line text editor nano. This ensures that our analysis is reproducible and traceable (we can go back to the script to see how the analysis was run).\nFirst, we activate our software environment, to ensure Nextflow is available to us:\nmamba activate nextflow\nThen, we run our script with:\nbash scripts/01-run_viralrecon.sh\nWhich will start executing the pipeline.\nFor reference, here is the command included in that script:\nnextflow run nf-core/viralrecon \\\n -r 2.6.0 -profile singularity \\\n --max_memory '16.GB' --max_cpus 8 \\\n --platform nanopore \\\n --input samplesheet.csv \\\n --fastq_dir data/fastq_pass/ \\\n --outdir results/viralrecon \\\n --protocol amplicon \\\n --genome 'MN908947.3' \\\n --primer_set artic \\\n --primer_set_version 3 \\\n --artic_minion_caller medaka \\\n --artic_minion_medaka_model r941_min_fast_g303 \\\n --skip_assembly --skip_asciigenome \\\n --skip_pangolin --skip_nextclade\nIn this case, we used the medaka model r941_min_fast_g303, because that is the latest one available (even though our Guppy version is 6.1.5). We also restricted our --max_memory and --max_cpus due to the size of our processing computers. If a larger computer was available, we could have used higher values for these parameters.\n\n\n\n\n\n\nThe \\ in long commands\n\n\n\nIn the command above, you will notice several \\ at the end of each line. This is indicating that we want to continue writing our command in the next line. Notice that the last line does not include \\, because that is the end of the command. This is very useful when commands are very long, because it makes the code more readable." + }, + { + "objectID": "materials/03-case_studies/01-switzerland.html#consensus-quality", + "href": "materials/03-case_studies/01-switzerland.html#consensus-quality", + "title": "8  Switzerland (Nanopore)", + "section": "8.4 Consensus Quality", + "text": "8.4 Consensus Quality\n\n\n\n\n\n\nNote\n\n\n\nSee Section 5.2, if you need to revise how to assess the quality of consensus sequences.\n\n\n\n8.4.1 General Metrics\nWe used the MultiqQC report to assess the initial quality of our samples. The quality report can be found in results/viralrecon/multiqc/medaka/multiqc_report.html.\nWe paid particular attention to:\n\nNumber of reads mapped to the reference genome.\nMedian depth of coverage.\nPercentage of the genome with missing bases (‘N’).\nNumber of SNP + Indel variants.\n\n\n\n\nExample MultiQC output, highlighting the main columns with quality information that will be collected for the final report.\n\n\nWe noted that:\n\n9 samples had more than 15% missing bases.\nAll samples had median depth of coverage greater than 20x.\nThere was some systematic dropout for some amplicons, in particular nCoV-2019_64 had very low amplification in several of the samples. Of note was also nCoV-2019_73, and other neighbouring amplicons.\n\n\n\n\nAmplicon depth of sequencing (or coverage) across samples, from the MultiQC report. Darker regions indicate systematic amplicon dropout, probably due to issues during the PCR amplification.\n\n\nBesides the MultiQC report, the pipeline also outputs a CSV file with collected summary metrics (equivalent to the first table on the report): results/viralrecon/multiqc/medaka/summary_variants_metrics_mqc.csv. We will use this file later to join this information with our metadata and lineage assignment using the R software (detailed in “Integration & Visualisation” section, below).\n\n\n8.4.2 Variants\nWe also looked at the table of variants obtained from the pipeline. This is output in results/viralrecon/medaka/variants_long_table.csv. This table can be very useful to keep track of particular mutations that may be increasing over time. Later, we will tidy this table to attach to our reported results (“Integration & Visualisation” section).\nBut for now, we will explore this table to address a few more quality-related questions. We opened this table in Excel to answer the following:\n\nWhere there samples with a high number of intermediate allele frequencies? This could indicate mixed samples due to cross-contamination.\nWhere there samples with frameshift mutations? These mutations should be rare because they are highly disruptive to the functioning of the virus. So, their occurrence may be due to errors rather than a true mutation and it’s good to make a note of this.\n\nAll the variants in our samples had an alternative allele frequency (AF column) greater than 68%, which is a good indication that there were no mixed samples. We did note two samples – CH13 and CH49 – had a frameshift mutation each, in the Spike and ORF1a proteins, respectively. This may be due to a sequencing error, as it is not observed in any other samples. The inclusion of this error may not affect our downstream analysis significantly, but we make a note of these two samples, to check if their lineage assignment and phylogenetic analysis makes sense later on (or whether they appear as outliers, indicating broader sequencing quality issues).\n\n\n\nVariants table output by viralrecon, with a summary of the main columns of interest. Note that this table also includes a column with lineage assignment (not shown in this snapshot). Remember that at this stage we mostly ignore this column, as viralrecon does not use the most up-to-date version of the lineage databases.\n\n\n\n\n8.4.3 Clean FASTA\nThe pipeline outputs the consensus sequences in results/viralrecon/medaka/*.consensus.fasta (one file for each sample). For downstream analysis, it is convenient to combine all these sequences into a single file, and also clean the sequence names (to remove some text – “/ARTIC/medaka MN908947.3” –, which is added by the medaka variant caller).\nWe created a new script to clean our consensus FASTA files, which we ran with bash scripts/02-clean_fasta.sh:\n#!/bin/bash \n\n# combine and clean FASTA files\ncat results/viralrecon/medaka/*.consensus.fasta | sed 's|/ARTIC/medaka MN908947.3||' > report/consensus.fa\nThis command does two things:\n\nCombine all our FASTA consensus sequences into a single file (using cat).\nClean the sequence names (using sed).\n\nThe output was saved as a new FASTA file: report/consensus.fa.\n\n\n8.4.4 Missing Intervals\nAs a further quality check, we also generated a table of missing intervals (indicated by the N character in the FASTA sequences). We used the seqkit software to achieve this.\nFirst, we activate our software environment:\nmamba activate seqkit\nThen, we ran the script bash scripts/03-missing_intervals.sh, which includes the following command:\nseqkit locate -i -P -G -M -r -p \"N+\" report/consensus.fa > results/missing_intervals.tsv\nThis software outputs a tab-delimited table, which we saved as results/missing_intervals.tsv. The table looks like this (only the top few rows are shown):\nseqID patternName pattern strand start end\nCH01 N+ N+ + 1 54\nCH01 N+ N+ + 1193 1264\nCH01 N+ N+ + 4143 4322\nCH01 N+ N+ + 6248 6294\nCH01 N+ N+ + 7561 7561\nCH01 N+ N+ + 9243 9311\nCH01 N+ N+ + 10367 10367\nCH01 N+ N+ + 11361 11370\nCH01 N+ N+ + 13599 13613\nWe opened this file missing_intervals.tsv in Excel and quickly calculated the length of each interval. We noted that two samples – CH07 and CH59 – both have a continuous interval of 5130 missing bases between positions 19580 and 24709. This includes part of the ORF1a gene and nearly all of the S (Spike) gene. This may be due to a poor amplification of one of the PCR amplicons and may affect the interpretation of the results for these two samples. We make a note of these samples as being possibly problematic in downstream analysis steps." + }, + { + "objectID": "materials/03-case_studies/01-switzerland.html#downstream-analyses", + "href": "materials/03-case_studies/01-switzerland.html#downstream-analyses", + "title": "8  Switzerland (Nanopore)", + "section": "8.5 Downstream Analyses", + "text": "8.5 Downstream Analyses\nBased on the clean consensus sequences, we then perform several downstream analysis.\n\n8.5.1 Lineage Assignment\n\n\n\n\n\n\nNote\n\n\n\nSee Section 6.1, if you need to revise how lineage assignment works.\n\n\nAlthough the Viralrecon pipeline runs Pangolin and Nextclade, it does not use the latest version of these programs (because lineages evolve so fast, the nomenclature constantly changes). An up-to-date run of both of these tools can be done using each of their web applications:\n\nclades.nextstrain.org\npangolin.cog-uk.io\n\nHowever, for automation, reproducibility and traceability purposes, we used the command line versions of these tools, and included their analysis in two scripts.\n\nNextcladePangolin\n\n\nFor Nextclade, we first activate the software environment:\nmamba activate nextclade\nAnd then we ran the script bash scripts/04-nextclade.sh, which contains the following commands:\n# get nextclade data\nnextclade dataset get --name sars-cov-2 --output-dir resources/nextclade_background_data\n\n# run nextclade\nnextclade run --input-dataset resources/nextclade_background_data/ --output-all results/nextclade report/consensus.fa\nThe first command downloads the latest version of the Nextclade background data using nextclade dataset. We use that data as input to the second command (nextclade run) to make sure it runs with the most up-to-date lineages.\n\n\nFor Pangolin, we first activate the software environment:\nmamba activate pangolin\nAnd then we ran the script bash/04-pangolin.sh, which contains the following commands:\n# update pangolin data\npangolin --update-data\n\n# run pangolin\npangolin --outdir results/pangolin/ --outfile pango_report.csv report/consensus.fa\nSimilarly to before, we first ran pangolin --update-data to ensure we were using the latest lineages available. We can check the version of the data used with pangolin --all-versions (at the time we ran this we had pangolin-data: 1.23.1).\n\n\n\nBoth of these tools output CSV files, which can be open in Excel for further examination.\nOpening the pangolin results (results/pangolin/pango_report.csv), we noticed that two samples – CH07 and CH59 – failed the QC due to high fraction of missing data. These are the same two samples that had a large gap of missing data in the previous section. Several other samples were classified as “Probable Omicron”, which from the “scorpio_notes” column we can see may be because too many of the expected mutations had missing (ambiguous) bases in those consensus sequences.\nOpening the nextclade results (results/nextclade/nextclade.tsv), we noticed that 23 samples were assigned a QC status of “bad”, mostly due to high percentage of missing data (nextclade uses a stringent threshold of 3000 sites, or ~10%, missing data).\nLike before, we will do further analysis (and visualisation) of these data using the software R, in the section “Integration & Visualisation”, detailed below.\n\n\n8.5.2 Phylogeny\n\n\n\n\n\n\nNote\n\n\n\nSee Section 7.1, if you need to revise how to build phylogenetic trees.\n\n\nAlthough a tool such as Nextclade can place our samples in a global phylogeny context, sometimes it may be convient to build our own phylogenies. This requires three steps:\n\nProducing a multiple sequence alignment from all consensus sequences.\nTree inference.\nTree visualisation and annotation.\n\nBefore our analysis, we first activated our software environment:\nmamba activate phylo\nWe performed the first two steps with the following script, which we ran with bash scripts/05-phylogeny.sh:\n# alignment\nmkdir -p results/mafft\nmafft --6merpair --maxambiguous 0.2 --addfragments report/consensus.fa resources/reference/sarscov2.fa > results/mafft/alignment.fa\n\n# tree inference\nmkdir -p results/iqtree\niqtree2 -s results/mafft/alignment.fa --prefix results/iqtree/consensus\nThe output of iqtree includes a tree file, which can be visualised using FigTree (or online using Microreact). The figure below shows an example of an annotated tree, where we highlight the main VOCs detected. This annotation was done based on the file that we generate in the next section (“Integration & Visualisation”), so those steps would have to be done first.\nWe can see that our samples fall broadly into two large clusters, which correlate with the VOC classification for these samples.\n\n\n\nPhylogenetic tree for our samples. The top cluster (purple) highlights samples classified as Omicron (BA.1-related) and the bottom cluster (pink) samples classified as Delta (AY lineage, also known as B.1.617.2). The tips of the tree are coloured according to Nextclade’s QC status: green = “good”; blue = “mediocre”; red = “bad”.\n\n\n\n\n8.5.3 Clustering\nWe identified groups of similar sequences in our data using the software civet (Cluster Investigation and Virus Epidemiology Tool). This software compares our samples with a background dataset of our choice, which givus us more context for our analysis. In this case we are using the example background data that comes with civet. However, in a real-world analysis, it would have been ideal to choose local samples as background data. For example, we could download samples from Switzerland from around the time period of our sample collection, from GISAID following the instructions on the civet documentation (you need an account on GISAID to obtain these data).\nFor this example, we already prepared civet background dataset saved in resources/civet_background_data.\nBefore our analysis, we first activate our software environment:\nmamba activate civet\nThen, we ran the script bash scripts/06-civet.sh, which contains the following code:\n# run civet analysis\ncivet \\\n -i sample_info.csv \\\n -f report/consensus.fa \\\n -icol sample \\\n -idate collection_date \\\n -d resources/civet_background_data/ \\\n -o results/civet\nThe result of this analysis includes an interactive HTML report (in results/civet/civet.html). We can see that our samples were grouped into 2 catchments, using this background data. This makes sense from our previous lineage/variant analysis: the two catchments correlate with the two main variants in these data (Omicron and Delta).\n\n\n\nExample of results from civet report for catchment 2, showing the phylogeny in the context of the samples in the background data (coloured according to their lineages).\n\n\nCivet also outputs a CSV file (results/civet/master_metadata.csv), which includes the catchment that each sample was assigned to. We will use this CSV file later to integrate this information with other parts of our analysis, in R, detailed in the “Integration & Visualisation” section." + }, + { + "objectID": "materials/03-case_studies/01-switzerland.html#integration-visualisation", + "href": "materials/03-case_studies/01-switzerland.html#integration-visualisation", + "title": "8  Switzerland (Nanopore)", + "section": "8.6 Integration & Visualisation", + "text": "8.6 Integration & Visualisation\nAt this point in our analysis, we have several tables with different pieces of information:\n\nsample_info.csv → the original table with metadata for our samples.\nresults/viralrecon/multiqc/medaka/summary_variants_metrics_mqc.csv → quality metrics from the MultiQC report generated by the viralrecon pipeline.\nresults/nextclade/nextclade.tsv → the results from Nextclade.\nresults/pangolin/pango_report.csv → the results from Pangolin.\nresults/civet/master_metadata.csv → the results from the civet analysis, namely the catchment (or cluster) that each of our samples was grouped into.\n\nTo consolidate our analysis, we tidied and integrated the information from across these different files, into a single table using the software R. The script used to do this is in scripts/07-data_integration.R. Because this is an R script, we opened it in RStudio to execute the code.\nThe output of our script is a new tab-delimited table, which we saved in report/consensus_metrics.tsv, and contains the following columns:\n\nsample → sample ID.\ncollection_date → date of collection day.\ncollection_week → date of collection week (useful for summarising/visualising counts per-week).\ncountry → country of origin.\nlatitude/longitude → latitude and longitude of collection.\nn_mapped_reads → number of mapped reads.\nmedian_depth → median depth.\npct_missing → percentage of missing data.\npct_coverage → percentage of coverage.\nn_variants → number of SNP + indel variants detected.\nnextclade → nextclade clade.\nqc_status → QC status as determined by Nextclade (“bad”, “mediocre”, “good”).\nlineage → Pangolin lineage.\nwho_variant → variant of concern designation.\ncatchment → catchment group from Civet.\n\nThis table, which aggregates information from many of the tools we used, was then used to produce different visualisations of our analysis. These visualisations were also done using the R software (scripts/08-visualisation.R), and integrated into a report, shown below." + }, + { + "objectID": "materials/03-case_studies/01-switzerland.html#bonus-full-workflow", + "href": "materials/03-case_studies/01-switzerland.html#bonus-full-workflow", + "title": "8  Switzerland (Nanopore)", + "section": "8.7 Bonus: Full Workflow", + "text": "8.7 Bonus: Full Workflow\nAlthough we have ran each of the steps of our analysis individually (each in their own script), now that we have everything working, we could integrate all these steps into a single “master” script:\n#!/bin/bash\n\n# make mamba activate command available\neval \"$(conda shell.bash hook)\"\nsource $(mamba info --base)/etc/profile.d/mamba.sh\n\n# make report directory\nmkdir -p report\n\nmamba activate nextflow\n\n# run viralrecon\nnextflow run nf-core/viralrecon \\\n -r 2.6.0 -profile singularity \\\n --max_memory '16.GB' --max_cpus 8 \\\n --platform nanopore \\\n --input samplesheet.csv \\\n --fastq_dir data/fastq_pass/ \\\n --outdir results/viralrecon \\\n --protocol amplicon \\\n --genome 'MN908947.3' \\\n --primer_set artic \\\n --primer_set_version 3 \\\n --artic_minion_caller medaka \\\n --artic_minion_medaka_model r941_min_fast_g303 \\\n --skip_assembly --skip_asciigenome \\\n --skip_pangolin --skip_nextclade\n\n# combine and clean FASTA files\ncat results/viralrecon/medaka/*.consensus.fasta | sed 's|/ARTIC/medaka MN908947.3||' > report/consensus.fa\n\nmamba activate seqkit\n\n# create missing bases TSV file\nseqkit locate -i -P -G -M -r -p \"N+\" report/consensus.fa > results/missing_intervals.tsv\n\nmamba activate nextclade\n\n# get nextclade data\nnextclade dataset get --name sars-cov-2 --output-dir resources/nextclade_background_data\n\n# run nextclade\nnextclade run --input-dataset resources/nextclade_background_data/ --output-all results/nextclade report/consensus.fa\n\nmamba activate pangolin\n\n# update pangolin data\npangolin --update-data\n\n# run pangolin\npangolin --outdir results/pangolin/ --outfile pango_report.csv report/consensus.fa\n\nmamba activate phylo\n\n# alignment\nmkdir -p results/mafft\nmafft --6merpair --maxambiguous 0.2 --addfragments report/consensus.fa resources/reference/sarscov2.fa > results/mafft/alignment.fa\n\n# tree inference\nmkdir -p results/iqtree\niqtree2 -s results/mafft/alignment.fa --prefix results/iqtree/consensus\n\n# data integration and cleaning\nRscript scripts/07-data_integration.R\nNotice that we included the R script that does the data cleaning here, using the Rscript program that allows to execute an R script from the command-line.\nHaving this “master” script, we could run all these steps from start-to-finish with a single command, which can be very useful if you want to fully automate your analysis across multiple runs." + }, + { + "objectID": "materials/03-case_studies/02-southafrica.html#pipeline-overview", + "href": "materials/03-case_studies/02-southafrica.html#pipeline-overview", + "title": "9  South Africa (Illumina)", + "section": "9.1 Pipeline Overview", + "text": "9.1 Pipeline Overview\nOur analysis starts with FASTQ files, which will be used with the nf-core/viralrecon Nextflow pipeline. This will give us several quality control metrics essential for our downstream analysis and reporting.\nCritical files output by the pipeline will need to be further processed, including combining our consensus FASTA files and obtaining a list of filtered SNP/indel variants. Using these clean files, we can then proceed to downstream analysis, which includes assigning each sample to the most up-to-date Pango lineage, Nextclade clade and WHO designation. Finally, we can do more advanced analysis, including the idenfication of sample clusters based on phylogenetic analysis, or produce timeseries visualisations of mutations or variants of concern. With all this information together, we will have the necessary pieces to submit our results to public repositories and write reports to inform public health decisions." + }, + { + "objectID": "materials/03-case_studies/02-southafrica.html#preparing-files", + "href": "materials/03-case_studies/02-southafrica.html#preparing-files", + "title": "9  South Africa (Illumina)", + "section": "9.2 Preparing Files", + "text": "9.2 Preparing Files\nBefore we start our work, it’s always a good idea to setup our directory structure, so we keep our files organised as the analysis progresses. From the data we are starting with, we already have the following directories:\n\ndata → contains the sequencing data in a sub-directory called reads.\nresources → files that were downloaded from public repositories.\nscripts → bash and R scripts used to run the analysis.\n\nWe create two additional directories:\n\nreport → files and documents that we report to our colleagues or upload to public repositories.\nresults → results of the analysis.\n\nYou can create directories from the command line using the mkdir command:\nmkdir results\nmkdir report\n\n9.2.1 Data\nWe start our analysis from FASTQ files generated by the Illumina sequencer. As this is paired-end sequencing, we have two files per sample (with suffix _1 and _2):\nls data/reads\nSRR17051908_1.fastq.gz SRR17051953_1.fastq.gz SRR17461700_1.fastq.gz SRR17712594_1.fastq.gz\nSRR17051908_2.fastq.gz SRR17051953_2.fastq.gz SRR17461700_2.fastq.gz SRR17712594_2.fastq.gz\nSRR17051916_1.fastq.gz SRR17054503_1.fastq.gz SRR17461712_1.fastq.gz SRR17712607_1.fastq.gz\nSRR17051916_2.fastq.gz SRR17054503_2.fastq.gz SRR17461712_2.fastq.gz SRR17712607_2.fastq.gz\nSRR17051923_1.fastq.gz SRR17088917_1.fastq.gz SRR17701832_1.fastq.gz SRR17712711_1.fastq.gz\nSRR17051923_2.fastq.gz SRR17088917_2.fastq.gz SRR17701832_2.fastq.gz SRR17712711_2.fastq.gz\nSRR17051932_1.fastq.gz SRR17088924_1.fastq.gz SRR17701841_1.fastq.gz SRR17712779_1.fastq.gz\nSRR17051932_2.fastq.gz SRR17088924_2.fastq.gz SRR17701841_2.fastq.gz SRR17712779_2.fastq.gz\nSRR17051935_1.fastq.gz SRR17088928_1.fastq.gz SRR17701890_1.fastq.gz SRR17712994_1.fastq.gz\nSRR17051935_2.fastq.gz SRR17088928_2.fastq.gz SRR17701890_2.fastq.gz SRR17712994_2.fastq.gz\nSRR17051951_1.fastq.gz SRR17088930_1.fastq.gz SRR17712442_1.fastq.gz SRR17712997_1.fastq.gz\nSRR17051951_2.fastq.gz SRR17088930_2.fastq.gz SRR17712442_2.fastq.gz SRR17712997_2.fastq.gz\n\n\n9.2.2 Metadata\nMetadata for these samples is available in the file sample_info.csv. Here is some of the information we have available for these samples:\n\nsample → the sample ID.\ncollection_date → the date of collection for the sample in the format YYYY-MM-DD.\ncountry → the country of origin for this sample.\ngeo_loc_region → the region within the country where the sample was collected.\nlatitude/longitude → coordinates for sample location (in this case we’re only given a single coordinate for the whole country - in a real setting you may want to collect a precise location).\nsequencing_instrument → the model for the sequencing instrument used (e.g. NovaSeq 6000, MinION, etc.).\nsequencing_protocol_name → the type of protocol used to prepare the samples (e.g. ARTIC).\namplicon_primer_scheme → for amplicon protocols, what version of the primers was used (e.g. V3, V4.1)" + }, + { + "objectID": "materials/03-case_studies/02-southafrica.html#consensus-assembly", + "href": "materials/03-case_studies/02-southafrica.html#consensus-assembly", + "title": "9  South Africa (Illumina)", + "section": "9.3 Consensus Assembly", + "text": "9.3 Consensus Assembly\n\n\n\n\n\n\nNote\n\n\n\nSee Section 4.1, if you need to revise how the nf-core/viralrecon pipeline works.\n\n\nThe first step in the bioinformatic analysis is to run the nf-core/viralrecon pipeline. But first we need to prepare our input files.\n\n9.3.1 Samplesheet\nFor Illumina data, we need a samplesheet CSV file with three columns, indicating sample name (first column) and the respective FASTQ file paths for read 1 (second column) and read 2 (third column).\nBecause our FASTQ file names are not very user-friendly, we used some command-line tricks to help us produce this table:\n# list read 1 files and save output in a temporary file\nls data/reads/*_1.fastq.gz > read1_filenames.txt\n\n# list read 2 files and save output in a temporary file\nls data/reads/*_2.fastq.gz > read2_filenames.txt\n\n# initiate a file with column names\necho \"fastq_1,fastq_2\" > samplesheet.csv\n\n# paste the two temporary files together, using comma as a delimiter\npaste -d \",\" read1_filenames.txt read2_filenames.txt >> samplesheet.csv\n\n# remove the two temporary files\nrm read1_filenames.txt read2_filenames.txt\nThese commands resulted in creating a file called samplesheet.csv, which contains the following:\nhead samplesheet.csv\nfastq_1,fastq_2\ndata/reads/SRR17051908_1.fastq.gz,data/reads/SRR17051908_2.fastq.gz\ndata/reads/SRR17051916_1.fastq.gz,data/reads/SRR17051916_2.fastq.gz\ndata/reads/SRR17051923_1.fastq.gz,data/reads/SRR17051923_2.fastq.gz\ndata/reads/SRR17051932_1.fastq.gz,data/reads/SRR17051932_2.fastq.gz\ndata/reads/SRR17051935_1.fastq.gz,data/reads/SRR17051935_2.fastq.gz\ndata/reads/SRR17051951_1.fastq.gz,data/reads/SRR17051951_2.fastq.gz\ndata/reads/SRR17051953_1.fastq.gz,data/reads/SRR17051953_2.fastq.gz\ndata/reads/SRR17054503_1.fastq.gz,data/reads/SRR17054503_2.fastq.gz\ndata/reads/SRR17088917_1.fastq.gz,data/reads/SRR17088917_2.fastq.gz\nSo, we programmatically created the last two columns of our file. We then opened this CSV file in Excel to add another column “sample” where we included our sample names and saved the file again as a CSV format. Here are the top few rows of the final file:\nhead samplesheet.csv\nsample,fastq_1,fastq_2\nZA01,data/reads/SRR17051908_1.fastq.gz,data/reads/SRR17051908_2.fastq.gz\nZA02,data/reads/SRR17051923_1.fastq.gz,data/reads/SRR17051923_2.fastq.gz\nZA03,data/reads/SRR17051916_1.fastq.gz,data/reads/SRR17051916_2.fastq.gz\nZA04,data/reads/SRR17051953_1.fastq.gz,data/reads/SRR17051953_2.fastq.gz\nZA05,data/reads/SRR17051951_1.fastq.gz,data/reads/SRR17051951_2.fastq.gz\nZA06,data/reads/SRR17051935_1.fastq.gz,data/reads/SRR17051935_2.fastq.gz\nZA07,data/reads/SRR17051932_1.fastq.gz,data/reads/SRR17051932_2.fastq.gz\nZA08,data/reads/SRR17054503_1.fastq.gz,data/reads/SRR17054503_2.fastq.gz\nZA09,data/reads/SRR17088930_1.fastq.gz,data/reads/SRR17088930_2.fastq.gz\n\n\n9.3.2 Running Viralrecon\nNow we are ready to run the nf-core/viralrecon pipeline (see Section 4.3 for details). We saved our command in a script (scripts/01-run_viralrecon.sh), which we created with the command line text editor nano. This ensures that our analysis is reproducible and traceable (we can go back to the script to see how the analysis was run).\nFirst, we activate our software environment, to ensure Nextflow is available to us:\nmamba activate nextflow\nThen, we run our script with:\nbash scripts/01-run_viralrecon.sh\nWhich will start executing the pipeline.\nFor reference, here is the command included in that script:\nnextflow run nf-core/viralrecon \\\n -r 2.6.0 -profile singularity \\\n --max_memory '15.GB' --max_cpus 8 \\\n --platform illumina \\\n --input samplesheet.csv \\\n --outdir results/viralrecon \\\n --protocol amplicon \\\n --genome 'MN908947.3' \\\n --primer_set artic \\\n --primer_set_version 3 \\\n --skip_assembly --skip_asciigenome \\\n --skip_pangolin --skip_nextclade\n\n\n\n\n\n\nThe \\ in long commands\n\n\n\nIn the command above, you will notice several \\ at the end of each line. This is indicating that we want to continue writing our command in the next line. Notice that the last line does not include \\, because that is the end of the command. This is very useful when commands are very long, because it makes the code more readable.\n\n\nAfter running the pipeline, we notice the following message:\n-[nf-core/viralrecon] 2 samples skipped since they failed Bowtie2 1000 mapped read threshold:\n 201: ZA09\n 176: ZA12\nThis indicates that two samples - ZA09 and ZA12 - had very few reads aligned to the SARS-CoV-2 genome. Could the reason be that they had very few reads to start with? We can quickly investigate this hypothesis by counting the number of lines in the FASTQ files from these samples:\n# FASTQ file for ZA09\nzcat data/reads/SRR17088930_1.fastq.gz | wc -l\n698044\nSince each sequence takes 4 lines in a FASTQ format, this indicates that this sample had 698044/4 = 124511 reads. That’s certainly more than 1000 reads, so the reason the sample failed must be something else. We will investigate this further in the next section." + }, + { + "objectID": "materials/03-case_studies/02-southafrica.html#consensus-quality", + "href": "materials/03-case_studies/02-southafrica.html#consensus-quality", + "title": "9  South Africa (Illumina)", + "section": "9.4 Consensus Quality", + "text": "9.4 Consensus Quality\n\n\n\n\n\n\nNote\n\n\n\nSee Section 5.2, if you need to revise how to assess the quality of consensus sequences.\n\n\n\n9.4.1 General Metrics\nWe used the MultiqQC report to assess the initial quality of our samples. The quality report can be found in results/viralrecon/multiqc/multiqc_report.html.\nWe paid particular attention to:\n\nNumber of reads mapped to the reference genome.\nMedian depth of coverage.\nPercentage of the genome with missing bases (‘N’).\nNumber of SNP + Indel variants.\n\n\n\n\nExample MultiQC output, highlighting the main columns with quality information that will be collected for the final report. Note that some columns have been ommited here for clarity.\n\n\nWe noted that:\n\n2 samples - ZA09 and ZA12 - completely failed. This was already noted after we ran the pipeline, above. The reason seems to be because these samples have a very low percentage of non-human reads (less than 2%), indicating that the sequenced material was mostly human. Because of this, there were not enough reads to assemble a genome.\n2 other samples - ZA10 and ZA23 - had > 90% missing bases, also indicating a very poor assembly. The reason was the same as above, both samples had low % of non-human reads.\n1 samples - ZA14 - had ~39% missing bases. In this case the reason was not a low number of mapped reads. For example, sample ZA13 had a very similar median depth of coverage (121 vs 161 in ZA14) and even fewer mapped reads (24k vs 60k in ZA14). But ZA13 only had 2% missing bases. However, upon inspection of the amplicon heatmap, we detected that several amplicons were not properly amplified in ZA14 compared to ZA13. Therefore, the reason for high % missing bases in ZA14 was due to low amplification efficiency in this sample.\nThere was some systematic dropout for some amplicons, in particular nCoV-2019_64 had very low amplification in several of the samples. Of note was also nCoV-2019_73, and other neighbouring amplicons.\n\n\n\n\nAmplicon depth of sequencing (or coverage) across samples, from the MultiQC report. Darker regions indicate systematic amplicon dropout, probably due to issues during the PCR amplification.\n\n\nBesides the MultiQC report, the pipeline also outputs a CSV file with collected summary metrics (equivalent to the first table on the report): results/viralrecon/multiqc/summary_variants_metrics_mqc.csv. We will use this file later to join this information with our metadata and lineage assignment using the R software (detailed in “Integration & Visualisation” section, below).\n\n\n9.4.2 Variants\nWe also looked at the table of variants obtained from the pipeline. This is output in results/viralrecon/variants/ivar/variants_long_table.csv. This table can be very useful to keep track of particular mutations that may be increasing over time. Later, we will tidy this table to attach to our reported results (“Integration & Visualisation” section).\nBut for now, we will explore this table to address a few more quality-related questions. We opened this table in Excel to answer the following:\n\nWhere there samples with a high number of intermediate allele frequencies? This could indicate mixed samples due to cross-contamination.\n\nWhere there samples with frameshift mutations? These mutations should be rare because they are highly disruptive to the functioning of the virus. So, their occurrence may be due to errors rather than a true mutation and it’s good to make a note of this.\n\nBy manual inspection of this table (and using the “filter” feature in Excel), we found 74 variants with alternative allele frequency less than 75%. These were spread across samples, all samples having less than 10 such low-frequency mutations, and most samples having less than 5. Sample ZA23 - which we had previously highlighted has having a high % missing bases (39%) - had 8 low-frequency mutations out of a total of 48 mutations in this sample (~16%), suggesting further quality issues in this sample.\n\n\n\nVariants table output by viralrecon, with a summary of the main columns of interest. Note that this table also includes a column with lineage assignment (not shown in this snapshot). Remember that at this stage we mostly ignore this column, as viralrecon does not use the most up-to-date version of the lineage databases." + }, + { + "objectID": "materials/03-case_studies/02-southafrica.html#clean-fasta", + "href": "materials/03-case_studies/02-southafrica.html#clean-fasta", + "title": "9  South Africa (Illumina)", + "section": "9.5 Clean FASTA", + "text": "9.5 Clean FASTA\nThe pipeline outputs the consensus sequences in results/viralrecon/variants/ivar/consensus/bcftools/*.consensus.fa (one file for each sample). For downstream analysis, it is convenient to combine all these sequences into a single file, and also clean the sequence names (to remove some text – ” MN908947.3” –, which is added by the bcftools variant caller).\nWe created a new script to clean our consensus FASTA files, which we ran with bash scripts/02-clean_fasta.sh:\n\n# combine and clean FASTA files\ncat results/viralrecon/variants/ivar/consensus/bcftools/*.consensus.fa | sed 's| MN908947.3||' > report/consensus.fa\nThis command does two things:\n\nCombine all our FASTA consensus sequences into a single file (using cat).\nClean the sequence names (using sed).\n\nThe output was saved as a new FASTA file: report/consensus.fa.\n\n\n9.5.1 Missing Intervals\nAs a further quality check, we also generated a table of missing intervals (indicated by the N character in the FASTA sequences). We used the seqkit software to achieve this.\nFirst, we activate our software environment:\nmamba activate seqkit\nThen, we ran the script bash scripts/03-missing_intervals.sh, which includes the following command:\nseqkit locate -i -P -G -M -r -p \"N+\" report/consensus.fa > results/missing_intervals.tsv\nThis software outputs a tab-delimited table, which we saved as results/missing_intervals.tsv. The table looks like this (only the top few rows are shown):\nseqID patternName pattern strand start end\nZA01 N+ N+ + 1 54\nZA01 N+ N+ + 22771 22926\nZA01 N+ N+ + 23603 23835\nZA01 N+ N+ + 26948 26948\nZA01 N+ N+ + 26968 27137\nZA01 N+ N+ + 29801 29867\nZA02 N+ N+ + 1 54\nZA02 N+ N+ + 22771 22921\nZA02 N+ N+ + 23603 23835\nWe opened this file missing_intervals.tsv in Excel and quickly calculated the length of each interval. We noted that two samples - ZA10 and ZA23 - both have a continuous interval of over 18kb missing bases, which is not surprising as we had already identified these samples has having >90% missing data. We make a note of these samples as being possibly problematic in downstream analysis steps." + }, + { + "objectID": "materials/03-case_studies/02-southafrica.html#downstream-analyses", + "href": "materials/03-case_studies/02-southafrica.html#downstream-analyses", + "title": "9  South Africa (Illumina)", + "section": "9.6 Downstream Analyses", + "text": "9.6 Downstream Analyses\nBased on the clean consensus sequences, we then perform several downstream analysis.\n\n9.6.1 Lineage Assignment\n\n\n\n\n\n\nNote\n\n\n\nSee Section 6.1, if you need to revise how lineage assignment works.\n\n\nAlthough the Viralrecon pipeline runs Pangolin and Nextclade, it does not use the latest version of these programs (because lineages evolve so fast, the nomenclature constantly changes). An up-to-date run of both of these tools can be done using each of their web applications:\n\nclades.nextstrain.org\npangolin.cog-uk.io\n\nHowever, for automation, reproducibility and traceability purposes, we used the command line versions of these tools, and included their analysis in two scripts.\n\nNextcladePangolin\n\n\nFor Nextclade, we first activate the software environment:\nmamba activate nextclade\nAnd then we ran the script bash scripts/04-nextclade.sh, which contains the following commands:\n# get nextclade data\nnextclade dataset get --name sars-cov-2 --output-dir resources/nextclade_background_data\n\n# run nextclade\nnextclade run --input-dataset resources/nextclade_background_data/ --output-all results/nextclade report/consensus.fa\nThe first command downloads the latest version of the Nextclade background data using nextclade dataset. We use that data as input to the second command (nextclade run) to make sure it runs with the most up-to-date lineages.\n\n\nFor Pangolin, we first activate the software environment:\nmamba activate pangolin\nAnd then we ran the script bash/04-pangolin.sh, which contains the following commands:\n# update pangolin data\npangolin --update-data\n\n# run pangolin\npangolin --outdir results/pangolin/ --outfile pango_report.csv report/consensus.fa\nSimilarly to before, we first ran pangolin --update-data to ensure we were using the latest lineages available. We can check the version of the data used with pangolin --all-versions (at the time we ran this we had pangolin-data: 1.23.1).\n\n\n\nBoth of these tools output CSV files, which can be open in Excel for further examination.\nOpening the pangolin results (results/pangolin/pango_report.csv), we noticed that the two problematic samples – ZA10 and ZA23 – failed the QC due to high fraction of missing data. The other samples all seemed to have been assigned to “Omicron” variant with a high support.\nOpening the nextclade results (results/nextclade/nextclade.tsv), we noticed that the two problematic samples were classified as “recombinant”! We know from our quality control that we should not trust this assessment, and that most likely these are bad quality samples, not true recombinant lineages. Nextclade is more relaxed in assigning samples to lineages, so we should always check the QC status as well. We will notice that both of these samples were assigned QC status “bad”, due to their high percentage of missing data (nextclade uses a stringent threshold of 3000 sites, or ~10%, missing data). Two other samples had “bad” QC status. Sample ZA14 due to high % of missing data, and sample ZA18 from a mixture of a high number of private mutations and the presence of a frameshift mutation in ORF1b.\nLike before, we will do further analysis (and visualisation) of these data using the software R, in the section “Integration & Visualisation”, detailed below.\n\n\n9.6.2 Phylogeny\n\n\n\n\n\n\nNote\n\n\n\nSee Section 7.1, if you need to revise how to build phylogenetic trees.\n\n\nAlthough a tool such as Nextclade can place our samples in a global phylogeny context, sometimes it may be convient to build our own phylogenies. This requires three steps:\n\nProducing a multiple sequence alignment from all consensus sequences.\nTree inference.\nTree visualisation and annotation.\n\nBefore our analysis, we first activated our software environment:\nmamba activate phylo\nWe performed the first two steps with the following script, which we ran with bash scripts/05-phylogeny.sh:\n# alignment\nmkdir -p results/mafft\nmafft --6merpair --maxambiguous 0.2 --addfragments report/consensus.fa resources/reference/sarscov2.fa > results/mafft/alignment.fa\n\n# tree inference\nmkdir -p results/iqtree\niqtree2 -s results/mafft/alignment.fa --prefix results/iqtree/consensus\nThe output of iqtree includes a tree file, which can be visualised using FigTree (or online using Microreact). The figure below shows our tree, which shows all our samples fall mostly clustering together. This makes sense, as all our samples were classified as “Omicron (BA.1-related)”.\nIt is worth noting that the samples ZA10, ZA14 and ZA23 are not included in this phylogeny, as they contained >20% missing data (we used that threshold with MAFFT alignment, option --maxambiguous 0.2). Also, note that sample ZA18, which Nextclade had identified as “bad” QC (due to excess private mutations) also appears slightly separated from the other samples in the tree (the sample in red on the tree). The sample still clusters well with the rest, suggesting we can probably trust that it is indeed an Omicron variant, however the excess of private mutations (which may be due to sequencing errors) is making it stand apart from the others in the phylogeny.\n\n\n\nPhylogenetic tree for our samples. All samples were classified as lineage BA.1 (and some of its sub-lineages), corresponding to the Omicron VOC. The tips of the tree are coloured according to Nextclade’s QC status: green = “good”; blue = “mediocre”; red = “bad”.\n\n\n\n\n9.6.3 Clustering\nWe identified groups of similar sequences in our data using the software civet (Cluster Investigation and Virus Epidemiology Tool). This software compares our samples with a background dataset of our choice, which givus us more context for our analysis. In this case we are using the example background data that comes with civet. However, in a real-world analysis, it would have been ideal to choose local samples as background data. For example, we could download samples from South Africa from around the time period of our sample collection, from GISAID following the instructions on the civet documentation (you need an account on GISAID to obtain these data).\nFor this example, we already prepared civet background dataset saved in resources/civet_background_data.\nBefore our analysis, we first activate our software environment:\nmamba activate civet\nThen, we ran the script bash scripts/06-civet.sh, which contains the following code:\n# run civet analysis\ncivet \\\n -i sample_info.csv \\\n -f report/consensus.fa \\\n -icol sample \\\n -idate collection_date \\\n -d resources/civet_background_data/ \\\n -o results/civet\nThe result of this analysis includes an interactive HTML report (in results/civet/civet.html). We can see that our samples were grouped into a single catchment. This makes sense from our previous lineage/variant analysis: all our samples were classfied as Omicron VOC. We can see that our samples seem to be more diverged from the Australian BA.1 sample present in the background data, with new SNPs in our samples creating a longer branch in the tree.\n\n\n\nResults from civet report for catchment 1, showing the phylogeny in the context of a BA.1 Australian sample from the background data.\n\n\nCivet also outputs a CSV file (results/civet/master_metadata.csv), which includes the catchment that each sample was assigned to. We will use this CSV file later to integrate this information with other parts of our analysis, in R, detailed in the “Integration & Visualisation” section." + }, + { + "objectID": "materials/03-case_studies/02-southafrica.html#integration-visualisation", + "href": "materials/03-case_studies/02-southafrica.html#integration-visualisation", + "title": "9  South Africa (Illumina)", + "section": "9.7 Integration & Visualisation", + "text": "9.7 Integration & Visualisation\nAt this point in our analysis, we have several tables with different pieces of information:\n\nsample_info.csv → the original table with metadata for our samples.\nresults/viralrecon/multiqc/medaka/summary_variants_metrics_mqc.csv → quality metrics from the MultiQC report generated by the viralrecon pipeline.\nresults/nextclade/nextclade.tsv → the results from Nextclade.\nresults/pangolin/pango_report.csv → the results from Pangolin.\nresults/civet/master_metadata.csv → the results from the civet analysis, namely the catchment (or cluster) that each of our samples was grouped into.\n\nTo consolidate our analysis, we tidied and integrated the information from across these different files, into a single table using the software R. The script used to do this is in scripts/07-data_integration.R. Because this is an R script, we opened it in RStudio to execute the code.\nThe output of our script is a new tab-delimited table, which we saved in report/consensus_metrics.tsv, and contains the following columns:\n\nsample → sample ID.\ncollection_date → date of collection day.\ncollection_week → date of collection week (useful for summarising/visualising counts per-week).\ncountry → country of origin.\nlatitude/longitude → latitude and longitude of collection.\nn_mapped_reads → number of mapped reads.\nmedian_depth → median depth.\npct_missing → percentage of missing data.\npct_coverage → percentage of coverage.\nn_variants → number of SNP + indel variants detected.\nnextclade → nextclade clade.\nqc_status → QC status as determined by Nextclade (“bad”, “mediocre”, “good”).\nlineage → Pangolin lineage.\nwho_variant → variant of concern designation.\ncatchment → catchment group from Civet.\n\nThis table, which aggregates information from many of the tools we used, was then used to produce different visualisations of our analysis. These visualisations were also done using the R software (scripts/08-visualisation.R), and integrated into a report, shown below." + }, + { + "objectID": "materials/03-case_studies/02-southafrica.html#bonus-full-workflow", + "href": "materials/03-case_studies/02-southafrica.html#bonus-full-workflow", + "title": "9  South Africa (Illumina)", + "section": "9.8 Bonus: Full Workflow", + "text": "9.8 Bonus: Full Workflow\nAlthough we have ran each of the steps of our analysis individually (each in their own script), now that we have everything working, we could integrate all these steps into a single “master” script:\n#!/bin/bash\n\n# make mamba activate command available\neval \"$(conda shell.bash hook)\"\nsource $(mamba info --base)/etc/profile.d/mamba.sh\n\n# make report directory\nmkdir -p report\n\nmamba activate nextflow\n# run viralrecon\nnextflow run nf-core/viralrecon \\\n -r 2.6.0 -profile singularity \\\n --max_memory '15.GB' --max_cpus 8 \\\n --platform illumina \\\n --input samplesheet.csv \\\n --outdir results/viralrecon \\\n --protocol amplicon \\\n --genome 'MN908947.3' \\\n --primer_set artic \\\n --primer_set_version 3 \\\n --skip_assembly --skip_asciigenome \\\n --skip_pangolin --skip_nextclade\n\n# combine and clean FASTA files\ncat results/viralrecon/variants/ivar/consensus/bcftools/*.consensus.fa | sed 's| MN908947.3||' > report/consensus.fa\n\nmamba activate seqkit\n# create missing bases TSV file\nseqkit locate -i -P -G -M -r -p \"N+\" report/consensus.fa > results/missing_intervals.tsv\n\nmamba activate nextclade\n# get nextclade data\nnextclade dataset get --name sars-cov-2 --output-dir resources/nextclade_background_data\n\n# run nextclade\nnextclade run --input-dataset resources/nextclade_background_data/ --output-all results/nextclade report/consensus.fa\n\nmamba activate pangolin\n# update pangolin data\npangolin --update-data\n\n# run pangolin\npangolin --outdir results/pangolin/ --outfile pango_report.csv report/consensus.fa\n\nmamba activate phylo\n# alignment\nmkdir -p results/mafft\nmafft --6merpair --maxambiguous 0.2 --addfragments report/consensus.fa resources/reference/sarscov2.fa > results/mafft/alignment.fa\n\n# tree inference\nmkdir -p results/iqtree\niqtree2 -s results/mafft/alignment.fa --prefix results/iqtree/consensus\n\n# data integration and cleaning\nRscript scripts/07-data_integration.R\nNotice that we included the R script that does the data cleaning here, using the Rscript program that allows to execute an R script from the command-line.\nHaving this “master” script, we could run all these steps from start-to-finish with a single command, which can be very useful if you want to fully automate your analysis across multiple runs." + }, + { + "objectID": "materials/03-case_studies/03-eqa.html#pipeline-overview", + "href": "materials/03-case_studies/03-eqa.html#pipeline-overview", + "title": "10  EQA (Exercise)", + "section": "10.1 Pipeline Overview", + "text": "10.1 Pipeline Overview\nWe will start our analysis with FASTQ files produced by our sequencing platforms (Illumina and Nanopore are considered here). These FASTQ files will be used with the nf-core/viralrecon Nextflow pipeline, allowing us to automate the generation of consensus sequences and produce several quality control metrics essential for our downstream analysis and reporting.\nCritical files output by the pipeline will need to be further processed, including combining and cleaning our consensus FASTA files. Using these clean files, we can then proceed to downstream analysis, which includes assigning each sample to the most up-to-date Pango lineage, Nextclade clade and WHO designation. Finally, we can do more advanced analysis, including the idenfication of sample clusters based on phylogenetic analysis, or produce timeseries visualisations of variants of concern. With all this information together, we will have the necessary pieces to submit our results to public repositories and write reports to inform public health decisions." + }, + { + "objectID": "materials/03-case_studies/03-eqa.html#preparing-files", + "href": "materials/03-case_studies/03-eqa.html#preparing-files", + "title": "10  EQA (Exercise)", + "section": "10.2 Preparing Files", + "text": "10.2 Preparing Files\nBefore we start our work, it’s always a good idea to setup our directory structure, so we keep our files organised as the analysis progresses. There is no standard way to do this, but here are some suggested directories:\n\ndata → contains the sequencing data for the particular run or project you are working on. Data may sometimes be stored in a separate server, in which case you may not need to create this directory. Generally, you should leave the original data unmodified, in case something goes wrong during your analysis, you can always re-run it from the start.\nresults → to save results of the analysis.\nscripts → to save scripts used to run the analysis. You should always try to save all the commands you run in a script, this way you will have a record of what was done to produce the results, and it makes your life easier if you want to run the analysis again on a new set of data.\nreport → to save the final files and documents that we report to our colleagues or upload to public repositories.\nresources → files that you download from the internet could be saved here. For example, the reference genome sequence, background data used for phylogenetics, etc. Sometimes you may share these resources across multiple projects, in which case you could have this folder somewhere else that you can access across multiple projects.\n\nOn our computers, we have a directory in ~/Documents/eqa_workshop, where we will do all our analysis. We already include the following:\n\ndata → with the results of the EQA sample sequencing.\nresources → where we include the SARS-CoV-2 reference genome, and some background datasets that will be used with some of the tools we will cover.\nscripts → where we include some scripts that we will use towards the end of the workshop. You should also create several scripts during the workshop, which you will save here.\nsample_info.csv → a table with some metadata for our samples.\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nYour first task is to create two new directories in the project folder called report and results. You can do this either using the file explorer or from the command line (using the mkdir command).\n\n\n\n\n\n\n10.2.1 Data\nRegardless of which platform you used to sequence your samples, the analysis starts with FASTQ files (if you need a reminder of what a FASTQ file is, look at the Introduction to NGS section). However, the organisation of these files is slightly different depending on the platform, and is detailed below.\n\nNanoporeIllumina\n\n\nTypically, Nanopore data is converted to FASTQ format using the program Guppy. This software outputs the files to a directory called fastq_pass. Within this directory, it creates further sub-directories for each sample barcode, which are named barcodeXX (XX is the barcode number). Finally, within each barcode directory there is one (or sometimes more) FASTQ files corresponding to that sample.\nYou can look at the files you have available from the command line using:\nls data/fastq_pass\n\n\nThe Illumina files come as compressed FASTQ files (.fq.gz format) and there’s two files per sample, corresponding to read 1 and read 2. This is indicated by the file name suffix:\n\n*_1.fq.gz for read 1\n*_2.fq.gz for read 2\n\nYou can look at the files you have available from the command line using:\nls data/\n\n\n\n\n\n10.2.2 Metadata\nA critical step in any analysis is to make sure that our samples have all the relevant metadata associated with them. This is important to make sense of our results and produce informative reports at the end. There are many types of information that can be collected from each sample (revise the Genomic Surveillance > Metadata section of the materials to learn more about this). For effective genomic surveillance, we need at the very minimum three pieces of information:\n\nWhen: date when the sample was collected (not when it was sequenced!).\nWhere: the location where the sample was collected (not where it was sequenced!).\nHow: how the sample was sequenced (sequencing platform and protocol used).\n\nOf course, this is the minimum metadata we need for a useful analysis. However, the more information you collect about each sample, the more questions you can ask from your data – so always remember to record as much information as possible for each sample.\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nNote: If you are using our pre-sequenced data, you can skip this exercise.\nWe already provide some basic metadata for these samples in the file sample_info.csv:\n\nsample → the sample ID.\ncollection_date → the date of collection for the sample in the format YYYY-MM-DD.\ncountry → the country of origin for this sample.\nexpected_lineage and expected_voc → because we are using the EQA panel, we know what lineage and variant-of-concern (VOC) each sample belongs to. We will use this later to assess the quality of our analysis.\n\nThere is however some information missing from this table, which you may have available at this point. Open this file in Excel and create the following columns:\n\nct → Ct value from qPCR viral load quantification.\nsequencing_instrument → the model for the sequencing instrument used (e.g. NovaSeq 6000, MinION, etc.).\nsequencing_protocol_name → the type of protocol used to prepare the samples (e.g. ARTIC).\namplicon_primer_scheme → for amplicon protocols, what version of the primers was used (e.g. V3, V4.1)\nSpecific columns for Oxford Nanopore data, which are essential for the bioinformatic analysis:\n\nont_nanopore → the version of the pores used (e.g. 9.4.1 or 10.4.1).\nont_guppy_version → the version of the Guppy software used for basecalling.\nont_guppy_mode → the basecalling mode used with Guppy (usually “fast”, “high”, “sup” or “hac”).\n\n\nThis will ensure that in the future people have sufficient information to re-run the analysis on your data.\n\n\n\n\n\n\n\n\n\n\n\nDates in Spreadsheet Programs\n\n\n\nNote that programs such as Excel often convert date columns to their own format, and this can cause problems when analysing data later on. For example, GISAID wants dates in the format YYYY-MM-DD, but by default Excel displays dates as DD/MM/YYYY.\nYou can change how Excel displays dates by highlighting the date column, right-clicking and selecting Format cells, then select “Date” and pick the format that matches YYYY-MM-DD. However, every time you open the CSV file, Excel annoyingly converts it back to its default format!\nTo make sure no date information is lost due to Excel’s behaviour, it’s a good idea to store information about year, month and day in separate columns (stored just as regular numbers)." + }, + { + "objectID": "materials/03-case_studies/03-eqa.html#consensus-assembly", + "href": "materials/03-case_studies/03-eqa.html#consensus-assembly", + "title": "10  EQA (Exercise)", + "section": "10.3 Consensus Assembly", + "text": "10.3 Consensus Assembly\nAt this point we are ready to start our analysis with the first step: generating a consensus genome for our samples. We will use a standardised pipeline called viralrecon, which automates most of this process for us, helping us be more efficient and reproducible in our analysis.\n\n\n\n\n\n\nNote\n\n\n\nSee Section 4.1, if you need to revise how the nf-core/viralrecon pipeline works.\n\n\n\n10.3.1 Samplesheet\nThe first step in this process is to prepare a CSV file with information about our sequencing files, which will be used as the input to the viralrecon pipeline.\nThe pipeline’s documentation gives details about the format of this samplesheet, depending on whether you are working with Illumina or Nanopore data.\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nNote: If you are using our pre-sequenced data, you can skip this exercise.\nUsing Excel, produce the input samplesheet for nf-core/viralrecon, making sure that you save it as a CSV file (File → Save As… and choose “CSV” as the file format).\nNote: make sure that the sample names you use in this samplesheet match those in the metadata sample_info.csv file (pay attention to things like spaces, uppercase/lowercase, etc. – make sure the names used match exactly).\nIf you are working with Illumina data, you should check the tip below.\n\n\nIllumina samplesheet: saving time with the command line!\n\nYou can save some time (and a lot of typing!) in making the Illumina samplesheet using the command line to get a list of file paths. For example, if your files are saved in a folder called data, you could do:\n# list read 1 files and save output in a temporary file\nls data/*_1.fq.gz > read1_filenames.txt\n\n# list read 2 files and save output in a temporary file\nls data/*_2.fq.gz > read2_filenames.txt\n\n# initiate a file with column names\necho \"fastq_1,fastq_2\" > samplesheet.csv\n\n# paste the two temporary files together, using comma as a delimiter\npaste -d \",\" read1_filenames.txt read2_filenames.txt >> samplesheet.csv\n\n# remove the two temporary files\nrm read1_filenames.txt read2_filenames.txt\nNow, you can open this file in Excel and continue editing it to add a new column of sample names.\n\n\n\n\n\n\n\n\n10.3.2 Running Viralrecon\nThe next step in our analysis is to run the nf-core/viralrecon pipeline. The way the command is structured depends on which kind of data we are working with. There are many options that can be used to customise the pipeline, but typical commands are shown below for each platform.\n\nNanoporeIllumina\n\n\nnextflow run nf-core/viralrecon \\\n -r 2.6.0 -profile singularity \\\n --max_memory '15.GB' --max_cpus 4 \\\n --platform nanopore \\\n --input SAMPLESHEET_CSV \\\n --fastq_dir data/fastq_pass/ \\\n --outdir results/viralrecon \\\n --protocol amplicon \\\n --genome 'MN908947.3' \\\n --primer_set artic \\\n --primer_set_version PRIMER_VERSION \\\n --artic_minion_caller medaka \\\n --artic_minion_medaka_model MEDAKA_MODEL \\\n --skip_assembly --skip_asciigenome \\\n --skip_pangolin --skip_nextclade\nYou need to check which model the medaka software should use to process the data. You need three pieces of information to determine this:\n\nThe version of the nanopores used (usually 9.4.1 or 10.4.1).\nThe sequencing device model (MinION, GridION or PromethION).\nThe mode used in the Guppy software for basecalling (“fast”, “high”, “sup” or “hac”).\nThe version of the Guppy software.\n\nOnce you have these pieces of information, you can see how the model is specified based on the model files available on the medaka GitHub repository. For example, if you used a flowcell with chemistry 9.4.1, sequenced on a MinION using the “fast” algorithm on Guppy version 5.0.7, the model used should be r941_min_fast_g507.\nNote that in some cases there is no model for recent versions of Guppy, in which case you use the version for the latest version available. In our example, if our version of Guppy was 6.1.5 we would use the same model above, since that’s the most recent one available.\n\n\nnextflow run nf-core/viralrecon \\\n -r 2.6.0 -profile singularity \\\n --max_memory '15.GB' --max_cpus 4 \\\n --platform illumina \\\n --input SAMPLESHEET_CSV \\\n --outdir results/viralrecon \\\n --protocol amplicon \\\n --genome 'MN908947.3' \\\n --primer_set artic \\\n --primer_set_version PRIMER_VERSION \\\n --skip_assembly --skip_asciigenome \\\n --skip_pangolin --skip_nextclade\n\n\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nYour next task is to run the pipeline on your data. However, rather than run the command directly from the command line, let’s save it in a shell script – for reproducibility and as a form of documenting our analysis.\n\nFirst, open the README.txt file found in your folder, which has some details about your samples, including the amplicon primer scheme used.\nUsing a text editor, create a new shell script and save it in scripts/01-run_viralrecon.sh. You can either use the command-line text editor nano or use Gedit, which comes installed with Ubuntu.\nCopy the command shown above (either Illumina or Nanopore, depending on your data) to your new script.\nAdjust the code, to use the correct input samplesheet file, primer scheme and, in the case of ONT, the medaka model. For information about primer scheme options see the “Amplicon Primer Schemes” appendix.\nActivate the software environment to use Nextflow: mamba activate nextflow.\nSave the script and run it from the command line using bash scripts/01-run_viralrecon.sh.\n\nIf you need a reminder of how to work with shell scripts, revise the Shell Scripts section of the accompanying Unix materials.\n\n\n\n\n\n\n\n\n\n\n\nMaximum Memory and CPUs\n\n\n\nIn our Nextflow command above we have set --max_memory '15.GB' --max_cpus 8 to limit the resources used in the analysis. This is suitable for the computers we are using in this workshop. However, make sure to set these options to the maximum resources available on the computer where you process your data." + }, + { + "objectID": "materials/03-case_studies/03-eqa.html#consensus-quality", + "href": "materials/03-case_studies/03-eqa.html#consensus-quality", + "title": "10  EQA (Exercise)", + "section": "10.4 Consensus Quality", + "text": "10.4 Consensus Quality\nOnce your workflow is complete, it’s time to assess the quality of the assembly.\n\n\n\n\n\n\nNote\n\n\n\nSee Section 5.2, if you need to revise how to assess the quality of consensus sequences.\n\n\n\n10.4.1 Coverage\nAt this stage we want to identify issues such as:\n\nAny samples which have critically low coverage. There is no defined threshold, but samples with less than 85% coverage should be considered carefully.\nAny problematic regions that systematically did not get amplified (amplicon dropout).\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nTo assess the quality of our assemblies, we can use the MultiQC report generated by the pipeline, which compiles several pieces of information about our samples. If you need a reminder of where to find this file, consult the “Consensus assembly” section of the materials, or the Viralrecon output documentation.\nOpen the quality report and try to answer the following questions:\n\nWere there any samples with more than 15% missing bases (’N’s)?\nWere there any samples with a median depth of coverage <20x?\nWere there any problematic amplicons with low depth of coverage across multiple samples?\n\nMake a note of any samples that you think are problematic. You can discuss with your colleagues and compare your results/conclusions to see if you reach similar conclusions.\n\n\n\n\n\n\n\n10.4.2 Variants\nThe viralrecon pipeline outputs a table with information about SNP/Indel variants as a CSV file named variants_long_table.csv. It is important to inspect the results of this file, to identify any mutations with severe effects on annotated proteins, or identify samples with an abnormal high number of “mixed” bases.\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nOpen the variants_long_table.csv file and answer the following questions. If you need a reminder of where to find this file, consult the “Consensus assembly” section of the materials, or the Viralrecon output documentation.\n\nHow many variants have allele frequency < 75%?\nDoes any of the samples have a particularly high number of these low-frequency variants, compared to other samples? (This could indicate cross-contamination of samples)\nInvestigate if there are any samples with frameshift mutations. These mutations should be rare because they are highly disruptive to the functioning of the virus. So, their occurrence may be due to errors rather than a true mutation and it’s good to make a note of this.\n\nIf you need a reminder of the meaning of the columns in this file, consult the Consensus > Mutation/Variant Analysis section of the materials.\nMake a note of any samples that you think may be problematic, either because they have a high number of low-frequency variants or because they have frameshift mutations.\n(Optional)\nIf you identify samples with frameshift mutations, open the BAM file of the sample using the software IGV. Go to the position where the mutation is located, and try to see if there is evidence that this mutation is an error (for example, if it’s near an homopolymer).\n\n\n\n\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nThe samples we are using in this analysis come from a standard EQA panel. As such, we already know what lineages we expect to find in these samples.\n\nOpen IGV and load one (or more) of the alignment BAM files into it. If you need a reminder of where to find this file, consult the “Consensus assembly” section of the materials, or the Viralrecon output documentation.\nOpen your metadata table (sample_info.csv) and check which lineages your loaded samples belong to.\nDo a web search to find what mutations characterise those lineages.\nOn IGV, go to the location of some of the expected mutations, and see if you can see it in the respective samples.\n\n\n\n\n\n\n\n\n10.4.3 Clean FASTA\nThe viralrecon pipeline outputs each of our consensus sequences as individual FASTA files for each sample (look at the FASTA Files section if you need a reminder of what these files are). However, by default, the sample names in this file have extra information added to them, which makes some downstream analysis less friendly (because the names will be too long and complicated).\nTo clean up these files, we will do two things:\n\nCombine all the individual FASTA files together.\nRemove the extra text from the sequence names.\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nWe will use the programs cat (concatenate) and sed (text replacement) to clean our FASTA files. Consider the commands given below:\n\nNanoporeIllumina\n\n\ncat <INPUT> | sed 's|/ARTIC/medaka MN908947.3||' > report/consensus.fa\n\n\ncat <INPUT> | sed 's| MN908947.3||' > report/consensus.fa\n\n\n\nThe sed command shown is used to remove the extra pieces of text added by the pipeline to each sample name. This is a slightly more advanced program to use, so we already give you the code. If you want to learn more about it, check the “Text Replacement” section of the accompanying Unix materials.\nCopy the command above to a new shell script called scripts/02-clean_fasta.sh. Fix the code by replacing <INPUT> with the path to all the FASTA files generated by the pipeline (remember you can use the * wildcard).\nIf you need a reminder of where to find the FASTA consensus files from viralrecon, consult the “Consensus assembly” section of the materials, or the Viralrecon output documentation.\nOnce you fixed the script, run it with bash and check that the output file is generated.\nBonus:\nCheck that you have all expected sequences in your FASTA file using grep to find the lines of the file with > (which is used to indicate sample names).\n\n\n\n\n\n\n\n10.4.4 Missing Intervals\nWhen we generate our consensus assembly, there are regions for which we did not have enough information (e.g. due to amplicon dropout) or where there were mixed bases. These regions are missing data, and are denoted as ‘N’ in our sequences. Although we already determined the percentage of each sequence that is missing from the MultiQC report, we don’t have the intervals of missing data, which can be a useful quality metric to have. In particular, we may want to know what is the largest continuous stretch of missing data in each sample.\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nWe will use the software tool seqkit to find intervals where the ‘N’ character occurs. This tool has several functions available, one of which is called locate.\nHere is the command that would be used to locate intervals containing one or more ‘N’ characters:\nseqkit locate -i -P -G -M -r -p \"N+\" report/consensus.fa\nThe meaning of the options is detailed in seqkit’s documentation.\n\nCreate a new shell script called scripts/03-missing_intervals.sh.\nCopy the command shown above to the script and modify it to redirect the output (using >) to a file called results/missing_intervals.tsv.\nActivate the software environment: mamba activate seqkit.\nRun the script you created using bash.\n\n\n\n\n\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nOpen the file you created in the previous step (results/missing_intervals.tsv) in a spreadsheet program. Create a new column with the length of each interval (end - start + 1).\nNote if any missing intervals are larger than 1Kb, and whether they overlap with the Spike gene. (Note: you can use the Sars-CoV-2 genome browser to help you see the location of the annotated genes.)" + }, + { + "objectID": "materials/03-case_studies/03-eqa.html#downstream-analyses", + "href": "materials/03-case_studies/03-eqa.html#downstream-analyses", + "title": "10  EQA (Exercise)", + "section": "10.5 Downstream Analyses", + "text": "10.5 Downstream Analyses\nNow that we have cleaned our FASTA file, we can use it for several downstream analysis. We will focus on these:\n\nLineage assignment: identify if our samples come from known lineages from the Pangolin consortium.\nPhylogeny: produce an annotated phylogenetic tree of our samples.\nClustering: assess how many clusters of sequences we have, based on a phylogenetic analysis.\nIntegration & Visualisation: cross-reference different results tables and produce visualisations of how variants changed over time.\n\n\n10.5.1 Lineage Assignment\n\n\n\n\n\n\nNote\n\n\n\nSee Section 6.1, if you need to revise how lineage assignment works.\n\n\nAlthough the Viralrecon pipeline can run Pangolin and Nextclade, it does not use the latest version of these programs (because lineages evolve so fast, the nomenclature constantly changes). Although it is possible to configure viralrecon to use more recent versions of these tools, it requires more advanced use of configuration files with the pipeline.\nAlternatively, we can run our consensus sequences through the latest versions of Nextclade and Pangolin. There are two ways to do this: using their respective web applications, or their command-line versions. For this task, we recommend that you use the command line tools (this will ensure our downstream analysis works well), but we also provide the option to use the web apps for your reference.\n\nCommand lineWeb Apps\n\n\nTo run the command-line version of these tools, there are two steps:\n\nUpdate the datasets of each software (to ensure they are using the latest lineage/clade nomenclature available).\nRun the actual analysis on your samples.\n\nWe will use the exercises below to see what the commands to achieve this are.\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nWe will start doing the Nextclade analysis.\n\nCreate a new script file called scripts/04-nextclade.sh and copy this code into it:\n# update nextclade data\nnextclade dataset get --name sars-cov-2 --output-dir resources/nextclade_background_data\n\n# run nextclade\nnextclade run --input-dataset resources/nextclade_background_data/ --output-all results/nextclade/ <INPUT>\nFix the code, replacing <INPUT> with the path to your consensus sequence file. Save the file.\nActivate the software environment: mamba activate nextclade.\nRun your script using bash.\nOnce the analysis completes, open the file results/nextclade/nextclade.tsv in Excel and see what problems your samples may have (in particular those classified as “bad” quality).\n\n\n\n\n\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nFor the Pangolin analysis:\n\nCreate a new script file called scripts/04-pangolin.sh and copy this code into it:\n# update pangolin data\n<DATA_UPDATE_COMMAND>\n\n# run pangolin\npangolin --outdir results/pangolin/ --outfile pango_report.csv <INPUT>\nFix the code:\n\nReplace <INPUT> with the path to your consensus sequence file.\nReplace <DATA_UPDATE_COMMAND> with the pangolin command used to update its data. Check the documentation of the tool with pangolin --help to see if you can find what this option is called. Alternatively, look at the documentation online.\n\nSave the file.\nActivate the software environment: mamba activate pangolin.\nRun your script using bash.\nOnce the analysis completes, open the file results/pangolin/pango_report.csv in Excel and see if there were any samples for which the analysis failed. If there were any failed samples, check if they match the report from Nextclade.\n\n\n\n\n\n\n\n\nThese exercises are optional - during the workshop please use the command line version of the tools.\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nRunning Nextclade\nGo to clades.nextstrain.org and run Nextclade on the clean FASTA file you created earlier (report/consensus.fa).\nIf you need a reminder about this tool, see the “Lineages and variants” section of the materials.\nOnce the analysis completes, pay particular attention to the quality control column, to see what problems your samples may have (in particular those classified as “bad” quality).\nThen:\n\nCreate a new folder in your project directory: results/nextclade.\nUse the “download” button (top-right) and download the file nextclade.tsv (tab-delimited file), which contains the results of the analysis. Important: please download the TSV file (not the CSV file, as it uses ; to separate columns, and will not work later with the “Data Integration” section).\nSave this file in the results/nextclade folder you created.\n\n\n\n\n\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nRunning Pangolin\nGo to pangolin.cog-uk.io and run Pangolin on the clean FASTA file you created earlier (report/consensus.fa).\nIf you need a reminder about this tool, see the “Lineages and variants” section of the materials.\nOnce the analysis completes, pay particular attention to any samples that failed. If there were any failed samples, check if they match the report from Nextclade.\nThen:\n\nCreate a new folder in your project directory: results/pangolin.\nUse the “download” button (the “arrow down” symbol on the results table) and download the file results.csv. Rename this file to report.csv (important: make sure to rename the file like this, otherwise it will not work later with the “Data Integration” section).\nSave this file in the results/pangolin folder you created.\n\n\n\n\n\n\n\n\n\n\n\n10.5.2 Phylogeny\n\n\n\n\n\n\nNote\n\n\n\nSee Section 7.1, if you need to revise how to build phylogenetic trees.\n\n\nAlthough tools such as Nextclade and civet can place our samples in a phylogeny, sometimes it may be convenient to build our own phylogenies. This requires three steps:\n\nProducing a multiple sequence alignment from all consensus sequences.\nTree inference.\nTree visualisation.\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\n\nStart by creating two directories to store the output of our analysis: results/mafft and results/iqtree.\nActivate the software environment: mamba activate phylo (this environment includes both mafft and iqtree).\nTo make our analysis more interesting, we will combine our sequences with sequences from previous workshops. Use the cat program to concatenate your sequences (report/consensus.fa) with the sequences from our collaborators (resources/eqa_collaborators/eqa_consensus.fa). Using >, save the output in a new file results/mafft/unaligned_consensus.fa.\nPerform a multiple sequence alignment of the combined consensus sequences using the program mafft (see Section 7.2 for how to use this program). Save the output in a file called results/mafft/aligned_consensus.fa.\nInfer a phylogenetic tree using the iqtree2 program (see Section 7.3).\nOnce you have both of these commands working, make sure to save them in a new shell script (as a record of your analysis). Save the script as scripts/05-phylogeny.sh.\nVisualise the tree using FigTree (see Section 7.4).\n\nWhat substitution model was chosen as the best for your data by IQ-Tree?\nDo your samples group with samples from previous workshops in the way that is expected, or are there any outliers?\nAre there any obvious differences between sequencing technologies?\n\n\n\n\n\n\n\n10.5.3 Clustering (Optional Section)\n\nThe software civet (Cluster Investigation and Virus Epidemiology Tool) can be used to produce a report on the phylogenetic relationship between viral samples, using a background dataset to contextualise them (e.g. a global or regional sample of sequences) as well as metadata information to enable the identification of emerging clusters of samples.\nCivet works in several stages, in summary:\n\nEach input sequence is assigned to a “catchment” group. These are groups of samples that occur at a given genetic distance (number of SNP differences) from each other.\nWithin each catchment group, a phylogeny is built (using the iqtree2 software with the HKY substitution model).\nThe phylogeny is annotated with user-input metadata (e.g. location, collection date, etc.). The results are displayed in an interactive HTML report.\n\nOne of the critical pieces of information needed by civet is the background data, to which the input samples will be compared with. These background data could be anything the user wants, but typically it’s a good idea to use samples from the local geographic area from where the samples were collected. For example, a national centre may choose to have their background data composed of all the samples that have been sequenced in the country so far. Or perhaps all the samples in its country and other neighbouring countries.\nOne way to obtain background data is to download it from GISAID. An example of how to do this is detailed in the civet documentation. We have already downloaded the data from GISAID, so we can go straight to running our civet analysis.\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\n\nCreate a new script in scripts/06-civet.sh, with the following command, adjusted to fit your files:\ncivet -i <PATH_TO_YOUR_SAMPLE_METADATA> \\\n -f <PATH_TO_YOUR_CONSENSUS_FASTA> \\\n -icol <COLUMN_NAME_FOR_YOUR_SAMPLE_IDS> \\\n -idate <COLUMN_NAME_FOR_YOUR_COLLECTION_DATE> \\\n -d <PATH_TO_CIVET_BACKGROUND_DATA> \\\n -o results/civet\nActivate the software environment: mamba activate civet.\nOnce your script is ready, run it with bash.\nAfter the analysis completes, open the HTML output file in results/civet and examine into how many catchments your samples were grouped into." + }, + { + "objectID": "materials/03-case_studies/03-eqa.html#integration-visualisation", + "href": "materials/03-case_studies/03-eqa.html#integration-visualisation", + "title": "10  EQA (Exercise)", + "section": "10.6 Integration & Visualisation", + "text": "10.6 Integration & Visualisation\nAt this point in our analysis, we have several tables with different pieces of information:\n\nsample_info.csv → the original table with metadata for our samples.\nresults/viralrecon/multiqc/medaka/summary_variants_metrics_mqc.csv → quality metrics from the MultiQC report generated by the viralrecon pipeline.\nresults/nextclade/nextclade.tsv → the results from Nextclade.\nresults/pangolin/pango_report.csv → the results from Pangolin.\n(optional) results/civet/master_metadata.csv → the results from the civet analysis, namely the catchment (or cluster) that each of our samples was grouped into.\n\nEach of these tables stores different pieces of information, and it would be great if we could integrate them together, to facilitate their interpration and generate some visualisations.\nWe will demonstrate how this analysis can be done using the R software, which is a popular programming language used for data analysis and visualisation. Check the Quick R Intro appendix for the basics of how to work with R and RStudio.\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nData Integration\nFrom RStudio, open the script in scripts/07-data_integration.R. Run the code in the script, going line by line (remember in RStudio you can run code from the script panel using Ctrl + Enter). As you run the code check the tables that are created (in your “Environment” panel on the top-right) and see if they were correctly imported.\nOnce you reach the end of the script, you should have two tab-delimited files named report/consensus_metrics.tsv and report/variants.tsv. Open both files in Excel to check their content and confirm they contain information from across the multiple tables.\n\n\n\n\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nData Visualisation\nAfter integrating the data, it’s time to produce some visualisations. From RStudio, open the script in scripts/08-visualisation.R. Run the code in the script, going line by line.\nAs you run the code, several plots will be created. You can export these plots from within RStudio using the “Export” button on the plotting panel – these will be useful when you write your report later.\n\n\n\n\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nAnnotating Phylogenetic Tree\nUsing FigTree, import two annotation files (File > Import Annotations…):\n\nreport/consensus_metrics.tsv, which was created in the Data Integration exercise.\nresources/eqa_collaborators/metadata.tsv, which has lineage assignment for EQA samples sequenced by other labs.\n\nAfter importing both files, annotate your phylogenetic tree to display the lineages assigned to each sample as the tip labels. See Section 7.4, if you need a reminder of how to annotate trees using FigTree." + }, + { + "objectID": "materials/03-case_studies/03-eqa.html#eqa-panels", + "href": "materials/03-case_studies/03-eqa.html#eqa-panels", + "title": "10  EQA (Exercise)", + "section": "10.7 EQA Panels", + "text": "10.7 EQA Panels\nThe analysis we have done so far is applicable to any new sequences that you want to process. However, we have also been using a pre-defined panel of samples to be used as part of an External Quality Assessment (EQA) process. This allows us to assess whether our bioinformatic analysis identified all the expected mutations in these samples, as well as assigned them to the correct lineages.\nTo do this, requires to compare the mutations we detected in our EQA samples to the expected mutations provided to us. From this, we will be able to calculate True Positives (TP), False Positives (FP) and False Negatives (FN), and calculate two performance metrics:\n\nPrecision = TP / (TP + FP) → what fraction of the mutations we detected are true?\nSensitivity = TP / (TP + FN) → what fraction of all true mutations did we detect?\n\nThere can be cases where one of these metrics is high and the other one lower. For example, if you have a high-coverage genome (say >95%) but lots of sequencing errors, you may have a high sensitivity (you manage to detect all true mutations), but low precision (you also detect lots of false positives, due to sequencing errors). Conversely, if you have a low-coverage genome (say <50%) but very high-quality sequencing, you may have low sensitivity (you missed lots of true mutations, because of missing data), but high precision (those mutations that you did manage to identify were all true, you didn’t have many false positives).\n\nIf you are submiting your samples to GenQA’s platform, they will also provide with a general accuracy score called F-score. This is calculated as the harmonic mean of precision and sensitivity:\n\\(F_{score} = \\frac{2 \\times Precision \\times Sensitivity}{Precision + Sensitivity}\\)\nA high F-score is indicative of both high precision and sensitivity, whereas a lower score indicates that at least one of those metrics are low.\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nFrom RStudio, open the script in scripts/09-eqa_validation.R. Run the code in the script, going line by line.\nThis script will save a new tab-delimited file in report/eqa_performance_metrics.tsv. Open this file in Excel and check what the precision and sensitivity of your analysis was. Are you happy with the results?\n\n\n\n\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\n(Optional)\nWhile you run the R script, you will have an object called all_mutations in your environment (check the top-right panel). From RStudio, click on the name of this object to open it in the table viewer.\nLook at mutations that were not detected in your samples, but were present in the expected mutations (i.e. false negatives). Open the BAM file for one of those samples in IGV and navigate to the position where that mutation should occur. Investigate what the reason may have been for missing that mutation.\nIf you need a reminder of where to find the BAM file, consult the Consensus > Output Files section of the materials, or the Viralrecon output documentation." + }, + { + "objectID": "materials/03-case_studies/03-eqa.html#reporting", + "href": "materials/03-case_studies/03-eqa.html#reporting", + "title": "10  EQA (Exercise)", + "section": "10.8 Reporting", + "text": "10.8 Reporting\nFinally, we have come to the end of our analysis. So, all that is left is to report our results in the most informative way to our colleagues and decision-makers. You may already have established reporting templates in your institution, and if that’s the case, you should use those. Alternatively, we will look at a suggested template, based on the reports done by the UK Health Security Agency (UKHSA).\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nOpen our shared report template document and download it as a Word document (File → Download → Microsoft Word (.docx)). Save the file in report/YYYY-MM-DD_analysis_report.docx (replace YYYY-MM-DD by today’s date).\nOpen the file and complete it with the information you collected throughout your analysis. You should be able to get all this information from the files in your report/ directory." + }, + { + "objectID": "materials/04-wastewater/00-ww_temp.html", + "href": "materials/04-wastewater/00-ww_temp.html", + "title": "11  Wastewater surveillance", + "section": "", + "text": "Caution\n\n\n\nUnder development" + }, + { + "objectID": "materials/05-software/01-managing_software.html#why-linux", + "href": "materials/05-software/01-managing_software.html#why-linux", + "title": "12  Managing Bioinformatics Software", + "section": "12.1 Why Linux?", + "text": "12.1 Why Linux?\nMost computer users either use Windows or macOS as their operating system. However, in bioinformatics the Linux operating system is the most popular.\nThere are different reasons to prefer Linux, for example:\n\nMost bioinformatics software is only available for this operating system.\nGenerally more secure.\nFree and open source.\nThe operating system of choice on high performance compute (HPC) cluster environments.\nIt has flexible built-in command line tools to manipulate large data (cat, grep, sed, and the | pipe for chaining commands).\n\nBecause Linux is an open source project, there are many different types of Linux operating systems available. Generally, we recommend using Ubuntu, as it’s a well-supported and widely used distribution.\nThe first step for running bioinformatics software therefore is to ensure that you have access to Ubuntu Linux on your machine. We give instructions for this in Section 13.1." + }, + { + "objectID": "materials/05-software/01-managing_software.html#package-managers", + "href": "materials/05-software/01-managing_software.html#package-managers", + "title": "12  Managing Bioinformatics Software", + "section": "12.2 Package Managers", + "text": "12.2 Package Managers\nOnce you have Linux available, there is the question of how to install software, especially when using the command line. Most modern Linux operating systems have a default package manager available. A package manager allows the user to install (or remove, or upgrade) software with a single command. The package manager takes care of automatically downloading and installing the software we want, as well as any dependencies it requires.\n\n\n\nDiagram illustrating how package managers work. Image source.\n\n\n\n12.2.1 Ubuntu apt\nOn Ubuntu, the default package manager is called apt. This software can be used to do system-wide updates, upgrades and installation of software packages. There are several commands available:\n\napt update updates the database of available packages, to check the latest versions available. This command doesn’t actually install anything.\napt upgrade upgrades all the packages to their latest version. This command upgrades existing packages, and installs new ones if required as dependencies.\napt install is used to install a single software of your choice.\n\nFor example, let’s say we wanted to install the latest version of java (Java is used for many applications, including IGV, which we introduced earlier). We could run the following commands:\nsudo apt update # make sure the package database is up-to-date\nsudo apt upgrade # upgrade existing software to their latest version\nsudo apt install default-jre # install java\nThe sudo command at the beginning indicates that we want to run the apt command with administrator privilege, which will require you to input your password. This is necessary, because the apt command does a system installation of the software, so it can only be done if the user trying to do the installation has those permissions. With the install command, we give the name of the software we want to install. In this case, default-jre will install the default Java runtime environment.\nThe apt package manager is extremely useful to install core system software, however most bioinformatics software are not available from apt. Also, as it requires administrator (sudo) permissions, it is not always possible to install software with apt (for example, if you are using a HPC cluster). This is why we turn to alternative package managers such as Conda.\n\n\n12.2.2 Debian Packages\nSometimes, software is not available through the apt repositories, but instead distributed as a file. Ubuntu uses the Debian package format. To install Debian packages, you can use the dpkg command.\nFor example, the RStudio application for Linux is distributed as a .deb file. After you download it, you can install it as follows:\nsudo dpkg -i rstudio-2023.12.0-369-amd64.deb\nAs with apt this is a system installation and so requires admin privileges (sudo).\n\n\n12.2.3 Conda/Mamba Package Manager\nOften you may want to use software packages that are not available on the apt repositories. A popular alternative in bioinformatics is to use the package manager Mamba, which is a successor to another package manager called Conda.\nConda and Mamba are package managers commonly used in data science, scientific computing, and bioinformatics. Conda, originally developed by Anaconda, is a package manager and environment manager that simplifies the creation, distribution, and management of software environments containing different packages and dependencies. It is known for its cross-platform compatibility and ease of use. Mamba is a more recent and high-performance alternative to Conda. While it maintains compatibility with Conda’s package and environment management capabilities, Mamba is designed for faster dependency resolution and installation, making it a better choice nowadays.\nOne of the strengths of using Mamba to manage your software is that you can have different versions of your software installed alongside each other, organised in environments. Organising software packages into environments is extremely useful, as it allows to have a reproducible set of software versions that you can use and reuse in your projects.\nFor example, imagine you are working on two projects with different software requirements:\n\nProject A: requires Python 3.7, NumPy 1.15, and scikit-learn 0.20.\nProject B: requires Python 3.9, the latest version of NumPy, and TensorFlow 2.0.\n\nIf you don’t use environments, you would need to install and maintain these packages globally on your system. This can lead to several issues:\n\nVersion conflicts: different projects may require different versions of the same library. For example, Project A might not be compatible with the latest NumPy, while Project B needs it.\nDependency chaos: as your projects grow, you might install numerous packages, and they could interfere with each other, causing unexpected errors or instability.\nDifficulty collaborating: sharing your code with colleagues or collaborators becomes complex because they may have different versions of packages installed, leading to compatibility issues.\n\n\n\n\nIllustration of Conda/Mamba environments. Each environment is isolated from the others (effectively in its own folder), so different versions of the packages can be installed for distinct projects or parts of a long analysis pipeline.\n\n\nEnvironments allow you to create isolated, self-contained environments for each project, addressing these issues:\n\nIsolation: you can create a separate environment for each project using tools like Conda/Mamba. This ensures that the dependencies for one project don’t affect another.\nVersion control: you can specify the exact versions of libraries and packages required for each project within its environment. This eliminates version conflicts and ensures reproducibility.\nEase of collaboration: sharing your code and environment file makes it easy for collaborators to replicate your environment and run your project without worrying about conflicts.\nSimplified maintenance: if you need to update a library for one project, it won’t impact others. You can manage environments separately, making maintenance more straightforward.\n\nAnother advantage of using Mamba is that the software is installed locally (by default in your home directory), without the need for admin (sudo) permissions.\nYou can search for available packages from the anaconda.org website. Packages are organised into “channels”, which represent communities that develop and maintain the installation “recipes” for each software. The most popular channels for bioinformatics and data analysis are “bioconda” and “conda-forge”.\nThere are three main commands to use with Mamba:\n\nmamba create -n ENVIRONMENT-NAME: this command creates a new software environment, which can be named as you want. Usually people name their environments to either match the name of the main package they are installating there (e.g. an environment called pangolin if it’s to install the Pangolin software). Or, if you are installing several packages in the same environment, then you can name it as a topic (e.g. an environment called rnaseq if it contains several packages for RNA-seq data analysis).\nmamba install -n ENVIRONMENT-NAME NAME-OF-PACKAGE: this command installs the desired package in the specified environment.\nmamba activate ENVIRONMENT-NAME: this command “activates” the environment, which means the software installed there becomes available from the terminal.\n\nLet’s see a concrete example. If we wanted to install packages for phylogenetic analysis, we could do:\n# create an environment named \"phylo\"\nmamba create -n phylo\n \n# install some software in that environment\nmamba install -n phylo iqtree mafft\nIf we run the command:\nmamba env list\nWe will get a list of environments we created, and “phylo” should be listed there. If we want to use the software we installed in that environment, then we can activate it:\nmamba activate phylo\nAnd usually this changes your terminal to have the word (phylo) at the start of your prompt." + }, + { + "objectID": "materials/05-software/01-managing_software.html#software-containers", + "href": "materials/05-software/01-managing_software.html#software-containers", + "title": "12  Managing Bioinformatics Software", + "section": "12.3 Software Containers", + "text": "12.3 Software Containers\nSoftware containerization is a way to package software and its dependencies in a single file. A software container can be thought of as a very small virtual machine, with everything needed to run that software stored inside that file. For this reason, software containerization solutions, such as Docker and Singularity, are widely used in bioinformatics.\nSoftware containers ensure reproducibility, allowing the same analysis to run on different systems. They can run on a local computer or on a high-performance computing cluster, producing the same result. The software within a container is isolated from other software, addressing the issue of incompatible dependencies between tools (similarly to Mamba environments).\nAs we already saw, analysis pipelines can be very complex, using many tools, each with their own dependencies. Therefore, worflow managers such as Nextflow use software containers to run their analysis (both Singularity and Docker are supported). This is what we’ve been doing throughout these materials, when running the nf-core/viralrecon pipeline, where we used the option -profile singularity. With this option, Nextflow will download the necessary software containers to run each step of the pipeline, which are available in public repositories online.\nTo use Singularity and Docker containers, the respective programs have to be installed, which we detail in our software setup page.\n\n\n\n\n\n\nPackage managers or containers?\n\n\n\nAs we’ve seen, the Mamba package manager and the containerisation solutions such as Docker and Singularity are trying to achieve similar things: enabling the reproducible installation of software and its dependencies in an isolated environment. So, why use one or the other?\nMamba is more user-friendly, allowing you to easily install packages of your choice. However, Mamba often works less well for more complex environments and can become extremely inneficient for environments with too many packages and conflicting versions. The recommendation is to keep your Mamba environments relatively small and atomic (e.g. an environment for each software package or for small sets of related packages).\nSingularity and Docker, on the other hand, allow for more complex environments. However, to create your own container requires more advanced knowledge and a steep learning curve. Fortunately, there are many existing containers available for bioinformatics, which Nextflow uses in its pipelines." + }, + { + "objectID": "materials/05-software/01-managing_software.html#summary", + "href": "materials/05-software/01-managing_software.html#summary", + "title": "12  Managing Bioinformatics Software", + "section": "12.4 Summary", + "text": "12.4 Summary\n\n\n\n\n\n\nKey Points\n\n\n\n\nLinux is used in bioinformatics due to its open-source nature, powerful command-line interface, and compatibility with bioinformatics tools.\nPackage managers such as apt enable global software installations on Linux systems, simplifying the process of obtaining and managing software at the system level.\nLocal package managers such as mamba allow users to install and manage software within user-specific environments, avoiding conflicts with system-level packages.\nSoftware containers, such as Docker and Singularity, encapsulate entire environments in a file and can be used to manage more complex software enviroments.\nBioinformatics workflow managers such as Nextflow can make use of software containers to run complex bioinformatic pipelines." + }, + { + "objectID": "materials/05-software/03-software_setup.html#sec-install-linux", + "href": "materials/05-software/03-software_setup.html#sec-install-linux", + "title": "13  Software setup", + "section": "13.1 Install Linux", + "text": "13.1 Install Linux\n\nFresh InstallationWindows WSLVirtual Machine\n\n\nThe recommendation for bioinformatic analysis is to have a dedicated computer running a Linux distribution. The kind of distribution you choose is not critical, but we recommend Ubuntu if you are unsure.\nYou can follow the installation tutorial on the Ubuntu webpage.\n\n\n\n\n\n\nWarning\n\n\n\nInstalling Ubuntu on the computer will remove any other operating system you had previously installed, and can lead to data loss.\n\n\n\n\nThe Windows Subsystem for Linux (WSL2) runs a compiled version of Ubuntu natively on Windows.\nThere are detailed instructions on how to install WSL on the Microsoft documentation page. But briefly:\n\nClick the Windows key and search for Windows PowerShell, right-click on the app and choose Run as administrator.\nAnswer “Yes” when it asks if you want the App to make changes on your computer.\nA terminal will open; run the command: wsl --install.\nProgress bars will show while installing “Virtual Machine Platform”, “Windows Subsystem for Linux” and finally “Ubuntu” (this process can take a long time).\n\nNote: it has happened to us in the past that the terminal freezes at the step of installing “Ubuntu”. If it is frozen for ~1h at that step, press Ctrl + C and hopefully you will get a message saying “Ubuntu installed successfully”.\n\nAfter installation completes, restart your computer.\nAfter restart, a terminal window will open asking you to create a username and password.\nIf it doesn’t, click the Windows key and search for Ubuntu, click on the App and it should open a new terminal.\n\nYou can use the same username and password that you have on Windows, or a different one - it’s your choice. Spaces and other special characters are not allowed for your Ubuntu username.\nNote: when you type your password nothing seems to be happening as the cursor doesn’t move. However, the terminal is recording your password as you type. You will be asked to type the new password again to confirm it, so you can always try again if you get it wrong the first time.\n\n\nYou should now have access to a Ubuntu Linux terminal. This behaves very much like a regular Ubuntu server.\n\nConfiguring WSL2\nAfter installation, it is useful to create shortcuts to your files on Windows. Your main C:\\ drive is located in /mnt/c/ and other drives will be equally available based on their letter. To create shortcuts to commonly-used directories you use symbolic links. Here are some commands to automatically create shortcuts to your Windows “Documents”, “Desktop” and “Downloads” folders (copy/paste these commands on the terminal):\nln -s $(wslpath $(powershell.exe '[environment]::getfolderpath(\"MyDocuments\")' | tr -d '\\r')) ~/Documents\nln -s $(wslpath $(powershell.exe '[environment]::getfolderpath(\"Desktop\")' | tr -d '\\r')) ~/Desktop\nln -s $(wslpath $(powershell.exe '[environment]::getfolderpath(\"UserProfile\")' | tr -d '\\r'))/Downloads ~/Downloads\nYou may also want to configure the Windows terminal to automatically open WSL2 (instead of the default Windows Command Prompt or Powershell):\n\nSearch for and open the “ Terminal” application.\nClick on the down arrow in the toolbar.\nClick on “ Settings”.\nUnder “Default Profile” select “ Ubuntu”.\n\n\n\n\nAnother way to run Linux within Windows (or macOS) is to install a Virtual Machine. However, this is mostly suitable for practicing and not suitable for real data analysis.\nDetails for installing Ubuntu on VirtualBox is given on this page. Make sure to do these things, while you are setting it up:\n\nIn Step 2 “Create a user profile”: make sure to tick the Guest Additions option.\nIn Step 2 “Define the Virtual Machine’s resources”:\n\nAssign at least 4 CPUs and 16000MB of RAM. At the very minimum you need 2 CPUs to run an Ubuntu VM.\nSet at least 100GB as disk size, more if you have it available (note, this will not take 100GB of space on your computer, but it will allow using up to a maximum of that value, which is useful as we are working with sequencing data).\n\n\nOnce the installation completes, login to the Ubuntu Virtual machine, open a terminal and do the following:\n\nRun su command.\nEnter your user password. Your terminal should change to start with root@\nType the command: usermod -a -G sudo YOUR-USERNAME-HERE.\nClose the terminal and restart the virtual machine.\n\nThese commands will add your newly created user to the “sudo” (admin) group.\n\n\n\nAfter making a fresh install of Ubuntu, open a terminal and run the following commands to update your system and install some essential packages:\nsudo apt update && sudo apt upgrade -y && sudo apt autoremove -y\nsudo apt install -y git\nsudo apt install -y default-jre" + }, + { + "objectID": "materials/05-software/03-software_setup.html#conda", + "href": "materials/05-software/03-software_setup.html#conda", + "title": "13  Software setup", + "section": "13.2 Conda", + "text": "13.2 Conda\nWe recommend using the Conda package manager to install your software. In particular, the newest implementation called Mamba.\nTo install Mamba, run the following commands from the terminal:\nwget \"https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh\"\nbash Miniforge3-$(uname)-$(uname -m).sh -b -p $HOME/miniforge3\nrm Miniforge3-$(uname)-$(uname -m).sh\n$HOME/miniforge3/bin/mamba init\nRestart your terminal (or open a new one) and confirm that your shell now starts with the word (base). Then run the following commands:\nRestart your terminal (or open a new one) and confirm that your shell now starts with the word (base). Then run the following commands:\nconda config --add channels defaults\nconda config --add channels bioconda\nconda config --add channels conda-forge\nconda config --set remote_read_timeout_secs 1000" + }, + { + "objectID": "materials/05-software/03-software_setup.html#nextflow", + "href": "materials/05-software/03-software_setup.html#nextflow", + "title": "13  Software setup", + "section": "13.3 Nextflow", + "text": "13.3 Nextflow\nWe recommend that you install Nextflow within a conda environment. You can do that with the following command:\nmamba create -n nextflow -y nextflow\nWhen you want to use Nextflow make sure to activate this software environment by running mamba activate nextflow.\nAlso run the following command to create a configuration file to setup Nextflow correctly (make sure to copy all the code from top to bottom):\nmkdir -p $HOME/.nextflow\necho \"\nconda {\n conda.enabled = true\n singularity.enabled = false\n docker.enabled = false\n useMamba = true\n createTimeout = '4 h'\n cacheDir = '$HOME/.nextflow-conda-cache/'\n}\nsingularity {\n singularity.enabled = true\n conda.enabled = false\n docker.enabled = false\n pullTimeout = '4 h'\n cacheDir = '$HOME/.nextflow-singularity-cache/'\n}\ndocker {\n docker.enabled = true\n singularity.enabled = false\n conda.enabled = false\n}\n\" >> $HOME/.nextflow/config" + }, + { + "objectID": "materials/05-software/03-software_setup.html#bioinformatics-software", + "href": "materials/05-software/03-software_setup.html#bioinformatics-software", + "title": "13  Software setup", + "section": "13.4 Bioinformatics Software", + "text": "13.4 Bioinformatics Software\nDue to conflicts in software versions required by different packages, we install them in separate environments:\nmamba create -n seqkit -y seqkit\nmamba create -n pangolin -y pangolin\nmamba create -n nextclade -y nextclade\nmamba create -n civet -y civet\nmamba create -n phylo -y mafft iqtree treetime figtree\nIn addition to these packages, you can also install AliView (which unfortunately is not available through Conda):\nwget https://ormbunkar.se/aliview/downloads/linux/linux-version-1.28/aliview.tgz\ntar -xzvf aliview.tgz\nrm aliview.tgz\necho \"alias aliview='java -jar $HOME/aliview/aliview.jar'\" >> $HOME/.bashrc\nFinally, you can install IGV by downloading the installer from their website." + }, + { + "objectID": "materials/05-software/03-software_setup.html#software-image-containers", + "href": "materials/05-software/03-software_setup.html#software-image-containers", + "title": "13  Software setup", + "section": "13.5 Software Image Containers", + "text": "13.5 Software Image Containers\n\n13.5.1 Singularity\nWe recommend that you install Singularity and use the -profile singularity option when running Nextflow pipelines. On Ubuntu/WSL2, you can install Singularity using the following commands:\nsudo apt install -y runc cryptsetup-bin uidmap\nwget -O singularity.deb https://github.com/sylabs/singularity/releases/download/v4.0.2/singularity-ce_4.0.2-$(lsb_release -cs)_amd64.deb\nsudo dpkg -i singularity.deb\nrm singularity.deb\nIf you have a different Linux distribution, you can find more detailed instructions on the Singularity documentation page.\nIf you have issues running Nextflow pipelines with Singularity, then you can follow the instructions below for Docker instead.\n\n\n13.5.2 Docker\n\nWindows WSLLinux\n\n\nWhen using WSL2 on Windows, running Nextflow pipelines with -profile singularity sometimes doesn’t work.\nAs an alternative you can instead use Docker, which is another software containerisation solution. To set this up, you can follow the full instructions given on the Microsoft Documentation: Get started with Docker remote containers on WSL 2.\nWe briefly summarise the instructions here (but check that page for details and images):\n\nDownload Docker for Windows.\nRun the installer and install accepting default options.\nRestart the computer.\nOpen Docker and go to Settings > General to tick “Use the WSL 2 based engine”.\nGo to Settings > Resources > WSL Integration to enable your Ubuntu WSL installation.\n\nOnce you have Docker set and installed, you can use -profile docker when running your Nextflow command.\n\n\nFor Linux, here are the installation instructions:\nsudo apt install curl\ncurl -fsSL https://get.docker.com -o get-docker.sh\nsudo sh ./get-docker.sh\nsudo groupadd docker\nsudo usermod -aG docker $USER\nAfter the last step, you will need to restart your computer. From now on, you can use -profile docker when you run Nextflow" + }, + { + "objectID": "materials/appendices/file_formats.html#bam-binary-alignment-map", + "href": "materials/appendices/file_formats.html#bam-binary-alignment-map", + "title": "Common file formats", + "section": "BAM (“Binary Alignment Map”)", + "text": "BAM (“Binary Alignment Map”)\n\nBinary file.\nSame as a SAM file but compressed in binary form.\nFile extensions: .bam" + }, + { + "objectID": "materials/appendices/file_formats.html#bed-browser-extensible-data", + "href": "materials/appendices/file_formats.html#bed-browser-extensible-data", + "title": "Common file formats", + "section": "BED (“Browser Extensible Data”)", + "text": "BED (“Browser Extensible Data”)\n\nText file.\nStores coordinates of genomic regions.\nFile extension: .bed" + }, + { + "objectID": "materials/appendices/file_formats.html#csv-comma-separated-values", + "href": "materials/appendices/file_formats.html#csv-comma-separated-values", + "title": "Common file formats", + "section": "CSV (“Comma Separated Values”)", + "text": "CSV (“Comma Separated Values”)\n\nText file.\nStores tabular data in a text file. (also see TSV format)\nFile extensions: .csv\n\nThese files can be opened with spreadsheet programs (such as Microsoft Excel). They can also be created from spreadsheet programs by going to File > Save As… and select “CSV (Comma delimited)” as the file format." + }, + { + "objectID": "materials/appendices/file_formats.html#fast5", + "href": "materials/appendices/file_formats.html#fast5", + "title": "Common file formats", + "section": "FAST5", + "text": "FAST5\n\nBinary file. More specifically, this is a Hierarchical Data Format (HDF5) file.\nUsed by Nanopore platforms to store the called sequences (in FASTQ format) as well as the raw electrical signal data from the pore.\nFile extensions: .fast5" + }, + { + "objectID": "materials/appendices/file_formats.html#fasta", + "href": "materials/appendices/file_formats.html#fasta", + "title": "Common file formats", + "section": "FASTA", + "text": "FASTA\n\nText file.\nStores nucleotide or amino acid sequences.\nFile extensions: .fa or .fas or .fasta" + }, + { + "objectID": "materials/appendices/file_formats.html#fastq", + "href": "materials/appendices/file_formats.html#fastq", + "title": "Common file formats", + "section": "FASTQ", + "text": "FASTQ\n\nText file, but often compressed with gzip.\nStores sequences and their quality scores.\nFile extensions: .fq or .fastq (compressed as .fq.gz or .fastq.gz)" + }, + { + "objectID": "materials/appendices/file_formats.html#gff-general-feature-format", + "href": "materials/appendices/file_formats.html#gff-general-feature-format", + "title": "Common file formats", + "section": "GFF (“General Feature Format”)", + "text": "GFF (“General Feature Format”)\n\nText file.\nStores gene coordinates and other features.\nFile extension: .gff" + }, + { + "objectID": "materials/appendices/file_formats.html#newick", + "href": "materials/appendices/file_formats.html#newick", + "title": "Common file formats", + "section": "NEWICK", + "text": "NEWICK\n\nText file.\nStores phylogenetic trees including nodes names and edge lengths.\nFile extensions: .tree or .treefile" + }, + { + "objectID": "materials/appendices/file_formats.html#sam-sequence-alignment-map", + "href": "materials/appendices/file_formats.html#sam-sequence-alignment-map", + "title": "Common file formats", + "section": "SAM (“Sequence Alignment Map”)", + "text": "SAM (“Sequence Alignment Map”)\n\nText file.\nStores sequences aligned to a reference genome. (also see BAM format)\nFile extensions: .sam" + }, + { + "objectID": "materials/appendices/file_formats.html#tsv-tab-separated-values", + "href": "materials/appendices/file_formats.html#tsv-tab-separated-values", + "title": "Common file formats", + "section": "TSV (“Tab-Separated Values”)", + "text": "TSV (“Tab-Separated Values”)\n\nText file.\nStores tabular data in a text file. (also see CSV format)\nFile extensions: .tsv or .txt\n\nThese files can be opened with spreadsheet programs (such as Microsoft Excel). They can also be created from spreadsheet programs by going to File > Save As… and select “Text (Tab delimited)” as the file format." + }, + { + "objectID": "materials/appendices/file_formats.html#vcf-variant-calling-format", + "href": "materials/appendices/file_formats.html#vcf-variant-calling-format", + "title": "Common file formats", + "section": "VCF (“Variant Calling Format”)", + "text": "VCF (“Variant Calling Format”)\n\nText file but often compressed with gzip.\nStores SNP/Indel variants\nFile extension: .vcf (or compressed as .vcf.gz)" + }, + { + "objectID": "materials/appendices/unix_cheatsheet.html#documentation-and-help", + "href": "materials/appendices/unix_cheatsheet.html#documentation-and-help", + "title": "Unix cheat sheet", + "section": "Documentation and Help", + "text": "Documentation and Help\n\n\n\n\n\n\n\nman {command}\nmanual page for the program\n\n\nwhatis {command}\nshort description of the program\n\n\n{command} --help\nmany programs use the --help flag to print documentation" + }, + { + "objectID": "materials/appendices/unix_cheatsheet.html#listing-files", + "href": "materials/appendices/unix_cheatsheet.html#listing-files", + "title": "Unix cheat sheet", + "section": "Listing files", + "text": "Listing files\n\n\n\nls\nlist files in the current directory\n\n\nls {path}\nlist files in the specified path\n\n\nls -l {path}\nlist files in long format (more information)\n\n\nls -a {path}\nlist all files (including hidden files)" + }, + { + "objectID": "materials/appendices/unix_cheatsheet.html#change-directories", + "href": "materials/appendices/unix_cheatsheet.html#change-directories", + "title": "Unix cheat sheet", + "section": "Change Directories", + "text": "Change Directories\n\n\n\n\n\n\n\ncd {path}\nchange to the specified directory\n\n\ncd or cd ~\nchange to the home directory\n\n\ncd ..\nmove back one directory\n\n\npwd\nprint working directory. Shows the full path of where you are at the moment (useful if you are lost)" + }, + { + "objectID": "materials/appendices/unix_cheatsheet.html#make-or-remove-directories", + "href": "materials/appendices/unix_cheatsheet.html#make-or-remove-directories", + "title": "Unix cheat sheet", + "section": "Make or Remove Directories", + "text": "Make or Remove Directories\n\n\n\n\n\n\n\nmkdir {dirname}\ncreate a directory with specified name\n\n\nrmdir {dirname}\nremove a directory (only works if the directory is empty)\n\n\nrm -r {dirname}\nremove the directory and all it’s contents (use with care)" + }, + { + "objectID": "materials/appendices/unix_cheatsheet.html#copy-move-and-remove-files", + "href": "materials/appendices/unix_cheatsheet.html#copy-move-and-remove-files", + "title": "Unix cheat sheet", + "section": "Copy, Move and Remove Files", + "text": "Copy, Move and Remove Files\n\n\n\n\n\n\n\ncp {source/path/file1} {target/path/}\ncopy “file1” to another directory keeping its name\n\n\ncp {source/path/file1} {target/path/file2}\ncopy “file1” to another directory naming it “file2”\n\n\ncp {file1} {file2}\nmake a copy of “file1” in the same directory with a new name “file2”\n\n\nmv {source/path/file1} {target/path/}\nmove “file1” to another directory keeping its name\n\n\nmv {source/path/file1} {target/path/file2}\nmove “file1” to another directory renaming it as “file2”\n\n\nmv {file1} {file2}\nis equivalent to renaming a file\n\n\nrm {filename}\nremove a file" + }, + { + "objectID": "materials/appendices/unix_cheatsheet.html#view-text-files", + "href": "materials/appendices/unix_cheatsheet.html#view-text-files", + "title": "Unix cheat sheet", + "section": "View Text Files", + "text": "View Text Files\n\n\n\n\n\n\n\nless {file}\nview and scroll through a text file\n\n\nhead {file}\nprint the first 10 lines of a file\n\n\nhead -n {N} {file}\nprint the first N lines of a file\n\n\ntail {file}\nprint the last 10 lines of a file\n\n\ntail -n {N} {file}\nprint the last N lines of a file\n\n\nhead -n {N} {file} | tail -n 1\nprint the Nth line of a file\n\n\ncat {file}\nprint the whole content of the file\n\n\ncat {file1} {file2} {...} {fileN}\nconcatenate files and print the result\n\n\nzcat {file} and zless {file}\nlike cat and less but for compressed files (.zip or .gz)" + }, + { + "objectID": "materials/appendices/unix_cheatsheet.html#find-patterns", + "href": "materials/appendices/unix_cheatsheet.html#find-patterns", + "title": "Unix cheat sheet", + "section": "Find Patterns", + "text": "Find Patterns\nFinding (and replacing) patterns in text is a very powerful feature of several command line programs. The patterns are specified using regular expressions (shortened as regex), which are not covered in this document. See this Regular Expressions Cheat Sheet for a comprehensive overview.\n\n\n\n\n\n\n\ngrep {regex} {file}\nprint the lines of the file that have a match with the regular expression pattern" + }, + { + "objectID": "materials/appendices/unix_cheatsheet.html#wildcards", + "href": "materials/appendices/unix_cheatsheet.html#wildcards", + "title": "Unix cheat sheet", + "section": "Wildcards", + "text": "Wildcards\n\n\n\n\n\n\n\n*\nmatch any number of characters\n\n\n?\nmatch any character only once\n\n\nExamples\n\n\n\nls sample*\nlist all files that start with the word “sample”\n\n\nls *.txt\nlist all the files with .txt extension\n\n\ncp * {another/directory}\ncopy all the files in the current directory to a different directory" + }, + { + "objectID": "materials/appendices/unix_cheatsheet.html#redirect-output", + "href": "materials/appendices/unix_cheatsheet.html#redirect-output", + "title": "Unix cheat sheet", + "section": "Redirect Output", + "text": "Redirect Output\n\n\n\n\n\n\n\n{command} > {file}\nredirect output to a file (overwrites if the file exists)\n\n\n{command} >> {file}\nappend output to a file (creates a new file if it does not already exist)" + }, + { + "objectID": "materials/appendices/unix_cheatsheet.html#combining-commands-with-pipes", + "href": "materials/appendices/unix_cheatsheet.html#combining-commands-with-pipes", + "title": "Unix cheat sheet", + "section": "Combining Commands with | Pipes", + "text": "Combining Commands with | Pipes\n\n\n\n\n\n\n\n<command1> | <command2>\nthe output of “command1” is passed as input to “command2”\n\n\nExamples\n\n\n\nls | wc -l\ncount the number of files in a directory\n\n\ncat {file1} {file2} | less\nconcatenate files and view them with less\n\n\ncat {file} | grep \"{pattern}\" | wc -l\ncount how many lines in the file have a match with “pattern”" + }, + { + "objectID": "materials/appendices/quick_r.html#rstudio", + "href": "materials/appendices/quick_r.html#rstudio", + "title": "R fundamentals", + "section": "RStudio", + "text": "RStudio\nR is the software and programming language itself, but the R interface is quite basic and not very user-friendly. Fortunately, there is a companion piece of software called RStudio, which makes working with R a little bit easier.\nThere are 4 basic panels in RStudio (see image below):\n\nThe script panel is essentially a text editor, where we can write code and save it as a text file, which in this case, because it contains R code, we call it an R script (but remember, a script is just a text file with some code in it. We’ve been creating shell scripts that run on the command line, here we have some R scripts, which contain R code)\nThe console panel is where the code actually runs, or is executed. This is equivalent to the terminal, on the command line. If we want to execute a line of code, then we need to run it on the console.\n\nOne nice feature of RStudio is that we can edit our code on the script panel and then run a line of code from the script on the console - it’s like copy/pasting that line of code from the script to the console. This makes working interactively with RStudio much easier, because we can edit our code in the script and run it as we go along.\n\nOn the top-right we have the Environment panel, which shows us objects that we create, that store information such as tables of data that we read into R.\nFinally, on the bottom-right there are a few tabs: a file browser (allowing us to see files in our computer), a plot display tab (for plots we generate) and a help tab to look at documentation.\n\n\n\nSetting RStudio\nBefore we start working with RStudio, it’s a good idea to change one of its default options. Go to Tools → Global Options… and change the following:\n\nThis will tell RStudio to NOT automatically save and load things that we have done in the past. You may think this is a helpful thing, but actually it’s very inconvenient, because if you are doing multiple analysis, it might get very very confusing what the objects that you created are! So, it’s always best to start R with a fresh session, and setting these options makes sure we do this.\n\n\nStarting a Project\nR has a concept called working directory, which is the location on your computer where it is working from (looking for files and folders). You can think of it as the folder that you cd into if you were working on the command-line.\nThe easiest way to ensure that R is using the correct working directory for our analysis, is to create an R project. In RStudio: File → New Project… → Existing Directory and then click the Browse… button to navigate to the folder where your project files are located.\nThis will create an .Rproj file on your project folder. Next time you want to work on your analysis, you can simply double-click on this file, and it will open RStudio, with the correct working directory already set for you." + }, + { + "objectID": "materials/appendices/quick_r.html#r-basics", + "href": "materials/appendices/quick_r.html#r-basics", + "title": "R fundamentals", + "section": "R Basics", + "text": "R Basics\nThis section introduces some of the basic concepts in the R programming language.\n\nPackages/Libraries\nR has several extensions to the basic functionality called “packages” or “libraries”. A popular library for data manipulation is called tidyverse, which we are using in this course. Each time we start a new R session, we need to load the libraries we want to use:\n\nlibrary(tidyverse)\n\nIf you get an error like Error in library(tidyverse) : there is no package called 'tidyverse', that means that you didn’t install the package. To install packages you can run:\n\ninstall.packages(\"tidyverse\")\n\nYou only need to do this the first time you want to use a library. Once it’s installed, you don’t need to run this command again (unless you want to update the library to its latest version – often a good idea!).\n\n\nCreate objects\nCreate objects (something that contains a value) with <-. For example, the following creates an object called x containing a single number:\n\nx <- 53.341\n\nWe can print the content of the object by typing its name:\n\nx\n\n[1] 53.341\n\n\n\n\nFunctions\nMost of the tasks we can achieve in R are done through the use of functions. We can think of functions as mini-programs that take an input and give an output.\nFunctions are easy to identify, because they are always followed by parenthesis. Inside the parenthesis we include the inputs to the function.\n\nround(x) # round the the value of x\n\n[1] 53\n\n\nFunctions have options that can change their behaviour. Separate options using a comma:\n\nround(x, digits = 1) # round to one decimal point\n\n[1] 53.3\n\n\n\n\nVector\nA vector is the most basic type of object in R. It is a collection of values, which are all of the same type, for example numeric, character or logical (TRUE/FALSE).\n\nx_chr <- c(\"dog\", \"cat\", \"goldfish\") # character vector\nx_num <- c(1, 5, 23.3, 55.2) # numeric vector\nx_log <- c(TRUE, TRUE, FALSE, TRUE) # logical vector\n\nAccess values inside the vector with []:\n\nx_chr[2] # the second value\n\n[1] \"cat\"\n\nx_chr[c(2, 3)] # the second and third values\n\n[1] \"cat\" \"goldfish\"\n\n\n\n\nConditions\nIn many situations (for example to filter rows in a table), it’s useful to evaluate a set of conditions. We can create logical vectors using conditions:\n\nx_num\n\n[1] 1.0 5.0 23.3 55.2\n\n# is x_num greater than 20?\nx_num > 20\n\n[1] FALSE FALSE TRUE TRUE\n\n# is x_num equal to 5?\nx_num == 5\n\n[1] FALSE TRUE FALSE FALSE\n\n# is x_num contained the vector on the right?\nx_num %in% c(20, 30, 1)\n\n[1] TRUE FALSE FALSE FALSE\n\n\nCombine conditions with & (AND) and | (OR):\n\nx_num\n\n[1] 1.0 5.0 23.3 55.2\n\n# is x_num greater than or equal to 10 AND smaller than or equal to 30?\nx_num >= 10 & x_num <= 30\n\n[1] FALSE FALSE TRUE FALSE\n\n# is x_num smaller than 10 OR greater than 30?\nx_num < 10 | x_num > 30\n\n[1] TRUE TRUE FALSE TRUE\n\n\nTo set the filtering conditions, several relational operators can be used:\n\n== is equal to\n!= is different from\n%in% is contained in\n> is greater than\n>= is greater than or equal to\n< is less than\n<= is less than or equal to\n\nIt is also possible to combine several conditions together using the following logical operators:\n\n& AND\n| OR\n\n\n\nMissing Values\nSometimes we have missing values in our data, which are encoded as NA:\n\ny <- c(23, 44, NA, 212)\n\nWe need to ensure these are dealt with properly\n\n# returns NA\nmean(y)\n\n[1] NA\n\n# removes NA and then calculates mean\nmean(y, na.rm = TRUE)\n\n[1] 93\n\n\nThe is.na() function is important to deal with missing values:\n\ny\n\n[1] 23 44 NA 212\n\n# create a logical that is true if value is missing\nis.na(y)\n\n[1] FALSE FALSE TRUE FALSE\n\n# Negate that expression using !\n!is.na(y)\n\n[1] TRUE TRUE FALSE TRUE\n\n\n\n\nTables: data.frame/tibble\nTables in R are called data.frame. The tidyverse package has its own version of a data.frame called a tibble. For the most part they are basically equivalent, but the tibble object has a nicer printing function to display our data on the console.\nAs an example for working with tables in R, let’s read a TSV (tab-delimited) file that contains intervals of missing information in 5 SARS-CoV-2 consensus sequences (this data comes from the Switzerland case study). To import a TSV file into R as a data.frame we can use the function read_tsv() (for a CSV file we would use read_csv()):\n\nmissing_intervals <- read_tsv(\"missing_intervals.tsv\")\n\nRows: 132 Columns: 6\n── Column specification ────────────────────────────────────────────────────────\nDelimiter: \"\\t\"\nchr (4): seqID, patternName, pattern, strand\ndbl (2): start, end\n\nℹ Use `spec()` to retrieve the full column specification for this data.\nℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.\n\n\nWhen we read the table in, we get a message informing us of the column types found. In this case we have character columns containing text (indicated by chr) and numeric columns (indicated by dbl, which refers to the double-precission floating point format that computers use to store numbers).\nTo see the content of the table you can type the name of the object:\n\nmissing_intervals\n\n# A tibble: 132 × 6\n seqID patternName pattern strand start end\n <chr> <chr> <chr> <chr> <dbl> <dbl>\n 1 CH01 N+ N+ + 1 54\n 2 CH01 N+ N+ + 1193 1264\n 3 CH01 N+ N+ + 4143 4322\n 4 CH01 N+ N+ + 6248 6294\n 5 CH01 N+ N+ + 7561 7561\n 6 CH01 N+ N+ + 9243 9311\n 7 CH01 N+ N+ + 10367 10367\n 8 CH01 N+ N+ + 11361 11370\n 9 CH01 N+ N+ + 13599 13613\n10 CH01 N+ N+ + 16699 16758\n# ℹ 122 more rows\n\n\nSometimes a more convenient way is to click the name of the table on the environment, which opens a new tab to preview your data.\n\n\n\nData viewer in RStudio." + }, + { + "objectID": "materials/appendices/quick_r.html#data-manipulation", + "href": "materials/appendices/quick_r.html#data-manipulation", + "title": "R fundamentals", + "section": "Data Manipulation", + "text": "Data Manipulation\nMost of the work you will do in R is with tables of data (data.frame/tibble objects). There are several ways to manipulate tables in R, but we will give a quick overview of the functionality available through the tidyverse collection of packages.\n\nBasic “verbs”\nThere’s a set of basic functions that can be thought of as “data manipulation verbs”. They are:\n\nmutate() → add a new column of modify an existing one.\nselect() → select columns from the table.\nfilter() → subset the rows from the table that fullfill a certain logical condition.\n\nHere are some examples of each:\n\n# create a new column with the missing interval lengths\nmutate(missing_intervals, \n length = (end - start) + 1)\n\n# A tibble: 132 × 7\n seqID patternName pattern strand start end length\n <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>\n 1 CH01 N+ N+ + 1 54 54\n 2 CH01 N+ N+ + 1193 1264 72\n 3 CH01 N+ N+ + 4143 4322 180\n 4 CH01 N+ N+ + 6248 6294 47\n 5 CH01 N+ N+ + 7561 7561 1\n 6 CH01 N+ N+ + 9243 9311 69\n 7 CH01 N+ N+ + 10367 10367 1\n 8 CH01 N+ N+ + 11361 11370 10\n 9 CH01 N+ N+ + 13599 13613 15\n10 CH01 N+ N+ + 16699 16758 60\n# ℹ 122 more rows\n\n# select only a few columns of the table\nselect(missing_intervals,\n seqID, start, end)\n\n# A tibble: 132 × 3\n seqID start end\n <chr> <dbl> <dbl>\n 1 CH01 1 54\n 2 CH01 1193 1264\n 3 CH01 4143 4322\n 4 CH01 6248 6294\n 5 CH01 7561 7561\n 6 CH01 9243 9311\n 7 CH01 10367 10367\n 8 CH01 11361 11370\n 9 CH01 13599 13613\n10 CH01 16699 16758\n# ℹ 122 more rows\n\n# subset the table to include only intervals within the Spike protein\nfilter(missing_intervals,\n start > 21563 & end < 25384)\n\n# A tibble: 15 × 6\n seqID patternName pattern strand start end\n <chr> <chr> <chr> <chr> <dbl> <dbl>\n 1 CH01 N+ N+ + 21621 21670\n 2 CH01 N+ N+ + 23480 23507\n 3 CH02 N+ N+ + 21620 21670\n 4 CH02 N+ N+ + 23480 23508\n 5 CH03 N+ N+ + 21621 21670\n 6 CH03 N+ N+ + 23480 23507\n 7 CH04 N+ N+ + 21620 21670\n 8 CH04 N+ N+ + 22240 22240\n 9 CH04 N+ N+ + 23480 23509\n10 CH04 N+ N+ + 24062 24395\n11 CH05 N+ N+ + 21620 22530\n12 CH05 N+ N+ + 23489 24709\n13 CH06 N+ N+ + 21605 22515\n14 CH06 N+ N+ + 23474 23525\n15 CH06 N+ N+ + 23721 24694\n\n\n\n\nPipes\nWe can chain multiple commands together using pipes (similarly to pipes in Unix). In R the pipe is represented by |> (or %>%). The way the pipe works is that the output of one function is sent as the input to the next function.\nTaking the examples from the previous section, we could chain all those commands like this:\n\nmissing_intervals |> \n mutate(length = (end - start) + 1) |> \n select(seqID, start, end, length)\n\n# A tibble: 132 × 4\n seqID start end length\n <chr> <dbl> <dbl> <dbl>\n 1 CH01 1 54 54\n 2 CH01 1193 1264 72\n 3 CH01 4143 4322 180\n 4 CH01 6248 6294 47\n 5 CH01 7561 7561 1\n 6 CH01 9243 9311 69\n 7 CH01 10367 10367 1\n 8 CH01 11361 11370 10\n 9 CH01 13599 13613 15\n10 CH01 16699 16758 60\n# ℹ 122 more rows\n\n\nIf you want to update the missing_intervals table, then you need to use <- at the beggining of the chain of pipes:\n\nmissing_intervals <- missing_intervals |> \n mutate(length = (end - start) + 1) |> \n select(seqID, start, end, length)\n\n\n\nGrouped Summaries\nWe can calculate summaries of the data (e.g. mean, standard deviation, maximum, minimum) per group (e.g. per sample) using a pair of functions together. For example:\n\n# mean and maximum interval length per sample\nmissing_intervals |> \n # for each sample\n group_by(seqID) |> \n # calculate summary statistics\n summarise(max_length = max(length),\n min_length = min(length),\n mean_length = mean(length))\n\n# A tibble: 7 × 4\n seqID max_length min_length mean_length\n <chr> <dbl> <dbl> <dbl>\n1 CH01 238 1 57 \n2 CH02 274 2 66 \n3 CH03 279 10 70.4\n4 CH04 1011 1 178. \n5 CH05 1221 11 200 \n6 CH06 974 1 166. \n7 CH07 5130 1 343. \n\n\nAs before, if we wanted to save this result in a new object, we would need to use <-:\n\nintervals_summary <- missing_intervals |> \n # for each sample\n group_by(seqID) |> \n # calculate summary statistics\n summarise(max_length = max(length),\n min_length = min(length),\n mean_length = mean(length)) |> \n # and rename the seqID column\n rename(sample = seqID)\n\nNotice in this case we also renamed the column called seqID to be named sample instead (this will be useful for the exercise later on).\nAnother useful function is count(), which counts how many times the values in a column appear on a data frame. For example, if we wanted to know how many missing intervals each sample had, we could do it like this:\n\nmissing_intervals |> \n count(seqID)\n\n# A tibble: 7 × 2\n seqID n\n <chr> <int>\n1 CH01 16\n2 CH02 19\n3 CH03 16\n4 CH04 21\n5 CH05 15\n6 CH06 16\n7 CH07 29\n\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nModify the following code:\n\nmissing_intervals |> \n count(seqID)\n\nTo also:\n\nrename the column seqID to be named sample instead.\nsave the output in an object called intervals_count.\n\n\n\n\n\n\n\nAnswer\n\n\n\n\n\n\n\nTo rename the column, we could use the rename() function:\n\nmissing_intervals |> \n count(seqID) |> \n rename(sample = seqID)\n\n# A tibble: 7 × 2\n sample n\n <chr> <int>\n1 CH01 16\n2 CH02 19\n3 CH03 16\n4 CH04 21\n5 CH05 15\n6 CH06 16\n7 CH07 29\n\n\nAnd to save this output to a new object, we need to use <-:\n\nintervals_count <- missing_intervals |> \n count(seqID) |> \n rename(sample = seqID)\n\n\n\n\n\n\n\n\n\n\n\n\n\nJoining Tables\nWe can join multiple tables together based on a common identifier. There are different types of join operations, depending on what we want to achieve.\nTake these two tables as an example (these tables come pre-loaded with tidyverse):\n\nband_members\n\n# A tibble: 3 × 2\n name band \n <chr> <chr> \n1 Mick Stones \n2 John Beatles\n3 Paul Beatles\n\nband_instruments\n\n# A tibble: 3 × 2\n name plays \n <chr> <chr> \n1 John guitar\n2 Paul bass \n3 Keith guitar\n\n\nHere are some different ways we can join these tables:\n\n# keep all records from both tables\nfull_join(band_members, band_instruments, by = \"name\")\n\n# A tibble: 4 × 3\n name band plays \n <chr> <chr> <chr> \n1 Mick Stones <NA> \n2 John Beatles guitar\n3 Paul Beatles bass \n4 Keith <NA> guitar\n\n# keep all records from the first table\nleft_join(band_members, band_instruments, by = \"name\")\n\n# A tibble: 3 × 3\n name band plays \n <chr> <chr> <chr> \n1 Mick Stones <NA> \n2 John Beatles guitar\n3 Paul Beatles bass \n\n# keep all records from the second table\nright_join(band_members, band_instruments, by = \"name\")\n\n# A tibble: 3 × 3\n name band plays \n <chr> <chr> <chr> \n1 John Beatles guitar\n2 Paul Beatles bass \n3 Keith <NA> guitar\n\n# keep only the records occurring in both tables\ninner_join(band_members, band_instruments, by = \"name\")\n\n# A tibble: 2 × 3\n name band plays \n <chr> <chr> <chr> \n1 John Beatles guitar\n2 Paul Beatles bass \n\n\nIn each case, if there was no match between the two tables, the cells are filled with missing values NA.\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nIn this exercise we will join the intervals_summary table we created earlier, with the metadata table that contains information about our samples.\n\nRead the your metadata table sample_info.csv into R and save it as an object called sample_info.\n\n\nHint\n\nRemember that you can use the read_csv() function to read CSV files into R.\n\nJoin the sample_info table that you just imported with the intervals_summary table we created earlier. Save the output to the intervals_summary table (this will update the table).\n\n\nYou can use the left_join() function to achieve this, using the “sample” column as the identifier column used to join the two tables.\n\n\n\n\n\n\n\n\n\nAnswer\n\n\n\n\n\n\n\nAnswer 1\nTo read the metadata CSV file, we use the read_csv() function, and use <- to save the output in an object called sample_info:\n\nsample_info <- read_csv(\"sample_info.csv\")\n\nRows: 7 Columns: 4\n── Column specification ────────────────────────────────────────────────────────\nDelimiter: \",\"\nchr (3): sample, country, sequencing_instrument\ndate (1): collection_date\n\nℹ Use `spec()` to retrieve the full column specification for this data.\nℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.\n\n\nWe can see the content of our file by typing its name in the console. Here is the file in our example data (yours might look different):\n\nsample_info\n\n# A tibble: 7 × 4\n sample collection_date country sequencing_instrument\n <chr> <date> <chr> <chr> \n1 CH01 2021-11-09 Switzerland GridION \n2 CH04 2021-12-20 Switzerland GridION \n3 CH05 2021-12-20 Switzerland GridION \n4 CH06 2021-12-20 Switzerland GridION \n5 CH07 2021-12-20 Switzerland GridION \n6 CH02 2021-12-21 Switzerland GridION \n7 CH03 2021-12-22 Switzerland GridION \n\n\nAnswer 2\nTo join the two tables together, we can use one of the *_join() functions. In this case it doesn’t matter which function we use, because both tables have the same sample IDs. But, for example, let’s say we only wanted to retain the samples that are in common across both tables. In that case, we would use inner_join():\n\nintervals_summary <- inner_join(intervals_summary, sample_info, by = \"sample\")" + }, + { + "objectID": "materials/appendices/quick_r.html#data-visualisation", + "href": "materials/appendices/quick_r.html#data-visualisation", + "title": "R fundamentals", + "section": "Data Visualisation", + "text": "Data Visualisation\nFor this section, we will use another table, which contains some of the metrics that we can collect from our consensus pipeline:\n\n# read the table\nqc_metrics <- read_tsv(\"consensus_metrics.tsv\")\n\nRows: 7 Columns: 6\n── Column specification ────────────────────────────────────────────────────────\nDelimiter: \"\\t\"\nchr (3): sample, qc_status, lineage\ndbl (3): n_mapped_reads, median_depth, pct_missing\n\nℹ Use `spec()` to retrieve the full column specification for this data.\nℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.\n\n# preview the table\nqc_metrics\n\n# A tibble: 7 × 6\n sample n_mapped_reads median_depth pct_missing qc_status lineage\n <chr> <dbl> <dbl> <dbl> <chr> <chr> \n1 CH01 43900 248 3.05 good BA.1 \n2 CH02 42841 159 4.20 good BA.1 \n3 CH03 40079 160 3.77 good BA.1 \n4 CH04 50902 164 12.5 bad BA.1 \n5 CH05 32020 133 10.0 bad BA.1 \n6 CH06 46277 177 8.87 mediocre BA.1.1 \n7 CH07 15867 41 33.2 bad None \n\n\nWe can build plots from our tables using the ggplot2 package (which is also part of the tidyverse).\nTo build a ggplot, we usually need at least three things:\n\nThe data frame we want to use for the plot (the data).\nThe columns we want to visualise as our x-axis, y-axis, and colours (these are called aesthetics).\nThe type of shape that we want to plot (these are called geometries).\n\nFor example, let’s try to make a plot to show the relationship between total number of counts and the median depth of sequencing in these samples:\n\nggplot(data = qc_metrics, aes(x = n_mapped_reads, y = median_depth))\n\n\n\n\nWhen we do this, we simply get an empty plot, with x and y axis, but nothing drawn on it. To draw something on the plot, we add (literally with +) geometries to our plot. In this case, we can use the geom_point() geometry, which draws “points” on the plot:\n\nggplot(data = qc_metrics, aes(x = n_mapped_reads, y = median_depth)) +\n geom_point()\n\n\n\n\nThere are many geometries available with ggplot:\n\ngeom_point() draws points.\ngeom_boxplot() draws a boxplot.\ngeom_histogram() draws a histogram.\ngeom_col() draws a barplot (this one is named a little strangely, but “col” means it draws “columns” or bars).\n\nWe can further modify the look to the plot by adding other aesthetics such as the colour of the points. For example, let’s say we wanted to colour our points according to their “QC Status”:\n\nggplot(data = qc_metrics, \n aes(x = n_mapped_reads, y = median_depth, colour = qc_status)) +\n geom_point()\n\n\n\n\nFinally, we may sometimes want to change the labels of our plot. In that case, we can add the labs() function to our plotting code:\n\nggplot(data = qc_metrics, \n aes(x = n_mapped_reads, y = median_depth, colour = qc_status)) +\n geom_point() +\n labs(x = \"Number Mapped Reads\", \n y = \"Median Sequencing Depth\", \n colour = \"Nextclade QC Status\")\n\n\n\n\n\n\n\n\n\n\nExercise\n\n\n\n\n\n\n\nModify the plot we just did, to show the relationship between median depth of sequencing (x-axis) and percentage of missing bases (y-axis). Colour the points according to the lineage column.\n\n\n\n\n\n\nAnswer\n\n\n\n\n\n\n\nHere is the modified code:\n\nggplot(data = qc_metrics, \n aes(x = median_depth, y = pct_missing, colour = lineage)) +\n geom_point() +\n labs(x = \"Median Sequencing Depth\", \n y = \"% Missing Bases\", \n colour = \"Pango Lineage\")\n\n\n\n\nThe things we have changed were the aesthetics and labels." + }, + { + "objectID": "materials/appendices/tools_and_resources.html#amplicon-primer-schemes", + "href": "materials/appendices/tools_and_resources.html#amplicon-primer-schemes", + "title": "Tools and Resources", + "section": "Amplicon Primer Schemes", + "text": "Amplicon Primer Schemes\nFor amplicon sequencing, there are several protocols and commercial kits available. We try to summarise some of the common ones below.\n\nARTIC\n\nV5.3.2V4.1V4V3V2V1Midnight\n\n\nARTIC primer scheme version 5.3.2 (link).\nTo analyse with nf-core/viralrecon, add these options to the command:\n--genome 'MN908947.3' \\\n--primer_set artic \\\n--primer_set_version 5.3.2 \\\n--schema_ignore_params 'genomes,primer_set_version'\nOr, alternatively, you can use the direct links to the FASTA/BED files:\n--fasta 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V5.3.2/SARS-CoV-2.reference.fasta' \\\n--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \\\n--primer_bed 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V5.3.2/SARS-CoV-2.scheme.bed'\n\n\nARTIC primer scheme version 4.1 (link).\nTo analyse with nf-core/viralrecon, add these options to the command:\n--genome 'MN908947.3' \\\n--primer_set artic \\\n--primer_set_version 4.1\nOr, alternatively, you can use the direct links to the FASTA/BED files:\n--fasta 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V4.1/SARS-CoV-2.reference.fasta' \\\n--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \\\n--primer_bed 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V4.1/SARS-CoV-2.scheme.bed'\n\n\nARTIC primer scheme version 4 (link).\nTo analyse with nf-core/viralrecon, add these options to the command:\n--genome 'MN908947.3' \\\n--primer_set artic \\\n--primer_set_version 4\nOr, alternatively, you can use the direct links to the FASTA/BED files:\n--fasta 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V4/SARS-CoV-2.reference.fasta' \\\n--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \\\n--primer_bed 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V4/SARS-CoV-2.scheme.bed'\n\n\nARTIC primer scheme version 3 (link).\nTo analyse with nf-core/viralrecon, add these options to the command:\n--genome 'MN908947.3' \\\n--primer_set artic \\\n--primer_set_version 3\nOr, alternatively, you can use the direct links to the FASTA/BED files:\n--fasta 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V3/nCoV-2019.reference.fasta' \\\n--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \\\n--primer_bed 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V3/nCoV-2019.primer.bed'\n\n\nARTIC primer scheme version 2 (link).\nTo analyse with nf-core/viralrecon, add these options to the command:\n--genome 'MN908947.3' \\\n--primer_set artic \\\n--primer_set_version 2\nOr, alternatively, you can use the direct links to the FASTA/BED files:\n--fasta 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V2/nCoV-2019.reference.fasta' \\\n--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \\\n--primer_bed 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V2/nCoV-2019.primer.bed'\n\n\nARTIC primer scheme version 1 (link).\nTo analyse with nf-core/viralrecon, add these options to the command:\n--genome 'MN908947.3' \\\n--primer_set artic \\\n--primer_set_version 1\nOr, alternatively, you can use the direct links to the FASTA/BED files:\n--fasta 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V1/nCoV-2019.reference.fasta' \\\n--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \\\n--primer_bed 'https://github.com/artic-network/artic-ncov2019/raw/master/primer_schemes/nCoV-2019/V1/nCoV-2019.primer.bed'\n\n\nPrimers for the “Midnight” protocol, also known as “1200” as it produces fragments that are ~1200bp long. This primer scheme is optimised for ONT platforms (link).\nTo analyse with nf-core/viralrecon, add these options to the command:\n--genome 'MN908947.3' \\\n--primer_set artic \\\n--primer_set_version 1200\nOr, alternatively, you can use the direct links to the FASTA/BED files:\n--fasta 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/primer_schemes/artic/nCoV-2019/V1200/nCoV-2019.reference.fasta' \\\n--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \\\n--primer_bed 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/primer_schemes/artic/nCoV-2019/V1200/nCoV-2019.bed'\n\n\n\n\n\nNEB VarSkip\n\n1a2a2bLong 1a\n\n\nNEB’s VarSkip kit, primer version 1a (link).\nFor analysis with nf-core/viralrecon the direct link to the FASTA and BED files can be given:\n--fasta 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V1a/NEB_VarSkip.reference.fasta' \\\n--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \\\n--primer_bed 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V1a/NEB_VarSkip.scheme.bed'\n\n\nNEB’s VarSkip kit, primer version 2a (link).\nFor analysis with nf-core/viralrecon the direct link to the FASTA and BED files can be given:\n--fasta 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V2a/NEB_VarSkip.reference.fasta' \\\n--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \\\n--primer_bed 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V2a/NEB_VarSkip.scheme.bed'\n\n\nNEB’s VarSkip kit, primer version 2b (link).\nFor analysis with nf-core/viralrecon the direct link to the FASTA and BED files can be given:\n--fasta 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V2b/NEB_VarSkip.reference.fasta' \\\n--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \\\n--primer_bed 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V2b/NEB_VarSkip.scheme.bed'\n\n\nNEB’s VarSkip kit, primer version 1a, long primers (link).\nFor analysis with nf-core/viralrecon the direct link to the FASTA and BED files can be given:\n--fasta 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V1a-long/NEB_VarSkip.reference.fasta' \\\n--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/MN908947.3/GCA_009858895.3_ASM985889v3_genomic.200409.gff.gz' \\\n--primer_bed 'https://raw.githubusercontent.com/nebiolabs/VarSkip/main/schemes/NEB_VarSkip/V1a-long/NEB_VarSkip.scheme.bed'\n\n\n\n\n\nAtoplex\nATOPlex kit.\nFor analysis with nf-core/viralrecon the direct link to the FASTA and BED files can be given:\n--fasta 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/NC_045512.2/GCF_009858895.2_ASM985889v3_genomic.200409.fna.gz' \\\n--gff 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/NC_045512.2/GCF_009858895.2_ASM985889v3_genomic.200409.gff.gz' \\\n--primer_bed 'https://github.com/nf-core/test-datasets/raw/viralrecon/genome/NC_045512.2/amplicon/nCoV-2019.atoplex.V1.bed'\n\n\nIllumina COVIDseq\n\nCOVIDseqCOVIDseq (RUO version)\n\n\nThe original COVIDseq kit uses ARTIC V3 primers.\nSee section above for nf-core/viralrecon options.\n\n\nThe COVIDseq RUO version uses ARTIC V4 primers.\nSee section above for nf-core/viralrecon options.\n\n\n\n\n\nSWIFT / xGEN\nThe “xGen SARS-CoV-2 Amplicon Panels”, previously called “SWIFT Normalase”, is commercialised by IDT. Unfortunately IDT does not make their BED files available publicly. If you purchase a kit from IDT, the company should provide you with the correct BED file to process your samples.\nNOTE: in the future we will provide a link to a BED file with approximate coordinates, inferred bioinformatically. This can be useful if re-analysing public data." + }, + { + "objectID": "materials/appendices/tools_and_resources.html#data-resources", + "href": "materials/appendices/tools_and_resources.html#data-resources", + "title": "Tools and Resources", + "section": "Data Resources", + "text": "Data Resources\n\noutbreak.info - interactive exploration of global SARS-CoV-2 data.\nNextstrain SARS-CoV-2 resources - analysis and datasets curated by the Nextrain team.\nData portals:\n\nThe European COVID-19 Data Platform\nNCBI SARS-CoV-2 Resources\nGISAID\n\nUK-specific resources:\n\nCOVID-19 Genomics UK Consortium\nCOVID–19 Genomic Surveillance built by the Wellcome Sanger Institute." + }, + { + "objectID": "materials/appendices/tools_and_resources.html#web-tools", + "href": "materials/appendices/tools_and_resources.html#web-tools", + "title": "Tools and Resources", + "section": "Web Tools", + "text": "Web Tools\n\nTaxonium - a tool for large tree visualisation." + }, + { + "objectID": "materials/appendices/tools_and_resources.html#other-courses", + "href": "materials/appendices/tools_and_resources.html#other-courses", + "title": "Tools and Resources", + "section": "Other Courses", + "text": "Other Courses\n\nMutation calling, viral genome reconstruction and lineage/clade assignment from SARS-CoV-2 sequencing data - using the Galaxy platform.\nThe Power of Genomics to Understand the COVID-19 Pandemic - a general introduction to the COVID-19 pandemic.\nCOVID-10 Data Analysis - ARTIC and CLIMB-BIG-DATA joint workshop with a series of lectures and tutorials available." + } +] \ No newline at end of file diff --git a/setup.html b/setup.html new file mode 100644 index 0000000..e1dc8aa --- /dev/null +++ b/setup.html @@ -0,0 +1,812 @@ + + + + + + + + + +SARS Genomic Surveillance + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Data & Software

+
+ + + +
+ + + + +
+ + +
+ +
+
+
+ +
+
+Workshop Attendees +
+
+
+

If you are attending one of our workshops, we will provide a training environment with all of the required software and data. You do not need to download or install anything ahead of the workshop.
+If you want to setup your own computer to run the analysis demonstrated on this course, you can follow the instructions below.

+
+
+
+

Software

+

The software installation for this course is quite complex and detailed in a separate section.

+
+
+

Data

+

Different datasets are used throughout these materials. This page provides links to download each dataset, with a brief description for each of them. We have split the data between sequencing platforms.

+
+
+

Illumina

+
+

UK Sequences

+

These samples were downloaded from SRA and include samples collected in the UK. These data are used in the following sections of the materials: Consensus Assembly, Lineages and Variants and Building Phylogenies.

+ +
+ +Click here to see details of how the data was downloaded from public repositories + +

We obtained these data from the SRA repository, using the fastq-dump command as follows:

+
fastq-dump --split-3 --gzip ERR5728910 ERR5728911 ERR5728913 ERR5742126 ERR5742297 ERR5742457 ERR5742549 ERR5742553 ERR5761182 ERR5761193 ERR5765358 ERR5770082 ERR5855061 ERR5855065 ERR5855555 ERR5914874 ERR5921129 ERR5921248 ERR5921268 ERR5921355 ERR5921612 ERR5925864 ERR5926784 ERR5932087 ERR5932097 ERR5932290 ERR5932412 ERR5932418 ERR5932680 ERR5932985 ERR5933082 ERR5933090 ERR6106244 ERR6083647 ERR6085882 ERR6086247 ERR6104816 ERR6105221 ERR6105244 ERR6105337 ERR6105341 ERR6106514 ERR6106801 ERR6107074 ERR6128896 ERR6128978 ERR6129122 ERR6129126
+
+


+
+
+

South Africa

+

These samples were downloaded from SRA and include samples collected between Nov and Dec 2021 in South Africa. These data are used in the worked example South Africa (Illumina).

+

We provide two versions of the data: the full data includes 24 samples (more realistic sample size), whereas the small version includes only 8 samples (quicker processing time for practising).

+ +
+ +Click here to see details of how the data was downloaded from public repositories + +

We obtained these data from the SRA repository, using the fastq-dump command as follows:

+
fastq-dump --split-3 --gzip SRR17051908 SRR17051923 SRR17051916 SRR17051953 SRR17051951 SRR17051935 SRR17051932 SRR17054503 SRR17088930 SRR17088928 SRR17088924 SRR17088917 SRR17461712 SRR17461700 SRR17712997 SRR17712994 SRR17712779 SRR17701841 SRR17712711 SRR17701832 SRR17701890 SRR17712607 SRR17712594 SRR17712442 SRR17712435 SRR17712343 SRR17712341 SRR17712321 SRR17712313 SRR17712312 SRR17712387 SRR17970983 SRR17973983 SRR17973948 SRR17973937 SRR17973974 SRR17974004 SRR17974001 SRR17974000 SRR17973999 SRR17973998 SRR17973997 SRR17973996 SRR17973995 SRR17973992 SRR17973991 SRR17973989 SRR17973988
+
+


+
+
+

EQA panels

+

The standard panels for External Quality Assessment provided by NEQAS were sequenced on an Illumina platform by UKHSA and used to generate four sets of data for training purposes. These data can be used for the extended exercise: EQA (Exercise).

+

We provide a link to each of the four datasets below (the datasets are very similar to each other):

+ +
+
+
+

Nanopore

+
+

India

+

These samples were downloaded from SRA and include samples collected in India and sequenced by the National Institute of Mental Health and Neurosciences. These data are used for the exercises in the following sections of the materials: Consensus Assembly, Lineages and Variants and Building Phylogenies.

+
    +
  • Download (zip file) +
      +
    • Platform: MinION
    • +
    • Nanopore chemistry: not specified (we assumed 9.4.1)
    • +
    • Primer set: not specified (we assumed ARTIC primers v3)
    • +
    • Guppy version: not specified (we assumed 3.6 or higher)
    • +
    • Basecalling mode: not specified (we assumed “high”)
    • +
  • +
+

Note that several details needed to run the medaka pipeline were not provided in the public repository. We arbitrarily picked parameters as detailed above, but this is for demonstration purposes only.

+
+ +Click here to see details of how the data was downloaded from public repositories + +

We obtained these data from the SRA repository, using the fastq-dump command as follows:

+
fastq-dump --split-3 --gzip SRR14494107 SRR14493634 SRR14493632 SRR14493631 SRR14493707 SRR14493705 SRR14493730 SRR14493729 SRR14493728 SRR14493727 SRR14493726 SRR14493725 SRR14493724 SRR14493723 SRR14493722 SRR14493721 SRR14493719 SRR14493718 SRR14493717 SRR14493716 SRR14493715 SRR14493714 SRR14493713 SRR14493712 SRR14493711 SRR14494106 SRR14494095 SRR14494092 SRR14494091 SRR14494090 SRR14494089 SRR14494088 SRR14494087 SRR14494086 SRR14494105 SRR14494104 SRR14494103 SRR14494102 SRR14494101 SRR14494100 SRR14493626 SRR14494099 SRR14494098 SRR14494097 SRR14494096 SRR14494094 SRR14494093
+
+


+
+
+

Switzerland

+

These samples were downloaded from SRA and include samples collected between Nov 2021 and Jan 2022 in Switzerland and sequenced by the Institute for Infectious Diseases, University of Bern. These data are used in the worked example Switzerland (Nanopore).

+

We provide two versions of the data: the full data includes 65 samples (more realistic sample size), whereas the small version includes only 10 samples (quicker processing time for practising).

+ +

Note that several details needed to run the medaka pipeline were not provided in the public repository. We arbitrarily picked parameters as detailed above, but this is for demonstration purposes only.

+
+ +Click here to see details of how the data was downloaded from public repositories + +

We obtained these data from the SRA repository, using the fastq-dump command as follows:

+
fastq-dump --split-3 --gzip ERR8971298 ERR8961150 ERR8961147 ERR8961133 ERR8961130 ERR8961129 ERR8961128 ERR8961124 ERR8961123 ERR8961116 ERR8961115 ERR8961114 ERR8961112 ERR8961110 ERR8961065 ERR8961062 ERR8961333 ERR8959962 ERR8959961 ERR8959960 ERR8959959 ERR8959958 ERR8959957 ERR8959956 ERR8959955 ERR8959953 ERR8959952 ERR8959950 ERR8959949 ERR8959948 ERR8959946 ERR8959945 ERR8959943 ERR8959942 ERR8959940 ERR8959939 ERR8959938 ERR8959937 ERR8959936 ERR8959934 ERR8959933 ERR8959931 ERR8959927 ERR8959926 ERR8959925 ERR8959912 ERR8959911 ERR8959905 ERR8959901 ERR8959892 ERR8960229 ERR8960215 ERR8960216 ERR8960217 ERR8960218 ERR8960219 ERR8960220 ERR8960221 ERR8960223 ERR8959343 ERR8959341 ERR8959330 ERR8959327
+
+


+
+
+

EQA panels

+

The standard panels for External Quality Assessment provided by NEQAS were sequenced on an ONT platform by TODO. These data can be used for the extended exercise: EQA (Exercise).

+

We provide a link to datasets corresponding to two different runs:

+
    +
  • Dataset 1 (zip file) +
      +
    • Platform: PromethION
    • +
    • Nanopore chemistry: 9.4.1
    • +
    • Primer set: midnight
    • +
    • Guppy version: 6.6
    • +
    • Basecalling mode: “fast”
    • +
  • +
  • Dataset 2 (zip file) +
      +
    • Platform: GridION
    • +
    • Nanopore chemistry: 9.4.1
    • +
    • Primer set: 4.1
    • +
    • Guppy version: 6.6
    • +
    • Basecalling mode: “high”
    • +
  • +
+

We thank the collaborating institutions for sharing their trainining data with us.

+ + +
+
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/site_libs/bootstrap/bootstrap-icons.css b/site_libs/bootstrap/bootstrap-icons.css new file mode 100644 index 0000000..94f1940 --- /dev/null +++ b/site_libs/bootstrap/bootstrap-icons.css @@ -0,0 +1,2018 @@ +@font-face { + font-display: block; + font-family: "bootstrap-icons"; + src: +url("./bootstrap-icons.woff?2ab2cbbe07fcebb53bdaa7313bb290f2") 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-1::before { content: "\f2a5"; } +.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-1::before { content: "\f68a"; } +.bi-envelope-check-fill::before { content: "\f68b"; } +.bi-envelope-check::before { content: "\f68c"; } +.bi-envelope-dash-1::before { content: "\f68d"; } +.bi-envelope-dash-fill::before { content: "\f68e"; } +.bi-envelope-dash::before { content: "\f68f"; } +.bi-envelope-exclamation-1::before { content: "\f690"; } +.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-1::before { content: "\f695"; } +.bi-envelope-slash-fill::before { content: "\f696"; } +.bi-envelope-slash::before { content: "\f697"; } +.bi-envelope-x-1::before { content: "\f698"; } +.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-mortorboard-fill::before { content: "\f6a2"; } +.bi-mortorboard::before { content: "\f6a3"; } +.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-1::before { content: "\f6b6"; } +.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-1::before { content: "\f6c2"; } +.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-1::before { content: "\f6e0"; } +.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-ssd-fill::before { content: "\f6ed"; } +.bi-ssd::before { content: "\f6ee"; } +.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-1::before { content: "\f759"; } +.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-1::before { content: "\f769"; } +.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-1::before { content: "\f794"; } +.bi-1-circle-fill-1::before { content: "\f795"; } +.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-1::before { content: "\f79a"; } +.bi-2-circle-fill-1::before { content: "\f79b"; } +.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-1::before { content: "\f7a0"; } +.bi-3-circle-fill-1::before { content: "\f7a1"; } +.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-1::before { content: "\f7a6"; } +.bi-4-circle-fill-1::before { content: "\f7a7"; } +.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-1::before { content: "\f7ac"; } +.bi-5-circle-fill-1::before { content: "\f7ad"; } +.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-1::before { content: "\f7b2"; } +.bi-6-circle-fill-1::before { content: "\f7b3"; } +.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-1::before { content: "\f7b8"; } +.bi-7-circle-fill-1::before { content: "\f7b9"; } +.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-1::before { content: "\f7be"; } +.bi-8-circle-fill-1::before { content: "\f7bf"; } +.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-1::before { content: "\f7c4"; } +.bi-9-circle-fill-1::before { content: "\f7c5"; } +.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-1::before { content: "\f7d8"; } +.bi-c-circle-fill-1::before { content: "\f7d9"; } +.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-1::before { content: "\f7e4"; } +.bi-cc-circle-fill-1::before { content: "\f7e5"; } +.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-1::before { content: "\f7f8"; } +.bi-h-circle-fill-1::before { content: "\f7f9"; } +.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-1::before { content: "\f802"; } +.bi-p-circle-fill-1::before { content: "\f803"; } +.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-1::before { content: "\f80c"; } +.bi-r-circle-fill-1::before { content: "\f80d"; } +.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"; } diff --git a/site_libs/bootstrap/bootstrap-icons.woff b/site_libs/bootstrap/bootstrap-icons.woff new file mode 100644 index 0000000..18d21d4 Binary files /dev/null and b/site_libs/bootstrap/bootstrap-icons.woff differ diff --git a/site_libs/bootstrap/bootstrap.min.css b/site_libs/bootstrap/bootstrap.min.css new file mode 100644 index 0000000..47d6de8 --- /dev/null +++ b/site_libs/bootstrap/bootstrap.min.css @@ -0,0 +1,10 @@ +@import"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css";/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--bs-blue: #0d6efd;--bs-indigo: #6610f2;--bs-purple: #6f42c1;--bs-pink: #d63384;--bs-red: #dc3545;--bs-orange: #fd7e14;--bs-yellow: #ffc107;--bs-green: #198754;--bs-teal: #20c997;--bs-cyan: #0dcaf0;--bs-white: #ffffff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #343a40;--bs-gray-900: #212529;--bs-default: #dee2e6;--bs-primary: #0d6efd;--bs-secondary: #6c757d;--bs-success: #198754;--bs-info: #0dcaf0;--bs-warning: #ffc107;--bs-danger: #dc3545;--bs-light: #f8f9fa;--bs-dark: #212529;--bs-default-rgb: 222, 226, 230;--bs-primary-rgb: 13, 110, 253;--bs-secondary-rgb: 108, 117, 125;--bs-success-rgb: 25, 135, 84;--bs-info-rgb: 13, 202, 240;--bs-warning-rgb: 255, 193, 7;--bs-danger-rgb: 220, 53, 69;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 33, 37, 41;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-body-color-rgb: 0, 0, 0;--bs-body-bg-rgb: 255, 255, 255;--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--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: var(--bs-font-sans-serif);--bs-body-font-size: 1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: black;--bs-body-bg: #ffffff}*,*::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;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}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],abbr[data-bs-original-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:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:#003a41;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}a:hover{color:#002e34}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr /* rtl:ignore */;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f5f5f5;padding:.5rem;border:1px solid #dee2e6;border-radius:.25rem}pre code{background-color:rgba(0,0,0,0);font-size:inherit;color:inherit;word-break:normal}code{font-size:0.875em;color:#9753b8;background-color:#f5f5f5;border-radius:.25rem;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#212529;border-radius:.2em}kbd kbd{padding:0;font-size:1em;font-weight:700}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:#6c757d;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]::-webkit-calendar-picker-indicator{display:none}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]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-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:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:#6c757d}.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-bg: transparent;--bs-table-accent-bg: transparent;--bs-table-striped-color: black;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: black;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: black;--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#000;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid gray}.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-accent-bg: var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg: var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg: var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg: #cfe2ff;--bs-table-striped-bg: #c5d7f2;--bs-table-striped-color: #000;--bs-table-active-bg: #bacbe6;--bs-table-active-color: #000;--bs-table-hover-bg: #bfd1ec;--bs-table-hover-color: #000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg: #e2e3e5;--bs-table-striped-bg: #d7d8da;--bs-table-striped-color: #000;--bs-table-active-bg: #cbccce;--bs-table-active-color: #000;--bs-table-hover-bg: #d1d2d4;--bs-table-hover-color: #000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg: #d1e7dd;--bs-table-striped-bg: #c7dbd2;--bs-table-striped-color: #000;--bs-table-active-bg: #bcd0c7;--bs-table-active-color: #000;--bs-table-hover-bg: #c1d6cc;--bs-table-hover-color: #000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg: #cff4fc;--bs-table-striped-bg: #c5e8ef;--bs-table-striped-color: #000;--bs-table-active-bg: #badce3;--bs-table-active-color: #000;--bs-table-hover-bg: #bfe2e9;--bs-table-hover-color: #000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg: #fff3cd;--bs-table-striped-bg: #f2e7c3;--bs-table-striped-color: #000;--bs-table-active-bg: #e6dbb9;--bs-table-active-color: #000;--bs-table-hover-bg: #ece1be;--bs-table-hover-color: #000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg: #f8d7da;--bs-table-striped-bg: #eccccf;--bs-table-striped-color: #000;--bs-table-active-bg: #dfc2c4;--bs-table-active-color: #000;--bs-table-hover-bg: #e5c7ca;--bs-table-hover-color: #000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg: #f8f9fa;--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:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg: #212529;--bs-table-striped-bg: #2c3034;--bs-table-striped-color: #ffffff;--bs-table-active-bg: #373b3e;--bs-table-active-color: #ffffff;--bs-table-hover-bg: #323539;--bs-table-hover-color: #ffffff;color:#fff;border-color:#373b3e}.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:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#000;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;border-radius:.25rem;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:#000;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#000;background-color:#e9ecef;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:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#000;background-color:#e9ecef;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::-webkit-file-upload-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#000;background-color:rgba(0,0,0,0);border:solid rgba(0,0,0,0);border-width:1px 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 + 2px);padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#000;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.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 #000}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem;border-radius:.2em}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:.3rem}.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-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;color-adjust:exact;-webkit-print-color-adjust:exact}.form-check-input[type=checkbox],.shiny-input-container .checkbox input[type=checkbox],.shiny-input-container .checkbox-inline input[type=checkbox],.shiny-input-container .radio input[type=checkbox],.shiny-input-container .radio-inline input[type=checkbox]{border-radius:.25em}.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:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.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:#0d6efd;border-color:#0d6efd}.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]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-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]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23ffffff'/%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:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' 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{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{width:2em;margin-left:-2.5em;background-image: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");background-position:left center;border-radius:2em;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{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e")}.form-check-inline,.shiny-input-container .checkbox-inline,.shiny-input-container .radio-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}.form-range{width:100%;height:1.5rem;padding:0;background-color:rgba(0,0,0,0);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;background-color:#0d6efd;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;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{padding:1rem .75rem}.form-floating>.form-control::placeholder{color:rgba(0,0,0,0)}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-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-select~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.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{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{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.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:#000;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.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;border-radius:.3rem}.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;border-radius:.2em}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#198754}.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:rgba(25,135,84,.9);border-radius:.25rem}.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:#198754;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='%23198754' d='M2.3 6.73L.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:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.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:#198754}.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"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.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-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:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#198754}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#198754}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group .form-control:valid,.input-group .form-control.is-valid,.was-validated .input-group .form-select:valid,.input-group .form-select.is-valid{z-index:1}.was-validated .input-group .form-control:valid:focus,.input-group .form-control.is-valid:focus,.was-validated .input-group .form-select:valid:focus,.input-group .form-select.is-valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#dc3545}.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:rgba(220,53,69,.9);border-radius:.25rem}.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:#dc3545;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='%23dc3545'%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='%23dc3545' 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:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.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:#dc3545}.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"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),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='%23dc3545'%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='%23dc3545' stroke='none'/%3e%3c/svg%3e");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:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#dc3545}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#dc3545}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group .form-control:invalid,.input-group .form-control.is-invalid,.was-validated .input-group .form-select:invalid,.input-group .form-select.is-invalid{z-index:2}.was-validated .input-group .form-control:invalid:focus,.input-group .form-control.is-invalid:focus,.was-validated .input-group .form-select:invalid:focus,.input-group .form-select.is-invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#000;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;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;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:#000}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-default{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-default:hover{color:#000;background-color:#e3e6ea;border-color:#e1e5e9}.btn-check:focus+.btn-default,.btn-default:focus{color:#000;background-color:#e3e6ea;border-color:#e1e5e9;box-shadow:0 0 0 .25rem rgba(189,192,196,.5)}.btn-check:checked+.btn-default,.btn-check:active+.btn-default,.btn-default:active,.btn-default.active,.show>.btn-default.dropdown-toggle{color:#000;background-color:#e5e8eb;border-color:#e1e5e9}.btn-check:checked+.btn-default:focus,.btn-check:active+.btn-default:focus,.btn-default:active:focus,.btn-default.active:focus,.show>.btn-default.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(189,192,196,.5)}.btn-default:disabled,.btn-default.disabled{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:checked+.btn-primary,.btn-check:active+.btn-primary,.btn-primary:active,.btn-primary.active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:checked+.btn-primary:focus,.btn-check:active+.btn-primary:focus,.btn-primary:active:focus,.btn-primary.active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary:disabled,.btn-primary.disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:checked+.btn-secondary,.btn-check:active+.btn-secondary,.btn-secondary:active,.btn-secondary.active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:checked+.btn-secondary:focus,.btn-check:active+.btn-secondary:focus,.btn-secondary:active:focus,.btn-secondary.active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary:disabled,.btn-secondary.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:checked+.btn-success,.btn-check:active+.btn-success,.btn-success:active,.btn-success.active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:checked+.btn-success:focus,.btn-check:active+.btn-success:focus,.btn-success:active:focus,.btn-success.active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success:disabled,.btn-success.disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:checked+.btn-info,.btn-check:active+.btn-info,.btn-info:active,.btn-info.active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:checked+.btn-info:focus,.btn-check:active+.btn-info:focus,.btn-info:active:focus,.btn-info.active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info:disabled,.btn-info.disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:checked+.btn-warning,.btn-check:active+.btn-warning,.btn-warning:active,.btn-warning.active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:checked+.btn-warning:focus,.btn-check:active+.btn-warning:focus,.btn-warning:active:focus,.btn-warning.active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning:disabled,.btn-warning.disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:checked+.btn-danger,.btn-check:active+.btn-danger,.btn-danger:active,.btn-danger.active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:checked+.btn-danger:focus,.btn-check:active+.btn-danger:focus,.btn-danger:active:focus,.btn-danger.active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger:disabled,.btn-danger.disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:checked+.btn-light,.btn-check:active+.btn-light,.btn-light:active,.btn-light.active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:checked+.btn-light:focus,.btn-check:active+.btn-light:focus,.btn-light:active:focus,.btn-light.active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light:disabled,.btn-light.disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:checked+.btn-dark,.btn-check:active+.btn-dark,.btn-dark:active,.btn-dark.active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:checked+.btn-dark:focus,.btn-check:active+.btn-dark:focus,.btn-dark:active:focus,.btn-dark.active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark:disabled,.btn-dark.disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-default{color:#dee2e6;border-color:#dee2e6;background-color:rgba(0,0,0,0)}.btn-outline-default:hover{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-check:focus+.btn-outline-default,.btn-outline-default:focus{box-shadow:0 0 0 .25rem rgba(222,226,230,.5)}.btn-check:checked+.btn-outline-default,.btn-check:active+.btn-outline-default,.btn-outline-default:active,.btn-outline-default.active,.btn-outline-default.dropdown-toggle.show{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-check:checked+.btn-outline-default:focus,.btn-check:active+.btn-outline-default:focus,.btn-outline-default:active:focus,.btn-outline-default.active:focus,.btn-outline-default.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(222,226,230,.5)}.btn-outline-default:disabled,.btn-outline-default.disabled{color:#dee2e6;background-color:rgba(0,0,0,0)}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd;background-color:rgba(0,0,0,0)}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:checked+.btn-outline-primary,.btn-check:active+.btn-outline-primary,.btn-outline-primary:active,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:checked+.btn-outline-primary:focus,.btn-check:active+.btn-outline-primary:focus,.btn-outline-primary:active:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary:disabled,.btn-outline-primary.disabled{color:#0d6efd;background-color:rgba(0,0,0,0)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:checked+.btn-outline-secondary,.btn-check:active+.btn-outline-secondary,.btn-outline-secondary:active,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:checked+.btn-outline-secondary:focus,.btn-check:active+.btn-outline-secondary:focus,.btn-outline-secondary:active:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary:disabled,.btn-outline-secondary.disabled{color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-success{color:#198754;border-color:#198754;background-color:rgba(0,0,0,0)}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:checked+.btn-outline-success,.btn-check:active+.btn-outline-success,.btn-outline-success:active,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show{color:#fff;background-color:#198754;border-color:#198754}.btn-check:checked+.btn-outline-success:focus,.btn-check:active+.btn-outline-success:focus,.btn-outline-success:active:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success:disabled,.btn-outline-success.disabled{color:#198754;background-color:rgba(0,0,0,0)}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0;background-color:rgba(0,0,0,0)}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:checked+.btn-outline-info,.btn-check:active+.btn-outline-info,.btn-outline-info:active,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:checked+.btn-outline-info:focus,.btn-check:active+.btn-outline-info:focus,.btn-outline-info:active:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info:disabled,.btn-outline-info.disabled{color:#0dcaf0;background-color:rgba(0,0,0,0)}.btn-outline-warning{color:#ffc107;border-color:#ffc107;background-color:rgba(0,0,0,0)}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:checked+.btn-outline-warning,.btn-check:active+.btn-outline-warning,.btn-outline-warning:active,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:checked+.btn-outline-warning:focus,.btn-check:active+.btn-outline-warning:focus,.btn-outline-warning:active:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning:disabled,.btn-outline-warning.disabled{color:#ffc107;background-color:rgba(0,0,0,0)}.btn-outline-danger{color:#dc3545;border-color:#dc3545;background-color:rgba(0,0,0,0)}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:checked+.btn-outline-danger,.btn-check:active+.btn-outline-danger,.btn-outline-danger:active,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:checked+.btn-outline-danger:focus,.btn-check:active+.btn-outline-danger:focus,.btn-outline-danger:active:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger:disabled,.btn-outline-danger.disabled{color:#dc3545;background-color:rgba(0,0,0,0)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa;background-color:rgba(0,0,0,0)}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:checked+.btn-outline-light,.btn-check:active+.btn-outline-light,.btn-outline-light:active,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:checked+.btn-outline-light:focus,.btn-check:active+.btn-outline-light:focus,.btn-outline-light:active:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light:disabled,.btn-outline-light.disabled{color:#f8f9fa;background-color:rgba(0,0,0,0)}.btn-outline-dark{color:#212529;border-color:#212529;background-color:rgba(0,0,0,0)}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:checked+.btn-outline-dark,.btn-check:active+.btn-outline-dark,.btn-outline-dark:active,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show{color:#fff;background-color:#212529;border-color:#212529}.btn-check:checked+.btn-outline-dark:focus,.btn-check:active+.btn-outline-dark:focus,.btn-outline-dark:active:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark:disabled,.btn-outline-dark.disabled{color:#212529;background-color:rgba(0,0,0,0)}.btn-link{font-weight:400;color:#003a41;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:hover{color:#002e34}.btn-link:disabled,.btn-link.disabled{color:#6c757d}.btn-lg,.btn-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-sm,.btn-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.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{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{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#000;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.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:.125rem}.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:.125rem}.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:.125rem}.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:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;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:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:rgba(0,0,0,0)}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:0.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:hover,.dropdown-menu-dark .dropdown-item:focus{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .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>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.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:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn~.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{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:.5rem 1rem;color:#003a41;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;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:#002e34}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:none;border:1px solid rgba(0,0,0,0);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:rgba(0,0,0,0);border-color:rgba(0,0,0,0)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:none;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.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{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-top:.5rem;padding-bottom:.5rem}.navbar>.container-xxl,.navbar>.container-xl,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container,.navbar>.container-fluid{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:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-nav{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{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.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:.25 0;font-size:1.25rem;line-height:1;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);border-radius:.25rem;transition:box-shadow .15s ease-in-out}@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 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;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:.5rem;padding-left:.5rem}.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-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-top,.navbar-expand-sm .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .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:.5rem;padding-left:.5rem}.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-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-top,.navbar-expand-md .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .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:.5rem;padding-left:.5rem}.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-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-top,.navbar-expand-lg .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .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:.5rem;padding-left:.5rem}.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-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-top,.navbar-expand-xl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .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:.5rem;padding-left:.5rem}.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-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-top,.navbar-expand-xxl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .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:.5rem;padding-left:.5rem}.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-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-top,.navbar-expand .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-light{background-color:#106470}.navbar-light .navbar-brand{color:#fff}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:#fcfdfd}.navbar-light .navbar-nav .nav-link{color:#fff}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(252,253,253,.8)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.75)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .nav-link.active{color:#fcfdfd}.navbar-light .navbar-toggler{color:#fff;border-color:rgba(255,255,255,0)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='white' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#fff}.navbar-light .navbar-text a,.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:#fcfdfd}.navbar-dark{background-color:#106470}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fcfdfd}.navbar-dark .navbar-nav .nav-link{color:#fff}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:rgba(252,253,253,.8)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active{color:#fcfdfd}.navbar-dark .navbar-toggler{color:#fff;border-color:rgba(255,255,255,0)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='white' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:#fff}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fcfdfd}.card{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.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;border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.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:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-0.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:#adb5bd;border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:#adb5bd;border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@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}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#000;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%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");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='black'%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");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, ">") /* rtl: var(--bs-breadcrumb-divider, ">") */}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#003a41;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid #dee2e6;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:#002e34;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#002e34;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:0.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2em;border-bottom-left-radius:.2em}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2em;border-bottom-right-radius:.2em}.badge{display:inline-block;padding:.35em .65em;font-size:0.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid rgba(0,0,0,0);border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{color:#595a5c;background-color:#f8f9fa;border-color:#f5f6f8}.alert-default .alert-link{color:#47484a}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;display:-webkit-flex;height:1rem;overflow:hidden;font-size:0.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@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:1rem 1rem}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#000;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{color:#595a5c;background-color:#f8f9fa}.list-group-item-default.list-group-item-action:hover,.list-group-item-default.list-group-item-action:focus{color:#595a5c;background-color:#dfe0e1}.list-group-item-default.list-group-item-action.active{color:#fff;background-color:#595a5c;border-color:#595a5c}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:rgba(0,0,0,0) 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 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.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:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:0.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{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:.75rem}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.toast-header .btn-close{margin-right:-0.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;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% - 1rem)}.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% - 1rem)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.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:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-0.5rem -0.5rem -0.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem}.modal-footer{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(0.3rem - 1px);border-bottom-left-radius:calc(0.3rem - 1px)}.modal-footer>*{margin:.25rem}@media(min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media(min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media(min-width: 1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@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;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@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;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@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;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@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;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@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;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);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;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[data-popper-placement^=top]{padding:.4rem 0}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:0}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-end,.bs-tooltip-auto[data-popper-placement^=right]{padding:0 .4rem}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[data-popper-placement^=bottom]{padding:.4rem 0}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:0}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-start,.bs-tooltip-auto[data-popper-placement^=left]{padding:0 .4rem}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0 /* rtl:ignore */;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);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;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-0.5rem - 1px)}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-0.5rem - 1px)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.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:1rem;margin-left:-0.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#000}.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='%23ffffff'%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='%23ffffff'%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%;list-style:none}.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}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;border:.25em solid currentColor;border-right-color:rgba(0,0,0,0);border-radius:50%;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;background-color:currentColor;border-radius:50%;opacity:0;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{animation-duration:1.5s;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.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:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-0.5rem;margin-right:-0.5rem;margin-bottom:-0.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.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:""}.link-default{color:#dee2e6}.link-default:hover,.link-default:focus{color:#e5e8eb}.link-primary{color:#0d6efd}.link-primary:hover,.link-primary:focus{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:hover,.link-secondary:focus{color:#565e64}.link-success{color:#198754}.link-success:hover,.link-success:focus{color:#146c43}.link-info{color:#0dcaf0}.link-info:hover,.link-info:focus{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:hover,.link-warning:focus{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:hover,.link-danger:focus{color:#b02a37}.link-light{color:#f8f9fa}.link-light:hover,.link-light:focus{color:#f9fafb}.link-dark{color:#212529}.link-dark:hover,.link-dark:focus{color:#1a1e21}.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}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top: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){position:absolute !important;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}.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}.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}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display: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}.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:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-top-0{border-top:0 !important}.border-end{border-right:1px solid #dee2e6 !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:1px solid #dee2e6 !important}.border-start-0{border-left:0 !important}.border-default{border-color:#dee2e6 !important}.border-primary{border-color:#0d6efd !important}.border-secondary{border-color:#6c757d !important}.border-success{border-color:#198754 !important}.border-info{border-color:#0dcaf0 !important}.border-warning{border-color:#ffc107 !important}.border-danger{border-color:#dc3545 !important}.border-light{border-color:#f8f9fa !important}.border-dark{border-color:#212529 !important}.border-white{border-color:#fff !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}.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}.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}.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}.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-light{font-weight:300 !important}.fw-lighter{font-weight:lighter !important}.fw-normal{font-weight:400 !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:#6c757d !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-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}.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-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-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:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:.2em !important}.rounded-2{border-radius:.25rem !important}.rounded-3{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-end{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-start{border-bottom-left-radius:.25rem !important;border-top-left-radius:.25rem !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float: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-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}.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}.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}.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}.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-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}.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}.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}.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}.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-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}.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}.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}.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}.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-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}.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}.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}.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}.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-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}.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}.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}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#000}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#000}.bg-warning{color:#000}.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-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}}.sidebar-item .chapter-number{color:#000}.quarto-container{min-height:calc(100vh - 132px)}footer.footer .nav-footer,#quarto-header>nav{padding-left:1em;padding-right:1em}nav[role=doc-toc]{padding-left:.5em}#quarto-content>*{padding-top:14px}@media(max-width: 991.98px){#quarto-content>*{padding-top:0}#quarto-content .subtitle{padding-top:14px}#quarto-content section:first-of-type h2:first-of-type,#quarto-content section:first-of-type .h2:first-of-type{margin-top:1rem}}.headroom-target,header.headroom{will-change:transform;transition:position 200ms linear;transition:all 200ms linear}header.headroom--pinned{transform:translateY(0%)}header.headroom--unpinned{transform:translateY(-100%)}.navbar-container{width:100%}.navbar-brand{overflow:hidden;text-overflow:ellipsis}.navbar-brand-container{max-width:calc(100% - 115px);min-width:0;display:flex;align-items:center}@media(min-width: 992px){.navbar-brand-container{margin-right:1em}}.navbar-brand.navbar-brand-logo{margin-right:4px;display:inline-flex}.navbar-toggler{flex-basis:content;flex-shrink:0}.navbar .navbar-toggler{order:-1;margin-right:.5em}.navbar-logo{max-height:24px;width:auto;padding-right:4px}nav .nav-item:not(.compact){padding-top:1px}nav .nav-link i,nav .dropdown-item i{padding-right:1px}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.6rem;padding-right:.6rem}nav .nav-item.compact .nav-link{padding-left:.5rem;padding-right:.5rem;font-size:1.1rem}.navbar .quarto-navbar-tools div.dropdown{display:inline-block}.navbar .quarto-navbar-tools .quarto-navigation-tool{color:#fff}.navbar .quarto-navbar-tools .quarto-navigation-tool:hover{color:#fcfdfd}@media(max-width: 991.98px){.navbar .quarto-navbar-tools{margin-top:.25em;padding-top:.75em;display:block;color:solid gray 1px;text-align:center;vertical-align:middle;margin-right:auto}}.navbar-nav .dropdown-menu{min-width:220px;font-size:.9rem}.navbar .navbar-nav .nav-link.dropdown-toggle::after{opacity:.75;vertical-align:.175em}.navbar ul.dropdown-menu{padding-top:0;padding-bottom:0}.navbar .dropdown-header{text-transform:uppercase;font-size:.8rem;padding:0 .5rem}.navbar .dropdown-item{padding:.4rem .5rem}.navbar .dropdown-item>i.bi{margin-left:.1rem;margin-right:.25em}.sidebar #quarto-search{margin-top:-1px}.sidebar #quarto-search svg.aa-SubmitIcon{width:16px;height:16px}.sidebar-navigation a{color:inherit}.sidebar-title{margin-top:.25rem;padding-bottom:.5rem;font-size:1.3rem;line-height:1.6rem;visibility:visible}.sidebar-title>a{font-size:inherit;text-decoration:none}.sidebar-title .sidebar-tools-main{margin-top:-6px}@media(max-width: 991.98px){#quarto-sidebar div.sidebar-header{padding-top:.2em}}.sidebar-header-stacked .sidebar-title{margin-top:.6rem}.sidebar-logo{max-width:90%;padding-bottom:.5rem}.sidebar-logo-link{text-decoration:none}.sidebar-navigation li a{text-decoration:none}.sidebar-navigation .quarto-navigation-tool{opacity:.7;font-size:.875rem}#quarto-sidebar>nav>.sidebar-tools-main{margin-left:14px}.sidebar-tools-main{display:inline-flex;margin-left:0px;order:2}.sidebar-tools-main:not(.tools-wide){vertical-align:middle}.sidebar-navigation .quarto-navigation-tool.dropdown-toggle::after{display:none}.sidebar.sidebar-navigation>*{padding-top:1em}.sidebar-item{margin-bottom:.2em}.sidebar-section{margin-top:.2em;padding-left:.5em;padding-bottom:.2em}.sidebar-item .sidebar-item-container{display:flex;justify-content:space-between}.sidebar-item-toggle:hover{cursor:pointer}.sidebar-item .sidebar-item-toggle .bi{font-size:.7rem;text-align:center}.sidebar-item .sidebar-item-toggle .bi-chevron-right::before{transition:transform 200ms ease}.sidebar-item .sidebar-item-toggle[aria-expanded=false] .bi-chevron-right::before{transform:none}.sidebar-item .sidebar-item-toggle[aria-expanded=true] .bi-chevron-right::before{transform:rotate(90deg)}.sidebar-navigation .sidebar-divider{margin-left:0;margin-right:0;margin-top:.5rem;margin-bottom:.5rem}@media(max-width: 991.98px){.quarto-secondary-nav{display:block}.quarto-secondary-nav button.quarto-search-button{padding-right:0em;padding-left:2em}.quarto-secondary-nav button.quarto-btn-toggle{margin-left:-0.75rem;margin-right:.15rem}.quarto-secondary-nav nav.quarto-page-breadcrumbs{display:flex;align-items:center;padding-right:1em;margin-left:-0.25em}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{text-decoration:none}.quarto-secondary-nav nav.quarto-page-breadcrumbs ol.breadcrumb{margin-bottom:0}}@media(min-width: 992px){.quarto-secondary-nav{display:none}}.quarto-secondary-nav .quarto-btn-toggle{color:#595959}.quarto-secondary-nav[aria-expanded=false] .quarto-btn-toggle .bi-chevron-right::before{transform:none}.quarto-secondary-nav[aria-expanded=true] .quarto-btn-toggle .bi-chevron-right::before{transform:rotate(90deg)}.quarto-secondary-nav .quarto-btn-toggle .bi-chevron-right::before{transition:transform 200ms ease}.quarto-secondary-nav{cursor:pointer}.quarto-secondary-nav-title{margin-top:.3em;color:#595959;padding-top:4px}.quarto-secondary-nav nav.quarto-page-breadcrumbs{color:#595959}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{color:#595959}.quarto-secondary-nav nav.quarto-page-breadcrumbs a:hover{color:rgba(0,58,65,.8)}.quarto-secondary-nav nav.quarto-page-breadcrumbs .breadcrumb-item::before{color:#8c8c8c}div.sidebar-item-container{color:#595959}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(0,58,65,.8)}div.sidebar-item-container.disabled{color:rgba(89,89,89,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:#003a41}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#fff}@media(max-width: 991.98px){.sidebar-navigation .sidebar-item a,.nav-page .nav-page-text,.sidebar-navigation{font-size:1rem}.sidebar-navigation ul.sidebar-section.depth1 .sidebar-section-item{font-size:1.1rem}.sidebar-logo{display:none}.sidebar.sidebar-navigation{position:static;border-bottom:1px solid #dee2e6}.sidebar.sidebar-navigation.collapsing{position:fixed;z-index:1000}.sidebar.sidebar-navigation.show{position:fixed;z-index:1000}.sidebar.sidebar-navigation{min-height:100%}nav.quarto-secondary-nav{background-color:#fff;border-bottom:1px solid #dee2e6}.sidebar .sidebar-footer{visibility:visible;padding-top:1rem;position:inherit}.sidebar-tools-collapse{display:block}}#quarto-sidebar{transition:width .15s ease-in}#quarto-sidebar>*{padding-right:1em}@media(max-width: 991.98px){#quarto-sidebar .sidebar-menu-container{white-space:nowrap;min-width:225px}#quarto-sidebar.show{transition:width .15s ease-out}}@media(min-width: 992px){#quarto-sidebar{display:flex;flex-direction:column}.nav-page .nav-page-text,.sidebar-navigation .sidebar-section .sidebar-item{font-size:.875rem}.sidebar-navigation .sidebar-item{font-size:.925rem}.sidebar.sidebar-navigation{display:block;position:sticky}.sidebar-search{width:100%}.sidebar .sidebar-footer{visibility:visible}}@media(max-width: 991.98px){#quarto-sidebar-glass{position:fixed;top:0;bottom:0;left:0;right:0;background-color:rgba(255,255,255,0);transition:background-color .15s ease-in;z-index:-1}#quarto-sidebar-glass.collapsing{z-index:1000}#quarto-sidebar-glass.show{transition:background-color .15s ease-out;background-color:rgba(102,102,102,.4);z-index:1000}}.sidebar .sidebar-footer{padding:.5rem 1rem;align-self:flex-end;color:#6c757d;width:100%}.quarto-page-breadcrumbs .breadcrumb-item+.breadcrumb-item,.quarto-page-breadcrumbs .breadcrumb-item{padding-right:.33em;padding-left:0}.quarto-page-breadcrumbs .breadcrumb-item::before{padding-right:.33em}.quarto-sidebar-footer{font-size:.875em}.sidebar-section .bi-chevron-right{vertical-align:middle}.sidebar-section .bi-chevron-right::before{font-size:.9em}.notransition{-webkit-transition:none !important;-moz-transition:none !important;-o-transition:none !important;transition:none !important}.btn:focus:not(:focus-visible){box-shadow:none}.page-navigation{display:flex;justify-content:space-between}.nav-page{padding-bottom:.75em}.nav-page .bi{font-size:1.8rem;vertical-align:middle}.nav-page .nav-page-text{padding-left:.25em;padding-right:.25em}.nav-page a{color:#6c757d;text-decoration:none;display:flex;align-items:center}.nav-page a:hover{color:#002e34}.toc-actions{display:flex}.toc-actions p{margin-block-start:0;margin-block-end:0}.toc-actions a{text-decoration:none;color:inherit;font-weight:400}.toc-actions a:hover{color:#002e34}.toc-actions .action-links{margin-left:4px}.sidebar nav[role=doc-toc] .toc-actions .bi{margin-left:-4px;font-size:.7rem;color:#6c757d}.sidebar nav[role=doc-toc] .toc-actions .bi:before{padding-top:3px}#quarto-margin-sidebar .toc-actions .bi:before{margin-top:.3rem;font-size:.7rem;color:#6c757d;vertical-align:top}.sidebar nav[role=doc-toc] .toc-actions>div:first-of-type{margin-top:-3px}#quarto-margin-sidebar .toc-actions p,.sidebar nav[role=doc-toc] .toc-actions p{font-size:.875rem}.nav-footer .toc-actions{padding-bottom:.5em;padding-top:.5em}.nav-footer .toc-actions :first-child{margin-left:auto}.nav-footer .toc-actions :last-child{margin-right:auto}.nav-footer .toc-actions .action-links{display:flex}.nav-footer .toc-actions .action-links p{padding-right:1.5em}.nav-footer .toc-actions .action-links p:last-of-type{padding-right:0}.nav-footer{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:baseline;text-align:center;padding-top:.5rem;padding-bottom:.5rem;background-color:#fff}body.nav-fixed{padding-top:64px}.nav-footer-contents{color:#6c757d;margin-top:.25rem}.nav-footer{min-height:3.5em;color:#757575}.nav-footer a{color:#757575}.nav-footer .nav-footer-left{font-size:.825em}.nav-footer .nav-footer-center{font-size:.825em}.nav-footer .nav-footer-right{font-size:.825em}.nav-footer-left .footer-items,.nav-footer-center .footer-items,.nav-footer-right .footer-items{display:inline-flex;padding-top:.3em;padding-bottom:.3em;margin-bottom:0em}.nav-footer-left .footer-items .nav-link,.nav-footer-center .footer-items .nav-link,.nav-footer-right .footer-items .nav-link{padding-left:.6em;padding-right:.6em}.nav-footer-left{flex:1 1 0px;text-align:left}.nav-footer-right{flex:1 1 0px;text-align:right}.nav-footer-center{flex:1 1 0px;min-height:3em;text-align:center}.nav-footer-center .footer-items{justify-content:center}@media(max-width: 767.98px){.nav-footer-center{margin-top:3em}}.navbar .quarto-reader-toggle.reader .quarto-reader-toggle-btn{background-color:#fff;border-radius:3px}.quarto-reader-toggle.reader.quarto-navigation-tool .quarto-reader-toggle-btn{background-color:#595959;border-radius:3px}.quarto-reader-toggle .quarto-reader-toggle-btn{display:inline-flex;padding-left:.2em;padding-right:.2em;margin-left:-0.2em;margin-right:-0.2em;text-align:center}.navbar .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}#quarto-back-to-top{display:none;position:fixed;bottom:50px;background-color:#fff;border-radius:.25rem;box-shadow:0 .2rem .5rem #6c757d,0 0 .05rem #6c757d;color:#6c757d;text-decoration:none;font-size:.9em;text-align:center;left:50%;padding:.4rem .8rem;transform:translate(-50%, 0)}.aa-DetachedOverlay ul.aa-List,#quarto-search-results ul.aa-List{list-style:none;padding-left:0}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{background-color:#fff;position:absolute;z-index:2000}#quarto-search-results .aa-Panel{max-width:400px}#quarto-search input{font-size:.925rem}@media(min-width: 992px){.navbar #quarto-search{margin-left:.25rem;order:999}}@media(max-width: 991.98px){#quarto-sidebar .sidebar-search{display:none}}#quarto-sidebar .sidebar-search .aa-Autocomplete{width:100%}.navbar .aa-Autocomplete .aa-Form{width:180px}.navbar #quarto-search.type-overlay .aa-Autocomplete{width:40px}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form{background-color:inherit;border:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form:focus-within{box-shadow:none;outline:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper{display:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper:focus-within{display:inherit}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-Label svg,.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-LoadingIndicator svg{width:26px;height:26px;color:#fff;opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:#fff;opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;color:#000;display:flex;line-height:1em;margin:0;position:relative;width:100%}.aa-Autocomplete .aa-Form:focus-within,.aa-DetachedFormContainer .aa-Form:focus-within{box-shadow:rgba(13,110,253,.6) 0 0 0 1px;outline:currentColor none medium}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix{align-items:center;display:flex;flex-shrink:0;order:1}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{cursor:initial;flex-shrink:0;padding:0;text-align:left}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg{color:#000;opacity:.5}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton{appearance:none;background:none;border:0;margin:0}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{align-items:center;display:flex;justify-content:center}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapper,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper{order:3;position:relative;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input{appearance:none;background:none;border:0;color:#000;font:inherit;height:calc(1.5em + .1rem + 2px);padding:0;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::placeholder,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::placeholder{color:#000;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input:focus{border-color:none;box-shadow:none;outline:none}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix{align-items:center;display:flex;order:4}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton{align-items:center;background:none;border:0;color:#000;opacity:.8;cursor:pointer;display:flex;margin:0;width:calc(1.5em + .1rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus{color:#000;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg{width:calc(1.5em + 0.75rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton{border:none;align-items:center;background:none;color:#000;opacity:.4;font-size:.7rem;cursor:pointer;display:none;margin:0;width:calc(1em + .1rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus{color:#000;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden]{display:none}.aa-PanelLayout:empty{display:none}.quarto-search-no-results.no-query{display:none}.aa-Source:has(.no-query){display:none}#quarto-search-results .aa-Panel{border:solid #ced4da 1px}#quarto-search-results .aa-SourceNoResults{width:398px}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{max-height:65vh;overflow-y:auto;font-size:.925rem}.aa-DetachedOverlay .aa-SourceNoResults,#quarto-search-results .aa-SourceNoResults{height:60px;display:flex;justify-content:center;align-items:center}.aa-DetachedOverlay .search-error,#quarto-search-results .search-error{padding-top:10px;padding-left:20px;padding-right:20px;cursor:default}.aa-DetachedOverlay .search-error .search-error-title,#quarto-search-results .search-error .search-error-title{font-size:1.1rem;margin-bottom:.5rem}.aa-DetachedOverlay .search-error .search-error-title .search-error-icon,#quarto-search-results .search-error .search-error-title .search-error-icon{margin-right:8px}.aa-DetachedOverlay .search-error .search-error-text,#quarto-search-results .search-error .search-error-text{font-weight:300}.aa-DetachedOverlay .search-result-text,#quarto-search-results .search-result-text{font-weight:300;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.2rem;max-height:2.4rem}.aa-DetachedOverlay .aa-SourceHeader .search-result-header,#quarto-search-results .aa-SourceHeader .search-result-header{font-size:.875rem;background-color:#f2f2f2;padding-left:14px;padding-bottom:4px;padding-top:4px}.aa-DetachedOverlay .aa-SourceHeader .search-result-header-no-results,#quarto-search-results .aa-SourceHeader .search-result-header-no-results{display:none}.aa-DetachedOverlay .aa-SourceFooter .algolia-search-logo,#quarto-search-results .aa-SourceFooter .algolia-search-logo{width:110px;opacity:.85;margin:8px;float:right}.aa-DetachedOverlay .search-result-section,#quarto-search-results .search-result-section{font-size:.925em}.aa-DetachedOverlay a.search-result-link,#quarto-search-results a.search-result-link{color:inherit;text-decoration:none}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item,#quarto-search-results li.aa-Item[aria-selected=true] .search-item{background-color:#0d6efd}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text-container{color:#fff;background-color:#0d6efd}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=true] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-match.mark{color:#fff;background-color:#3586fd}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item,#quarto-search-results li.aa-Item[aria-selected=false] .search-item{background-color:#fff}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text-container{color:#000}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=false] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-match.mark{color:inherit;background-color:#e1edff}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container{background-color:#fff;color:#000}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container{padding-top:0px}.aa-DetachedOverlay li.aa-Item .search-result-doc.document-selectable .search-result-text-container,#quarto-search-results li.aa-Item .search-result-doc.document-selectable .search-result-text-container{margin-top:-4px}.aa-DetachedOverlay .aa-Item,#quarto-search-results .aa-Item{cursor:pointer}.aa-DetachedOverlay .aa-Item .search-item,#quarto-search-results .aa-Item .search-item{border-left:none;border-right:none;border-top:none;background-color:#fff;border-color:#ced4da;color:#000}.aa-DetachedOverlay .aa-Item .search-item p,#quarto-search-results .aa-Item .search-item p{margin-top:0;margin-bottom:0}.aa-DetachedOverlay .aa-Item .search-item i.bi,#quarto-search-results .aa-Item .search-item i.bi{padding-left:8px;padding-right:8px;font-size:1.3em}.aa-DetachedOverlay .aa-Item .search-item .search-result-title,#quarto-search-results .aa-Item .search-item .search-result-title{margin-top:.3em;margin-bottom:.1rem}.aa-DetachedOverlay .aa-Item .search-result-title-container,#quarto-search-results .aa-Item .search-result-title-container{font-size:1em;display:flex;padding:6px 4px 6px 4px}.aa-DetachedOverlay .aa-Item .search-result-text-container,#quarto-search-results .aa-Item .search-result-text-container{padding-bottom:8px;padding-right:8px;margin-left:44px}.aa-DetachedOverlay .aa-Item .search-result-doc-section,.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-doc-section,#quarto-search-results .aa-Item .search-result-more{padding-top:8px;padding-bottom:8px;padding-left:44px}.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-more{font-size:.8em;font-weight:400}.aa-DetachedOverlay .aa-Item .search-result-doc,#quarto-search-results .aa-Item .search-result-doc{border-top:1px solid #ced4da}.aa-DetachedSearchButton{background:none;border:none}.aa-DetachedSearchButton .aa-DetachedSearchButtonPlaceholder{display:none}.navbar .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#fff}.sidebar-tools-collapse #quarto-search,.sidebar-tools-main #quarto-search{display:inline}.sidebar-tools-collapse #quarto-search .aa-Autocomplete,.sidebar-tools-main #quarto-search .aa-Autocomplete{display:inline}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton{padding-left:4px;padding-right:4px}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#595959}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon{margin-top:-3px}.aa-DetachedContainer{background:rgba(255,255,255,.65);width:90%;bottom:0;box-shadow:rgba(206,212,218,.6) 0 0 0 1px;outline:currentColor none medium;display:flex;flex-direction:column;left:0;margin:0;overflow:hidden;padding:0;position:fixed;right:0;top:0;z-index:1101}.aa-DetachedContainer::after{height:32px}.aa-DetachedContainer .aa-SourceHeader{margin:var(--aa-spacing-half) 0 var(--aa-spacing-half) 2px}.aa-DetachedContainer .aa-Panel{background-color:#fff;border-radius:0;box-shadow:none;flex-grow:1;margin:0;padding:0;position:relative}.aa-DetachedContainer .aa-PanelLayout{bottom:0;box-shadow:none;left:0;margin:0;max-height:none;overflow-y:auto;position:absolute;right:0;top:0;width:100%}.aa-DetachedFormContainer{background-color:#fff;border-bottom:1px solid #ced4da;display:flex;flex-direction:row;justify-content:space-between;margin:0;padding:.5em}.aa-DetachedCancelButton{background:none;font-size:.8em;border:0;border-radius:3px;color:#000;cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(13,110,253,.6) 0 0 0 1px;outline:currentColor none medium}.aa-DetachedContainer--modal{bottom:inherit;height:auto;margin:0 auto;position:absolute;top:100px;border-radius:6px;max-width:850px}@media(max-width: 575.98px){.aa-DetachedContainer--modal{width:100%;top:0px;border-radius:0px;border:none}}.aa-DetachedContainer--modal .aa-PanelLayout{max-height:var(--aa-detached-modal-max-height);padding-bottom:var(--aa-spacing-half);position:static}.aa-Detached{height:100vh;overflow:hidden}.aa-DetachedOverlay{background-color:rgba(0,0,0,.4);position:fixed;left:0;right:0;top:0;margin:0;padding:0;height:100vh;z-index:1100}.quarto-listing{padding-bottom:1em}.listing-pagination{padding-top:.5em}ul.pagination{float:right;padding-left:8px;padding-top:.5em}ul.pagination li{padding-right:.75em}ul.pagination li.disabled a,ul.pagination li.active a{color:#000;text-decoration:none}ul.pagination li:last-of-type{padding-right:0}.listing-actions-group{display:flex}.quarto-listing-filter{margin-bottom:1em;width:200px;margin-left:auto}.quarto-listing-sort{margin-bottom:1em;margin-right:auto;width:auto}.quarto-listing-sort .input-group-text{font-size:.8em}.input-group-text{border-right:none}.quarto-listing-sort select.form-select{font-size:.8em}.listing-no-matching{text-align:center;padding-top:2em;padding-bottom:3em;font-size:1em}#quarto-margin-sidebar .quarto-listing-category{padding-top:0;font-size:1rem}#quarto-margin-sidebar .quarto-listing-category-title{cursor:pointer;font-weight:600;font-size:1rem}.quarto-listing-category .category{cursor:pointer}.quarto-listing-category .category.active{font-weight:600}.quarto-listing-category.category-cloud{display:flex;flex-wrap:wrap;align-items:baseline}.quarto-listing-category.category-cloud .category{padding-right:5px}.quarto-listing-category.category-cloud .category-cloud-1{font-size:.75em}.quarto-listing-category.category-cloud .category-cloud-2{font-size:.95em}.quarto-listing-category.category-cloud .category-cloud-3{font-size:1.15em}.quarto-listing-category.category-cloud .category-cloud-4{font-size:1.35em}.quarto-listing-category.category-cloud .category-cloud-5{font-size:1.55em}.quarto-listing-category.category-cloud .category-cloud-6{font-size:1.75em}.quarto-listing-category.category-cloud .category-cloud-7{font-size:1.95em}.quarto-listing-category.category-cloud .category-cloud-8{font-size:2.15em}.quarto-listing-category.category-cloud .category-cloud-9{font-size:2.35em}.quarto-listing-category.category-cloud .category-cloud-10{font-size:2.55em}.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-1{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-2{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-3{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-3{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-4{grid-template-columns:repeat(4, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-4{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-4{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-5{grid-template-columns:repeat(5, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-5{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-5{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-6{grid-template-columns:repeat(6, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-6{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-6{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-7{grid-template-columns:repeat(7, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-7{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-7{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-8{grid-template-columns:repeat(8, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-8{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-8{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-9{grid-template-columns:repeat(9, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-9{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-9{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-10{grid-template-columns:repeat(10, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-10{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-10{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-11{grid-template-columns:repeat(11, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-11{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-11{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-12{grid-template-columns:repeat(12, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-12{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-12{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-grid{gap:1.5em}.quarto-grid-item.borderless{border:none}.quarto-grid-item.borderless .listing-categories .listing-category:last-of-type,.quarto-grid-item.borderless .listing-categories .listing-category:first-of-type{padding-left:0}.quarto-grid-item.borderless .listing-categories .listing-category{border:0}.quarto-grid-link{text-decoration:none;color:inherit}.quarto-grid-link:hover{text-decoration:none;color:inherit}.quarto-grid-item h5.title,.quarto-grid-item .title.h5{margin-top:0;margin-bottom:0}.quarto-grid-item .card-footer{display:flex;justify-content:space-between;font-size:.8em}.quarto-grid-item .card-footer p{margin-bottom:0}.quarto-grid-item p.card-img-top{margin-bottom:0}.quarto-grid-item p.card-img-top>img{object-fit:cover}.quarto-grid-item .card-other-values{margin-top:.5em;font-size:.8em}.quarto-grid-item .card-other-values tr{margin-bottom:.5em}.quarto-grid-item .card-other-values tr>td:first-of-type{font-weight:600;padding-right:1em;padding-left:1em;vertical-align:top}.quarto-grid-item div.post-contents{display:flex;flex-direction:column;text-decoration:none;height:100%}.quarto-grid-item .listing-item-img-placeholder{background-color:#adb5bd;flex-shrink:0}.quarto-grid-item .card-attribution{padding-top:1em;display:flex;gap:1em;text-transform:uppercase;color:#6c757d;font-weight:500;flex-grow:10;align-items:flex-end}.quarto-grid-item .description{padding-bottom:1em}.quarto-grid-item .card-attribution .date{align-self:flex-end}.quarto-grid-item .card-attribution.justify{justify-content:space-between}.quarto-grid-item .card-attribution.start{justify-content:flex-start}.quarto-grid-item .card-attribution.end{justify-content:flex-end}.quarto-grid-item .card-title{margin-bottom:.1em}.quarto-grid-item .card-subtitle{padding-top:.25em}.quarto-grid-item .card-text{font-size:.9em}.quarto-grid-item .listing-reading-time{padding-bottom:.25em}.quarto-grid-item .card-text-small{font-size:.8em}.quarto-grid-item .card-subtitle.subtitle{font-size:.9em;font-weight:600;padding-bottom:.5em}.quarto-grid-item .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}.quarto-grid-item .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}.quarto-grid-item.card-right{text-align:right}.quarto-grid-item.card-right .listing-categories{justify-content:flex-end}.quarto-grid-item.card-left{text-align:left}.quarto-grid-item.card-center{text-align:center}.quarto-grid-item.card-center .listing-description{text-align:justify}.quarto-grid-item.card-center .listing-categories{justify-content:center}table.quarto-listing-table td.image{padding:0px}table.quarto-listing-table td.image img{width:100%;max-width:50px;object-fit:contain}table.quarto-listing-table a{text-decoration:none}table.quarto-listing-table th a{color:inherit}table.quarto-listing-table th a.asc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table th a.desc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table.table-hover td{cursor:pointer}.quarto-post.image-left{flex-direction:row}.quarto-post.image-right{flex-direction:row-reverse}@media(max-width: 767.98px){.quarto-post.image-right,.quarto-post.image-left{gap:0em;flex-direction:column}.quarto-post .metadata{padding-bottom:1em;order:2}.quarto-post .body{order:1}.quarto-post .thumbnail{order:3}}.list.quarto-listing-default div:last-of-type{border-bottom:none}@media(min-width: 992px){.quarto-listing-container-default{margin-right:2em}}div.quarto-post{display:flex;gap:2em;margin-bottom:1.5em;border-bottom:1px solid #dee2e6}@media(max-width: 767.98px){div.quarto-post{padding-bottom:1em}}div.quarto-post .metadata{flex-basis:20%;flex-grow:0;margin-top:.2em;flex-shrink:10}div.quarto-post .thumbnail{flex-basis:30%;flex-grow:0;flex-shrink:0}div.quarto-post .thumbnail img{margin-top:.4em;width:100%;object-fit:cover}div.quarto-post .body{flex-basis:45%;flex-grow:1;flex-shrink:0}div.quarto-post .body h3.listing-title,div.quarto-post .body .listing-title.h3{margin-top:0px;margin-bottom:0px;border-bottom:none}div.quarto-post .body .listing-subtitle{font-size:.875em;margin-bottom:.5em;margin-top:.2em}div.quarto-post .body .description{font-size:.9em}div.quarto-post a{color:#000;display:flex;flex-direction:column;text-decoration:none}div.quarto-post a div.description{flex-shrink:0}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:var(--bs-font-sans-serif);flex-basis:33%}div.quarto-post .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}div.quarto-post .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}div.quarto-post .listing-description{margin-bottom:.5em}div.quarto-about-jolla{display:flex !important;flex-direction:column;align-items:center;margin-top:10%;padding-bottom:1em}div.quarto-about-jolla .about-image{object-fit:cover;margin-left:auto;margin-right:auto;margin-bottom:1.5em}div.quarto-about-jolla img.round{border-radius:50%}div.quarto-about-jolla img.rounded{border-radius:10px}div.quarto-about-jolla .quarto-title h1.title,div.quarto-about-jolla .quarto-title .title.h1{text-align:center}div.quarto-about-jolla .quarto-title .description{text-align:center}div.quarto-about-jolla h2,div.quarto-about-jolla .h2{border-bottom:none}div.quarto-about-jolla .about-sep{width:60%}div.quarto-about-jolla main{text-align:center}div.quarto-about-jolla .about-links{display:flex}@media(min-width: 992px){div.quarto-about-jolla .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-jolla .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-jolla .about-link{color:#333;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-jolla .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-jolla .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-jolla .about-link:hover{color:#003a41}div.quarto-about-jolla .about-link i.bi{margin-right:.15em}div.quarto-about-solana{display:flex !important;flex-direction:column;padding-top:3em !important;padding-bottom:1em}div.quarto-about-solana .about-entity{display:flex !important;align-items:start;justify-content:space-between}@media(min-width: 992px){div.quarto-about-solana .about-entity{flex-direction:row}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity{flex-direction:column-reverse;align-items:center;text-align:center}}div.quarto-about-solana .about-entity .entity-contents{display:flex;flex-direction:column}@media(max-width: 767.98px){div.quarto-about-solana .about-entity .entity-contents{width:100%}}div.quarto-about-solana .about-entity .about-image{object-fit:cover}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-image{margin-bottom:1.5em}}div.quarto-about-solana .about-entity img.round{border-radius:50%}div.quarto-about-solana .about-entity img.rounded{border-radius:10px}div.quarto-about-solana .about-entity .about-links{display:flex;justify-content:left;padding-bottom:1.2em}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-solana .about-entity .about-link{color:#333;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-solana .about-entity .about-link:hover{color:#003a41}div.quarto-about-solana .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-solana .about-contents{padding-right:1.5em;flex-basis:0;flex-grow:1}div.quarto-about-solana .about-contents main.content{margin-top:0}div.quarto-about-solana .about-contents h2,div.quarto-about-solana .about-contents .h2{border-bottom:none}div.quarto-about-trestles{display:flex !important;flex-direction:row;padding-top:3em !important;padding-bottom:1em}@media(max-width: 991.98px){div.quarto-about-trestles{flex-direction:column;padding-top:0em !important}}div.quarto-about-trestles .about-entity{display:flex !important;flex-direction:column;align-items:center;text-align:center;padding-right:1em}@media(min-width: 992px){div.quarto-about-trestles .about-entity{flex:0 0 42%}}div.quarto-about-trestles .about-entity .about-image{object-fit:cover;margin-bottom:1.5em}div.quarto-about-trestles .about-entity img.round{border-radius:50%}div.quarto-about-trestles .about-entity img.rounded{border-radius:10px}div.quarto-about-trestles .about-entity .about-links{display:flex;justify-content:center}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-trestles .about-entity .about-link{color:#333;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-trestles .about-entity .about-link:hover{color:#003a41}div.quarto-about-trestles .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-trestles .about-contents{flex-basis:0;flex-grow:1}div.quarto-about-trestles .about-contents h2,div.quarto-about-trestles .about-contents .h2{border-bottom:none}@media(min-width: 992px){div.quarto-about-trestles .about-contents{border-left:solid 1px #dee2e6;padding-left:1.5em}}div.quarto-about-trestles .about-contents main.content{margin-top:0}div.quarto-about-marquee{padding-bottom:1em}div.quarto-about-marquee .about-contents{display:flex;flex-direction:column}div.quarto-about-marquee .about-image{max-height:550px;margin-bottom:1.5em;object-fit:cover}div.quarto-about-marquee img.round{border-radius:50%}div.quarto-about-marquee img.rounded{border-radius:10px}div.quarto-about-marquee h2,div.quarto-about-marquee .h2{border-bottom:none}div.quarto-about-marquee .about-links{display:flex;justify-content:center;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-marquee .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-marquee .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-marquee .about-link{color:#333;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-marquee .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-marquee .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-marquee .about-link:hover{color:#003a41}div.quarto-about-marquee .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-marquee .about-link{border:none}}div.quarto-about-broadside{display:flex;flex-direction:column;padding-bottom:1em}div.quarto-about-broadside .about-main{display:flex !important;padding-top:0 !important}@media(min-width: 992px){div.quarto-about-broadside .about-main{flex-direction:row;align-items:flex-start}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main{flex-direction:column}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main .about-entity{flex-shrink:0;width:100%;height:450px;margin-bottom:1.5em;background-size:cover;background-repeat:no-repeat}}@media(min-width: 992px){div.quarto-about-broadside .about-main .about-entity{flex:0 10 50%;margin-right:1.5em;width:100%;height:100%;background-size:100%;background-repeat:no-repeat}}div.quarto-about-broadside .about-main .about-contents{padding-top:14px;flex:0 0 50%}div.quarto-about-broadside h2,div.quarto-about-broadside .h2{border-bottom:none}div.quarto-about-broadside .about-sep{margin-top:1.5em;width:60%;align-self:center}div.quarto-about-broadside .about-links{display:flex;justify-content:center;column-gap:20px;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-broadside .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-broadside .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-broadside .about-link{color:#333;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-broadside .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-broadside .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-broadside .about-link:hover{color:#003a41}div.quarto-about-broadside .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-broadside .about-link{border:none}}.tippy-box[data-theme~=quarto]{background-color:#fff;border:solid 1px #dee2e6;border-radius:.25rem;color:#000;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:#dee2e6;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:#dee2e6;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:#dee2e6;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:#dee2e6}.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:#000}.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}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}.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:inline-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}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption{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{caption-side:top;margin-bottom:1.5rem}caption,.table-caption{padding-top:.5rem;padding-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:#6c757d}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}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}.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}div.ansi-escaped-output{font-family:monospace;display:block}/*! +* +* ansi colors from IPython notebook's +* +*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-fg{color:#282c36}.ansi-black-intense-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-fg{color:#b22b31}.ansi-red-intense-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-fg{color:#007427}.ansi-green-intense-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-fg{color:#b27d12}.ansi-yellow-intense-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-fg{color:#0065ca}.ansi-blue-intense-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-fg{color:#a03196}.ansi-magenta-intense-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-fg{color:#258f8f}.ansi-cyan-intense-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-fg{color:#a1a6b2}.ansi-white-intense-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: #ffffff;--quarto-body-color: black;--quarto-text-muted: #6c757d;--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.25rem}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:absolute;right:.5em;left:inherit;background-color:rgba(0,0,0,0)}:root{--mermaid-bg-color: #ffffff;--mermaid-edge-color: #6c757d;--mermaid-node-fg-color: black;--mermaid-fg-color: black;--mermaid-fg-color--lighter: #1a1a1a;--mermaid-fg-color--lightest: #333333;--mermaid-font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, Liberation Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;--mermaid-label-bg-color: #ffffff;--mermaid-label-fg-color: #0d6efd;--mermaid-node-bg-color: rgba(13, 110, 253, 0.1);--mermaid-node-fg-color: black}@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}.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}.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.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>*{grid-column:page-start/body-content-end}.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;transform:translate3d(0, 0, 0)}.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;transform:translate3d(0, 0, 0)}.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;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-right table{background:#fff}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page table{background:#fff}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.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;transform:translate3d(0, 0, 0)}.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;transform:translate3d(0, 0, 0)}.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;transform:translate3d(0, 0, 0)}.page-columns .column-page-left table{background:#fff}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.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;transform:translate3d(0, 0, 0)}.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;transform:translate3d(0, 0, 0)}.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;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.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;transform:translate3d(0, 0, 0);margin-bottom:1em}.zindex-content{z-index:998;transform:translate3d(0, 0, 0)}.zindex-modal{z-index:1055;transform:translate3d(0, 0, 0)}.zindex-over-content{z-index:999;transform:translate3d(0, 0, 0)}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,.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,.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,.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.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{opacity:.9;margin-top:2rem;margin-bottom:1rem;font-weight:600}h1.title,.title.h1{margin-top:0}h2,.h2{border-bottom:1px solid #dee2e6;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:#404040}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,caption,.figure-caption{font-size:.9rem}.panel-caption,.figure-caption,figcaption{color:#404040}.table-caption,caption{color:#000}.quarto-layout-cell[data-ref-parent] caption{color:#404040}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#404040;font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse){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:#dee2e6 1px solid;border-right:#dee2e6 1px solid;border-bottom:#dee2e6 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:1em}.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);border-radius:.25rem}pre.sourceCode{background-color:rgba(0,0,0,0)}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}.callout pre.sourceCode{padding-left:0}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:#404040}.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 code:not(.sourceCode),li code:not(.sourceCode),td code:not(.sourceCode){background-color:#f5f5f5;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:#6c757d;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.toc-left>*,.sidebar.margin-sidebar>*{padding-top:.5em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}figure .quarto-notebook-link{margin-top:.5em}.quarto-notebook-link{font-size:.75em;color:#6c757d;margin-bottom:1em;text-decoration:none;display:block}.quarto-notebook-link:hover{text-decoration:underline;color:#003a41}.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}.quarto-alternate-notebooks i.bi,.quarto-alternate-formats i.bi{margin-right:.4em}.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 h2,.quarto-notebook .h2{border-bottom:none}.sidebar .quarto-alternate-formats a,.sidebar .quarto-alternate-notebooks a{text-decoration:none}.sidebar .quarto-alternate-formats a:hover,.sidebar .quarto-alternate-notebooks a:hover{color:#003a41}.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-size:.875rem;font-weight:400;margin-bottom:.5rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2{margin-top:1rem}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #e9ecef;padding-left:.6rem}.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 .quarto-alternate-notebooks ul a:empty,.sidebar .quarto-alternate-formats ul a:empty,.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar .quarto-alternate-notebooks ul,.sidebar .quarto-alternate-formats ul,.sidebar nav[role=doc-toc] ul{padding-left:0;list-style:none;font-size:.875rem;font-weight:300}.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 #003a41;color:#003a41 !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#003a41 !important}kbd,.kbd{color:#000;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#dee2e6}div.hanging-indent{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}.table a{word-break:break-word}.table>thead{border-top-width:1px;border-top-color:#dee2e6;border-bottom:1px solid gray}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.25rem;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 #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.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.callout-titled .callout-body{margin-top:.2em}.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 div.callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body>:first-child{margin-top:.5em}.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){margin-bottom:.5rem}.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:#6c757d}div.callout.callout-style-default>.callout-header{background-color:#6c757d}div.callout-note.callout{border-left-color:#c6c5b9}div.callout-note.callout-style-default>.callout-header{background-color:#f9f9f8}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:#28828a}div.callout-tip.callout-style-default>.callout-header{background-color:#eaf3f3}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:#b72317}div.callout-warning.callout-style-default>.callout-header{background-color:#f8e9e8}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:#b72317}div.callout-caution.callout-style-default>.callout-header{background-color:#f8e9e8}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:#28828a}div.callout-important.callout-style-default>.callout-header{background-color:#eaf3f3}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 .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:#dee2e6;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem;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:#000}.quarto-sidebar-toggle-icon{color:#dee2e6;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 #dee2e6 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}.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 #dee2e6}#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 .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #dee2e6;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 #dee2e6;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{color:#fefefe;background-color:#6c757d;border-color:#6c757d}.btn.btn-quarto:hover,div.cell-output-display .btn-quarto:hover{color:#fefefe;background-color:#828a91;border-color:#7b838a}.btn-check:focus+.btn.btn-quarto,.btn.btn-quarto:focus,.btn-check:focus+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:focus{color:#fefefe;background-color:#828a91;border-color:#7b838a;box-shadow:0 0 0 .25rem rgba(130,138,144,.5)}.btn-check:checked+.btn.btn-quarto,.btn-check:active+.btn.btn-quarto,.btn.btn-quarto:active,.btn.btn-quarto.active,.show>.btn.btn-quarto.dropdown-toggle,.btn-check:checked+div.cell-output-display .btn-quarto,.btn-check:active+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:active,div.cell-output-display .btn-quarto.active,.show>div.cell-output-display .btn-quarto.dropdown-toggle{color:#000;background-color:#899197;border-color:#7b838a}.btn-check:checked+.btn.btn-quarto:focus,.btn-check:active+.btn.btn-quarto:focus,.btn.btn-quarto:active:focus,.btn.btn-quarto.active:focus,.show>.btn.btn-quarto.dropdown-toggle:focus,.btn-check:checked+div.cell-output-display .btn-quarto:focus,.btn-check:active+div.cell-output-display .btn-quarto:focus,div.cell-output-display .btn-quarto:active:focus,div.cell-output-display .btn-quarto.active:focus,.show>div.cell-output-display .btn-quarto.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,144,.5)}.btn.btn-quarto:disabled,.btn.btn-quarto.disabled,div.cell-output-display .btn-quarto:disabled,div.cell-output-display .btn-quarto.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}nav.quarto-secondary-nav.color-navbar{background-color:#106470;color:#fff}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#fff}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner{margin-bottom:0;padding-bottom:0}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:#6c757d}/*! 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:var(--bs-font-monospace);color:#1a1a1a;border:solid #1a1a1a 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:var(--bs-font-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}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;transform:translate3d(0, 0, 0)}.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;transform:translate3d(0, 0, 0)}.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;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.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;transform:translate3d(0, 0, 0);margin-bottom:1em}}.quarto-video{margin-bottom:1em}.table>thead{border-top-width:0}.table>:not(caption)>*:not(:last-child)>*{border-bottom-color:#b3b3b3;border-bottom-style:solid;border-bottom-width:1px}.table>:not(:first-child){border-top:1px solid gray;border-bottom:1px solid inherit}.table tbody{border-bottom-color:gray}a.external:after{display:inline-block;height:.75rem;width:.75rem;margin-bottom:.15em;margin-left:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}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,.code-with-filename .code-with-filename-file pre{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file,.quarto-dark .code-with-filename .code-with-filename-file pre{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.quarto-title-banner{margin-bottom:1em;color:#fff;background:#106470}.quarto-title-banner .code-tools-button{color:#ccc}.quarto-title-banner .code-tools-button:hover{color:#fff}.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}}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:.25rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:repeat(2, 1fr)}#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:-5px}#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 a{color:#000}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.7em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#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 .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#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 .description .abstract-title,#title-block-header.quarto-title-block.default .abstract .abstract-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:1fr 1fr}.quarto-title-tools-only{display:flex;justify-content:right}title,h1,.h1,h2,.h2,h3,.h3,h4,.h4,h5,.h5,h6,.h6{color:#003a41}div.sidebar-item-container .active{font-weight:bold}.sidebar .nav-link.active{font-weight:bold}.navbar-dark .navbar-nav .nav-link.active{color:#fff;text-decoration:underline}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:visited{text-decoration:underline;color:#fff}.nav-footer,.nav-footer-left,.nav-footer-right,.nav-footer-center{font-size:1rem}.figure{text-align:center;text-indent:0;border-bottom:1px solid #dee2e6}.navbar-title{padding-left:20px}.btn{background-color:#106470;border:none;color:#fff;padding:12px 30px;cursor:pointer;font-size:20px}.btn:hover{color:#fff;background-color:#003a41}div.callout-exercise.callout{border-left-color:#ffb400}div.callout-exercise.callout-style-default>.callout-header{background-color:#fff8e6}.callout-exercise>.callout-header::before{font-family:"Font Awesome 5 Free";content:"";color:#ffb400;margin-right:10px}.callout-answer>.callout-header::before{font-family:"Font Awesome 5 Free";content:"";color:#c6c5b9;margin-right:10px}div.callout-answer.callout{border-left-color:#c6c5b9}div.callout-answer.callout-style-default>.callout-header{background-color:#f9f9f8}.callout-hint>.callout-header::before{font-family:"Font Awesome 5 Free";content:"";color:#6c757d;margin-right:10px}div.callout-hint.callout{border-left-color:#6c757d}div.callout-hint.callout-style-default>.callout-header{background-color:#f0f1f2}/*# sourceMappingURL=038018dfc50d695214e8253e62c2ede5.css.map */ diff --git a/site_libs/bootstrap/bootstrap.min.js b/site_libs/bootstrap/bootstrap.min.js new file mode 100644 index 0000000..cc0a255 --- /dev/null +++ b/site_libs/bootstrap/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 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="transitionend",e=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 e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},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(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=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?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();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?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);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})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=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 L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),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(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.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.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").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(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.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(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{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=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={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;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;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=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={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];zt(s)&&Rt(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}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(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 Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(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 ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={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=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(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,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-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)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(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=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={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:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(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,de(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 fe={passive:!0};const pe={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=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(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?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,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]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={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,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.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"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(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 Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={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=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(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})}},Ie={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=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].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,[bt,_t].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}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={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=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(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 Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_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}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.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(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_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)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_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}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||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,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_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(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.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)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["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:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),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=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_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"function"==typeof t?t.call(this._element):t}_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:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?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()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_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(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/site_libs/clipboard/clipboard.min.js b/site_libs/clipboard/clipboard.min.js new file mode 100644 index 0000000..1103f81 --- /dev/null +++ b/site_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}",u.sheet.cssRules.length),u.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",u.sheet.cssRules.length),u.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")}',u.sheet.cssRules.length)),u=document.querySelectorAll("[id]"),t=[].map.call(u,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/site_libs/quarto-html/popper.min.js b/site_libs/quarto-html/popper.min.js new file mode 100644 index 0000000..2269d66 --- /dev/null +++ b/site_libs/quarto-html/popper.min.js @@ -0,0 +1,6 @@ +/** + * @popperjs/core v2.11.4 - 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(e,t){void 0===t&&(t=!1);var n=e.getBoundingClientRect(),o=1,i=1;if(r(e)&&t){var a=e.offsetHeight,f=e.offsetWidth;f>0&&(o=s(n.width)/f||1),a>0&&(i=s(n.height)/a||1)}return{width:n.width/o,height:n.height/i,top:n.top/i,right:n.right/o,bottom:n.bottom/i,left:n.left/o,x:n.left/o,y:n.top/i}}function c(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function p(e){return e?(e.nodeName||"").toLowerCase():null}function u(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function l(e){return f(u(e)).left+c(e).scrollLeft}function d(e){return t(e).getComputedStyle(e)}function h(e){var t=d(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function m(e,n,o){void 0===o&&(o=!1);var i,a,d=r(n),m=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),v=u(n),g=f(e,m),y={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(d||!d&&!o)&&(("body"!==p(n)||h(v))&&(y=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:c(i)),r(n)?((b=f(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):v&&(b.x=l(v))),{x:g.left+y.scrollLeft-b.x,y:g.top+y.scrollTop-b.y,width:g.width,height:g.height}}function v(e){var t=f(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 g(e){return"html"===p(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||u(e)}function y(e){return["html","body","#document"].indexOf(p(e))>=0?e.ownerDocument.body:r(e)&&h(e)?e:y(g(e))}function b(e,n){var r;void 0===n&&(n=[]);var o=y(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],h(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(b(g(s)))}function x(e){return["table","td","th"].indexOf(p(e))>=0}function w(e){return r(e)&&"fixed"!==d(e).position?e.offsetParent:null}function O(e){for(var n=t(e),i=w(e);i&&x(i)&&"static"===d(i).position;)i=w(i);return i&&("html"===p(i)||"body"===p(i)&&"static"===d(i).position)?n:i||function(e){var t=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&r(e)&&"fixed"===d(e).position)return null;var n=g(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(p(n))<0;){var i=d(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 j="top",E="bottom",D="right",A="left",L="auto",P=[j,E,D,A],M="start",k="end",W="viewport",B="popper",H=P.reduce((function(e,t){return e.concat([t+"-"+M,t+"-"+k])}),[]),T=[].concat(P,[L]).reduce((function(e,t){return e.concat([t,t+"-"+M,t+"-"+k])}),[]),R=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function S(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 q(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 V(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function N(e,r){return r===W?V(function(e){var n=t(e),r=u(e),o=n.visualViewport,i=r.clientWidth,a=r.clientHeight,s=0,f=0;return o&&(i=o.width,a=o.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(s=o.offsetLeft,f=o.offsetTop)),{width:i,height:a,x:s+l(e),y:f}}(e)):n(r)?function(e){var t=f(e);return t.top=t.top+e.clientTop,t.left=t.left+e.clientLeft,t.bottom=t.top+e.clientHeight,t.right=t.left+e.clientWidth,t.width=e.clientWidth,t.height=e.clientHeight,t.x=t.left,t.y=t.top,t}(r):V(function(e){var t,n=u(e),r=c(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+l(e),p=-r.scrollTop;return"rtl"===d(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:p}}(u(e)))}function I(e,t,o){var s="clippingParents"===t?function(e){var t=b(g(e)),o=["absolute","fixed"].indexOf(d(e).position)>=0&&r(e)?O(e):e;return n(o)?t.filter((function(e){return n(e)&&q(e,o)&&"body"!==p(e)})):[]}(e):[].concat(t),f=[].concat(s,[o]),c=f[0],u=f.reduce((function(t,n){var r=N(e,n);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}),N(e,c));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function _(e){return e.split("-")[1]}function F(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function U(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?_(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case j:t={x:s,y:n.y-r.height};break;case E:t={x:s,y:n.y+n.height};break;case D:t={x:n.x+n.width,y:f};break;case A:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?F(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case M:t[c]=t[c]-(n[p]/2-r[p]/2);break;case k:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function z(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function X(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function Y(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.boundary,s=void 0===a?"clippingParents":a,c=r.rootBoundary,p=void 0===c?W:c,l=r.elementContext,d=void 0===l?B:l,h=r.altBoundary,m=void 0!==h&&h,v=r.padding,g=void 0===v?0:v,y=z("number"!=typeof g?g:X(g,P)),b=d===B?"reference":B,x=e.rects.popper,w=e.elements[m?b:d],O=I(n(w)?w:w.contextElement||u(e.elements.popper),s,p),A=f(e.elements.reference),L=U({reference:A,element:x,strategy:"absolute",placement:i}),M=V(Object.assign({},x,L)),k=d===B?M:A,H={top:O.top-k.top+y.top,bottom:k.bottom-O.bottom+y.bottom,left:O.left-k.left+y.left,right:k.right-O.right+y.right},T=e.modifiersData.offset;if(d===B&&T){var R=T[i];Object.keys(H).forEach((function(e){var t=[D,E].indexOf(e)>=0?1:-1,n=[j,E].indexOf(e)>=0?"y":"x";H[e]+=R[n]*t}))}return H}var G={placement:"bottom",modifiers:[],strategy:"absolute"};function J(){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,[A,D].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}},ie={left:"right",right:"left",bottom:"top",top:"bottom"};function ae(e){return e.replace(/left|right|bottom|top/g,(function(e){return ie[e]}))}var se={start:"end",end:"start"};function fe(e){return e.replace(/start|end/g,(function(e){return se[e]}))}function ce(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?T:f,p=_(r),u=p?s?H:H.filter((function(e){return _(e)===p})):P,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]=Y(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 pe={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,g=C(v),y=f||(g===v||!h?[ae(v)]:function(e){if(C(e)===L)return[];var t=ae(e);return[fe(e),t,fe(t)]}(v)),b=[v].concat(y).reduce((function(e,n){return e.concat(C(n)===L?ce(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,P=!0,k=b[0],W=0;W=0,S=R?"width":"height",q=Y(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),V=R?T?D:A:T?E:j;x[S]>w[S]&&(V=ae(V));var N=ae(V),I=[];if(i&&I.push(q[H]<=0),s&&I.push(q[V]<=0,q[N]<=0),I.every((function(e){return e}))){k=B,P=!1;break}O.set(B,I)}if(P)for(var F=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 k=t,"break"},U=h?3:1;U>0;U--){if("break"===F(U))break}t.placement!==k&&(t.modifiersData[r]._skip=!0,t.placement=k,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function ue(e,t,n){return i(e,a(t,n))}var le={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,g=n.tetherOffset,y=void 0===g?0:g,b=Y(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=_(t.placement),L=!w,P=F(x),k="x"===P?"y":"x",W=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,q={x:0,y:0};if(W){if(s){var V,N="y"===P?j:A,I="y"===P?E:D,U="y"===P?"height":"width",z=W[P],X=z+b[N],G=z-b[I],J=m?-H[U]/2:0,K=w===M?B[U]:H[U],Q=w===M?-H[U]:-B[U],Z=t.elements.arrow,$=m&&Z?v(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=ue(0,B[U],$[U]),oe=L?B[U]/2-J-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=L?-B[U]/2+J+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&O(t.elements.arrow),se=ae?"y"===P?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(V=null==S?void 0:S[P])?V:0,ce=z+ie-fe,pe=ue(m?a(X,z+oe-fe-se):X,z,m?i(G,ce):G);W[P]=pe,q[P]=pe-z}if(c){var le,de="x"===P?j:A,he="x"===P?E:D,me=W[k],ve="y"===k?"height":"width",ge=me+b[de],ye=me-b[he],be=-1!==[j,A].indexOf(x),xe=null!=(le=null==S?void 0:S[k])?le:0,we=be?ge:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ye,je=m&&be?function(e,t,n){var r=ue(e,t,n);return r>n?n:r}(we,me,Oe):ue(m?we:ge,me,m?Oe:ye);W[k]=je,q[k]=je-me}t.modifiersData[r]=q}},requiresIfExists:["offset"]};var de={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=F(s),c=[A,D].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return z("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:X(e,P))}(o.padding,n),u=v(i),l="y"===f?j:A,d="y"===f?E:D,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],g=O(i),y=g?"y"===f?g.clientHeight||0:g.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],L=y/2-u[c]/2+b,M=ue(x,L,w),k=f;n.modifiersData[r]=((t={})[k]=M,t.centerOffset=M-L,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)))&&q(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function he(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 me(e){return[j,D,E,A].some((function(t){return e[t]>=0}))}var ve={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=Y(t,{elementContext:"reference"}),s=Y(t,{altBoundary:!0}),f=he(a,r),c=he(s,o,i),p=me(f),u=me(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})}},ge=K({defaultModifiers:[Z,$,ne,re]}),ye=[Z,$,ne,re,oe,pe,le,de,ve],be=K({defaultModifiers:ye});e.applyStyles=re,e.arrow=de,e.computeStyles=ne,e.createPopper=be,e.createPopperLite=ge,e.defaultModifiers=ye,e.detectOverflow=Y,e.eventListeners=Z,e.flip=pe,e.hide=ve,e.offset=oe,e.popperGenerator=K,e.popperOffsets=$,e.preventOverflow=le,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/site_libs/quarto-html/quarto-syntax-highlighting.css b/site_libs/quarto-html/quarto-syntax-highlighting.css new file mode 100644 index 0000000..d9fd98f --- /dev/null +++ b/site_libs/quarto-html/quarto-syntax-highlighting.css @@ -0,0 +1,203 @@ +/* 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-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-style: inherit; +} + +.prevent-inlining { + content: " { + // Find any conflicting margin elements and add margins to the + // top to prevent overlap + const marginChildren = window.document.querySelectorAll( + ".column-margin.column-container > * " + ); + + 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; + console.log({ + childtop: marginChild.getBoundingClientRect().top, + scroll: window.scrollY, + top, + lastBottom, + }); + if (top < lastBottom) { + const margin = lastBottom - top; + marginChild.style.marginTop = `${margin}px`; + } + const styles = window.getComputedStyle(marginChild); + const marginTop = parseFloat(styles["marginTop"]); + + console.log({ + top, + height: marginChild.getBoundingClientRect().height, + marginTop, + total: top + marginChild.getBoundingClientRect().height + 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, 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 + ) { + sectionIndex = 0; + } else { + 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() { + const currentPagePath = offsetAbsoluteUrl(window.location.href); + 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"; + } + + 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.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; + } + + 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) { + 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) { + walk(tocEl, 0); + updateActiveLink(); + } + + // 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 (!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/site_libs/quarto-html/tippy.css b/site_libs/quarto-html/tippy.css new file mode 100644 index 0000000..e6ae635 --- /dev/null +++ b/site_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/site_libs/quarto-html/tippy.umd.min.js b/site_libs/quarto-html/tippy.umd.min.js new file mode 100644 index 0000000..ca292be --- /dev/null +++ b/site_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/site_libs/quarto-nav/quarto-nav.js b/site_libs/quarto-nav/quarto-nav.js new file mode 100644 index 0000000..3b21201 --- /dev/null +++ b/site_libs/quarto-nav/quarto-nav.js @@ -0,0 +1,277 @@ +const headroomChanged = new CustomEvent("quarto-hrChanged", { + detail: {}, + bubbles: true, + cancelable: false, + composed: false, +}); + +window.document.addEventListener("DOMContentLoaded", function () { + let init = false; + + // Manage the back to top button, if one is present. + let lastScrollTop = window.pageYOffset || document.documentElement.scrollTop; + const scrollDownBuffer = 5; + const scrollUpBuffer = 35; + const btn = document.getElementById("quarto-back-to-top"); + const hideBackToTop = () => { + btn.style.display = "none"; + }; + const showBackToTop = () => { + btn.style.display = "inline-block"; + }; + if (btn) { + window.document.addEventListener( + "scroll", + function () { + const currentScrollTop = + window.pageYOffset || document.documentElement.scrollTop; + + // Shows and hides the button 'intelligently' as the user scrolls + if (currentScrollTop - scrollDownBuffer > lastScrollTop) { + hideBackToTop(); + lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop; + } else if (currentScrollTop < lastScrollTop - scrollUpBuffer) { + showBackToTop(); + lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop; + } + + // Show the button at the bottom, hides it at the top + if (currentScrollTop <= 0) { + hideBackToTop(); + } else if ( + window.innerHeight + currentScrollTop >= + document.body.offsetHeight + ) { + showBackToTop(); + } + }, + false + ); + } + + function throttle(func, wait) { + var timeout; + return function () { + const context = this; + const args = arguments; + const later = function () { + clearTimeout(timeout); + timeout = null; + func.apply(context, args); + }; + + if (!timeout) { + timeout = setTimeout(later, wait); + } + }; + } + + function headerOffset() { + // Set an offset if there is are fixed top navbar + const headerEl = window.document.querySelector("header.fixed-top"); + if (headerEl) { + return headerEl.clientHeight; + } else { + return 0; + } + } + + function footerOffset() { + const footerEl = window.document.querySelector("footer.footer"); + if (footerEl) { + return footerEl.clientHeight; + } else { + return 0; + } + } + + function updateDocumentOffsetWithoutAnimation() { + updateDocumentOffset(false); + } + + function updateDocumentOffset(animated) { + // set body offset + const topOffset = headerOffset(); + const bodyOffset = topOffset + footerOffset(); + const bodyEl = window.document.body; + bodyEl.setAttribute("data-bs-offset", topOffset); + bodyEl.style.paddingTop = topOffset + "px"; + + // deal with sidebar offsets + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + if (!animated) { + sidebar.classList.add("notransition"); + // Remove the no transition class after the animation has time to complete + setTimeout(function () { + sidebar.classList.remove("notransition"); + }, 201); + } + + if (window.Headroom && sidebar.classList.contains("sidebar-unpinned")) { + sidebar.style.top = "0"; + sidebar.style.maxHeight = "100vh"; + } else { + sidebar.style.top = topOffset + "px"; + sidebar.style.maxHeight = "calc(100vh - " + topOffset + "px)"; + } + }); + + // allow space for footer + const mainContainer = window.document.querySelector(".quarto-container"); + if (mainContainer) { + mainContainer.style.minHeight = "calc(100vh - " + bodyOffset + "px)"; + } + + // link offset + let linkStyle = window.document.querySelector("#quarto-target-style"); + if (!linkStyle) { + linkStyle = window.document.createElement("style"); + linkStyle.setAttribute("id", "quarto-target-style"); + window.document.head.appendChild(linkStyle); + } + while (linkStyle.firstChild) { + linkStyle.removeChild(linkStyle.firstChild); + } + if (topOffset > 0) { + linkStyle.appendChild( + window.document.createTextNode(` + section:target::before { + content: ""; + display: block; + height: ${topOffset}px; + margin: -${topOffset}px 0 0; + }`) + ); + } + if (init) { + window.dispatchEvent(headroomChanged); + } + init = true; + } + + // initialize headroom + var header = window.document.querySelector("#quarto-header"); + if (header && window.Headroom) { + const headroom = new window.Headroom(header, { + tolerance: 5, + onPin: function () { + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + sidebar.classList.remove("sidebar-unpinned"); + }); + updateDocumentOffset(); + }, + onUnpin: function () { + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + sidebar.classList.add("sidebar-unpinned"); + }); + updateDocumentOffset(); + }, + }); + headroom.init(); + + let frozen = false; + window.quartoToggleHeadroom = function () { + if (frozen) { + headroom.unfreeze(); + frozen = false; + } else { + headroom.freeze(); + frozen = true; + } + }; + } + + window.addEventListener( + "hashchange", + function (e) { + if ( + getComputedStyle(document.documentElement).scrollBehavior !== "smooth" + ) { + window.scrollTo(0, window.pageYOffset - headerOffset()); + } + }, + false + ); + + // Observe size changed for the header + const headerEl = window.document.querySelector("header.fixed-top"); + if (headerEl && window.ResizeObserver) { + const observer = new window.ResizeObserver( + updateDocumentOffsetWithoutAnimation + ); + observer.observe(headerEl, { + attributes: true, + childList: true, + characterData: true, + }); + } else { + window.addEventListener( + "resize", + throttle(updateDocumentOffsetWithoutAnimation, 50) + ); + } + setTimeout(updateDocumentOffsetWithoutAnimation, 250); + + // fixup index.html links if we aren't on the filesystem + if (window.location.protocol !== "file:") { + const links = window.document.querySelectorAll("a"); + for (let i = 0; i < links.length; i++) { + if (links[i].href) { + links[i].href = links[i].href.replace(/\/index\.html/, "/"); + } + } + + // Fixup any sharing links that require urls + // Append url to any sharing urls + const sharingLinks = window.document.querySelectorAll( + "a.sidebar-tools-main-item" + ); + for (let i = 0; i < sharingLinks.length; i++) { + const sharingLink = sharingLinks[i]; + const href = sharingLink.getAttribute("href"); + if (href) { + sharingLink.setAttribute( + "href", + href.replace("|url|", window.location.href) + ); + } + } + + // Scroll the active navigation item into view, if necessary + const navSidebar = window.document.querySelector("nav#quarto-sidebar"); + if (navSidebar) { + // Find the active item + const activeItem = navSidebar.querySelector("li.sidebar-item a.active"); + if (activeItem) { + // Wait for the scroll height and height to resolve by observing size changes on the + // nav element that is scrollable + const resizeObserver = new ResizeObserver((_entries) => { + // The bottom of the element + const elBottom = activeItem.offsetTop; + const viewBottom = navSidebar.scrollTop + navSidebar.clientHeight; + + // The element height and scroll height are the same, then we are still loading + if (viewBottom !== navSidebar.scrollHeight) { + // Determine if the item isn't visible and scroll to it + if (elBottom >= viewBottom) { + navSidebar.scrollTop = elBottom; + } + + // stop observing now since we've completed the scroll + resizeObserver.unobserve(navSidebar); + } + }); + resizeObserver.observe(navSidebar); + } + } + } +}); diff --git a/site_libs/quarto-search/autocomplete.umd.js b/site_libs/quarto-search/autocomplete.umd.js new file mode 100644 index 0000000..619c57c --- /dev/null +++ b/site_libs/quarto-search/autocomplete.umd.js @@ -0,0 +1,3 @@ +/*! @algolia/autocomplete-js 1.7.3 | MIT License | © Algolia, Inc. and contributors | https://github.com/algolia/autocomplete */ +!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)["@algolia/autocomplete-js"]={})}(this,(function(e){"use strict";function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function n(e){for(var n=1;n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function a(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null==n)return;var r,o,i=[],u=!0,a=!1;try{for(n=n.call(e);!(u=(r=n.next()).done)&&(i.push(r.value),!t||i.length!==t);u=!0);}catch(e){a=!0,o=e}finally{try{u||null==n.return||n.return()}finally{if(a)throw o}}return i}(e,t)||l(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function c(e){return function(e){if(Array.isArray(e))return s(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||l(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function l(e,t){if(e){if("string"==typeof e)return s(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?s(e,t):void 0}}function s(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=n?null===r?null:0:o}function S(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function I(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function E(e,t){var n=[];return Promise.resolve(e(t)).then((function(e){return Promise.all(e.filter((function(e){return Boolean(e)})).map((function(e){if(e.sourceId,n.includes(e.sourceId))throw new Error("[Autocomplete] The `sourceId` ".concat(JSON.stringify(e.sourceId)," is not unique."));n.push(e.sourceId);var t=function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var ae,ce,le,se=null,pe=(ae=-1,ce=-1,le=void 0,function(e){var t=++ae;return Promise.resolve(e).then((function(e){return le&&t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var ye=["props","refresh","store"],be=["inputElement","formElement","panelElement"],Oe=["inputElement"],_e=["inputElement","maxLength"],Pe=["item","source"];function je(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function we(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function Ee(e){var t=e.props,n=e.refresh,r=e.store,o=Ie(e,ye);return{getEnvironmentProps:function(e){var n=e.inputElement,o=e.formElement,i=e.panelElement;function u(e){!r.getState().isOpen&&r.pendingRequests.isEmpty()||e.target===n||!1===[o,i].some((function(t){return n=t,r=e.target,n===r||n.contains(r);var n,r}))&&(r.dispatch("blur",null),t.debug||r.pendingRequests.cancelAll())}return we({onTouchStart:u,onMouseDown:u,onTouchMove:function(e){!1!==r.getState().isOpen&&n===t.environment.document.activeElement&&e.target!==n&&n.blur()}},Ie(e,be))},getRootProps:function(e){return we({role:"combobox","aria-expanded":r.getState().isOpen,"aria-haspopup":"listbox","aria-owns":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label")},e)},getFormProps:function(e){return e.inputElement,we({action:"",noValidate:!0,role:"search",onSubmit:function(i){var u;i.preventDefault(),t.onSubmit(we({event:i,refresh:n,state:r.getState()},o)),r.dispatch("submit",null),null===(u=e.inputElement)||void 0===u||u.blur()},onReset:function(i){var u;i.preventDefault(),t.onReset(we({event:i,refresh:n,state:r.getState()},o)),r.dispatch("reset",null),null===(u=e.inputElement)||void 0===u||u.focus()}},Ie(e,Oe))},getLabelProps:function(e){return we({htmlFor:"".concat(t.id,"-input"),id:"".concat(t.id,"-label")},e)},getInputProps:function(e){var i;function u(e){(t.openOnFocus||Boolean(r.getState().query))&&fe(we({event:e,props:t,query:r.getState().completion||r.getState().query,refresh:n,store:r},o)),r.dispatch("focus",null)}var a=e||{};a.inputElement;var c=a.maxLength,l=void 0===c?512:c,s=Ie(a,_e),p=A(r.getState()),f=function(e){return Boolean(e&&e.match(C))}((null===(i=t.environment.navigator)||void 0===i?void 0:i.userAgent)||""),d=null!=p&&p.itemUrl&&!f?"go":"search";return we({"aria-autocomplete":"both","aria-activedescendant":r.getState().isOpen&&null!==r.getState().activeItemId?"".concat(t.id,"-item-").concat(r.getState().activeItemId):void 0,"aria-controls":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label"),value:r.getState().completion||r.getState().query,id:"".concat(t.id,"-input"),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",enterKeyHint:d,spellCheck:"false",autoFocus:t.autoFocus,placeholder:t.placeholder,maxLength:l,type:"search",onChange:function(e){fe(we({event:e,props:t,query:e.currentTarget.value.slice(0,l),refresh:n,store:r},o))},onKeyDown:function(e){!function(e){var t=e.event,n=e.props,r=e.refresh,o=e.store,i=ge(e,de);if("ArrowUp"===t.key||"ArrowDown"===t.key){var u=function(){var e=n.environment.document.getElementById("".concat(n.id,"-item-").concat(o.getState().activeItemId));e&&(e.scrollIntoViewIfNeeded?e.scrollIntoViewIfNeeded(!1):e.scrollIntoView(!1))},a=function(){var e=A(o.getState());if(null!==o.getState().activeItemId&&e){var n=e.item,u=e.itemInputValue,a=e.itemUrl,c=e.source;c.onActive(ve({event:t,item:n,itemInputValue:u,itemUrl:a,refresh:r,source:c,state:o.getState()},i))}};t.preventDefault(),!1===o.getState().isOpen&&(n.openOnFocus||Boolean(o.getState().query))?fe(ve({event:t,props:n,query:o.getState().query,refresh:r,store:o},i)).then((function(){o.dispatch(t.key,{nextActiveItemId:n.defaultActiveItemId}),a(),setTimeout(u,0)})):(o.dispatch(t.key,{}),a(),u())}else if("Escape"===t.key)t.preventDefault(),o.dispatch(t.key,null),o.pendingRequests.cancelAll();else if("Tab"===t.key)o.dispatch("blur",null),o.pendingRequests.cancelAll();else if("Enter"===t.key){if(null===o.getState().activeItemId||o.getState().collections.every((function(e){return 0===e.items.length})))return void(n.debug||o.pendingRequests.cancelAll());t.preventDefault();var c=A(o.getState()),l=c.item,s=c.itemInputValue,p=c.itemUrl,f=c.source;if(t.metaKey||t.ctrlKey)void 0!==p&&(f.onSelect(ve({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),n.navigator.navigateNewTab({itemUrl:p,item:l,state:o.getState()}));else if(t.shiftKey)void 0!==p&&(f.onSelect(ve({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),n.navigator.navigateNewWindow({itemUrl:p,item:l,state:o.getState()}));else if(t.altKey);else{if(void 0!==p)return f.onSelect(ve({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),void n.navigator.navigate({itemUrl:p,item:l,state:o.getState()});fe(ve({event:t,nextState:{isOpen:!1},props:n,query:s,refresh:r,store:o},i)).then((function(){f.onSelect(ve({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i))}))}}}(we({event:e,props:t,refresh:n,store:r},o))},onFocus:u,onBlur:y,onClick:function(n){e.inputElement!==t.environment.document.activeElement||r.getState().isOpen||u(n)}},s)},getPanelProps:function(e){return we({onMouseDown:function(e){e.preventDefault()},onMouseLeave:function(){r.dispatch("mouseleave",null)}},e)},getListProps:function(e){return we({role:"listbox","aria-labelledby":"".concat(t.id,"-label"),id:"".concat(t.id,"-list")},e)},getItemProps:function(e){var i=e.item,u=e.source,a=Ie(e,Pe);return we({id:"".concat(t.id,"-item-").concat(i.__autocomplete_id),role:"option","aria-selected":r.getState().activeItemId===i.__autocomplete_id,onMouseMove:function(e){if(i.__autocomplete_id!==r.getState().activeItemId){r.dispatch("mousemove",i.__autocomplete_id);var t=A(r.getState());if(null!==r.getState().activeItemId&&t){var u=t.item,a=t.itemInputValue,c=t.itemUrl,l=t.source;l.onActive(we({event:e,item:u,itemInputValue:a,itemUrl:c,refresh:n,source:l,state:r.getState()},o))}}},onMouseDown:function(e){e.preventDefault()},onClick:function(e){var a=u.getItemInputValue({item:i,state:r.getState()}),c=u.getItemUrl({item:i,state:r.getState()});(c?Promise.resolve():fe(we({event:e,nextState:{isOpen:!1},props:t,query:a,refresh:n,store:r},o))).then((function(){u.onSelect(we({event:e,item:i,itemInputValue:a,itemUrl:c,refresh:n,source:u,state:r.getState()},o))}))}},a)}}}function Ae(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Ce(e){for(var t=1;t0},reshape:function(e){return e.sources}},e),{},{id:null!==(n=e.id)&&void 0!==n?n:v(),plugins:o,initialState:H({activeItemId:null,query:"",completion:null,collections:[],isOpen:!1,status:"idle",context:{}},e.initialState),onStateChange:function(t){var n;null===(n=e.onStateChange)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onStateChange)||void 0===n?void 0:n.call(e,t)}))},onSubmit:function(t){var n;null===(n=e.onSubmit)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onSubmit)||void 0===n?void 0:n.call(e,t)}))},onReset:function(t){var n;null===(n=e.onReset)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onReset)||void 0===n?void 0:n.call(e,t)}))},getSources:function(n){return Promise.all([].concat(F(o.map((function(e){return e.getSources}))),[e.getSources]).filter(Boolean).map((function(e){return E(e,n)}))).then((function(e){return d(e)})).then((function(e){return e.map((function(e){return H(H({},e),{},{onSelect:function(n){e.onSelect(n),t.forEach((function(e){var t;return null===(t=e.onSelect)||void 0===t?void 0:t.call(e,n)}))},onActive:function(n){e.onActive(n),t.forEach((function(e){var t;return null===(t=e.onActive)||void 0===t?void 0:t.call(e,n)}))}})}))}))},navigator:H({navigate:function(e){var t=e.itemUrl;r.location.assign(t)},navigateNewTab:function(e){var t=e.itemUrl,n=r.open(t,"_blank","noopener");null==n||n.focus()},navigateNewWindow:function(e){var t=e.itemUrl;r.open(t,"_blank","noopener")}},e.navigator)})}(e,t),r=R(Te,n,(function(e){var t=e.prevState,r=e.state;n.onStateChange(Be({prevState:t,state:r,refresh:u},o))})),o=function(e){var t=e.store;return{setActiveItemId:function(e){t.dispatch("setActiveItemId",e)},setQuery:function(e){t.dispatch("setQuery",e)},setCollections:function(e){var n=0,r=e.map((function(e){return L(L({},e),{},{items:d(e.items).map((function(e){return L(L({},e),{},{__autocomplete_id:n++})}))})}));t.dispatch("setCollections",r)},setIsOpen:function(e){t.dispatch("setIsOpen",e)},setStatus:function(e){t.dispatch("setStatus",e)},setContext:function(e){t.dispatch("setContext",e)}}}({store:r}),i=Ee(Be({props:n,refresh:u,store:r},o));function u(){return fe(Be({event:new Event("input"),nextState:{isOpen:r.getState().isOpen},props:n,query:r.getState().query,refresh:u,store:r},o))}return n.plugins.forEach((function(e){var n;return null===(n=e.subscribe)||void 0===n?void 0:n.call(e,Be(Be({},o),{},{refresh:u,onSelect:function(e){t.push({onSelect:e})},onActive:function(e){t.push({onActive:e})}}))})),function(e){var t,n,r=e.metadata,o=e.environment;if(null===(t=o.navigator)||void 0===t||null===(n=t.userAgent)||void 0===n?void 0:n.includes("Algolia Crawler")){var i=o.document.createElement("meta"),u=o.document.querySelector("head");i.name="algolia:metadata",setTimeout((function(){i.content=JSON.stringify(r),u.appendChild(i)}),0)}}({metadata:ke({plugins:n.plugins,options:e}),environment:n.environment}),Be(Be({refresh:u},i),o)}var Ue=function(e,t,n,r){var o;t[0]=0;for(var i=1;i=5&&((o||!e&&5===r)&&(u.push(r,0,o,n),r=6),e&&(u.push(r,e,0,n),r=6)),o=""},c=0;c"===t?(r=1,o=""):o=t+o[0]:i?t===i?i="":o+=t:'"'===t||"'"===t?i=t:">"===t?(a(),r=1):r&&("="===t?(r=5,n=o,o=""):"/"===t&&(r<5||">"===e[c][l+1])?(a(),3===r&&(u=u[0]),r=u,(u=u[0]).push(2,0,r),r=0):" "===t||"\t"===t||"\n"===t||"\r"===t?(a(),r=2):o+=t),3===r&&"!--"===o&&(r=4,u=u[0])}return a(),u}(e)),t),arguments,[])).length>1?t:t[0]}var We=function(e){var t=e.environment,n=t.document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("class","aa-ClearIcon"),n.setAttribute("viewBox","0 0 24 24"),n.setAttribute("width","18"),n.setAttribute("height","18"),n.setAttribute("fill","currentColor");var r=t.document.createElementNS("http://www.w3.org/2000/svg","path");return r.setAttribute("d","M5.293 6.707l5.293 5.293-5.293 5.293c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0l5.293-5.293 5.293 5.293c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-5.293-5.293 5.293-5.293c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0l-5.293 5.293-5.293-5.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414z"),n.appendChild(r),n};function Qe(e,t){if("string"==typeof t){var n=e.document.querySelector(t);return"The element ".concat(JSON.stringify(t)," is not in the document."),n}return t}function $e(){for(var e=arguments.length,t=new Array(e),n=0;n2&&(u.children=arguments.length>3?lt.call(arguments,2):n),"function"==typeof e&&null!=e.defaultProps)for(i in e.defaultProps)void 0===u[i]&&(u[i]=e.defaultProps[i]);return _t(e,u,r,o,null)}function _t(e,t,n,r,o){var i={type:e,props:t,key:n,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++pt:o};return null==o&&null!=st.vnode&&st.vnode(i),i}function Pt(e){return e.children}function jt(e,t){this.props=e,this.context=t}function wt(e,t){if(null==t)return e.__?wt(e.__,e.__.__k.indexOf(e)+1):null;for(var n;t0?_t(d.type,d.props,d.key,null,d.__v):d)){if(d.__=n,d.__b=n.__b+1,null===(f=g[s])||f&&d.key==f.key&&d.type===f.type)g[s]=void 0;else for(p=0;p0&&void 0!==arguments[0]?arguments[0]:[];return{get:function(){return e},add:function(t){var n=e[e.length-1];(null==n?void 0:n.isHighlighted)===t.isHighlighted?e[e.length-1]={value:n.value+t.value,isHighlighted:n.isHighlighted}:e.push(t)}}}(n?[{value:n,isHighlighted:!1}]:[]);return t.forEach((function(e){var t=e.split(Ht);r.add({value:t[0],isHighlighted:!0}),""!==t[1]&&r.add({value:t[1],isHighlighted:!1})})),r.get()}function Wt(e){return function(e){if(Array.isArray(e))return Qt(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return Qt(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Qt(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function Qt(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n",""":'"',"'":"'"},Gt=new RegExp(/\w/i),Kt=/&(amp|quot|lt|gt|#39);/g,Jt=RegExp(Kt.source);function Yt(e,t){var n,r,o,i=e[t],u=(null===(n=e[t+1])||void 0===n?void 0:n.isHighlighted)||!0,a=(null===(r=e[t-1])||void 0===r?void 0:r.isHighlighted)||!0;return Gt.test((o=i.value)&&Jt.test(o)?o.replace(Kt,(function(e){return zt[e]})):o)||a!==u?i.isHighlighted:a}function Xt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Zt(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function mn(e){return function(e){if(Array.isArray(e))return vn(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return vn(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return vn(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function vn(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0;if(!O.value.core.openOnFocus&&!t.query)return n;var r=Boolean(h.current||O.value.renderer.renderNoResults);return!n&&r||n},__autocomplete_metadata:{userAgents:Sn,options:e}}))})),j=p(n({collections:[],completion:null,context:{},isOpen:!1,query:"",activeItemId:null,status:"idle"},O.value.core.initialState)),w={getEnvironmentProps:O.value.renderer.getEnvironmentProps,getFormProps:O.value.renderer.getFormProps,getInputProps:O.value.renderer.getInputProps,getItemProps:O.value.renderer.getItemProps,getLabelProps:O.value.renderer.getLabelProps,getListProps:O.value.renderer.getListProps,getPanelProps:O.value.renderer.getPanelProps,getRootProps:O.value.renderer.getRootProps},S={setActiveItemId:P.value.setActiveItemId,setQuery:P.value.setQuery,setCollections:P.value.setCollections,setIsOpen:P.value.setIsOpen,setStatus:P.value.setStatus,setContext:P.value.setContext,refresh:P.value.refresh},I=d((function(){return Ve.bind(O.value.renderer.renderer.createElement)})),E=d((function(){return ct({autocomplete:P.value,autocompleteScopeApi:S,classNames:O.value.renderer.classNames,environment:O.value.core.environment,isDetached:_.value,placeholder:O.value.core.placeholder,propGetters:w,setIsModalOpen:k,state:j.current,translations:O.value.renderer.translations})}));function A(){tt(E.value.panel,{style:_.value?{}:wn({panelPlacement:O.value.renderer.panelPlacement,container:E.value.root,form:E.value.form,environment:O.value.core.environment})})}function C(e){j.current=e;var t={autocomplete:P.value,autocompleteScopeApi:S,classNames:O.value.renderer.classNames,components:O.value.renderer.components,container:O.value.renderer.container,html:I.value,dom:E.value,panelContainer:_.value?E.value.detachedContainer:O.value.renderer.panelContainer,propGetters:w,state:j.current,renderer:O.value.renderer.renderer},r=!g(e)&&!h.current&&O.value.renderer.renderNoResults||O.value.renderer.render;!function(e){var t=e.autocomplete,r=e.autocompleteScopeApi,o=e.dom,i=e.propGetters,u=e.state;nt(o.root,i.getRootProps(n({state:u,props:t.getRootProps({})},r))),nt(o.input,i.getInputProps(n({state:u,props:t.getInputProps({inputElement:o.input}),inputElement:o.input},r))),tt(o.label,{hidden:"stalled"===u.status}),tt(o.loadingIndicator,{hidden:"stalled"!==u.status}),tt(o.clearButton,{hidden:!u.query})}(t),function(e,t){var r=t.autocomplete,o=t.autocompleteScopeApi,u=t.classNames,a=t.html,c=t.dom,l=t.panelContainer,s=t.propGetters,p=t.state,f=t.components,d=t.renderer;if(p.isOpen){l.contains(c.panel)||"loading"===p.status||l.appendChild(c.panel),c.panel.classList.toggle("aa-Panel--stalled","stalled"===p.status);var m=p.collections.filter((function(e){var t=e.source,n=e.items;return t.templates.noResults||n.length>0})).map((function(e,t){var c=e.source,l=e.items;return d.createElement("section",{key:t,className:u.source,"data-autocomplete-source-id":c.sourceId},c.templates.header&&d.createElement("div",{className:u.sourceHeader},c.templates.header({components:f,createElement:d.createElement,Fragment:d.Fragment,items:l,source:c,state:p,html:a})),c.templates.noResults&&0===l.length?d.createElement("div",{className:u.sourceNoResults},c.templates.noResults({components:f,createElement:d.createElement,Fragment:d.Fragment,source:c,state:p,html:a})):d.createElement("ul",i({className:u.list},s.getListProps(n({state:p,props:r.getListProps({})},o))),l.map((function(e){var t=r.getItemProps({item:e,source:c});return d.createElement("li",i({key:t.id,className:u.item},s.getItemProps(n({state:p,props:t},o))),c.templates.item({components:f,createElement:d.createElement,Fragment:d.Fragment,item:e,state:p,html:a}))}))),c.templates.footer&&d.createElement("div",{className:u.sourceFooter},c.templates.footer({components:f,createElement:d.createElement,Fragment:d.Fragment,items:l,source:c,state:p,html:a})))})),v=d.createElement(d.Fragment,null,d.createElement("div",{className:u.panelLayout},m),d.createElement("div",{className:"aa-GradientBottom"})),h=m.reduce((function(e,t){return e[t.props["data-autocomplete-source-id"]]=t,e}),{});e(n(n({children:v,state:p,sections:m,elements:h},d),{},{components:f,html:a},o),c.panel)}else l.contains(c.panel)&&l.removeChild(c.panel)}(r,t)}function D(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};c();var t=O.value.renderer,n=t.components,r=u(t,In);y.current=Ge(r,O.value.core,{components:Ke(n,(function(e){return!e.value.hasOwnProperty("__autocomplete_componentName")})),initialState:j.current},e),m(),l(),P.value.refresh().then((function(){C(j.current)}))}function k(e){requestAnimationFrame((function(){var t=O.value.core.environment.document.body.contains(E.value.detachedOverlay);e!==t&&(e?(O.value.core.environment.document.body.appendChild(E.value.detachedOverlay),O.value.core.environment.document.body.classList.add("aa-Detached"),E.value.input.focus()):(O.value.core.environment.document.body.removeChild(E.value.detachedOverlay),O.value.core.environment.document.body.classList.remove("aa-Detached"),P.value.setQuery(""),P.value.refresh()))}))}return a((function(){var e=P.value.getEnvironmentProps({formElement:E.value.form,panelElement:E.value.panel,inputElement:E.value.input});return tt(O.value.core.environment,e),function(){tt(O.value.core.environment,Object.keys(e).reduce((function(e,t){return n(n({},e),{},o({},t,void 0))}),{}))}})),a((function(){var e=_.value?O.value.core.environment.document.body:O.value.renderer.panelContainer,t=_.value?E.value.detachedOverlay:E.value.panel;return _.value&&j.current.isOpen&&k(!0),C(j.current),function(){e.contains(t)&&e.removeChild(t)}})),a((function(){var e=O.value.renderer.container;return e.appendChild(E.value.root),function(){e.removeChild(E.value.root)}})),a((function(){var e=f((function(e){C(e.state)}),0);return b.current=function(t){var n=t.state,r=t.prevState;(_.value&&r.isOpen!==n.isOpen&&k(n.isOpen),_.value||!n.isOpen||r.isOpen||A(),n.query!==r.query)&&O.value.core.environment.document.querySelectorAll(".aa-Panel--scrollable").forEach((function(e){0!==e.scrollTop&&(e.scrollTop=0)}));e({state:n})},function(){b.current=void 0}})),a((function(){var e=f((function(){var e=_.value;_.value=O.value.core.environment.matchMedia(O.value.renderer.detachedMediaQuery).matches,e!==_.value?D({}):requestAnimationFrame(A)}),20);return O.value.core.environment.addEventListener("resize",e),function(){O.value.core.environment.removeEventListener("resize",e)}})),a((function(){if(!_.value)return function(){};function e(e){E.value.detachedContainer.classList.toggle("aa-DetachedContainer--modal",e)}function t(t){e(t.matches)}var n=O.value.core.environment.matchMedia(getComputedStyle(O.value.core.environment.document.documentElement).getPropertyValue("--aa-detached-modal-media-query"));e(n.matches);var r=Boolean(n.addEventListener);return r?n.addEventListener("change",t):n.addListener(t),function(){r?n.removeEventListener("change",t):n.removeListener(t)}})),a((function(){return requestAnimationFrame(A),function(){}})),n(n({},S),{},{update:D,destroy:function(){c()}})},e.getAlgoliaFacets=function(e){var t=En({transformResponse:function(e){return e.facetHits}}),r=e.queries.map((function(e){return n(n({},e),{},{type:"facet"})}));return t(n(n({},e),{},{queries:r}))},e.getAlgoliaResults=An,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/site_libs/quarto-search/fuse.min.js b/site_libs/quarto-search/fuse.min.js new file mode 100644 index 0000000..adc2835 --- /dev/null +++ b/site_libs/quarto-search/fuse.min.js @@ -0,0 +1,9 @@ +/** + * Fuse.js v6.6.2 - Lightweight fuzzy-search (http://fusejs.io) + * + * Copyright (c) 2022 Kiro Risk (http://kiro.me) + * All Rights Reserved. Apache Software License 2.0 + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,n=new Map,r=Math.pow(10,t);return{get:function(t){var i=t.match(C).length;if(n.has(i))return n.get(i);var o=1/Math.pow(i,.5*e),c=parseFloat(Math.round(o*r)/r);return n.set(i,c),c},clear:function(){n.clear()}}}var $=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.getFn,i=void 0===n?I.getFn:n,o=t.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o;r(this,e),this.norm=E(c,3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return o(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,g(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();g(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?I.getFn:r,o=n.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o,a=new $({getFn:i,fieldNormWeight:c});return a.setKeys(e.map(_)),a.setSources(t),a.create(),a}function R(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,c=t.expectedLocation,a=void 0===c?0:c,s=t.distance,u=void 0===s?I.distance:s,h=t.ignoreLocation,l=void 0===h?I.ignoreLocation:h,f=r/e.length;if(l)return f;var d=Math.abs(a-o);return u?f+d/u:d?1:f}function N(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:I.minMatchCharLength,n=[],r=-1,i=-1,o=0,c=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}var P=32;function W(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,c=void 0===o?I.location:o,a=i.threshold,s=void 0===a?I.threshold:a,u=i.distance,h=void 0===u?I.distance:u,l=i.includeMatches,f=void 0===l?I.includeMatches:l,d=i.findAllMatches,v=void 0===d?I.findAllMatches:d,g=i.minMatchCharLength,y=void 0===g?I.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?I.isCaseSensitive:p,k=i.ignoreLocation,M=void 0===k?I.ignoreLocation:k;if(r(this,e),this.options={location:c,threshold:s,distance:h,includeMatches:f,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:M},this.pattern=m?t:t.toLowerCase(),this.chunks=[],this.pattern.length){var b=function(e,t){n.chunks.push({pattern:e,alphabet:W(e),startIndex:t})},x=this.pattern.length;if(x>P){for(var w=0,L=x%P,S=x-L;w3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?I.location:i,c=r.distance,a=void 0===c?I.distance:c,s=r.threshold,u=void 0===s?I.threshold:s,h=r.findAllMatches,l=void 0===h?I.findAllMatches:h,f=r.minMatchCharLength,d=void 0===f?I.minMatchCharLength:f,v=r.includeMatches,g=void 0===v?I.includeMatches:v,y=r.ignoreLocation,p=void 0===y?I.ignoreLocation:y;if(t.length>P)throw new Error(w(P));for(var m,k=t.length,M=e.length,b=Math.max(0,Math.min(o,M)),x=u,L=b,S=d>1||g,_=S?Array(M):[];(m=e.indexOf(t,L))>-1;){var O=R(t,{currentLocation:m,expectedLocation:b,distance:a,ignoreLocation:p});if(x=Math.min(O,x),L=m+k,S)for(var j=0;j=z;q-=1){var B=q-1,J=n[e.charAt(B)];if(S&&(_[B]=+!!J),K[q]=(K[q+1]<<1|1)&J,F&&(K[q]|=(A[q+1]|A[q])<<1|1|A[q+1]),K[q]&$&&(C=R(t,{errors:F,currentLocation:B,expectedLocation:b,distance:a,ignoreLocation:p}))<=x){if(x=C,(L=B)<=b)break;z=Math.max(1,2*b-L)}}if(R(t,{errors:F+1,currentLocation:b,expectedLocation:b,distance:a,ignoreLocation:p})>x)break;A=K}var U={isMatch:L>=0,score:Math.max(.001,C)};if(S){var V=N(_,d);V.length?g&&(U.indices=V):U.isMatch=!1}return U}(e,n,i,{location:c+o,distance:a,threshold:s,findAllMatches:u,minMatchCharLength:h,includeMatches:r,ignoreLocation:l}),p=y.isMatch,m=y.score,k=y.indices;p&&(g=!0),v+=m,p&&k&&(d=[].concat(f(d),f(k)))}));var y={isMatch:g,score:g?v/this.chunks.length:1};return g&&r&&(y.indices=d),y}}]),e}(),z=function(){function e(t){r(this,e),this.pattern=t}return o(e,[{key:"search",value:function(){}}],[{key:"isMultiMatch",value:function(e){return D(e,this.multiRegex)}},{key:"isSingleMatch",value:function(e){return D(e,this.singleRegex)}}]),e}();function D(e,t){var n=e.match(t);return n?n[1]:null}var K=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"exact"}},{key:"multiRegex",get:function(){return/^="(.*)"$/}},{key:"singleRegex",get:function(){return/^=(.*)$/}}]),n}(z),q=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"$/}},{key:"singleRegex",get:function(){return/^!(.*)$/}}]),n}(z),B=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"prefix-exact"}},{key:"multiRegex",get:function(){return/^\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^\^(.*)$/}}]),n}(z),J=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-prefix-exact"}},{key:"multiRegex",get:function(){return/^!\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^!\^(.*)$/}}]),n}(z),U=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}}],[{key:"type",get:function(){return"suffix-exact"}},{key:"multiRegex",get:function(){return/^"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^(.*)\$$/}}]),n}(z),V=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-suffix-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^!(.*)\$$/}}]),n}(z),G=function(e){a(n,e);var t=l(n);function n(e){var i,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},c=o.location,a=void 0===c?I.location:c,s=o.threshold,u=void 0===s?I.threshold:s,h=o.distance,l=void 0===h?I.distance:h,f=o.includeMatches,d=void 0===f?I.includeMatches:f,v=o.findAllMatches,g=void 0===v?I.findAllMatches:v,y=o.minMatchCharLength,p=void 0===y?I.minMatchCharLength:y,m=o.isCaseSensitive,k=void 0===m?I.isCaseSensitive:m,M=o.ignoreLocation,b=void 0===M?I.ignoreLocation:M;return r(this,n),(i=t.call(this,e))._bitapSearch=new T(e,{location:a,threshold:u,distance:l,includeMatches:d,findAllMatches:g,minMatchCharLength:p,isCaseSensitive:k,ignoreLocation:b}),i}return o(n,[{key:"search",value:function(e){return this._bitapSearch.searchIn(e)}}],[{key:"type",get:function(){return"fuzzy"}},{key:"multiRegex",get:function(){return/^"(.*)"$/}},{key:"singleRegex",get:function(){return/^(.*)$/}}]),n}(z),H=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){for(var t,n=0,r=[],i=this.pattern.length;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,r.push([t,n-1]);var o=!!r.length;return{isMatch:o,score:o?0:1,indices:r}}}],[{key:"type",get:function(){return"include"}},{key:"multiRegex",get:function(){return/^'"(.*)"$/}},{key:"singleRegex",get:function(){return/^'(.*)$/}}]),n}(z),Q=[K,H,B,J,V,U,q,G],X=Q.length,Y=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;function Z(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.split("|").map((function(e){for(var n=e.trim().split(Y).filter((function(e){return e&&!!e.trim()})),r=[],i=0,o=n.length;i1&&void 0!==arguments[1]?arguments[1]:{},i=n.isCaseSensitive,o=void 0===i?I.isCaseSensitive:i,c=n.includeMatches,a=void 0===c?I.includeMatches:c,s=n.minMatchCharLength,u=void 0===s?I.minMatchCharLength:s,h=n.ignoreLocation,l=void 0===h?I.ignoreLocation:h,f=n.findAllMatches,d=void 0===f?I.findAllMatches:f,v=n.location,g=void 0===v?I.location:v,y=n.threshold,p=void 0===y?I.threshold:y,m=n.distance,k=void 0===m?I.distance:m;r(this,e),this.query=null,this.options={isCaseSensitive:o,includeMatches:a,minMatchCharLength:u,findAllMatches:d,ignoreLocation:l,location:g,threshold:p,distance:k},this.pattern=o?t:t.toLowerCase(),this.query=Z(this.pattern,this.options)}return o(e,[{key:"searchIn",value:function(e){var t=this.query;if(!t)return{isMatch:!1,score:1};var n=this.options,r=n.includeMatches;e=n.isCaseSensitive?e:e.toLowerCase();for(var i=0,o=[],c=0,a=0,s=t.length;a-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function ve(e,t){t.score=e.score}function ge(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?I.includeMatches:r,o=n.includeScore,c=void 0===o?I.includeScore:o,a=[];return i&&a.push(de),c&&a.push(ve),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return a.length&&a.forEach((function(t){t(e,r)})),r}))}var ye=function(){function e(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2?arguments[2]:void 0;r(this,e),this.options=t(t({},I),i),this.options.useExtendedSearch,this._keyStore=new S(this.options.keys),this.setCollection(n,o)}return o(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof $))throw new Error("Incorrect 'index' type");this._myIndex=t||F(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}},{key:"add",value:function(e){k(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{},n=t.limit,r=void 0===n?-1:n,i=this.options,o=i.includeMatches,c=i.includeScore,a=i.shouldSort,s=i.sortFn,u=i.ignoreFieldNorm,h=g(e)?g(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return fe(h,{ignoreFieldNorm:u}),a&&h.sort(s),y(r)&&r>-1&&(h=h.slice(0,r)),ge(h,this._docs,{includeMatches:o,includeScore:c})}},{key:"_searchStringList",value:function(e){var t=re(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(k(n)){var c=t.searchIn(n),a=c.isMatch,s=c.score,u=c.indices;a&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:u}]})}})),r}},{key:"_searchLogical",value:function(e){var t=this,n=function(e,t){var n=(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).auto,r=void 0===n||n,i=function e(n){var i=Object.keys(n),o=ue(n);if(!o&&i.length>1&&!se(n))return e(le(n));if(he(n)){var c=o?n[ce]:i[0],a=o?n[ae]:n[c];if(!g(a))throw new Error(x(c));var s={keyId:j(c),pattern:a};return r&&(s.searcher=re(a,t)),s}var u={children:[],operator:i[0]};return i.forEach((function(t){var r=n[t];v(r)&&r.forEach((function(t){u.children.push(e(t))}))})),u};return se(e)||(e=le(e)),i(e)}(e,this.options),r=function e(n,r,i){if(!n.children){var o=n.keyId,c=n.searcher,a=t._findMatches({key:t._keyStore.get(o),value:t._myIndex.getValueForItemAtKeyId(r,o),searcher:c});return a&&a.length?[{idx:i,item:r,matches:a}]:[]}for(var s=[],u=0,h=n.children.length;u1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?I.getFn:n,i=t.fieldNormWeight,o=void 0===i?I.fieldNormWeight:i,c=e.keys,a=e.records,s=new $({getFn:r,fieldNormWeight:o});return s.setKeys(c),s.setIndexRecords(a),s},ye.config=I,function(){ne.push.apply(ne,arguments)}(te),ye},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Fuse=t(); \ No newline at end of file diff --git a/site_libs/quarto-search/quarto-search.js b/site_libs/quarto-search/quarto-search.js new file mode 100644 index 0000000..f5d852d --- /dev/null +++ b/site_libs/quarto-search/quarto-search.js @@ -0,0 +1,1140 @@ +const kQueryArg = "q"; +const kResultsArg = "show-results"; + +// If items don't provide a URL, then both the navigator and the onSelect +// function aren't called (and therefore, the default implementation is used) +// +// We're using this sentinel URL to signal to those handlers that this +// item is a more item (along with the type) and can be handled appropriately +const kItemTypeMoreHref = "0767FDFD-0422-4E5A-BC8A-3BE11E5BBA05"; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Ensure that search is available on this page. If it isn't, + // should return early and not do anything + var searchEl = window.document.getElementById("quarto-search"); + if (!searchEl) return; + + const { autocomplete } = window["@algolia/autocomplete-js"]; + + let quartoSearchOptions = {}; + let language = {}; + const searchOptionEl = window.document.getElementById( + "quarto-search-options" + ); + if (searchOptionEl) { + const jsonStr = searchOptionEl.textContent; + quartoSearchOptions = JSON.parse(jsonStr); + language = quartoSearchOptions.language; + } + + // note the search mode + if (quartoSearchOptions.type === "overlay") { + searchEl.classList.add("type-overlay"); + } else { + searchEl.classList.add("type-textbox"); + } + + // Used to determine highlighting behavior for this page + // A `q` query param is expected when the user follows a search + // to this page + const currentUrl = new URL(window.location); + const query = currentUrl.searchParams.get(kQueryArg); + const showSearchResults = currentUrl.searchParams.get(kResultsArg); + const mainEl = window.document.querySelector("main"); + + // highlight matches on the page + if (query !== null && mainEl) { + // perform any highlighting + highlight(escapeRegExp(query), mainEl); + + // fix up the URL to remove the q query param + const replacementUrl = new URL(window.location); + replacementUrl.searchParams.delete(kQueryArg); + window.history.replaceState({}, "", replacementUrl); + } + + // function to clear highlighting on the page when the search query changes + // (e.g. if the user edits the query or clears it) + let highlighting = true; + const resetHighlighting = (searchTerm) => { + if (mainEl && highlighting && query !== null && searchTerm !== query) { + clearHighlight(query, mainEl); + highlighting = false; + } + }; + + // Clear search highlighting when the user scrolls sufficiently + const resetFn = () => { + resetHighlighting(""); + window.removeEventListener("quarto-hrChanged", resetFn); + window.removeEventListener("quarto-sectionChanged", resetFn); + }; + + // Register this event after the initial scrolling and settling of events + // on the page + window.addEventListener("quarto-hrChanged", resetFn); + window.addEventListener("quarto-sectionChanged", resetFn); + + // Responsively switch to overlay mode if the search is present on the navbar + // Note that switching the sidebar to overlay mode requires more coordinate (not just + // the media query since we generate different HTML for sidebar overlays than we do + // for sidebar input UI) + const detachedMediaQuery = + quartoSearchOptions.type === "overlay" ? "all" : "(max-width: 991px)"; + + // If configured, include the analytics client to send insights + const plugins = configurePlugins(quartoSearchOptions); + + let lastState = null; + const { setIsOpen, setQuery, setCollections } = autocomplete({ + container: searchEl, + detachedMediaQuery: detachedMediaQuery, + defaultActiveItemId: 0, + panelContainer: "#quarto-search-results", + panelPlacement: quartoSearchOptions["panel-placement"], + debug: false, + openOnFocus: true, + plugins, + classNames: { + form: "d-flex", + }, + translations: { + clearButtonTitle: language["search-clear-button-title"], + detachedCancelButtonText: language["search-detached-cancel-button-title"], + submitButtonTitle: language["search-submit-button-title"], + }, + initialState: { + query, + }, + getItemUrl({ item }) { + return item.href; + }, + onStateChange({ state }) { + // Perhaps reset highlighting + resetHighlighting(state.query); + + // If the panel just opened, ensure the panel is positioned properly + if (state.isOpen) { + if (lastState && !lastState.isOpen) { + setTimeout(() => { + positionPanel(quartoSearchOptions["panel-placement"]); + }, 150); + } + } + + // Perhaps show the copy link + showCopyLink(state.query, quartoSearchOptions); + + lastState = state; + }, + reshape({ sources, state }) { + return sources.map((source) => { + try { + const items = source.getItems(); + + // Validate the items + validateItems(items); + + // group the items by document + const groupedItems = new Map(); + items.forEach((item) => { + const hrefParts = item.href.split("#"); + const baseHref = hrefParts[0]; + const isDocumentItem = hrefParts.length === 1; + + const items = groupedItems.get(baseHref); + if (!items) { + groupedItems.set(baseHref, [item]); + } else { + // If the href for this item matches the document + // exactly, place this item first as it is the item that represents + // the document itself + if (isDocumentItem) { + items.unshift(item); + } else { + items.push(item); + } + groupedItems.set(baseHref, items); + } + }); + + const reshapedItems = []; + let count = 1; + for (const [_key, value] of groupedItems) { + const firstItem = value[0]; + reshapedItems.push({ + ...firstItem, + type: kItemTypeDoc, + }); + + const collapseMatches = quartoSearchOptions["collapse-after"]; + const collapseCount = + typeof collapseMatches === "number" ? collapseMatches : 1; + + if (value.length > 1) { + const target = `search-more-${count}`; + const isExpanded = + state.context.expanded && + state.context.expanded.includes(target); + + const remainingCount = value.length - collapseCount; + + for (let i = 1; i < value.length; i++) { + if (collapseMatches && i === collapseCount) { + reshapedItems.push({ + target, + title: isExpanded + ? language["search-hide-matches-text"] + : remainingCount === 1 + ? `${remainingCount} ${language["search-more-match-text"]}` + : `${remainingCount} ${language["search-more-matches-text"]}`, + type: kItemTypeMore, + href: kItemTypeMoreHref, + }); + } + + if (isExpanded || !collapseMatches || i < collapseCount) { + reshapedItems.push({ + ...value[i], + type: kItemTypeItem, + target, + }); + } + } + } + count += 1; + } + + return { + ...source, + getItems() { + return reshapedItems; + }, + }; + } catch (error) { + // Some form of error occurred + return { + ...source, + getItems() { + return [ + { + title: error.name || "An Error Occurred While Searching", + text: + error.message || + "An unknown error occurred while attempting to perform the requested search.", + type: kItemTypeError, + }, + ]; + }, + }; + } + }); + }, + navigator: { + navigate({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + window.location.assign(itemUrl); + } + }, + navigateNewTab({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + const windowReference = window.open(itemUrl, "_blank", "noopener"); + if (windowReference) { + windowReference.focus(); + } + } + }, + navigateNewWindow({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + window.open(itemUrl, "_blank", "noopener"); + } + }, + }, + getSources({ state, setContext, setActiveItemId, refresh }) { + return [ + { + sourceId: "documents", + getItemUrl({ item }) { + if (item.href) { + return offsetURL(item.href); + } else { + return undefined; + } + }, + onSelect({ + item, + state, + setContext, + setIsOpen, + setActiveItemId, + refresh, + }) { + if (item.type === kItemTypeMore) { + toggleExpanded(item, state, setContext, setActiveItemId, refresh); + + // Toggle more + setIsOpen(true); + } + }, + getItems({ query }) { + if (query === null || query === "") { + return []; + } + + const limit = quartoSearchOptions.limit; + if (quartoSearchOptions.algolia) { + return algoliaSearch(query, limit, quartoSearchOptions.algolia); + } else { + // Fuse search options + const fuseSearchOptions = { + isCaseSensitive: false, + shouldSort: true, + minMatchCharLength: 2, + limit: limit, + }; + + return readSearchData().then(function (fuse) { + return fuseSearch(query, fuse, fuseSearchOptions); + }); + } + }, + templates: { + noResults({ createElement }) { + const hasQuery = lastState.query; + + return createElement( + "div", + { + class: `quarto-search-no-results${ + hasQuery ? "" : " no-query" + }`, + }, + language["search-no-results-text"] + ); + }, + header({ items, createElement }) { + // count the documents + const count = items.filter((item) => { + return item.type === kItemTypeDoc; + }).length; + + if (count > 0) { + return createElement( + "div", + { class: "search-result-header" }, + `${count} ${language["search-matching-documents-text"]}` + ); + } else { + return createElement( + "div", + { class: "search-result-header-no-results" }, + `` + ); + } + }, + footer({ _items, createElement }) { + if ( + quartoSearchOptions.algolia && + quartoSearchOptions.algolia["show-logo"] + ) { + const libDir = quartoSearchOptions.algolia["libDir"]; + const logo = createElement("img", { + src: offsetURL( + `${libDir}/quarto-search/search-by-algolia.svg` + ), + class: "algolia-search-logo", + }); + return createElement( + "a", + { href: "http://www.algolia.com/" }, + logo + ); + } + }, + + item({ item, createElement }) { + return renderItem( + item, + createElement, + state, + setActiveItemId, + setContext, + refresh + ); + }, + }, + }, + ]; + }, + }); + + window.quartoOpenSearch = () => { + setIsOpen(false); + setIsOpen(true); + focusSearchInput(); + }; + + // Remove the labeleledby attribute since it is pointing + // to a non-existent label + if (quartoSearchOptions.type === "overlay") { + const inputEl = window.document.querySelector( + "#quarto-search .aa-Autocomplete" + ); + if (inputEl) { + inputEl.removeAttribute("aria-labelledby"); + } + } + + // If the main document scrolls dismiss the search results + // (otherwise, since they're floating in the document they can scroll with the document) + window.document.body.onscroll = () => { + setIsOpen(false); + }; + + if (showSearchResults) { + setIsOpen(true); + focusSearchInput(); + } +}); + +function configurePlugins(quartoSearchOptions) { + const autocompletePlugins = []; + const algoliaOptions = quartoSearchOptions.algolia; + if ( + algoliaOptions && + algoliaOptions["analytics-events"] && + algoliaOptions["search-only-api-key"] && + algoliaOptions["application-id"] + ) { + const apiKey = algoliaOptions["search-only-api-key"]; + const appId = algoliaOptions["application-id"]; + + // Aloglia insights may not be loaded because they require cookie consent + // Use deferred loading so events will start being recorded when/if consent + // is granted. + const algoliaInsightsDeferredPlugin = deferredLoadPlugin(() => { + if ( + window.aa && + window["@algolia/autocomplete-plugin-algolia-insights"] + ) { + window.aa("init", { + appId, + apiKey, + useCookie: true, + }); + + const { createAlgoliaInsightsPlugin } = + window["@algolia/autocomplete-plugin-algolia-insights"]; + // Register the insights client + const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ + insightsClient: window.aa, + onItemsChange({ insights, insightsEvents }) { + const events = insightsEvents.map((event) => { + const maxEvents = event.objectIDs.slice(0, 20); + return { + ...event, + objectIDs: maxEvents, + }; + }); + + insights.viewedObjectIDs(...events); + }, + }); + return algoliaInsightsPlugin; + } + }); + + // Add the plugin + autocompletePlugins.push(algoliaInsightsDeferredPlugin); + return autocompletePlugins; + } +} + +// For plugins that may not load immediately, create a wrapper +// plugin and forward events and plugin data once the plugin +// is initialized. This is useful for cases like cookie consent +// which may prevent the analytics insights event plugin from initializing +// immediately. +function deferredLoadPlugin(createPlugin) { + let plugin = undefined; + let subscribeObj = undefined; + const wrappedPlugin = () => { + if (!plugin && subscribeObj) { + plugin = createPlugin(); + if (plugin && plugin.subscribe) { + plugin.subscribe(subscribeObj); + } + } + return plugin; + }; + + return { + subscribe: (obj) => { + subscribeObj = obj; + }, + onStateChange: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onStateChange) { + plugin.onStateChange(obj); + } + }, + onSubmit: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onSubmit) { + plugin.onSubmit(obj); + } + }, + onReset: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onReset) { + plugin.onReset(obj); + } + }, + getSources: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.getSources) { + return plugin.getSources(obj); + } else { + return Promise.resolve([]); + } + }, + data: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.data) { + plugin.data(obj); + } + }, + }; +} + +function validateItems(items) { + // Validate the first item + if (items.length > 0) { + const item = items[0]; + const missingFields = []; + if (item.href == undefined) { + missingFields.push("href"); + } + if (!item.title == undefined) { + missingFields.push("title"); + } + if (!item.text == undefined) { + missingFields.push("text"); + } + + if (missingFields.length === 1) { + throw { + name: `Error: Search index is missing the ${missingFields[0]} field.`, + message: `The items being returned for this search do not include all the required fields. Please ensure that your index items include the ${missingFields[0]} field or use index-fields in your _quarto.yml file to specify the field names.`, + }; + } else if (missingFields.length > 1) { + const missingFieldList = missingFields + .map((field) => { + return `${field}`; + }) + .join(", "); + + throw { + name: `Error: Search index is missing the following fields: ${missingFieldList}.`, + message: `The items being returned for this search do not include all the required fields. Please ensure that your index items includes the following fields: ${missingFieldList}, or use index-fields in your _quarto.yml file to specify the field names.`, + }; + } + } +} + +let lastQuery = null; +function showCopyLink(query, options) { + const language = options.language; + lastQuery = query; + // Insert share icon + const inputSuffixEl = window.document.body.querySelector( + ".aa-Form .aa-InputWrapperSuffix" + ); + + if (inputSuffixEl) { + let copyButtonEl = window.document.body.querySelector( + ".aa-Form .aa-InputWrapperSuffix .aa-CopyButton" + ); + + if (copyButtonEl === null) { + copyButtonEl = window.document.createElement("button"); + copyButtonEl.setAttribute("class", "aa-CopyButton"); + copyButtonEl.setAttribute("type", "button"); + copyButtonEl.setAttribute("title", language["search-copy-link-title"]); + copyButtonEl.onmousedown = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + const linkIcon = "bi-clipboard"; + const checkIcon = "bi-check2"; + + const shareIconEl = window.document.createElement("i"); + shareIconEl.setAttribute("class", `bi ${linkIcon}`); + copyButtonEl.appendChild(shareIconEl); + inputSuffixEl.prepend(copyButtonEl); + + const clipboard = new window.ClipboardJS(".aa-CopyButton", { + text: function (_trigger) { + const copyUrl = new URL(window.location); + copyUrl.searchParams.set(kQueryArg, lastQuery); + copyUrl.searchParams.set(kResultsArg, "1"); + return copyUrl.toString(); + }, + }); + clipboard.on("success", function (e) { + // Focus the input + + // button target + const button = e.trigger; + const icon = button.querySelector("i.bi"); + + // flash "checked" + icon.classList.add(checkIcon); + icon.classList.remove(linkIcon); + setTimeout(function () { + icon.classList.remove(checkIcon); + icon.classList.add(linkIcon); + }, 1000); + }); + } + + // If there is a query, show the link icon + if (copyButtonEl) { + if (lastQuery && options["copy-button"]) { + copyButtonEl.style.display = "flex"; + } else { + copyButtonEl.style.display = "none"; + } + } + } +} + +/* Search Index Handling */ +// create the index +var fuseIndex = undefined; +async function readSearchData() { + // Initialize the search index on demand + if (fuseIndex === undefined) { + // create fuse index + const options = { + keys: [ + { name: "title", weight: 20 }, + { name: "section", weight: 20 }, + { name: "text", weight: 10 }, + ], + ignoreLocation: true, + threshold: 0.1, + }; + const fuse = new window.Fuse([], options); + + // fetch the main search.json + const response = await fetch(offsetURL("search.json")); + if (response.status == 200) { + return response.json().then(function (searchDocs) { + searchDocs.forEach(function (searchDoc) { + fuse.add(searchDoc); + }); + fuseIndex = fuse; + return fuseIndex; + }); + } else { + return Promise.reject( + new Error( + "Unexpected status from search index request: " + response.status + ) + ); + } + } + return fuseIndex; +} + +function inputElement() { + return window.document.body.querySelector(".aa-Form .aa-Input"); +} + +function focusSearchInput() { + setTimeout(() => { + const inputEl = inputElement(); + if (inputEl) { + inputEl.focus(); + } + }, 50); +} + +/* Panels */ +const kItemTypeDoc = "document"; +const kItemTypeMore = "document-more"; +const kItemTypeItem = "document-item"; +const kItemTypeError = "error"; + +function renderItem( + item, + createElement, + state, + setActiveItemId, + setContext, + refresh +) { + switch (item.type) { + case kItemTypeDoc: + return createDocumentCard( + createElement, + "file-richtext", + item.title, + item.section, + item.text, + item.href + ); + case kItemTypeMore: + return createMoreCard( + createElement, + item, + state, + setActiveItemId, + setContext, + refresh + ); + case kItemTypeItem: + return createSectionCard( + createElement, + item.section, + item.text, + item.href + ); + case kItemTypeError: + return createErrorCard(createElement, item.title, item.text); + default: + return undefined; + } +} + +function createDocumentCard(createElement, icon, title, section, text, href) { + const iconEl = createElement("i", { + class: `bi bi-${icon} search-result-icon`, + }); + const titleEl = createElement("p", { class: "search-result-title" }, title); + const titleContainerEl = createElement( + "div", + { class: "search-result-title-container" }, + [iconEl, titleEl] + ); + + const textEls = []; + if (section) { + const sectionEl = createElement( + "p", + { class: "search-result-section" }, + section + ); + textEls.push(sectionEl); + } + const descEl = createElement("p", { + class: "search-result-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + textEls.push(descEl); + + const textContainerEl = createElement( + "div", + { class: "search-result-text-container" }, + textEls + ); + + const containerEl = createElement( + "div", + { + class: "search-result-container", + }, + [titleContainerEl, textContainerEl] + ); + + const linkEl = createElement( + "a", + { + href: offsetURL(href), + class: "search-result-link", + }, + containerEl + ); + + const classes = ["search-result-doc", "search-item"]; + if (!section) { + classes.push("document-selectable"); + } + + return createElement( + "div", + { + class: classes.join(" "), + }, + linkEl + ); +} + +function createMoreCard( + createElement, + item, + state, + setActiveItemId, + setContext, + refresh +) { + const moreCardEl = createElement( + "div", + { + class: "search-result-more search-item", + onClick: (e) => { + // Handle expanding the sections by adding the expanded + // section to the list of expanded sections + toggleExpanded(item, state, setContext, setActiveItemId, refresh); + e.stopPropagation(); + }, + }, + item.title + ); + + return moreCardEl; +} + +function toggleExpanded(item, state, setContext, setActiveItemId, refresh) { + const expanded = state.context.expanded || []; + if (expanded.includes(item.target)) { + setContext({ + expanded: expanded.filter((target) => target !== item.target), + }); + } else { + setContext({ expanded: [...expanded, item.target] }); + } + + refresh(); + setActiveItemId(item.__autocomplete_id); +} + +function createSectionCard(createElement, section, text, href) { + const sectionEl = createSection(createElement, section, text, href); + return createElement( + "div", + { + class: "search-result-doc-section search-item", + }, + sectionEl + ); +} + +function createSection(createElement, title, text, href) { + const descEl = createElement("p", { + class: "search-result-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + + const titleEl = createElement("p", { class: "search-result-section" }, title); + const linkEl = createElement( + "a", + { + href: offsetURL(href), + class: "search-result-link", + }, + [titleEl, descEl] + ); + return linkEl; +} + +function createErrorCard(createElement, title, text) { + const descEl = createElement("p", { + class: "search-error-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + + const titleEl = createElement("p", { + class: "search-error-title", + dangerouslySetInnerHTML: { + __html: ` ${title}`, + }, + }); + const errorEl = createElement("div", { class: "search-error" }, [ + titleEl, + descEl, + ]); + return errorEl; +} + +function positionPanel(pos) { + const panelEl = window.document.querySelector( + "#quarto-search-results .aa-Panel" + ); + const inputEl = window.document.querySelector( + "#quarto-search .aa-Autocomplete" + ); + + if (panelEl && inputEl) { + panelEl.style.top = `${Math.round(panelEl.offsetTop)}px`; + if (pos === "start") { + panelEl.style.left = `${Math.round(inputEl.left)}px`; + } else { + panelEl.style.right = `${Math.round(inputEl.offsetRight)}px`; + } + } +} + +/* Highlighting */ +// highlighting functions +function highlightMatch(query, text) { + if (text) { + const start = text.toLowerCase().indexOf(query.toLowerCase()); + if (start !== -1) { + const startMark = ""; + const endMark = ""; + + const end = start + query.length; + text = + text.slice(0, start) + + startMark + + text.slice(start, end) + + endMark + + text.slice(end); + const startInfo = clipStart(text, start); + const endInfo = clipEnd( + text, + startInfo.position + startMark.length + endMark.length + ); + text = + startInfo.prefix + + text.slice(startInfo.position, endInfo.position) + + endInfo.suffix; + + return text; + } else { + return text; + } + } else { + return text; + } +} + +function clipStart(text, pos) { + const clipStart = pos - 50; + if (clipStart < 0) { + // This will just return the start of the string + return { + position: 0, + prefix: "", + }; + } else { + // We're clipping before the start of the string, walk backwards to the first space. + const spacePos = findSpace(text, pos, -1); + return { + position: spacePos.position, + prefix: "", + }; + } +} + +function clipEnd(text, pos) { + const clipEnd = pos + 200; + if (clipEnd > text.length) { + return { + position: text.length, + suffix: "", + }; + } else { + const spacePos = findSpace(text, clipEnd, 1); + return { + position: spacePos.position, + suffix: spacePos.clipped ? "…" : "", + }; + } +} + +function findSpace(text, start, step) { + let stepPos = start; + while (stepPos > -1 && stepPos < text.length) { + const char = text[stepPos]; + if (char === " " || char === "," || char === ":") { + return { + position: step === 1 ? stepPos : stepPos - step, + clipped: stepPos > 1 && stepPos < text.length, + }; + } + stepPos = stepPos + step; + } + + return { + position: stepPos - step, + clipped: false, + }; +} + +// removes highlighting as implemented by the mark tag +function clearHighlight(searchterm, el) { + const childNodes = el.childNodes; + for (let i = childNodes.length - 1; i >= 0; i--) { + const node = childNodes[i]; + if (node.nodeType === Node.ELEMENT_NODE) { + if ( + node.tagName === "MARK" && + node.innerText.toLowerCase() === searchterm.toLowerCase() + ) { + el.replaceChild(document.createTextNode(node.innerText), node); + } else { + clearHighlight(searchterm, node); + } + } + } +} + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string +} + +// highlight matches +function highlight(term, el) { + const termRegex = new RegExp(term, "ig"); + const childNodes = el.childNodes; + + // walk back to front avoid mutating elements in front of us + for (let i = childNodes.length - 1; i >= 0; i--) { + const node = childNodes[i]; + + if (node.nodeType === Node.TEXT_NODE) { + // Search text nodes for text to highlight + const text = node.nodeValue; + + let startIndex = 0; + let matchIndex = text.search(termRegex); + if (matchIndex > -1) { + const markFragment = document.createDocumentFragment(); + while (matchIndex > -1) { + const prefix = text.slice(startIndex, matchIndex); + markFragment.appendChild(document.createTextNode(prefix)); + + const mark = document.createElement("mark"); + mark.appendChild( + document.createTextNode( + text.slice(matchIndex, matchIndex + term.length) + ) + ); + markFragment.appendChild(mark); + + startIndex = matchIndex + term.length; + matchIndex = text.slice(startIndex).search(new RegExp(term, "ig")); + if (matchIndex > -1) { + matchIndex = startIndex + matchIndex; + } + } + if (startIndex < text.length) { + markFragment.appendChild( + document.createTextNode(text.slice(startIndex, text.length)) + ); + } + + el.replaceChild(markFragment, node); + } + } else if (node.nodeType === Node.ELEMENT_NODE) { + // recurse through elements + highlight(term, node); + } + } +} + +/* Link Handling */ +// get the offset from this page for a given site root relative url +function offsetURL(url) { + var offset = getMeta("quarto:offset"); + return offset ? offset + url : url; +} + +// read a meta tag value +function getMeta(metaName) { + var 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 ""; +} + +function algoliaSearch(query, limit, algoliaOptions) { + const { getAlgoliaResults } = window["@algolia/autocomplete-preset-algolia"]; + + const applicationId = algoliaOptions["application-id"]; + const searchOnlyApiKey = algoliaOptions["search-only-api-key"]; + const indexName = algoliaOptions["index-name"]; + const indexFields = algoliaOptions["index-fields"]; + const searchClient = window.algoliasearch(applicationId, searchOnlyApiKey); + const searchParams = algoliaOptions["params"]; + const searchAnalytics = !!algoliaOptions["analytics-events"]; + + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: indexName, + query, + params: { + hitsPerPage: limit, + clickAnalytics: searchAnalytics, + ...searchParams, + }, + }, + ], + transformResponse: (response) => { + if (!indexFields) { + return response.hits.map((hit) => { + return hit.map((item) => { + return { + ...item, + text: highlightMatch(query, item.text), + }; + }); + }); + } else { + const remappedHits = response.hits.map((hit) => { + return hit.map((item) => { + const newItem = { ...item }; + ["href", "section", "title", "text"].forEach((keyName) => { + const mappedName = indexFields[keyName]; + if ( + mappedName && + item[mappedName] !== undefined && + mappedName !== keyName + ) { + newItem[keyName] = item[mappedName]; + delete newItem[mappedName]; + } + }); + newItem.text = highlightMatch(query, newItem.text); + return newItem; + }); + }); + return remappedHits; + } + }, + }); +} + +function fuseSearch(query, fuse, fuseOptions) { + return fuse.search(query, fuseOptions).map((result) => { + const addParam = (url, name, value) => { + const anchorParts = url.split("#"); + const baseUrl = anchorParts[0]; + const sep = baseUrl.search("\\?") > 0 ? "&" : "?"; + anchorParts[0] = baseUrl + sep + name + "=" + value; + return anchorParts.join("#"); + }; + + return { + title: result.item.title, + section: result.item.section, + href: addParam(result.item.href, kQueryArg, query), + text: highlightMatch(query, result.item.text), + }; + }); +}