diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 49ae2b2d..364be913 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,52 +1,55 @@ name: CI / Build and test on: - pull_request: - workflow_dispatch: - push: - branches: - - main + pull_request: + workflow_dispatch: + push: + branches: + - main jobs: - cypress-run: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install - - - name: Set up config files - run: cp config/config.example.toml config/config.toml - - - name: Build - run: pnpm build - - - name: Start MongoDB - uses: supercharge/mongodb-github-action@1.10.0 - - - name: Cypress run - uses: cypress-io/github-action@v6 - with: - start: pnpm start - browser: chrome - env: - CYPRESS: true - - - name: Upload screenshots - uses: actions/upload-artifact@v4 - if: failure() - with: - name: cypress-screenshots - path: cypress/screenshots + cypress-run: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Set up config files + run: cp config/config.example.toml config/config.toml + + - name: Lint + run: pnpm lint + + - name: Build + run: pnpm build + + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.10.0 + + - name: Cypress run + uses: cypress-io/github-action@v6 + with: + start: pnpm start + browser: chrome + env: + CYPRESS: true + + - name: Upload screenshots + uses: actions/upload-artifact@v4 + if: failure() + with: + name: cypress-screenshots + path: cypress/screenshots diff --git a/.prettierignore b/.prettierignore index f02c8361..686f1916 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,21 @@ dist views -pnpm-lock.yaml \ No newline at end of file +pnpm-lock.yaml +public/js/autosize.min.js +public/js/bootstrap.bundle.js +public/js/bootstrap.bundle.js.map +public/js/bootstrap.bundle.min.js +public/js/bootstrap.bundle.min.js.map +public/js/bootstrap.js +public/js/bootstrap.js.map +public/js/bootstrap.min.js +public/js/bootstrap.min.js.map +public/js/clipboard.min.js +public/js/generate-timezones.js +public/js/jquery-3.4.1.min.js +public/js/jquery.uploadPreview.min.js +public/js/moment-timezone.js +public/js/moment.js +public/js/password-score.js +public/js/popper.min.js +public/js/util.js diff --git a/.prettierrc.json b/.prettierrc.json index 0a02bcef..75fa1341 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,3 +1,3 @@ { - "tabWidth": 4 + "tabWidth": 2 } diff --git a/FEDERATION.md b/FEDERATION.md index c604246e..4c3a10bf 100644 --- a/FEDERATION.md +++ b/FEDERATION.md @@ -6,18 +6,18 @@ This document is meant to be a reference for all the ActivityPub federation-rela To keep things simple, sometimes you will see things formatted like `Create/Note` or `Delete/Event` or `Undo/Follow`. The thing before the slash is the Activity, and the thing after the slash is the Object inside the Activity, in an `object` property. So these are to be read as follows: -- `Create/Note`: a `Create` activity containing a `Note` in the `object` field -- `Delete/Event`: a `Delete` activity containing an `Event` in the `object` field -- `Undo/Follow`: an `Undo` activity containing a `Follow` in the `object` field +- `Create/Note`: a `Create` activity containing a `Note` in the `object` field +- `Delete/Event`: a `Delete` activity containing an `Event` in the `object` field +- `Undo/Follow`: an `Undo` activity containing a `Follow` in the `object` field When the word "broadcast" is used in this document, it means to send an Activity to individual inbox of each of the followers of a given Actor. This document has four main sections: -- **Federation philosophy** lays out the general model of how this is intended to federate -- **General Actor information** contains the basics of what to expect from our `Actor` objects -- **Inbox behavior** lists every incoming ActivityPub activity that the server recognizes, and tells you what it does in response to that activity, including any other ActivityPub activities it sends back out. -- **Activities triggered from the web app** tells you what circumstances on the web application cause the server to emit ActivityPub activities. (For example, when an event is updated via the web application, it lets all the ActivityPub followers know that the event has been updated.) +- **Federation philosophy** lays out the general model of how this is intended to federate +- **General Actor information** contains the basics of what to expect from our `Actor` objects +- **Inbox behavior** lists every incoming ActivityPub activity that the server recognizes, and tells you what it does in response to that activity, including any other ActivityPub activities it sends back out. +- **Activities triggered from the web app** tells you what circumstances on the web application cause the server to emit ActivityPub activities. (For example, when an event is updated via the web application, it lets all the ActivityPub followers know that the event has been updated.) Please note: there is an unfortunate collision between the English language and the ActivityPub spec that can make this document confusing. When this document uses the word 'event' with a lowercase-e and not in monospace, it refers to the thing that is being tracked in gathio: events that are being organized. When this document uses the word `Event` with a capital E and in monospace, it refers to the [`Event` object defined in the ActivityStreams Vocabulary spec](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event). @@ -37,29 +37,29 @@ Every event has an Actor. The Actor looks like this: ```json { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1" - ], - "id": "https://DOMAIN/EVENTID", - "type": "Person", - "preferredUsername": "EVENTID", - "inbox": "https://DOMAIN/activitypub/inbox", - "outbox": "https://DOMAIN/EVENTID/outbox", - "followers": "https://DOMAIN/EVENTID/followers", - "summary": "

DESCRIPTION

\n

Location: LOCATION.

Starting DATETIME (human readable).

", - "name": "EVENTNAME", - "featured": "https://DOMAIN/EVENTID/featured", - "publicKey": { - "id": "https://DOMAIN/EVENTID#main-key", - "owner": "https://DOMAIN/EVENTID", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nOURPUBLICKEY\n-----END PUBLIC KEY-----\n" - }, - "icon": { - "type": "Image", - "mediaType": "image/jpg", - "url": "https://DOMAIN/events/EVENTID.jpg" - } + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1" + ], + "id": "https://DOMAIN/EVENTID", + "type": "Person", + "preferredUsername": "EVENTID", + "inbox": "https://DOMAIN/activitypub/inbox", + "outbox": "https://DOMAIN/EVENTID/outbox", + "followers": "https://DOMAIN/EVENTID/followers", + "summary": "

DESCRIPTION

\n

Location: LOCATION.

Starting DATETIME (human readable).

", + "name": "EVENTNAME", + "featured": "https://DOMAIN/EVENTID/featured", + "publicKey": { + "id": "https://DOMAIN/EVENTID#main-key", + "owner": "https://DOMAIN/EVENTID", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nOURPUBLICKEY\n-----END PUBLIC KEY-----\n" + }, + "icon": { + "type": "Image", + "mediaType": "image/jpg", + "url": "https://DOMAIN/events/EVENTID.jpg" + } } ``` diff --git a/cypress.config.ts b/cypress.config.ts index 1f6f74a3..57fdc415 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,10 +1,10 @@ import { defineConfig } from "cypress"; export default defineConfig({ - e2e: { - baseUrl: "http://localhost:3000", - setupNodeEvents(/*on, config*/) { - // implement node event listeners here - }, + e2e: { + baseUrl: "http://localhost:3000", + setupNodeEvents(/*on, config*/) { + // implement node event listeners here }, + }, }); diff --git a/cypress/e2e/event.cy.ts b/cypress/e2e/event.cy.ts index f49342e1..5e4c67cd 100644 --- a/cypress/e2e/event.cy.ts +++ b/cypress/e2e/event.cy.ts @@ -2,328 +2,312 @@ import eventData from "../fixtures/eventData.json"; import crypto from "crypto"; describe("Events", () => { - beforeEach(() => { - cy.visit("/new"); - cy.get("#showNewEventFormButton").click(); - - cy.get("#eventName").type(eventData.eventName); - cy.get("#eventLocation").type(eventData.eventLocation); - // These are datetime-local inputs - cy.get("#eventStart").type(eventData.eventStart); - cy.get("#eventEnd").type(eventData.eventEnd); - - cy.get("select#timezone + span.select2").click(); - cy.get(".select2-results__option") - .contains(eventData.timezone) - .click({ force: true }); - - cy.get("#eventDescription").type(eventData.eventDescription); - cy.get("#eventURL").type(eventData.eventURL); - - cy.get("#hostName").type(eventData.hostName); - cy.get("#creatorEmail").type(eventData.creatorEmail); - - if (eventData.interactionCheckbox) { - cy.get("#interactionCheckbox").check(); - } - - if (eventData.joinCheckbox) { - cy.get("#joinCheckbox").check(); - } - - if (eventData.maxAttendeesCheckbox) { - cy.get("#maxAttendeesCheckbox").check(); - cy.get("#maxAttendees").type(eventData.maxAttendees.toString()); - } - - // Submit the form - cy.get("#newEventFormSubmit").click(); - - // Wait for the new page to load - cy.url({ timeout: 10000 }).should("not.include", "/new"); - - // Get the new event ID from the URL - cy.url().then((url) => { - const [eventID, editToken] = url.split("/").pop().split("?"); - cy.wrap(eventID).as("eventID"); - cy.wrap(editToken).as("editToken"); - }); + beforeEach(() => { + cy.visit("/new"); + cy.get("#showNewEventFormButton").click(); + + cy.get("#eventName").type(eventData.eventName); + cy.get("#eventLocation").type(eventData.eventLocation); + // These are datetime-local inputs + cy.get("#eventStart").type(eventData.eventStart); + cy.get("#eventEnd").type(eventData.eventEnd); + + cy.get("select#timezone + span.select2").click(); + cy.get(".select2-results__option") + .contains(eventData.timezone) + .click({ force: true }); + + cy.get("#eventDescription").type(eventData.eventDescription); + cy.get("#eventURL").type(eventData.eventURL); + + cy.get("#hostName").type(eventData.hostName); + cy.get("#creatorEmail").type(eventData.creatorEmail); + + if (eventData.interactionCheckbox) { + cy.get("#interactionCheckbox").check(); + } + + if (eventData.joinCheckbox) { + cy.get("#joinCheckbox").check(); + } + + if (eventData.maxAttendeesCheckbox) { + cy.get("#maxAttendeesCheckbox").check(); + cy.get("#maxAttendees").type(eventData.maxAttendees.toString()); + } + + // Submit the form + cy.get("#newEventFormSubmit").click(); + + // Wait for the new page to load + cy.url({ timeout: 10000 }).should("not.include", "/new"); + + // Get the new event ID from the URL + cy.url().then((url) => { + const [eventID, editToken] = url.split("/").pop().split("?"); + cy.wrap(eventID).as("eventID"); + cy.wrap(editToken).as("editToken"); }); - it("creates a new event", function () { - // Check that all the data is correct - cy.get(".p-name").should("have.text", eventData.eventName); - cy.get(".p-location").should("have.text", eventData.eventLocation); - cy.get(".p-summary").should("contain.text", eventData.eventDescription); - cy.get("#hosted-by").should( - "contain.text", - `Hosted by ${eventData.hostName}`, - ); - cy.get("#attendees-alert").should("contain.text", "10 spots remaining"); - cy.get(".dt-duration").should( - "contain.text", - "Tuesday 1 January 2030 from 12:00 am to 1:00 am (EST)", - ); + }); + it("creates a new event", function () { + // Check that all the data is correct + cy.get(".p-name").should("have.text", eventData.eventName); + cy.get(".p-location").should("have.text", eventData.eventLocation); + cy.get(".p-summary").should("contain.text", eventData.eventDescription); + cy.get("#hosted-by").should( + "contain.text", + `Hosted by ${eventData.hostName}`, + ); + cy.get("#attendees-alert").should("contain.text", "10 spots remaining"); + cy.get(".dt-duration").should( + "contain.text", + "Tuesday 1 January 2030 from 12:00 am to 1:00 am (EST)", + ); + }); + + it("allows you to attend an event - visible in public list", function () { + cy.get("button#attendEvent").click(); + cy.get("#attendeeName").type("Test Attendee"); + cy.get("#attendeeNumber").focus(); + cy.get("#attendeeNumber").clear(); + cy.get("#attendeeNumber").type("2"); + cy.get("form#attendEventForm").submit(); + cy.get("#attendees-alert").should("contain.text", "8 spots remaining"); + cy.get(".attendeesList").should("contain.text", "Test Attendee (2 people)"); + }); + + it("allows you to attend an event - hidden from public list", function () { + cy.get("button#attendEvent").click(); + cy.get("#attendeeName").type("Test Attendee"); + cy.get("#attendeeNumber").focus(); + cy.get("#attendeeNumber").clear(); + cy.get("#attendeeNumber").type("2"); + cy.get("#attendeeVisible").uncheck(); + cy.get("form#attendEventForm").submit(); + cy.get("#attendees-alert").should("contain.text", "8 spots remaining"); + cy.get(".attendeesList").should( + "contain.text", + "Test Attendee (2 people) (hidden from public list)", + ); + }); + + it("allows you to comment on an event", function () { + cy.get("#commentAuthor").type("Test Author"); + cy.get("#commentContent").type("Test Comment"); + cy.get("#postComment").click(); + cy.get(".comment").should("contain.text", "Test Author"); + cy.get(".comment").should("contain.text", "Test Comment"); + }); + + it("displays the ActivityPub featured post", function () { + cy.log(this.eventID); + + cy.request({ + url: `/${this.eventID}/featured`, + headers: { + Accept: + 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + }, + }).then((response) => { + expect(response.body).to.have.property("@context"); + expect(response.body).to.have.property("id"); + expect(response.body).to.have.property("type"); + expect(response.body).to.have.property("orderedItems"); + expect(response.body.orderedItems) + .to.be.an("array") + .and.to.have.lengthOf(1); + const featuredPost = response.body.orderedItems[0]; + expect(featuredPost).to.have.property("@context"); + expect(featuredPost).to.have.property("id"); + expect(featuredPost).to.have.property("type"); + expect(featuredPost).to.have.property("name"); + expect(featuredPost).to.have.property("content"); + expect(featuredPost).to.have.property("attributedTo"); }); - - it("allows you to attend an event - visible in public list", function () { - cy.get("button#attendEvent").click(); - cy.get("#attendeeName").type("Test Attendee"); - cy.get("#attendeeNumber").focus(); - cy.get("#attendeeNumber").clear(); - cy.get("#attendeeNumber").type("2"); - cy.get("form#attendEventForm").submit(); - cy.get("#attendees-alert").should("contain.text", "8 spots remaining"); - cy.get(".attendeesList").should( - "contain.text", - "Test Attendee (2 people)", - ); + }); + + it("responds correctly to ActivityPub webfinger requests", function () { + cy.request({ + url: `/.well-known/webfinger?resource=acct:${ + this.eventID + }@${Cypress.env("CYPRESS_DOMAIN")}`, + headers: { + Accept: + 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + }, + }).then((response) => { + expect(response.body).to.have.property("subject"); + expect(response.body).to.have.property("links"); + expect(response.body.links).to.be.an("array").and.to.have.lengthOf(1); + const link = response.body.links[0]; + expect(link).to.have.property("rel"); + expect(link).to.have.property("type"); + expect(link).to.have.property("href"); + }); + }); + + it("edits an event", function () { + cy.get("#editEvent").click(); + + // The edit form is the same as the new form, so we can just re-use the same selectors + // but we need to clear the fields first + cy.get("#editEventForm #eventName").focus(); + cy.get("#editEventForm #eventName").clear(); + cy.get("#editEventForm #eventLocation").focus(); + cy.get("#editEventForm #eventLocation").clear(); + cy.get("#editEventForm #eventStart").focus(); + cy.get("#editEventForm #eventStart").clear(); + cy.get("#editEventForm #eventEnd").focus(); + cy.get("#editEventForm #eventEnd").clear(); + cy.get("#editEventForm #eventDescription").focus(); + cy.get("#editEventForm #eventDescription").clear(); + cy.get("#editEventForm #eventURL").focus(); + cy.get("#editEventForm #eventURL").clear(); + cy.get("#editEventForm #hostName").focus(); + cy.get("#editEventForm #hostName").clear(); + cy.get("#editEventForm #creatorEmail").focus(); + cy.get("#editEventForm #creatorEmail").clear(); + cy.get("#editEventForm #maxAttendees").focus(); + cy.get("#editEventForm #maxAttendees").clear(); + + cy.get("#editEventForm #eventName").type("Edited Event Name"); + cy.get("#editEventForm #eventLocation").type("Edited Event Location"); + // These are datetime-local inputs + cy.get("#editEventForm #eventStart").type("2030-12-01T00:00"); + cy.get("#editEventForm #eventEnd").type("2030-12-01T01:00"); + + cy.get("#editEventForm select#timezone + span.select2").click(); + cy.get(".select2-results__option") + .contains("Australia/Sydney") + .click({ force: true }); + + cy.get("#editEventForm #eventDescription").type("Edited Event Description"); + cy.get("#editEventForm #eventURL").type("https://edited.example.com"); + cy.get("#editEventForm #hostName").type("Edited Name"); + cy.get("#editEventForm #creatorEmail").type("edited@example.com"); + + cy.get("#editEventForm #maxAttendeesCheckbox").uncheck(); + + cy.get("#editEventForm #interactionCheckbox").uncheck(); + + cy.get("#editEventForm #joinCheckbox").uncheck(); + + // Submit the form + cy.get("#editEventForm").submit(); + + // Wait for the modal to not be visible + cy.get("#editModal").should("not.be.visible"); + + // Check that all the data is correct + cy.get(".p-name").should("have.text", "Edited Event Name"); + cy.get(".p-location").should("have.text", "Edited Event Location"); + cy.get(".p-summary").should("contain.text", "Edited Event Description"); + cy.get("#hosted-by").should("contain.text", "Hosted by Edited Name"); + cy.get(".dt-duration").should( + "contain.text", + "Sunday 1 December 2030 from 12:00 am to 1:00 am", + ); + cy.get(".dt-duration") + .invoke("text") + .should("match", /AE(D|S)T/); + // Check that the comment form is not visible + cy.get("#postComment").should("not.exist"); + // Check that the attendee form is not visible + cy.get("#attendEvent").should("not.exist"); + }); + + it("sets a group for an event", function () { + // For this we need to create a group first. This will load the group edit token + // into our localStorage, and will then appear in the group select dropdown. + // We then go back to the event page, edit the event, and set the group. + cy.createGroup({ + eventGroupName: "Test Group", + eventGroupDescription: "Test Group Description", + eventGroupURL: "https://example.com", + hostName: "Test Host", + creatorEmail: "test@example.com", }); - it("allows you to attend an event - hidden from public list", function () { - cy.get("button#attendEvent").click(); - cy.get("#attendeeName").type("Test Attendee"); - cy.get("#attendeeNumber").focus(); - cy.get("#attendeeNumber").clear(); - cy.get("#attendeeNumber").type("2"); - cy.get("#attendeeVisible").uncheck(); + cy.visit(`/${this.eventID}`); + cy.url().should("include", this.editToken); + + cy.get("#editEvent").click(); + cy.get("#editEventForm #eventGroupCheckbox").check(); + cy.get("select#eventGroupSelect + span.select2").click(); + cy.get(".select2-results__option") + .contains("Test Group") + .click({ force: true }); + cy.get("#editEventForm").submit(); + + cy.get("#editModal").should("not.be.visible"); + + cy.get("#event-group").should("contain.text", "Test Group"); + }); + + it("removes you from the event with a one-click unattend link", function () { + cy.get("button#attendEvent").click(); + cy.get("#attendeeName").type("Test Attendee"); + cy.get("#attendeeNumber").focus(); + cy.get("#attendeeNumber").clear(); + cy.get("#attendeeNumber").type("2"); + cy.get("#removalPassword") + .invoke("val") + .then((removalPassword) => { + cy.wrap(removalPassword).as("removalPassword"); + cy.log(this.removalPassword); cy.get("form#attendEventForm").submit(); cy.get("#attendees-alert").should("contain.text", "8 spots remaining"); cy.get(".attendeesList").should( - "contain.text", - "Test Attendee (2 people) (hidden from public list)", + "contain.text", + "Test Attendee (2 people)", ); - }); - - it("allows you to comment on an event", function () { - cy.get("#commentAuthor").type("Test Author"); - cy.get("#commentContent").type("Test Comment"); - cy.get("#postComment").click(); - cy.get(".comment").should("contain.text", "Test Author"); - cy.get(".comment").should("contain.text", "Test Comment"); - }); - - it("displays the ActivityPub featured post", function () { - cy.log(this.eventID); - - cy.request({ - url: `/${this.eventID}/featured`, - headers: { - Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', - }, - }).then((response) => { - expect(response.body).to.have.property("@context"); - expect(response.body).to.have.property("id"); - expect(response.body).to.have.property("type"); - expect(response.body).to.have.property("orderedItems"); - expect(response.body.orderedItems) - .to.be.an("array") - .and.to.have.lengthOf(1); - const featuredPost = response.body.orderedItems[0]; - expect(featuredPost).to.have.property("@context"); - expect(featuredPost).to.have.property("id"); - expect(featuredPost).to.have.property("type"); - expect(featuredPost).to.have.property("name"); - expect(featuredPost).to.have.property("content"); - expect(featuredPost).to.have.property("attributedTo"); - }); - }); - - it("responds correctly to ActivityPub webfinger requests", function () { - cy.request({ - url: `/.well-known/webfinger?resource=acct:${ - this.eventID - }@${Cypress.env("CYPRESS_DOMAIN")}`, - headers: { - Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', - }, - }).then((response) => { - expect(response.body).to.have.property("subject"); - expect(response.body).to.have.property("links"); - expect(response.body.links) - .to.be.an("array") - .and.to.have.lengthOf(1); - const link = response.body.links[0]; - expect(link).to.have.property("rel"); - expect(link).to.have.property("type"); - expect(link).to.have.property("href"); - }); - }); - - it("edits an event", function () { - cy.get("#editEvent").click(); - - // The edit form is the same as the new form, so we can just re-use the same selectors - // but we need to clear the fields first - cy.get("#editEventForm #eventName").focus(); - cy.get("#editEventForm #eventName").clear(); - cy.get("#editEventForm #eventLocation").focus(); - cy.get("#editEventForm #eventLocation").clear(); - cy.get("#editEventForm #eventStart").focus(); - cy.get("#editEventForm #eventStart").clear(); - cy.get("#editEventForm #eventEnd").focus(); - cy.get("#editEventForm #eventEnd").clear(); - cy.get("#editEventForm #eventDescription").focus(); - cy.get("#editEventForm #eventDescription").clear(); - cy.get("#editEventForm #eventURL").focus(); - cy.get("#editEventForm #eventURL").clear(); - cy.get("#editEventForm #hostName").focus(); - cy.get("#editEventForm #hostName").clear(); - cy.get("#editEventForm #creatorEmail").focus(); - cy.get("#editEventForm #creatorEmail").clear(); - cy.get("#editEventForm #maxAttendees").focus(); - cy.get("#editEventForm #maxAttendees").clear(); - - cy.get("#editEventForm #eventName").type("Edited Event Name"); - cy.get("#editEventForm #eventLocation").type("Edited Event Location"); - // These are datetime-local inputs - cy.get("#editEventForm #eventStart").type("2030-12-01T00:00"); - cy.get("#editEventForm #eventEnd").type("2030-12-01T01:00"); - - cy.get("#editEventForm select#timezone + span.select2").click(); - cy.get(".select2-results__option") - .contains("Australia/Sydney") - .click({ force: true }); - - cy.get("#editEventForm #eventDescription").type( - "Edited Event Description", + const removalPasswordHash = crypto + .createHash("sha256") + .update(removalPassword) + .digest("hex"); + const unattendLink = `http://localhost:3000/event/${this.eventID}/unattend/${removalPasswordHash}`; + cy.visit(unattendLink); + cy.get("#event__message").should( + "contain.text", + "You have been removed from this event.", ); - cy.get("#editEventForm #eventURL").type("https://edited.example.com"); - cy.get("#editEventForm #hostName").type("Edited Name"); - cy.get("#editEventForm #creatorEmail").type("edited@example.com"); - - cy.get("#editEventForm #maxAttendeesCheckbox").uncheck(); - - cy.get("#editEventForm #interactionCheckbox").uncheck(); - - cy.get("#editEventForm #joinCheckbox").uncheck(); - - // Submit the form - cy.get("#editEventForm").submit(); - - // Wait for the modal to not be visible - cy.get("#editModal").should("not.be.visible"); - - // Check that all the data is correct - cy.get(".p-name").should("have.text", "Edited Event Name"); - cy.get(".p-location").should("have.text", "Edited Event Location"); - cy.get(".p-summary").should("contain.text", "Edited Event Description"); - cy.get("#hosted-by").should("contain.text", "Hosted by Edited Name"); - cy.get(".dt-duration").should( - "contain.text", - "Sunday 1 December 2030 from 12:00 am to 1:00 am", + cy.get("#attendees-alert").should("contain.text", "10 spots remaining"); + cy.get("#eventAttendees").should("contain.text", "No attendees yet!"); + }); + }); + describe("Query string editing tokens", function () { + it("given a valid editing token is in the URL, should add it to localStorage", function () { + cy.visit(`/${this.eventID}?${this.editToken}`).then(() => { + expect(localStorage.getItem("editTokens")).to.include( + this.editToken.split("=")[1], ); - cy.get(".dt-duration") - .invoke("text") - .should("match", /AE(D|S)T/); - // Check that the comment form is not visible - cy.get("#postComment").should("not.exist"); - // Check that the attendee form is not visible - cy.get("#attendEvent").should("not.exist"); + }); }); - it("sets a group for an event", function () { - // For this we need to create a group first. This will load the group edit token - // into our localStorage, and will then appear in the group select dropdown. - // We then go back to the event page, edit the event, and set the group. - cy.createGroup({ - eventGroupName: "Test Group", - eventGroupDescription: "Test Group Description", - eventGroupURL: "https://example.com", - hostName: "Test Host", - creatorEmail: "test@example.com", - }); - - cy.visit(`/${this.eventID}`); - cy.url().should("include", this.editToken); - - cy.get("#editEvent").click(); - cy.get("#editEventForm #eventGroupCheckbox").check(); - cy.get("select#eventGroupSelect + span.select2").click(); - cy.get(".select2-results__option") - .contains("Test Group") - .click({ force: true }); - cy.get("#editEventForm").submit(); - - cy.get("#editModal").should("not.be.visible"); - - cy.get("#event-group").should("contain.text", "Test Group"); + it("given an invalid editing token is in the URL, should delete it from the URL", function () { + cy.visit(`/${this.eventID}?e=invalid`).then(() => { + expect(localStorage.getItem("editTokens")).to.not.include("invalid"); + }); }); - it("removes you from the event with a one-click unattend link", function () { - cy.get("button#attendEvent").click(); - cy.get("#attendeeName").type("Test Attendee"); - cy.get("#attendeeNumber").focus(); - cy.get("#attendeeNumber").clear(); - cy.get("#attendeeNumber").type("2"); - cy.get("#removalPassword") - .invoke("val") - .then((removalPassword) => { - cy.wrap(removalPassword).as("removalPassword"); - cy.log(this.removalPassword); - cy.get("form#attendEventForm").submit(); - cy.get("#attendees-alert").should( - "contain.text", - "8 spots remaining", - ); - cy.get(".attendeesList").should( - "contain.text", - "Test Attendee (2 people)", - ); - const removalPasswordHash = crypto - .createHash("sha256") - .update(removalPassword) - .digest("hex"); - const unattendLink = `http://localhost:3000/event/${this.eventID}/unattend/${removalPasswordHash}`; - cy.visit(unattendLink); - cy.get("#event__message").should( - "contain.text", - "You have been removed from this event.", - ); - cy.get("#attendees-alert").should( - "contain.text", - "10 spots remaining", - ); - cy.get("#eventAttendees").should( - "contain.text", - "No attendees yet!", - ); - }); + it("given a valid editing token in localStorage, should add it to the URL", function () { + cy.visit(`/${this.eventID}`).then(() => { + cy.url().should("include", this.editToken); + }); }); - describe("Query string editing tokens", function () { - it("given a valid editing token is in the URL, should add it to localStorage", function () { - cy.visit(`/${this.eventID}?${this.editToken}`).then(() => { - expect(localStorage.getItem("editTokens")).to.include( - this.editToken.split("=")[1], - ); - }); - }); - - it("given an invalid editing token is in the URL, should delete it from the URL", function () { - cy.visit(`/${this.eventID}?e=invalid`).then(() => { - expect(localStorage.getItem("editTokens")).to.not.include( - "invalid", - ); - }); - }); - - it("given a valid editing token in localStorage, should add it to the URL", function () { - cy.visit(`/${this.eventID}`).then(() => { - cy.url().should("include", this.editToken); - }); - }); - - it("given an invalid editing token in localStorage, should remove it from localStorage", function () { - cy.clearAllLocalStorage(); - localStorage.setItem("editTokens", "invalid"); - cy.visit(`/${this.eventID}`).then(() => { - const editTokens = localStorage.getItem("editTokens"); - if (editTokens !== null) { - expect(editTokens).to.not.include("invalid"); - } else { - // If it's null, the invalid token was successfully removed - expect(editTokens).to.be.null; - } - }); - }); + + it("given an invalid editing token in localStorage, should remove it from localStorage", function () { + cy.clearAllLocalStorage(); + localStorage.setItem("editTokens", "invalid"); + cy.visit(`/${this.eventID}`).then(() => { + const editTokens = localStorage.getItem("editTokens"); + if (editTokens !== null) { + expect(editTokens).to.not.include("invalid"); + } else { + // If it's null, the invalid token was successfully removed + expect(editTokens).to.be.null; + } + }); }); + }); }); diff --git a/cypress/e2e/group.cy.ts b/cypress/e2e/group.cy.ts index f80fc7f8..d4b73de1 100644 --- a/cypress/e2e/group.cy.ts +++ b/cypress/e2e/group.cy.ts @@ -1,66 +1,63 @@ import groupData from "../fixtures/groupData.json"; describe("Groups", () => { - beforeEach(() => { - cy.createGroup(groupData, false); - }); - it("creates a new group", function () { - cy.get("#eventGroupName").should("have.text", groupData.eventGroupName); - cy.get("#eventDescription").should( - "contain.text", - groupData.eventGroupDescription, - ); - cy.get("#eventGroupURL").should( - "contain.text", - groupData.eventGroupURL, - ); - cy.get("#hostName").should("contain.text", groupData.hostName); - cy.get("#eventGroupID").should("contain.text", this.groupID); - cy.get("#eventGroupEditToken").should("contain.text", this.editToken); - }); + beforeEach(() => { + cy.createGroup(groupData, false); + }); + it("creates a new group", function () { + cy.get("#eventGroupName").should("have.text", groupData.eventGroupName); + cy.get("#eventDescription").should( + "contain.text", + groupData.eventGroupDescription, + ); + cy.get("#eventGroupURL").should("contain.text", groupData.eventGroupURL); + cy.get("#hostName").should("contain.text", groupData.hostName); + cy.get("#eventGroupID").should("contain.text", this.groupID); + cy.get("#eventGroupEditToken").should("contain.text", this.editToken); + }); - it("edits a group", function () { - cy.get("#editGroup").click(); + it("edits a group", function () { + cy.get("#editGroup").click(); - cy.get("#editEventGroupForm #eventGroupName").focus(); - cy.get("#editEventGroupForm #eventGroupName").clear(); - cy.get("#editEventGroupForm #eventGroupDescription").focus(); - cy.get("#editEventGroupForm #eventGroupDescription").clear(); - cy.get("#editEventGroupForm #eventGroupURL").focus(); - cy.get("#editEventGroupForm #eventGroupURL").clear(); - cy.get("#editEventGroupForm #eventGroupHostName").focus(); - cy.get("#editEventGroupForm #eventGroupHostName").clear(); - cy.get("#editEventGroupForm #eventGroupCreatorEmail").focus(); - cy.get("#editEventGroupForm #eventGroupCreatorEmail").clear(); + cy.get("#editEventGroupForm #eventGroupName").focus(); + cy.get("#editEventGroupForm #eventGroupName").clear(); + cy.get("#editEventGroupForm #eventGroupDescription").focus(); + cy.get("#editEventGroupForm #eventGroupDescription").clear(); + cy.get("#editEventGroupForm #eventGroupURL").focus(); + cy.get("#editEventGroupForm #eventGroupURL").clear(); + cy.get("#editEventGroupForm #eventGroupHostName").focus(); + cy.get("#editEventGroupForm #eventGroupHostName").clear(); + cy.get("#editEventGroupForm #eventGroupCreatorEmail").focus(); + cy.get("#editEventGroupForm #eventGroupCreatorEmail").clear(); - cy.get("#editEventGroupForm #eventGroupName").type("Edited Group Name"); - cy.get("#editEventGroupForm #eventGroupDescription").type( - "Edited Group Description", - ); - cy.get("#editEventGroupForm #eventGroupURL").type( - "https://edited.example.com", - ); - cy.get("#editEventGroupForm #eventGroupHostName").type("Edited Name"); - cy.get("#editEventGroupForm #eventGroupCreatorEmail").type( - "edited@example.com", - ); + cy.get("#editEventGroupForm #eventGroupName").type("Edited Group Name"); + cy.get("#editEventGroupForm #eventGroupDescription").type( + "Edited Group Description", + ); + cy.get("#editEventGroupForm #eventGroupURL").type( + "https://edited.example.com", + ); + cy.get("#editEventGroupForm #eventGroupHostName").type("Edited Name"); + cy.get("#editEventGroupForm #eventGroupCreatorEmail").type( + "edited@example.com", + ); - // Submit the form - cy.get("#editEventGroupForm").submit(); + // Submit the form + cy.get("#editEventGroupForm").submit(); - // Wait for the modal to not be visible - cy.get("#editModal").should("not.be.visible"); + // Wait for the modal to not be visible + cy.get("#editModal").should("not.be.visible"); - // Check that all the data is correct - cy.get("#eventGroupName").should("have.text", "Edited Group Name"); - cy.get("#eventDescription").should( - "contain.text", - "Edited Group Description", - ); - cy.get("#eventGroupURL").should( - "contain.text", - "https://edited.example.com", - ); - cy.get("#hostName").should("contain.text", "Edited Name"); - }); + // Check that all the data is correct + cy.get("#eventGroupName").should("have.text", "Edited Group Name"); + cy.get("#eventDescription").should( + "contain.text", + "Edited Group Description", + ); + cy.get("#eventGroupURL").should( + "contain.text", + "https://edited.example.com", + ); + cy.get("#hostName").should("contain.text", "Edited Name"); + }); }); diff --git a/cypress/e2e/magicLink.cy.ts b/cypress/e2e/magicLink.cy.ts index 55404159..778a2bdb 100644 --- a/cypress/e2e/magicLink.cy.ts +++ b/cypress/e2e/magicLink.cy.ts @@ -1,14 +1,14 @@ describe("Restricted Event Creation", () => { - it("should redirect to the magic link form", () => { - cy.setCookie( - "cypressConfigOverride", - JSON.stringify({ - general: { - creator_email_addresses: ["test@test.com"], - }, - }), - ); - cy.visit("/new"); - cy.get("h2").should("contain", "Request a link to create a new event"); - }); + it("should redirect to the magic link form", () => { + cy.setCookie( + "cypressConfigOverride", + JSON.stringify({ + general: { + creator_email_addresses: ["test@test.com"], + }, + }), + ); + cy.visit("/new"); + cy.get("h2").should("contain", "Request a link to create a new event"); + }); }); diff --git a/cypress/e2e/publicEvent.cy.ts b/cypress/e2e/publicEvent.cy.ts index f110c029..3cd56ff5 100644 --- a/cypress/e2e/publicEvent.cy.ts +++ b/cypress/e2e/publicEvent.cy.ts @@ -1,75 +1,75 @@ import eventData from "../fixtures/eventData.json"; describe("Events", () => { - beforeEach(() => { - cy.setCookie( - "cypressConfigOverride", - JSON.stringify({ - general: { - show_public_event_list: true, - }, - }), - ); - cy.visit("/new"); - cy.get("#showNewEventFormButton").click(); + beforeEach(() => { + cy.setCookie( + "cypressConfigOverride", + JSON.stringify({ + general: { + show_public_event_list: true, + }, + }), + ); + cy.visit("/new"); + cy.get("#showNewEventFormButton").click(); - cy.get("#eventName").type(eventData.eventName); - cy.get("#eventLocation").type(eventData.eventLocation); - // These are datetime-local inputs - cy.get("#eventStart").type(eventData.eventStart); - cy.get("#eventEnd").type(eventData.eventEnd); + cy.get("#eventName").type(eventData.eventName); + cy.get("#eventLocation").type(eventData.eventLocation); + // These are datetime-local inputs + cy.get("#eventStart").type(eventData.eventStart); + cy.get("#eventEnd").type(eventData.eventEnd); - cy.get("select#timezone + span.select2").click(); - cy.get(".select2-results__option") - .contains(eventData.timezone) - .click({ force: true }); + cy.get("select#timezone + span.select2").click(); + cy.get(".select2-results__option") + .contains(eventData.timezone) + .click({ force: true }); - cy.get("#eventDescription").type(eventData.eventDescription); - cy.get("#eventURL").type(eventData.eventURL); + cy.get("#eventDescription").type(eventData.eventDescription); + cy.get("#eventURL").type(eventData.eventURL); - cy.get("#hostName").type(eventData.hostName); - cy.get("#creatorEmail").type(eventData.creatorEmail); + cy.get("#hostName").type(eventData.hostName); + cy.get("#creatorEmail").type(eventData.creatorEmail); - // Check checkboxes based on eventData - if (eventData.interactionCheckbox) { - cy.get("#interactionCheckbox").check(); - } + // Check checkboxes based on eventData + if (eventData.interactionCheckbox) { + cy.get("#interactionCheckbox").check(); + } - if (eventData.joinCheckbox) { - cy.get("#joinCheckbox").check(); - } + if (eventData.joinCheckbox) { + cy.get("#joinCheckbox").check(); + } - if (eventData.maxAttendeesCheckbox) { - cy.get("#maxAttendeesCheckbox").check(); - cy.get("#maxAttendees").type(eventData.maxAttendees.toString()); - } + if (eventData.maxAttendeesCheckbox) { + cy.get("#maxAttendeesCheckbox").check(); + cy.get("#maxAttendees").type(eventData.maxAttendees.toString()); + } - cy.get("#publicEventCheckbox").check(); + cy.get("#publicEventCheckbox").check(); - // Submit the form - cy.get("#newEventFormSubmit").click(); + // Submit the form + cy.get("#newEventFormSubmit").click(); - // Wait for the new page to load - cy.url({ timeout: 10000 }).should("not.include", "/new"); + // Wait for the new page to load + cy.url({ timeout: 10000 }).should("not.include", "/new"); - // Get the new event ID from the URL - cy.url().then((url) => { - const [eventID, editToken] = url.split("/").pop().split("?"); - cy.wrap(eventID).as("eventID"); - cy.wrap(editToken).as("editToken"); - }); + // Get the new event ID from the URL + cy.url().then((url) => { + const [eventID, editToken] = url.split("/").pop().split("?"); + cy.wrap(eventID).as("eventID"); + cy.wrap(editToken).as("editToken"); }); + }); - it("should be visible in the public event list", function () { - cy.setCookie( - "cypressConfigOverride", - JSON.stringify({ - general: { - show_public_event_list: true, - }, - }), - ); - cy.visit("/"); - cy.get("#upcomingEvents").should("contain", eventData.eventName); - }); + it("should be visible in the public event list", function () { + cy.setCookie( + "cypressConfigOverride", + JSON.stringify({ + general: { + show_public_event_list: true, + }, + }), + ); + cy.visit("/"); + cy.get("#upcomingEvents").should("contain", eventData.eventName); + }); }); diff --git a/cypress/e2e/publicGroup.cy.ts b/cypress/e2e/publicGroup.cy.ts index 45361956..0fd53c13 100644 --- a/cypress/e2e/publicGroup.cy.ts +++ b/cypress/e2e/publicGroup.cy.ts @@ -1,28 +1,28 @@ import groupData from "../fixtures/groupData.json"; describe("Groups", () => { - beforeEach(() => { - cy.setCookie( - "cypressConfigOverride", - JSON.stringify({ - general: { - show_public_event_list: true, - }, - }), - ); - cy.createGroup(groupData, true); - }); - it("should be visible in the public group list", function () { - cy.setCookie( - "cypressConfigOverride", - JSON.stringify({ - general: { - show_public_event_list: true, - }, - }), - ); - cy.visit("/"); - cy.get("#groupsTab").click(); - cy.get("#eventGroups").should("contain", groupData.eventGroupName); - }); + beforeEach(() => { + cy.setCookie( + "cypressConfigOverride", + JSON.stringify({ + general: { + show_public_event_list: true, + }, + }), + ); + cy.createGroup(groupData, true); + }); + it("should be visible in the public group list", function () { + cy.setCookie( + "cypressConfigOverride", + JSON.stringify({ + general: { + show_public_event_list: true, + }, + }), + ); + cy.visit("/"); + cy.get("#groupsTab").click(); + cy.get("#eventGroups").should("contain", groupData.eventGroupName); + }); }); diff --git a/cypress/fixtures/eventData.json b/cypress/fixtures/eventData.json index a38ccf2b..f5081b82 100644 --- a/cypress/fixtures/eventData.json +++ b/cypress/fixtures/eventData.json @@ -1,16 +1,16 @@ { - "eventName": "Your Event Name", - "eventLocation": "Event Location", - "timezone": "America/New York", - "eventDescription": "Event Description", - "eventURL": "https://example.com", - "hostName": "Your Name", - "creatorEmail": "test@example.com", - "eventGroupCheckbox": false, - "interactionCheckbox": true, - "joinCheckbox": true, - "maxAttendeesCheckbox": true, - "maxAttendees": 10, - "eventStart": "2030-01-01T00:00", - "eventEnd": "2030-01-01T01:00" + "eventName": "Your Event Name", + "eventLocation": "Event Location", + "timezone": "America/New York", + "eventDescription": "Event Description", + "eventURL": "https://example.com", + "hostName": "Your Name", + "creatorEmail": "test@example.com", + "eventGroupCheckbox": false, + "interactionCheckbox": true, + "joinCheckbox": true, + "maxAttendeesCheckbox": true, + "maxAttendees": 10, + "eventStart": "2030-01-01T00:00", + "eventEnd": "2030-01-01T01:00" } diff --git a/cypress/fixtures/groupData.json b/cypress/fixtures/groupData.json index 907c3b22..48bc2e3c 100644 --- a/cypress/fixtures/groupData.json +++ b/cypress/fixtures/groupData.json @@ -1,7 +1,7 @@ { - "eventGroupName": "Test Group", - "eventGroupDescription": "Test Group Description", - "eventGroupURL": "https://example.com", - "hostName": "Test Host", - "creatorEmail": "test@example.com" + "eventGroupName": "Test Group", + "eventGroupDescription": "Test Group Description", + "eventGroupURL": "https://example.com", + "hostName": "Test Host", + "creatorEmail": "test@example.com" } diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 546ab7bb..c470b82f 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -38,45 +38,45 @@ // eslint-disable-next-line @typescript-eslint/no-namespace declare namespace Cypress { - interface Chainable { - createGroup( - groupData: { - eventGroupName: string; - eventGroupDescription: string; - eventGroupURL: string; - hostName: string; - creatorEmail: string; - }, - isPublic?: boolean, - ): Chainable; - } + interface Chainable { + createGroup( + groupData: { + eventGroupName: string; + eventGroupDescription: string; + eventGroupURL: string; + hostName: string; + creatorEmail: string; + }, + isPublic?: boolean, + ): Chainable; + } } Cypress.Commands.add("createGroup", (groupData, isPublic) => { - cy.visit("/new"); - cy.get("#showNewEventGroupFormButton").click(); + cy.visit("/new"); + cy.get("#showNewEventGroupFormButton").click(); - // Fill in the form - cy.get("#eventGroupName").type(groupData.eventGroupName); - cy.get("#eventGroupDescription").type(groupData.eventGroupDescription); - cy.get("#eventGroupURL").type(groupData.eventGroupURL); - cy.get("#eventGroupHostName").type(groupData.hostName); - cy.get("#eventGroupCreatorEmail").type(groupData.creatorEmail); + // Fill in the form + cy.get("#eventGroupName").type(groupData.eventGroupName); + cy.get("#eventGroupDescription").type(groupData.eventGroupDescription); + cy.get("#eventGroupURL").type(groupData.eventGroupURL); + cy.get("#eventGroupHostName").type(groupData.hostName); + cy.get("#eventGroupCreatorEmail").type(groupData.creatorEmail); - if (isPublic) { - cy.get("#publicGroupCheckbox").check(); - } + if (isPublic) { + cy.get("#publicGroupCheckbox").check(); + } - // Submit the form - cy.get("#newEventGroupForm").submit(); + // Submit the form + cy.get("#newEventGroupForm").submit(); - // Wait for the new page to load - cy.url({ timeout: 10000 }).should("not.include", "/new"); + // Wait for the new page to load + cy.url({ timeout: 10000 }).should("not.include", "/new"); - // Get the new group ID from the URL - cy.url().then((url) => { - const [groupID, editToken] = url.split("/").pop().split("?"); - cy.wrap(groupID).as("groupID"); - cy.wrap(editToken.slice(2)).as("editToken"); - }); + // Get the new group ID from the URL + cy.url().then((url) => { + const [groupID, editToken] = url.split("/").pop().split("?"); + cy.wrap(groupID).as("groupID"); + cy.wrap(editToken.slice(2)).as("editToken"); + }); }); diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 9a8b8a07..1b6425b8 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -1,10 +1,10 @@ { - "compilerOptions": { - "target": "es5", - "lib": ["es5", "dom"], - "types": ["cypress", "node"], - "resolveJsonModule": true, - "esModuleInterop": true - }, - "include": ["**/*.ts"] + "compilerOptions": { + "target": "es5", + "lib": ["es5", "dom"], + "types": ["cypress", "node"], + "resolveJsonModule": true, + "esModuleInterop": true + }, + "include": ["**/*.ts"] } diff --git a/docker-compose.yml b/docker-compose.yml index f799a9ed..7aa056fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,24 +1,24 @@ volumes: - mongodb_data_db: + mongodb_data_db: services: - gathio: - container_name: gathio-app - image: ghcr.io/lowercasename/gathio:latest - links: - - mongo - ports: - - 3000:3000 - volumes: - # The path to Gathio's config folder - change to match your system - - ./gathio-docker/config:/app/config - # The path to Gathio's static, public pages including instance description - # and privacy policy - change to match your system - - ./gathio-docker/static:/app/static - # The path to Gathio's user-uploaded event images folder - change to match your system - - ./gathio-docker/images:/app/public/events - mongo: - container_name: gathio-db - image: mongo:latest - volumes: - - mongodb_data_db:/data/db + gathio: + container_name: gathio-app + image: ghcr.io/lowercasename/gathio:latest + links: + - mongo + ports: + - 3000:3000 + volumes: + # The path to Gathio's config folder - change to match your system + - ./gathio-docker/config:/app/config + # The path to Gathio's static, public pages including instance description + # and privacy policy - change to match your system + - ./gathio-docker/static:/app/static + # The path to Gathio's user-uploaded event images folder - change to match your system + - ./gathio-docker/images:/app/public/events + mongo: + container_name: gathio-db + image: mongo:latest + volumes: + - mongodb_data_db:/data/db diff --git a/docs/index.md b/docs/index.md index b42a01fa..015abfa0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,8 +8,8 @@ on your own server. These docs are here to: -- Explain the more esoteric or complex features of Gathio -- Help you set up, customize, and run Gathio on your own system +- Explain the more esoteric or complex features of Gathio +- Help you set up, customize, and run Gathio on your own system ## Documentation diff --git a/docs/running-gathio/installation.md b/docs/running-gathio/installation.md index 59f458db..f3a614d1 100644 --- a/docs/running-gathio/installation.md +++ b/docs/running-gathio/installation.md @@ -136,7 +136,7 @@ volumes: ``` As with all things in the Docker universe, two things seperated by a colon -means `:`. So +means `:`. So here you're saying "any files I put in the folder called `/home/username/docker/gathio-docker/config` on my computer will appear inside the Docker container at the path `/app/static`. Don't change the paths on the diff --git a/docs/using-gathio/fediverse.md b/docs/using-gathio/fediverse.md index e31bbb6c..acf40f3d 100644 --- a/docs/using-gathio/fediverse.md +++ b/docs/using-gathio/fediverse.md @@ -16,7 +16,7 @@ Each event created on Gathio has a randomly-generated handle that looks somethin 1. If the event allows for people to RSVP, you'll get a DM with a poll asking if you are going to attend. If you vote "Yes" in the poll, your @ handle on your instance will be listed on the event page and it will link back to your Mastodon profile. (The following gif shows this flow in action.) If you RSVP then you will be DMed when the organiser changes any details about the event. - [![](https://tinysubversions.com/pics/mastodon-01.gif)](https://tinysubversions.com/pics/mastodon-01.gif) + [![](https://tinysubversions.com/pics/mastodon-01.gif)](https://tinysubversions.com/pics/mastodon-01.gif) 2. Regardless of whether you RSVP, following the account means that you will see any updates made by the organiser in your home timeline, like following any normal account. The event account will also automatically boost any conversation about the event so you can see what people are talking about. (This is subject to the same moderation as any Gathio event: if the event organiser deletes the comment from the event page, then the boost will be un-boosted as well.) diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index ac93da88..992817c6 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -1,24 +1,24 @@ module.exports = { - apps: [ - { - name: "gathio-prod", - script: "pnpm start", - watch: false, - instances: 1, - autorestart: true, - max_restarts: 10, - max_memory_restart: "512M", - }, - ], + apps: [ + { + name: "gathio-prod", + script: "pnpm start", + watch: false, + instances: 1, + autorestart: true, + max_restarts: 10, + max_memory_restart: "512M", + }, + ], - deploy: { - production: { - user: "raphael", - host: "gath.io", - ref: "origin/main", - repo: "git@github.com:lowercasename/gathio", - path: "/home/raphael/gathio-production", - "post-deploy": "./deploy.sh", - }, + deploy: { + production: { + user: "raphael", + host: "gath.io", + ref: "origin/main", + repo: "git@github.com:lowercasename/gathio", + path: "/home/raphael/gathio-production", + "post-deploy": "./deploy.sh", }, + }, }; diff --git a/eslint.config.mjs b/eslint.config.mjs index f41e9717..e51b6f3e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -6,32 +6,32 @@ import tseslint from "typescript-eslint"; import pluginCypress from "eslint-plugin-cypress/flat"; export default tseslint.config( - globalIgnores(["dist/", "public/js/**/*.js"]), - eslint.configs.recommended, - tseslint.configs.recommended, - { - languageOptions: { - globals: { - ...globals.node, - }, - }, + globalIgnores(["dist/", "public/js/**/*.js"]), + eslint.configs.recommended, + tseslint.configs.recommended, + { + languageOptions: { + globals: { + ...globals.node, + }, }, - { - rules: { - "@typescript-eslint/no-unused-vars": [ - "error", - { argsIgnorePattern: "^_" }, - ], - }, + }, + { + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { argsIgnorePattern: "^_" }, + ], }, - { - files: ["cypress/**/*.ts"], - plugins: { - cypress: pluginCypress, - }, - ...pluginCypress.configs.recommended, - rules: { - "@typescript-eslint/no-unused-expressions": "off", - }, + }, + { + files: ["cypress/**/*.ts"], + plugins: { + cypress: pluginCypress, }, + ...pluginCypress.configs.recommended, + rules: { + "@typescript-eslint/no-unused-expressions": "off", + }, + }, ); diff --git a/locales/de.json b/locales/de.json index 7e01403c..8f816814 100644 --- a/locales/de.json +++ b/locales/de.json @@ -308,4 +308,4 @@ "views.publiceventlist.pastevents": "Vergangene Veranstaltungen", "views.publiceventlist.upcomingevents": "Kommende Veranstaltungen", "views.right": "Mach es richtig!" -} \ No newline at end of file +} diff --git a/locales/en.json b/locales/en.json index fc7eba0f..3e82893d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,311 +1,311 @@ { - "common.close": "Close", - "common.copied": "Copied!", - "common.copy": "Copy", - "common.coverimg": "Cover image", - "common.create": "Create", - "common.creating": "Creating...", - "common.datetimeformat": "dddd D MMMM YYYY h:mm a", - "common.emailaddr": "Email address", - "common.eventend": "Ends", - "common.eventgroupid": "Event group ID", - "common.eventgroupname": "Event group name", - "common.eventgroups": "Event groups", - "common.eventlocation": "Location", - "common.eventname": "Event name", - "common.eventstart": "Starts", - "common.timezone": "Timezone", - "common.year-month-format": "MMMM YYYY", - "common.youremail": "Your email", - "config.defaultinstancedesc": "**{{ siteName }}** is running on Gathio — a simple, federated, privacy-first event hosting platform.", - "config.instancerule.creatoremail-false": "Anyone can create events and groups", - "config.instancerule.creatoremail-true": "Only specific people can create events and groups", - "config.instancerule.deleteafterdays-false": "Events are permanent, and are never automatically deleted", - "config.instancerule.deleteafterdays-true": "Events are automatically deleted {{ days }} days after they end", - "config.instancerule.isfederated-false": "This instance does not federate with other instances", - "config.instancerule.isfederated-true": "This instance federates with other instances using ActivityPub", - "config.instancerule.showpubliceventlist-false": "Events and groups can only be accessed by direct link", - "config.instancerule.showpubliceventlist-true": "Public events and groups are displayed on the homepage", - "frontend.dateformat": "dddd D MMMM YYYY", - "frontend.displaydate-days": "{{ startdate }} at {{ starttime }} - {{ enddate }} at {{ endtime }} {{ timezone }}", - "frontend.displaydate-sameday": "{{ startdate }} from {{ starttime }} to {{ endtime }} {{ timezone }}", - "frontend.elnumber": "({{count}} people)", - "frontend.eventattendees": "people", - "frontend.newevent": "New event", - "frontend.publicevents": "Public events", - "frontend.timeformat": "h:mm a", - "routes.addeventattendeesubject": "You're RSVPed to {{ eventName }}", - "routes.addeventcommentsubject": "New comment in {{eventName}}", - "routes.deleteeventsubject": "{{ eventName }} was deleted", - "routes.event.datetimeformat": "{{thedate, intlDate}}", - "routes.event.descriptionchanged": "the event description changed", - "routes.event.difftext": "This event was just updated with new information.", - "routes.event.editedsubject": "{{ eventname }} was just edited", - "routes.event.endtimechanged": "the end time changed to {{ endtime }}", - "routes.event.locationchanged": "the location changed to {{ location }}", - "routes.event.namechanged": "the event name changed to {{ eventname }}", - "routes.event.starttimechanged": "the start time changed to {{ starttime }}", - "routes.event.timezonechanged": "the time zone changed to {{ timezone }}", - "routes.magiclink-invalid": "This magic link is invalid or has expired. Please request a new one here.", - "routes.magiclink.mailsubject": "Magic link to create an event", - "routes.magiclink.provideemail": "Please provide an email address.", - "routes.magiclink.requestmlbutton": "Request magic link", - "routes.magiclink.thanks": "Thanks! If this email address can create events, you should receive an email with a magic link.", - "routes.removeeventattendeesubject": "You have been removed from an event", - "routes.subscribedsubject": "You have subscribed to an event group", - "util.validation.eventdata.creatoremail": "Email address is invalid.", - "util.validation.eventdata.eventdescription": "Event description is required.", - "util.validation.eventdata.eventend": "Event end time is required.", - "util.validation.eventdata.eventgroupboolean": "Event group ID is required.", - "util.validation.eventdata.eventgroupedittoken": "Event group edit token is required.", - "util.validation.eventdata.eventlocation": "Event location is required.", - "util.validation.eventdata.eventname": "Event name is required.", - "util.validation.eventdata.eventstart": "Event start time is required.", - "util.validation.eventdata.eventurl": "Event link is invalid.", - "util.validation.eventdata.maxattendees": "Max number of attendees must be a number.", - "util.validation.eventdata.maxattendeesboolean": "Max number of attendees is required.", - "util.validation.eventdata.timezone": "Event timezone is required.", - "util.validation.eventtime.endisbefore": "End time must be in the future.", - "util.validation.eventtime.endyears": "Event duration cannot be longer than 1 year.", - "util.validation.eventtime.startisafter": "Start time must be before end time.", - "util.validation.eventtime.startisbefore": "Start time must be in the future.", - "util.validation.groupdata.creatoremail": "Email address is invalid.", - "util.validation.groupdata.eventgroupdescription": "Event group description is required.", - "util.validation.groupdata.eventgroupname": "Event group name is required.", - "util.validation.groupdata.eventgroupurl": "Group link is invalid.", - "views.404desc": "It may have never existed, or it's been removed from the server. Don't despair - why not create a new one? I for one would love to come to your ocarina recital.", - "views.404notfound": "Event not found!", - "views.createeventmagiclink.requestmlcontact": "If you run into any issues, please contact the instance administrator.", - "views.createeventmagiclink.requestmldesc": "The administrator of this instance has limited event creation to a set of specific email addresses. If your email address is allowed to create events, you will be sent a magic link. If not, you won't receive anything.", - "views.createeventmagiclink.requestmltitle": "Request a link to create a new event", - "views.del": "Delete", - "views.edittoken": "Enter editing password", - "views.edittokendesc": "Enter the editing password you received by email or were shown when the event was created.", - "views.emails.addeventattendee.clicktocancel": "Click this link", - "views.emails.addeventattendee.dontknowhtml": "If you didn't mark yourself as attending an event on {{ siteName }}, someone may have accidentally typed your email instead of theirs. If you don't want to attend this event, use the deletion password above to remove yourself from the event page.", - "views.emails.addeventattendee.eventlink": "Follow this link to open the event page any time", - "views.emails.addeventattendee.preface": "You just marked yourself as attending an event on {{ siteName }}. Thank you! We'll send you another email if there are any updates to the event. Your email will be automatically removed from the database once the event finishes.", - "views.emails.addeventattendee.removapasswordhtml": "You can also head to the event page and use this deletion password", - "views.emails.addeventattendee.removelink": "Need to remove yourself from this event? Click this link", - "views.emails.addeventattendee.removepassword": "You can also head to the event page and use this deletion password", - "views.emails.addeventattendee.toremove": "Need to remove yourself from this event?", - "views.emails.addeventcomment.link": "Click here to see the comment", - "views.emails.addeventcomment.preface": "{{ commentAuthor }} has just posted a comment on an event you're attending on {{ siteName }}.", - "views.emails.addeventcomment.prefacehtml": "{{ commentAuthor }} has just posted a comment on an event you're attending on {{ siteName }}.", - "views.emails.createevent.desc": "Use the following link to edit your event. DO NOT SHARE THIS, as anyone with this link can edit your event.", - "views.emails.createevent.editpswddesc": "Event group secret editing code", - "views.emails.createevent.preface": "Your event has been created!", - "views.emails.createevent.sharelink": "Use this link to share it with people", - "views.emails.createeventgroup.done": "And that's it - have a great day!", - "views.emails.createeventgroup.dontknow": "If you didn't make an event group on {{ siteName }}, someone may have accidentally typed your email instead of theirs when they were making the group. Just click on the edit link above and delete that event group, which removes your email from the system as well.", - "views.emails.createeventgroup.editgrouplink": "Edit the event group here", - "views.emails.createeventgroup.editgrouplinkhtml": "Edit event group", - "views.emails.createeventgroup.eventlink": "To add events to this group (whether brand new events or ones you've already made), click the 'This event is part of an event group' checkbox. You will need to copy the following two codes into the box which opens:", - "views.emails.createeventgroup.link": "You can edit your event group by clicking the button below, or just following this link", - "views.emails.createeventgroup.preface": "You just created a new event group on {{ siteName }}! Thanks a bunch - we're delighted to have you.", - "views.emails.createeventgroup.sharelink": "To let others know about your event group, send them this link", - "views.emails.createeventhtml.desc": "Click this button to edit your event. DO NOT SHARE THIS, as anyone with this link can edit your event.", - "views.emails.createeventhtml.editevent": "Edit Your Event", - "views.emails.deleteevent.done": "If you didn't mark yourself as attending an event on {{siteName}}, someone may have accidentally typed your email instead of theirs. Don't worry - that event, and your email, is deleted from the system now.", - "views.emails.deleteevent.preface": "The {{ eventName }} event you're attending on {{ siteName }} was just deleted by its creator.", - "views.emails.dontknow": "If you didn't mark yourself as attending an event on {{siteName}}, someone may have accidentally typed your email instead of theirs. If you don't want to attend this event, use the deletion password above to remove yourself from the event page.", - "views.emails.editevent.dontknow": "If you didn't mark yourself as attending an event on {{ siteName }}, someone may have accidentally typed your email instead of theirs. Don't worry - there isn't anything you need to do. Your email will be removed from the system when the event finishes.", - "views.emails.editevent.link": "Click here to see the event", - "views.emails.editevent.preface": "An event you're attending on {{ siteName }} has just been edited.", - "views.emails.eventgroupupdate.afterlink": " ", - "views.emails.eventgroupupdate.beforelink": "The event is ", - "views.emails.eventgroupupdate.dontknow": "If you didn't subscribe yourself to this event group on {{ siteName }}, someone may have accidentally typed your email instead of theirs.", - "views.emails.eventgroupupdate.dontknowhtml": "If you didn't subscribe yourself to this event group on {{ siteName }}, someone may have accidentally typed your email instead of theirs. Click here to unsubscribe", - "views.emails.eventgroupupdate.grouplink": "Click here to see the event group", - "views.emails.eventgroupupdate.holduphtml": "Hold up - I don't want to receive these emails any more!", - "views.emails.eventgroupupdate.link": "The event is '{{ eventName }}'", - "views.emails.eventgroupupdate.preface": "A new event has been added to the event group '{{ eventGroupName }}' on {{ siteName }}", - "views.emails.eventgroupupdate.prefacehtml": "A new event has been added to the event group '{eventGroupName}' on {{ siteName }}.", - "views.emails.eventgroupupdate.unsubscribe": "Click here to unsubscribe", - "views.emails.holdup": "Hold up - I have no idea what this email is about!", - "views.emails.love": "Love,", - "views.emails.magiclink.dontknow": "If you didn't try to create an event on {{ siteName }}, someone may have accidentally typed your email instead of theirs. Don't worry - there isn't anything you need to do. Your email address will be deleted after the magic link expires.", - "views.emails.magiclink.link": "This link will expire in 24 hours and can be used multiple times before then. Don't share it publicly, because it will allow anyone to create an event on your behalf!", - "views.emails.magiclink.preface": "Here's a magic link which will allow you to create an event on {{ siteName }}.", - "views.emails.removeeventattendee.dontknow": "If you didn't mark yourself as attending an event on {{siteName}}, someone may have accidentally typed your email instead of theirs. Don't worry - you won't receive any more of these emails for this event, and your email has been removed from the database.", - "views.emails.removeeventattendee.preface": "You have been removed from the event {{ eventName }} on {{ siteName }} by the organizer of the event.", - "views.emails.subscribed.desc": "You will receive emails when new events are added to the group, and can unsubscribe at any time.", - "views.emails.subscribed.preface": "You have been subscribed to the event group '{{eventGroupName}}' on {{ siteName }}.", - "views.emails.unattendevent.desc": "If you didn't mean to do this, an admin may have removed you from the event.", - "views.emails.unattendevent.dontknow": "If you didn't mark yourself as attending an event on {{siteName}}, someone may have accidentally typed your email instead of theirs, then removed it. Don't worry - you won't receive any more emails linked to this event.", - "views.emails.unattendevent.preface": "You just removed yourself from an event on {{ siteName }}. You will no longer receive update emails for this event.", - "views.event.about": "About", - "views.event.publiclink": "Public link", - "views.event.fediverselink": "Fediverse address", - "views.event.addme": "Add me", - "views.event.addmyself": "Add myself", - "views.event.addself": "Add yourself to '{{eventData.name}}'", - "views.event.addtoGC": "Add to Google Calendar", - "views.event.attendeeemail": "Your email (optional)", - "views.event.attendeename": "Your name", - "views.event.attendeenamedesc": "Or an alias, perhaps...", - "views.event.attendeenum": "How many people in your party?", - "views.event.attendees": "Attendees", - "views.event.attendeevisible": "Show my name in the public list of attendees", - "views.event.attendeevisibledesc": "If you choose to hide your name, only the event organiser will be able to see it.", - "views.event.attention": "Your secret editing password for this event is: {{eventData.editToken}}. It's been saved in your browser storage, and if you supplied your email, it's also been sent to you. If you didn't supply your email, you must save it somewhere safe, because it won't be shown again!", - "views.event.capacity": "This event is at capacity.", - "views.event.comment": "Comment", - "views.event.commentauthor": "Name", - "views.event.commentauthorph": "Your name", - "views.event.commentcontent": "What would you like to say?", - "views.event.concludeddel": " This event has concluded. It can no longer be edited{{#if eventWillBeDeleted}}, and will be automatically deleted {{daysUntilDeletion}}{{/if}}.", - "views.event.confremoveattendee": "Are you sure you want to remove this attendee from the event? This action cannot be undone.", - "views.event.del": "Delete event", - "views.event.delconfirm": "Are you sure you want to delete this event? This action cannot be undone.", - "views.event.deletetitle": "Delete '{{ eventData.name }}'", - "views.event.discussion": "Discussion", - "views.event.edit": "Edit event", - "views.event.editlater": "You can always edit it later.", - "views.event.ended": "Ended", - "views.event.enternum": "Enter a number.", - "views.event.hidden": "(hidden from public list)", - "views.event.hiddenattendee_one": "{{count}} hidden attendee", - "views.event.hiddenattendee_other": "{{count}} hidden attendees", - "views.event.hiddenattendee_zero": "No hidden attendee", - "views.event.hostedby": "Hosted by {{eventData.hostName}}", - "views.event.ICSexport": "Export as ICS", - "views.event.joinemaildesc": "If you provide your email, you will receive updates to the event.", - "views.event.locationdesc": "Be specific.", - "views.event.lostpswd": "Forgot password? Get in touch with the event organiser.", - "views.event.noattendees": "No attendees yet!", - "views.event.nospam": "We won't spam you <3", - "views.event.numberofattende": "({{numberOfAttendees}})", - "views.event.numlimit": "Please enter a number between 1 and ${response.data.freeSpots}", - "views.event.partof": "{{eventData.eventGroup.name}}", - "views.event.postbutton": "Post comment", - "views.event.remaining_one": "{{count}} spot remaining - add yourself now!", - "views.event.remaining_other": "{{count}} spots remaining - add yourself now!", - "views.event.remaining_zero": "This event is at capacity.", - "views.event.removeAttendee": "Remove attendee", - "views.event.removeattendeedesc": "Remove attendee from '{{eventData.name}}'", - "views.event.removeme": "Remove me", - "views.event.removemyself": "Remove myself", - "views.event.removemyselfdesc": "Remove yourself from '{{eventData.name}}'", - "views.event.removepswd": "Removal password", - "views.event.removepswddesc": "You can use this password to remove yourself from the list of event attendees. If you provided your email, you'll receive it by email. Otherwise, write it down now because it will not be shown again.", - "views.event.removetitle": "Remove {{ attendeeName }} from {{ eventName }}", - "views.event.removeuser": "Remove user from event", - "views.event.reply": "Reply", - "views.event.replycontent": "What would you like to reply?", - "views.event.share": "To share your event, use the link you can see just above this message - that way your attendees won't be able to edit or delete your event!", - "views.event.showonGM": "Show on Google Maps", - "views.event.showonOM": "Show on OpenStreetMap", - "views.event.started": "Started", - "views.event.welcome": "Welcome to your event!", - "views.event.currentlyediting": "You are currently editing this event. Do not share this link!", - "views.eventgroup.about": "About", - "views.eventgroup.addevent": "To link an existing event to this group, copy and paste the two codes below into the 'Event Group' box when creating a new event or editing an existing event.", - "views.eventgroup.del": "Delete this event group", - "views.eventgroup.delconfirm": "Are you sure you want to delete this event group? This action cannot be undone.", - "views.eventgroup.deldesc": "

This will not delete the individual events contained in this group. They can be linked to another group later.", - "views.eventgroup.deletetitle": "Delete '{{ eventGroupData.name }}'", - "views.eventgroup.edit": "Edit group", - "views.eventgroup.editmode": "Switch to editing mode", - "views.eventgroup.editpswd": "Event group editing password", - "views.eventgroup.feedlinkdesc": "Paste this URL into your calendar app\nto subscribe to a live feed of events from this group.", - "views.eventgroup.hostedby": "Hosted by {{eventGroupData.hostName}}", - "views.eventgroup.ICSexport": "Export as ICS", - "views.eventgroup.pastevents": "Past events", - "views.eventgroup.subscribe": "Subscribe to events from\n'{{eventGroupData.name}}'", - "views.eventgroup.subscribebutton": "Subscribe", - "views.eventgroup.subscribedesc": "Enter your email address (optional) to receive\nupdates\nwhenever a new event is created in this group.", - "views.eventgroup.subscribetitle": "Subscribe to updates", - "views.eventgroup.upcomingevents": "Upcoming events", - "views.eventgroup.welcome": "Welcome to your event group! We've just sent you an email with your secret editing link, which you can also see in the address bar above. Haven't got the email? Check your spam or junk folder. To share your event group, use the link you can see just below this message - that way your attendees won't be able to edit or delete your event group!", - "views.home.about": "About {{ siteName }}", - "views.home.aboutgathio": "About Gathio", - "views.home.attention": "But remember: all events are visible to anyone who knows the link, so probably don't use Gathio to plot your surprise birthday party or revolution. Or whatever, you do you.", - "views.home.autodelete": "If this instance automatically deletes its events, sometime after the event finishes, it's deleted from the database for ever, and your data goes with it.", - "views.home.conftitle": "Configurable", - "views.home.fedtitle": "Federation and self-hosting", - "views.home.flagshipsetting": "The flagship Gathio instance at gath.io is designed for anyone to create ephemeral, hidden events. Anyone can create an event; events are never displayed anywhere public; and they're deleted 7 days after they end.", - "views.home.imgexample": "An example event page for a picnic. The page shows the event's location, host, date and time, and description, as well as buttons to save the event to Google Calendar, export it, and open the location in OpenStreetMap and Google Maps.", - "views.home.intro": "Gathio is a simple, federated, privacy-first event hosting platform.", - "views.home.kofi": "Support Me on Ko-fi", - "views.home.onpre": "But if your community sets up their own instance, you can limit event creation to a specific list of people, display events on a handy list on the homepage, and disable event deletion entirely!", - "views.home.opensource": "Open source", - "views.home.osdesc": "Gathio is delighted to be open source, and is built by a lovely group of people. Leave a question in our tracker if you encounter any issues.", - "views.home.privacy": "Also, Gathio doesn't show you ads, doesn't sell your data, and never sends you unnecessary emails.", - "views.home.privacytitle": "Privacy-first", - "views.home.privdesc": "There are no accounts on Gathio. When you create an event, we generate a password which allows you to edit the event. Send all your guests the public link, and all your co-hosts the secret editing link containing the password.", - "views.home.privmail": "If you supply your email, we'll send you the editing password so you don't lose it - but supplying your email is optional!", - "views.home.selfhost": "Gathio can easily be self-hosted, and supports ActivityPub services like Mastodon, Pleroma, and Friendica, allowing you to access events from anywhere on the Fediverse. We encourage you to spin up your own instance for your community. Detailed instructions on ActivityPub access and self-hosted installation live on our GitHub wiki.", - "views.home.sponsor": "If you find yourself using and enjoying Gathio, consider supporting Raphael via GitHub Sponsors. It'll help keep the project and main site running! ", - "views.incorrectpswd": "That editing password is incorrect. Try again.", - "views.interaction": "Users can post comments on this event", - "views.join": "Users can mark themselves as attending this event", - "views.layouts.main.defaultmetadata": "An easier, quicker, and much less privacy-invading way to make and share events", - "views.layouts.main.footnote": " Gathio version {{version}} · GitHub · Made with by Raphael and contributors", - "views.newevent.createnew": "Create an event", - "views.newevent.groupattention": "Event groups do not get automatically removed like events do, but events which have been removed from {{siteName}} will of course not show up in an event group.", - "views.newevent.groupdesc": "An event group is a holding area for a set of linked events, like a series of film nights, a festival, or a band tour. You can share a public link to your event group just like an individual event link, and people who know the secret editing code (sent in an email when you create the event group) will be able to add future events to the group.", - "views.newevent.importevent": "Import an existing event", - "views.newevent.neweventbutton": "Create a new event", - "views.newevent.neweventgroup": "Create a new event group", - "views.newevent.newgroup": "Create an event group", - "views.newevent.pagetitle": "What would you like to do?", - "views.newevent.visiblealert": "Events are visible to anyone who knows the link.", - "views.options.showlistattendees": "Display the list of attendees", - "views.partials.choosefile": "Choose file", - "views.partials.creatoremaildesc": "If you provide your email, we will send your secret editing password here, and use it to notify you of updates to the event.", - "views.partials.delimg": "Delete image", - "views.partials.editevent.delthis": "Delete this event", - "views.partials.editevent.edit": "Edit '{{eventData.name}}'", - "views.partials.editeventgroup.del": "Delete this event group", - "views.partials.editeventgroup.delbutton": "Delete event group", - "views.partials.eventform.creatoremail": "Your email", - "views.partials.eventform.eventdescription": "Description", - "views.partials.eventform.eventgroup": "This event is part of an event group", - "views.partials.eventform.eventgroupdata": "Link this event to an event group", - "views.partials.eventform.eventgroupedittoken": "You can find this long string of characters in the\nconfirmation email you received when you created the event group.", - "views.partials.eventform.eventgroupid": "You can find this short string of characters in the event group's link, in your confirmation email, or on the event group's page.", - "views.partials.eventform.eventgrouplinker": "Choose a group you've edited before", - "views.partials.eventform.eventurl": "Link", - "views.partials.eventform.eventurldesc": "For tickets or another event page (optional).", - "views.partials.eventform.groupbutton": "Enter group details manually", - "views.partials.eventform.hostname": "Host name", - "views.partials.eventform.hostnamedesc": "Will be shown on the event page (optional).", - "views.partials.eventform.maxattendees": "Attendee limit", - "views.partials.eventform.maxattendeestitle": "Set a limit on the maximum number of attendees", - "views.partials.eventform.options": "Options", - "views.partials.eventform.publicevent": "Display this event on the public event list", - "views.partials.eventgroup.options": "Options", - "views.partials.eventgroupform.creatoremail": "Your email", - "views.partials.eventgroupform.eventgroupddesc": "Description", - "views.partials.eventgroupform.eventgroupurl": "Link", - "views.partials.eventgroupform.eventgroupurldesc": "For tickets or another event page (optional).", - "views.partials.eventgroupform.hostname": "Host or organisation name", - "views.partials.eventgroupform.isshowningroup": "Will be shown on the event group page (optional).", - "views.partials.eventgroupform.publicgroup": "Display this group on the public group list", - "views.partials.eventlist.noevents": "No events!", - "views.partials.fixerrors": "Please fix these errors:", - "views.partials.importevent.selectfile": "Select file", - "views.partials.importeventform.import": "Import", - "views.partials.importeventform.importdesc": "Upload an .ics file here to instantly create an event. You can save a Facebook event as an .ics file by clicking on the context menu next to the 'Import' and 'Edit' buttons on the event page and choosing the 'Export Event' option. Then select the 'Save to calendar' option and save the file on your computer.", - "views.partials.importeventform.importing": "Importing...", - "views.partials.instancerules.instancesettings": "Instance settings", - "views.partials.mdsupport": "Markdown formatting\nsupported.", - "views.partials.recommendeddimensions": "Recommended dimensions (w x h): 920px by 300px.", - "views.partials.save": "Save changes", - "views.partials.saving": "Saving...", - "views.partials.sidebar.about": "About", - "views.partials.sidebar.createevent": "Create an event", - "views.partials.sidebar.events": "View events", - "views.partials.snappy": "Make it snappy.", - "views.partials.wontshow": "Will not be shown anywhere (optional).", - "views.publiceventlist.events": "Events", - "views.publiceventlist.groups": "Groups", - "views.publiceventlist.nogroups": "No groups!", - "views.publiceventlist.numoevents_one": "{{count}} event", - "views.publiceventlist.numoevents_other": "{{count}} events", - "views.publiceventlist.numoevents_zero": "No event", - "views.publiceventlist.pastevents": "Past events", - "views.publiceventlist.upcomingevents": "Upcoming events", - "views.right": "Get it right!" + "common.close": "Close", + "common.copied": "Copied!", + "common.copy": "Copy", + "common.coverimg": "Cover image", + "common.create": "Create", + "common.creating": "Creating...", + "common.datetimeformat": "dddd D MMMM YYYY h:mm a", + "common.emailaddr": "Email address", + "common.eventend": "Ends", + "common.eventgroupid": "Event group ID", + "common.eventgroupname": "Event group name", + "common.eventgroups": "Event groups", + "common.eventlocation": "Location", + "common.eventname": "Event name", + "common.eventstart": "Starts", + "common.timezone": "Timezone", + "common.year-month-format": "MMMM YYYY", + "common.youremail": "Your email", + "config.defaultinstancedesc": "**{{ siteName }}** is running on Gathio — a simple, federated, privacy-first event hosting platform.", + "config.instancerule.creatoremail-false": "Anyone can create events and groups", + "config.instancerule.creatoremail-true": "Only specific people can create events and groups", + "config.instancerule.deleteafterdays-false": "Events are permanent, and are never automatically deleted", + "config.instancerule.deleteafterdays-true": "Events are automatically deleted {{ days }} days after they end", + "config.instancerule.isfederated-false": "This instance does not federate with other instances", + "config.instancerule.isfederated-true": "This instance federates with other instances using ActivityPub", + "config.instancerule.showpubliceventlist-false": "Events and groups can only be accessed by direct link", + "config.instancerule.showpubliceventlist-true": "Public events and groups are displayed on the homepage", + "frontend.dateformat": "dddd D MMMM YYYY", + "frontend.displaydate-days": "{{ startdate }} at {{ starttime }} - {{ enddate }} at {{ endtime }} {{ timezone }}", + "frontend.displaydate-sameday": "{{ startdate }} from {{ starttime }} to {{ endtime }} {{ timezone }}", + "frontend.elnumber": "({{count}} people)", + "frontend.eventattendees": "people", + "frontend.newevent": "New event", + "frontend.publicevents": "Public events", + "frontend.timeformat": "h:mm a", + "routes.addeventattendeesubject": "You're RSVPed to {{ eventName }}", + "routes.addeventcommentsubject": "New comment in {{eventName}}", + "routes.deleteeventsubject": "{{ eventName }} was deleted", + "routes.event.datetimeformat": "{{thedate, intlDate}}", + "routes.event.descriptionchanged": "the event description changed", + "routes.event.difftext": "This event was just updated with new information.", + "routes.event.editedsubject": "{{ eventname }} was just edited", + "routes.event.endtimechanged": "the end time changed to {{ endtime }}", + "routes.event.locationchanged": "the location changed to {{ location }}", + "routes.event.namechanged": "the event name changed to {{ eventname }}", + "routes.event.starttimechanged": "the start time changed to {{ starttime }}", + "routes.event.timezonechanged": "the time zone changed to {{ timezone }}", + "routes.magiclink-invalid": "This magic link is invalid or has expired. Please request a new one here.", + "routes.magiclink.mailsubject": "Magic link to create an event", + "routes.magiclink.provideemail": "Please provide an email address.", + "routes.magiclink.requestmlbutton": "Request magic link", + "routes.magiclink.thanks": "Thanks! If this email address can create events, you should receive an email with a magic link.", + "routes.removeeventattendeesubject": "You have been removed from an event", + "routes.subscribedsubject": "You have subscribed to an event group", + "util.validation.eventdata.creatoremail": "Email address is invalid.", + "util.validation.eventdata.eventdescription": "Event description is required.", + "util.validation.eventdata.eventend": "Event end time is required.", + "util.validation.eventdata.eventgroupboolean": "Event group ID is required.", + "util.validation.eventdata.eventgroupedittoken": "Event group edit token is required.", + "util.validation.eventdata.eventlocation": "Event location is required.", + "util.validation.eventdata.eventname": "Event name is required.", + "util.validation.eventdata.eventstart": "Event start time is required.", + "util.validation.eventdata.eventurl": "Event link is invalid.", + "util.validation.eventdata.maxattendees": "Max number of attendees must be a number.", + "util.validation.eventdata.maxattendeesboolean": "Max number of attendees is required.", + "util.validation.eventdata.timezone": "Event timezone is required.", + "util.validation.eventtime.endisbefore": "End time must be in the future.", + "util.validation.eventtime.endyears": "Event duration cannot be longer than 1 year.", + "util.validation.eventtime.startisafter": "Start time must be before end time.", + "util.validation.eventtime.startisbefore": "Start time must be in the future.", + "util.validation.groupdata.creatoremail": "Email address is invalid.", + "util.validation.groupdata.eventgroupdescription": "Event group description is required.", + "util.validation.groupdata.eventgroupname": "Event group name is required.", + "util.validation.groupdata.eventgroupurl": "Group link is invalid.", + "views.404desc": "It may have never existed, or it's been removed from the server. Don't despair - why not create a new one? I for one would love to come to your ocarina recital.", + "views.404notfound": "Event not found!", + "views.createeventmagiclink.requestmlcontact": "If you run into any issues, please contact the instance administrator.", + "views.createeventmagiclink.requestmldesc": "The administrator of this instance has limited event creation to a set of specific email addresses. If your email address is allowed to create events, you will be sent a magic link. If not, you won't receive anything.", + "views.createeventmagiclink.requestmltitle": "Request a link to create a new event", + "views.del": "Delete", + "views.edittoken": "Enter editing password", + "views.edittokendesc": "Enter the editing password you received by email or were shown when the event was created.", + "views.emails.addeventattendee.clicktocancel": "Click this link", + "views.emails.addeventattendee.dontknowhtml": "If you didn't mark yourself as attending an event on {{ siteName }}, someone may have accidentally typed your email instead of theirs. If you don't want to attend this event, use the deletion password above to remove yourself from the event page.", + "views.emails.addeventattendee.eventlink": "Follow this link to open the event page any time", + "views.emails.addeventattendee.preface": "You just marked yourself as attending an event on {{ siteName }}. Thank you! We'll send you another email if there are any updates to the event. Your email will be automatically removed from the database once the event finishes.", + "views.emails.addeventattendee.removapasswordhtml": "You can also head to the event page and use this deletion password", + "views.emails.addeventattendee.removelink": "Need to remove yourself from this event? Click this link", + "views.emails.addeventattendee.removepassword": "You can also head to the event page and use this deletion password", + "views.emails.addeventattendee.toremove": "Need to remove yourself from this event?", + "views.emails.addeventcomment.link": "Click here to see the comment", + "views.emails.addeventcomment.preface": "{{ commentAuthor }} has just posted a comment on an event you're attending on {{ siteName }}.", + "views.emails.addeventcomment.prefacehtml": "{{ commentAuthor }} has just posted a comment on an event you're attending on {{ siteName }}.", + "views.emails.createevent.desc": "Use the following link to edit your event. DO NOT SHARE THIS, as anyone with this link can edit your event.", + "views.emails.createevent.editpswddesc": "Event group secret editing code", + "views.emails.createevent.preface": "Your event has been created!", + "views.emails.createevent.sharelink": "Use this link to share it with people", + "views.emails.createeventgroup.done": "And that's it - have a great day!", + "views.emails.createeventgroup.dontknow": "If you didn't make an event group on {{ siteName }}, someone may have accidentally typed your email instead of theirs when they were making the group. Just click on the edit link above and delete that event group, which removes your email from the system as well.", + "views.emails.createeventgroup.editgrouplink": "Edit the event group here", + "views.emails.createeventgroup.editgrouplinkhtml": "Edit event group", + "views.emails.createeventgroup.eventlink": "To add events to this group (whether brand new events or ones you've already made), click the 'This event is part of an event group' checkbox. You will need to copy the following two codes into the box which opens:", + "views.emails.createeventgroup.link": "You can edit your event group by clicking the button below, or just following this link", + "views.emails.createeventgroup.preface": "You just created a new event group on {{ siteName }}! Thanks a bunch - we're delighted to have you.", + "views.emails.createeventgroup.sharelink": "To let others know about your event group, send them this link", + "views.emails.createeventhtml.desc": "Click this button to edit your event. DO NOT SHARE THIS, as anyone with this link can edit your event.", + "views.emails.createeventhtml.editevent": "Edit Your Event", + "views.emails.deleteevent.done": "If you didn't mark yourself as attending an event on {{siteName}}, someone may have accidentally typed your email instead of theirs. Don't worry - that event, and your email, is deleted from the system now.", + "views.emails.deleteevent.preface": "The {{ eventName }} event you're attending on {{ siteName }} was just deleted by its creator.", + "views.emails.dontknow": "If you didn't mark yourself as attending an event on {{siteName}}, someone may have accidentally typed your email instead of theirs. If you don't want to attend this event, use the deletion password above to remove yourself from the event page.", + "views.emails.editevent.dontknow": "If you didn't mark yourself as attending an event on {{ siteName }}, someone may have accidentally typed your email instead of theirs. Don't worry - there isn't anything you need to do. Your email will be removed from the system when the event finishes.", + "views.emails.editevent.link": "Click here to see the event", + "views.emails.editevent.preface": "An event you're attending on {{ siteName }} has just been edited.", + "views.emails.eventgroupupdate.afterlink": " ", + "views.emails.eventgroupupdate.beforelink": "The event is ", + "views.emails.eventgroupupdate.dontknow": "If you didn't subscribe yourself to this event group on {{ siteName }}, someone may have accidentally typed your email instead of theirs.", + "views.emails.eventgroupupdate.dontknowhtml": "If you didn't subscribe yourself to this event group on {{ siteName }}, someone may have accidentally typed your email instead of theirs. Click here to unsubscribe", + "views.emails.eventgroupupdate.grouplink": "Click here to see the event group", + "views.emails.eventgroupupdate.holduphtml": "Hold up - I don't want to receive these emails any more!", + "views.emails.eventgroupupdate.link": "The event is '{{ eventName }}'", + "views.emails.eventgroupupdate.preface": "A new event has been added to the event group '{{ eventGroupName }}' on {{ siteName }}", + "views.emails.eventgroupupdate.prefacehtml": "A new event has been added to the event group '{eventGroupName}' on {{ siteName }}.", + "views.emails.eventgroupupdate.unsubscribe": "Click here to unsubscribe", + "views.emails.holdup": "Hold up - I have no idea what this email is about!", + "views.emails.love": "Love,", + "views.emails.magiclink.dontknow": "If you didn't try to create an event on {{ siteName }}, someone may have accidentally typed your email instead of theirs. Don't worry - there isn't anything you need to do. Your email address will be deleted after the magic link expires.", + "views.emails.magiclink.link": "This link will expire in 24 hours and can be used multiple times before then. Don't share it publicly, because it will allow anyone to create an event on your behalf!", + "views.emails.magiclink.preface": "Here's a magic link which will allow you to create an event on {{ siteName }}.", + "views.emails.removeeventattendee.dontknow": "If you didn't mark yourself as attending an event on {{siteName}}, someone may have accidentally typed your email instead of theirs. Don't worry - you won't receive any more of these emails for this event, and your email has been removed from the database.", + "views.emails.removeeventattendee.preface": "You have been removed from the event {{ eventName }} on {{ siteName }} by the organizer of the event.", + "views.emails.subscribed.desc": "You will receive emails when new events are added to the group, and can unsubscribe at any time.", + "views.emails.subscribed.preface": "You have been subscribed to the event group '{{eventGroupName}}' on {{ siteName }}.", + "views.emails.unattendevent.desc": "If you didn't mean to do this, an admin may have removed you from the event.", + "views.emails.unattendevent.dontknow": "If you didn't mark yourself as attending an event on {{siteName}}, someone may have accidentally typed your email instead of theirs, then removed it. Don't worry - you won't receive any more emails linked to this event.", + "views.emails.unattendevent.preface": "You just removed yourself from an event on {{ siteName }}. You will no longer receive update emails for this event.", + "views.event.about": "About", + "views.event.publiclink": "Public link", + "views.event.fediverselink": "Fediverse address", + "views.event.addme": "Add me", + "views.event.addmyself": "Add myself", + "views.event.addself": "Add yourself to '{{eventData.name}}'", + "views.event.addtoGC": "Add to Google Calendar", + "views.event.attendeeemail": "Your email (optional)", + "views.event.attendeename": "Your name", + "views.event.attendeenamedesc": "Or an alias, perhaps...", + "views.event.attendeenum": "How many people in your party?", + "views.event.attendees": "Attendees", + "views.event.attendeevisible": "Show my name in the public list of attendees", + "views.event.attendeevisibledesc": "If you choose to hide your name, only the event organiser will be able to see it.", + "views.event.attention": "Your secret editing password for this event is: {{eventData.editToken}}. It's been saved in your browser storage, and if you supplied your email, it's also been sent to you. If you didn't supply your email, you must save it somewhere safe, because it won't be shown again!", + "views.event.capacity": "This event is at capacity.", + "views.event.comment": "Comment", + "views.event.commentauthor": "Name", + "views.event.commentauthorph": "Your name", + "views.event.commentcontent": "What would you like to say?", + "views.event.concludeddel": " This event has concluded. It can no longer be edited{{#if eventWillBeDeleted}}, and will be automatically deleted {{daysUntilDeletion}}{{/if}}.", + "views.event.confremoveattendee": "Are you sure you want to remove this attendee from the event? This action cannot be undone.", + "views.event.del": "Delete event", + "views.event.delconfirm": "Are you sure you want to delete this event? This action cannot be undone.", + "views.event.deletetitle": "Delete '{{ eventData.name }}'", + "views.event.discussion": "Discussion", + "views.event.edit": "Edit event", + "views.event.editlater": "You can always edit it later.", + "views.event.ended": "Ended", + "views.event.enternum": "Enter a number.", + "views.event.hidden": "(hidden from public list)", + "views.event.hiddenattendee_one": "{{count}} hidden attendee", + "views.event.hiddenattendee_other": "{{count}} hidden attendees", + "views.event.hiddenattendee_zero": "No hidden attendee", + "views.event.hostedby": "Hosted by {{eventData.hostName}}", + "views.event.ICSexport": "Export as ICS", + "views.event.joinemaildesc": "If you provide your email, you will receive updates to the event.", + "views.event.locationdesc": "Be specific.", + "views.event.lostpswd": "Forgot password? Get in touch with the event organiser.", + "views.event.noattendees": "No attendees yet!", + "views.event.nospam": "We won't spam you <3", + "views.event.numberofattende": "({{numberOfAttendees}})", + "views.event.numlimit": "Please enter a number between 1 and ${response.data.freeSpots}", + "views.event.partof": "{{eventData.eventGroup.name}}", + "views.event.postbutton": "Post comment", + "views.event.remaining_one": "{{count}} spot remaining - add yourself now!", + "views.event.remaining_other": "{{count}} spots remaining - add yourself now!", + "views.event.remaining_zero": "This event is at capacity.", + "views.event.removeAttendee": "Remove attendee", + "views.event.removeattendeedesc": "Remove attendee from '{{eventData.name}}'", + "views.event.removeme": "Remove me", + "views.event.removemyself": "Remove myself", + "views.event.removemyselfdesc": "Remove yourself from '{{eventData.name}}'", + "views.event.removepswd": "Removal password", + "views.event.removepswddesc": "You can use this password to remove yourself from the list of event attendees. If you provided your email, you'll receive it by email. Otherwise, write it down now because it will not be shown again.", + "views.event.removetitle": "Remove {{ attendeeName }} from {{ eventName }}", + "views.event.removeuser": "Remove user from event", + "views.event.reply": "Reply", + "views.event.replycontent": "What would you like to reply?", + "views.event.share": "To share your event, use the link you can see just above this message - that way your attendees won't be able to edit or delete your event!", + "views.event.showonGM": "Show on Google Maps", + "views.event.showonOM": "Show on OpenStreetMap", + "views.event.started": "Started", + "views.event.welcome": "Welcome to your event!", + "views.event.currentlyediting": "You are currently editing this event. Do not share this link!", + "views.eventgroup.about": "About", + "views.eventgroup.addevent": "To link an existing event to this group, copy and paste the two codes below into the 'Event Group' box when creating a new event or editing an existing event.", + "views.eventgroup.del": "Delete this event group", + "views.eventgroup.delconfirm": "Are you sure you want to delete this event group? This action cannot be undone.", + "views.eventgroup.deldesc": "

This will not delete the individual events contained in this group. They can be linked to another group later.", + "views.eventgroup.deletetitle": "Delete '{{ eventGroupData.name }}'", + "views.eventgroup.edit": "Edit group", + "views.eventgroup.editmode": "Switch to editing mode", + "views.eventgroup.editpswd": "Event group editing password", + "views.eventgroup.feedlinkdesc": "Paste this URL into your calendar app\nto subscribe to a live feed of events from this group.", + "views.eventgroup.hostedby": "Hosted by {{eventGroupData.hostName}}", + "views.eventgroup.ICSexport": "Export as ICS", + "views.eventgroup.pastevents": "Past events", + "views.eventgroup.subscribe": "Subscribe to events from\n'{{eventGroupData.name}}'", + "views.eventgroup.subscribebutton": "Subscribe", + "views.eventgroup.subscribedesc": "Enter your email address (optional) to receive\nupdates\nwhenever a new event is created in this group.", + "views.eventgroup.subscribetitle": "Subscribe to updates", + "views.eventgroup.upcomingevents": "Upcoming events", + "views.eventgroup.welcome": "Welcome to your event group! We've just sent you an email with your secret editing link, which you can also see in the address bar above. Haven't got the email? Check your spam or junk folder. To share your event group, use the link you can see just below this message - that way your attendees won't be able to edit or delete your event group!", + "views.home.about": "About {{ siteName }}", + "views.home.aboutgathio": "About Gathio", + "views.home.attention": "But remember: all events are visible to anyone who knows the link, so probably don't use Gathio to plot your surprise birthday party or revolution. Or whatever, you do you.", + "views.home.autodelete": "If this instance automatically deletes its events, sometime after the event finishes, it's deleted from the database for ever, and your data goes with it.", + "views.home.conftitle": "Configurable", + "views.home.fedtitle": "Federation and self-hosting", + "views.home.flagshipsetting": "The flagship Gathio instance at gath.io is designed for anyone to create ephemeral, hidden events. Anyone can create an event; events are never displayed anywhere public; and they're deleted 7 days after they end.", + "views.home.imgexample": "An example event page for a picnic. The page shows the event's location, host, date and time, and description, as well as buttons to save the event to Google Calendar, export it, and open the location in OpenStreetMap and Google Maps.", + "views.home.intro": "Gathio is a simple, federated, privacy-first event hosting platform.", + "views.home.kofi": "Support Me on Ko-fi", + "views.home.onpre": "But if your community sets up their own instance, you can limit event creation to a specific list of people, display events on a handy list on the homepage, and disable event deletion entirely!", + "views.home.opensource": "Open source", + "views.home.osdesc": "Gathio is delighted to be open source, and is built by a lovely group of people. Leave a question in our tracker if you encounter any issues.", + "views.home.privacy": "Also, Gathio doesn't show you ads, doesn't sell your data, and never sends you unnecessary emails.", + "views.home.privacytitle": "Privacy-first", + "views.home.privdesc": "There are no accounts on Gathio. When you create an event, we generate a password which allows you to edit the event. Send all your guests the public link, and all your co-hosts the secret editing link containing the password.", + "views.home.privmail": "If you supply your email, we'll send you the editing password so you don't lose it - but supplying your email is optional!", + "views.home.selfhost": "Gathio can easily be self-hosted, and supports ActivityPub services like Mastodon, Pleroma, and Friendica, allowing you to access events from anywhere on the Fediverse. We encourage you to spin up your own instance for your community. Detailed instructions on ActivityPub access and self-hosted installation live on our GitHub wiki.", + "views.home.sponsor": "If you find yourself using and enjoying Gathio, consider supporting Raphael via GitHub Sponsors. It'll help keep the project and main site running! ", + "views.incorrectpswd": "That editing password is incorrect. Try again.", + "views.interaction": "Users can post comments on this event", + "views.join": "Users can mark themselves as attending this event", + "views.layouts.main.defaultmetadata": "An easier, quicker, and much less privacy-invading way to make and share events", + "views.layouts.main.footnote": " Gathio version {{version}} · GitHub · Made with by Raphael and contributors", + "views.newevent.createnew": "Create an event", + "views.newevent.groupattention": "Event groups do not get automatically removed like events do, but events which have been removed from {{siteName}} will of course not show up in an event group.", + "views.newevent.groupdesc": "An event group is a holding area for a set of linked events, like a series of film nights, a festival, or a band tour. You can share a public link to your event group just like an individual event link, and people who know the secret editing code (sent in an email when you create the event group) will be able to add future events to the group.", + "views.newevent.importevent": "Import an existing event", + "views.newevent.neweventbutton": "Create a new event", + "views.newevent.neweventgroup": "Create a new event group", + "views.newevent.newgroup": "Create an event group", + "views.newevent.pagetitle": "What would you like to do?", + "views.newevent.visiblealert": "Events are visible to anyone who knows the link.", + "views.options.showlistattendees": "Display the list of attendees", + "views.partials.choosefile": "Choose file", + "views.partials.creatoremaildesc": "If you provide your email, we will send your secret editing password here, and use it to notify you of updates to the event.", + "views.partials.delimg": "Delete image", + "views.partials.editevent.delthis": "Delete this event", + "views.partials.editevent.edit": "Edit '{{eventData.name}}'", + "views.partials.editeventgroup.del": "Delete this event group", + "views.partials.editeventgroup.delbutton": "Delete event group", + "views.partials.eventform.creatoremail": "Your email", + "views.partials.eventform.eventdescription": "Description", + "views.partials.eventform.eventgroup": "This event is part of an event group", + "views.partials.eventform.eventgroupdata": "Link this event to an event group", + "views.partials.eventform.eventgroupedittoken": "You can find this long string of characters in the\nconfirmation email you received when you created the event group.", + "views.partials.eventform.eventgroupid": "You can find this short string of characters in the event group's link, in your confirmation email, or on the event group's page.", + "views.partials.eventform.eventgrouplinker": "Choose a group you've edited before", + "views.partials.eventform.eventurl": "Link", + "views.partials.eventform.eventurldesc": "For tickets or another event page (optional).", + "views.partials.eventform.groupbutton": "Enter group details manually", + "views.partials.eventform.hostname": "Host name", + "views.partials.eventform.hostnamedesc": "Will be shown on the event page (optional).", + "views.partials.eventform.maxattendees": "Attendee limit", + "views.partials.eventform.maxattendeestitle": "Set a limit on the maximum number of attendees", + "views.partials.eventform.options": "Options", + "views.partials.eventform.publicevent": "Display this event on the public event list", + "views.partials.eventgroup.options": "Options", + "views.partials.eventgroupform.creatoremail": "Your email", + "views.partials.eventgroupform.eventgroupddesc": "Description", + "views.partials.eventgroupform.eventgroupurl": "Link", + "views.partials.eventgroupform.eventgroupurldesc": "For tickets or another event page (optional).", + "views.partials.eventgroupform.hostname": "Host or organisation name", + "views.partials.eventgroupform.isshowningroup": "Will be shown on the event group page (optional).", + "views.partials.eventgroupform.publicgroup": "Display this group on the public group list", + "views.partials.eventlist.noevents": "No events!", + "views.partials.fixerrors": "Please fix these errors:", + "views.partials.importevent.selectfile": "Select file", + "views.partials.importeventform.import": "Import", + "views.partials.importeventform.importdesc": "Upload an .ics file here to instantly create an event. You can save a Facebook event as an .ics file by clicking on the context menu next to the 'Import' and 'Edit' buttons on the event page and choosing the 'Export Event' option. Then select the 'Save to calendar' option and save the file on your computer.", + "views.partials.importeventform.importing": "Importing...", + "views.partials.instancerules.instancesettings": "Instance settings", + "views.partials.mdsupport": "Markdown formatting\nsupported.", + "views.partials.recommendeddimensions": "Recommended dimensions (w x h): 920px by 300px.", + "views.partials.save": "Save changes", + "views.partials.saving": "Saving...", + "views.partials.sidebar.about": "About", + "views.partials.sidebar.createevent": "Create an event", + "views.partials.sidebar.events": "View events", + "views.partials.snappy": "Make it snappy.", + "views.partials.wontshow": "Will not be shown anywhere (optional).", + "views.publiceventlist.events": "Events", + "views.publiceventlist.groups": "Groups", + "views.publiceventlist.nogroups": "No groups!", + "views.publiceventlist.numoevents_one": "{{count}} event", + "views.publiceventlist.numoevents_other": "{{count}} events", + "views.publiceventlist.numoevents_zero": "No event", + "views.publiceventlist.pastevents": "Past events", + "views.publiceventlist.upcomingevents": "Upcoming events", + "views.right": "Get it right!" } diff --git a/locales/ja.json b/locales/ja.json index 94d134df..942558c2 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -308,4 +308,4 @@ "views.publiceventlist.pastevents": "過去の公開イベント", "views.publiceventlist.upcomingevents": "今後の公開イベント", "views.right": "了解!" -} \ No newline at end of file +} diff --git a/mkdocs.yml b/mkdocs.yml index 39356c36..73ecd7eb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,13 +1,13 @@ site_name: Gathio theme: - name: material - features: - - content.code.copy + name: material + features: + - content.code.copy markdown_extensions: - - pymdownx.highlight: - anchor_linenums: true - line_spans: __span - pygments_lang_class: true - - pymdownx.inlinehilite - - pymdownx.snippets - - pymdownx.superfences + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences diff --git a/package.json b/package.json index e68da1ac..66d287ba 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build": "tsc", "start": "node dist/start.js", "dev": "nodemon -e ts,js --watch src --exec \"pnpm run build ; pnpm run start\"", - "lint": "eslint", + "lint": "eslint && prettier --check .", + "lint:fix": "eslint --fix && prettier --write .", "test:dev": "CYPRESS=true pnpm run dev & wait-on http://localhost:3000 && cypress open --e2e --browser chrome", "test": "pnpm run build || true && CYPRESS=true pnpm run start & wait-on http://localhost:3000 && cypress run --e2e --browser chrome" }, diff --git a/public/css/bootstrap-grid.css b/public/css/bootstrap-grid.css index bf2181b1..4106ae9b 100644 --- a/public/css/bootstrap-grid.css +++ b/public/css/bootstrap-grid.css @@ -29,25 +29,35 @@ html { } @media (min-width: 576px) { - .container, .container-sm { + .container, + .container-sm { max-width: 540px; } } @media (min-width: 768px) { - .container, .container-sm, .container-md { + .container, + .container-sm, + .container-md { max-width: 720px; } } @media (min-width: 992px) { - .container, .container-sm, .container-md, .container-lg { + .container, + .container-sm, + .container-md, + .container-lg { max-width: 960px; } } @media (min-width: 1200px) { - .container, .container-sm, .container-md, .container-lg, .container-xl { + .container, + .container-sm, + .container-md, + .container-lg, + .container-xl { max-width: 1140px; } } @@ -72,11 +82,75 @@ html { padding-left: 0; } -.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, -.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, -.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, -.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, -.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, +.col-1, +.col-2, +.col-3, +.col-4, +.col-5, +.col-6, +.col-7, +.col-8, +.col-9, +.col-10, +.col-11, +.col-12, +.col, +.col-auto, +.col-sm-1, +.col-sm-2, +.col-sm-3, +.col-sm-4, +.col-sm-5, +.col-sm-6, +.col-sm-7, +.col-sm-8, +.col-sm-9, +.col-sm-10, +.col-sm-11, +.col-sm-12, +.col-sm, +.col-sm-auto, +.col-md-1, +.col-md-2, +.col-md-3, +.col-md-4, +.col-md-5, +.col-md-6, +.col-md-7, +.col-md-8, +.col-md-9, +.col-md-10, +.col-md-11, +.col-md-12, +.col-md, +.col-md-auto, +.col-lg-1, +.col-lg-2, +.col-lg-3, +.col-lg-4, +.col-lg-5, +.col-lg-6, +.col-lg-7, +.col-lg-8, +.col-lg-9, +.col-lg-10, +.col-lg-11, +.col-lg-12, +.col-lg, +.col-lg-auto, +.col-xl-1, +.col-xl-2, +.col-xl-3, +.col-xl-4, +.col-xl-5, +.col-xl-6, +.col-xl-7, +.col-xl-8, +.col-xl-9, +.col-xl-10, +.col-xl-11, +.col-xl-12, +.col-xl, .col-xl-auto { position: relative; width: 100%; @@ -3869,4 +3943,4 @@ html { margin-left: auto !important; } } -/*# sourceMappingURL=bootstrap-grid.css.map */ \ No newline at end of file +/*# sourceMappingURL=bootstrap-grid.css.map */ diff --git a/public/css/bootstrap-grid.min.css b/public/css/bootstrap-grid.min.css index e840e6c5..3136f965 100644 --- a/public/css/bootstrap-grid.min.css +++ b/public/css/bootstrap-grid.min.css @@ -3,5 +3,3738 @@ * Copyright 2011-2021 The Bootstrap Authors * Copyright 2011-2021 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */html{box-sizing:border-box;-ms-overflow-style:scrollbar}*,::after,::before{box-sizing:inherit}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}} -/*# sourceMappingURL=bootstrap-grid.min.css.map */ \ No newline at end of file + */ +html { + box-sizing: border-box; + -ms-overflow-style: scrollbar; +} +*, +::after, +::before { + box-sizing: inherit; +} +.container, +.container-fluid, +.container-lg, +.container-md, +.container-sm, +.container-xl { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 576px) { + .container, + .container-sm { + max-width: 540px; + } +} +@media (min-width: 768px) { + .container, + .container-md, + .container-sm { + max-width: 720px; + } +} +@media (min-width: 992px) { + .container, + .container-lg, + .container-md, + .container-sm { + max-width: 960px; + } +} +@media (min-width: 1200px) { + .container, + .container-lg, + .container-md, + .container-sm, + .container-xl { + max-width: 1140px; + } +} +.row { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; +} +.no-gutters { + margin-right: 0; + margin-left: 0; +} +.no-gutters > .col, +.no-gutters > [class*="col-"] { + padding-right: 0; + padding-left: 0; +} +.col, +.col-1, +.col-10, +.col-11, +.col-12, +.col-2, +.col-3, +.col-4, +.col-5, +.col-6, +.col-7, +.col-8, +.col-9, +.col-auto, +.col-lg, +.col-lg-1, +.col-lg-10, +.col-lg-11, +.col-lg-12, +.col-lg-2, +.col-lg-3, +.col-lg-4, +.col-lg-5, +.col-lg-6, +.col-lg-7, +.col-lg-8, +.col-lg-9, +.col-lg-auto, +.col-md, +.col-md-1, +.col-md-10, +.col-md-11, +.col-md-12, +.col-md-2, +.col-md-3, +.col-md-4, +.col-md-5, +.col-md-6, +.col-md-7, +.col-md-8, +.col-md-9, +.col-md-auto, +.col-sm, +.col-sm-1, +.col-sm-10, +.col-sm-11, +.col-sm-12, +.col-sm-2, +.col-sm-3, +.col-sm-4, +.col-sm-5, +.col-sm-6, +.col-sm-7, +.col-sm-8, +.col-sm-9, +.col-sm-auto, +.col-xl, +.col-xl-1, +.col-xl-10, +.col-xl-11, +.col-xl-12, +.col-xl-2, +.col-xl-3, +.col-xl-4, +.col-xl-5, +.col-xl-6, +.col-xl-7, +.col-xl-8, +.col-xl-9, +.col-xl-auto { + position: relative; + width: 100%; + padding-right: 15px; + padding-left: 15px; +} +.col { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; +} +.row-cols-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} +.row-cols-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} +.row-cols-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} +.row-cols-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} +.row-cols-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; +} +.row-cols-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; +} +.col-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; +} +.col-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; +} +.col-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; +} +.col-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} +.col-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} +.col-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; +} +.col-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} +.col-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; +} +.col-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; +} +.col-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; +} +.col-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; +} +.col-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; +} +.col-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} +.order-first { + -ms-flex-order: -1; + order: -1; +} +.order-last { + -ms-flex-order: 13; + order: 13; +} +.order-0 { + -ms-flex-order: 0; + order: 0; +} +.order-1 { + -ms-flex-order: 1; + order: 1; +} +.order-2 { + -ms-flex-order: 2; + order: 2; +} +.order-3 { + -ms-flex-order: 3; + order: 3; +} +.order-4 { + -ms-flex-order: 4; + order: 4; +} +.order-5 { + -ms-flex-order: 5; + order: 5; +} +.order-6 { + -ms-flex-order: 6; + order: 6; +} +.order-7 { + -ms-flex-order: 7; + order: 7; +} +.order-8 { + -ms-flex-order: 8; + order: 8; +} +.order-9 { + -ms-flex-order: 9; + order: 9; +} +.order-10 { + -ms-flex-order: 10; + order: 10; +} +.order-11 { + -ms-flex-order: 11; + order: 11; +} +.order-12 { + -ms-flex-order: 12; + order: 12; +} +.offset-1 { + margin-left: 8.333333%; +} +.offset-2 { + margin-left: 16.666667%; +} +.offset-3 { + margin-left: 25%; +} +.offset-4 { + margin-left: 33.333333%; +} +.offset-5 { + margin-left: 41.666667%; +} +.offset-6 { + margin-left: 50%; +} +.offset-7 { + margin-left: 58.333333%; +} +.offset-8 { + margin-left: 66.666667%; +} +.offset-9 { + margin-left: 75%; +} +.offset-10 { + margin-left: 83.333333%; +} +.offset-11 { + margin-left: 91.666667%; +} +@media (min-width: 576px) { + .col-sm { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-sm-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-sm-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-sm-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-sm-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-sm-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-sm-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-sm-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-sm-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-sm-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-sm-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-sm-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-sm-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-sm-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-sm-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-sm-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-sm-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-sm-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-sm-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-sm-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-sm-first { + -ms-flex-order: -1; + order: -1; + } + .order-sm-last { + -ms-flex-order: 13; + order: 13; + } + .order-sm-0 { + -ms-flex-order: 0; + order: 0; + } + .order-sm-1 { + -ms-flex-order: 1; + order: 1; + } + .order-sm-2 { + -ms-flex-order: 2; + order: 2; + } + .order-sm-3 { + -ms-flex-order: 3; + order: 3; + } + .order-sm-4 { + -ms-flex-order: 4; + order: 4; + } + .order-sm-5 { + -ms-flex-order: 5; + order: 5; + } + .order-sm-6 { + -ms-flex-order: 6; + order: 6; + } + .order-sm-7 { + -ms-flex-order: 7; + order: 7; + } + .order-sm-8 { + -ms-flex-order: 8; + order: 8; + } + .order-sm-9 { + -ms-flex-order: 9; + order: 9; + } + .order-sm-10 { + -ms-flex-order: 10; + order: 10; + } + .order-sm-11 { + -ms-flex-order: 11; + order: 11; + } + .order-sm-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-sm-0 { + margin-left: 0; + } + .offset-sm-1 { + margin-left: 8.333333%; + } + .offset-sm-2 { + margin-left: 16.666667%; + } + .offset-sm-3 { + margin-left: 25%; + } + .offset-sm-4 { + margin-left: 33.333333%; + } + .offset-sm-5 { + margin-left: 41.666667%; + } + .offset-sm-6 { + margin-left: 50%; + } + .offset-sm-7 { + margin-left: 58.333333%; + } + .offset-sm-8 { + margin-left: 66.666667%; + } + .offset-sm-9 { + margin-left: 75%; + } + .offset-sm-10 { + margin-left: 83.333333%; + } + .offset-sm-11 { + margin-left: 91.666667%; + } +} +@media (min-width: 768px) { + .col-md { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-md-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-md-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-md-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-md-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-md-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-md-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-md-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-md-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-md-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-md-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-md-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-md-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-md-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-md-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-md-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-md-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-md-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-md-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-md-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-md-first { + -ms-flex-order: -1; + order: -1; + } + .order-md-last { + -ms-flex-order: 13; + order: 13; + } + .order-md-0 { + -ms-flex-order: 0; + order: 0; + } + .order-md-1 { + -ms-flex-order: 1; + order: 1; + } + .order-md-2 { + -ms-flex-order: 2; + order: 2; + } + .order-md-3 { + -ms-flex-order: 3; + order: 3; + } + .order-md-4 { + -ms-flex-order: 4; + order: 4; + } + .order-md-5 { + -ms-flex-order: 5; + order: 5; + } + .order-md-6 { + -ms-flex-order: 6; + order: 6; + } + .order-md-7 { + -ms-flex-order: 7; + order: 7; + } + .order-md-8 { + -ms-flex-order: 8; + order: 8; + } + .order-md-9 { + -ms-flex-order: 9; + order: 9; + } + .order-md-10 { + -ms-flex-order: 10; + order: 10; + } + .order-md-11 { + -ms-flex-order: 11; + order: 11; + } + .order-md-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-md-0 { + margin-left: 0; + } + .offset-md-1 { + margin-left: 8.333333%; + } + .offset-md-2 { + margin-left: 16.666667%; + } + .offset-md-3 { + margin-left: 25%; + } + .offset-md-4 { + margin-left: 33.333333%; + } + .offset-md-5 { + margin-left: 41.666667%; + } + .offset-md-6 { + margin-left: 50%; + } + .offset-md-7 { + margin-left: 58.333333%; + } + .offset-md-8 { + margin-left: 66.666667%; + } + .offset-md-9 { + margin-left: 75%; + } + .offset-md-10 { + margin-left: 83.333333%; + } + .offset-md-11 { + margin-left: 91.666667%; + } +} +@media (min-width: 992px) { + .col-lg { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-lg-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-lg-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-lg-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-lg-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-lg-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-lg-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-lg-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-lg-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-lg-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-lg-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-lg-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-lg-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-lg-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-lg-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-lg-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-lg-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-lg-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-lg-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-lg-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-lg-first { + -ms-flex-order: -1; + order: -1; + } + .order-lg-last { + -ms-flex-order: 13; + order: 13; + } + .order-lg-0 { + -ms-flex-order: 0; + order: 0; + } + .order-lg-1 { + -ms-flex-order: 1; + order: 1; + } + .order-lg-2 { + -ms-flex-order: 2; + order: 2; + } + .order-lg-3 { + -ms-flex-order: 3; + order: 3; + } + .order-lg-4 { + -ms-flex-order: 4; + order: 4; + } + .order-lg-5 { + -ms-flex-order: 5; + order: 5; + } + .order-lg-6 { + -ms-flex-order: 6; + order: 6; + } + .order-lg-7 { + -ms-flex-order: 7; + order: 7; + } + .order-lg-8 { + -ms-flex-order: 8; + order: 8; + } + .order-lg-9 { + -ms-flex-order: 9; + order: 9; + } + .order-lg-10 { + -ms-flex-order: 10; + order: 10; + } + .order-lg-11 { + -ms-flex-order: 11; + order: 11; + } + .order-lg-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-lg-0 { + margin-left: 0; + } + .offset-lg-1 { + margin-left: 8.333333%; + } + .offset-lg-2 { + margin-left: 16.666667%; + } + .offset-lg-3 { + margin-left: 25%; + } + .offset-lg-4 { + margin-left: 33.333333%; + } + .offset-lg-5 { + margin-left: 41.666667%; + } + .offset-lg-6 { + margin-left: 50%; + } + .offset-lg-7 { + margin-left: 58.333333%; + } + .offset-lg-8 { + margin-left: 66.666667%; + } + .offset-lg-9 { + margin-left: 75%; + } + .offset-lg-10 { + margin-left: 83.333333%; + } + .offset-lg-11 { + margin-left: 91.666667%; + } +} +@media (min-width: 1200px) { + .col-xl { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-xl-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-xl-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-xl-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-xl-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-xl-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-xl-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-xl-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-xl-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-xl-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-xl-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-xl-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-xl-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-xl-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-xl-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-xl-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-xl-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-xl-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-xl-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-xl-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-xl-first { + -ms-flex-order: -1; + order: -1; + } + .order-xl-last { + -ms-flex-order: 13; + order: 13; + } + .order-xl-0 { + -ms-flex-order: 0; + order: 0; + } + .order-xl-1 { + -ms-flex-order: 1; + order: 1; + } + .order-xl-2 { + -ms-flex-order: 2; + order: 2; + } + .order-xl-3 { + -ms-flex-order: 3; + order: 3; + } + .order-xl-4 { + -ms-flex-order: 4; + order: 4; + } + .order-xl-5 { + -ms-flex-order: 5; + order: 5; + } + .order-xl-6 { + -ms-flex-order: 6; + order: 6; + } + .order-xl-7 { + -ms-flex-order: 7; + order: 7; + } + .order-xl-8 { + -ms-flex-order: 8; + order: 8; + } + .order-xl-9 { + -ms-flex-order: 9; + order: 9; + } + .order-xl-10 { + -ms-flex-order: 10; + order: 10; + } + .order-xl-11 { + -ms-flex-order: 11; + order: 11; + } + .order-xl-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-xl-0 { + margin-left: 0; + } + .offset-xl-1 { + margin-left: 8.333333%; + } + .offset-xl-2 { + margin-left: 16.666667%; + } + .offset-xl-3 { + margin-left: 25%; + } + .offset-xl-4 { + margin-left: 33.333333%; + } + .offset-xl-5 { + margin-left: 41.666667%; + } + .offset-xl-6 { + margin-left: 50%; + } + .offset-xl-7 { + margin-left: 58.333333%; + } + .offset-xl-8 { + margin-left: 66.666667%; + } + .offset-xl-9 { + margin-left: 75%; + } + .offset-xl-10 { + margin-left: 83.333333%; + } + .offset-xl-11 { + margin-left: 91.666667%; + } +} +.d-none { + display: none !important; +} +.d-inline { + display: inline !important; +} +.d-inline-block { + display: inline-block !important; +} +.d-block { + display: block !important; +} +.d-table { + display: table !important; +} +.d-table-row { + display: table-row !important; +} +.d-table-cell { + display: table-cell !important; +} +.d-flex { + display: -ms-flexbox !important; + display: flex !important; +} +.d-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; +} +@media (min-width: 576px) { + .d-sm-none { + display: none !important; + } + .d-sm-inline { + display: inline !important; + } + .d-sm-inline-block { + display: inline-block !important; + } + .d-sm-block { + display: block !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: -ms-flexbox !important; + display: flex !important; + } + .d-sm-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +@media (min-width: 768px) { + .d-md-none { + display: none !important; + } + .d-md-inline { + display: inline !important; + } + .d-md-inline-block { + display: inline-block !important; + } + .d-md-block { + display: block !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: -ms-flexbox !important; + display: flex !important; + } + .d-md-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +@media (min-width: 992px) { + .d-lg-none { + display: none !important; + } + .d-lg-inline { + display: inline !important; + } + .d-lg-inline-block { + display: inline-block !important; + } + .d-lg-block { + display: block !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: -ms-flexbox !important; + display: flex !important; + } + .d-lg-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +@media (min-width: 1200px) { + .d-xl-none { + display: none !important; + } + .d-xl-inline { + display: inline !important; + } + .d-xl-inline-block { + display: inline-block !important; + } + .d-xl-block { + display: block !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: -ms-flexbox !important; + display: flex !important; + } + .d-xl-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +@media print { + .d-print-none { + display: none !important; + } + .d-print-inline { + display: inline !important; + } + .d-print-inline-block { + display: inline-block !important; + } + .d-print-block { + display: block !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: -ms-flexbox !important; + display: flex !important; + } + .d-print-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +.flex-row { + -ms-flex-direction: row !important; + flex-direction: row !important; +} +.flex-column { + -ms-flex-direction: column !important; + flex-direction: column !important; +} +.flex-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; +} +.flex-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; +} +.flex-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; +} +.flex-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; +} +.flex-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; +} +.flex-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; +} +.flex-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; +} +.flex-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; +} +.flex-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; +} +.flex-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; +} +.justify-content-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; +} +.justify-content-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; +} +.justify-content-center { + -ms-flex-pack: center !important; + justify-content: center !important; +} +.justify-content-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; +} +.justify-content-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; +} +.align-items-start { + -ms-flex-align: start !important; + align-items: flex-start !important; +} +.align-items-end { + -ms-flex-align: end !important; + align-items: flex-end !important; +} +.align-items-center { + -ms-flex-align: center !important; + align-items: center !important; +} +.align-items-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; +} +.align-items-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; +} +.align-content-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; +} +.align-content-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; +} +.align-content-center { + -ms-flex-line-pack: center !important; + align-content: center !important; +} +.align-content-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; +} +.align-content-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; +} +.align-content-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; +} +.align-self-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; +} +.align-self-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; +} +.align-self-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; +} +.align-self-center { + -ms-flex-item-align: center !important; + align-self: center !important; +} +.align-self-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; +} +.align-self-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; +} +@media (min-width: 576px) { + .flex-sm-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-sm-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-sm-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-sm-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-sm-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-sm-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-sm-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-sm-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-sm-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-sm-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-sm-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-sm-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-sm-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-sm-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-sm-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-sm-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-sm-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-sm-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-sm-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-sm-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-sm-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-sm-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-sm-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-sm-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-sm-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-sm-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-sm-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-sm-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-sm-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-sm-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-sm-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-sm-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-sm-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-sm-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +@media (min-width: 768px) { + .flex-md-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-md-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-md-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-md-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-md-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-md-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-md-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-md-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-md-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-md-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-md-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-md-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-md-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-md-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-md-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-md-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-md-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-md-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-md-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-md-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-md-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-md-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-md-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-md-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-md-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-md-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-md-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-md-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-md-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-md-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-md-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-md-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-md-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-md-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +@media (min-width: 992px) { + .flex-lg-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-lg-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-lg-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-lg-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-lg-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-lg-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-lg-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-lg-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-lg-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-lg-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-lg-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-lg-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-lg-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-lg-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-lg-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-lg-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-lg-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-lg-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-lg-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-lg-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-lg-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-lg-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-lg-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-lg-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-lg-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-lg-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-lg-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-lg-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-lg-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-lg-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-lg-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-lg-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-lg-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-lg-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +@media (min-width: 1200px) { + .flex-xl-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-xl-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-xl-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-xl-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-xl-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-xl-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-xl-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-xl-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-xl-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-xl-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-xl-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-xl-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-xl-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-xl-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-xl-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-xl-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-xl-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-xl-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-xl-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-xl-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-xl-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-xl-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-xl-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-xl-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-xl-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-xl-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-xl-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-xl-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-xl-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-xl-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-xl-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-xl-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-xl-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-xl-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +.m-0 { + margin: 0 !important; +} +.mt-0, +.my-0 { + margin-top: 0 !important; +} +.mr-0, +.mx-0 { + margin-right: 0 !important; +} +.mb-0, +.my-0 { + margin-bottom: 0 !important; +} +.ml-0, +.mx-0 { + margin-left: 0 !important; +} +.m-1 { + margin: 0.25rem !important; +} +.mt-1, +.my-1 { + margin-top: 0.25rem !important; +} +.mr-1, +.mx-1 { + margin-right: 0.25rem !important; +} +.mb-1, +.my-1 { + margin-bottom: 0.25rem !important; +} +.ml-1, +.mx-1 { + margin-left: 0.25rem !important; +} +.m-2 { + margin: 0.5rem !important; +} +.mt-2, +.my-2 { + margin-top: 0.5rem !important; +} +.mr-2, +.mx-2 { + margin-right: 0.5rem !important; +} +.mb-2, +.my-2 { + margin-bottom: 0.5rem !important; +} +.ml-2, +.mx-2 { + margin-left: 0.5rem !important; +} +.m-3 { + margin: 1rem !important; +} +.mt-3, +.my-3 { + margin-top: 1rem !important; +} +.mr-3, +.mx-3 { + margin-right: 1rem !important; +} +.mb-3, +.my-3 { + margin-bottom: 1rem !important; +} +.ml-3, +.mx-3 { + margin-left: 1rem !important; +} +.m-4 { + margin: 1.5rem !important; +} +.mt-4, +.my-4 { + margin-top: 1.5rem !important; +} +.mr-4, +.mx-4 { + margin-right: 1.5rem !important; +} +.mb-4, +.my-4 { + margin-bottom: 1.5rem !important; +} +.ml-4, +.mx-4 { + margin-left: 1.5rem !important; +} +.m-5 { + margin: 3rem !important; +} +.mt-5, +.my-5 { + margin-top: 3rem !important; +} +.mr-5, +.mx-5 { + margin-right: 3rem !important; +} +.mb-5, +.my-5 { + margin-bottom: 3rem !important; +} +.ml-5, +.mx-5 { + margin-left: 3rem !important; +} +.p-0 { + padding: 0 !important; +} +.pt-0, +.py-0 { + padding-top: 0 !important; +} +.pr-0, +.px-0 { + padding-right: 0 !important; +} +.pb-0, +.py-0 { + padding-bottom: 0 !important; +} +.pl-0, +.px-0 { + padding-left: 0 !important; +} +.p-1 { + padding: 0.25rem !important; +} +.pt-1, +.py-1 { + padding-top: 0.25rem !important; +} +.pr-1, +.px-1 { + padding-right: 0.25rem !important; +} +.pb-1, +.py-1 { + padding-bottom: 0.25rem !important; +} +.pl-1, +.px-1 { + padding-left: 0.25rem !important; +} +.p-2 { + padding: 0.5rem !important; +} +.pt-2, +.py-2 { + padding-top: 0.5rem !important; +} +.pr-2, +.px-2 { + padding-right: 0.5rem !important; +} +.pb-2, +.py-2 { + padding-bottom: 0.5rem !important; +} +.pl-2, +.px-2 { + padding-left: 0.5rem !important; +} +.p-3 { + padding: 1rem !important; +} +.pt-3, +.py-3 { + padding-top: 1rem !important; +} +.pr-3, +.px-3 { + padding-right: 1rem !important; +} +.pb-3, +.py-3 { + padding-bottom: 1rem !important; +} +.pl-3, +.px-3 { + padding-left: 1rem !important; +} +.p-4 { + padding: 1.5rem !important; +} +.pt-4, +.py-4 { + padding-top: 1.5rem !important; +} +.pr-4, +.px-4 { + padding-right: 1.5rem !important; +} +.pb-4, +.py-4 { + padding-bottom: 1.5rem !important; +} +.pl-4, +.px-4 { + padding-left: 1.5rem !important; +} +.p-5 { + padding: 3rem !important; +} +.pt-5, +.py-5 { + padding-top: 3rem !important; +} +.pr-5, +.px-5 { + padding-right: 3rem !important; +} +.pb-5, +.py-5 { + padding-bottom: 3rem !important; +} +.pl-5, +.px-5 { + padding-left: 3rem !important; +} +.m-n1 { + margin: -0.25rem !important; +} +.mt-n1, +.my-n1 { + margin-top: -0.25rem !important; +} +.mr-n1, +.mx-n1 { + margin-right: -0.25rem !important; +} +.mb-n1, +.my-n1 { + margin-bottom: -0.25rem !important; +} +.ml-n1, +.mx-n1 { + margin-left: -0.25rem !important; +} +.m-n2 { + margin: -0.5rem !important; +} +.mt-n2, +.my-n2 { + margin-top: -0.5rem !important; +} +.mr-n2, +.mx-n2 { + margin-right: -0.5rem !important; +} +.mb-n2, +.my-n2 { + margin-bottom: -0.5rem !important; +} +.ml-n2, +.mx-n2 { + margin-left: -0.5rem !important; +} +.m-n3 { + margin: -1rem !important; +} +.mt-n3, +.my-n3 { + margin-top: -1rem !important; +} +.mr-n3, +.mx-n3 { + margin-right: -1rem !important; +} +.mb-n3, +.my-n3 { + margin-bottom: -1rem !important; +} +.ml-n3, +.mx-n3 { + margin-left: -1rem !important; +} +.m-n4 { + margin: -1.5rem !important; +} +.mt-n4, +.my-n4 { + margin-top: -1.5rem !important; +} +.mr-n4, +.mx-n4 { + margin-right: -1.5rem !important; +} +.mb-n4, +.my-n4 { + margin-bottom: -1.5rem !important; +} +.ml-n4, +.mx-n4 { + margin-left: -1.5rem !important; +} +.m-n5 { + margin: -3rem !important; +} +.mt-n5, +.my-n5 { + margin-top: -3rem !important; +} +.mr-n5, +.mx-n5 { + margin-right: -3rem !important; +} +.mb-n5, +.my-n5 { + margin-bottom: -3rem !important; +} +.ml-n5, +.mx-n5 { + margin-left: -3rem !important; +} +.m-auto { + margin: auto !important; +} +.mt-auto, +.my-auto { + margin-top: auto !important; +} +.mr-auto, +.mx-auto { + margin-right: auto !important; +} +.mb-auto, +.my-auto { + margin-bottom: auto !important; +} +.ml-auto, +.mx-auto { + margin-left: auto !important; +} +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 !important; + } + .mt-sm-0, + .my-sm-0 { + margin-top: 0 !important; + } + .mr-sm-0, + .mx-sm-0 { + margin-right: 0 !important; + } + .mb-sm-0, + .my-sm-0 { + margin-bottom: 0 !important; + } + .ml-sm-0, + .mx-sm-0 { + margin-left: 0 !important; + } + .m-sm-1 { + margin: 0.25rem !important; + } + .mt-sm-1, + .my-sm-1 { + margin-top: 0.25rem !important; + } + .mr-sm-1, + .mx-sm-1 { + margin-right: 0.25rem !important; + } + .mb-sm-1, + .my-sm-1 { + margin-bottom: 0.25rem !important; + } + .ml-sm-1, + .mx-sm-1 { + margin-left: 0.25rem !important; + } + .m-sm-2 { + margin: 0.5rem !important; + } + .mt-sm-2, + .my-sm-2 { + margin-top: 0.5rem !important; + } + .mr-sm-2, + .mx-sm-2 { + margin-right: 0.5rem !important; + } + .mb-sm-2, + .my-sm-2 { + margin-bottom: 0.5rem !important; + } + .ml-sm-2, + .mx-sm-2 { + margin-left: 0.5rem !important; + } + .m-sm-3 { + margin: 1rem !important; + } + .mt-sm-3, + .my-sm-3 { + margin-top: 1rem !important; + } + .mr-sm-3, + .mx-sm-3 { + margin-right: 1rem !important; + } + .mb-sm-3, + .my-sm-3 { + margin-bottom: 1rem !important; + } + .ml-sm-3, + .mx-sm-3 { + margin-left: 1rem !important; + } + .m-sm-4 { + margin: 1.5rem !important; + } + .mt-sm-4, + .my-sm-4 { + margin-top: 1.5rem !important; + } + .mr-sm-4, + .mx-sm-4 { + margin-right: 1.5rem !important; + } + .mb-sm-4, + .my-sm-4 { + margin-bottom: 1.5rem !important; + } + .ml-sm-4, + .mx-sm-4 { + margin-left: 1.5rem !important; + } + .m-sm-5 { + margin: 3rem !important; + } + .mt-sm-5, + .my-sm-5 { + margin-top: 3rem !important; + } + .mr-sm-5, + .mx-sm-5 { + margin-right: 3rem !important; + } + .mb-sm-5, + .my-sm-5 { + margin-bottom: 3rem !important; + } + .ml-sm-5, + .mx-sm-5 { + margin-left: 3rem !important; + } + .p-sm-0 { + padding: 0 !important; + } + .pt-sm-0, + .py-sm-0 { + padding-top: 0 !important; + } + .pr-sm-0, + .px-sm-0 { + padding-right: 0 !important; + } + .pb-sm-0, + .py-sm-0 { + padding-bottom: 0 !important; + } + .pl-sm-0, + .px-sm-0 { + padding-left: 0 !important; + } + .p-sm-1 { + padding: 0.25rem !important; + } + .pt-sm-1, + .py-sm-1 { + padding-top: 0.25rem !important; + } + .pr-sm-1, + .px-sm-1 { + padding-right: 0.25rem !important; + } + .pb-sm-1, + .py-sm-1 { + padding-bottom: 0.25rem !important; + } + .pl-sm-1, + .px-sm-1 { + padding-left: 0.25rem !important; + } + .p-sm-2 { + padding: 0.5rem !important; + } + .pt-sm-2, + .py-sm-2 { + padding-top: 0.5rem !important; + } + .pr-sm-2, + .px-sm-2 { + padding-right: 0.5rem !important; + } + .pb-sm-2, + .py-sm-2 { + padding-bottom: 0.5rem !important; + } + .pl-sm-2, + .px-sm-2 { + padding-left: 0.5rem !important; + } + .p-sm-3 { + padding: 1rem !important; + } + .pt-sm-3, + .py-sm-3 { + padding-top: 1rem !important; + } + .pr-sm-3, + .px-sm-3 { + padding-right: 1rem !important; + } + .pb-sm-3, + .py-sm-3 { + padding-bottom: 1rem !important; + } + .pl-sm-3, + .px-sm-3 { + padding-left: 1rem !important; + } + .p-sm-4 { + padding: 1.5rem !important; + } + .pt-sm-4, + .py-sm-4 { + padding-top: 1.5rem !important; + } + .pr-sm-4, + .px-sm-4 { + padding-right: 1.5rem !important; + } + .pb-sm-4, + .py-sm-4 { + padding-bottom: 1.5rem !important; + } + .pl-sm-4, + .px-sm-4 { + padding-left: 1.5rem !important; + } + .p-sm-5 { + padding: 3rem !important; + } + .pt-sm-5, + .py-sm-5 { + padding-top: 3rem !important; + } + .pr-sm-5, + .px-sm-5 { + padding-right: 3rem !important; + } + .pb-sm-5, + .py-sm-5 { + padding-bottom: 3rem !important; + } + .pl-sm-5, + .px-sm-5 { + padding-left: 3rem !important; + } + .m-sm-n1 { + margin: -0.25rem !important; + } + .mt-sm-n1, + .my-sm-n1 { + margin-top: -0.25rem !important; + } + .mr-sm-n1, + .mx-sm-n1 { + margin-right: -0.25rem !important; + } + .mb-sm-n1, + .my-sm-n1 { + margin-bottom: -0.25rem !important; + } + .ml-sm-n1, + .mx-sm-n1 { + margin-left: -0.25rem !important; + } + .m-sm-n2 { + margin: -0.5rem !important; + } + .mt-sm-n2, + .my-sm-n2 { + margin-top: -0.5rem !important; + } + .mr-sm-n2, + .mx-sm-n2 { + margin-right: -0.5rem !important; + } + .mb-sm-n2, + .my-sm-n2 { + margin-bottom: -0.5rem !important; + } + .ml-sm-n2, + .mx-sm-n2 { + margin-left: -0.5rem !important; + } + .m-sm-n3 { + margin: -1rem !important; + } + .mt-sm-n3, + .my-sm-n3 { + margin-top: -1rem !important; + } + .mr-sm-n3, + .mx-sm-n3 { + margin-right: -1rem !important; + } + .mb-sm-n3, + .my-sm-n3 { + margin-bottom: -1rem !important; + } + .ml-sm-n3, + .mx-sm-n3 { + margin-left: -1rem !important; + } + .m-sm-n4 { + margin: -1.5rem !important; + } + .mt-sm-n4, + .my-sm-n4 { + margin-top: -1.5rem !important; + } + .mr-sm-n4, + .mx-sm-n4 { + margin-right: -1.5rem !important; + } + .mb-sm-n4, + .my-sm-n4 { + margin-bottom: -1.5rem !important; + } + .ml-sm-n4, + .mx-sm-n4 { + margin-left: -1.5rem !important; + } + .m-sm-n5 { + margin: -3rem !important; + } + .mt-sm-n5, + .my-sm-n5 { + margin-top: -3rem !important; + } + .mr-sm-n5, + .mx-sm-n5 { + margin-right: -3rem !important; + } + .mb-sm-n5, + .my-sm-n5 { + margin-bottom: -3rem !important; + } + .ml-sm-n5, + .mx-sm-n5 { + margin-left: -3rem !important; + } + .m-sm-auto { + margin: auto !important; + } + .mt-sm-auto, + .my-sm-auto { + margin-top: auto !important; + } + .mr-sm-auto, + .mx-sm-auto { + margin-right: auto !important; + } + .mb-sm-auto, + .my-sm-auto { + margin-bottom: auto !important; + } + .ml-sm-auto, + .mx-sm-auto { + margin-left: auto !important; + } +} +@media (min-width: 768px) { + .m-md-0 { + margin: 0 !important; + } + .mt-md-0, + .my-md-0 { + margin-top: 0 !important; + } + .mr-md-0, + .mx-md-0 { + margin-right: 0 !important; + } + .mb-md-0, + .my-md-0 { + margin-bottom: 0 !important; + } + .ml-md-0, + .mx-md-0 { + margin-left: 0 !important; + } + .m-md-1 { + margin: 0.25rem !important; + } + .mt-md-1, + .my-md-1 { + margin-top: 0.25rem !important; + } + .mr-md-1, + .mx-md-1 { + margin-right: 0.25rem !important; + } + .mb-md-1, + .my-md-1 { + margin-bottom: 0.25rem !important; + } + .ml-md-1, + .mx-md-1 { + margin-left: 0.25rem !important; + } + .m-md-2 { + margin: 0.5rem !important; + } + .mt-md-2, + .my-md-2 { + margin-top: 0.5rem !important; + } + .mr-md-2, + .mx-md-2 { + margin-right: 0.5rem !important; + } + .mb-md-2, + .my-md-2 { + margin-bottom: 0.5rem !important; + } + .ml-md-2, + .mx-md-2 { + margin-left: 0.5rem !important; + } + .m-md-3 { + margin: 1rem !important; + } + .mt-md-3, + .my-md-3 { + margin-top: 1rem !important; + } + .mr-md-3, + .mx-md-3 { + margin-right: 1rem !important; + } + .mb-md-3, + .my-md-3 { + margin-bottom: 1rem !important; + } + .ml-md-3, + .mx-md-3 { + margin-left: 1rem !important; + } + .m-md-4 { + margin: 1.5rem !important; + } + .mt-md-4, + .my-md-4 { + margin-top: 1.5rem !important; + } + .mr-md-4, + .mx-md-4 { + margin-right: 1.5rem !important; + } + .mb-md-4, + .my-md-4 { + margin-bottom: 1.5rem !important; + } + .ml-md-4, + .mx-md-4 { + margin-left: 1.5rem !important; + } + .m-md-5 { + margin: 3rem !important; + } + .mt-md-5, + .my-md-5 { + margin-top: 3rem !important; + } + .mr-md-5, + .mx-md-5 { + margin-right: 3rem !important; + } + .mb-md-5, + .my-md-5 { + margin-bottom: 3rem !important; + } + .ml-md-5, + .mx-md-5 { + margin-left: 3rem !important; + } + .p-md-0 { + padding: 0 !important; + } + .pt-md-0, + .py-md-0 { + padding-top: 0 !important; + } + .pr-md-0, + .px-md-0 { + padding-right: 0 !important; + } + .pb-md-0, + .py-md-0 { + padding-bottom: 0 !important; + } + .pl-md-0, + .px-md-0 { + padding-left: 0 !important; + } + .p-md-1 { + padding: 0.25rem !important; + } + .pt-md-1, + .py-md-1 { + padding-top: 0.25rem !important; + } + .pr-md-1, + .px-md-1 { + padding-right: 0.25rem !important; + } + .pb-md-1, + .py-md-1 { + padding-bottom: 0.25rem !important; + } + .pl-md-1, + .px-md-1 { + padding-left: 0.25rem !important; + } + .p-md-2 { + padding: 0.5rem !important; + } + .pt-md-2, + .py-md-2 { + padding-top: 0.5rem !important; + } + .pr-md-2, + .px-md-2 { + padding-right: 0.5rem !important; + } + .pb-md-2, + .py-md-2 { + padding-bottom: 0.5rem !important; + } + .pl-md-2, + .px-md-2 { + padding-left: 0.5rem !important; + } + .p-md-3 { + padding: 1rem !important; + } + .pt-md-3, + .py-md-3 { + padding-top: 1rem !important; + } + .pr-md-3, + .px-md-3 { + padding-right: 1rem !important; + } + .pb-md-3, + .py-md-3 { + padding-bottom: 1rem !important; + } + .pl-md-3, + .px-md-3 { + padding-left: 1rem !important; + } + .p-md-4 { + padding: 1.5rem !important; + } + .pt-md-4, + .py-md-4 { + padding-top: 1.5rem !important; + } + .pr-md-4, + .px-md-4 { + padding-right: 1.5rem !important; + } + .pb-md-4, + .py-md-4 { + padding-bottom: 1.5rem !important; + } + .pl-md-4, + .px-md-4 { + padding-left: 1.5rem !important; + } + .p-md-5 { + padding: 3rem !important; + } + .pt-md-5, + .py-md-5 { + padding-top: 3rem !important; + } + .pr-md-5, + .px-md-5 { + padding-right: 3rem !important; + } + .pb-md-5, + .py-md-5 { + padding-bottom: 3rem !important; + } + .pl-md-5, + .px-md-5 { + padding-left: 3rem !important; + } + .m-md-n1 { + margin: -0.25rem !important; + } + .mt-md-n1, + .my-md-n1 { + margin-top: -0.25rem !important; + } + .mr-md-n1, + .mx-md-n1 { + margin-right: -0.25rem !important; + } + .mb-md-n1, + .my-md-n1 { + margin-bottom: -0.25rem !important; + } + .ml-md-n1, + .mx-md-n1 { + margin-left: -0.25rem !important; + } + .m-md-n2 { + margin: -0.5rem !important; + } + .mt-md-n2, + .my-md-n2 { + margin-top: -0.5rem !important; + } + .mr-md-n2, + .mx-md-n2 { + margin-right: -0.5rem !important; + } + .mb-md-n2, + .my-md-n2 { + margin-bottom: -0.5rem !important; + } + .ml-md-n2, + .mx-md-n2 { + margin-left: -0.5rem !important; + } + .m-md-n3 { + margin: -1rem !important; + } + .mt-md-n3, + .my-md-n3 { + margin-top: -1rem !important; + } + .mr-md-n3, + .mx-md-n3 { + margin-right: -1rem !important; + } + .mb-md-n3, + .my-md-n3 { + margin-bottom: -1rem !important; + } + .ml-md-n3, + .mx-md-n3 { + margin-left: -1rem !important; + } + .m-md-n4 { + margin: -1.5rem !important; + } + .mt-md-n4, + .my-md-n4 { + margin-top: -1.5rem !important; + } + .mr-md-n4, + .mx-md-n4 { + margin-right: -1.5rem !important; + } + .mb-md-n4, + .my-md-n4 { + margin-bottom: -1.5rem !important; + } + .ml-md-n4, + .mx-md-n4 { + margin-left: -1.5rem !important; + } + .m-md-n5 { + margin: -3rem !important; + } + .mt-md-n5, + .my-md-n5 { + margin-top: -3rem !important; + } + .mr-md-n5, + .mx-md-n5 { + margin-right: -3rem !important; + } + .mb-md-n5, + .my-md-n5 { + margin-bottom: -3rem !important; + } + .ml-md-n5, + .mx-md-n5 { + margin-left: -3rem !important; + } + .m-md-auto { + margin: auto !important; + } + .mt-md-auto, + .my-md-auto { + margin-top: auto !important; + } + .mr-md-auto, + .mx-md-auto { + margin-right: auto !important; + } + .mb-md-auto, + .my-md-auto { + margin-bottom: auto !important; + } + .ml-md-auto, + .mx-md-auto { + margin-left: auto !important; + } +} +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 !important; + } + .mt-lg-0, + .my-lg-0 { + margin-top: 0 !important; + } + .mr-lg-0, + .mx-lg-0 { + margin-right: 0 !important; + } + .mb-lg-0, + .my-lg-0 { + margin-bottom: 0 !important; + } + .ml-lg-0, + .mx-lg-0 { + margin-left: 0 !important; + } + .m-lg-1 { + margin: 0.25rem !important; + } + .mt-lg-1, + .my-lg-1 { + margin-top: 0.25rem !important; + } + .mr-lg-1, + .mx-lg-1 { + margin-right: 0.25rem !important; + } + .mb-lg-1, + .my-lg-1 { + margin-bottom: 0.25rem !important; + } + .ml-lg-1, + .mx-lg-1 { + margin-left: 0.25rem !important; + } + .m-lg-2 { + margin: 0.5rem !important; + } + .mt-lg-2, + .my-lg-2 { + margin-top: 0.5rem !important; + } + .mr-lg-2, + .mx-lg-2 { + margin-right: 0.5rem !important; + } + .mb-lg-2, + .my-lg-2 { + margin-bottom: 0.5rem !important; + } + .ml-lg-2, + .mx-lg-2 { + margin-left: 0.5rem !important; + } + .m-lg-3 { + margin: 1rem !important; + } + .mt-lg-3, + .my-lg-3 { + margin-top: 1rem !important; + } + .mr-lg-3, + .mx-lg-3 { + margin-right: 1rem !important; + } + .mb-lg-3, + .my-lg-3 { + margin-bottom: 1rem !important; + } + .ml-lg-3, + .mx-lg-3 { + margin-left: 1rem !important; + } + .m-lg-4 { + margin: 1.5rem !important; + } + .mt-lg-4, + .my-lg-4 { + margin-top: 1.5rem !important; + } + .mr-lg-4, + .mx-lg-4 { + margin-right: 1.5rem !important; + } + .mb-lg-4, + .my-lg-4 { + margin-bottom: 1.5rem !important; + } + .ml-lg-4, + .mx-lg-4 { + margin-left: 1.5rem !important; + } + .m-lg-5 { + margin: 3rem !important; + } + .mt-lg-5, + .my-lg-5 { + margin-top: 3rem !important; + } + .mr-lg-5, + .mx-lg-5 { + margin-right: 3rem !important; + } + .mb-lg-5, + .my-lg-5 { + margin-bottom: 3rem !important; + } + .ml-lg-5, + .mx-lg-5 { + margin-left: 3rem !important; + } + .p-lg-0 { + padding: 0 !important; + } + .pt-lg-0, + .py-lg-0 { + padding-top: 0 !important; + } + .pr-lg-0, + .px-lg-0 { + padding-right: 0 !important; + } + .pb-lg-0, + .py-lg-0 { + padding-bottom: 0 !important; + } + .pl-lg-0, + .px-lg-0 { + padding-left: 0 !important; + } + .p-lg-1 { + padding: 0.25rem !important; + } + .pt-lg-1, + .py-lg-1 { + padding-top: 0.25rem !important; + } + .pr-lg-1, + .px-lg-1 { + padding-right: 0.25rem !important; + } + .pb-lg-1, + .py-lg-1 { + padding-bottom: 0.25rem !important; + } + .pl-lg-1, + .px-lg-1 { + padding-left: 0.25rem !important; + } + .p-lg-2 { + padding: 0.5rem !important; + } + .pt-lg-2, + .py-lg-2 { + padding-top: 0.5rem !important; + } + .pr-lg-2, + .px-lg-2 { + padding-right: 0.5rem !important; + } + .pb-lg-2, + .py-lg-2 { + padding-bottom: 0.5rem !important; + } + .pl-lg-2, + .px-lg-2 { + padding-left: 0.5rem !important; + } + .p-lg-3 { + padding: 1rem !important; + } + .pt-lg-3, + .py-lg-3 { + padding-top: 1rem !important; + } + .pr-lg-3, + .px-lg-3 { + padding-right: 1rem !important; + } + .pb-lg-3, + .py-lg-3 { + padding-bottom: 1rem !important; + } + .pl-lg-3, + .px-lg-3 { + padding-left: 1rem !important; + } + .p-lg-4 { + padding: 1.5rem !important; + } + .pt-lg-4, + .py-lg-4 { + padding-top: 1.5rem !important; + } + .pr-lg-4, + .px-lg-4 { + padding-right: 1.5rem !important; + } + .pb-lg-4, + .py-lg-4 { + padding-bottom: 1.5rem !important; + } + .pl-lg-4, + .px-lg-4 { + padding-left: 1.5rem !important; + } + .p-lg-5 { + padding: 3rem !important; + } + .pt-lg-5, + .py-lg-5 { + padding-top: 3rem !important; + } + .pr-lg-5, + .px-lg-5 { + padding-right: 3rem !important; + } + .pb-lg-5, + .py-lg-5 { + padding-bottom: 3rem !important; + } + .pl-lg-5, + .px-lg-5 { + padding-left: 3rem !important; + } + .m-lg-n1 { + margin: -0.25rem !important; + } + .mt-lg-n1, + .my-lg-n1 { + margin-top: -0.25rem !important; + } + .mr-lg-n1, + .mx-lg-n1 { + margin-right: -0.25rem !important; + } + .mb-lg-n1, + .my-lg-n1 { + margin-bottom: -0.25rem !important; + } + .ml-lg-n1, + .mx-lg-n1 { + margin-left: -0.25rem !important; + } + .m-lg-n2 { + margin: -0.5rem !important; + } + .mt-lg-n2, + .my-lg-n2 { + margin-top: -0.5rem !important; + } + .mr-lg-n2, + .mx-lg-n2 { + margin-right: -0.5rem !important; + } + .mb-lg-n2, + .my-lg-n2 { + margin-bottom: -0.5rem !important; + } + .ml-lg-n2, + .mx-lg-n2 { + margin-left: -0.5rem !important; + } + .m-lg-n3 { + margin: -1rem !important; + } + .mt-lg-n3, + .my-lg-n3 { + margin-top: -1rem !important; + } + .mr-lg-n3, + .mx-lg-n3 { + margin-right: -1rem !important; + } + .mb-lg-n3, + .my-lg-n3 { + margin-bottom: -1rem !important; + } + .ml-lg-n3, + .mx-lg-n3 { + margin-left: -1rem !important; + } + .m-lg-n4 { + margin: -1.5rem !important; + } + .mt-lg-n4, + .my-lg-n4 { + margin-top: -1.5rem !important; + } + .mr-lg-n4, + .mx-lg-n4 { + margin-right: -1.5rem !important; + } + .mb-lg-n4, + .my-lg-n4 { + margin-bottom: -1.5rem !important; + } + .ml-lg-n4, + .mx-lg-n4 { + margin-left: -1.5rem !important; + } + .m-lg-n5 { + margin: -3rem !important; + } + .mt-lg-n5, + .my-lg-n5 { + margin-top: -3rem !important; + } + .mr-lg-n5, + .mx-lg-n5 { + margin-right: -3rem !important; + } + .mb-lg-n5, + .my-lg-n5 { + margin-bottom: -3rem !important; + } + .ml-lg-n5, + .mx-lg-n5 { + margin-left: -3rem !important; + } + .m-lg-auto { + margin: auto !important; + } + .mt-lg-auto, + .my-lg-auto { + margin-top: auto !important; + } + .mr-lg-auto, + .mx-lg-auto { + margin-right: auto !important; + } + .mb-lg-auto, + .my-lg-auto { + margin-bottom: auto !important; + } + .ml-lg-auto, + .mx-lg-auto { + margin-left: auto !important; + } +} +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 !important; + } + .mt-xl-0, + .my-xl-0 { + margin-top: 0 !important; + } + .mr-xl-0, + .mx-xl-0 { + margin-right: 0 !important; + } + .mb-xl-0, + .my-xl-0 { + margin-bottom: 0 !important; + } + .ml-xl-0, + .mx-xl-0 { + margin-left: 0 !important; + } + .m-xl-1 { + margin: 0.25rem !important; + } + .mt-xl-1, + .my-xl-1 { + margin-top: 0.25rem !important; + } + .mr-xl-1, + .mx-xl-1 { + margin-right: 0.25rem !important; + } + .mb-xl-1, + .my-xl-1 { + margin-bottom: 0.25rem !important; + } + .ml-xl-1, + .mx-xl-1 { + margin-left: 0.25rem !important; + } + .m-xl-2 { + margin: 0.5rem !important; + } + .mt-xl-2, + .my-xl-2 { + margin-top: 0.5rem !important; + } + .mr-xl-2, + .mx-xl-2 { + margin-right: 0.5rem !important; + } + .mb-xl-2, + .my-xl-2 { + margin-bottom: 0.5rem !important; + } + .ml-xl-2, + .mx-xl-2 { + margin-left: 0.5rem !important; + } + .m-xl-3 { + margin: 1rem !important; + } + .mt-xl-3, + .my-xl-3 { + margin-top: 1rem !important; + } + .mr-xl-3, + .mx-xl-3 { + margin-right: 1rem !important; + } + .mb-xl-3, + .my-xl-3 { + margin-bottom: 1rem !important; + } + .ml-xl-3, + .mx-xl-3 { + margin-left: 1rem !important; + } + .m-xl-4 { + margin: 1.5rem !important; + } + .mt-xl-4, + .my-xl-4 { + margin-top: 1.5rem !important; + } + .mr-xl-4, + .mx-xl-4 { + margin-right: 1.5rem !important; + } + .mb-xl-4, + .my-xl-4 { + margin-bottom: 1.5rem !important; + } + .ml-xl-4, + .mx-xl-4 { + margin-left: 1.5rem !important; + } + .m-xl-5 { + margin: 3rem !important; + } + .mt-xl-5, + .my-xl-5 { + margin-top: 3rem !important; + } + .mr-xl-5, + .mx-xl-5 { + margin-right: 3rem !important; + } + .mb-xl-5, + .my-xl-5 { + margin-bottom: 3rem !important; + } + .ml-xl-5, + .mx-xl-5 { + margin-left: 3rem !important; + } + .p-xl-0 { + padding: 0 !important; + } + .pt-xl-0, + .py-xl-0 { + padding-top: 0 !important; + } + .pr-xl-0, + .px-xl-0 { + padding-right: 0 !important; + } + .pb-xl-0, + .py-xl-0 { + padding-bottom: 0 !important; + } + .pl-xl-0, + .px-xl-0 { + padding-left: 0 !important; + } + .p-xl-1 { + padding: 0.25rem !important; + } + .pt-xl-1, + .py-xl-1 { + padding-top: 0.25rem !important; + } + .pr-xl-1, + .px-xl-1 { + padding-right: 0.25rem !important; + } + .pb-xl-1, + .py-xl-1 { + padding-bottom: 0.25rem !important; + } + .pl-xl-1, + .px-xl-1 { + padding-left: 0.25rem !important; + } + .p-xl-2 { + padding: 0.5rem !important; + } + .pt-xl-2, + .py-xl-2 { + padding-top: 0.5rem !important; + } + .pr-xl-2, + .px-xl-2 { + padding-right: 0.5rem !important; + } + .pb-xl-2, + .py-xl-2 { + padding-bottom: 0.5rem !important; + } + .pl-xl-2, + .px-xl-2 { + padding-left: 0.5rem !important; + } + .p-xl-3 { + padding: 1rem !important; + } + .pt-xl-3, + .py-xl-3 { + padding-top: 1rem !important; + } + .pr-xl-3, + .px-xl-3 { + padding-right: 1rem !important; + } + .pb-xl-3, + .py-xl-3 { + padding-bottom: 1rem !important; + } + .pl-xl-3, + .px-xl-3 { + padding-left: 1rem !important; + } + .p-xl-4 { + padding: 1.5rem !important; + } + .pt-xl-4, + .py-xl-4 { + padding-top: 1.5rem !important; + } + .pr-xl-4, + .px-xl-4 { + padding-right: 1.5rem !important; + } + .pb-xl-4, + .py-xl-4 { + padding-bottom: 1.5rem !important; + } + .pl-xl-4, + .px-xl-4 { + padding-left: 1.5rem !important; + } + .p-xl-5 { + padding: 3rem !important; + } + .pt-xl-5, + .py-xl-5 { + padding-top: 3rem !important; + } + .pr-xl-5, + .px-xl-5 { + padding-right: 3rem !important; + } + .pb-xl-5, + .py-xl-5 { + padding-bottom: 3rem !important; + } + .pl-xl-5, + .px-xl-5 { + padding-left: 3rem !important; + } + .m-xl-n1 { + margin: -0.25rem !important; + } + .mt-xl-n1, + .my-xl-n1 { + margin-top: -0.25rem !important; + } + .mr-xl-n1, + .mx-xl-n1 { + margin-right: -0.25rem !important; + } + .mb-xl-n1, + .my-xl-n1 { + margin-bottom: -0.25rem !important; + } + .ml-xl-n1, + .mx-xl-n1 { + margin-left: -0.25rem !important; + } + .m-xl-n2 { + margin: -0.5rem !important; + } + .mt-xl-n2, + .my-xl-n2 { + margin-top: -0.5rem !important; + } + .mr-xl-n2, + .mx-xl-n2 { + margin-right: -0.5rem !important; + } + .mb-xl-n2, + .my-xl-n2 { + margin-bottom: -0.5rem !important; + } + .ml-xl-n2, + .mx-xl-n2 { + margin-left: -0.5rem !important; + } + .m-xl-n3 { + margin: -1rem !important; + } + .mt-xl-n3, + .my-xl-n3 { + margin-top: -1rem !important; + } + .mr-xl-n3, + .mx-xl-n3 { + margin-right: -1rem !important; + } + .mb-xl-n3, + .my-xl-n3 { + margin-bottom: -1rem !important; + } + .ml-xl-n3, + .mx-xl-n3 { + margin-left: -1rem !important; + } + .m-xl-n4 { + margin: -1.5rem !important; + } + .mt-xl-n4, + .my-xl-n4 { + margin-top: -1.5rem !important; + } + .mr-xl-n4, + .mx-xl-n4 { + margin-right: -1.5rem !important; + } + .mb-xl-n4, + .my-xl-n4 { + margin-bottom: -1.5rem !important; + } + .ml-xl-n4, + .mx-xl-n4 { + margin-left: -1.5rem !important; + } + .m-xl-n5 { + margin: -3rem !important; + } + .mt-xl-n5, + .my-xl-n5 { + margin-top: -3rem !important; + } + .mr-xl-n5, + .mx-xl-n5 { + margin-right: -3rem !important; + } + .mb-xl-n5, + .my-xl-n5 { + margin-bottom: -3rem !important; + } + .ml-xl-n5, + .mx-xl-n5 { + margin-left: -3rem !important; + } + .m-xl-auto { + margin: auto !important; + } + .mt-xl-auto, + .my-xl-auto { + margin-top: auto !important; + } + .mr-xl-auto, + .mx-xl-auto { + margin-right: auto !important; + } + .mb-xl-auto, + .my-xl-auto { + margin-bottom: auto !important; + } + .ml-xl-auto, + .mx-xl-auto { + margin-left: auto !important; + } +} +/*# sourceMappingURL=bootstrap-grid.min.css.map */ diff --git a/public/css/bootstrap-reboot.css b/public/css/bootstrap-reboot.css index d3174350..49b18a57 100644 --- a/public/css/bootstrap-reboot.css +++ b/public/css/bootstrap-reboot.css @@ -18,13 +18,24 @@ html { -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } -article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { +article, +aside, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section { display: block; } body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 1rem; font-weight: 400; line-height: 1.5; @@ -43,7 +54,12 @@ hr { overflow: visible; } -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { margin-top: 0; margin-bottom: 0.5rem; } @@ -89,7 +105,7 @@ dt { } dd { - margin-bottom: .5rem; + margin-bottom: 0.5rem; margin-left: 0; } @@ -115,11 +131,11 @@ sup { } sub { - bottom: -.25em; + bottom: -0.25em; } sup { - top: -.5em; + top: -0.5em; } a { @@ -147,7 +163,8 @@ pre, code, kbd, samp { - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; font-size: 1em; } @@ -276,7 +293,7 @@ legend { width: 100%; max-width: 100%; padding: 0; - margin-bottom: .5rem; + margin-bottom: 0.5rem; font-size: 1.5rem; line-height: inherit; color: inherit; @@ -322,4 +339,4 @@ template { [hidden] { display: none !important; } -/*# sourceMappingURL=bootstrap-reboot.css.map */ \ No newline at end of file +/*# sourceMappingURL=bootstrap-reboot.css.map */ diff --git a/public/css/bootstrap-reboot.min.css b/public/css/bootstrap-reboot.min.css index 8901a3ba..7ed221f7 100644 --- a/public/css/bootstrap-reboot.min.css +++ b/public/css/bootstrap-reboot.min.css @@ -4,5 +4,284 @@ * Copyright 2011-2021 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) - */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} -/*# sourceMappingURL=bootstrap-reboot.min.css.map */ \ No newline at end of file + */ +*, +::after, +::before { + box-sizing: border-box; +} +html { + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; +} +article, +aside, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section { + display: block; +} +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; +} +[tabindex="-1"]:focus:not(:focus-visible) { + outline: 0 !important; +} +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 0; + margin-bottom: 0.5rem; +} +p { + margin-top: 0; + margin-bottom: 1rem; +} +abbr[data-original-title], +abbr[title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; + -webkit-text-decoration-skip-ink: none; + text-decoration-skip-ink: none; +} +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} +dl, +ol, +ul { + margin-top: 0; + margin-bottom: 1rem; +} +ol ol, +ol ul, +ul ol, +ul ul { + margin-bottom: 0; +} +dt { + font-weight: 700; +} +dd { + margin-bottom: 0.5rem; + margin-left: 0; +} +blockquote { + margin: 0 0 1rem; +} +b, +strong { + font-weight: bolder; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} +a { + color: #007bff; + text-decoration: none; + background-color: transparent; +} +a:hover { + color: #0056b3; + text-decoration: underline; +} +a:not([href]):not([class]) { + color: inherit; + text-decoration: none; +} +a:not([href]):not([class]):hover { + color: inherit; + text-decoration: none; +} +code, +kbd, +pre, +samp { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + font-size: 1em; +} +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + -ms-overflow-style: scrollbar; +} +figure { + margin: 0 0 1rem; +} +img { + vertical-align: middle; + border-style: none; +} +svg { + overflow: hidden; + vertical-align: middle; +} +table { + border-collapse: collapse; +} +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #6c757d; + text-align: left; + caption-side: bottom; +} +th { + text-align: inherit; + text-align: -webkit-match-parent; +} +label { + display: inline-block; + margin-bottom: 0.5rem; +} +button { + border-radius: 0; +} +button:focus:not(:focus-visible) { + outline: 0; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +button, +input { + overflow: visible; +} +button, +select { + text-transform: none; +} +[role="button"] { + cursor: pointer; +} +select { + word-wrap: normal; +} +[type="button"], +[type="reset"], +[type="submit"], +button { + -webkit-appearance: button; +} +[type="button"]:not(:disabled), +[type="reset"]:not(:disabled), +[type="submit"]:not(:disabled), +button:not(:disabled) { + cursor: pointer; +} +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner, +button::-moz-focus-inner { + padding: 0; + border-style: none; +} +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + padding: 0; +} +textarea { + overflow: auto; + resize: vertical; +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: 0.5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal; +} +progress { + vertical-align: baseline; +} +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} +[type="search"] { + outline-offset: -2px; + -webkit-appearance: none; +} +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} +output { + display: inline-block; +} +summary { + display: list-item; + cursor: pointer; +} +template { + display: none; +} +[hidden] { + display: none !important; +} +/*# sourceMappingURL=bootstrap-reboot.min.css.map */ diff --git a/public/css/bootstrap.css b/public/css/bootstrap.css index 9a54320c..1a5d415d 100644 --- a/public/css/bootstrap.css +++ b/public/css/bootstrap.css @@ -31,8 +31,11 @@ --breakpoint-md: 768px; --breakpoint-lg: 992px; --breakpoint-xl: 1200px; - --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", + Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; } *, @@ -48,13 +51,24 @@ html { -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } -article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { +article, +aside, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section { display: block; } body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 1rem; font-weight: 400; line-height: 1.5; @@ -73,7 +87,12 @@ hr { overflow: visible; } -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { margin-top: 0; margin-bottom: 0.5rem; } @@ -119,7 +138,7 @@ dt { } dd { - margin-bottom: .5rem; + margin-bottom: 0.5rem; margin-left: 0; } @@ -145,11 +164,11 @@ sup { } sub { - bottom: -.25em; + bottom: -0.25em; } sup { - top: -.5em; + top: -0.5em; } a { @@ -177,7 +196,8 @@ pre, code, kbd, samp { - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; font-size: 1em; } @@ -306,7 +326,7 @@ legend { width: 100%; max-width: 100%; padding: 0; - margin-bottom: .5rem; + margin-bottom: 0.5rem; font-size: 1.5rem; line-height: inherit; color: inherit; @@ -353,34 +373,50 @@ template { display: none !important; } -h1, h2, h3, h4, h5, h6, -.h1, .h2, .h3, .h4, .h5, .h6 { +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { margin-bottom: 0.5rem; font-weight: 500; line-height: 1.2; } -h1, .h1 { +h1, +.h1 { font-size: 2.5rem; } -h2, .h2 { +h2, +.h2 { font-size: 2rem; } -h3, .h3 { +h3, +.h3 { font-size: 1.75rem; } -h4, .h4 { +h4, +.h4 { font-size: 1.5rem; } -h5, .h5 { +h5, +.h5 { font-size: 1.25rem; } -h6, .h6 { +h6, +.h6 { font-size: 1rem; } @@ -553,25 +589,35 @@ pre code { } @media (min-width: 576px) { - .container, .container-sm { + .container, + .container-sm { max-width: 540px; } } @media (min-width: 768px) { - .container, .container-sm, .container-md { + .container, + .container-sm, + .container-md { max-width: 720px; } } @media (min-width: 992px) { - .container, .container-sm, .container-md, .container-lg { + .container, + .container-sm, + .container-md, + .container-lg { max-width: 960px; } } @media (min-width: 1200px) { - .container, .container-sm, .container-md, .container-lg, .container-xl { + .container, + .container-sm, + .container-md, + .container-lg, + .container-xl { max-width: 1140px; } } @@ -596,11 +642,75 @@ pre code { padding-left: 0; } -.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, -.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, -.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, -.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, -.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, +.col-1, +.col-2, +.col-3, +.col-4, +.col-5, +.col-6, +.col-7, +.col-8, +.col-9, +.col-10, +.col-11, +.col-12, +.col, +.col-auto, +.col-sm-1, +.col-sm-2, +.col-sm-3, +.col-sm-4, +.col-sm-5, +.col-sm-6, +.col-sm-7, +.col-sm-8, +.col-sm-9, +.col-sm-10, +.col-sm-11, +.col-sm-12, +.col-sm, +.col-sm-auto, +.col-md-1, +.col-md-2, +.col-md-3, +.col-md-4, +.col-md-5, +.col-md-6, +.col-md-7, +.col-md-8, +.col-md-9, +.col-md-10, +.col-md-11, +.col-md-12, +.col-md, +.col-md-auto, +.col-lg-1, +.col-lg-2, +.col-lg-3, +.col-lg-4, +.col-lg-5, +.col-lg-6, +.col-lg-7, +.col-lg-8, +.col-lg-9, +.col-lg-10, +.col-lg-11, +.col-lg-12, +.col-lg, +.col-lg-auto, +.col-xl-1, +.col-xl-2, +.col-xl-3, +.col-xl-4, +.col-xl-5, +.col-xl-6, +.col-xl-7, +.col-xl-8, +.col-xl-9, +.col-xl-10, +.col-xl-11, +.col-xl-12, +.col-xl, .col-xl-auto { position: relative; width: 100%; @@ -2014,7 +2124,9 @@ pre code { background-clip: padding-box; border: 1px solid #ced4da; border-radius: 0.25rem; - transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { @@ -2061,7 +2173,8 @@ pre code { opacity: 1; } -.form-control:disabled, .form-control[readonly] { +.form-control:disabled, +.form-control[readonly] { background-color: #e9ecef; opacity: 1; } @@ -2126,7 +2239,8 @@ select.form-control:focus::-ms-value { border-width: 1px 0; } -.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg { +.form-control-plaintext.form-control-sm, +.form-control-plaintext.form-control-lg { padding-right: 0; padding-left: 0; } @@ -2147,7 +2261,8 @@ select.form-control:focus::-ms-value { border-radius: 0.3rem; } -select.form-control[size], select.form-control[multiple] { +select.form-control[size], +select.form-control[multiple] { height: auto; } @@ -2232,7 +2347,7 @@ textarea.form-control { display: none; max-width: 100%; padding: 0.25rem 0.5rem; - margin-top: .1rem; + margin-top: 0.1rem; font-size: 0.875rem; line-height: 1.5; color: #fff; @@ -2252,7 +2367,8 @@ textarea.form-control { display: block; } -.was-validated .form-control:valid, .form-control.is-valid { +.was-validated .form-control:valid, +.form-control.is-valid { border-color: #28a745; padding-right: calc(1.5em + 0.75rem) !important; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' 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"); @@ -2261,68 +2377,96 @@ textarea.form-control { background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } -.was-validated .form-control:valid:focus, .form-control.is-valid:focus { +.was-validated .form-control:valid:focus, +.form-control.is-valid:focus { border-color: #28a745; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } -.was-validated select.form-control:valid, select.form-control.is-valid { +.was-validated select.form-control:valid, +select.form-control.is-valid { padding-right: 3rem !important; background-position: right 1.5rem center; } -.was-validated textarea.form-control:valid, textarea.form-control.is-valid { +.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); + background-position: top calc(0.375em + 0.1875rem) right + calc(0.375em + 0.1875rem); } -.was-validated .custom-select:valid, .custom-select.is-valid { +.was-validated .custom-select:valid, +.custom-select.is-valid { border-color: #28a745; padding-right: calc(0.75em + 2.3125rem) !important; - background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 0.75rem center/8px 10px no-repeat, #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' 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") center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) no-repeat; + background: + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") + right 0.75rem center/8px 10px no-repeat, + #fff + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' 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") + center right 1.75rem / calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) + no-repeat; } -.was-validated .custom-select:valid:focus, .custom-select.is-valid:focus { +.was-validated .custom-select:valid:focus, +.custom-select.is-valid:focus { border-color: #28a745; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } -.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { +.was-validated .form-check-input:valid ~ .form-check-label, +.form-check-input.is-valid ~ .form-check-label { color: #28a745; } .was-validated .form-check-input:valid ~ .valid-feedback, -.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback, +.was-validated .form-check-input:valid ~ .valid-tooltip, +.form-check-input.is-valid ~ .valid-feedback, .form-check-input.is-valid ~ .valid-tooltip { display: block; } -.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label { +.was-validated .custom-control-input:valid ~ .custom-control-label, +.custom-control-input.is-valid ~ .custom-control-label { color: #28a745; } -.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before { +.was-validated .custom-control-input:valid ~ .custom-control-label::before, +.custom-control-input.is-valid ~ .custom-control-label::before { border-color: #28a745; } -.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before { +.was-validated + .custom-control-input:valid:checked + ~ .custom-control-label::before, +.custom-control-input.is-valid:checked ~ .custom-control-label::before { border-color: #34ce57; background-color: #34ce57; } -.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before { +.was-validated + .custom-control-input:valid:focus + ~ .custom-control-label::before, +.custom-control-input.is-valid:focus ~ .custom-control-label::before { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } -.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before { +.was-validated + .custom-control-input:valid:focus:not(:checked) + ~ .custom-control-label::before, +.custom-control-input.is-valid:focus:not(:checked) + ~ .custom-control-label::before { border-color: #28a745; } -.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label { +.was-validated .custom-file-input:valid ~ .custom-file-label, +.custom-file-input.is-valid ~ .custom-file-label { border-color: #28a745; } -.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label { +.was-validated .custom-file-input:valid:focus ~ .custom-file-label, +.custom-file-input.is-valid:focus ~ .custom-file-label { border-color: #28a745; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } @@ -2343,7 +2487,7 @@ textarea.form-control { display: none; max-width: 100%; padding: 0.25rem 0.5rem; - margin-top: .1rem; + margin-top: 0.1rem; font-size: 0.875rem; line-height: 1.5; color: #fff; @@ -2363,7 +2507,8 @@ textarea.form-control { display: block; } -.was-validated .form-control:invalid, .form-control.is-invalid { +.was-validated .form-control:invalid, +.form-control.is-invalid { border-color: #dc3545; padding-right: calc(1.5em + 0.75rem) !important; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%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"); @@ -2372,68 +2517,96 @@ textarea.form-control { background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } -.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus { +.was-validated .form-control:invalid:focus, +.form-control.is-invalid:focus { border-color: #dc3545; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } -.was-validated select.form-control:invalid, select.form-control.is-invalid { +.was-validated select.form-control:invalid, +select.form-control.is-invalid { padding-right: 3rem !important; background-position: right 1.5rem center; } -.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid { +.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); + background-position: top calc(0.375em + 0.1875rem) right + calc(0.375em + 0.1875rem); } -.was-validated .custom-select:invalid, .custom-select.is-invalid { +.was-validated .custom-select:invalid, +.custom-select.is-invalid { border-color: #dc3545; padding-right: calc(0.75em + 2.3125rem) !important; - background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 0.75rem center/8px 10px no-repeat, #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%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") center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) no-repeat; + background: + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") + right 0.75rem center/8px 10px no-repeat, + #fff + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%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") + center right 1.75rem / calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) + no-repeat; } -.was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus { +.was-validated .custom-select:invalid:focus, +.custom-select.is-invalid:focus { border-color: #dc3545; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } -.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { +.was-validated .form-check-input:invalid ~ .form-check-label, +.form-check-input.is-invalid ~ .form-check-label { color: #dc3545; } .was-validated .form-check-input:invalid ~ .invalid-feedback, -.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback, +.was-validated .form-check-input:invalid ~ .invalid-tooltip, +.form-check-input.is-invalid ~ .invalid-feedback, .form-check-input.is-invalid ~ .invalid-tooltip { display: block; } -.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label { +.was-validated .custom-control-input:invalid ~ .custom-control-label, +.custom-control-input.is-invalid ~ .custom-control-label { color: #dc3545; } -.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before { +.was-validated .custom-control-input:invalid ~ .custom-control-label::before, +.custom-control-input.is-invalid ~ .custom-control-label::before { border-color: #dc3545; } -.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before { +.was-validated + .custom-control-input:invalid:checked + ~ .custom-control-label::before, +.custom-control-input.is-invalid:checked ~ .custom-control-label::before { border-color: #e4606d; background-color: #e4606d; } -.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before { +.was-validated + .custom-control-input:invalid:focus + ~ .custom-control-label::before, +.custom-control-input.is-invalid:focus ~ .custom-control-label::before { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } -.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before { +.was-validated + .custom-control-input:invalid:focus:not(:checked) + ~ .custom-control-label::before, +.custom-control-input.is-invalid:focus:not(:checked) + ~ .custom-control-label::before { border-color: #dc3545; } -.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label { +.was-validated .custom-file-input:invalid ~ .custom-file-label, +.custom-file-input.is-invalid ~ .custom-file-label { border-color: #dc3545; } -.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label { +.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, +.custom-file-input.is-invalid:focus ~ .custom-file-label { border-color: #dc3545; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } @@ -2529,7 +2702,11 @@ textarea.form-control { font-size: 1rem; line-height: 1.5; border-radius: 0.25rem; - transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: + color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { @@ -2543,12 +2720,14 @@ textarea.form-control { text-decoration: none; } -.btn:focus, .btn.focus { +.btn:focus, +.btn.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } -.btn.disabled, .btn:disabled { +.btn.disabled, +.btn:disabled { opacity: 0.65; } @@ -2573,27 +2752,31 @@ fieldset:disabled a.btn { border-color: #0062cc; } -.btn-primary:focus, .btn-primary.focus { +.btn-primary:focus, +.btn-primary.focus { color: #fff; background-color: #0069d9; border-color: #0062cc; box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5); } -.btn-primary.disabled, .btn-primary:disabled { +.btn-primary.disabled, +.btn-primary:disabled { color: #fff; background-color: #007bff; border-color: #007bff; } -.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, +.btn-primary:not(:disabled):not(.disabled):active, +.btn-primary:not(:disabled):not(.disabled).active, .show > .btn-primary.dropdown-toggle { color: #fff; background-color: #0062cc; border-color: #005cbf; } -.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, +.btn-primary:not(:disabled):not(.disabled):active:focus, +.btn-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-primary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5); } @@ -2610,27 +2793,31 @@ fieldset:disabled a.btn { border-color: #545b62; } -.btn-secondary:focus, .btn-secondary.focus { +.btn-secondary:focus, +.btn-secondary.focus { color: #fff; background-color: #5a6268; border-color: #545b62; box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); } -.btn-secondary.disabled, .btn-secondary:disabled { +.btn-secondary.disabled, +.btn-secondary:disabled { color: #fff; background-color: #6c757d; border-color: #6c757d; } -.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, +.btn-secondary:not(:disabled):not(.disabled):active, +.btn-secondary:not(:disabled):not(.disabled).active, .show > .btn-secondary.dropdown-toggle { color: #fff; background-color: #545b62; border-color: #4e555b; } -.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus, +.btn-secondary:not(:disabled):not(.disabled):active:focus, +.btn-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-secondary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); } @@ -2647,27 +2834,31 @@ fieldset:disabled a.btn { border-color: #1e7e34; } -.btn-success:focus, .btn-success.focus { +.btn-success:focus, +.btn-success.focus { color: #fff; background-color: #218838; border-color: #1e7e34; box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5); } -.btn-success.disabled, .btn-success:disabled { +.btn-success.disabled, +.btn-success:disabled { color: #fff; background-color: #28a745; border-color: #28a745; } -.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active, +.btn-success:not(:disabled):not(.disabled):active, +.btn-success:not(:disabled):not(.disabled).active, .show > .btn-success.dropdown-toggle { color: #fff; background-color: #1e7e34; border-color: #1c7430; } -.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus, +.btn-success:not(:disabled):not(.disabled):active:focus, +.btn-success:not(:disabled):not(.disabled).active:focus, .show > .btn-success.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5); } @@ -2684,27 +2875,31 @@ fieldset:disabled a.btn { border-color: #117a8b; } -.btn-info:focus, .btn-info.focus { +.btn-info:focus, +.btn-info.focus { color: #fff; background-color: #138496; border-color: #117a8b; box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); } -.btn-info.disabled, .btn-info:disabled { +.btn-info.disabled, +.btn-info:disabled { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } -.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active, +.btn-info:not(:disabled):not(.disabled):active, +.btn-info:not(:disabled):not(.disabled).active, .show > .btn-info.dropdown-toggle { color: #fff; background-color: #117a8b; border-color: #10707f; } -.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus, +.btn-info:not(:disabled):not(.disabled):active:focus, +.btn-info:not(:disabled):not(.disabled).active:focus, .show > .btn-info.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); } @@ -2721,27 +2916,31 @@ fieldset:disabled a.btn { border-color: #d39e00; } -.btn-warning:focus, .btn-warning.focus { +.btn-warning:focus, +.btn-warning.focus { color: #212529; background-color: #e0a800; border-color: #d39e00; box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5); } -.btn-warning.disabled, .btn-warning:disabled { +.btn-warning.disabled, +.btn-warning:disabled { color: #212529; background-color: #ffc107; border-color: #ffc107; } -.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active, +.btn-warning:not(:disabled):not(.disabled):active, +.btn-warning:not(:disabled):not(.disabled).active, .show > .btn-warning.dropdown-toggle { color: #212529; background-color: #d39e00; border-color: #c69500; } -.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus, +.btn-warning:not(:disabled):not(.disabled):active:focus, +.btn-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-warning.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5); } @@ -2758,27 +2957,31 @@ fieldset:disabled a.btn { border-color: #bd2130; } -.btn-danger:focus, .btn-danger.focus { +.btn-danger:focus, +.btn-danger.focus { color: #fff; background-color: #c82333; border-color: #bd2130; box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5); } -.btn-danger.disabled, .btn-danger:disabled { +.btn-danger.disabled, +.btn-danger:disabled { color: #fff; background-color: #dc3545; border-color: #dc3545; } -.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active, +.btn-danger:not(:disabled):not(.disabled):active, +.btn-danger:not(:disabled):not(.disabled).active, .show > .btn-danger.dropdown-toggle { color: #fff; background-color: #bd2130; border-color: #b21f2d; } -.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus, +.btn-danger:not(:disabled):not(.disabled):active:focus, +.btn-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-danger.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5); } @@ -2795,27 +2998,31 @@ fieldset:disabled a.btn { border-color: #dae0e5; } -.btn-light:focus, .btn-light.focus { +.btn-light:focus, +.btn-light.focus { color: #212529; background-color: #e2e6ea; border-color: #dae0e5; box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); } -.btn-light.disabled, .btn-light:disabled { +.btn-light.disabled, +.btn-light:disabled { color: #212529; background-color: #f8f9fa; border-color: #f8f9fa; } -.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active, +.btn-light:not(:disabled):not(.disabled):active, +.btn-light:not(:disabled):not(.disabled).active, .show > .btn-light.dropdown-toggle { color: #212529; background-color: #dae0e5; border-color: #d3d9df; } -.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus, +.btn-light:not(:disabled):not(.disabled):active:focus, +.btn-light:not(:disabled):not(.disabled).active:focus, .show > .btn-light.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); } @@ -2832,27 +3039,31 @@ fieldset:disabled a.btn { border-color: #1d2124; } -.btn-dark:focus, .btn-dark.focus { +.btn-dark:focus, +.btn-dark.focus { color: #fff; background-color: #23272b; border-color: #1d2124; box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); } -.btn-dark.disabled, .btn-dark:disabled { +.btn-dark.disabled, +.btn-dark:disabled { color: #fff; background-color: #343a40; border-color: #343a40; } -.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active, +.btn-dark:not(:disabled):not(.disabled):active, +.btn-dark:not(:disabled):not(.disabled).active, .show > .btn-dark.dropdown-toggle { color: #fff; background-color: #1d2124; border-color: #171a1d; } -.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus, +.btn-dark:not(:disabled):not(.disabled):active:focus, +.btn-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-dark.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); } @@ -2868,23 +3079,27 @@ fieldset:disabled a.btn { border-color: #007bff; } -.btn-outline-primary:focus, .btn-outline-primary.focus { +.btn-outline-primary:focus, +.btn-outline-primary.focus { box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); } -.btn-outline-primary.disabled, .btn-outline-primary:disabled { +.btn-outline-primary.disabled, +.btn-outline-primary:disabled { color: #007bff; background-color: transparent; } -.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active, +.btn-outline-primary:not(:disabled):not(.disabled):active, +.btn-outline-primary:not(:disabled):not(.disabled).active, .show > .btn-outline-primary.dropdown-toggle { color: #fff; background-color: #007bff; border-color: #007bff; } -.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus, +.btn-outline-primary:not(:disabled):not(.disabled):active:focus, +.btn-outline-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-primary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); } @@ -2900,23 +3115,27 @@ fieldset:disabled a.btn { border-color: #6c757d; } -.btn-outline-secondary:focus, .btn-outline-secondary.focus { +.btn-outline-secondary:focus, +.btn-outline-secondary.focus { box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } -.btn-outline-secondary.disabled, .btn-outline-secondary:disabled { +.btn-outline-secondary.disabled, +.btn-outline-secondary:disabled { color: #6c757d; background-color: transparent; } -.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active, +.btn-outline-secondary:not(:disabled):not(.disabled):active, +.btn-outline-secondary:not(:disabled):not(.disabled).active, .show > .btn-outline-secondary.dropdown-toggle { color: #fff; background-color: #6c757d; border-color: #6c757d; } -.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus, +.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, +.btn-outline-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-secondary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } @@ -2932,23 +3151,27 @@ fieldset:disabled a.btn { border-color: #28a745; } -.btn-outline-success:focus, .btn-outline-success.focus { +.btn-outline-success:focus, +.btn-outline-success.focus { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } -.btn-outline-success.disabled, .btn-outline-success:disabled { +.btn-outline-success.disabled, +.btn-outline-success:disabled { color: #28a745; background-color: transparent; } -.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active, +.btn-outline-success:not(:disabled):not(.disabled):active, +.btn-outline-success:not(:disabled):not(.disabled).active, .show > .btn-outline-success.dropdown-toggle { color: #fff; background-color: #28a745; border-color: #28a745; } -.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus, +.btn-outline-success:not(:disabled):not(.disabled):active:focus, +.btn-outline-success:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-success.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } @@ -2964,23 +3187,27 @@ fieldset:disabled a.btn { border-color: #17a2b8; } -.btn-outline-info:focus, .btn-outline-info.focus { +.btn-outline-info:focus, +.btn-outline-info.focus { box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } -.btn-outline-info.disabled, .btn-outline-info:disabled { +.btn-outline-info.disabled, +.btn-outline-info:disabled { color: #17a2b8; background-color: transparent; } -.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, +.btn-outline-info:not(:disabled):not(.disabled):active, +.btn-outline-info:not(:disabled):not(.disabled).active, .show > .btn-outline-info.dropdown-toggle { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } -.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus, +.btn-outline-info:not(:disabled):not(.disabled):active:focus, +.btn-outline-info:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-info.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } @@ -2996,23 +3223,27 @@ fieldset:disabled a.btn { border-color: #ffc107; } -.btn-outline-warning:focus, .btn-outline-warning.focus { +.btn-outline-warning:focus, +.btn-outline-warning.focus { box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); } -.btn-outline-warning.disabled, .btn-outline-warning:disabled { +.btn-outline-warning.disabled, +.btn-outline-warning:disabled { color: #ffc107; background-color: transparent; } -.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active, +.btn-outline-warning:not(:disabled):not(.disabled):active, +.btn-outline-warning:not(:disabled):not(.disabled).active, .show > .btn-outline-warning.dropdown-toggle { color: #212529; background-color: #ffc107; border-color: #ffc107; } -.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus, +.btn-outline-warning:not(:disabled):not(.disabled):active:focus, +.btn-outline-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-warning.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); } @@ -3028,23 +3259,27 @@ fieldset:disabled a.btn { border-color: #dc3545; } -.btn-outline-danger:focus, .btn-outline-danger.focus { +.btn-outline-danger:focus, +.btn-outline-danger.focus { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } -.btn-outline-danger.disabled, .btn-outline-danger:disabled { +.btn-outline-danger.disabled, +.btn-outline-danger:disabled { color: #dc3545; background-color: transparent; } -.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active, +.btn-outline-danger:not(:disabled):not(.disabled):active, +.btn-outline-danger:not(:disabled):not(.disabled).active, .show > .btn-outline-danger.dropdown-toggle { color: #fff; background-color: #dc3545; border-color: #dc3545; } -.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus, +.btn-outline-danger:not(:disabled):not(.disabled):active:focus, +.btn-outline-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-danger.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } @@ -3060,23 +3295,27 @@ fieldset:disabled a.btn { border-color: #f8f9fa; } -.btn-outline-light:focus, .btn-outline-light.focus { +.btn-outline-light:focus, +.btn-outline-light.focus { box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } -.btn-outline-light.disabled, .btn-outline-light:disabled { +.btn-outline-light.disabled, +.btn-outline-light:disabled { color: #f8f9fa; background-color: transparent; } -.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active, +.btn-outline-light:not(:disabled):not(.disabled):active, +.btn-outline-light:not(:disabled):not(.disabled).active, .show > .btn-outline-light.dropdown-toggle { color: #212529; background-color: #f8f9fa; border-color: #f8f9fa; } -.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus, +.btn-outline-light:not(:disabled):not(.disabled):active:focus, +.btn-outline-light:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-light.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } @@ -3092,23 +3331,27 @@ fieldset:disabled a.btn { border-color: #343a40; } -.btn-outline-dark:focus, .btn-outline-dark.focus { +.btn-outline-dark:focus, +.btn-outline-dark.focus { box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } -.btn-outline-dark.disabled, .btn-outline-dark:disabled { +.btn-outline-dark.disabled, +.btn-outline-dark:disabled { color: #343a40; background-color: transparent; } -.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active, +.btn-outline-dark:not(:disabled):not(.disabled):active, +.btn-outline-dark:not(:disabled):not(.disabled).active, .show > .btn-outline-dark.dropdown-toggle { color: #fff; background-color: #343a40; border-color: #343a40; } -.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus, +.btn-outline-dark:not(:disabled):not(.disabled):active:focus, +.btn-outline-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-dark.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } @@ -3124,23 +3367,27 @@ fieldset:disabled a.btn { text-decoration: underline; } -.btn-link:focus, .btn-link.focus { +.btn-link:focus, +.btn-link.focus { text-decoration: underline; } -.btn-link:disabled, .btn-link.disabled { +.btn-link:disabled, +.btn-link.disabled { color: #6c757d; pointer-events: none; } -.btn-lg, .btn-group-lg > .btn { +.btn-lg, +.btn-group-lg > .btn { padding: 0.5rem 1rem; font-size: 1.25rem; line-height: 1.5; border-radius: 0.3rem; } -.btn-sm, .btn-group-sm > .btn { +.btn-sm, +.btn-group-sm > .btn { padding: 0.25rem 0.5rem; font-size: 0.875rem; line-height: 1.5; @@ -3379,7 +3626,10 @@ input[type="button"].btn-block { vertical-align: 0; } -.dropdown-menu[x-placement^="top"], .dropdown-menu[x-placement^="right"], .dropdown-menu[x-placement^="bottom"], .dropdown-menu[x-placement^="left"] { +.dropdown-menu[x-placement^="top"], +.dropdown-menu[x-placement^="right"], +.dropdown-menu[x-placement^="bottom"], +.dropdown-menu[x-placement^="left"] { right: auto; bottom: auto; } @@ -3404,19 +3654,22 @@ input[type="button"].btn-block { border: 0; } -.dropdown-item:hover, .dropdown-item:focus { +.dropdown-item:hover, +.dropdown-item:focus { color: #16181b; text-decoration: none; background-color: #e9ecef; } -.dropdown-item.active, .dropdown-item:active { +.dropdown-item.active, +.dropdown-item:active { color: #fff; text-decoration: none; background-color: #007bff; } -.dropdown-item.disabled, .dropdown-item:disabled { +.dropdown-item.disabled, +.dropdown-item:disabled { color: #adb5bd; pointer-events: none; background-color: transparent; @@ -3461,7 +3714,9 @@ input[type="button"].btn-block { z-index: 1; } -.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, +.btn-group > .btn:focus, +.btn-group > .btn:active, +.btn-group > .btn.active, .btn-group-vertical > .btn:focus, .btn-group-vertical > .btn:active, .btn-group-vertical > .btn.active { @@ -3513,12 +3768,14 @@ input[type="button"].btn-block { margin-right: 0; } -.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { +.btn-sm + .dropdown-toggle-split, +.btn-group-sm > .btn + .dropdown-toggle-split { padding-right: 0.375rem; padding-left: 0.375rem; } -.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { +.btn-lg + .dropdown-toggle-split, +.btn-group-lg > .btn + .dropdown-toggle-split { padding-right: 0.75rem; padding-left: 0.75rem; } @@ -3642,16 +3899,24 @@ input[type="button"].btn-block { .input-group:not(.has-validation) > .form-control:not(:last-child), .input-group:not(.has-validation) > .custom-select:not(:last-child), -.input-group:not(.has-validation) > .custom-file:not(:last-child) .custom-file-label, -.input-group:not(.has-validation) > .custom-file:not(:last-child) .custom-file-label::after { +.input-group:not(.has-validation) + > .custom-file:not(:last-child) + .custom-file-label, +.input-group:not(.has-validation) + > .custom-file:not(:last-child) + .custom-file-label::after { border-top-right-radius: 0; border-bottom-right-radius: 0; } .input-group.has-validation > .form-control:nth-last-child(n + 3), .input-group.has-validation > .custom-select:nth-last-child(n + 3), -.input-group.has-validation > .custom-file:nth-last-child(n + 3) .custom-file-label, -.input-group.has-validation > .custom-file:nth-last-child(n + 3) .custom-file-label::after { +.input-group.has-validation + > .custom-file:nth-last-child(n + 3) + .custom-file-label, +.input-group.has-validation + > .custom-file:nth-last-child(n + 3) + .custom-file-label::after { border-top-right-radius: 0; border-bottom-right-radius: 0; } @@ -3757,11 +4022,19 @@ input[type="button"].btn-block { .input-group > .input-group-prepend > .btn, .input-group > .input-group-prepend > .input-group-text, .input-group:not(.has-validation) > .input-group-append:not(:last-child) > .btn, -.input-group:not(.has-validation) > .input-group-append:not(:last-child) > .input-group-text, +.input-group:not(.has-validation) + > .input-group-append:not(:last-child) + > .input-group-text, .input-group.has-validation > .input-group-append:nth-last-child(n + 3) > .btn, -.input-group.has-validation > .input-group-append:nth-last-child(n + 3) > .input-group-text, -.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), -.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) { +.input-group.has-validation + > .input-group-append:nth-last-child(n + 3) + > .input-group-text, +.input-group + > .input-group-append:last-child + > .btn:not(:last-child):not(.dropdown-toggle), +.input-group + > .input-group-append:last-child + > .input-group-text:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } @@ -3771,7 +4044,9 @@ input[type="button"].btn-block { .input-group > .input-group-prepend:not(:first-child) > .btn, .input-group > .input-group-prepend:not(:first-child) > .input-group-text, .input-group > .input-group-prepend:first-child > .btn:not(:first-child), -.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) { +.input-group + > .input-group-prepend:first-child + > .input-group-text:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } @@ -3821,11 +4096,13 @@ input[type="button"].btn-block { border-color: #b3d7ff; } -.custom-control-input[disabled] ~ .custom-control-label, .custom-control-input:disabled ~ .custom-control-label { +.custom-control-input[disabled] ~ .custom-control-label, +.custom-control-input:disabled ~ .custom-control-label { color: #6c757d; } -.custom-control-input[disabled] ~ .custom-control-label::before, .custom-control-input:disabled ~ .custom-control-label::before { +.custom-control-input[disabled] ~ .custom-control-label::before, +.custom-control-input:disabled ~ .custom-control-label::before { background-color: #e9ecef; } @@ -3867,20 +4144,28 @@ input[type="button"].btn-block { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e"); } -.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before { +.custom-checkbox + .custom-control-input:indeterminate + ~ .custom-control-label::before { border-color: #007bff; background-color: #007bff; } -.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after { +.custom-checkbox + .custom-control-input:indeterminate + ~ .custom-control-label::after { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e"); } -.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before { +.custom-checkbox + .custom-control-input:disabled:checked + ~ .custom-control-label::before { background-color: rgba(0, 123, 255, 0.5); } -.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before { +.custom-checkbox + .custom-control-input:disabled:indeterminate + ~ .custom-control-label::before { background-color: rgba(0, 123, 255, 0.5); } @@ -3892,7 +4177,9 @@ input[type="button"].btn-block { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); } -.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before { +.custom-radio + .custom-control-input:disabled:checked + ~ .custom-control-label::before { background-color: rgba(0, 123, 255, 0.5); } @@ -3914,9 +4201,22 @@ input[type="button"].btn-block { height: calc(1rem - 4px); background-color: #adb5bd; border-radius: 0.5rem; - transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out; - transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out; + transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out, + -webkit-transform 0.15s ease-in-out; + transition: + transform 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + transition: + transform 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out, + -webkit-transform 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { @@ -3931,7 +4231,9 @@ input[type="button"].btn-block { transform: translateX(0.75rem); } -.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before { +.custom-switch + .custom-control-input:disabled:checked + ~ .custom-control-label::before { background-color: rgba(0, 123, 255, 0.5); } @@ -3945,7 +4247,9 @@ input[type="button"].btn-block { line-height: 1.5; color: #495057; vertical-align: middle; - background: #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 0.75rem center/8px 10px no-repeat; + background: #fff + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") + right 0.75rem center/8px 10px no-repeat; border: 1px solid #ced4da; border-radius: 0.25rem; -webkit-appearance: none; @@ -3964,7 +4268,8 @@ input[type="button"].btn-block { background-color: #fff; } -.custom-select[multiple], .custom-select[size]:not([size="1"]) { +.custom-select[multiple], +.custom-select[size]:not([size="1"]) { height: auto; padding-right: 0.75rem; background-image: none; @@ -4085,15 +4390,21 @@ input[type="button"].btn-block { } .custom-range:focus::-webkit-slider-thumb { - box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + box-shadow: + 0 0 0 1px #fff, + 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-range:focus::-moz-range-thumb { - box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + box-shadow: + 0 0 0 1px #fff, + 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-range:focus::-ms-thumb { - box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + box-shadow: + 0 0 0 1px #fff, + 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-range::-moz-focus-outer { @@ -4107,8 +4418,14 @@ input[type="button"].btn-block { background-color: #007bff; border: 0; border-radius: 1rem; - -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + -webkit-transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; -webkit-appearance: none; appearance: none; } @@ -4140,8 +4457,14 @@ input[type="button"].btn-block { background-color: #007bff; border: 0; border-radius: 1rem; - -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + -moz-transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; -moz-appearance: none; appearance: none; } @@ -4176,8 +4499,14 @@ input[type="button"].btn-block { background-color: #007bff; border: 0; border-radius: 1rem; - -ms-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + -ms-transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; appearance: none; } @@ -4236,7 +4565,10 @@ input[type="button"].btn-block { .custom-control-label::before, .custom-file-label, .custom-select { - transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { @@ -4262,7 +4594,8 @@ input[type="button"].btn-block { padding: 0.5rem 1rem; } -.nav-link:hover, .nav-link:focus { +.nav-link:hover, +.nav-link:focus { text-decoration: none; } @@ -4283,7 +4616,8 @@ input[type="button"].btn-block { border-top-right-radius: 0.25rem; } -.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { +.nav-tabs .nav-link:hover, +.nav-tabs .nav-link:focus { border-color: #e9ecef #e9ecef #dee2e6; } @@ -4354,7 +4688,11 @@ input[type="button"].btn-block { } .navbar .container, -.navbar .container-fluid, .navbar .container-sm, .navbar .container-md, .navbar .container-lg, .navbar .container-xl { +.navbar .container-fluid, +.navbar .container-sm, +.navbar .container-md, +.navbar .container-lg, +.navbar .container-xl { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; @@ -4375,7 +4713,8 @@ input[type="button"].btn-block { white-space: nowrap; } -.navbar-brand:hover, .navbar-brand:focus { +.navbar-brand:hover, +.navbar-brand:focus { text-decoration: none; } @@ -4423,7 +4762,8 @@ input[type="button"].btn-block { border-radius: 0.25rem; } -.navbar-toggler:hover, .navbar-toggler:focus { +.navbar-toggler:hover, +.navbar-toggler:focus { text-decoration: none; } @@ -4443,7 +4783,11 @@ input[type="button"].btn-block { @media (max-width: 575.98px) { .navbar-expand-sm > .container, - .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl { + .navbar-expand-sm > .container-fluid, + .navbar-expand-sm > .container-sm, + .navbar-expand-sm > .container-md, + .navbar-expand-sm > .container-lg, + .navbar-expand-sm > .container-xl { padding-right: 0; padding-left: 0; } @@ -4468,7 +4812,11 @@ input[type="button"].btn-block { padding-left: 0.5rem; } .navbar-expand-sm > .container, - .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl { + .navbar-expand-sm > .container-fluid, + .navbar-expand-sm > .container-sm, + .navbar-expand-sm > .container-md, + .navbar-expand-sm > .container-lg, + .navbar-expand-sm > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } @@ -4488,7 +4836,11 @@ input[type="button"].btn-block { @media (max-width: 767.98px) { .navbar-expand-md > .container, - .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl { + .navbar-expand-md > .container-fluid, + .navbar-expand-md > .container-sm, + .navbar-expand-md > .container-md, + .navbar-expand-md > .container-lg, + .navbar-expand-md > .container-xl { padding-right: 0; padding-left: 0; } @@ -4513,7 +4865,11 @@ input[type="button"].btn-block { padding-left: 0.5rem; } .navbar-expand-md > .container, - .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl { + .navbar-expand-md > .container-fluid, + .navbar-expand-md > .container-sm, + .navbar-expand-md > .container-md, + .navbar-expand-md > .container-lg, + .navbar-expand-md > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } @@ -4533,7 +4889,11 @@ input[type="button"].btn-block { @media (max-width: 991.98px) { .navbar-expand-lg > .container, - .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl { + .navbar-expand-lg > .container-fluid, + .navbar-expand-lg > .container-sm, + .navbar-expand-lg > .container-md, + .navbar-expand-lg > .container-lg, + .navbar-expand-lg > .container-xl { padding-right: 0; padding-left: 0; } @@ -4558,7 +4918,11 @@ input[type="button"].btn-block { padding-left: 0.5rem; } .navbar-expand-lg > .container, - .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl { + .navbar-expand-lg > .container-fluid, + .navbar-expand-lg > .container-sm, + .navbar-expand-lg > .container-md, + .navbar-expand-lg > .container-lg, + .navbar-expand-lg > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } @@ -4578,7 +4942,11 @@ input[type="button"].btn-block { @media (max-width: 1199.98px) { .navbar-expand-xl > .container, - .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl { + .navbar-expand-xl > .container-fluid, + .navbar-expand-xl > .container-sm, + .navbar-expand-xl > .container-md, + .navbar-expand-xl > .container-lg, + .navbar-expand-xl > .container-xl { padding-right: 0; padding-left: 0; } @@ -4603,7 +4971,11 @@ input[type="button"].btn-block { padding-left: 0.5rem; } .navbar-expand-xl > .container, - .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl { + .navbar-expand-xl > .container-fluid, + .navbar-expand-xl > .container-sm, + .navbar-expand-xl > .container-md, + .navbar-expand-xl > .container-lg, + .navbar-expand-xl > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } @@ -4629,7 +5001,11 @@ input[type="button"].btn-block { } .navbar-expand > .container, -.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl { +.navbar-expand > .container-fluid, +.navbar-expand > .container-sm, +.navbar-expand > .container-md, +.navbar-expand > .container-lg, +.navbar-expand > .container-xl { padding-right: 0; padding-left: 0; } @@ -4649,7 +5025,11 @@ input[type="button"].btn-block { } .navbar-expand > .container, -.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl { +.navbar-expand > .container-fluid, +.navbar-expand > .container-sm, +.navbar-expand > .container-md, +.navbar-expand > .container-lg, +.navbar-expand > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } @@ -4673,7 +5053,8 @@ input[type="button"].btn-block { color: rgba(0, 0, 0, 0.9); } -.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus { +.navbar-light .navbar-brand:hover, +.navbar-light .navbar-brand:focus { color: rgba(0, 0, 0, 0.9); } @@ -4681,7 +5062,8 @@ input[type="button"].btn-block { color: rgba(0, 0, 0, 0.5); } -.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus { +.navbar-light .navbar-nav .nav-link:hover, +.navbar-light .navbar-nav .nav-link:focus { color: rgba(0, 0, 0, 0.7); } @@ -4713,7 +5095,8 @@ input[type="button"].btn-block { color: rgba(0, 0, 0, 0.9); } -.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus { +.navbar-light .navbar-text a:hover, +.navbar-light .navbar-text a:focus { color: rgba(0, 0, 0, 0.9); } @@ -4721,7 +5104,8 @@ input[type="button"].btn-block { color: #fff; } -.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus { +.navbar-dark .navbar-brand:hover, +.navbar-dark .navbar-brand:focus { color: #fff; } @@ -4729,7 +5113,8 @@ input[type="button"].btn-block { color: rgba(255, 255, 255, 0.5); } -.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus { +.navbar-dark .navbar-nav .nav-link:hover, +.navbar-dark .navbar-nav .nav-link:focus { color: rgba(255, 255, 255, 0.75); } @@ -4761,7 +5146,8 @@ input[type="button"].btn-block { color: #fff; } -.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus { +.navbar-dark .navbar-text a:hover, +.navbar-dark .navbar-text a:focus { color: #fff; } @@ -5146,7 +5532,11 @@ input[type="button"].btn-block { white-space: nowrap; vertical-align: baseline; border-radius: 0.25rem; - transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: + color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { @@ -5155,7 +5545,8 @@ input[type="button"].btn-block { } } -a.badge:hover, a.badge:focus { +a.badge:hover, +a.badge:focus { text-decoration: none; } @@ -5179,12 +5570,14 @@ a.badge:hover, a.badge:focus { background-color: #007bff; } -a.badge-primary:hover, a.badge-primary:focus { +a.badge-primary:hover, +a.badge-primary:focus { color: #fff; background-color: #0062cc; } -a.badge-primary:focus, a.badge-primary.focus { +a.badge-primary:focus, +a.badge-primary.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); } @@ -5194,12 +5587,14 @@ a.badge-primary:focus, a.badge-primary.focus { background-color: #6c757d; } -a.badge-secondary:hover, a.badge-secondary:focus { +a.badge-secondary:hover, +a.badge-secondary:focus { color: #fff; background-color: #545b62; } -a.badge-secondary:focus, a.badge-secondary.focus { +a.badge-secondary:focus, +a.badge-secondary.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } @@ -5209,12 +5604,14 @@ a.badge-secondary:focus, a.badge-secondary.focus { background-color: #28a745; } -a.badge-success:hover, a.badge-success:focus { +a.badge-success:hover, +a.badge-success:focus { color: #fff; background-color: #1e7e34; } -a.badge-success:focus, a.badge-success.focus { +a.badge-success:focus, +a.badge-success.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } @@ -5224,12 +5621,14 @@ a.badge-success:focus, a.badge-success.focus { background-color: #17a2b8; } -a.badge-info:hover, a.badge-info:focus { +a.badge-info:hover, +a.badge-info:focus { color: #fff; background-color: #117a8b; } -a.badge-info:focus, a.badge-info.focus { +a.badge-info:focus, +a.badge-info.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } @@ -5239,12 +5638,14 @@ a.badge-info:focus, a.badge-info.focus { background-color: #ffc107; } -a.badge-warning:hover, a.badge-warning:focus { +a.badge-warning:hover, +a.badge-warning:focus { color: #212529; background-color: #d39e00; } -a.badge-warning:focus, a.badge-warning.focus { +a.badge-warning:focus, +a.badge-warning.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); } @@ -5254,12 +5655,14 @@ a.badge-warning:focus, a.badge-warning.focus { background-color: #dc3545; } -a.badge-danger:hover, a.badge-danger:focus { +a.badge-danger:hover, +a.badge-danger:focus { color: #fff; background-color: #bd2130; } -a.badge-danger:focus, a.badge-danger.focus { +a.badge-danger:focus, +a.badge-danger.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } @@ -5269,12 +5672,14 @@ a.badge-danger:focus, a.badge-danger.focus { background-color: #f8f9fa; } -a.badge-light:hover, a.badge-light:focus { +a.badge-light:hover, +a.badge-light:focus { color: #212529; background-color: #dae0e5; } -a.badge-light:focus, a.badge-light.focus { +a.badge-light:focus, +a.badge-light.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } @@ -5284,12 +5689,14 @@ a.badge-light:focus, a.badge-light.focus { background-color: #343a40; } -a.badge-dark:hover, a.badge-dark:focus { +a.badge-dark:hover, +a.badge-dark:focus { color: #fff; background-color: #1d2124; } -a.badge-dark:focus, a.badge-dark.focus { +a.badge-dark:focus, +a.badge-dark.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } @@ -5505,7 +5912,16 @@ a.badge-dark:focus, a.badge-dark.focus { } .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-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; } @@ -5549,7 +5965,8 @@ a.badge-dark:focus, a.badge-dark.focus { text-align: inherit; } -.list-group-item-action:hover, .list-group-item-action:focus { +.list-group-item-action:hover, +.list-group-item-action:focus { z-index: 1; color: #495057; text-decoration: none; @@ -5579,7 +5996,8 @@ a.badge-dark:focus, a.badge-dark.focus { border-bottom-left-radius: inherit; } -.list-group-item.disabled, .list-group-item:disabled { +.list-group-item.disabled, +.list-group-item:disabled { color: #6c757d; pointer-events: none; background-color: #fff; @@ -5751,7 +6169,8 @@ a.badge-dark:focus, a.badge-dark.focus { background-color: #b8daff; } -.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus { +.list-group-item-primary.list-group-item-action:hover, +.list-group-item-primary.list-group-item-action:focus { color: #004085; background-color: #9fcdff; } @@ -5767,7 +6186,8 @@ a.badge-dark:focus, a.badge-dark.focus { background-color: #d6d8db; } -.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus { +.list-group-item-secondary.list-group-item-action:hover, +.list-group-item-secondary.list-group-item-action:focus { color: #383d41; background-color: #c8cbcf; } @@ -5783,7 +6203,8 @@ a.badge-dark:focus, a.badge-dark.focus { background-color: #c3e6cb; } -.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus { +.list-group-item-success.list-group-item-action:hover, +.list-group-item-success.list-group-item-action:focus { color: #155724; background-color: #b1dfbb; } @@ -5799,7 +6220,8 @@ a.badge-dark:focus, a.badge-dark.focus { background-color: #bee5eb; } -.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus { +.list-group-item-info.list-group-item-action:hover, +.list-group-item-info.list-group-item-action:focus { color: #0c5460; background-color: #abdde5; } @@ -5815,7 +6237,8 @@ a.badge-dark:focus, a.badge-dark.focus { background-color: #ffeeba; } -.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus { +.list-group-item-warning.list-group-item-action:hover, +.list-group-item-warning.list-group-item-action:focus { color: #856404; background-color: #ffe8a1; } @@ -5831,7 +6254,8 @@ a.badge-dark:focus, a.badge-dark.focus { background-color: #f5c6cb; } -.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus { +.list-group-item-danger.list-group-item-action:hover, +.list-group-item-danger.list-group-item-action:focus { color: #721c24; background-color: #f1b0b7; } @@ -5847,7 +6271,8 @@ a.badge-dark:focus, a.badge-dark.focus { background-color: #fdfdfe; } -.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus { +.list-group-item-light.list-group-item-action:hover, +.list-group-item-light.list-group-item-action:focus { color: #818182; background-color: #ececf6; } @@ -5863,7 +6288,8 @@ a.badge-dark:focus, a.badge-dark.focus { background-color: #c6c8ca; } -.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus { +.list-group-item-dark.list-group-item-action:hover, +.list-group-item-dark.list-group-item-action:focus { color: #1b1e21; background-color: #b9bbbe; } @@ -5881,7 +6307,7 @@ a.badge-dark:focus, a.badge-dark.focus { line-height: 1; color: #000; text-shadow: 0 1px 0 #fff; - opacity: .5; + opacity: 0.5; } .close:hover { @@ -5889,8 +6315,9 @@ a.badge-dark:focus, a.badge-dark.focus { text-decoration: none; } -.close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus { - opacity: .75; +.close:not(:disabled):not(.disabled):hover, +.close:not(:disabled):not(.disabled):focus { + opacity: 0.75; } button.close { @@ -5982,7 +6409,9 @@ a.close.disabled { .modal.fade .modal-dialog { transition: -webkit-transform 0.3s ease-out; transition: transform 0.3s ease-out; - transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out; + transition: + transform 0.3s ease-out, + -webkit-transform 0.3s ease-out; -webkit-transform: translate(0, -50px); transform: translate(0, -50px); } @@ -6190,7 +6619,9 @@ a.close.disabled { z-index: 1070; display: block; margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-style: normal; font-weight: 400; line-height: 1.5; @@ -6227,61 +6658,73 @@ a.close.disabled { border-style: solid; } -.bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"] { +.bs-tooltip-top, +.bs-tooltip-auto[x-placement^="top"] { padding: 0.4rem 0; } -.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^="top"] .arrow { +.bs-tooltip-top .arrow, +.bs-tooltip-auto[x-placement^="top"] .arrow { bottom: 0; } -.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^="top"] .arrow::before { +.bs-tooltip-top .arrow::before, +.bs-tooltip-auto[x-placement^="top"] .arrow::before { top: 0; border-width: 0.4rem 0.4rem 0; border-top-color: #000; } -.bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"] { +.bs-tooltip-right, +.bs-tooltip-auto[x-placement^="right"] { padding: 0 0.4rem; } -.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^="right"] .arrow { +.bs-tooltip-right .arrow, +.bs-tooltip-auto[x-placement^="right"] .arrow { left: 0; width: 0.4rem; height: 0.8rem; } -.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^="right"] .arrow::before { +.bs-tooltip-right .arrow::before, +.bs-tooltip-auto[x-placement^="right"] .arrow::before { right: 0; border-width: 0.4rem 0.4rem 0.4rem 0; border-right-color: #000; } -.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"] { +.bs-tooltip-bottom, +.bs-tooltip-auto[x-placement^="bottom"] { padding: 0.4rem 0; } -.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^="bottom"] .arrow { +.bs-tooltip-bottom .arrow, +.bs-tooltip-auto[x-placement^="bottom"] .arrow { top: 0; } -.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^="bottom"] .arrow::before { +.bs-tooltip-bottom .arrow::before, +.bs-tooltip-auto[x-placement^="bottom"] .arrow::before { bottom: 0; border-width: 0 0.4rem 0.4rem; border-bottom-color: #000; } -.bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"] { +.bs-tooltip-left, +.bs-tooltip-auto[x-placement^="left"] { padding: 0 0.4rem; } -.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^="left"] .arrow { +.bs-tooltip-left .arrow, +.bs-tooltip-auto[x-placement^="left"] .arrow { right: 0; width: 0.4rem; height: 0.8rem; } -.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^="left"] .arrow::before { +.bs-tooltip-left .arrow::before, +.bs-tooltip-auto[x-placement^="left"] .arrow::before { left: 0; border-width: 0.4rem 0 0.4rem 0.4rem; border-left-color: #000; @@ -6303,7 +6746,9 @@ a.close.disabled { z-index: 1060; display: block; max-width: 276px; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-style: normal; font-weight: 400; line-height: 1.5; @@ -6333,7 +6778,8 @@ a.close.disabled { margin: 0 0.3rem; } -.popover .arrow::before, .popover .arrow::after { +.popover .arrow::before, +.popover .arrow::after { position: absolute; display: block; content: ""; @@ -6341,70 +6787,83 @@ a.close.disabled { border-style: solid; } -.bs-popover-top, .bs-popover-auto[x-placement^="top"] { +.bs-popover-top, +.bs-popover-auto[x-placement^="top"] { margin-bottom: 0.5rem; } -.bs-popover-top > .arrow, .bs-popover-auto[x-placement^="top"] > .arrow { +.bs-popover-top > .arrow, +.bs-popover-auto[x-placement^="top"] > .arrow { bottom: calc(-0.5rem - 1px); } -.bs-popover-top > .arrow::before, .bs-popover-auto[x-placement^="top"] > .arrow::before { +.bs-popover-top > .arrow::before, +.bs-popover-auto[x-placement^="top"] > .arrow::before { bottom: 0; border-width: 0.5rem 0.5rem 0; border-top-color: rgba(0, 0, 0, 0.25); } -.bs-popover-top > .arrow::after, .bs-popover-auto[x-placement^="top"] > .arrow::after { +.bs-popover-top > .arrow::after, +.bs-popover-auto[x-placement^="top"] > .arrow::after { bottom: 1px; border-width: 0.5rem 0.5rem 0; border-top-color: #fff; } -.bs-popover-right, .bs-popover-auto[x-placement^="right"] { +.bs-popover-right, +.bs-popover-auto[x-placement^="right"] { margin-left: 0.5rem; } -.bs-popover-right > .arrow, .bs-popover-auto[x-placement^="right"] > .arrow { +.bs-popover-right > .arrow, +.bs-popover-auto[x-placement^="right"] > .arrow { left: calc(-0.5rem - 1px); width: 0.5rem; height: 1rem; margin: 0.3rem 0; } -.bs-popover-right > .arrow::before, .bs-popover-auto[x-placement^="right"] > .arrow::before { +.bs-popover-right > .arrow::before, +.bs-popover-auto[x-placement^="right"] > .arrow::before { left: 0; border-width: 0.5rem 0.5rem 0.5rem 0; border-right-color: rgba(0, 0, 0, 0.25); } -.bs-popover-right > .arrow::after, .bs-popover-auto[x-placement^="right"] > .arrow::after { +.bs-popover-right > .arrow::after, +.bs-popover-auto[x-placement^="right"] > .arrow::after { left: 1px; border-width: 0.5rem 0.5rem 0.5rem 0; border-right-color: #fff; } -.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"] { +.bs-popover-bottom, +.bs-popover-auto[x-placement^="bottom"] { margin-top: 0.5rem; } -.bs-popover-bottom > .arrow, .bs-popover-auto[x-placement^="bottom"] > .arrow { +.bs-popover-bottom > .arrow, +.bs-popover-auto[x-placement^="bottom"] > .arrow { top: calc(-0.5rem - 1px); } -.bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^="bottom"] > .arrow::before { +.bs-popover-bottom > .arrow::before, +.bs-popover-auto[x-placement^="bottom"] > .arrow::before { top: 0; border-width: 0 0.5rem 0.5rem 0.5rem; border-bottom-color: rgba(0, 0, 0, 0.25); } -.bs-popover-bottom > .arrow::after, .bs-popover-auto[x-placement^="bottom"] > .arrow::after { +.bs-popover-bottom > .arrow::after, +.bs-popover-auto[x-placement^="bottom"] > .arrow::after { top: 1px; border-width: 0 0.5rem 0.5rem 0.5rem; border-bottom-color: #fff; } -.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^="bottom"] .popover-header::before { +.bs-popover-bottom .popover-header::before, +.bs-popover-auto[x-placement^="bottom"] .popover-header::before { position: absolute; top: 0; left: 50%; @@ -6415,24 +6874,28 @@ a.close.disabled { border-bottom: 1px solid #f7f7f7; } -.bs-popover-left, .bs-popover-auto[x-placement^="left"] { +.bs-popover-left, +.bs-popover-auto[x-placement^="left"] { margin-right: 0.5rem; } -.bs-popover-left > .arrow, .bs-popover-auto[x-placement^="left"] > .arrow { +.bs-popover-left > .arrow, +.bs-popover-auto[x-placement^="left"] > .arrow { right: calc(-0.5rem - 1px); width: 0.5rem; height: 1rem; margin: 0.3rem 0; } -.bs-popover-left > .arrow::before, .bs-popover-auto[x-placement^="left"] > .arrow::before { +.bs-popover-left > .arrow::before, +.bs-popover-auto[x-placement^="left"] > .arrow::before { right: 0; border-width: 0.5rem 0 0.5rem 0.5rem; border-left-color: rgba(0, 0, 0, 0.25); } -.bs-popover-left > .arrow::after, .bs-popover-auto[x-placement^="left"] > .arrow::after { +.bs-popover-left > .arrow::after, +.bs-popover-auto[x-placement^="left"] > .arrow::after { right: 1px; border-width: 0.5rem 0 0.5rem 0.5rem; border-left-color: #fff; @@ -6488,7 +6951,9 @@ a.close.disabled { backface-visibility: hidden; transition: -webkit-transform 0.6s ease-in-out; transition: transform 0.6s ease-in-out; - transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out; + transition: + transform 0.6s ease-in-out, + -webkit-transform 0.6s ease-in-out; } @media (prefers-reduced-motion: reduce) { @@ -6572,7 +7037,8 @@ a.close.disabled { } } -.carousel-control-prev:hover, .carousel-control-prev:focus, +.carousel-control-prev:hover, +.carousel-control-prev:focus, .carousel-control-next:hover, .carousel-control-next:focus { color: #fff; @@ -6635,7 +7101,7 @@ a.close.disabled { background-clip: padding-box; border-top: 10px solid transparent; border-bottom: 10px solid transparent; - opacity: .5; + opacity: 0.5; transition: opacity 0.6s ease; } @@ -6683,8 +7149,8 @@ a.close.disabled { border: 0.25em solid currentColor; border-right-color: transparent; border-radius: 50%; - -webkit-animation: .75s linear infinite spinner-border; - animation: .75s linear infinite spinner-border; + -webkit-animation: 0.75s linear infinite spinner-border; + animation: 0.75s linear infinite spinner-border; } .spinner-border-sm { @@ -6725,8 +7191,8 @@ a.close.disabled { background-color: currentColor; border-radius: 50%; opacity: 0; - -webkit-animation: .75s linear infinite spinner-grow; - animation: .75s linear infinite spinner-grow; + -webkit-animation: 0.75s linear infinite spinner-grow; + animation: 0.75s linear infinite spinner-grow; } .spinner-grow-sm { @@ -6770,7 +7236,8 @@ a.close.disabled { background-color: #007bff !important; } -a.bg-primary:hover, a.bg-primary:focus, +a.bg-primary:hover, +a.bg-primary:focus, button.bg-primary:hover, button.bg-primary:focus { background-color: #0062cc !important; @@ -6780,7 +7247,8 @@ button.bg-primary:focus { background-color: #6c757d !important; } -a.bg-secondary:hover, a.bg-secondary:focus, +a.bg-secondary:hover, +a.bg-secondary:focus, button.bg-secondary:hover, button.bg-secondary:focus { background-color: #545b62 !important; @@ -6790,7 +7258,8 @@ button.bg-secondary:focus { background-color: #28a745 !important; } -a.bg-success:hover, a.bg-success:focus, +a.bg-success:hover, +a.bg-success:focus, button.bg-success:hover, button.bg-success:focus { background-color: #1e7e34 !important; @@ -6800,7 +7269,8 @@ button.bg-success:focus { background-color: #17a2b8 !important; } -a.bg-info:hover, a.bg-info:focus, +a.bg-info:hover, +a.bg-info:focus, button.bg-info:hover, button.bg-info:focus { background-color: #117a8b !important; @@ -6810,7 +7280,8 @@ button.bg-info:focus { background-color: #ffc107 !important; } -a.bg-warning:hover, a.bg-warning:focus, +a.bg-warning:hover, +a.bg-warning:focus, button.bg-warning:hover, button.bg-warning:focus { background-color: #d39e00 !important; @@ -6820,7 +7291,8 @@ button.bg-warning:focus { background-color: #dc3545 !important; } -a.bg-danger:hover, a.bg-danger:focus, +a.bg-danger:hover, +a.bg-danger:focus, button.bg-danger:hover, button.bg-danger:focus { background-color: #bd2130 !important; @@ -6830,7 +7302,8 @@ button.bg-danger:focus { background-color: #f8f9fa !important; } -a.bg-light:hover, a.bg-light:focus, +a.bg-light:hover, +a.bg-light:focus, button.bg-light:hover, button.bg-light:focus { background-color: #dae0e5 !important; @@ -6840,7 +7313,8 @@ button.bg-light:focus { background-color: #343a40 !important; } -a.bg-dark:hover, a.bg-dark:focus, +a.bg-dark:hover, +a.bg-dark:focus, button.bg-dark:hover, button.bg-dark:focus { background-color: #1d2124 !important; @@ -8093,7 +8567,8 @@ button.bg-dark:focus { border: 0; } -.sr-only-focusable:active, .sr-only-focusable:focus { +.sr-only-focusable:active, +.sr-only-focusable:focus { position: static; width: auto; height: auto; @@ -10007,7 +10482,8 @@ button.bg-dark:focus { } .text-monospace { - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace !important; } .text-justify { @@ -10132,7 +10608,8 @@ button.bg-dark:focus { color: #007bff !important; } -a.text-primary:hover, a.text-primary:focus { +a.text-primary:hover, +a.text-primary:focus { color: #0056b3 !important; } @@ -10140,7 +10617,8 @@ a.text-primary:hover, a.text-primary:focus { color: #6c757d !important; } -a.text-secondary:hover, a.text-secondary:focus { +a.text-secondary:hover, +a.text-secondary:focus { color: #494f54 !important; } @@ -10148,7 +10626,8 @@ a.text-secondary:hover, a.text-secondary:focus { color: #28a745 !important; } -a.text-success:hover, a.text-success:focus { +a.text-success:hover, +a.text-success:focus { color: #19692c !important; } @@ -10156,7 +10635,8 @@ a.text-success:hover, a.text-success:focus { color: #17a2b8 !important; } -a.text-info:hover, a.text-info:focus { +a.text-info:hover, +a.text-info:focus { color: #0f6674 !important; } @@ -10164,7 +10644,8 @@ a.text-info:hover, a.text-info:focus { color: #ffc107 !important; } -a.text-warning:hover, a.text-warning:focus { +a.text-warning:hover, +a.text-warning:focus { color: #ba8b00 !important; } @@ -10172,7 +10653,8 @@ a.text-warning:hover, a.text-warning:focus { color: #dc3545 !important; } -a.text-danger:hover, a.text-danger:focus { +a.text-danger:hover, +a.text-danger:focus { color: #a71d2a !important; } @@ -10180,7 +10662,8 @@ a.text-danger:hover, a.text-danger:focus { color: #f8f9fa !important; } -a.text-light:hover, a.text-light:focus { +a.text-light:hover, +a.text-light:focus { color: #cbd3da !important; } @@ -10188,7 +10671,8 @@ a.text-light:hover, a.text-light:focus { color: #343a40 !important; } -a.text-dark:hover, a.text-dark:focus { +a.text-dark:hover, +a.text-dark:focus { color: #121416 !important; } @@ -10312,4 +10796,4 @@ a.text-dark:hover, a.text-dark:focus { border-color: #dee2e6; } } -/*# sourceMappingURL=bootstrap.css.map */ \ No newline at end of file +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/public/css/bootstrap.min.css b/public/css/bootstrap.min.css index 6ee59568..86fbc94f 100644 --- a/public/css/bootstrap.min.css +++ b/public/css/bootstrap.min.css @@ -3,5 +3,9587 @@ * Copyright 2011-2021 The Bootstrap Authors * Copyright 2011-2021 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.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:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.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:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;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::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem)!important;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' 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(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated select.form-control:valid,select.form-control.is-valid{padding-right:3rem!important;background-position:right 1.5rem center}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem)!important;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' 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") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem)!important;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%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(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated select.form-control:invalid,select.form-control.is-invalid{padding-right:3rem!important;background-position:right 1.5rem center}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem)!important;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%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") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;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:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{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 transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;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-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{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 transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label,.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label::after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label,.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label::after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;z-index:1;display:block;min-height:1.5rem;padding-left:1.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;overflow:hidden;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;overflow:hidden;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{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{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item,.nav-fill>.nav-link{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;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;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:50%/100% 100% no-repeat}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;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>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;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>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;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>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;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>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;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>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;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(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;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-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{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-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;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}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.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:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;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){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;z-index:2;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;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,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;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:#007bff;border-color:#007bff}.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{-ms-flex-direction:row;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{-ms-flex-direction:row;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{-ms-flex-direction:row;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{-ms-flex-direction:row;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{-ms-flex-direction:row;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}}.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-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{-ms-flex-preferred-size:350px;flex-basis:350px;max-width:350px;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .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(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;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:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.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}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";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:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;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;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";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:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;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%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:50%/100% 100% no-repeat}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.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}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{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-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.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}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!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}.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}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file + */ +:root { + --blue: #007bff; + --indigo: #6610f2; + --purple: #6f42c1; + --pink: #e83e8c; + --red: #dc3545; + --orange: #fd7e14; + --yellow: #ffc107; + --green: #28a745; + --teal: #20c997; + --cyan: #17a2b8; + --white: #fff; + --gray: #6c757d; + --gray-dark: #343a40; + --primary: #007bff; + --secondary: #6c757d; + --success: #28a745; + --info: #17a2b8; + --warning: #ffc107; + --danger: #dc3545; + --light: #f8f9fa; + --dark: #343a40; + --breakpoint-xs: 0; + --breakpoint-sm: 576px; + --breakpoint-md: 768px; + --breakpoint-lg: 992px; + --breakpoint-xl: 1200px; + --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", + Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; +} +*, +::after, +::before { + box-sizing: border-box; +} +html { + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; +} +article, +aside, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section { + display: block; +} +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; +} +[tabindex="-1"]:focus:not(:focus-visible) { + outline: 0 !important; +} +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 0; + margin-bottom: 0.5rem; +} +p { + margin-top: 0; + margin-bottom: 1rem; +} +abbr[data-original-title], +abbr[title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; + -webkit-text-decoration-skip-ink: none; + text-decoration-skip-ink: none; +} +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} +dl, +ol, +ul { + margin-top: 0; + margin-bottom: 1rem; +} +ol ol, +ol ul, +ul ol, +ul ul { + margin-bottom: 0; +} +dt { + font-weight: 700; +} +dd { + margin-bottom: 0.5rem; + margin-left: 0; +} +blockquote { + margin: 0 0 1rem; +} +b, +strong { + font-weight: bolder; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} +a { + color: #007bff; + text-decoration: none; + background-color: transparent; +} +a:hover { + color: #0056b3; + text-decoration: underline; +} +a:not([href]):not([class]) { + color: inherit; + text-decoration: none; +} +a:not([href]):not([class]):hover { + color: inherit; + text-decoration: none; +} +code, +kbd, +pre, +samp { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + font-size: 1em; +} +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + -ms-overflow-style: scrollbar; +} +figure { + margin: 0 0 1rem; +} +img { + vertical-align: middle; + border-style: none; +} +svg { + overflow: hidden; + vertical-align: middle; +} +table { + border-collapse: collapse; +} +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #6c757d; + text-align: left; + caption-side: bottom; +} +th { + text-align: inherit; + text-align: -webkit-match-parent; +} +label { + display: inline-block; + margin-bottom: 0.5rem; +} +button { + border-radius: 0; +} +button:focus:not(:focus-visible) { + outline: 0; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +button, +input { + overflow: visible; +} +button, +select { + text-transform: none; +} +[role="button"] { + cursor: pointer; +} +select { + word-wrap: normal; +} +[type="button"], +[type="reset"], +[type="submit"], +button { + -webkit-appearance: button; +} +[type="button"]:not(:disabled), +[type="reset"]:not(:disabled), +[type="submit"]:not(:disabled), +button:not(:disabled) { + cursor: pointer; +} +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner, +button::-moz-focus-inner { + padding: 0; + border-style: none; +} +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + padding: 0; +} +textarea { + overflow: auto; + resize: vertical; +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: 0.5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal; +} +progress { + vertical-align: baseline; +} +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} +[type="search"] { + outline-offset: -2px; + -webkit-appearance: none; +} +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} +output { + display: inline-block; +} +summary { + display: list-item; + cursor: pointer; +} +template { + display: none; +} +[hidden] { + display: none !important; +} +.h1, +.h2, +.h3, +.h4, +.h5, +.h6, +h1, +h2, +h3, +h4, +h5, +h6 { + margin-bottom: 0.5rem; + font-weight: 500; + line-height: 1.2; +} +.h1, +h1 { + font-size: 2.5rem; +} +.h2, +h2 { + font-size: 2rem; +} +.h3, +h3 { + font-size: 1.75rem; +} +.h4, +h4 { + font-size: 1.5rem; +} +.h5, +h5 { + font-size: 1.25rem; +} +.h6, +h6 { + font-size: 1rem; +} +.lead { + font-size: 1.25rem; + font-weight: 300; +} +.display-1 { + font-size: 6rem; + font-weight: 300; + line-height: 1.2; +} +.display-2 { + font-size: 5.5rem; + font-weight: 300; + line-height: 1.2; +} +.display-3 { + font-size: 4.5rem; + font-weight: 300; + line-height: 1.2; +} +.display-4 { + font-size: 3.5rem; + font-weight: 300; + line-height: 1.2; +} +hr { + margin-top: 1rem; + margin-bottom: 1rem; + border: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} +.small, +small { + font-size: 80%; + font-weight: 400; +} +.mark, +mark { + padding: 0.2em; + background-color: #fcf8e3; +} +.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: 0.5rem; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem; +} +.blockquote-footer { + display: block; + font-size: 80%; + color: #6c757d; +} +.blockquote-footer::before { + content: "\2014\00A0"; +} +.img-fluid { + max-width: 100%; + height: auto; +} +.img-thumbnail { + padding: 0.25rem; + background-color: #fff; + border: 1px solid #dee2e6; + border-radius: 0.25rem; + max-width: 100%; + height: auto; +} +.figure { + display: inline-block; +} +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; +} +.figure-caption { + font-size: 90%; + color: #6c757d; +} +code { + font-size: 87.5%; + color: #e83e8c; + word-wrap: break-word; +} +a > code { + color: inherit; +} +kbd { + padding: 0.2rem 0.4rem; + font-size: 87.5%; + color: #fff; + background-color: #212529; + border-radius: 0.2rem; +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 700; +} +pre { + display: block; + font-size: 87.5%; + color: #212529; +} +pre code { + font-size: inherit; + color: inherit; + word-break: normal; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container, +.container-fluid, +.container-lg, +.container-md, +.container-sm, +.container-xl { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 576px) { + .container, + .container-sm { + max-width: 540px; + } +} +@media (min-width: 768px) { + .container, + .container-md, + .container-sm { + max-width: 720px; + } +} +@media (min-width: 992px) { + .container, + .container-lg, + .container-md, + .container-sm { + max-width: 960px; + } +} +@media (min-width: 1200px) { + .container, + .container-lg, + .container-md, + .container-sm, + .container-xl { + max-width: 1140px; + } +} +.row { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; +} +.no-gutters { + margin-right: 0; + margin-left: 0; +} +.no-gutters > .col, +.no-gutters > [class*="col-"] { + padding-right: 0; + padding-left: 0; +} +.col, +.col-1, +.col-10, +.col-11, +.col-12, +.col-2, +.col-3, +.col-4, +.col-5, +.col-6, +.col-7, +.col-8, +.col-9, +.col-auto, +.col-lg, +.col-lg-1, +.col-lg-10, +.col-lg-11, +.col-lg-12, +.col-lg-2, +.col-lg-3, +.col-lg-4, +.col-lg-5, +.col-lg-6, +.col-lg-7, +.col-lg-8, +.col-lg-9, +.col-lg-auto, +.col-md, +.col-md-1, +.col-md-10, +.col-md-11, +.col-md-12, +.col-md-2, +.col-md-3, +.col-md-4, +.col-md-5, +.col-md-6, +.col-md-7, +.col-md-8, +.col-md-9, +.col-md-auto, +.col-sm, +.col-sm-1, +.col-sm-10, +.col-sm-11, +.col-sm-12, +.col-sm-2, +.col-sm-3, +.col-sm-4, +.col-sm-5, +.col-sm-6, +.col-sm-7, +.col-sm-8, +.col-sm-9, +.col-sm-auto, +.col-xl, +.col-xl-1, +.col-xl-10, +.col-xl-11, +.col-xl-12, +.col-xl-2, +.col-xl-3, +.col-xl-4, +.col-xl-5, +.col-xl-6, +.col-xl-7, +.col-xl-8, +.col-xl-9, +.col-xl-auto { + position: relative; + width: 100%; + padding-right: 15px; + padding-left: 15px; +} +.col { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; +} +.row-cols-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} +.row-cols-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} +.row-cols-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} +.row-cols-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} +.row-cols-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; +} +.row-cols-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; +} +.col-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; +} +.col-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; +} +.col-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; +} +.col-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} +.col-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} +.col-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; +} +.col-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} +.col-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; +} +.col-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; +} +.col-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; +} +.col-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; +} +.col-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; +} +.col-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} +.order-first { + -ms-flex-order: -1; + order: -1; +} +.order-last { + -ms-flex-order: 13; + order: 13; +} +.order-0 { + -ms-flex-order: 0; + order: 0; +} +.order-1 { + -ms-flex-order: 1; + order: 1; +} +.order-2 { + -ms-flex-order: 2; + order: 2; +} +.order-3 { + -ms-flex-order: 3; + order: 3; +} +.order-4 { + -ms-flex-order: 4; + order: 4; +} +.order-5 { + -ms-flex-order: 5; + order: 5; +} +.order-6 { + -ms-flex-order: 6; + order: 6; +} +.order-7 { + -ms-flex-order: 7; + order: 7; +} +.order-8 { + -ms-flex-order: 8; + order: 8; +} +.order-9 { + -ms-flex-order: 9; + order: 9; +} +.order-10 { + -ms-flex-order: 10; + order: 10; +} +.order-11 { + -ms-flex-order: 11; + order: 11; +} +.order-12 { + -ms-flex-order: 12; + order: 12; +} +.offset-1 { + margin-left: 8.333333%; +} +.offset-2 { + margin-left: 16.666667%; +} +.offset-3 { + margin-left: 25%; +} +.offset-4 { + margin-left: 33.333333%; +} +.offset-5 { + margin-left: 41.666667%; +} +.offset-6 { + margin-left: 50%; +} +.offset-7 { + margin-left: 58.333333%; +} +.offset-8 { + margin-left: 66.666667%; +} +.offset-9 { + margin-left: 75%; +} +.offset-10 { + margin-left: 83.333333%; +} +.offset-11 { + margin-left: 91.666667%; +} +@media (min-width: 576px) { + .col-sm { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-sm-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-sm-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-sm-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-sm-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-sm-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-sm-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-sm-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-sm-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-sm-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-sm-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-sm-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-sm-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-sm-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-sm-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-sm-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-sm-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-sm-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-sm-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-sm-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-sm-first { + -ms-flex-order: -1; + order: -1; + } + .order-sm-last { + -ms-flex-order: 13; + order: 13; + } + .order-sm-0 { + -ms-flex-order: 0; + order: 0; + } + .order-sm-1 { + -ms-flex-order: 1; + order: 1; + } + .order-sm-2 { + -ms-flex-order: 2; + order: 2; + } + .order-sm-3 { + -ms-flex-order: 3; + order: 3; + } + .order-sm-4 { + -ms-flex-order: 4; + order: 4; + } + .order-sm-5 { + -ms-flex-order: 5; + order: 5; + } + .order-sm-6 { + -ms-flex-order: 6; + order: 6; + } + .order-sm-7 { + -ms-flex-order: 7; + order: 7; + } + .order-sm-8 { + -ms-flex-order: 8; + order: 8; + } + .order-sm-9 { + -ms-flex-order: 9; + order: 9; + } + .order-sm-10 { + -ms-flex-order: 10; + order: 10; + } + .order-sm-11 { + -ms-flex-order: 11; + order: 11; + } + .order-sm-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-sm-0 { + margin-left: 0; + } + .offset-sm-1 { + margin-left: 8.333333%; + } + .offset-sm-2 { + margin-left: 16.666667%; + } + .offset-sm-3 { + margin-left: 25%; + } + .offset-sm-4 { + margin-left: 33.333333%; + } + .offset-sm-5 { + margin-left: 41.666667%; + } + .offset-sm-6 { + margin-left: 50%; + } + .offset-sm-7 { + margin-left: 58.333333%; + } + .offset-sm-8 { + margin-left: 66.666667%; + } + .offset-sm-9 { + margin-left: 75%; + } + .offset-sm-10 { + margin-left: 83.333333%; + } + .offset-sm-11 { + margin-left: 91.666667%; + } +} +@media (min-width: 768px) { + .col-md { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-md-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-md-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-md-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-md-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-md-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-md-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-md-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-md-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-md-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-md-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-md-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-md-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-md-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-md-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-md-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-md-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-md-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-md-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-md-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-md-first { + -ms-flex-order: -1; + order: -1; + } + .order-md-last { + -ms-flex-order: 13; + order: 13; + } + .order-md-0 { + -ms-flex-order: 0; + order: 0; + } + .order-md-1 { + -ms-flex-order: 1; + order: 1; + } + .order-md-2 { + -ms-flex-order: 2; + order: 2; + } + .order-md-3 { + -ms-flex-order: 3; + order: 3; + } + .order-md-4 { + -ms-flex-order: 4; + order: 4; + } + .order-md-5 { + -ms-flex-order: 5; + order: 5; + } + .order-md-6 { + -ms-flex-order: 6; + order: 6; + } + .order-md-7 { + -ms-flex-order: 7; + order: 7; + } + .order-md-8 { + -ms-flex-order: 8; + order: 8; + } + .order-md-9 { + -ms-flex-order: 9; + order: 9; + } + .order-md-10 { + -ms-flex-order: 10; + order: 10; + } + .order-md-11 { + -ms-flex-order: 11; + order: 11; + } + .order-md-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-md-0 { + margin-left: 0; + } + .offset-md-1 { + margin-left: 8.333333%; + } + .offset-md-2 { + margin-left: 16.666667%; + } + .offset-md-3 { + margin-left: 25%; + } + .offset-md-4 { + margin-left: 33.333333%; + } + .offset-md-5 { + margin-left: 41.666667%; + } + .offset-md-6 { + margin-left: 50%; + } + .offset-md-7 { + margin-left: 58.333333%; + } + .offset-md-8 { + margin-left: 66.666667%; + } + .offset-md-9 { + margin-left: 75%; + } + .offset-md-10 { + margin-left: 83.333333%; + } + .offset-md-11 { + margin-left: 91.666667%; + } +} +@media (min-width: 992px) { + .col-lg { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-lg-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-lg-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-lg-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-lg-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-lg-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-lg-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-lg-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-lg-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-lg-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-lg-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-lg-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-lg-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-lg-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-lg-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-lg-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-lg-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-lg-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-lg-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-lg-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-lg-first { + -ms-flex-order: -1; + order: -1; + } + .order-lg-last { + -ms-flex-order: 13; + order: 13; + } + .order-lg-0 { + -ms-flex-order: 0; + order: 0; + } + .order-lg-1 { + -ms-flex-order: 1; + order: 1; + } + .order-lg-2 { + -ms-flex-order: 2; + order: 2; + } + .order-lg-3 { + -ms-flex-order: 3; + order: 3; + } + .order-lg-4 { + -ms-flex-order: 4; + order: 4; + } + .order-lg-5 { + -ms-flex-order: 5; + order: 5; + } + .order-lg-6 { + -ms-flex-order: 6; + order: 6; + } + .order-lg-7 { + -ms-flex-order: 7; + order: 7; + } + .order-lg-8 { + -ms-flex-order: 8; + order: 8; + } + .order-lg-9 { + -ms-flex-order: 9; + order: 9; + } + .order-lg-10 { + -ms-flex-order: 10; + order: 10; + } + .order-lg-11 { + -ms-flex-order: 11; + order: 11; + } + .order-lg-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-lg-0 { + margin-left: 0; + } + .offset-lg-1 { + margin-left: 8.333333%; + } + .offset-lg-2 { + margin-left: 16.666667%; + } + .offset-lg-3 { + margin-left: 25%; + } + .offset-lg-4 { + margin-left: 33.333333%; + } + .offset-lg-5 { + margin-left: 41.666667%; + } + .offset-lg-6 { + margin-left: 50%; + } + .offset-lg-7 { + margin-left: 58.333333%; + } + .offset-lg-8 { + margin-left: 66.666667%; + } + .offset-lg-9 { + margin-left: 75%; + } + .offset-lg-10 { + margin-left: 83.333333%; + } + .offset-lg-11 { + margin-left: 91.666667%; + } +} +@media (min-width: 1200px) { + .col-xl { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-xl-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-xl-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-xl-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-xl-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-xl-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-xl-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-xl-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-xl-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-xl-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-xl-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-xl-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-xl-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-xl-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-xl-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-xl-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-xl-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-xl-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-xl-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-xl-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-xl-first { + -ms-flex-order: -1; + order: -1; + } + .order-xl-last { + -ms-flex-order: 13; + order: 13; + } + .order-xl-0 { + -ms-flex-order: 0; + order: 0; + } + .order-xl-1 { + -ms-flex-order: 1; + order: 1; + } + .order-xl-2 { + -ms-flex-order: 2; + order: 2; + } + .order-xl-3 { + -ms-flex-order: 3; + order: 3; + } + .order-xl-4 { + -ms-flex-order: 4; + order: 4; + } + .order-xl-5 { + -ms-flex-order: 5; + order: 5; + } + .order-xl-6 { + -ms-flex-order: 6; + order: 6; + } + .order-xl-7 { + -ms-flex-order: 7; + order: 7; + } + .order-xl-8 { + -ms-flex-order: 8; + order: 8; + } + .order-xl-9 { + -ms-flex-order: 9; + order: 9; + } + .order-xl-10 { + -ms-flex-order: 10; + order: 10; + } + .order-xl-11 { + -ms-flex-order: 11; + order: 11; + } + .order-xl-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-xl-0 { + margin-left: 0; + } + .offset-xl-1 { + margin-left: 8.333333%; + } + .offset-xl-2 { + margin-left: 16.666667%; + } + .offset-xl-3 { + margin-left: 25%; + } + .offset-xl-4 { + margin-left: 33.333333%; + } + .offset-xl-5 { + margin-left: 41.666667%; + } + .offset-xl-6 { + margin-left: 50%; + } + .offset-xl-7 { + margin-left: 58.333333%; + } + .offset-xl-8 { + margin-left: 66.666667%; + } + .offset-xl-9 { + margin-left: 75%; + } + .offset-xl-10 { + margin-left: 83.333333%; + } + .offset-xl-11 { + margin-left: 91.666667%; + } +} +.table { + width: 100%; + margin-bottom: 1rem; + color: #212529; +} +.table td, +.table th { + padding: 0.75rem; + vertical-align: top; + border-top: 1px solid #dee2e6; +} +.table thead th { + vertical-align: bottom; + border-bottom: 2px solid #dee2e6; +} +.table tbody + tbody { + border-top: 2px solid #dee2e6; +} +.table-sm td, +.table-sm th { + padding: 0.3rem; +} +.table-bordered { + border: 1px solid #dee2e6; +} +.table-bordered td, +.table-bordered th { + border: 1px solid #dee2e6; +} +.table-bordered thead td, +.table-bordered thead th { + border-bottom-width: 2px; +} +.table-borderless tbody + tbody, +.table-borderless td, +.table-borderless th, +.table-borderless thead th { + border: 0; +} +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, 0.05); +} +.table-hover tbody tr:hover { + color: #212529; + background-color: rgba(0, 0, 0, 0.075); +} +.table-primary, +.table-primary > td, +.table-primary > th { + background-color: #b8daff; +} +.table-primary tbody + tbody, +.table-primary td, +.table-primary th, +.table-primary thead th { + border-color: #7abaff; +} +.table-hover .table-primary:hover { + background-color: #9fcdff; +} +.table-hover .table-primary:hover > td, +.table-hover .table-primary:hover > th { + background-color: #9fcdff; +} +.table-secondary, +.table-secondary > td, +.table-secondary > th { + background-color: #d6d8db; +} +.table-secondary tbody + tbody, +.table-secondary td, +.table-secondary th, +.table-secondary thead th { + border-color: #b3b7bb; +} +.table-hover .table-secondary:hover { + background-color: #c8cbcf; +} +.table-hover .table-secondary:hover > td, +.table-hover .table-secondary:hover > th { + background-color: #c8cbcf; +} +.table-success, +.table-success > td, +.table-success > th { + background-color: #c3e6cb; +} +.table-success tbody + tbody, +.table-success td, +.table-success th, +.table-success thead th { + border-color: #8fd19e; +} +.table-hover .table-success:hover { + background-color: #b1dfbb; +} +.table-hover .table-success:hover > td, +.table-hover .table-success:hover > th { + background-color: #b1dfbb; +} +.table-info, +.table-info > td, +.table-info > th { + background-color: #bee5eb; +} +.table-info tbody + tbody, +.table-info td, +.table-info th, +.table-info thead th { + border-color: #86cfda; +} +.table-hover .table-info:hover { + background-color: #abdde5; +} +.table-hover .table-info:hover > td, +.table-hover .table-info:hover > th { + background-color: #abdde5; +} +.table-warning, +.table-warning > td, +.table-warning > th { + background-color: #ffeeba; +} +.table-warning tbody + tbody, +.table-warning td, +.table-warning th, +.table-warning thead th { + border-color: #ffdf7e; +} +.table-hover .table-warning:hover { + background-color: #ffe8a1; +} +.table-hover .table-warning:hover > td, +.table-hover .table-warning:hover > th { + background-color: #ffe8a1; +} +.table-danger, +.table-danger > td, +.table-danger > th { + background-color: #f5c6cb; +} +.table-danger tbody + tbody, +.table-danger td, +.table-danger th, +.table-danger thead th { + border-color: #ed969e; +} +.table-hover .table-danger:hover { + background-color: #f1b0b7; +} +.table-hover .table-danger:hover > td, +.table-hover .table-danger:hover > th { + background-color: #f1b0b7; +} +.table-light, +.table-light > td, +.table-light > th { + background-color: #fdfdfe; +} +.table-light tbody + tbody, +.table-light td, +.table-light th, +.table-light thead th { + border-color: #fbfcfc; +} +.table-hover .table-light:hover { + background-color: #ececf6; +} +.table-hover .table-light:hover > td, +.table-hover .table-light:hover > th { + background-color: #ececf6; +} +.table-dark, +.table-dark > td, +.table-dark > th { + background-color: #c6c8ca; +} +.table-dark tbody + tbody, +.table-dark td, +.table-dark th, +.table-dark thead th { + border-color: #95999c; +} +.table-hover .table-dark:hover { + background-color: #b9bbbe; +} +.table-hover .table-dark:hover > td, +.table-hover .table-dark:hover > th { + background-color: #b9bbbe; +} +.table-active, +.table-active > td, +.table-active > th { + background-color: rgba(0, 0, 0, 0.075); +} +.table-hover .table-active:hover { + background-color: rgba(0, 0, 0, 0.075); +} +.table-hover .table-active:hover > td, +.table-hover .table-active:hover > th { + background-color: rgba(0, 0, 0, 0.075); +} +.table .thead-dark th { + color: #fff; + background-color: #343a40; + border-color: #454d55; +} +.table .thead-light th { + color: #495057; + background-color: #e9ecef; + border-color: #dee2e6; +} +.table-dark { + color: #fff; + background-color: #343a40; +} +.table-dark td, +.table-dark th, +.table-dark thead th { + border-color: #454d55; +} +.table-dark.table-bordered { + border: 0; +} +.table-dark.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(255, 255, 255, 0.05); +} +.table-dark.table-hover tbody tr:hover { + color: #fff; + background-color: rgba(255, 255, 255, 0.075); +} +@media (max-width: 575.98px) { + .table-responsive-sm { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .table-responsive-sm > .table-bordered { + border: 0; + } +} +@media (max-width: 767.98px) { + .table-responsive-md { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .table-responsive-md > .table-bordered { + border: 0; + } +} +@media (max-width: 991.98px) { + .table-responsive-lg { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .table-responsive-lg > .table-bordered { + border: 0; + } +} +@media (max-width: 1199.98px) { + .table-responsive-xl { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .table-responsive-xl > .table-bordered { + border: 0; + } +} +.table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} +.table-responsive > .table-bordered { + border: 0; +} +.form-control { + display: block; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + padding: 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ced4da; + border-radius: 0.25rem; + transition: + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-control { + transition: none; + } +} +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} +.form-control:focus { + color: #495057; + background-color: #fff; + border-color: #80bdff; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.form-control::-webkit-input-placeholder { + color: #6c757d; + opacity: 1; +} +.form-control::-moz-placeholder { + color: #6c757d; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #6c757d; + opacity: 1; +} +.form-control::-ms-input-placeholder { + color: #6c757d; + opacity: 1; +} +.form-control::placeholder { + color: #6c757d; + opacity: 1; +} +.form-control:disabled, +.form-control[readonly] { + background-color: #e9ecef; + opacity: 1; +} +input[type="date"].form-control, +input[type="datetime-local"].form-control, +input[type="month"].form-control, +input[type="time"].form-control { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +select.form-control:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #495057; +} +select.form-control:focus::-ms-value { + color: #495057; + background-color: #fff; +} +.form-control-file, +.form-control-range { + display: block; + width: 100%; +} +.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; + line-height: 1.5; +} +.col-form-label-sm { + padding-top: calc(0.25rem + 1px); + padding-bottom: calc(0.25rem + 1px); + font-size: 0.875rem; + line-height: 1.5; +} +.form-control-plaintext { + display: block; + width: 100%; + padding: 0.375rem 0; + margin-bottom: 0; + font-size: 1rem; + line-height: 1.5; + color: #212529; + background-color: transparent; + border: solid transparent; + border-width: 1px 0; +} +.form-control-plaintext.form-control-lg, +.form-control-plaintext.form-control-sm { + padding-right: 0; + padding-left: 0; +} +.form-control-sm { + height: calc(1.5em + 0.5rem + 2px); + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} +.form-control-lg { + height: calc(1.5em + 1rem + 2px); + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} +select.form-control[multiple], +select.form-control[size] { + height: auto; +} +textarea.form-control { + height: auto; +} +.form-group { + margin-bottom: 1rem; +} +.form-text { + display: block; + margin-top: 0.25rem; +} +.form-row { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -5px; + margin-left: -5px; +} +.form-row > .col, +.form-row > [class*="col-"] { + padding-right: 5px; + padding-left: 5px; +} +.form-check { + position: relative; + display: block; + padding-left: 1.25rem; +} +.form-check-input { + position: absolute; + margin-top: 0.3rem; + margin-left: -1.25rem; +} +.form-check-input:disabled ~ .form-check-label, +.form-check-input[disabled] ~ .form-check-label { + color: #6c757d; +} +.form-check-label { + margin-bottom: 0; +} +.form-check-inline { + display: -ms-inline-flexbox; + display: inline-flex; + -ms-flex-align: center; + align-items: center; + padding-left: 0; + margin-right: 0.75rem; +} +.form-check-inline .form-check-input { + position: static; + margin-top: 0; + margin-right: 0.3125rem; + margin-left: 0; +} +.valid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #28a745; +} +.valid-tooltip { + position: absolute; + top: 100%; + left: 0; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: 0.1rem; + font-size: 0.875rem; + line-height: 1.5; + color: #fff; + background-color: rgba(40, 167, 69, 0.9); + border-radius: 0.25rem; +} +.form-row > .col > .valid-tooltip, +.form-row > [class*="col-"] > .valid-tooltip { + left: 5px; +} +.is-valid ~ .valid-feedback, +.is-valid ~ .valid-tooltip, +.was-validated :valid ~ .valid-feedback, +.was-validated :valid ~ .valid-tooltip { + display: block; +} +.form-control.is-valid, +.was-validated .form-control:valid { + border-color: #28a745; + padding-right: calc(1.5em + 0.75rem) !important; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' 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); +} +.form-control.is-valid:focus, +.was-validated .form-control:valid:focus { + border-color: #28a745; + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} +.was-validated select.form-control:valid, +select.form-control.is-valid { + padding-right: 3rem !important; + background-position: right 1.5rem center; +} +.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); +} +.custom-select.is-valid, +.was-validated .custom-select:valid { + border-color: #28a745; + padding-right: calc(0.75em + 2.3125rem) !important; + background: + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") + right 0.75rem center/8px 10px no-repeat, + #fff + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' 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") + center right 1.75rem / calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) + no-repeat; +} +.custom-select.is-valid:focus, +.was-validated .custom-select:valid:focus { + border-color: #28a745; + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} +.form-check-input.is-valid ~ .form-check-label, +.was-validated .form-check-input:valid ~ .form-check-label { + color: #28a745; +} +.form-check-input.is-valid ~ .valid-feedback, +.form-check-input.is-valid ~ .valid-tooltip, +.was-validated .form-check-input:valid ~ .valid-feedback, +.was-validated .form-check-input:valid ~ .valid-tooltip { + display: block; +} +.custom-control-input.is-valid ~ .custom-control-label, +.was-validated .custom-control-input:valid ~ .custom-control-label { + color: #28a745; +} +.custom-control-input.is-valid ~ .custom-control-label::before, +.was-validated .custom-control-input:valid ~ .custom-control-label::before { + border-color: #28a745; +} +.custom-control-input.is-valid:checked ~ .custom-control-label::before, +.was-validated + .custom-control-input:valid:checked + ~ .custom-control-label::before { + border-color: #34ce57; + background-color: #34ce57; +} +.custom-control-input.is-valid:focus ~ .custom-control-label::before, +.was-validated + .custom-control-input:valid:focus + ~ .custom-control-label::before { + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} +.custom-control-input.is-valid:focus:not(:checked) + ~ .custom-control-label::before, +.was-validated + .custom-control-input:valid:focus:not(:checked) + ~ .custom-control-label::before { + border-color: #28a745; +} +.custom-file-input.is-valid ~ .custom-file-label, +.was-validated .custom-file-input:valid ~ .custom-file-label { + border-color: #28a745; +} +.custom-file-input.is-valid:focus ~ .custom-file-label, +.was-validated .custom-file-input:valid:focus ~ .custom-file-label { + border-color: #28a745; + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} +.invalid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #dc3545; +} +.invalid-tooltip { + position: absolute; + top: 100%; + left: 0; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: 0.1rem; + font-size: 0.875rem; + line-height: 1.5; + color: #fff; + background-color: rgba(220, 53, 69, 0.9); + border-radius: 0.25rem; +} +.form-row > .col > .invalid-tooltip, +.form-row > [class*="col-"] > .invalid-tooltip { + left: 5px; +} +.is-invalid ~ .invalid-feedback, +.is-invalid ~ .invalid-tooltip, +.was-validated :invalid ~ .invalid-feedback, +.was-validated :invalid ~ .invalid-tooltip { + display: block; +} +.form-control.is-invalid, +.was-validated .form-control:invalid { + border-color: #dc3545; + padding-right: calc(1.5em + 0.75rem) !important; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%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); +} +.form-control.is-invalid:focus, +.was-validated .form-control:invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} +.was-validated select.form-control:invalid, +select.form-control.is-invalid { + padding-right: 3rem !important; + background-position: right 1.5rem center; +} +.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); +} +.custom-select.is-invalid, +.was-validated .custom-select:invalid { + border-color: #dc3545; + padding-right: calc(0.75em + 2.3125rem) !important; + background: + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") + right 0.75rem center/8px 10px no-repeat, + #fff + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%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") + center right 1.75rem / calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) + no-repeat; +} +.custom-select.is-invalid:focus, +.was-validated .custom-select:invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} +.form-check-input.is-invalid ~ .form-check-label, +.was-validated .form-check-input:invalid ~ .form-check-label { + color: #dc3545; +} +.form-check-input.is-invalid ~ .invalid-feedback, +.form-check-input.is-invalid ~ .invalid-tooltip, +.was-validated .form-check-input:invalid ~ .invalid-feedback, +.was-validated .form-check-input:invalid ~ .invalid-tooltip { + display: block; +} +.custom-control-input.is-invalid ~ .custom-control-label, +.was-validated .custom-control-input:invalid ~ .custom-control-label { + color: #dc3545; +} +.custom-control-input.is-invalid ~ .custom-control-label::before, +.was-validated .custom-control-input:invalid ~ .custom-control-label::before { + border-color: #dc3545; +} +.custom-control-input.is-invalid:checked ~ .custom-control-label::before, +.was-validated + .custom-control-input:invalid:checked + ~ .custom-control-label::before { + border-color: #e4606d; + background-color: #e4606d; +} +.custom-control-input.is-invalid:focus ~ .custom-control-label::before, +.was-validated + .custom-control-input:invalid:focus + ~ .custom-control-label::before { + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} +.custom-control-input.is-invalid:focus:not(:checked) + ~ .custom-control-label::before, +.was-validated + .custom-control-input:invalid:focus:not(:checked) + ~ .custom-control-label::before { + border-color: #dc3545; +} +.custom-file-input.is-invalid ~ .custom-file-label, +.was-validated .custom-file-input:invalid ~ .custom-file-label { + border-color: #dc3545; +} +.custom-file-input.is-invalid:focus ~ .custom-file-label, +.was-validated .custom-file-input:invalid:focus ~ .custom-file-label { + border-color: #dc3545; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} +.form-inline { + display: -ms-flexbox; + display: flex; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -ms-flex-align: center; + align-items: center; +} +.form-inline .form-check { + width: 100%; +} +@media (min-width: 576px) { + .form-inline label { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + margin-bottom: 0; + } + .form-inline .form-group { + display: -ms-flexbox; + display: flex; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -ms-flex-align: center; + align-items: center; + margin-bottom: 0; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-plaintext { + display: inline-block; + } + .form-inline .custom-select, + .form-inline .input-group { + width: auto; + } + .form-inline .form-check { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + width: auto; + padding-left: 0; + } + .form-inline .form-check-input { + position: relative; + -ms-flex-negative: 0; + flex-shrink: 0; + margin-top: 0; + margin-right: 0.25rem; + margin-left: 0; + } + .form-inline .custom-control { + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + } + .form-inline .custom-control-label { + margin-bottom: 0; + } +} +.btn { + display: inline-block; + font-weight: 400; + color: #212529; + text-align: center; + vertical-align: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + border-radius: 0.25rem; + transition: + color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .btn { + transition: none; + } +} +.btn:hover { + color: #212529; + text-decoration: none; +} +.btn.focus, +.btn:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.btn.disabled, +.btn:disabled { + opacity: 0.65; +} +.btn:not(:disabled):not(.disabled) { + cursor: pointer; +} +a.btn.disabled, +fieldset:disabled a.btn { + pointer-events: none; +} +.btn-primary { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} +.btn-primary:hover { + color: #fff; + background-color: #0069d9; + border-color: #0062cc; +} +.btn-primary.focus, +.btn-primary:focus { + color: #fff; + background-color: #0069d9; + border-color: #0062cc; + box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5); +} +.btn-primary.disabled, +.btn-primary:disabled { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} +.btn-primary:not(:disabled):not(.disabled).active, +.btn-primary:not(:disabled):not(.disabled):active, +.show > .btn-primary.dropdown-toggle { + color: #fff; + background-color: #0062cc; + border-color: #005cbf; +} +.btn-primary:not(:disabled):not(.disabled).active:focus, +.btn-primary:not(:disabled):not(.disabled):active:focus, +.show > .btn-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5); +} +.btn-secondary { + color: #fff; + background-color: #6c757d; + border-color: #6c757d; +} +.btn-secondary:hover { + color: #fff; + background-color: #5a6268; + border-color: #545b62; +} +.btn-secondary.focus, +.btn-secondary:focus { + color: #fff; + background-color: #5a6268; + border-color: #545b62; + box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); +} +.btn-secondary.disabled, +.btn-secondary:disabled { + color: #fff; + background-color: #6c757d; + border-color: #6c757d; +} +.btn-secondary:not(:disabled):not(.disabled).active, +.btn-secondary:not(:disabled):not(.disabled):active, +.show > .btn-secondary.dropdown-toggle { + color: #fff; + background-color: #545b62; + border-color: #4e555b; +} +.btn-secondary:not(:disabled):not(.disabled).active:focus, +.btn-secondary:not(:disabled):not(.disabled):active:focus, +.show > .btn-secondary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); +} +.btn-success { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} +.btn-success:hover { + color: #fff; + background-color: #218838; + border-color: #1e7e34; +} +.btn-success.focus, +.btn-success:focus { + color: #fff; + background-color: #218838; + border-color: #1e7e34; + box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5); +} +.btn-success.disabled, +.btn-success:disabled { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} +.btn-success:not(:disabled):not(.disabled).active, +.btn-success:not(:disabled):not(.disabled):active, +.show > .btn-success.dropdown-toggle { + color: #fff; + background-color: #1e7e34; + border-color: #1c7430; +} +.btn-success:not(:disabled):not(.disabled).active:focus, +.btn-success:not(:disabled):not(.disabled):active:focus, +.show > .btn-success.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5); +} +.btn-info { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} +.btn-info:hover { + color: #fff; + background-color: #138496; + border-color: #117a8b; +} +.btn-info.focus, +.btn-info:focus { + color: #fff; + background-color: #138496; + border-color: #117a8b; + box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); +} +.btn-info.disabled, +.btn-info:disabled { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} +.btn-info:not(:disabled):not(.disabled).active, +.btn-info:not(:disabled):not(.disabled):active, +.show > .btn-info.dropdown-toggle { + color: #fff; + background-color: #117a8b; + border-color: #10707f; +} +.btn-info:not(:disabled):not(.disabled).active:focus, +.btn-info:not(:disabled):not(.disabled):active:focus, +.show > .btn-info.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); +} +.btn-warning { + color: #212529; + background-color: #ffc107; + border-color: #ffc107; +} +.btn-warning:hover { + color: #212529; + background-color: #e0a800; + border-color: #d39e00; +} +.btn-warning.focus, +.btn-warning:focus { + color: #212529; + background-color: #e0a800; + border-color: #d39e00; + box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5); +} +.btn-warning.disabled, +.btn-warning:disabled { + color: #212529; + background-color: #ffc107; + border-color: #ffc107; +} +.btn-warning:not(:disabled):not(.disabled).active, +.btn-warning:not(:disabled):not(.disabled):active, +.show > .btn-warning.dropdown-toggle { + color: #212529; + background-color: #d39e00; + border-color: #c69500; +} +.btn-warning:not(:disabled):not(.disabled).active:focus, +.btn-warning:not(:disabled):not(.disabled):active:focus, +.show > .btn-warning.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5); +} +.btn-danger { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} +.btn-danger:hover { + color: #fff; + background-color: #c82333; + border-color: #bd2130; +} +.btn-danger.focus, +.btn-danger:focus { + color: #fff; + background-color: #c82333; + border-color: #bd2130; + box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5); +} +.btn-danger.disabled, +.btn-danger:disabled { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} +.btn-danger:not(:disabled):not(.disabled).active, +.btn-danger:not(:disabled):not(.disabled):active, +.show > .btn-danger.dropdown-toggle { + color: #fff; + background-color: #bd2130; + border-color: #b21f2d; +} +.btn-danger:not(:disabled):not(.disabled).active:focus, +.btn-danger:not(:disabled):not(.disabled):active:focus, +.show > .btn-danger.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5); +} +.btn-light { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-light:hover { + color: #212529; + background-color: #e2e6ea; + border-color: #dae0e5; +} +.btn-light.focus, +.btn-light:focus { + color: #212529; + background-color: #e2e6ea; + border-color: #dae0e5; + box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); +} +.btn-light.disabled, +.btn-light:disabled { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-light:not(:disabled):not(.disabled).active, +.btn-light:not(:disabled):not(.disabled):active, +.show > .btn-light.dropdown-toggle { + color: #212529; + background-color: #dae0e5; + border-color: #d3d9df; +} +.btn-light:not(:disabled):not(.disabled).active:focus, +.btn-light:not(:disabled):not(.disabled):active:focus, +.show > .btn-light.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); +} +.btn-dark { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} +.btn-dark:hover { + color: #fff; + background-color: #23272b; + border-color: #1d2124; +} +.btn-dark.focus, +.btn-dark:focus { + color: #fff; + background-color: #23272b; + border-color: #1d2124; + box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); +} +.btn-dark.disabled, +.btn-dark:disabled { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} +.btn-dark:not(:disabled):not(.disabled).active, +.btn-dark:not(:disabled):not(.disabled):active, +.show > .btn-dark.dropdown-toggle { + color: #fff; + background-color: #1d2124; + border-color: #171a1d; +} +.btn-dark:not(:disabled):not(.disabled).active:focus, +.btn-dark:not(:disabled):not(.disabled):active:focus, +.show > .btn-dark.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); +} +.btn-outline-primary { + color: #007bff; + border-color: #007bff; +} +.btn-outline-primary:hover { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} +.btn-outline-primary.focus, +.btn-outline-primary:focus { + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} +.btn-outline-primary.disabled, +.btn-outline-primary:disabled { + color: #007bff; + background-color: transparent; +} +.btn-outline-primary:not(:disabled):not(.disabled).active, +.btn-outline-primary:not(:disabled):not(.disabled):active, +.show > .btn-outline-primary.dropdown-toggle { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} +.btn-outline-primary:not(:disabled):not(.disabled).active:focus, +.btn-outline-primary:not(:disabled):not(.disabled):active:focus, +.show > .btn-outline-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} +.btn-outline-secondary { + color: #6c757d; + border-color: #6c757d; +} +.btn-outline-secondary:hover { + color: #fff; + background-color: #6c757d; + border-color: #6c757d; +} +.btn-outline-secondary.focus, +.btn-outline-secondary:focus { + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); +} +.btn-outline-secondary.disabled, +.btn-outline-secondary:disabled { + color: #6c757d; + background-color: transparent; +} +.btn-outline-secondary:not(:disabled):not(.disabled).active, +.btn-outline-secondary:not(:disabled):not(.disabled):active, +.show > .btn-outline-secondary.dropdown-toggle { + color: #fff; + background-color: #6c757d; + border-color: #6c757d; +} +.btn-outline-secondary:not(:disabled):not(.disabled).active:focus, +.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, +.show > .btn-outline-secondary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); +} +.btn-outline-success { + color: #28a745; + border-color: #28a745; +} +.btn-outline-success:hover { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} +.btn-outline-success.focus, +.btn-outline-success:focus { + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} +.btn-outline-success.disabled, +.btn-outline-success:disabled { + color: #28a745; + background-color: transparent; +} +.btn-outline-success:not(:disabled):not(.disabled).active, +.btn-outline-success:not(:disabled):not(.disabled):active, +.show > .btn-outline-success.dropdown-toggle { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} +.btn-outline-success:not(:disabled):not(.disabled).active:focus, +.btn-outline-success:not(:disabled):not(.disabled):active:focus, +.show > .btn-outline-success.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} +.btn-outline-info { + color: #17a2b8; + border-color: #17a2b8; +} +.btn-outline-info:hover { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} +.btn-outline-info.focus, +.btn-outline-info:focus { + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} +.btn-outline-info.disabled, +.btn-outline-info:disabled { + color: #17a2b8; + background-color: transparent; +} +.btn-outline-info:not(:disabled):not(.disabled).active, +.btn-outline-info:not(:disabled):not(.disabled):active, +.show > .btn-outline-info.dropdown-toggle { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} +.btn-outline-info:not(:disabled):not(.disabled).active:focus, +.btn-outline-info:not(:disabled):not(.disabled):active:focus, +.show > .btn-outline-info.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} +.btn-outline-warning { + color: #ffc107; + border-color: #ffc107; +} +.btn-outline-warning:hover { + color: #212529; + background-color: #ffc107; + border-color: #ffc107; +} +.btn-outline-warning.focus, +.btn-outline-warning:focus { + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} +.btn-outline-warning.disabled, +.btn-outline-warning:disabled { + color: #ffc107; + background-color: transparent; +} +.btn-outline-warning:not(:disabled):not(.disabled).active, +.btn-outline-warning:not(:disabled):not(.disabled):active, +.show > .btn-outline-warning.dropdown-toggle { + color: #212529; + background-color: #ffc107; + border-color: #ffc107; +} +.btn-outline-warning:not(:disabled):not(.disabled).active:focus, +.btn-outline-warning:not(:disabled):not(.disabled):active:focus, +.show > .btn-outline-warning.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} +.btn-outline-danger { + color: #dc3545; + border-color: #dc3545; +} +.btn-outline-danger:hover { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} +.btn-outline-danger.focus, +.btn-outline-danger:focus { + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} +.btn-outline-danger.disabled, +.btn-outline-danger:disabled { + color: #dc3545; + background-color: transparent; +} +.btn-outline-danger:not(:disabled):not(.disabled).active, +.btn-outline-danger:not(:disabled):not(.disabled):active, +.show > .btn-outline-danger.dropdown-toggle { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} +.btn-outline-danger:not(:disabled):not(.disabled).active:focus, +.btn-outline-danger:not(:disabled):not(.disabled):active:focus, +.show > .btn-outline-danger.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} +.btn-outline-light { + color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-outline-light:hover { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-outline-light.focus, +.btn-outline-light:focus { + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} +.btn-outline-light.disabled, +.btn-outline-light:disabled { + color: #f8f9fa; + background-color: transparent; +} +.btn-outline-light:not(:disabled):not(.disabled).active, +.btn-outline-light:not(:disabled):not(.disabled):active, +.show > .btn-outline-light.dropdown-toggle { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} +.btn-outline-light:not(:disabled):not(.disabled).active:focus, +.btn-outline-light:not(:disabled):not(.disabled):active:focus, +.show > .btn-outline-light.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} +.btn-outline-dark { + color: #343a40; + border-color: #343a40; +} +.btn-outline-dark:hover { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} +.btn-outline-dark.focus, +.btn-outline-dark:focus { + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} +.btn-outline-dark.disabled, +.btn-outline-dark:disabled { + color: #343a40; + background-color: transparent; +} +.btn-outline-dark:not(:disabled):not(.disabled).active, +.btn-outline-dark:not(:disabled):not(.disabled):active, +.show > .btn-outline-dark.dropdown-toggle { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} +.btn-outline-dark:not(:disabled):not(.disabled).active:focus, +.btn-outline-dark:not(:disabled):not(.disabled):active:focus, +.show > .btn-outline-dark.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} +.btn-link { + font-weight: 400; + color: #007bff; + text-decoration: none; +} +.btn-link:hover { + color: #0056b3; + text-decoration: underline; +} +.btn-link.focus, +.btn-link:focus { + text-decoration: underline; +} +.btn-link.disabled, +.btn-link:disabled { + color: #6c757d; + pointer-events: none; +} +.btn-group-lg > .btn, +.btn-lg { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} +.btn-group-sm > .btn, +.btn-sm { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 0.5rem; +} +input[type="button"].btn-block, +input[type="reset"].btn-block, +input[type="submit"].btn-block { + width: 100%; +} +.fade { + transition: opacity 0.15s linear; +} +@media (prefers-reduced-motion: reduce) { + .fade { + transition: none; + } +} +.fade:not(.show) { + opacity: 0; +} +.collapse:not(.show) { + display: none; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + transition: height 0.35s ease; +} +@media (prefers-reduced-motion: reduce) { + .collapsing { + transition: none; + } +} +.dropdown, +.dropleft, +.dropright, +.dropup { + position: relative; +} +.dropdown-toggle { + white-space: nowrap; +} +.dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; +} +.dropdown-toggle:empty::after { + margin-left: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 10rem; + padding: 0.5rem 0; + margin: 0.125rem 0 0; + font-size: 1rem; + color: #212529; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; +} +.dropdown-menu-left { + right: auto; + left: 0; +} +.dropdown-menu-right { + right: 0; + left: auto; +} +@media (min-width: 576px) { + .dropdown-menu-sm-left { + right: auto; + left: 0; + } + .dropdown-menu-sm-right { + right: 0; + left: auto; + } +} +@media (min-width: 768px) { + .dropdown-menu-md-left { + right: auto; + left: 0; + } + .dropdown-menu-md-right { + right: 0; + left: auto; + } +} +@media (min-width: 992px) { + .dropdown-menu-lg-left { + right: auto; + left: 0; + } + .dropdown-menu-lg-right { + right: 0; + left: auto; + } +} +@media (min-width: 1200px) { + .dropdown-menu-xl-left { + right: auto; + left: 0; + } + .dropdown-menu-xl-right { + right: 0; + left: auto; + } +} +.dropup .dropdown-menu { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: 0.125rem; +} +.dropup .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0; + border-right: 0.3em solid transparent; + border-bottom: 0.3em solid; + border-left: 0.3em solid transparent; +} +.dropup .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropright .dropdown-menu { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: 0.125rem; +} +.dropright .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0; + border-bottom: 0.3em solid transparent; + border-left: 0.3em solid; +} +.dropright .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropright .dropdown-toggle::after { + vertical-align: 0; +} +.dropleft .dropdown-menu { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: 0.125rem; +} +.dropleft .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; +} +.dropleft .dropdown-toggle::after { + display: none; +} +.dropleft .dropdown-toggle::before { + display: inline-block; + margin-right: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0.3em solid; + border-bottom: 0.3em solid transparent; +} +.dropleft .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropleft .dropdown-toggle::before { + vertical-align: 0; +} +.dropdown-menu[x-placement^="bottom"], +.dropdown-menu[x-placement^="left"], +.dropdown-menu[x-placement^="right"], +.dropdown-menu[x-placement^="top"] { + right: auto; + bottom: auto; +} +.dropdown-divider { + height: 0; + margin: 0.5rem 0; + overflow: hidden; + border-top: 1px solid #e9ecef; +} +.dropdown-item { + display: block; + width: 100%; + padding: 0.25rem 1.5rem; + clear: both; + font-weight: 400; + color: #212529; + text-align: inherit; + white-space: nowrap; + background-color: transparent; + border: 0; +} +.dropdown-item:focus, +.dropdown-item:hover { + color: #16181b; + text-decoration: none; + background-color: #e9ecef; +} +.dropdown-item.active, +.dropdown-item:active { + color: #fff; + text-decoration: none; + background-color: #007bff; +} +.dropdown-item.disabled, +.dropdown-item:disabled { + color: #adb5bd; + pointer-events: none; + background-color: transparent; +} +.dropdown-menu.show { + display: block; +} +.dropdown-header { + display: block; + padding: 0.5rem 1.5rem; + margin-bottom: 0; + font-size: 0.875rem; + color: #6c757d; + white-space: nowrap; +} +.dropdown-item-text { + display: block; + padding: 0.25rem 1.5rem; + color: #212529; +} +.btn-group, +.btn-group-vertical { + position: relative; + display: -ms-inline-flexbox; + display: inline-flex; + vertical-align: middle; +} +.btn-group-vertical > .btn, +.btn-group > .btn { + position: relative; + -ms-flex: 1 1 auto; + flex: 1 1 auto; +} +.btn-group-vertical > .btn:hover, +.btn-group > .btn:hover { + z-index: 1; +} +.btn-group-vertical > .btn.active, +.btn-group-vertical > .btn:active, +.btn-group-vertical > .btn:focus, +.btn-group > .btn.active, +.btn-group > .btn:active, +.btn-group > .btn:focus { + z-index: 1; +} +.btn-toolbar { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-pack: start; + justify-content: flex-start; +} +.btn-toolbar .input-group { + width: auto; +} +.btn-group > .btn-group:not(:first-child), +.btn-group > .btn:not(:first-child) { + margin-left: -1px; +} +.btn-group > .btn-group:not(:last-child) > .btn, +.btn-group > .btn:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:not(:first-child) > .btn, +.btn-group > .btn:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.dropdown-toggle-split { + padding-right: 0.5625rem; + padding-left: 0.5625rem; +} +.dropdown-toggle-split::after, +.dropright .dropdown-toggle-split::after, +.dropup .dropdown-toggle-split::after { + margin-left: 0; +} +.dropleft .dropdown-toggle-split::before { + margin-right: 0; +} +.btn-group-sm > .btn + .dropdown-toggle-split, +.btn-sm + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; +} +.btn-group-lg > .btn + .dropdown-toggle-split, +.btn-lg + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; +} +.btn-group-vertical { + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-align: start; + align-items: flex-start; + -ms-flex-pack: center; + justify-content: center; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group { + width: 100%; +} +.btn-group-vertical > .btn-group:not(:first-child), +.btn-group-vertical > .btn:not(:first-child) { + margin-top: -1px; +} +.btn-group-vertical > .btn-group:not(:last-child) > .btn, +.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle) { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:not(:first-child) > .btn, +.btn-group-vertical > .btn:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.btn-group-toggle > .btn, +.btn-group-toggle > .btn-group > .btn { + margin-bottom: 0; +} +.btn-group-toggle > .btn input[type="checkbox"], +.btn-group-toggle > .btn input[type="radio"], +.btn-group-toggle > .btn-group > .btn input[type="checkbox"], +.btn-group-toggle > .btn-group > .btn input[type="radio"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-align: stretch; + align-items: stretch; + width: 100%; +} +.input-group > .custom-file, +.input-group > .custom-select, +.input-group > .form-control, +.input-group > .form-control-plaintext { + position: relative; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + width: 1%; + min-width: 0; + margin-bottom: 0; +} +.input-group > .custom-file + .custom-file, +.input-group > .custom-file + .custom-select, +.input-group > .custom-file + .form-control, +.input-group > .custom-select + .custom-file, +.input-group > .custom-select + .custom-select, +.input-group > .custom-select + .form-control, +.input-group > .form-control + .custom-file, +.input-group > .form-control + .custom-select, +.input-group > .form-control + .form-control, +.input-group > .form-control-plaintext + .custom-file, +.input-group > .form-control-plaintext + .custom-select, +.input-group > .form-control-plaintext + .form-control { + margin-left: -1px; +} +.input-group > .custom-file .custom-file-input:focus ~ .custom-file-label, +.input-group > .custom-select:focus, +.input-group > .form-control:focus { + z-index: 3; +} +.input-group > .custom-file .custom-file-input:focus { + z-index: 4; +} +.input-group > .custom-select:not(:first-child), +.input-group > .form-control:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group > .custom-file { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; +} +.input-group > .custom-file:not(:last-child) .custom-file-label, +.input-group > .custom-file:not(:last-child) .custom-file-label::after { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group > .custom-file:not(:first-child) .custom-file-label { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group:not(.has-validation) + > .custom-file:not(:last-child) + .custom-file-label, +.input-group:not(.has-validation) + > .custom-file:not(:last-child) + .custom-file-label::after, +.input-group:not(.has-validation) > .custom-select:not(:last-child), +.input-group:not(.has-validation) > .form-control:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group.has-validation + > .custom-file:nth-last-child(n + 3) + .custom-file-label, +.input-group.has-validation + > .custom-file:nth-last-child(n + 3) + .custom-file-label::after, +.input-group.has-validation > .custom-select:nth-last-child(n + 3), +.input-group.has-validation > .form-control:nth-last-child(n + 3) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group-append, +.input-group-prepend { + display: -ms-flexbox; + display: flex; +} +.input-group-append .btn, +.input-group-prepend .btn { + position: relative; + z-index: 2; +} +.input-group-append .btn:focus, +.input-group-prepend .btn:focus { + z-index: 3; +} +.input-group-append .btn + .btn, +.input-group-append .btn + .input-group-text, +.input-group-append .input-group-text + .btn, +.input-group-append .input-group-text + .input-group-text, +.input-group-prepend .btn + .btn, +.input-group-prepend .btn + .input-group-text, +.input-group-prepend .input-group-text + .btn, +.input-group-prepend .input-group-text + .input-group-text { + margin-left: -1px; +} +.input-group-prepend { + margin-right: -1px; +} +.input-group-append { + margin-left: -1px; +} +.input-group-text { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + padding: 0.375rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + text-align: center; + white-space: nowrap; + background-color: #e9ecef; + border: 1px solid #ced4da; + border-radius: 0.25rem; +} +.input-group-text input[type="checkbox"], +.input-group-text input[type="radio"] { + margin-top: 0; +} +.input-group-lg > .custom-select, +.input-group-lg > .form-control:not(textarea) { + height: calc(1.5em + 1rem + 2px); +} +.input-group-lg > .custom-select, +.input-group-lg > .form-control, +.input-group-lg > .input-group-append > .btn, +.input-group-lg > .input-group-append > .input-group-text, +.input-group-lg > .input-group-prepend > .btn, +.input-group-lg > .input-group-prepend > .input-group-text { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} +.input-group-sm > .custom-select, +.input-group-sm > .form-control:not(textarea) { + height: calc(1.5em + 0.5rem + 2px); +} +.input-group-sm > .custom-select, +.input-group-sm > .form-control, +.input-group-sm > .input-group-append > .btn, +.input-group-sm > .input-group-append > .input-group-text, +.input-group-sm > .input-group-prepend > .btn, +.input-group-sm > .input-group-prepend > .input-group-text { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} +.input-group-lg > .custom-select, +.input-group-sm > .custom-select { + padding-right: 1.75rem; +} +.input-group.has-validation > .input-group-append:nth-last-child(n + 3) > .btn, +.input-group.has-validation + > .input-group-append:nth-last-child(n + 3) + > .input-group-text, +.input-group:not(.has-validation) > .input-group-append:not(:last-child) > .btn, +.input-group:not(.has-validation) + > .input-group-append:not(:last-child) + > .input-group-text, +.input-group + > .input-group-append:last-child + > .btn:not(:last-child):not(.dropdown-toggle), +.input-group + > .input-group-append:last-child + > .input-group-text:not(:last-child), +.input-group > .input-group-prepend > .btn, +.input-group > .input-group-prepend > .input-group-text { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group > .input-group-append > .btn, +.input-group > .input-group-append > .input-group-text, +.input-group > .input-group-prepend:first-child > .btn:not(:first-child), +.input-group + > .input-group-prepend:first-child + > .input-group-text:not(:first-child), +.input-group > .input-group-prepend:not(:first-child) > .btn, +.input-group > .input-group-prepend:not(:first-child) > .input-group-text { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.custom-control { + position: relative; + z-index: 1; + display: block; + min-height: 1.5rem; + padding-left: 1.5rem; + -webkit-print-color-adjust: exact; + color-adjust: exact; +} +.custom-control-inline { + display: -ms-inline-flexbox; + display: inline-flex; + margin-right: 1rem; +} +.custom-control-input { + position: absolute; + left: 0; + z-index: -1; + width: 1rem; + height: 1.25rem; + opacity: 0; +} +.custom-control-input:checked ~ .custom-control-label::before { + color: #fff; + border-color: #007bff; + background-color: #007bff; +} +.custom-control-input:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.custom-control-input:focus:not(:checked) ~ .custom-control-label::before { + border-color: #80bdff; +} +.custom-control-input:not(:disabled):active ~ .custom-control-label::before { + color: #fff; + background-color: #b3d7ff; + border-color: #b3d7ff; +} +.custom-control-input:disabled ~ .custom-control-label, +.custom-control-input[disabled] ~ .custom-control-label { + color: #6c757d; +} +.custom-control-input:disabled ~ .custom-control-label::before, +.custom-control-input[disabled] ~ .custom-control-label::before { + background-color: #e9ecef; +} +.custom-control-label { + position: relative; + margin-bottom: 0; + vertical-align: top; +} +.custom-control-label::before { + position: absolute; + top: 0.25rem; + left: -1.5rem; + display: block; + width: 1rem; + height: 1rem; + pointer-events: none; + content: ""; + background-color: #fff; + border: #adb5bd solid 1px; +} +.custom-control-label::after { + position: absolute; + top: 0.25rem; + left: -1.5rem; + display: block; + width: 1rem; + height: 1rem; + content: ""; + background: 50%/50% 50% no-repeat; +} +.custom-checkbox .custom-control-label::before { + border-radius: 0.25rem; +} +.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e"); +} +.custom-checkbox + .custom-control-input:indeterminate + ~ .custom-control-label::before { + border-color: #007bff; + background-color: #007bff; +} +.custom-checkbox + .custom-control-input:indeterminate + ~ .custom-control-label::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e"); +} +.custom-checkbox + .custom-control-input:disabled:checked + ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, 0.5); +} +.custom-checkbox + .custom-control-input:disabled:indeterminate + ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, 0.5); +} +.custom-radio .custom-control-label::before { + border-radius: 50%; +} +.custom-radio .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); +} +.custom-radio + .custom-control-input:disabled:checked + ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, 0.5); +} +.custom-switch { + padding-left: 2.25rem; +} +.custom-switch .custom-control-label::before { + left: -2.25rem; + width: 1.75rem; + pointer-events: all; + border-radius: 0.5rem; +} +.custom-switch .custom-control-label::after { + top: calc(0.25rem + 2px); + left: calc(-2.25rem + 2px); + width: calc(1rem - 4px); + height: calc(1rem - 4px); + background-color: #adb5bd; + border-radius: 0.5rem; + transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out, + -webkit-transform 0.15s ease-in-out; + transition: + transform 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + transition: + transform 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out, + -webkit-transform 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .custom-switch .custom-control-label::after { + transition: none; + } +} +.custom-switch .custom-control-input:checked ~ .custom-control-label::after { + background-color: #fff; + -webkit-transform: translateX(0.75rem); + transform: translateX(0.75rem); +} +.custom-switch + .custom-control-input:disabled:checked + ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, 0.5); +} +.custom-select { + display: inline-block; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + padding: 0.375rem 1.75rem 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + vertical-align: middle; + background: #fff + url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") + right 0.75rem center/8px 10px no-repeat; + border: 1px solid #ced4da; + border-radius: 0.25rem; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +.custom-select:focus { + border-color: #80bdff; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.custom-select:focus::-ms-value { + color: #495057; + background-color: #fff; +} +.custom-select[multiple], +.custom-select[size]:not([size="1"]) { + height: auto; + padding-right: 0.75rem; + background-image: none; +} +.custom-select:disabled { + color: #6c757d; + background-color: #e9ecef; +} +.custom-select::-ms-expand { + display: none; +} +.custom-select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #495057; +} +.custom-select-sm { + height: calc(1.5em + 0.5rem + 2px); + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-left: 0.5rem; + font-size: 0.875rem; +} +.custom-select-lg { + height: calc(1.5em + 1rem + 2px); + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + font-size: 1.25rem; +} +.custom-file { + position: relative; + display: inline-block; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + margin-bottom: 0; +} +.custom-file-input { + position: relative; + z-index: 2; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + margin: 0; + overflow: hidden; + opacity: 0; +} +.custom-file-input:focus ~ .custom-file-label { + border-color: #80bdff; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.custom-file-input:disabled ~ .custom-file-label, +.custom-file-input[disabled] ~ .custom-file-label { + background-color: #e9ecef; +} +.custom-file-input:lang(en) ~ .custom-file-label::after { + content: "Browse"; +} +.custom-file-input ~ .custom-file-label[data-browse]::after { + content: attr(data-browse); +} +.custom-file-label { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 1; + height: calc(1.5em + 0.75rem + 2px); + padding: 0.375rem 0.75rem; + overflow: hidden; + font-weight: 400; + line-height: 1.5; + color: #495057; + background-color: #fff; + border: 1px solid #ced4da; + border-radius: 0.25rem; +} +.custom-file-label::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + z-index: 3; + display: block; + height: calc(1.5em + 0.75rem); + padding: 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + content: "Browse"; + background-color: #e9ecef; + border-left: inherit; + border-radius: 0 0.25rem 0.25rem 0; +} +.custom-range { + width: 100%; + height: 1.4rem; + padding: 0; + background-color: transparent; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +.custom-range:focus { + outline: 0; +} +.custom-range:focus::-webkit-slider-thumb { + box-shadow: + 0 0 0 1px #fff, + 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.custom-range:focus::-moz-range-thumb { + box-shadow: + 0 0 0 1px #fff, + 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.custom-range:focus::-ms-thumb { + box-shadow: + 0 0 0 1px #fff, + 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.custom-range::-moz-focus-outer { + border: 0; +} +.custom-range::-webkit-slider-thumb { + width: 1rem; + height: 1rem; + margin-top: -0.25rem; + background-color: #007bff; + border: 0; + border-radius: 1rem; + -webkit-transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + -webkit-appearance: none; + appearance: none; +} +@media (prefers-reduced-motion: reduce) { + .custom-range::-webkit-slider-thumb { + -webkit-transition: none; + transition: none; + } +} +.custom-range::-webkit-slider-thumb:active { + background-color: #b3d7ff; +} +.custom-range::-webkit-slider-runnable-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: #dee2e6; + border-color: transparent; + border-radius: 1rem; +} +.custom-range::-moz-range-thumb { + width: 1rem; + height: 1rem; + background-color: #007bff; + border: 0; + border-radius: 1rem; + -moz-transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + -moz-appearance: none; + appearance: none; +} +@media (prefers-reduced-motion: reduce) { + .custom-range::-moz-range-thumb { + -moz-transition: none; + transition: none; + } +} +.custom-range::-moz-range-thumb:active { + background-color: #b3d7ff; +} +.custom-range::-moz-range-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: #dee2e6; + border-color: transparent; + border-radius: 1rem; +} +.custom-range::-ms-thumb { + width: 1rem; + height: 1rem; + margin-top: 0; + margin-right: 0.2rem; + margin-left: 0.2rem; + background-color: #007bff; + border: 0; + border-radius: 1rem; + -ms-transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + appearance: none; +} +@media (prefers-reduced-motion: reduce) { + .custom-range::-ms-thumb { + -ms-transition: none; + transition: none; + } +} +.custom-range::-ms-thumb:active { + background-color: #b3d7ff; +} +.custom-range::-ms-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: transparent; + border-color: transparent; + border-width: 0.5rem; +} +.custom-range::-ms-fill-lower { + background-color: #dee2e6; + border-radius: 1rem; +} +.custom-range::-ms-fill-upper { + margin-right: 15px; + background-color: #dee2e6; + border-radius: 1rem; +} +.custom-range:disabled::-webkit-slider-thumb { + background-color: #adb5bd; +} +.custom-range:disabled::-webkit-slider-runnable-track { + cursor: default; +} +.custom-range:disabled::-moz-range-thumb { + background-color: #adb5bd; +} +.custom-range:disabled::-moz-range-track { + cursor: default; +} +.custom-range:disabled::-ms-thumb { + background-color: #adb5bd; +} +.custom-control-label::before, +.custom-file-label, +.custom-select { + transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .custom-control-label::before, + .custom-file-label, + .custom-select { + transition: none; + } +} +.nav { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav-link { + display: block; + padding: 0.5rem 1rem; +} +.nav-link:focus, +.nav-link:hover { + text-decoration: none; +} +.nav-link.disabled { + color: #6c757d; + pointer-events: none; + cursor: default; +} +.nav-tabs { + border-bottom: 1px solid #dee2e6; +} +.nav-tabs .nav-link { + margin-bottom: -1px; + border: 1px solid transparent; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} +.nav-tabs .nav-link:focus, +.nav-tabs .nav-link:hover { + border-color: #e9ecef #e9ecef #dee2e6; +} +.nav-tabs .nav-link.disabled { + color: #6c757d; + background-color: transparent; + border-color: transparent; +} +.nav-tabs .nav-item.show .nav-link, +.nav-tabs .nav-link.active { + 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 { + border-radius: 0.25rem; +} +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: #fff; + background-color: #007bff; +} +.nav-fill .nav-item, +.nav-fill > .nav-link { + -ms-flex: 1 1 auto; + flex: 1 1 auto; + text-align: center; +} +.nav-justified .nav-item, +.nav-justified > .nav-link { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + text-align: center; +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.navbar { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 0.5rem 1rem; +} +.navbar .container, +.navbar .container-fluid, +.navbar .container-lg, +.navbar .container-md, +.navbar .container-sm, +.navbar .container-xl { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: justify; + justify-content: space-between; +} +.navbar-brand { + display: inline-block; + padding-top: 0.3125rem; + padding-bottom: 0.3125rem; + margin-right: 1rem; + font-size: 1.25rem; + line-height: inherit; + white-space: nowrap; +} +.navbar-brand:focus, +.navbar-brand:hover { + text-decoration: none; +} +.navbar-nav { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + 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; + float: none; +} +.navbar-text { + display: inline-block; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} +.navbar-collapse { + -ms-flex-preferred-size: 100%; + flex-basis: 100%; + -ms-flex-positive: 1; + flex-grow: 1; + -ms-flex-align: center; + align-items: center; +} +.navbar-toggler { + padding: 0.25rem 0.75rem; + font-size: 1.25rem; + line-height: 1; + background-color: transparent; + border: 1px solid transparent; + border-radius: 0.25rem; +} +.navbar-toggler:focus, +.navbar-toggler:hover { + text-decoration: none; +} +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + content: ""; + background: 50%/100% 100% no-repeat; +} +.navbar-nav-scroll { + max-height: 75vh; + overflow-y: auto; +} +@media (max-width: 575.98px) { + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid, + .navbar-expand-sm > .container-lg, + .navbar-expand-sm > .container-md, + .navbar-expand-sm > .container-sm, + .navbar-expand-sm > .container-xl { + padding-right: 0; + padding-left: 0; + } +} +@media (min-width: 576px) { + .navbar-expand-sm { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-sm .navbar-nav { + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; + } + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid, + .navbar-expand-sm > .container-lg, + .navbar-expand-sm > .container-md, + .navbar-expand-sm > .container-sm, + .navbar-expand-sm > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-sm .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-sm .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-sm .navbar-toggler { + display: none; + } +} +@media (max-width: 767.98px) { + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid, + .navbar-expand-md > .container-lg, + .navbar-expand-md > .container-md, + .navbar-expand-md > .container-sm, + .navbar-expand-md > .container-xl { + padding-right: 0; + padding-left: 0; + } +} +@media (min-width: 768px) { + .navbar-expand-md { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-md .navbar-nav { + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; + } + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid, + .navbar-expand-md > .container-lg, + .navbar-expand-md > .container-md, + .navbar-expand-md > .container-sm, + .navbar-expand-md > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-md .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-md .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-md .navbar-toggler { + display: none; + } +} +@media (max-width: 991.98px) { + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid, + .navbar-expand-lg > .container-lg, + .navbar-expand-lg > .container-md, + .navbar-expand-lg > .container-sm, + .navbar-expand-lg > .container-xl { + padding-right: 0; + padding-left: 0; + } +} +@media (min-width: 992px) { + .navbar-expand-lg { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-lg .navbar-nav { + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; + } + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid, + .navbar-expand-lg > .container-lg, + .navbar-expand-lg > .container-md, + .navbar-expand-lg > .container-sm, + .navbar-expand-lg > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-lg .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-lg .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-lg .navbar-toggler { + display: none; + } +} +@media (max-width: 1199.98px) { + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid, + .navbar-expand-xl > .container-lg, + .navbar-expand-xl > .container-md, + .navbar-expand-xl > .container-sm, + .navbar-expand-xl > .container-xl { + padding-right: 0; + padding-left: 0; + } +} +@media (min-width: 1200px) { + .navbar-expand-xl { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-xl .navbar-nav { + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; + } + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid, + .navbar-expand-xl > .container-lg, + .navbar-expand-xl > .container-md, + .navbar-expand-xl > .container-sm, + .navbar-expand-xl > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-xl .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-xl .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-xl .navbar-toggler { + display: none; + } +} +.navbar-expand { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start; +} +.navbar-expand > .container, +.navbar-expand > .container-fluid, +.navbar-expand > .container-lg, +.navbar-expand > .container-md, +.navbar-expand > .container-sm, +.navbar-expand > .container-xl { + padding-right: 0; + padding-left: 0; +} +.navbar-expand .navbar-nav { + -ms-flex-direction: row; + flex-direction: row; +} +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute; +} +.navbar-expand .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; +} +.navbar-expand > .container, +.navbar-expand > .container-fluid, +.navbar-expand > .container-lg, +.navbar-expand > .container-md, +.navbar-expand > .container-sm, +.navbar-expand > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; +} +.navbar-expand .navbar-nav-scroll { + overflow: visible; +} +.navbar-expand .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; +} +.navbar-expand .navbar-toggler { + display: none; +} +.navbar-light .navbar-brand { + color: rgba(0, 0, 0, 0.9); +} +.navbar-light .navbar-brand:focus, +.navbar-light .navbar-brand:hover { + color: rgba(0, 0, 0, 0.9); +} +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, 0.5); +} +.navbar-light .navbar-nav .nav-link:focus, +.navbar-light .navbar-nav .nav-link:hover { + color: rgba(0, 0, 0, 0.7); +} +.navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, 0.3); +} +.navbar-light .navbar-nav .active > .nav-link, +.navbar-light .navbar-nav .nav-link.active, +.navbar-light .navbar-nav .nav-link.show, +.navbar-light .navbar-nav .show > .nav-link { + color: rgba(0, 0, 0, 0.9); +} +.navbar-light .navbar-toggler { + color: rgba(0, 0, 0, 0.5); + border-color: rgba(0, 0, 0, 0.1); +} +.navbar-light .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} +.navbar-light .navbar-text { + color: rgba(0, 0, 0, 0.5); +} +.navbar-light .navbar-text a { + color: rgba(0, 0, 0, 0.9); +} +.navbar-light .navbar-text a:focus, +.navbar-light .navbar-text a:hover { + color: rgba(0, 0, 0, 0.9); +} +.navbar-dark .navbar-brand { + color: #fff; +} +.navbar-dark .navbar-brand:focus, +.navbar-dark .navbar-brand:hover { + color: #fff; +} +.navbar-dark .navbar-nav .nav-link { + color: rgba(255, 255, 255, 0.5); +} +.navbar-dark .navbar-nav .nav-link:focus, +.navbar-dark .navbar-nav .nav-link:hover { + color: rgba(255, 255, 255, 0.75); +} +.navbar-dark .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, 0.25); +} +.navbar-dark .navbar-nav .active > .nav-link, +.navbar-dark .navbar-nav .nav-link.active, +.navbar-dark .navbar-nav .nav-link.show, +.navbar-dark .navbar-nav .show > .nav-link { + color: #fff; +} +.navbar-dark .navbar-toggler { + color: rgba(255, 255, 255, 0.5); + border-color: rgba(255, 255, 255, 0.1); +} +.navbar-dark .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} +.navbar-dark .navbar-text { + color: rgba(255, 255, 255, 0.5); +} +.navbar-dark .navbar-text a { + color: #fff; +} +.navbar-dark .navbar-text a:focus, +.navbar-dark .navbar-text a:hover { + color: #fff; +} +.card { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.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 { + -ms-flex: 1 1 auto; + flex: 1 1 auto; + min-height: 1px; + padding: 1.25rem; +} +.card-title { + margin-bottom: 0.75rem; +} +.card-subtitle { + margin-top: -0.375rem; + margin-bottom: 0; +} +.card-text:last-child { + margin-bottom: 0; +} +.card-link:hover { + text-decoration: none; +} +.card-link + .card-link { + margin-left: 1.25rem; +} +.card-header { + padding: 0.75rem 1.25rem; + margin-bottom: 0; + background-color: rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgba(0, 0, 0, 0.125); +} +.card-header:first-child { + border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; +} +.card-footer { + padding: 0.75rem 1.25rem; + background-color: rgba(0, 0, 0, 0.03); + border-top: 1px solid rgba(0, 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.625rem; + margin-bottom: -0.75rem; + margin-left: -0.625rem; + border-bottom: 0; +} +.card-header-pills { + margin-right: -0.625rem; + margin-left: -0.625rem; +} +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 1.25rem; + border-radius: calc(0.25rem - 1px); +} +.card-img, +.card-img-bottom, +.card-img-top { + -ms-flex-negative: 0; + flex-shrink: 0; + 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-deck .card { + margin-bottom: 15px; +} +@media (min-width: 576px) { + .card-deck { + display: -ms-flexbox; + display: flex; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + margin-right: -15px; + margin-left: -15px; + } + .card-deck .card { + -ms-flex: 1 0 0%; + flex: 1 0 0%; + margin-right: 15px; + margin-bottom: 0; + margin-left: 15px; + } +} +.card-group > .card { + margin-bottom: 15px; +} +@media (min-width: 576px) { + .card-group { + display: -ms-flexbox; + display: flex; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + } + .card-group > .card { + -ms-flex: 1 0 0%; + 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-header, + .card-group > .card:not(:last-child) .card-img-top { + border-top-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-footer, + .card-group > .card:not(:last-child) .card-img-bottom { + 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-header, + .card-group > .card:not(:first-child) .card-img-top { + border-top-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-footer, + .card-group > .card:not(:first-child) .card-img-bottom { + border-bottom-left-radius: 0; + } +} +.card-columns .card { + margin-bottom: 0.75rem; +} +@media (min-width: 576px) { + .card-columns { + -webkit-column-count: 3; + -moz-column-count: 3; + column-count: 3; + -webkit-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; + orphans: 1; + widows: 1; + } + .card-columns .card { + display: inline-block; + width: 100%; + } +} +.accordion { + overflow-anchor: none; +} +.accordion > .card { + overflow: hidden; +} +.accordion > .card:not(:last-of-type) { + border-bottom: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.accordion > .card:not(:first-of-type) { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.accordion > .card > .card-header { + border-radius: 0; + margin-bottom: -1px; +} +.breadcrumb { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding: 0.75rem 1rem; + margin-bottom: 1rem; + list-style: none; + background-color: #e9ecef; + border-radius: 0.25rem; +} +.breadcrumb-item + .breadcrumb-item { + padding-left: 0.5rem; +} +.breadcrumb-item + .breadcrumb-item::before { + float: left; + padding-right: 0.5rem; + color: #6c757d; + content: "/"; +} +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: underline; +} +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: none; +} +.breadcrumb-item.active { + color: #6c757d; +} +.pagination { + display: -ms-flexbox; + display: flex; + padding-left: 0; + list-style: none; + border-radius: 0.25rem; +} +.page-link { + position: relative; + display: block; + padding: 0.5rem 0.75rem; + margin-left: -1px; + line-height: 1.25; + color: #007bff; + background-color: #fff; + border: 1px solid #dee2e6; +} +.page-link:hover { + z-index: 2; + color: #0056b3; + text-decoration: none; + background-color: #e9ecef; + border-color: #dee2e6; +} +.page-link:focus { + z-index: 3; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} +.page-item:first-child .page-link { + margin-left: 0; + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} +.page-item:last-child .page-link { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; +} +.page-item.active .page-link { + z-index: 3; + color: #fff; + background-color: #007bff; + border-color: #007bff; +} +.page-item.disabled .page-link { + color: #6c757d; + pointer-events: none; + cursor: auto; + background-color: #fff; + border-color: #dee2e6; +} +.pagination-lg .page-link { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; + line-height: 1.5; +} +.pagination-lg .page-item:first-child .page-link { + border-top-left-radius: 0.3rem; + border-bottom-left-radius: 0.3rem; +} +.pagination-lg .page-item:last-child .page-link { + border-top-right-radius: 0.3rem; + border-bottom-right-radius: 0.3rem; +} +.pagination-sm .page-link { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; +} +.pagination-sm .page-item:first-child .page-link { + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; +} +.pagination-sm .page-item:last-child .page-link { + border-top-right-radius: 0.2rem; + border-bottom-right-radius: 0.2rem; +} +.badge { + display: inline-block; + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25rem; + transition: + color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .badge { + transition: none; + } +} +a.badge:focus, +a.badge:hover { + text-decoration: none; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.badge-pill { + padding-right: 0.6em; + padding-left: 0.6em; + border-radius: 10rem; +} +.badge-primary { + color: #fff; + background-color: #007bff; +} +a.badge-primary:focus, +a.badge-primary:hover { + color: #fff; + background-color: #0062cc; +} +a.badge-primary.focus, +a.badge-primary:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} +.badge-secondary { + color: #fff; + background-color: #6c757d; +} +a.badge-secondary:focus, +a.badge-secondary:hover { + color: #fff; + background-color: #545b62; +} +a.badge-secondary.focus, +a.badge-secondary:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); +} +.badge-success { + color: #fff; + background-color: #28a745; +} +a.badge-success:focus, +a.badge-success:hover { + color: #fff; + background-color: #1e7e34; +} +a.badge-success.focus, +a.badge-success:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} +.badge-info { + color: #fff; + background-color: #17a2b8; +} +a.badge-info:focus, +a.badge-info:hover { + color: #fff; + background-color: #117a8b; +} +a.badge-info.focus, +a.badge-info:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} +.badge-warning { + color: #212529; + background-color: #ffc107; +} +a.badge-warning:focus, +a.badge-warning:hover { + color: #212529; + background-color: #d39e00; +} +a.badge-warning.focus, +a.badge-warning:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} +.badge-danger { + color: #fff; + background-color: #dc3545; +} +a.badge-danger:focus, +a.badge-danger:hover { + color: #fff; + background-color: #bd2130; +} +a.badge-danger.focus, +a.badge-danger:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} +.badge-light { + color: #212529; + background-color: #f8f9fa; +} +a.badge-light:focus, +a.badge-light:hover { + color: #212529; + background-color: #dae0e5; +} +a.badge-light.focus, +a.badge-light:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} +.badge-dark { + color: #fff; + background-color: #343a40; +} +a.badge-dark:focus, +a.badge-dark:hover { + color: #fff; + background-color: #1d2124; +} +a.badge-dark.focus, +a.badge-dark:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} +.jumbotron { + padding: 2rem 1rem; + margin-bottom: 2rem; + background-color: #e9ecef; + border-radius: 0.3rem; +} +@media (min-width: 576px) { + .jumbotron { + padding: 4rem 2rem; + } +} +.jumbotron-fluid { + padding-right: 0; + padding-left: 0; + border-radius: 0; +} +.alert { + position: relative; + padding: 0.75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; +} +.alert-heading { + color: inherit; +} +.alert-link { + font-weight: 700; +} +.alert-dismissible { + padding-right: 4rem; +} +.alert-dismissible .close { + position: absolute; + top: 0; + right: 0; + z-index: 2; + padding: 0.75rem 1.25rem; + color: inherit; +} +.alert-primary { + color: #004085; + background-color: #cce5ff; + border-color: #b8daff; +} +.alert-primary hr { + border-top-color: #9fcdff; +} +.alert-primary .alert-link { + color: #002752; +} +.alert-secondary { + color: #383d41; + background-color: #e2e3e5; + border-color: #d6d8db; +} +.alert-secondary hr { + border-top-color: #c8cbcf; +} +.alert-secondary .alert-link { + color: #202326; +} +.alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; +} +.alert-success hr { + border-top-color: #b1dfbb; +} +.alert-success .alert-link { + color: #0b2e13; +} +.alert-info { + color: #0c5460; + background-color: #d1ecf1; + border-color: #bee5eb; +} +.alert-info hr { + border-top-color: #abdde5; +} +.alert-info .alert-link { + color: #062c33; +} +.alert-warning { + color: #856404; + background-color: #fff3cd; + border-color: #ffeeba; +} +.alert-warning hr { + border-top-color: #ffe8a1; +} +.alert-warning .alert-link { + color: #533f03; +} +.alert-danger { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; +} +.alert-danger hr { + border-top-color: #f1b0b7; +} +.alert-danger .alert-link { + color: #491217; +} +.alert-light { + color: #818182; + background-color: #fefefe; + border-color: #fdfdfe; +} +.alert-light hr { + border-top-color: #ececf6; +} +.alert-light .alert-link { + color: #686868; +} +.alert-dark { + color: #1b1e21; + background-color: #d6d8d9; + border-color: #c6c8ca; +} +.alert-dark hr { + border-top-color: #b9bbbe; +} +.alert-dark .alert-link { + color: #040505; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 1rem 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 1rem 0; + } + to { + background-position: 0 0; + } +} +.progress { + display: -ms-flexbox; + display: flex; + height: 1rem; + overflow: hidden; + line-height: 0; + font-size: 0.75rem; + background-color: #e9ecef; + border-radius: 0.25rem; +} +.progress-bar { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-pack: center; + justify-content: center; + overflow: hidden; + color: #fff; + text-align: center; + white-space: nowrap; + background-color: #007bff; + transition: width 0.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 { + -webkit-animation: 1s linear infinite progress-bar-stripes; + animation: 1s linear infinite progress-bar-stripes; +} +@media (prefers-reduced-motion: reduce) { + .progress-bar-animated { + -webkit-animation: none; + animation: none; + } +} +.media { + display: -ms-flexbox; + display: flex; + -ms-flex-align: start; + align-items: flex-start; +} +.media-body { + -ms-flex: 1; + flex: 1; +} +.list-group { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + border-radius: 0.25rem; +} +.list-group-item-action { + width: 100%; + color: #495057; + text-align: inherit; +} +.list-group-item-action:focus, +.list-group-item-action:hover { + z-index: 1; + color: #495057; + text-decoration: none; + background-color: #f8f9fa; +} +.list-group-item-action:active { + color: #212529; + background-color: #e9ecef; +} +.list-group-item { + position: relative; + display: block; + padding: 0.75rem 1.25rem; + background-color: #fff; + border: 1px solid rgba(0, 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: #007bff; + border-color: #007bff; +} +.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 { + -ms-flex-direction: row; + flex-direction: row; +} +.list-group-horizontal > .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; +} +.list-group-horizontal > .list-group-item:last-child { + border-top-right-radius: 0.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 { + -ms-flex-direction: row; + flex-direction: row; + } + .list-group-horizontal-sm > .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; + } + .list-group-horizontal-sm > .list-group-item:last-child { + border-top-right-radius: 0.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 { + -ms-flex-direction: row; + flex-direction: row; + } + .list-group-horizontal-md > .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; + } + .list-group-horizontal-md > .list-group-item:last-child { + border-top-right-radius: 0.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 { + -ms-flex-direction: row; + flex-direction: row; + } + .list-group-horizontal-lg > .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; + } + .list-group-horizontal-lg > .list-group-item:last-child { + border-top-right-radius: 0.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 { + -ms-flex-direction: row; + flex-direction: row; + } + .list-group-horizontal-xl > .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; + } + .list-group-horizontal-xl > .list-group-item:last-child { + border-top-right-radius: 0.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; + } +} +.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-primary { + color: #004085; + background-color: #b8daff; +} +.list-group-item-primary.list-group-item-action:focus, +.list-group-item-primary.list-group-item-action:hover { + color: #004085; + background-color: #9fcdff; +} +.list-group-item-primary.list-group-item-action.active { + color: #fff; + background-color: #004085; + border-color: #004085; +} +.list-group-item-secondary { + color: #383d41; + background-color: #d6d8db; +} +.list-group-item-secondary.list-group-item-action:focus, +.list-group-item-secondary.list-group-item-action:hover { + color: #383d41; + background-color: #c8cbcf; +} +.list-group-item-secondary.list-group-item-action.active { + color: #fff; + background-color: #383d41; + border-color: #383d41; +} +.list-group-item-success { + color: #155724; + background-color: #c3e6cb; +} +.list-group-item-success.list-group-item-action:focus, +.list-group-item-success.list-group-item-action:hover { + color: #155724; + background-color: #b1dfbb; +} +.list-group-item-success.list-group-item-action.active { + color: #fff; + background-color: #155724; + border-color: #155724; +} +.list-group-item-info { + color: #0c5460; + background-color: #bee5eb; +} +.list-group-item-info.list-group-item-action:focus, +.list-group-item-info.list-group-item-action:hover { + color: #0c5460; + background-color: #abdde5; +} +.list-group-item-info.list-group-item-action.active { + color: #fff; + background-color: #0c5460; + border-color: #0c5460; +} +.list-group-item-warning { + color: #856404; + background-color: #ffeeba; +} +.list-group-item-warning.list-group-item-action:focus, +.list-group-item-warning.list-group-item-action:hover { + color: #856404; + background-color: #ffe8a1; +} +.list-group-item-warning.list-group-item-action.active { + color: #fff; + background-color: #856404; + border-color: #856404; +} +.list-group-item-danger { + color: #721c24; + background-color: #f5c6cb; +} +.list-group-item-danger.list-group-item-action:focus, +.list-group-item-danger.list-group-item-action:hover { + color: #721c24; + background-color: #f1b0b7; +} +.list-group-item-danger.list-group-item-action.active { + color: #fff; + background-color: #721c24; + border-color: #721c24; +} +.list-group-item-light { + color: #818182; + background-color: #fdfdfe; +} +.list-group-item-light.list-group-item-action:focus, +.list-group-item-light.list-group-item-action:hover { + color: #818182; + background-color: #ececf6; +} +.list-group-item-light.list-group-item-action.active { + color: #fff; + background-color: #818182; + border-color: #818182; +} +.list-group-item-dark { + color: #1b1e21; + background-color: #c6c8ca; +} +.list-group-item-dark.list-group-item-action:focus, +.list-group-item-dark.list-group-item-action:hover { + color: #1b1e21; + background-color: #b9bbbe; +} +.list-group-item-dark.list-group-item-action.active { + color: #fff; + background-color: #1b1e21; + border-color: #1b1e21; +} +.close { + float: right; + font-size: 1.5rem; + font-weight: 700; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + opacity: 0.5; +} +.close:hover { + color: #000; + text-decoration: none; +} +.close:not(:disabled):not(.disabled):focus, +.close:not(:disabled):not(.disabled):hover { + opacity: 0.75; +} +button.close { + padding: 0; + background-color: transparent; + border: 0; +} +a.close.disabled { + pointer-events: none; +} +.toast { + -ms-flex-preferred-size: 350px; + flex-basis: 350px; + max-width: 350px; + font-size: 0.875rem; + background-color: rgba(255, 255, 255, 0.85); + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1); + opacity: 0; + border-radius: 0.25rem; +} +.toast:not(:last-child) { + margin-bottom: 0.75rem; +} +.toast.showing { + opacity: 1; +} +.toast.show { + display: block; + opacity: 1; +} +.toast.hide { + display: none; +} +.toast-header { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + padding: 0.25rem 0.75rem; + color: #6c757d; + background-color: rgba(255, 255, 255, 0.85); + background-clip: padding-box; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); +} +.toast-body { + padding: 0.75rem; +} +.modal-open { + overflow: hidden; +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal { + position: fixed; + top: 0; + left: 0; + z-index: 1050; + display: none; + width: 100%; + height: 100%; + overflow: hidden; + outline: 0; +} +.modal-dialog { + position: relative; + width: auto; + margin: 0.5rem; + pointer-events: none; +} +.modal.fade .modal-dialog { + transition: -webkit-transform 0.3s ease-out; + transition: transform 0.3s ease-out; + transition: + transform 0.3s ease-out, + -webkit-transform 0.3s ease-out; + -webkit-transform: translate(0, -50px); + transform: translate(0, -50px); +} +@media (prefers-reduced-motion: reduce) { + .modal.fade .modal-dialog { + transition: none; + } +} +.modal.show .modal-dialog { + -webkit-transform: none; + transform: none; +} +.modal.modal-static .modal-dialog { + -webkit-transform: scale(1.02); + transform: scale(1.02); +} +.modal-dialog-scrollable { + display: -ms-flexbox; + display: flex; + max-height: calc(100% - 1rem); +} +.modal-dialog-scrollable .modal-content { + max-height: calc(100vh - 1rem); + overflow: hidden; +} +.modal-dialog-scrollable .modal-footer, +.modal-dialog-scrollable .modal-header { + -ms-flex-negative: 0; + flex-shrink: 0; +} +.modal-dialog-scrollable .modal-body { + overflow-y: auto; +} +.modal-dialog-centered { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + min-height: calc(100% - 1rem); +} +.modal-dialog-centered::before { + display: block; + height: calc(100vh - 1rem); + height: -webkit-min-content; + height: -moz-min-content; + height: min-content; + content: ""; +} +.modal-dialog-centered.modal-dialog-scrollable { + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-pack: center; + justify-content: center; + height: 100%; +} +.modal-dialog-centered.modal-dialog-scrollable .modal-content { + max-height: none; +} +.modal-dialog-centered.modal-dialog-scrollable::before { + content: none; +} +.modal-content { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + width: 100%; + pointer-events: auto; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; + outline: 0; +} +.modal-backdrop { + position: fixed; + top: 0; + left: 0; + z-index: 1040; + width: 100vw; + height: 100vh; + background-color: #000; +} +.modal-backdrop.fade { + opacity: 0; +} +.modal-backdrop.show { + opacity: 0.5; +} +.modal-header { + display: -ms-flexbox; + display: flex; + -ms-flex-align: start; + align-items: flex-start; + -ms-flex-pack: justify; + 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 .close { + padding: 1rem 1rem; + margin: -1rem -1rem -1rem auto; +} +.modal-title { + margin-bottom: 0; + line-height: 1.5; +} +.modal-body { + position: relative; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 1rem; +} +.modal-footer { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: end; + justify-content: flex-end; + padding: 0.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: 0.25rem; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 576px) { + .modal-dialog { + max-width: 500px; + margin: 1.75rem auto; + } + .modal-dialog-scrollable { + max-height: calc(100% - 3.5rem); + } + .modal-dialog-scrollable .modal-content { + max-height: calc(100vh - 3.5rem); + } + .modal-dialog-centered { + min-height: calc(100% - 3.5rem); + } + .modal-dialog-centered::before { + height: calc(100vh - 3.5rem); + height: -webkit-min-content; + height: -moz-min-content; + height: min-content; + } + .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; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + 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: 0.9; +} +.tooltip .arrow { + position: absolute; + display: block; + width: 0.8rem; + height: 0.4rem; +} +.tooltip .arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; +} +.bs-tooltip-auto[x-placement^="top"], +.bs-tooltip-top { + padding: 0.4rem 0; +} +.bs-tooltip-auto[x-placement^="top"] .arrow, +.bs-tooltip-top .arrow { + bottom: 0; +} +.bs-tooltip-auto[x-placement^="top"] .arrow::before, +.bs-tooltip-top .arrow::before { + top: 0; + border-width: 0.4rem 0.4rem 0; + border-top-color: #000; +} +.bs-tooltip-auto[x-placement^="right"], +.bs-tooltip-right { + padding: 0 0.4rem; +} +.bs-tooltip-auto[x-placement^="right"] .arrow, +.bs-tooltip-right .arrow { + left: 0; + width: 0.4rem; + height: 0.8rem; +} +.bs-tooltip-auto[x-placement^="right"] .arrow::before, +.bs-tooltip-right .arrow::before { + right: 0; + border-width: 0.4rem 0.4rem 0.4rem 0; + border-right-color: #000; +} +.bs-tooltip-auto[x-placement^="bottom"], +.bs-tooltip-bottom { + padding: 0.4rem 0; +} +.bs-tooltip-auto[x-placement^="bottom"] .arrow, +.bs-tooltip-bottom .arrow { + top: 0; +} +.bs-tooltip-auto[x-placement^="bottom"] .arrow::before, +.bs-tooltip-bottom .arrow::before { + bottom: 0; + border-width: 0 0.4rem 0.4rem; + border-bottom-color: #000; +} +.bs-tooltip-auto[x-placement^="left"], +.bs-tooltip-left { + padding: 0 0.4rem; +} +.bs-tooltip-auto[x-placement^="left"] .arrow, +.bs-tooltip-left .arrow { + right: 0; + width: 0.4rem; + height: 0.8rem; +} +.bs-tooltip-auto[x-placement^="left"] .arrow::before, +.bs-tooltip-left .arrow::before { + left: 0; + border-width: 0.4rem 0 0.4rem 0.4rem; + border-left-color: #000; +} +.tooltip-inner { + max-width: 200px; + padding: 0.25rem 0.5rem; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 0.25rem; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: block; + max-width: 276px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + 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, 0.2); + border-radius: 0.3rem; +} +.popover .arrow { + position: absolute; + display: block; + width: 1rem; + height: 0.5rem; + margin: 0 0.3rem; +} +.popover .arrow::after, +.popover .arrow::before { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; +} +.bs-popover-auto[x-placement^="top"], +.bs-popover-top { + margin-bottom: 0.5rem; +} +.bs-popover-auto[x-placement^="top"] > .arrow, +.bs-popover-top > .arrow { + bottom: calc(-0.5rem - 1px); +} +.bs-popover-auto[x-placement^="top"] > .arrow::before, +.bs-popover-top > .arrow::before { + bottom: 0; + border-width: 0.5rem 0.5rem 0; + border-top-color: rgba(0, 0, 0, 0.25); +} +.bs-popover-auto[x-placement^="top"] > .arrow::after, +.bs-popover-top > .arrow::after { + bottom: 1px; + border-width: 0.5rem 0.5rem 0; + border-top-color: #fff; +} +.bs-popover-auto[x-placement^="right"], +.bs-popover-right { + margin-left: 0.5rem; +} +.bs-popover-auto[x-placement^="right"] > .arrow, +.bs-popover-right > .arrow { + left: calc(-0.5rem - 1px); + width: 0.5rem; + height: 1rem; + margin: 0.3rem 0; +} +.bs-popover-auto[x-placement^="right"] > .arrow::before, +.bs-popover-right > .arrow::before { + left: 0; + border-width: 0.5rem 0.5rem 0.5rem 0; + border-right-color: rgba(0, 0, 0, 0.25); +} +.bs-popover-auto[x-placement^="right"] > .arrow::after, +.bs-popover-right > .arrow::after { + left: 1px; + border-width: 0.5rem 0.5rem 0.5rem 0; + border-right-color: #fff; +} +.bs-popover-auto[x-placement^="bottom"], +.bs-popover-bottom { + margin-top: 0.5rem; +} +.bs-popover-auto[x-placement^="bottom"] > .arrow, +.bs-popover-bottom > .arrow { + top: calc(-0.5rem - 1px); +} +.bs-popover-auto[x-placement^="bottom"] > .arrow::before, +.bs-popover-bottom > .arrow::before { + top: 0; + border-width: 0 0.5rem 0.5rem 0.5rem; + border-bottom-color: rgba(0, 0, 0, 0.25); +} +.bs-popover-auto[x-placement^="bottom"] > .arrow::after, +.bs-popover-bottom > .arrow::after { + top: 1px; + border-width: 0 0.5rem 0.5rem 0.5rem; + border-bottom-color: #fff; +} +.bs-popover-auto[x-placement^="bottom"] .popover-header::before, +.bs-popover-bottom .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 1rem; + margin-left: -0.5rem; + content: ""; + border-bottom: 1px solid #f7f7f7; +} +.bs-popover-auto[x-placement^="left"], +.bs-popover-left { + margin-right: 0.5rem; +} +.bs-popover-auto[x-placement^="left"] > .arrow, +.bs-popover-left > .arrow { + right: calc(-0.5rem - 1px); + width: 0.5rem; + height: 1rem; + margin: 0.3rem 0; +} +.bs-popover-auto[x-placement^="left"] > .arrow::before, +.bs-popover-left > .arrow::before { + right: 0; + border-width: 0.5rem 0 0.5rem 0.5rem; + border-left-color: rgba(0, 0, 0, 0.25); +} +.bs-popover-auto[x-placement^="left"] > .arrow::after, +.bs-popover-left > .arrow::after { + right: 1px; + border-width: 0.5rem 0 0.5rem 0.5rem; + border-left-color: #fff; +} +.popover-header { + padding: 0.5rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-top-left-radius: calc(0.3rem - 1px); + border-top-right-radius: calc(0.3rem - 1px); +} +.popover-header:empty { + display: none; +} +.popover-body { + padding: 0.5rem 0.75rem; + color: #212529; +} +.carousel { + position: relative; +} +.carousel.pointer-event { + -ms-touch-action: pan-y; + 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%; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + transition: -webkit-transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out; + transition: + transform 0.6s ease-in-out, + -webkit-transform 0.6s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .carousel-item { + transition: none; + } +} +.carousel-item-next, +.carousel-item-prev, +.carousel-item.active { + display: block; +} +.active.carousel-item-right, +.carousel-item-next:not(.carousel-item-left) { + -webkit-transform: translateX(100%); + transform: translateX(100%); +} +.active.carousel-item-left, +.carousel-item-prev:not(.carousel-item-right) { + -webkit-transform: translateX(-100%); + transform: translateX(-100%); +} +.carousel-fade .carousel-item { + opacity: 0; + transition-property: opacity; + -webkit-transform: none; + transform: none; +} +.carousel-fade .carousel-item-next.carousel-item-left, +.carousel-fade .carousel-item-prev.carousel-item-right, +.carousel-fade .carousel-item.active { + z-index: 1; + opacity: 1; +} +.carousel-fade .active.carousel-item-left, +.carousel-fade .active.carousel-item-right { + z-index: 0; + opacity: 0; + transition: opacity 0s 0.6s; +} +@media (prefers-reduced-motion: reduce) { + .carousel-fade .active.carousel-item-left, + .carousel-fade .active.carousel-item-right { + transition: none; + } +} +.carousel-control-next, +.carousel-control-prev { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + width: 15%; + padding: 0; + color: #fff; + text-align: center; + background: 0 0; + border: 0; + opacity: 0.5; + transition: opacity 0.15s ease; +} +@media (prefers-reduced-motion: reduce) { + .carousel-control-next, + .carousel-control-prev { + transition: none; + } +} +.carousel-control-next:focus, +.carousel-control-next:hover, +.carousel-control-prev:focus, +.carousel-control-prev:hover { + color: #fff; + text-decoration: none; + outline: 0; + opacity: 0.9; +} +.carousel-control-prev { + left: 0; +} +.carousel-control-next { + right: 0; +} +.carousel-control-next-icon, +.carousel-control-prev-icon { + display: inline-block; + width: 20px; + height: 20px; + background: 50%/100% 100% no-repeat; +} +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e"); +} +.carousel-control-next-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e"); +} +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 15; + display: -ms-flexbox; + display: flex; + -ms-flex-pack: center; + justify-content: center; + padding-left: 0; + margin-right: 15%; + margin-left: 15%; + list-style: none; +} +.carousel-indicators li { + box-sizing: content-box; + -ms-flex: 0 1 auto; + flex: 0 1 auto; + width: 30px; + height: 3px; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + cursor: pointer; + background-color: #fff; + background-clip: padding-box; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + opacity: 0.5; + transition: opacity 0.6s ease; +} +@media (prefers-reduced-motion: reduce) { + .carousel-indicators li { + transition: none; + } +} +.carousel-indicators .active { + opacity: 1; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; +} +@-webkit-keyframes spinner-border { + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes spinner-border { + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +.spinner-border { + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: -0.125em; + border: 0.25em solid currentColor; + border-right-color: transparent; + border-radius: 50%; + -webkit-animation: 0.75s linear infinite spinner-border; + animation: 0.75s linear infinite spinner-border; +} +.spinner-border-sm { + width: 1rem; + height: 1rem; + border-width: 0.2em; +} +@-webkit-keyframes spinner-grow { + 0% { + -webkit-transform: scale(0); + transform: scale(0); + } + 50% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} +@keyframes spinner-grow { + 0% { + -webkit-transform: scale(0); + transform: scale(0); + } + 50% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} +.spinner-grow { + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: -0.125em; + background-color: currentColor; + border-radius: 50%; + opacity: 0; + -webkit-animation: 0.75s linear infinite spinner-grow; + animation: 0.75s linear infinite spinner-grow; +} +.spinner-grow-sm { + width: 1rem; + height: 1rem; +} +@media (prefers-reduced-motion: reduce) { + .spinner-border, + .spinner-grow { + -webkit-animation-duration: 1.5s; + animation-duration: 1.5s; + } +} +.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; +} +.bg-primary { + background-color: #007bff !important; +} +a.bg-primary:focus, +a.bg-primary:hover, +button.bg-primary:focus, +button.bg-primary:hover { + background-color: #0062cc !important; +} +.bg-secondary { + background-color: #6c757d !important; +} +a.bg-secondary:focus, +a.bg-secondary:hover, +button.bg-secondary:focus, +button.bg-secondary:hover { + background-color: #545b62 !important; +} +.bg-success { + background-color: #28a745 !important; +} +a.bg-success:focus, +a.bg-success:hover, +button.bg-success:focus, +button.bg-success:hover { + background-color: #1e7e34 !important; +} +.bg-info { + background-color: #17a2b8 !important; +} +a.bg-info:focus, +a.bg-info:hover, +button.bg-info:focus, +button.bg-info:hover { + background-color: #117a8b !important; +} +.bg-warning { + background-color: #ffc107 !important; +} +a.bg-warning:focus, +a.bg-warning:hover, +button.bg-warning:focus, +button.bg-warning:hover { + background-color: #d39e00 !important; +} +.bg-danger { + background-color: #dc3545 !important; +} +a.bg-danger:focus, +a.bg-danger:hover, +button.bg-danger:focus, +button.bg-danger:hover { + background-color: #bd2130 !important; +} +.bg-light { + background-color: #f8f9fa !important; +} +a.bg-light:focus, +a.bg-light:hover, +button.bg-light:focus, +button.bg-light:hover { + background-color: #dae0e5 !important; +} +.bg-dark { + background-color: #343a40 !important; +} +a.bg-dark:focus, +a.bg-dark:hover, +button.bg-dark:focus, +button.bg-dark:hover { + background-color: #1d2124 !important; +} +.bg-white { + background-color: #fff !important; +} +.bg-transparent { + background-color: transparent !important; +} +.border { + border: 1px solid #dee2e6 !important; +} +.border-top { + border-top: 1px solid #dee2e6 !important; +} +.border-right { + border-right: 1px solid #dee2e6 !important; +} +.border-bottom { + border-bottom: 1px solid #dee2e6 !important; +} +.border-left { + border-left: 1px solid #dee2e6 !important; +} +.border-0 { + border: 0 !important; +} +.border-top-0 { + border-top: 0 !important; +} +.border-right-0 { + border-right: 0 !important; +} +.border-bottom-0 { + border-bottom: 0 !important; +} +.border-left-0 { + border-left: 0 !important; +} +.border-primary { + border-color: #007bff !important; +} +.border-secondary { + border-color: #6c757d !important; +} +.border-success { + border-color: #28a745 !important; +} +.border-info { + border-color: #17a2b8 !important; +} +.border-warning { + border-color: #ffc107 !important; +} +.border-danger { + border-color: #dc3545 !important; +} +.border-light { + border-color: #f8f9fa !important; +} +.border-dark { + border-color: #343a40 !important; +} +.border-white { + border-color: #fff !important; +} +.rounded-sm { + border-radius: 0.2rem !important; +} +.rounded { + border-radius: 0.25rem !important; +} +.rounded-top { + border-top-left-radius: 0.25rem !important; + border-top-right-radius: 0.25rem !important; +} +.rounded-right { + border-top-right-radius: 0.25rem !important; + border-bottom-right-radius: 0.25rem !important; +} +.rounded-bottom { + border-bottom-right-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; +} +.rounded-left { + border-top-left-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; +} +.rounded-lg { + border-radius: 0.3rem !important; +} +.rounded-circle { + border-radius: 50% !important; +} +.rounded-pill { + border-radius: 50rem !important; +} +.rounded-0 { + border-radius: 0 !important; +} +.clearfix::after { + display: block; + clear: both; + content: ""; +} +.d-none { + display: none !important; +} +.d-inline { + display: inline !important; +} +.d-inline-block { + display: inline-block !important; +} +.d-block { + display: block !important; +} +.d-table { + display: table !important; +} +.d-table-row { + display: table-row !important; +} +.d-table-cell { + display: table-cell !important; +} +.d-flex { + display: -ms-flexbox !important; + display: flex !important; +} +.d-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; +} +@media (min-width: 576px) { + .d-sm-none { + display: none !important; + } + .d-sm-inline { + display: inline !important; + } + .d-sm-inline-block { + display: inline-block !important; + } + .d-sm-block { + display: block !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: -ms-flexbox !important; + display: flex !important; + } + .d-sm-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +@media (min-width: 768px) { + .d-md-none { + display: none !important; + } + .d-md-inline { + display: inline !important; + } + .d-md-inline-block { + display: inline-block !important; + } + .d-md-block { + display: block !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: -ms-flexbox !important; + display: flex !important; + } + .d-md-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +@media (min-width: 992px) { + .d-lg-none { + display: none !important; + } + .d-lg-inline { + display: inline !important; + } + .d-lg-inline-block { + display: inline-block !important; + } + .d-lg-block { + display: block !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: -ms-flexbox !important; + display: flex !important; + } + .d-lg-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +@media (min-width: 1200px) { + .d-xl-none { + display: none !important; + } + .d-xl-inline { + display: inline !important; + } + .d-xl-inline-block { + display: inline-block !important; + } + .d-xl-block { + display: block !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: -ms-flexbox !important; + display: flex !important; + } + .d-xl-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +@media print { + .d-print-none { + display: none !important; + } + .d-print-inline { + display: inline !important; + } + .d-print-inline-block { + display: inline-block !important; + } + .d-print-block { + display: block !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: -ms-flexbox !important; + display: flex !important; + } + .d-print-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} +.embed-responsive { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden; +} +.embed-responsive::before { + display: block; + content: ""; +} +.embed-responsive .embed-responsive-item, +.embed-responsive embed, +.embed-responsive iframe, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} +.embed-responsive-21by9::before { + padding-top: 42.857143%; +} +.embed-responsive-16by9::before { + padding-top: 56.25%; +} +.embed-responsive-4by3::before { + padding-top: 75%; +} +.embed-responsive-1by1::before { + padding-top: 100%; +} +.flex-row { + -ms-flex-direction: row !important; + flex-direction: row !important; +} +.flex-column { + -ms-flex-direction: column !important; + flex-direction: column !important; +} +.flex-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; +} +.flex-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; +} +.flex-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; +} +.flex-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; +} +.flex-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; +} +.flex-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; +} +.flex-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; +} +.flex-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; +} +.flex-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; +} +.flex-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; +} +.justify-content-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; +} +.justify-content-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; +} +.justify-content-center { + -ms-flex-pack: center !important; + justify-content: center !important; +} +.justify-content-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; +} +.justify-content-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; +} +.align-items-start { + -ms-flex-align: start !important; + align-items: flex-start !important; +} +.align-items-end { + -ms-flex-align: end !important; + align-items: flex-end !important; +} +.align-items-center { + -ms-flex-align: center !important; + align-items: center !important; +} +.align-items-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; +} +.align-items-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; +} +.align-content-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; +} +.align-content-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; +} +.align-content-center { + -ms-flex-line-pack: center !important; + align-content: center !important; +} +.align-content-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; +} +.align-content-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; +} +.align-content-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; +} +.align-self-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; +} +.align-self-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; +} +.align-self-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; +} +.align-self-center { + -ms-flex-item-align: center !important; + align-self: center !important; +} +.align-self-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; +} +.align-self-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; +} +@media (min-width: 576px) { + .flex-sm-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-sm-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-sm-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-sm-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-sm-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-sm-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-sm-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-sm-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-sm-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-sm-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-sm-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-sm-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-sm-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-sm-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-sm-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-sm-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-sm-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-sm-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-sm-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-sm-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-sm-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-sm-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-sm-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-sm-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-sm-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-sm-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-sm-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-sm-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-sm-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-sm-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-sm-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-sm-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-sm-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-sm-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +@media (min-width: 768px) { + .flex-md-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-md-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-md-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-md-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-md-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-md-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-md-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-md-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-md-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-md-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-md-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-md-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-md-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-md-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-md-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-md-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-md-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-md-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-md-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-md-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-md-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-md-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-md-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-md-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-md-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-md-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-md-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-md-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-md-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-md-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-md-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-md-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-md-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-md-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +@media (min-width: 992px) { + .flex-lg-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-lg-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-lg-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-lg-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-lg-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-lg-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-lg-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-lg-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-lg-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-lg-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-lg-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-lg-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-lg-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-lg-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-lg-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-lg-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-lg-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-lg-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-lg-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-lg-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-lg-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-lg-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-lg-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-lg-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-lg-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-lg-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-lg-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-lg-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-lg-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-lg-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-lg-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-lg-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-lg-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-lg-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +@media (min-width: 1200px) { + .flex-xl-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-xl-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-xl-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-xl-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-xl-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-xl-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-xl-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-xl-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-xl-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-xl-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-xl-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-xl-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-xl-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-xl-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-xl-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-xl-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-xl-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-xl-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-xl-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-xl-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-xl-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-xl-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-xl-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-xl-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-xl-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-xl-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-xl-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-xl-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-xl-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-xl-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-xl-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-xl-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-xl-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-xl-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +.float-left { + float: left !important; +} +.float-right { + float: right !important; +} +.float-none { + float: none !important; +} +@media (min-width: 576px) { + .float-sm-left { + float: left !important; + } + .float-sm-right { + float: right !important; + } + .float-sm-none { + float: none !important; + } +} +@media (min-width: 768px) { + .float-md-left { + float: left !important; + } + .float-md-right { + float: right !important; + } + .float-md-none { + float: none !important; + } +} +@media (min-width: 992px) { + .float-lg-left { + float: left !important; + } + .float-lg-right { + float: right !important; + } + .float-lg-none { + float: none !important; + } +} +@media (min-width: 1200px) { + .float-xl-left { + float: left !important; + } + .float-xl-right { + float: right !important; + } + .float-xl-none { + float: none !important; + } +} +.user-select-all { + -webkit-user-select: all !important; + -moz-user-select: all !important; + user-select: all !important; +} +.user-select-auto { + -webkit-user-select: auto !important; + -moz-user-select: auto !important; + -ms-user-select: auto !important; + user-select: auto !important; +} +.user-select-none { + -webkit-user-select: none !important; + -moz-user-select: none !important; + -ms-user-select: none !important; + user-select: none !important; +} +.overflow-auto { + overflow: auto !important; +} +.overflow-hidden { + overflow: hidden !important; +} +.position-static { + position: static !important; +} +.position-relative { + position: relative !important; +} +.position-absolute { + position: absolute !important; +} +.position-fixed { + position: fixed !important; +} +.position-sticky { + position: -webkit-sticky !important; + position: sticky !important; +} +.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; +} +@supports ((position: -webkit-sticky) or (position: sticky)) { + .sticky-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + overflow: visible; + clip: auto; + white-space: normal; +} +.shadow-sm { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; +} +.shadow { + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; +} +.shadow-lg { + box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important; +} +.shadow-none { + box-shadow: none !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; +} +.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; +} +.mw-100 { + max-width: 100% !important; +} +.mh-100 { + max-height: 100% !important; +} +.min-vw-100 { + min-width: 100vw !important; +} +.min-vh-100 { + min-height: 100vh !important; +} +.vw-100 { + width: 100vw !important; +} +.vh-100 { + height: 100vh !important; +} +.m-0 { + margin: 0 !important; +} +.mt-0, +.my-0 { + margin-top: 0 !important; +} +.mr-0, +.mx-0 { + margin-right: 0 !important; +} +.mb-0, +.my-0 { + margin-bottom: 0 !important; +} +.ml-0, +.mx-0 { + margin-left: 0 !important; +} +.m-1 { + margin: 0.25rem !important; +} +.mt-1, +.my-1 { + margin-top: 0.25rem !important; +} +.mr-1, +.mx-1 { + margin-right: 0.25rem !important; +} +.mb-1, +.my-1 { + margin-bottom: 0.25rem !important; +} +.ml-1, +.mx-1 { + margin-left: 0.25rem !important; +} +.m-2 { + margin: 0.5rem !important; +} +.mt-2, +.my-2 { + margin-top: 0.5rem !important; +} +.mr-2, +.mx-2 { + margin-right: 0.5rem !important; +} +.mb-2, +.my-2 { + margin-bottom: 0.5rem !important; +} +.ml-2, +.mx-2 { + margin-left: 0.5rem !important; +} +.m-3 { + margin: 1rem !important; +} +.mt-3, +.my-3 { + margin-top: 1rem !important; +} +.mr-3, +.mx-3 { + margin-right: 1rem !important; +} +.mb-3, +.my-3 { + margin-bottom: 1rem !important; +} +.ml-3, +.mx-3 { + margin-left: 1rem !important; +} +.m-4 { + margin: 1.5rem !important; +} +.mt-4, +.my-4 { + margin-top: 1.5rem !important; +} +.mr-4, +.mx-4 { + margin-right: 1.5rem !important; +} +.mb-4, +.my-4 { + margin-bottom: 1.5rem !important; +} +.ml-4, +.mx-4 { + margin-left: 1.5rem !important; +} +.m-5 { + margin: 3rem !important; +} +.mt-5, +.my-5 { + margin-top: 3rem !important; +} +.mr-5, +.mx-5 { + margin-right: 3rem !important; +} +.mb-5, +.my-5 { + margin-bottom: 3rem !important; +} +.ml-5, +.mx-5 { + margin-left: 3rem !important; +} +.p-0 { + padding: 0 !important; +} +.pt-0, +.py-0 { + padding-top: 0 !important; +} +.pr-0, +.px-0 { + padding-right: 0 !important; +} +.pb-0, +.py-0 { + padding-bottom: 0 !important; +} +.pl-0, +.px-0 { + padding-left: 0 !important; +} +.p-1 { + padding: 0.25rem !important; +} +.pt-1, +.py-1 { + padding-top: 0.25rem !important; +} +.pr-1, +.px-1 { + padding-right: 0.25rem !important; +} +.pb-1, +.py-1 { + padding-bottom: 0.25rem !important; +} +.pl-1, +.px-1 { + padding-left: 0.25rem !important; +} +.p-2 { + padding: 0.5rem !important; +} +.pt-2, +.py-2 { + padding-top: 0.5rem !important; +} +.pr-2, +.px-2 { + padding-right: 0.5rem !important; +} +.pb-2, +.py-2 { + padding-bottom: 0.5rem !important; +} +.pl-2, +.px-2 { + padding-left: 0.5rem !important; +} +.p-3 { + padding: 1rem !important; +} +.pt-3, +.py-3 { + padding-top: 1rem !important; +} +.pr-3, +.px-3 { + padding-right: 1rem !important; +} +.pb-3, +.py-3 { + padding-bottom: 1rem !important; +} +.pl-3, +.px-3 { + padding-left: 1rem !important; +} +.p-4 { + padding: 1.5rem !important; +} +.pt-4, +.py-4 { + padding-top: 1.5rem !important; +} +.pr-4, +.px-4 { + padding-right: 1.5rem !important; +} +.pb-4, +.py-4 { + padding-bottom: 1.5rem !important; +} +.pl-4, +.px-4 { + padding-left: 1.5rem !important; +} +.p-5 { + padding: 3rem !important; +} +.pt-5, +.py-5 { + padding-top: 3rem !important; +} +.pr-5, +.px-5 { + padding-right: 3rem !important; +} +.pb-5, +.py-5 { + padding-bottom: 3rem !important; +} +.pl-5, +.px-5 { + padding-left: 3rem !important; +} +.m-n1 { + margin: -0.25rem !important; +} +.mt-n1, +.my-n1 { + margin-top: -0.25rem !important; +} +.mr-n1, +.mx-n1 { + margin-right: -0.25rem !important; +} +.mb-n1, +.my-n1 { + margin-bottom: -0.25rem !important; +} +.ml-n1, +.mx-n1 { + margin-left: -0.25rem !important; +} +.m-n2 { + margin: -0.5rem !important; +} +.mt-n2, +.my-n2 { + margin-top: -0.5rem !important; +} +.mr-n2, +.mx-n2 { + margin-right: -0.5rem !important; +} +.mb-n2, +.my-n2 { + margin-bottom: -0.5rem !important; +} +.ml-n2, +.mx-n2 { + margin-left: -0.5rem !important; +} +.m-n3 { + margin: -1rem !important; +} +.mt-n3, +.my-n3 { + margin-top: -1rem !important; +} +.mr-n3, +.mx-n3 { + margin-right: -1rem !important; +} +.mb-n3, +.my-n3 { + margin-bottom: -1rem !important; +} +.ml-n3, +.mx-n3 { + margin-left: -1rem !important; +} +.m-n4 { + margin: -1.5rem !important; +} +.mt-n4, +.my-n4 { + margin-top: -1.5rem !important; +} +.mr-n4, +.mx-n4 { + margin-right: -1.5rem !important; +} +.mb-n4, +.my-n4 { + margin-bottom: -1.5rem !important; +} +.ml-n4, +.mx-n4 { + margin-left: -1.5rem !important; +} +.m-n5 { + margin: -3rem !important; +} +.mt-n5, +.my-n5 { + margin-top: -3rem !important; +} +.mr-n5, +.mx-n5 { + margin-right: -3rem !important; +} +.mb-n5, +.my-n5 { + margin-bottom: -3rem !important; +} +.ml-n5, +.mx-n5 { + margin-left: -3rem !important; +} +.m-auto { + margin: auto !important; +} +.mt-auto, +.my-auto { + margin-top: auto !important; +} +.mr-auto, +.mx-auto { + margin-right: auto !important; +} +.mb-auto, +.my-auto { + margin-bottom: auto !important; +} +.ml-auto, +.mx-auto { + margin-left: auto !important; +} +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 !important; + } + .mt-sm-0, + .my-sm-0 { + margin-top: 0 !important; + } + .mr-sm-0, + .mx-sm-0 { + margin-right: 0 !important; + } + .mb-sm-0, + .my-sm-0 { + margin-bottom: 0 !important; + } + .ml-sm-0, + .mx-sm-0 { + margin-left: 0 !important; + } + .m-sm-1 { + margin: 0.25rem !important; + } + .mt-sm-1, + .my-sm-1 { + margin-top: 0.25rem !important; + } + .mr-sm-1, + .mx-sm-1 { + margin-right: 0.25rem !important; + } + .mb-sm-1, + .my-sm-1 { + margin-bottom: 0.25rem !important; + } + .ml-sm-1, + .mx-sm-1 { + margin-left: 0.25rem !important; + } + .m-sm-2 { + margin: 0.5rem !important; + } + .mt-sm-2, + .my-sm-2 { + margin-top: 0.5rem !important; + } + .mr-sm-2, + .mx-sm-2 { + margin-right: 0.5rem !important; + } + .mb-sm-2, + .my-sm-2 { + margin-bottom: 0.5rem !important; + } + .ml-sm-2, + .mx-sm-2 { + margin-left: 0.5rem !important; + } + .m-sm-3 { + margin: 1rem !important; + } + .mt-sm-3, + .my-sm-3 { + margin-top: 1rem !important; + } + .mr-sm-3, + .mx-sm-3 { + margin-right: 1rem !important; + } + .mb-sm-3, + .my-sm-3 { + margin-bottom: 1rem !important; + } + .ml-sm-3, + .mx-sm-3 { + margin-left: 1rem !important; + } + .m-sm-4 { + margin: 1.5rem !important; + } + .mt-sm-4, + .my-sm-4 { + margin-top: 1.5rem !important; + } + .mr-sm-4, + .mx-sm-4 { + margin-right: 1.5rem !important; + } + .mb-sm-4, + .my-sm-4 { + margin-bottom: 1.5rem !important; + } + .ml-sm-4, + .mx-sm-4 { + margin-left: 1.5rem !important; + } + .m-sm-5 { + margin: 3rem !important; + } + .mt-sm-5, + .my-sm-5 { + margin-top: 3rem !important; + } + .mr-sm-5, + .mx-sm-5 { + margin-right: 3rem !important; + } + .mb-sm-5, + .my-sm-5 { + margin-bottom: 3rem !important; + } + .ml-sm-5, + .mx-sm-5 { + margin-left: 3rem !important; + } + .p-sm-0 { + padding: 0 !important; + } + .pt-sm-0, + .py-sm-0 { + padding-top: 0 !important; + } + .pr-sm-0, + .px-sm-0 { + padding-right: 0 !important; + } + .pb-sm-0, + .py-sm-0 { + padding-bottom: 0 !important; + } + .pl-sm-0, + .px-sm-0 { + padding-left: 0 !important; + } + .p-sm-1 { + padding: 0.25rem !important; + } + .pt-sm-1, + .py-sm-1 { + padding-top: 0.25rem !important; + } + .pr-sm-1, + .px-sm-1 { + padding-right: 0.25rem !important; + } + .pb-sm-1, + .py-sm-1 { + padding-bottom: 0.25rem !important; + } + .pl-sm-1, + .px-sm-1 { + padding-left: 0.25rem !important; + } + .p-sm-2 { + padding: 0.5rem !important; + } + .pt-sm-2, + .py-sm-2 { + padding-top: 0.5rem !important; + } + .pr-sm-2, + .px-sm-2 { + padding-right: 0.5rem !important; + } + .pb-sm-2, + .py-sm-2 { + padding-bottom: 0.5rem !important; + } + .pl-sm-2, + .px-sm-2 { + padding-left: 0.5rem !important; + } + .p-sm-3 { + padding: 1rem !important; + } + .pt-sm-3, + .py-sm-3 { + padding-top: 1rem !important; + } + .pr-sm-3, + .px-sm-3 { + padding-right: 1rem !important; + } + .pb-sm-3, + .py-sm-3 { + padding-bottom: 1rem !important; + } + .pl-sm-3, + .px-sm-3 { + padding-left: 1rem !important; + } + .p-sm-4 { + padding: 1.5rem !important; + } + .pt-sm-4, + .py-sm-4 { + padding-top: 1.5rem !important; + } + .pr-sm-4, + .px-sm-4 { + padding-right: 1.5rem !important; + } + .pb-sm-4, + .py-sm-4 { + padding-bottom: 1.5rem !important; + } + .pl-sm-4, + .px-sm-4 { + padding-left: 1.5rem !important; + } + .p-sm-5 { + padding: 3rem !important; + } + .pt-sm-5, + .py-sm-5 { + padding-top: 3rem !important; + } + .pr-sm-5, + .px-sm-5 { + padding-right: 3rem !important; + } + .pb-sm-5, + .py-sm-5 { + padding-bottom: 3rem !important; + } + .pl-sm-5, + .px-sm-5 { + padding-left: 3rem !important; + } + .m-sm-n1 { + margin: -0.25rem !important; + } + .mt-sm-n1, + .my-sm-n1 { + margin-top: -0.25rem !important; + } + .mr-sm-n1, + .mx-sm-n1 { + margin-right: -0.25rem !important; + } + .mb-sm-n1, + .my-sm-n1 { + margin-bottom: -0.25rem !important; + } + .ml-sm-n1, + .mx-sm-n1 { + margin-left: -0.25rem !important; + } + .m-sm-n2 { + margin: -0.5rem !important; + } + .mt-sm-n2, + .my-sm-n2 { + margin-top: -0.5rem !important; + } + .mr-sm-n2, + .mx-sm-n2 { + margin-right: -0.5rem !important; + } + .mb-sm-n2, + .my-sm-n2 { + margin-bottom: -0.5rem !important; + } + .ml-sm-n2, + .mx-sm-n2 { + margin-left: -0.5rem !important; + } + .m-sm-n3 { + margin: -1rem !important; + } + .mt-sm-n3, + .my-sm-n3 { + margin-top: -1rem !important; + } + .mr-sm-n3, + .mx-sm-n3 { + margin-right: -1rem !important; + } + .mb-sm-n3, + .my-sm-n3 { + margin-bottom: -1rem !important; + } + .ml-sm-n3, + .mx-sm-n3 { + margin-left: -1rem !important; + } + .m-sm-n4 { + margin: -1.5rem !important; + } + .mt-sm-n4, + .my-sm-n4 { + margin-top: -1.5rem !important; + } + .mr-sm-n4, + .mx-sm-n4 { + margin-right: -1.5rem !important; + } + .mb-sm-n4, + .my-sm-n4 { + margin-bottom: -1.5rem !important; + } + .ml-sm-n4, + .mx-sm-n4 { + margin-left: -1.5rem !important; + } + .m-sm-n5 { + margin: -3rem !important; + } + .mt-sm-n5, + .my-sm-n5 { + margin-top: -3rem !important; + } + .mr-sm-n5, + .mx-sm-n5 { + margin-right: -3rem !important; + } + .mb-sm-n5, + .my-sm-n5 { + margin-bottom: -3rem !important; + } + .ml-sm-n5, + .mx-sm-n5 { + margin-left: -3rem !important; + } + .m-sm-auto { + margin: auto !important; + } + .mt-sm-auto, + .my-sm-auto { + margin-top: auto !important; + } + .mr-sm-auto, + .mx-sm-auto { + margin-right: auto !important; + } + .mb-sm-auto, + .my-sm-auto { + margin-bottom: auto !important; + } + .ml-sm-auto, + .mx-sm-auto { + margin-left: auto !important; + } +} +@media (min-width: 768px) { + .m-md-0 { + margin: 0 !important; + } + .mt-md-0, + .my-md-0 { + margin-top: 0 !important; + } + .mr-md-0, + .mx-md-0 { + margin-right: 0 !important; + } + .mb-md-0, + .my-md-0 { + margin-bottom: 0 !important; + } + .ml-md-0, + .mx-md-0 { + margin-left: 0 !important; + } + .m-md-1 { + margin: 0.25rem !important; + } + .mt-md-1, + .my-md-1 { + margin-top: 0.25rem !important; + } + .mr-md-1, + .mx-md-1 { + margin-right: 0.25rem !important; + } + .mb-md-1, + .my-md-1 { + margin-bottom: 0.25rem !important; + } + .ml-md-1, + .mx-md-1 { + margin-left: 0.25rem !important; + } + .m-md-2 { + margin: 0.5rem !important; + } + .mt-md-2, + .my-md-2 { + margin-top: 0.5rem !important; + } + .mr-md-2, + .mx-md-2 { + margin-right: 0.5rem !important; + } + .mb-md-2, + .my-md-2 { + margin-bottom: 0.5rem !important; + } + .ml-md-2, + .mx-md-2 { + margin-left: 0.5rem !important; + } + .m-md-3 { + margin: 1rem !important; + } + .mt-md-3, + .my-md-3 { + margin-top: 1rem !important; + } + .mr-md-3, + .mx-md-3 { + margin-right: 1rem !important; + } + .mb-md-3, + .my-md-3 { + margin-bottom: 1rem !important; + } + .ml-md-3, + .mx-md-3 { + margin-left: 1rem !important; + } + .m-md-4 { + margin: 1.5rem !important; + } + .mt-md-4, + .my-md-4 { + margin-top: 1.5rem !important; + } + .mr-md-4, + .mx-md-4 { + margin-right: 1.5rem !important; + } + .mb-md-4, + .my-md-4 { + margin-bottom: 1.5rem !important; + } + .ml-md-4, + .mx-md-4 { + margin-left: 1.5rem !important; + } + .m-md-5 { + margin: 3rem !important; + } + .mt-md-5, + .my-md-5 { + margin-top: 3rem !important; + } + .mr-md-5, + .mx-md-5 { + margin-right: 3rem !important; + } + .mb-md-5, + .my-md-5 { + margin-bottom: 3rem !important; + } + .ml-md-5, + .mx-md-5 { + margin-left: 3rem !important; + } + .p-md-0 { + padding: 0 !important; + } + .pt-md-0, + .py-md-0 { + padding-top: 0 !important; + } + .pr-md-0, + .px-md-0 { + padding-right: 0 !important; + } + .pb-md-0, + .py-md-0 { + padding-bottom: 0 !important; + } + .pl-md-0, + .px-md-0 { + padding-left: 0 !important; + } + .p-md-1 { + padding: 0.25rem !important; + } + .pt-md-1, + .py-md-1 { + padding-top: 0.25rem !important; + } + .pr-md-1, + .px-md-1 { + padding-right: 0.25rem !important; + } + .pb-md-1, + .py-md-1 { + padding-bottom: 0.25rem !important; + } + .pl-md-1, + .px-md-1 { + padding-left: 0.25rem !important; + } + .p-md-2 { + padding: 0.5rem !important; + } + .pt-md-2, + .py-md-2 { + padding-top: 0.5rem !important; + } + .pr-md-2, + .px-md-2 { + padding-right: 0.5rem !important; + } + .pb-md-2, + .py-md-2 { + padding-bottom: 0.5rem !important; + } + .pl-md-2, + .px-md-2 { + padding-left: 0.5rem !important; + } + .p-md-3 { + padding: 1rem !important; + } + .pt-md-3, + .py-md-3 { + padding-top: 1rem !important; + } + .pr-md-3, + .px-md-3 { + padding-right: 1rem !important; + } + .pb-md-3, + .py-md-3 { + padding-bottom: 1rem !important; + } + .pl-md-3, + .px-md-3 { + padding-left: 1rem !important; + } + .p-md-4 { + padding: 1.5rem !important; + } + .pt-md-4, + .py-md-4 { + padding-top: 1.5rem !important; + } + .pr-md-4, + .px-md-4 { + padding-right: 1.5rem !important; + } + .pb-md-4, + .py-md-4 { + padding-bottom: 1.5rem !important; + } + .pl-md-4, + .px-md-4 { + padding-left: 1.5rem !important; + } + .p-md-5 { + padding: 3rem !important; + } + .pt-md-5, + .py-md-5 { + padding-top: 3rem !important; + } + .pr-md-5, + .px-md-5 { + padding-right: 3rem !important; + } + .pb-md-5, + .py-md-5 { + padding-bottom: 3rem !important; + } + .pl-md-5, + .px-md-5 { + padding-left: 3rem !important; + } + .m-md-n1 { + margin: -0.25rem !important; + } + .mt-md-n1, + .my-md-n1 { + margin-top: -0.25rem !important; + } + .mr-md-n1, + .mx-md-n1 { + margin-right: -0.25rem !important; + } + .mb-md-n1, + .my-md-n1 { + margin-bottom: -0.25rem !important; + } + .ml-md-n1, + .mx-md-n1 { + margin-left: -0.25rem !important; + } + .m-md-n2 { + margin: -0.5rem !important; + } + .mt-md-n2, + .my-md-n2 { + margin-top: -0.5rem !important; + } + .mr-md-n2, + .mx-md-n2 { + margin-right: -0.5rem !important; + } + .mb-md-n2, + .my-md-n2 { + margin-bottom: -0.5rem !important; + } + .ml-md-n2, + .mx-md-n2 { + margin-left: -0.5rem !important; + } + .m-md-n3 { + margin: -1rem !important; + } + .mt-md-n3, + .my-md-n3 { + margin-top: -1rem !important; + } + .mr-md-n3, + .mx-md-n3 { + margin-right: -1rem !important; + } + .mb-md-n3, + .my-md-n3 { + margin-bottom: -1rem !important; + } + .ml-md-n3, + .mx-md-n3 { + margin-left: -1rem !important; + } + .m-md-n4 { + margin: -1.5rem !important; + } + .mt-md-n4, + .my-md-n4 { + margin-top: -1.5rem !important; + } + .mr-md-n4, + .mx-md-n4 { + margin-right: -1.5rem !important; + } + .mb-md-n4, + .my-md-n4 { + margin-bottom: -1.5rem !important; + } + .ml-md-n4, + .mx-md-n4 { + margin-left: -1.5rem !important; + } + .m-md-n5 { + margin: -3rem !important; + } + .mt-md-n5, + .my-md-n5 { + margin-top: -3rem !important; + } + .mr-md-n5, + .mx-md-n5 { + margin-right: -3rem !important; + } + .mb-md-n5, + .my-md-n5 { + margin-bottom: -3rem !important; + } + .ml-md-n5, + .mx-md-n5 { + margin-left: -3rem !important; + } + .m-md-auto { + margin: auto !important; + } + .mt-md-auto, + .my-md-auto { + margin-top: auto !important; + } + .mr-md-auto, + .mx-md-auto { + margin-right: auto !important; + } + .mb-md-auto, + .my-md-auto { + margin-bottom: auto !important; + } + .ml-md-auto, + .mx-md-auto { + margin-left: auto !important; + } +} +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 !important; + } + .mt-lg-0, + .my-lg-0 { + margin-top: 0 !important; + } + .mr-lg-0, + .mx-lg-0 { + margin-right: 0 !important; + } + .mb-lg-0, + .my-lg-0 { + margin-bottom: 0 !important; + } + .ml-lg-0, + .mx-lg-0 { + margin-left: 0 !important; + } + .m-lg-1 { + margin: 0.25rem !important; + } + .mt-lg-1, + .my-lg-1 { + margin-top: 0.25rem !important; + } + .mr-lg-1, + .mx-lg-1 { + margin-right: 0.25rem !important; + } + .mb-lg-1, + .my-lg-1 { + margin-bottom: 0.25rem !important; + } + .ml-lg-1, + .mx-lg-1 { + margin-left: 0.25rem !important; + } + .m-lg-2 { + margin: 0.5rem !important; + } + .mt-lg-2, + .my-lg-2 { + margin-top: 0.5rem !important; + } + .mr-lg-2, + .mx-lg-2 { + margin-right: 0.5rem !important; + } + .mb-lg-2, + .my-lg-2 { + margin-bottom: 0.5rem !important; + } + .ml-lg-2, + .mx-lg-2 { + margin-left: 0.5rem !important; + } + .m-lg-3 { + margin: 1rem !important; + } + .mt-lg-3, + .my-lg-3 { + margin-top: 1rem !important; + } + .mr-lg-3, + .mx-lg-3 { + margin-right: 1rem !important; + } + .mb-lg-3, + .my-lg-3 { + margin-bottom: 1rem !important; + } + .ml-lg-3, + .mx-lg-3 { + margin-left: 1rem !important; + } + .m-lg-4 { + margin: 1.5rem !important; + } + .mt-lg-4, + .my-lg-4 { + margin-top: 1.5rem !important; + } + .mr-lg-4, + .mx-lg-4 { + margin-right: 1.5rem !important; + } + .mb-lg-4, + .my-lg-4 { + margin-bottom: 1.5rem !important; + } + .ml-lg-4, + .mx-lg-4 { + margin-left: 1.5rem !important; + } + .m-lg-5 { + margin: 3rem !important; + } + .mt-lg-5, + .my-lg-5 { + margin-top: 3rem !important; + } + .mr-lg-5, + .mx-lg-5 { + margin-right: 3rem !important; + } + .mb-lg-5, + .my-lg-5 { + margin-bottom: 3rem !important; + } + .ml-lg-5, + .mx-lg-5 { + margin-left: 3rem !important; + } + .p-lg-0 { + padding: 0 !important; + } + .pt-lg-0, + .py-lg-0 { + padding-top: 0 !important; + } + .pr-lg-0, + .px-lg-0 { + padding-right: 0 !important; + } + .pb-lg-0, + .py-lg-0 { + padding-bottom: 0 !important; + } + .pl-lg-0, + .px-lg-0 { + padding-left: 0 !important; + } + .p-lg-1 { + padding: 0.25rem !important; + } + .pt-lg-1, + .py-lg-1 { + padding-top: 0.25rem !important; + } + .pr-lg-1, + .px-lg-1 { + padding-right: 0.25rem !important; + } + .pb-lg-1, + .py-lg-1 { + padding-bottom: 0.25rem !important; + } + .pl-lg-1, + .px-lg-1 { + padding-left: 0.25rem !important; + } + .p-lg-2 { + padding: 0.5rem !important; + } + .pt-lg-2, + .py-lg-2 { + padding-top: 0.5rem !important; + } + .pr-lg-2, + .px-lg-2 { + padding-right: 0.5rem !important; + } + .pb-lg-2, + .py-lg-2 { + padding-bottom: 0.5rem !important; + } + .pl-lg-2, + .px-lg-2 { + padding-left: 0.5rem !important; + } + .p-lg-3 { + padding: 1rem !important; + } + .pt-lg-3, + .py-lg-3 { + padding-top: 1rem !important; + } + .pr-lg-3, + .px-lg-3 { + padding-right: 1rem !important; + } + .pb-lg-3, + .py-lg-3 { + padding-bottom: 1rem !important; + } + .pl-lg-3, + .px-lg-3 { + padding-left: 1rem !important; + } + .p-lg-4 { + padding: 1.5rem !important; + } + .pt-lg-4, + .py-lg-4 { + padding-top: 1.5rem !important; + } + .pr-lg-4, + .px-lg-4 { + padding-right: 1.5rem !important; + } + .pb-lg-4, + .py-lg-4 { + padding-bottom: 1.5rem !important; + } + .pl-lg-4, + .px-lg-4 { + padding-left: 1.5rem !important; + } + .p-lg-5 { + padding: 3rem !important; + } + .pt-lg-5, + .py-lg-5 { + padding-top: 3rem !important; + } + .pr-lg-5, + .px-lg-5 { + padding-right: 3rem !important; + } + .pb-lg-5, + .py-lg-5 { + padding-bottom: 3rem !important; + } + .pl-lg-5, + .px-lg-5 { + padding-left: 3rem !important; + } + .m-lg-n1 { + margin: -0.25rem !important; + } + .mt-lg-n1, + .my-lg-n1 { + margin-top: -0.25rem !important; + } + .mr-lg-n1, + .mx-lg-n1 { + margin-right: -0.25rem !important; + } + .mb-lg-n1, + .my-lg-n1 { + margin-bottom: -0.25rem !important; + } + .ml-lg-n1, + .mx-lg-n1 { + margin-left: -0.25rem !important; + } + .m-lg-n2 { + margin: -0.5rem !important; + } + .mt-lg-n2, + .my-lg-n2 { + margin-top: -0.5rem !important; + } + .mr-lg-n2, + .mx-lg-n2 { + margin-right: -0.5rem !important; + } + .mb-lg-n2, + .my-lg-n2 { + margin-bottom: -0.5rem !important; + } + .ml-lg-n2, + .mx-lg-n2 { + margin-left: -0.5rem !important; + } + .m-lg-n3 { + margin: -1rem !important; + } + .mt-lg-n3, + .my-lg-n3 { + margin-top: -1rem !important; + } + .mr-lg-n3, + .mx-lg-n3 { + margin-right: -1rem !important; + } + .mb-lg-n3, + .my-lg-n3 { + margin-bottom: -1rem !important; + } + .ml-lg-n3, + .mx-lg-n3 { + margin-left: -1rem !important; + } + .m-lg-n4 { + margin: -1.5rem !important; + } + .mt-lg-n4, + .my-lg-n4 { + margin-top: -1.5rem !important; + } + .mr-lg-n4, + .mx-lg-n4 { + margin-right: -1.5rem !important; + } + .mb-lg-n4, + .my-lg-n4 { + margin-bottom: -1.5rem !important; + } + .ml-lg-n4, + .mx-lg-n4 { + margin-left: -1.5rem !important; + } + .m-lg-n5 { + margin: -3rem !important; + } + .mt-lg-n5, + .my-lg-n5 { + margin-top: -3rem !important; + } + .mr-lg-n5, + .mx-lg-n5 { + margin-right: -3rem !important; + } + .mb-lg-n5, + .my-lg-n5 { + margin-bottom: -3rem !important; + } + .ml-lg-n5, + .mx-lg-n5 { + margin-left: -3rem !important; + } + .m-lg-auto { + margin: auto !important; + } + .mt-lg-auto, + .my-lg-auto { + margin-top: auto !important; + } + .mr-lg-auto, + .mx-lg-auto { + margin-right: auto !important; + } + .mb-lg-auto, + .my-lg-auto { + margin-bottom: auto !important; + } + .ml-lg-auto, + .mx-lg-auto { + margin-left: auto !important; + } +} +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 !important; + } + .mt-xl-0, + .my-xl-0 { + margin-top: 0 !important; + } + .mr-xl-0, + .mx-xl-0 { + margin-right: 0 !important; + } + .mb-xl-0, + .my-xl-0 { + margin-bottom: 0 !important; + } + .ml-xl-0, + .mx-xl-0 { + margin-left: 0 !important; + } + .m-xl-1 { + margin: 0.25rem !important; + } + .mt-xl-1, + .my-xl-1 { + margin-top: 0.25rem !important; + } + .mr-xl-1, + .mx-xl-1 { + margin-right: 0.25rem !important; + } + .mb-xl-1, + .my-xl-1 { + margin-bottom: 0.25rem !important; + } + .ml-xl-1, + .mx-xl-1 { + margin-left: 0.25rem !important; + } + .m-xl-2 { + margin: 0.5rem !important; + } + .mt-xl-2, + .my-xl-2 { + margin-top: 0.5rem !important; + } + .mr-xl-2, + .mx-xl-2 { + margin-right: 0.5rem !important; + } + .mb-xl-2, + .my-xl-2 { + margin-bottom: 0.5rem !important; + } + .ml-xl-2, + .mx-xl-2 { + margin-left: 0.5rem !important; + } + .m-xl-3 { + margin: 1rem !important; + } + .mt-xl-3, + .my-xl-3 { + margin-top: 1rem !important; + } + .mr-xl-3, + .mx-xl-3 { + margin-right: 1rem !important; + } + .mb-xl-3, + .my-xl-3 { + margin-bottom: 1rem !important; + } + .ml-xl-3, + .mx-xl-3 { + margin-left: 1rem !important; + } + .m-xl-4 { + margin: 1.5rem !important; + } + .mt-xl-4, + .my-xl-4 { + margin-top: 1.5rem !important; + } + .mr-xl-4, + .mx-xl-4 { + margin-right: 1.5rem !important; + } + .mb-xl-4, + .my-xl-4 { + margin-bottom: 1.5rem !important; + } + .ml-xl-4, + .mx-xl-4 { + margin-left: 1.5rem !important; + } + .m-xl-5 { + margin: 3rem !important; + } + .mt-xl-5, + .my-xl-5 { + margin-top: 3rem !important; + } + .mr-xl-5, + .mx-xl-5 { + margin-right: 3rem !important; + } + .mb-xl-5, + .my-xl-5 { + margin-bottom: 3rem !important; + } + .ml-xl-5, + .mx-xl-5 { + margin-left: 3rem !important; + } + .p-xl-0 { + padding: 0 !important; + } + .pt-xl-0, + .py-xl-0 { + padding-top: 0 !important; + } + .pr-xl-0, + .px-xl-0 { + padding-right: 0 !important; + } + .pb-xl-0, + .py-xl-0 { + padding-bottom: 0 !important; + } + .pl-xl-0, + .px-xl-0 { + padding-left: 0 !important; + } + .p-xl-1 { + padding: 0.25rem !important; + } + .pt-xl-1, + .py-xl-1 { + padding-top: 0.25rem !important; + } + .pr-xl-1, + .px-xl-1 { + padding-right: 0.25rem !important; + } + .pb-xl-1, + .py-xl-1 { + padding-bottom: 0.25rem !important; + } + .pl-xl-1, + .px-xl-1 { + padding-left: 0.25rem !important; + } + .p-xl-2 { + padding: 0.5rem !important; + } + .pt-xl-2, + .py-xl-2 { + padding-top: 0.5rem !important; + } + .pr-xl-2, + .px-xl-2 { + padding-right: 0.5rem !important; + } + .pb-xl-2, + .py-xl-2 { + padding-bottom: 0.5rem !important; + } + .pl-xl-2, + .px-xl-2 { + padding-left: 0.5rem !important; + } + .p-xl-3 { + padding: 1rem !important; + } + .pt-xl-3, + .py-xl-3 { + padding-top: 1rem !important; + } + .pr-xl-3, + .px-xl-3 { + padding-right: 1rem !important; + } + .pb-xl-3, + .py-xl-3 { + padding-bottom: 1rem !important; + } + .pl-xl-3, + .px-xl-3 { + padding-left: 1rem !important; + } + .p-xl-4 { + padding: 1.5rem !important; + } + .pt-xl-4, + .py-xl-4 { + padding-top: 1.5rem !important; + } + .pr-xl-4, + .px-xl-4 { + padding-right: 1.5rem !important; + } + .pb-xl-4, + .py-xl-4 { + padding-bottom: 1.5rem !important; + } + .pl-xl-4, + .px-xl-4 { + padding-left: 1.5rem !important; + } + .p-xl-5 { + padding: 3rem !important; + } + .pt-xl-5, + .py-xl-5 { + padding-top: 3rem !important; + } + .pr-xl-5, + .px-xl-5 { + padding-right: 3rem !important; + } + .pb-xl-5, + .py-xl-5 { + padding-bottom: 3rem !important; + } + .pl-xl-5, + .px-xl-5 { + padding-left: 3rem !important; + } + .m-xl-n1 { + margin: -0.25rem !important; + } + .mt-xl-n1, + .my-xl-n1 { + margin-top: -0.25rem !important; + } + .mr-xl-n1, + .mx-xl-n1 { + margin-right: -0.25rem !important; + } + .mb-xl-n1, + .my-xl-n1 { + margin-bottom: -0.25rem !important; + } + .ml-xl-n1, + .mx-xl-n1 { + margin-left: -0.25rem !important; + } + .m-xl-n2 { + margin: -0.5rem !important; + } + .mt-xl-n2, + .my-xl-n2 { + margin-top: -0.5rem !important; + } + .mr-xl-n2, + .mx-xl-n2 { + margin-right: -0.5rem !important; + } + .mb-xl-n2, + .my-xl-n2 { + margin-bottom: -0.5rem !important; + } + .ml-xl-n2, + .mx-xl-n2 { + margin-left: -0.5rem !important; + } + .m-xl-n3 { + margin: -1rem !important; + } + .mt-xl-n3, + .my-xl-n3 { + margin-top: -1rem !important; + } + .mr-xl-n3, + .mx-xl-n3 { + margin-right: -1rem !important; + } + .mb-xl-n3, + .my-xl-n3 { + margin-bottom: -1rem !important; + } + .ml-xl-n3, + .mx-xl-n3 { + margin-left: -1rem !important; + } + .m-xl-n4 { + margin: -1.5rem !important; + } + .mt-xl-n4, + .my-xl-n4 { + margin-top: -1.5rem !important; + } + .mr-xl-n4, + .mx-xl-n4 { + margin-right: -1.5rem !important; + } + .mb-xl-n4, + .my-xl-n4 { + margin-bottom: -1.5rem !important; + } + .ml-xl-n4, + .mx-xl-n4 { + margin-left: -1.5rem !important; + } + .m-xl-n5 { + margin: -3rem !important; + } + .mt-xl-n5, + .my-xl-n5 { + margin-top: -3rem !important; + } + .mr-xl-n5, + .mx-xl-n5 { + margin-right: -3rem !important; + } + .mb-xl-n5, + .my-xl-n5 { + margin-bottom: -3rem !important; + } + .ml-xl-n5, + .mx-xl-n5 { + margin-left: -3rem !important; + } + .m-xl-auto { + margin: auto !important; + } + .mt-xl-auto, + .my-xl-auto { + margin-top: auto !important; + } + .mr-xl-auto, + .mx-xl-auto { + margin-right: auto !important; + } + .mb-xl-auto, + .my-xl-auto { + margin-bottom: auto !important; + } + .ml-xl-auto, + .mx-xl-auto { + margin-left: auto !important; + } +} +.stretched-link::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + pointer-events: auto; + content: ""; + background-color: rgba(0, 0, 0, 0); +} +.text-monospace { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace !important; +} +.text-justify { + text-align: justify !important; +} +.text-wrap { + white-space: normal !important; +} +.text-nowrap { + white-space: nowrap !important; +} +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.text-left { + text-align: left !important; +} +.text-right { + text-align: right !important; +} +.text-center { + text-align: center !important; +} +@media (min-width: 576px) { + .text-sm-left { + text-align: left !important; + } + .text-sm-right { + text-align: right !important; + } + .text-sm-center { + text-align: center !important; + } +} +@media (min-width: 768px) { + .text-md-left { + text-align: left !important; + } + .text-md-right { + text-align: right !important; + } + .text-md-center { + text-align: center !important; + } +} +@media (min-width: 992px) { + .text-lg-left { + text-align: left !important; + } + .text-lg-right { + text-align: right !important; + } + .text-lg-center { + text-align: center !important; + } +} +@media (min-width: 1200px) { + .text-xl-left { + text-align: left !important; + } + .text-xl-right { + text-align: right !important; + } + .text-xl-center { + text-align: center !important; + } +} +.text-lowercase { + text-transform: lowercase !important; +} +.text-uppercase { + text-transform: uppercase !important; +} +.text-capitalize { + text-transform: capitalize !important; +} +.font-weight-light { + font-weight: 300 !important; +} +.font-weight-lighter { + font-weight: lighter !important; +} +.font-weight-normal { + font-weight: 400 !important; +} +.font-weight-bold { + font-weight: 700 !important; +} +.font-weight-bolder { + font-weight: bolder !important; +} +.font-italic { + font-style: italic !important; +} +.text-white { + color: #fff !important; +} +.text-primary { + color: #007bff !important; +} +a.text-primary:focus, +a.text-primary:hover { + color: #0056b3 !important; +} +.text-secondary { + color: #6c757d !important; +} +a.text-secondary:focus, +a.text-secondary:hover { + color: #494f54 !important; +} +.text-success { + color: #28a745 !important; +} +a.text-success:focus, +a.text-success:hover { + color: #19692c !important; +} +.text-info { + color: #17a2b8 !important; +} +a.text-info:focus, +a.text-info:hover { + color: #0f6674 !important; +} +.text-warning { + color: #ffc107 !important; +} +a.text-warning:focus, +a.text-warning:hover { + color: #ba8b00 !important; +} +.text-danger { + color: #dc3545 !important; +} +a.text-danger:focus, +a.text-danger:hover { + color: #a71d2a !important; +} +.text-light { + color: #f8f9fa !important; +} +a.text-light:focus, +a.text-light:hover { + color: #cbd3da !important; +} +.text-dark { + color: #343a40 !important; +} +a.text-dark:focus, +a.text-dark:hover { + color: #121416 !important; +} +.text-body { + color: #212529 !important; +} +.text-muted { + color: #6c757d !important; +} +.text-black-50 { + color: rgba(0, 0, 0, 0.5) !important; +} +.text-white-50 { + color: rgba(255, 255, 255, 0.5) !important; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.text-decoration-none { + text-decoration: none !important; +} +.text-break { + word-break: break-word !important; + word-wrap: break-word !important; +} +.text-reset { + color: inherit !important; +} +.visible { + visibility: visible !important; +} +.invisible { + visibility: hidden !important; +} +@media print { + *, + ::after, + ::before { + text-shadow: none !important; + box-shadow: none !important; + } + a:not(.btn) { + text-decoration: underline; + } + abbr[title]::after { + content: " (" attr(title) ")"; + } + pre { + white-space: pre-wrap !important; + } + blockquote, + pre { + border: 1px solid #adb5bd; + page-break-inside: avoid; + } + img, + tr { + page-break-inside: avoid; + } + h2, + h3, + p { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + @page { + size: a3; + } + body { + min-width: 992px !important; + } + .container { + min-width: 992px !important; + } + .navbar { + display: none; + } + .badge { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered td, + .table-bordered th { + border: 1px solid #dee2e6 !important; + } + .table-dark { + color: inherit; + } + .table-dark tbody + tbody, + .table-dark td, + .table-dark th, + .table-dark thead th { + border-color: #dee2e6; + } + .table .thead-dark th { + color: inherit; + border-color: #dee2e6; + } +} +/*# sourceMappingURL=bootstrap.min.css.map */ diff --git a/public/css/fontawesome.css b/public/css/fontawesome.css index f6ff0154..e0557d9b 100644 --- a/public/css/fontawesome.css +++ b/public/css/fontawesome.css @@ -14,145 +14,183 @@ font-style: normal; font-variant: normal; text-rendering: auto; - line-height: 1; } + line-height: 1; +} .fa-lg { font-size: 1.33333em; line-height: 0.75em; - vertical-align: -.0667em; } + vertical-align: -0.0667em; +} .fa-xs { - font-size: .75em; } + font-size: 0.75em; +} .fa-sm { - font-size: .875em; } + font-size: 0.875em; +} .fa-1x { - font-size: 1em; } + font-size: 1em; +} .fa-2x { - font-size: 2em; } + font-size: 2em; +} .fa-3x { - font-size: 3em; } + font-size: 3em; +} .fa-4x { - font-size: 4em; } + font-size: 4em; +} .fa-5x { - font-size: 5em; } + font-size: 5em; +} .fa-6x { - font-size: 6em; } + font-size: 6em; +} .fa-7x { - font-size: 7em; } + font-size: 7em; +} .fa-8x { - font-size: 8em; } + font-size: 8em; +} .fa-9x { - font-size: 9em; } + font-size: 9em; +} .fa-10x { - font-size: 10em; } + font-size: 10em; +} .fa-fw { text-align: center; - width: 1.25em; } + width: 1.25em; +} .fa-ul { list-style-type: none; margin-left: 2.5em; - padding-left: 0; } - .fa-ul > li { - position: relative; } + padding-left: 0; +} +.fa-ul > li { + position: relative; +} .fa-li { left: -2em; position: absolute; text-align: center; width: 2em; - line-height: inherit; } + line-height: inherit; +} .fa-border { border: solid 0.08em #eee; - border-radius: .1em; - padding: .2em .25em .15em; } + border-radius: 0.1em; + padding: 0.2em 0.25em 0.15em; +} .fa-pull-left { - float: left; } + float: left; +} .fa-pull-right { - float: right; } + float: right; +} .fa.fa-pull-left, .fas.fa-pull-left, .far.fa-pull-left, .fal.fa-pull-left, .fab.fa-pull-left { - margin-right: .3em; } + margin-right: 0.3em; +} .fa.fa-pull-right, .fas.fa-pull-right, .far.fa-pull-right, .fal.fa-pull-right, .fab.fa-pull-right { - margin-left: .3em; } + margin-left: 0.3em; +} .fa-spin { -webkit-animation: fa-spin 2s infinite linear; - animation: fa-spin 2s infinite linear; } + animation: fa-spin 2s infinite linear; +} .fa-pulse { -webkit-animation: fa-spin 1s infinite steps(8); - animation: fa-spin 1s infinite steps(8); } + animation: fa-spin 1s infinite steps(8); +} @-webkit-keyframes fa-spin { 0% { -webkit-transform: rotate(0deg); - transform: rotate(0deg); } + transform: rotate(0deg); + } 100% { -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } + transform: rotate(360deg); + } +} @keyframes fa-spin { 0% { -webkit-transform: rotate(0deg); - transform: rotate(0deg); } + transform: rotate(0deg); + } 100% { -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } + transform: rotate(360deg); + } +} .fa-rotate-90 { -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; -webkit-transform: rotate(90deg); - transform: rotate(90deg); } + transform: rotate(90deg); +} .fa-rotate-180 { -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; -webkit-transform: rotate(180deg); - transform: rotate(180deg); } + transform: rotate(180deg); +} .fa-rotate-270 { -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; -webkit-transform: rotate(270deg); - transform: rotate(270deg); } + transform: rotate(270deg); +} .fa-flip-horizontal { -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; -webkit-transform: scale(-1, 1); - transform: scale(-1, 1); } + transform: scale(-1, 1); +} .fa-flip-vertical { -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; -webkit-transform: scale(1, -1); - transform: scale(1, -1); } + transform: scale(1, -1); +} -.fa-flip-both, .fa-flip-horizontal.fa-flip-vertical { +.fa-flip-both, +.fa-flip-horizontal.fa-flip-vertical { -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; -webkit-transform: scale(-1, -1); - transform: scale(-1, -1); } + transform: scale(-1, -1); +} :root .fa-rotate-90, :root .fa-rotate-180, @@ -161,7 +199,8 @@ :root .fa-flip-vertical, :root .fa-flip-both { -webkit-filter: none; - filter: none; } + filter: none; +} .fa-stack { display: inline-block; @@ -169,4234 +208,5642 @@ line-height: 2em; position: relative; vertical-align: middle; - width: 2.5em; } + width: 2.5em; +} .fa-stack-1x, .fa-stack-2x { left: 0; position: absolute; text-align: center; - width: 100%; } + width: 100%; +} .fa-stack-1x { - line-height: inherit; } + line-height: inherit; +} .fa-stack-2x { - font-size: 2em; } + font-size: 2em; +} .fa-inverse { - color: #fff; } + color: #fff; +} /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen readers do not read off random characters that represent icons */ .fa-500px:before { - content: "\f26e"; } + content: "\f26e"; +} .fa-accessible-icon:before { - content: "\f368"; } + content: "\f368"; +} .fa-accusoft:before { - content: "\f369"; } + content: "\f369"; +} .fa-acquisitions-incorporated:before { - content: "\f6af"; } + content: "\f6af"; +} .fa-ad:before { - content: "\f641"; } + content: "\f641"; +} .fa-address-book:before { - content: "\f2b9"; } + content: "\f2b9"; +} .fa-address-card:before { - content: "\f2bb"; } + content: "\f2bb"; +} .fa-adjust:before { - content: "\f042"; } + content: "\f042"; +} .fa-adn:before { - content: "\f170"; } + content: "\f170"; +} .fa-adobe:before { - content: "\f778"; } + content: "\f778"; +} .fa-adversal:before { - content: "\f36a"; } + content: "\f36a"; +} .fa-affiliatetheme:before { - content: "\f36b"; } + content: "\f36b"; +} .fa-air-freshener:before { - content: "\f5d0"; } + content: "\f5d0"; +} .fa-airbnb:before { - content: "\f834"; } + content: "\f834"; +} .fa-algolia:before { - content: "\f36c"; } + content: "\f36c"; +} .fa-align-center:before { - content: "\f037"; } + content: "\f037"; +} .fa-align-justify:before { - content: "\f039"; } + content: "\f039"; +} .fa-align-left:before { - content: "\f036"; } + content: "\f036"; +} .fa-align-right:before { - content: "\f038"; } + content: "\f038"; +} .fa-alipay:before { - content: "\f642"; } + content: "\f642"; +} .fa-allergies:before { - content: "\f461"; } + content: "\f461"; +} .fa-amazon:before { - content: "\f270"; } + content: "\f270"; +} .fa-amazon-pay:before { - content: "\f42c"; } + content: "\f42c"; +} .fa-ambulance:before { - content: "\f0f9"; } + content: "\f0f9"; +} .fa-american-sign-language-interpreting:before { - content: "\f2a3"; } + content: "\f2a3"; +} .fa-amilia:before { - content: "\f36d"; } + content: "\f36d"; +} .fa-anchor:before { - content: "\f13d"; } + content: "\f13d"; +} .fa-android:before { - content: "\f17b"; } + content: "\f17b"; +} .fa-angellist:before { - content: "\f209"; } + content: "\f209"; +} .fa-angle-double-down:before { - content: "\f103"; } + content: "\f103"; +} .fa-angle-double-left:before { - content: "\f100"; } + content: "\f100"; +} .fa-angle-double-right:before { - content: "\f101"; } + content: "\f101"; +} .fa-angle-double-up:before { - content: "\f102"; } + content: "\f102"; +} .fa-angle-down:before { - content: "\f107"; } + content: "\f107"; +} .fa-angle-left:before { - content: "\f104"; } + content: "\f104"; +} .fa-angle-right:before { - content: "\f105"; } + content: "\f105"; +} .fa-angle-up:before { - content: "\f106"; } + content: "\f106"; +} .fa-angry:before { - content: "\f556"; } + content: "\f556"; +} .fa-angrycreative:before { - content: "\f36e"; } + content: "\f36e"; +} .fa-angular:before { - content: "\f420"; } + content: "\f420"; +} .fa-ankh:before { - content: "\f644"; } + content: "\f644"; +} .fa-app-store:before { - content: "\f36f"; } + content: "\f36f"; +} .fa-app-store-ios:before { - content: "\f370"; } + content: "\f370"; +} .fa-apper:before { - content: "\f371"; } + content: "\f371"; +} .fa-apple:before { - content: "\f179"; } + content: "\f179"; +} .fa-apple-alt:before { - content: "\f5d1"; } + content: "\f5d1"; +} .fa-apple-pay:before { - content: "\f415"; } + content: "\f415"; +} .fa-archive:before { - content: "\f187"; } + content: "\f187"; +} .fa-archway:before { - content: "\f557"; } + content: "\f557"; +} .fa-arrow-alt-circle-down:before { - content: "\f358"; } + content: "\f358"; +} .fa-arrow-alt-circle-left:before { - content: "\f359"; } + content: "\f359"; +} .fa-arrow-alt-circle-right:before { - content: "\f35a"; } + content: "\f35a"; +} .fa-arrow-alt-circle-up:before { - content: "\f35b"; } + content: "\f35b"; +} .fa-arrow-circle-down:before { - content: "\f0ab"; } + content: "\f0ab"; +} .fa-arrow-circle-left:before { - content: "\f0a8"; } + content: "\f0a8"; +} .fa-arrow-circle-right:before { - content: "\f0a9"; } + content: "\f0a9"; +} .fa-arrow-circle-up:before { - content: "\f0aa"; } + content: "\f0aa"; +} .fa-arrow-down:before { - content: "\f063"; } + content: "\f063"; +} .fa-arrow-left:before { - content: "\f060"; } + content: "\f060"; +} .fa-arrow-right:before { - content: "\f061"; } + content: "\f061"; +} .fa-arrow-up:before { - content: "\f062"; } + content: "\f062"; +} .fa-arrows-alt:before { - content: "\f0b2"; } + content: "\f0b2"; +} .fa-arrows-alt-h:before { - content: "\f337"; } + content: "\f337"; +} .fa-arrows-alt-v:before { - content: "\f338"; } + content: "\f338"; +} .fa-artstation:before { - content: "\f77a"; } + content: "\f77a"; +} .fa-assistive-listening-systems:before { - content: "\f2a2"; } + content: "\f2a2"; +} .fa-asterisk:before { - content: "\f069"; } + content: "\f069"; +} .fa-asymmetrik:before { - content: "\f372"; } + content: "\f372"; +} .fa-at:before { - content: "\f1fa"; } + content: "\f1fa"; +} .fa-atlas:before { - content: "\f558"; } + content: "\f558"; +} .fa-atlassian:before { - content: "\f77b"; } + content: "\f77b"; +} .fa-atom:before { - content: "\f5d2"; } + content: "\f5d2"; +} .fa-audible:before { - content: "\f373"; } + content: "\f373"; +} .fa-audio-description:before { - content: "\f29e"; } + content: "\f29e"; +} .fa-autoprefixer:before { - content: "\f41c"; } + content: "\f41c"; +} .fa-avianex:before { - content: "\f374"; } + content: "\f374"; +} .fa-aviato:before { - content: "\f421"; } + content: "\f421"; +} .fa-award:before { - content: "\f559"; } + content: "\f559"; +} .fa-aws:before { - content: "\f375"; } + content: "\f375"; +} .fa-baby:before { - content: "\f77c"; } + content: "\f77c"; +} .fa-baby-carriage:before { - content: "\f77d"; } + content: "\f77d"; +} .fa-backspace:before { - content: "\f55a"; } + content: "\f55a"; +} .fa-backward:before { - content: "\f04a"; } + content: "\f04a"; +} .fa-bacon:before { - content: "\f7e5"; } + content: "\f7e5"; +} .fa-bahai:before { - content: "\f666"; } + content: "\f666"; +} .fa-balance-scale:before { - content: "\f24e"; } + content: "\f24e"; +} .fa-balance-scale-left:before { - content: "\f515"; } + content: "\f515"; +} .fa-balance-scale-right:before { - content: "\f516"; } + content: "\f516"; +} .fa-ban:before { - content: "\f05e"; } + content: "\f05e"; +} .fa-band-aid:before { - content: "\f462"; } + content: "\f462"; +} .fa-bandcamp:before { - content: "\f2d5"; } + content: "\f2d5"; +} .fa-barcode:before { - content: "\f02a"; } + content: "\f02a"; +} .fa-bars:before { - content: "\f0c9"; } + content: "\f0c9"; +} .fa-baseball-ball:before { - content: "\f433"; } + content: "\f433"; +} .fa-basketball-ball:before { - content: "\f434"; } + content: "\f434"; +} .fa-bath:before { - content: "\f2cd"; } + content: "\f2cd"; +} .fa-battery-empty:before { - content: "\f244"; } + content: "\f244"; +} .fa-battery-full:before { - content: "\f240"; } + content: "\f240"; +} .fa-battery-half:before { - content: "\f242"; } + content: "\f242"; +} .fa-battery-quarter:before { - content: "\f243"; } + content: "\f243"; +} .fa-battery-three-quarters:before { - content: "\f241"; } + content: "\f241"; +} .fa-battle-net:before { - content: "\f835"; } + content: "\f835"; +} .fa-bed:before { - content: "\f236"; } + content: "\f236"; +} .fa-beer:before { - content: "\f0fc"; } + content: "\f0fc"; +} .fa-behance:before { - content: "\f1b4"; } + content: "\f1b4"; +} .fa-behance-square:before { - content: "\f1b5"; } + content: "\f1b5"; +} .fa-bell:before { - content: "\f0f3"; } + content: "\f0f3"; +} .fa-bell-slash:before { - content: "\f1f6"; } + content: "\f1f6"; +} .fa-bezier-curve:before { - content: "\f55b"; } + content: "\f55b"; +} .fa-bible:before { - content: "\f647"; } + content: "\f647"; +} .fa-bicycle:before { - content: "\f206"; } + content: "\f206"; +} .fa-biking:before { - content: "\f84a"; } + content: "\f84a"; +} .fa-bimobject:before { - content: "\f378"; } + content: "\f378"; +} .fa-binoculars:before { - content: "\f1e5"; } + content: "\f1e5"; +} .fa-biohazard:before { - content: "\f780"; } + content: "\f780"; +} .fa-birthday-cake:before { - content: "\f1fd"; } + content: "\f1fd"; +} .fa-bitbucket:before { - content: "\f171"; } + content: "\f171"; +} .fa-bitcoin:before { - content: "\f379"; } + content: "\f379"; +} .fa-bity:before { - content: "\f37a"; } + content: "\f37a"; +} .fa-black-tie:before { - content: "\f27e"; } + content: "\f27e"; +} .fa-blackberry:before { - content: "\f37b"; } + content: "\f37b"; +} .fa-blender:before { - content: "\f517"; } + content: "\f517"; +} .fa-blender-phone:before { - content: "\f6b6"; } + content: "\f6b6"; +} .fa-blind:before { - content: "\f29d"; } + content: "\f29d"; +} .fa-blog:before { - content: "\f781"; } + content: "\f781"; +} .fa-blogger:before { - content: "\f37c"; } + content: "\f37c"; +} .fa-blogger-b:before { - content: "\f37d"; } + content: "\f37d"; +} .fa-bluetooth:before { - content: "\f293"; } + content: "\f293"; +} .fa-bluetooth-b:before { - content: "\f294"; } + content: "\f294"; +} .fa-bold:before { - content: "\f032"; } + content: "\f032"; +} .fa-bolt:before { - content: "\f0e7"; } + content: "\f0e7"; +} .fa-bomb:before { - content: "\f1e2"; } + content: "\f1e2"; +} .fa-bone:before { - content: "\f5d7"; } + content: "\f5d7"; +} .fa-bong:before { - content: "\f55c"; } + content: "\f55c"; +} .fa-book:before { - content: "\f02d"; } + content: "\f02d"; +} .fa-book-dead:before { - content: "\f6b7"; } + content: "\f6b7"; +} .fa-book-medical:before { - content: "\f7e6"; } + content: "\f7e6"; +} .fa-book-open:before { - content: "\f518"; } + content: "\f518"; +} .fa-book-reader:before { - content: "\f5da"; } + content: "\f5da"; +} .fa-bookmark:before { - content: "\f02e"; } + content: "\f02e"; +} .fa-bootstrap:before { - content: "\f836"; } + content: "\f836"; +} .fa-border-all:before { - content: "\f84c"; } + content: "\f84c"; +} .fa-border-none:before { - content: "\f850"; } + content: "\f850"; +} .fa-border-style:before { - content: "\f853"; } + content: "\f853"; +} .fa-bowling-ball:before { - content: "\f436"; } + content: "\f436"; +} .fa-box:before { - content: "\f466"; } + content: "\f466"; +} .fa-box-open:before { - content: "\f49e"; } + content: "\f49e"; +} .fa-boxes:before { - content: "\f468"; } + content: "\f468"; +} .fa-braille:before { - content: "\f2a1"; } + content: "\f2a1"; +} .fa-brain:before { - content: "\f5dc"; } + content: "\f5dc"; +} .fa-bread-slice:before { - content: "\f7ec"; } + content: "\f7ec"; +} .fa-briefcase:before { - content: "\f0b1"; } + content: "\f0b1"; +} .fa-briefcase-medical:before { - content: "\f469"; } + content: "\f469"; +} .fa-broadcast-tower:before { - content: "\f519"; } + content: "\f519"; +} .fa-broom:before { - content: "\f51a"; } + content: "\f51a"; +} .fa-brush:before { - content: "\f55d"; } + content: "\f55d"; +} .fa-btc:before { - content: "\f15a"; } + content: "\f15a"; +} .fa-buffer:before { - content: "\f837"; } + content: "\f837"; +} .fa-bug:before { - content: "\f188"; } + content: "\f188"; +} .fa-building:before { - content: "\f1ad"; } + content: "\f1ad"; +} .fa-bullhorn:before { - content: "\f0a1"; } + content: "\f0a1"; +} .fa-bullseye:before { - content: "\f140"; } + content: "\f140"; +} .fa-burn:before { - content: "\f46a"; } + content: "\f46a"; +} .fa-buromobelexperte:before { - content: "\f37f"; } + content: "\f37f"; +} .fa-bus:before { - content: "\f207"; } + content: "\f207"; +} .fa-bus-alt:before { - content: "\f55e"; } + content: "\f55e"; +} .fa-business-time:before { - content: "\f64a"; } + content: "\f64a"; +} .fa-buy-n-large:before { - content: "\f8a6"; } + content: "\f8a6"; +} .fa-buysellads:before { - content: "\f20d"; } + content: "\f20d"; +} .fa-calculator:before { - content: "\f1ec"; } + content: "\f1ec"; +} .fa-calendar:before { - content: "\f133"; } + content: "\f133"; +} .fa-calendar-alt:before { - content: "\f073"; } + content: "\f073"; +} .fa-calendar-check:before { - content: "\f274"; } + content: "\f274"; +} .fa-calendar-day:before { - content: "\f783"; } + content: "\f783"; +} .fa-calendar-minus:before { - content: "\f272"; } + content: "\f272"; +} .fa-calendar-plus:before { - content: "\f271"; } + content: "\f271"; +} .fa-calendar-times:before { - content: "\f273"; } + content: "\f273"; +} .fa-calendar-week:before { - content: "\f784"; } + content: "\f784"; +} .fa-camera:before { - content: "\f030"; } + content: "\f030"; +} .fa-camera-retro:before { - content: "\f083"; } + content: "\f083"; +} .fa-campground:before { - content: "\f6bb"; } + content: "\f6bb"; +} .fa-canadian-maple-leaf:before { - content: "\f785"; } + content: "\f785"; +} .fa-candy-cane:before { - content: "\f786"; } + content: "\f786"; +} .fa-cannabis:before { - content: "\f55f"; } + content: "\f55f"; +} .fa-capsules:before { - content: "\f46b"; } + content: "\f46b"; +} .fa-car:before { - content: "\f1b9"; } + content: "\f1b9"; +} .fa-car-alt:before { - content: "\f5de"; } + content: "\f5de"; +} .fa-car-battery:before { - content: "\f5df"; } + content: "\f5df"; +} .fa-car-crash:before { - content: "\f5e1"; } + content: "\f5e1"; +} .fa-car-side:before { - content: "\f5e4"; } + content: "\f5e4"; +} .fa-caravan:before { - content: "\f8ff"; } + content: "\f8ff"; +} .fa-caret-down:before { - content: "\f0d7"; } + content: "\f0d7"; +} .fa-caret-left:before { - content: "\f0d9"; } + content: "\f0d9"; +} .fa-caret-right:before { - content: "\f0da"; } + content: "\f0da"; +} .fa-caret-square-down:before { - content: "\f150"; } + content: "\f150"; +} .fa-caret-square-left:before { - content: "\f191"; } + content: "\f191"; +} .fa-caret-square-right:before { - content: "\f152"; } + content: "\f152"; +} .fa-caret-square-up:before { - content: "\f151"; } + content: "\f151"; +} .fa-caret-up:before { - content: "\f0d8"; } + content: "\f0d8"; +} .fa-carrot:before { - content: "\f787"; } + content: "\f787"; +} .fa-cart-arrow-down:before { - content: "\f218"; } + content: "\f218"; +} .fa-cart-plus:before { - content: "\f217"; } + content: "\f217"; +} .fa-cash-register:before { - content: "\f788"; } + content: "\f788"; +} .fa-cat:before { - content: "\f6be"; } + content: "\f6be"; +} .fa-cc-amazon-pay:before { - content: "\f42d"; } + content: "\f42d"; +} .fa-cc-amex:before { - content: "\f1f3"; } + content: "\f1f3"; +} .fa-cc-apple-pay:before { - content: "\f416"; } + content: "\f416"; +} .fa-cc-diners-club:before { - content: "\f24c"; } + content: "\f24c"; +} .fa-cc-discover:before { - content: "\f1f2"; } + content: "\f1f2"; +} .fa-cc-jcb:before { - content: "\f24b"; } + content: "\f24b"; +} .fa-cc-mastercard:before { - content: "\f1f1"; } + content: "\f1f1"; +} .fa-cc-paypal:before { - content: "\f1f4"; } + content: "\f1f4"; +} .fa-cc-stripe:before { - content: "\f1f5"; } + content: "\f1f5"; +} .fa-cc-visa:before { - content: "\f1f0"; } + content: "\f1f0"; +} .fa-centercode:before { - content: "\f380"; } + content: "\f380"; +} .fa-centos:before { - content: "\f789"; } + content: "\f789"; +} .fa-certificate:before { - content: "\f0a3"; } + content: "\f0a3"; +} .fa-chair:before { - content: "\f6c0"; } + content: "\f6c0"; +} .fa-chalkboard:before { - content: "\f51b"; } + content: "\f51b"; +} .fa-chalkboard-teacher:before { - content: "\f51c"; } + content: "\f51c"; +} .fa-charging-station:before { - content: "\f5e7"; } + content: "\f5e7"; +} .fa-chart-area:before { - content: "\f1fe"; } + content: "\f1fe"; +} .fa-chart-bar:before { - content: "\f080"; } + content: "\f080"; +} .fa-chart-line:before { - content: "\f201"; } + content: "\f201"; +} .fa-chart-pie:before { - content: "\f200"; } + content: "\f200"; +} .fa-check:before { - content: "\f00c"; } + content: "\f00c"; +} .fa-check-circle:before { - content: "\f058"; } + content: "\f058"; +} .fa-check-double:before { - content: "\f560"; } + content: "\f560"; +} .fa-check-square:before { - content: "\f14a"; } + content: "\f14a"; +} .fa-cheese:before { - content: "\f7ef"; } + content: "\f7ef"; +} .fa-chess:before { - content: "\f439"; } + content: "\f439"; +} .fa-chess-bishop:before { - content: "\f43a"; } + content: "\f43a"; +} .fa-chess-board:before { - content: "\f43c"; } + content: "\f43c"; +} .fa-chess-king:before { - content: "\f43f"; } + content: "\f43f"; +} .fa-chess-knight:before { - content: "\f441"; } + content: "\f441"; +} .fa-chess-pawn:before { - content: "\f443"; } + content: "\f443"; +} .fa-chess-queen:before { - content: "\f445"; } + content: "\f445"; +} .fa-chess-rook:before { - content: "\f447"; } + content: "\f447"; +} .fa-chevron-circle-down:before { - content: "\f13a"; } + content: "\f13a"; +} .fa-chevron-circle-left:before { - content: "\f137"; } + content: "\f137"; +} .fa-chevron-circle-right:before { - content: "\f138"; } + content: "\f138"; +} .fa-chevron-circle-up:before { - content: "\f139"; } + content: "\f139"; +} .fa-chevron-down:before { - content: "\f078"; } + content: "\f078"; +} .fa-chevron-left:before { - content: "\f053"; } + content: "\f053"; +} .fa-chevron-right:before { - content: "\f054"; } + content: "\f054"; +} .fa-chevron-up:before { - content: "\f077"; } + content: "\f077"; +} .fa-child:before { - content: "\f1ae"; } + content: "\f1ae"; +} .fa-chrome:before { - content: "\f268"; } + content: "\f268"; +} .fa-chromecast:before { - content: "\f838"; } + content: "\f838"; +} .fa-church:before { - content: "\f51d"; } + content: "\f51d"; +} .fa-circle:before { - content: "\f111"; } + content: "\f111"; +} .fa-circle-notch:before { - content: "\f1ce"; } + content: "\f1ce"; +} .fa-city:before { - content: "\f64f"; } + content: "\f64f"; +} .fa-clinic-medical:before { - content: "\f7f2"; } + content: "\f7f2"; +} .fa-clipboard:before { - content: "\f328"; } + content: "\f328"; +} .fa-clipboard-check:before { - content: "\f46c"; } + content: "\f46c"; +} .fa-clipboard-list:before { - content: "\f46d"; } + content: "\f46d"; +} .fa-clock:before { - content: "\f017"; } + content: "\f017"; +} .fa-clone:before { - content: "\f24d"; } + content: "\f24d"; +} .fa-closed-captioning:before { - content: "\f20a"; } + content: "\f20a"; +} .fa-cloud:before { - content: "\f0c2"; } + content: "\f0c2"; +} .fa-cloud-download-alt:before { - content: "\f381"; } + content: "\f381"; +} .fa-cloud-meatball:before { - content: "\f73b"; } + content: "\f73b"; +} .fa-cloud-moon:before { - content: "\f6c3"; } + content: "\f6c3"; +} .fa-cloud-moon-rain:before { - content: "\f73c"; } + content: "\f73c"; +} .fa-cloud-rain:before { - content: "\f73d"; } + content: "\f73d"; +} .fa-cloud-showers-heavy:before { - content: "\f740"; } + content: "\f740"; +} .fa-cloud-sun:before { - content: "\f6c4"; } + content: "\f6c4"; +} .fa-cloud-sun-rain:before { - content: "\f743"; } + content: "\f743"; +} .fa-cloud-upload-alt:before { - content: "\f382"; } + content: "\f382"; +} .fa-cloudscale:before { - content: "\f383"; } + content: "\f383"; +} .fa-cloudsmith:before { - content: "\f384"; } + content: "\f384"; +} .fa-cloudversify:before { - content: "\f385"; } + content: "\f385"; +} .fa-cocktail:before { - content: "\f561"; } + content: "\f561"; +} .fa-code:before { - content: "\f121"; } + content: "\f121"; +} .fa-code-branch:before { - content: "\f126"; } + content: "\f126"; +} .fa-codepen:before { - content: "\f1cb"; } + content: "\f1cb"; +} .fa-codiepie:before { - content: "\f284"; } + content: "\f284"; +} .fa-coffee:before { - content: "\f0f4"; } + content: "\f0f4"; +} .fa-cog:before { - content: "\f013"; } + content: "\f013"; +} .fa-cogs:before { - content: "\f085"; } + content: "\f085"; +} .fa-coins:before { - content: "\f51e"; } + content: "\f51e"; +} .fa-columns:before { - content: "\f0db"; } + content: "\f0db"; +} .fa-comment:before { - content: "\f075"; } + content: "\f075"; +} .fa-comment-alt:before { - content: "\f27a"; } + content: "\f27a"; +} .fa-comment-dollar:before { - content: "\f651"; } + content: "\f651"; +} .fa-comment-dots:before { - content: "\f4ad"; } + content: "\f4ad"; +} .fa-comment-medical:before { - content: "\f7f5"; } + content: "\f7f5"; +} .fa-comment-slash:before { - content: "\f4b3"; } + content: "\f4b3"; +} .fa-comments:before { - content: "\f086"; } + content: "\f086"; +} .fa-comments-dollar:before { - content: "\f653"; } + content: "\f653"; +} .fa-compact-disc:before { - content: "\f51f"; } + content: "\f51f"; +} .fa-compass:before { - content: "\f14e"; } + content: "\f14e"; +} .fa-compress:before { - content: "\f066"; } + content: "\f066"; +} .fa-compress-alt:before { - content: "\f422"; } + content: "\f422"; +} .fa-compress-arrows-alt:before { - content: "\f78c"; } + content: "\f78c"; +} .fa-concierge-bell:before { - content: "\f562"; } + content: "\f562"; +} .fa-confluence:before { - content: "\f78d"; } + content: "\f78d"; +} .fa-connectdevelop:before { - content: "\f20e"; } + content: "\f20e"; +} .fa-contao:before { - content: "\f26d"; } + content: "\f26d"; +} .fa-cookie:before { - content: "\f563"; } + content: "\f563"; +} .fa-cookie-bite:before { - content: "\f564"; } + content: "\f564"; +} .fa-copy:before { - content: "\f0c5"; } + content: "\f0c5"; +} .fa-copyright:before { - content: "\f1f9"; } + content: "\f1f9"; +} .fa-cotton-bureau:before { - content: "\f89e"; } + content: "\f89e"; +} .fa-couch:before { - content: "\f4b8"; } + content: "\f4b8"; +} .fa-cpanel:before { - content: "\f388"; } + content: "\f388"; +} .fa-creative-commons:before { - content: "\f25e"; } + content: "\f25e"; +} .fa-creative-commons-by:before { - content: "\f4e7"; } + content: "\f4e7"; +} .fa-creative-commons-nc:before { - content: "\f4e8"; } + content: "\f4e8"; +} .fa-creative-commons-nc-eu:before { - content: "\f4e9"; } + content: "\f4e9"; +} .fa-creative-commons-nc-jp:before { - content: "\f4ea"; } + content: "\f4ea"; +} .fa-creative-commons-nd:before { - content: "\f4eb"; } + content: "\f4eb"; +} .fa-creative-commons-pd:before { - content: "\f4ec"; } + content: "\f4ec"; +} .fa-creative-commons-pd-alt:before { - content: "\f4ed"; } + content: "\f4ed"; +} .fa-creative-commons-remix:before { - content: "\f4ee"; } + content: "\f4ee"; +} .fa-creative-commons-sa:before { - content: "\f4ef"; } + content: "\f4ef"; +} .fa-creative-commons-sampling:before { - content: "\f4f0"; } + content: "\f4f0"; +} .fa-creative-commons-sampling-plus:before { - content: "\f4f1"; } + content: "\f4f1"; +} .fa-creative-commons-share:before { - content: "\f4f2"; } + content: "\f4f2"; +} .fa-creative-commons-zero:before { - content: "\f4f3"; } + content: "\f4f3"; +} .fa-credit-card:before { - content: "\f09d"; } + content: "\f09d"; +} .fa-critical-role:before { - content: "\f6c9"; } + content: "\f6c9"; +} .fa-crop:before { - content: "\f125"; } + content: "\f125"; +} .fa-crop-alt:before { - content: "\f565"; } + content: "\f565"; +} .fa-cross:before { - content: "\f654"; } + content: "\f654"; +} .fa-crosshairs:before { - content: "\f05b"; } + content: "\f05b"; +} .fa-crow:before { - content: "\f520"; } + content: "\f520"; +} .fa-crown:before { - content: "\f521"; } + content: "\f521"; +} .fa-crutch:before { - content: "\f7f7"; } + content: "\f7f7"; +} .fa-css3:before { - content: "\f13c"; } + content: "\f13c"; +} .fa-css3-alt:before { - content: "\f38b"; } + content: "\f38b"; +} .fa-cube:before { - content: "\f1b2"; } + content: "\f1b2"; +} .fa-cubes:before { - content: "\f1b3"; } + content: "\f1b3"; +} .fa-cut:before { - content: "\f0c4"; } + content: "\f0c4"; +} .fa-cuttlefish:before { - content: "\f38c"; } + content: "\f38c"; +} .fa-d-and-d:before { - content: "\f38d"; } + content: "\f38d"; +} .fa-d-and-d-beyond:before { - content: "\f6ca"; } + content: "\f6ca"; +} .fa-dashcube:before { - content: "\f210"; } + content: "\f210"; +} .fa-database:before { - content: "\f1c0"; } + content: "\f1c0"; +} .fa-deaf:before { - content: "\f2a4"; } + content: "\f2a4"; +} .fa-delicious:before { - content: "\f1a5"; } + content: "\f1a5"; +} .fa-democrat:before { - content: "\f747"; } + content: "\f747"; +} .fa-deploydog:before { - content: "\f38e"; } + content: "\f38e"; +} .fa-deskpro:before { - content: "\f38f"; } + content: "\f38f"; +} .fa-desktop:before { - content: "\f108"; } + content: "\f108"; +} .fa-dev:before { - content: "\f6cc"; } + content: "\f6cc"; +} .fa-deviantart:before { - content: "\f1bd"; } + content: "\f1bd"; +} .fa-dharmachakra:before { - content: "\f655"; } + content: "\f655"; +} .fa-dhl:before { - content: "\f790"; } + content: "\f790"; +} .fa-diagnoses:before { - content: "\f470"; } + content: "\f470"; +} .fa-diaspora:before { - content: "\f791"; } + content: "\f791"; +} .fa-dice:before { - content: "\f522"; } + content: "\f522"; +} .fa-dice-d20:before { - content: "\f6cf"; } + content: "\f6cf"; +} .fa-dice-d6:before { - content: "\f6d1"; } + content: "\f6d1"; +} .fa-dice-five:before { - content: "\f523"; } + content: "\f523"; +} .fa-dice-four:before { - content: "\f524"; } + content: "\f524"; +} .fa-dice-one:before { - content: "\f525"; } + content: "\f525"; +} .fa-dice-six:before { - content: "\f526"; } + content: "\f526"; +} .fa-dice-three:before { - content: "\f527"; } + content: "\f527"; +} .fa-dice-two:before { - content: "\f528"; } + content: "\f528"; +} .fa-digg:before { - content: "\f1a6"; } + content: "\f1a6"; +} .fa-digital-ocean:before { - content: "\f391"; } + content: "\f391"; +} .fa-digital-tachograph:before { - content: "\f566"; } + content: "\f566"; +} .fa-directions:before { - content: "\f5eb"; } + content: "\f5eb"; +} .fa-discord:before { - content: "\f392"; } + content: "\f392"; +} .fa-discourse:before { - content: "\f393"; } + content: "\f393"; +} .fa-divide:before { - content: "\f529"; } + content: "\f529"; +} .fa-dizzy:before { - content: "\f567"; } + content: "\f567"; +} .fa-dna:before { - content: "\f471"; } + content: "\f471"; +} .fa-dochub:before { - content: "\f394"; } + content: "\f394"; +} .fa-docker:before { - content: "\f395"; } + content: "\f395"; +} .fa-dog:before { - content: "\f6d3"; } + content: "\f6d3"; +} .fa-dollar-sign:before { - content: "\f155"; } + content: "\f155"; +} .fa-dolly:before { - content: "\f472"; } + content: "\f472"; +} .fa-dolly-flatbed:before { - content: "\f474"; } + content: "\f474"; +} .fa-donate:before { - content: "\f4b9"; } + content: "\f4b9"; +} .fa-door-closed:before { - content: "\f52a"; } + content: "\f52a"; +} .fa-door-open:before { - content: "\f52b"; } + content: "\f52b"; +} .fa-dot-circle:before { - content: "\f192"; } + content: "\f192"; +} .fa-dove:before { - content: "\f4ba"; } + content: "\f4ba"; +} .fa-download:before { - content: "\f019"; } + content: "\f019"; +} .fa-draft2digital:before { - content: "\f396"; } + content: "\f396"; +} .fa-drafting-compass:before { - content: "\f568"; } + content: "\f568"; +} .fa-dragon:before { - content: "\f6d5"; } + content: "\f6d5"; +} .fa-draw-polygon:before { - content: "\f5ee"; } + content: "\f5ee"; +} .fa-dribbble:before { - content: "\f17d"; } + content: "\f17d"; +} .fa-dribbble-square:before { - content: "\f397"; } + content: "\f397"; +} .fa-dropbox:before { - content: "\f16b"; } + content: "\f16b"; +} .fa-drum:before { - content: "\f569"; } + content: "\f569"; +} .fa-drum-steelpan:before { - content: "\f56a"; } + content: "\f56a"; +} .fa-drumstick-bite:before { - content: "\f6d7"; } + content: "\f6d7"; +} .fa-drupal:before { - content: "\f1a9"; } + content: "\f1a9"; +} .fa-dumbbell:before { - content: "\f44b"; } + content: "\f44b"; +} .fa-dumpster:before { - content: "\f793"; } + content: "\f793"; +} .fa-dumpster-fire:before { - content: "\f794"; } + content: "\f794"; +} .fa-dungeon:before { - content: "\f6d9"; } + content: "\f6d9"; +} .fa-dyalog:before { - content: "\f399"; } + content: "\f399"; +} .fa-earlybirds:before { - content: "\f39a"; } + content: "\f39a"; +} .fa-ebay:before { - content: "\f4f4"; } + content: "\f4f4"; +} .fa-edge:before { - content: "\f282"; } + content: "\f282"; +} .fa-edit:before { - content: "\f044"; } + content: "\f044"; +} .fa-egg:before { - content: "\f7fb"; } + content: "\f7fb"; +} .fa-eject:before { - content: "\f052"; } + content: "\f052"; +} .fa-elementor:before { - content: "\f430"; } + content: "\f430"; +} .fa-ellipsis-h:before { - content: "\f141"; } + content: "\f141"; +} .fa-ellipsis-v:before { - content: "\f142"; } + content: "\f142"; +} .fa-ello:before { - content: "\f5f1"; } + content: "\f5f1"; +} .fa-ember:before { - content: "\f423"; } + content: "\f423"; +} .fa-empire:before { - content: "\f1d1"; } + content: "\f1d1"; +} .fa-envelope:before { - content: "\f0e0"; } + content: "\f0e0"; +} .fa-envelope-open:before { - content: "\f2b6"; } + content: "\f2b6"; +} .fa-envelope-open-text:before { - content: "\f658"; } + content: "\f658"; +} .fa-envelope-square:before { - content: "\f199"; } + content: "\f199"; +} .fa-envira:before { - content: "\f299"; } + content: "\f299"; +} .fa-equals:before { - content: "\f52c"; } + content: "\f52c"; +} .fa-eraser:before { - content: "\f12d"; } + content: "\f12d"; +} .fa-erlang:before { - content: "\f39d"; } + content: "\f39d"; +} .fa-ethereum:before { - content: "\f42e"; } + content: "\f42e"; +} .fa-ethernet:before { - content: "\f796"; } + content: "\f796"; +} .fa-etsy:before { - content: "\f2d7"; } + content: "\f2d7"; +} .fa-euro-sign:before { - content: "\f153"; } + content: "\f153"; +} .fa-evernote:before { - content: "\f839"; } + content: "\f839"; +} .fa-exchange-alt:before { - content: "\f362"; } + content: "\f362"; +} .fa-exclamation:before { - content: "\f12a"; } + content: "\f12a"; +} .fa-exclamation-circle:before { - content: "\f06a"; } + content: "\f06a"; +} .fa-exclamation-triangle:before { - content: "\f071"; } + content: "\f071"; +} .fa-expand:before { - content: "\f065"; } + content: "\f065"; +} .fa-expand-alt:before { - content: "\f424"; } + content: "\f424"; +} .fa-expand-arrows-alt:before { - content: "\f31e"; } + content: "\f31e"; +} .fa-expeditedssl:before { - content: "\f23e"; } + content: "\f23e"; +} .fa-external-link-alt:before { - content: "\f35d"; } + content: "\f35d"; +} .fa-external-link-square-alt:before { - content: "\f360"; } + content: "\f360"; +} .fa-eye:before { - content: "\f06e"; } + content: "\f06e"; +} .fa-eye-dropper:before { - content: "\f1fb"; } + content: "\f1fb"; +} .fa-eye-slash:before { - content: "\f070"; } + content: "\f070"; +} .fa-facebook:before { - content: "\f09a"; } + content: "\f09a"; +} .fa-facebook-f:before { - content: "\f39e"; } + content: "\f39e"; +} .fa-facebook-messenger:before { - content: "\f39f"; } + content: "\f39f"; +} .fa-facebook-square:before { - content: "\f082"; } + content: "\f082"; +} .fa-fan:before { - content: "\f863"; } + content: "\f863"; +} .fa-fantasy-flight-games:before { - content: "\f6dc"; } + content: "\f6dc"; +} .fa-fast-backward:before { - content: "\f049"; } + content: "\f049"; +} .fa-fast-forward:before { - content: "\f050"; } + content: "\f050"; +} .fa-fax:before { - content: "\f1ac"; } + content: "\f1ac"; +} .fa-feather:before { - content: "\f52d"; } + content: "\f52d"; +} .fa-feather-alt:before { - content: "\f56b"; } + content: "\f56b"; +} .fa-fedex:before { - content: "\f797"; } + content: "\f797"; +} .fa-fedora:before { - content: "\f798"; } + content: "\f798"; +} .fa-female:before { - content: "\f182"; } + content: "\f182"; +} .fa-fighter-jet:before { - content: "\f0fb"; } + content: "\f0fb"; +} .fa-figma:before { - content: "\f799"; } + content: "\f799"; +} .fa-file:before { - content: "\f15b"; } + content: "\f15b"; +} .fa-file-alt:before { - content: "\f15c"; } + content: "\f15c"; +} .fa-file-archive:before { - content: "\f1c6"; } + content: "\f1c6"; +} .fa-file-audio:before { - content: "\f1c7"; } + content: "\f1c7"; +} .fa-file-code:before { - content: "\f1c9"; } + content: "\f1c9"; +} .fa-file-contract:before { - content: "\f56c"; } + content: "\f56c"; +} .fa-file-csv:before { - content: "\f6dd"; } + content: "\f6dd"; +} .fa-file-download:before { - content: "\f56d"; } + content: "\f56d"; +} .fa-file-excel:before { - content: "\f1c3"; } + content: "\f1c3"; +} .fa-file-export:before { - content: "\f56e"; } + content: "\f56e"; +} .fa-file-image:before { - content: "\f1c5"; } + content: "\f1c5"; +} .fa-file-import:before { - content: "\f56f"; } + content: "\f56f"; +} .fa-file-invoice:before { - content: "\f570"; } + content: "\f570"; +} .fa-file-invoice-dollar:before { - content: "\f571"; } + content: "\f571"; +} .fa-file-medical:before { - content: "\f477"; } + content: "\f477"; +} .fa-file-medical-alt:before { - content: "\f478"; } + content: "\f478"; +} .fa-file-pdf:before { - content: "\f1c1"; } + content: "\f1c1"; +} .fa-file-powerpoint:before { - content: "\f1c4"; } + content: "\f1c4"; +} .fa-file-prescription:before { - content: "\f572"; } + content: "\f572"; +} .fa-file-signature:before { - content: "\f573"; } + content: "\f573"; +} .fa-file-upload:before { - content: "\f574"; } + content: "\f574"; +} .fa-file-video:before { - content: "\f1c8"; } + content: "\f1c8"; +} .fa-file-word:before { - content: "\f1c2"; } + content: "\f1c2"; +} .fa-fill:before { - content: "\f575"; } + content: "\f575"; +} .fa-fill-drip:before { - content: "\f576"; } + content: "\f576"; +} .fa-film:before { - content: "\f008"; } + content: "\f008"; +} .fa-filter:before { - content: "\f0b0"; } + content: "\f0b0"; +} .fa-fingerprint:before { - content: "\f577"; } + content: "\f577"; +} .fa-fire:before { - content: "\f06d"; } + content: "\f06d"; +} .fa-fire-alt:before { - content: "\f7e4"; } + content: "\f7e4"; +} .fa-fire-extinguisher:before { - content: "\f134"; } + content: "\f134"; +} .fa-firefox:before { - content: "\f269"; } + content: "\f269"; +} .fa-firefox-browser:before { - content: "\f907"; } + content: "\f907"; +} .fa-first-aid:before { - content: "\f479"; } + content: "\f479"; +} .fa-first-order:before { - content: "\f2b0"; } + content: "\f2b0"; +} .fa-first-order-alt:before { - content: "\f50a"; } + content: "\f50a"; +} .fa-firstdraft:before { - content: "\f3a1"; } + content: "\f3a1"; +} .fa-fish:before { - content: "\f578"; } + content: "\f578"; +} .fa-fist-raised:before { - content: "\f6de"; } + content: "\f6de"; +} .fa-flag:before { - content: "\f024"; } + content: "\f024"; +} .fa-flag-checkered:before { - content: "\f11e"; } + content: "\f11e"; +} .fa-flag-usa:before { - content: "\f74d"; } + content: "\f74d"; +} .fa-flask:before { - content: "\f0c3"; } + content: "\f0c3"; +} .fa-flickr:before { - content: "\f16e"; } + content: "\f16e"; +} .fa-flipboard:before { - content: "\f44d"; } + content: "\f44d"; +} .fa-flushed:before { - content: "\f579"; } + content: "\f579"; +} .fa-fly:before { - content: "\f417"; } + content: "\f417"; +} .fa-folder:before { - content: "\f07b"; } + content: "\f07b"; +} .fa-folder-minus:before { - content: "\f65d"; } + content: "\f65d"; +} .fa-folder-open:before { - content: "\f07c"; } + content: "\f07c"; +} .fa-folder-plus:before { - content: "\f65e"; } + content: "\f65e"; +} .fa-font:before { - content: "\f031"; } + content: "\f031"; +} .fa-font-awesome:before { - content: "\f2b4"; } + content: "\f2b4"; +} .fa-font-awesome-alt:before { - content: "\f35c"; } + content: "\f35c"; +} .fa-font-awesome-flag:before { - content: "\f425"; } + content: "\f425"; +} .fa-font-awesome-logo-full:before { - content: "\f4e6"; } + content: "\f4e6"; +} .fa-fonticons:before { - content: "\f280"; } + content: "\f280"; +} .fa-fonticons-fi:before { - content: "\f3a2"; } + content: "\f3a2"; +} .fa-football-ball:before { - content: "\f44e"; } + content: "\f44e"; +} .fa-fort-awesome:before { - content: "\f286"; } + content: "\f286"; +} .fa-fort-awesome-alt:before { - content: "\f3a3"; } + content: "\f3a3"; +} .fa-forumbee:before { - content: "\f211"; } + content: "\f211"; +} .fa-forward:before { - content: "\f04e"; } + content: "\f04e"; +} .fa-foursquare:before { - content: "\f180"; } + content: "\f180"; +} .fa-free-code-camp:before { - content: "\f2c5"; } + content: "\f2c5"; +} .fa-freebsd:before { - content: "\f3a4"; } + content: "\f3a4"; +} .fa-frog:before { - content: "\f52e"; } + content: "\f52e"; +} .fa-frown:before { - content: "\f119"; } + content: "\f119"; +} .fa-frown-open:before { - content: "\f57a"; } + content: "\f57a"; +} .fa-fulcrum:before { - content: "\f50b"; } + content: "\f50b"; +} .fa-funnel-dollar:before { - content: "\f662"; } + content: "\f662"; +} .fa-futbol:before { - content: "\f1e3"; } + content: "\f1e3"; +} .fa-galactic-republic:before { - content: "\f50c"; } + content: "\f50c"; +} .fa-galactic-senate:before { - content: "\f50d"; } + content: "\f50d"; +} .fa-gamepad:before { - content: "\f11b"; } + content: "\f11b"; +} .fa-gas-pump:before { - content: "\f52f"; } + content: "\f52f"; +} .fa-gavel:before { - content: "\f0e3"; } + content: "\f0e3"; +} .fa-gem:before { - content: "\f3a5"; } + content: "\f3a5"; +} .fa-genderless:before { - content: "\f22d"; } + content: "\f22d"; +} .fa-get-pocket:before { - content: "\f265"; } + content: "\f265"; +} .fa-gg:before { - content: "\f260"; } + content: "\f260"; +} .fa-gg-circle:before { - content: "\f261"; } + content: "\f261"; +} .fa-ghost:before { - content: "\f6e2"; } + content: "\f6e2"; +} .fa-gift:before { - content: "\f06b"; } + content: "\f06b"; +} .fa-gifts:before { - content: "\f79c"; } + content: "\f79c"; +} .fa-git:before { - content: "\f1d3"; } + content: "\f1d3"; +} .fa-git-alt:before { - content: "\f841"; } + content: "\f841"; +} .fa-git-square:before { - content: "\f1d2"; } + content: "\f1d2"; +} .fa-github:before { - content: "\f09b"; } + content: "\f09b"; +} .fa-github-alt:before { - content: "\f113"; } + content: "\f113"; +} .fa-github-square:before { - content: "\f092"; } + content: "\f092"; +} .fa-gitkraken:before { - content: "\f3a6"; } + content: "\f3a6"; +} .fa-gitlab:before { - content: "\f296"; } + content: "\f296"; +} .fa-gitter:before { - content: "\f426"; } + content: "\f426"; +} .fa-glass-cheers:before { - content: "\f79f"; } + content: "\f79f"; +} .fa-glass-martini:before { - content: "\f000"; } + content: "\f000"; +} .fa-glass-martini-alt:before { - content: "\f57b"; } + content: "\f57b"; +} .fa-glass-whiskey:before { - content: "\f7a0"; } + content: "\f7a0"; +} .fa-glasses:before { - content: "\f530"; } + content: "\f530"; +} .fa-glide:before { - content: "\f2a5"; } + content: "\f2a5"; +} .fa-glide-g:before { - content: "\f2a6"; } + content: "\f2a6"; +} .fa-globe:before { - content: "\f0ac"; } + content: "\f0ac"; +} .fa-globe-africa:before { - content: "\f57c"; } + content: "\f57c"; +} .fa-globe-americas:before { - content: "\f57d"; } + content: "\f57d"; +} .fa-globe-asia:before { - content: "\f57e"; } + content: "\f57e"; +} .fa-globe-europe:before { - content: "\f7a2"; } + content: "\f7a2"; +} .fa-gofore:before { - content: "\f3a7"; } + content: "\f3a7"; +} .fa-golf-ball:before { - content: "\f450"; } + content: "\f450"; +} .fa-goodreads:before { - content: "\f3a8"; } + content: "\f3a8"; +} .fa-goodreads-g:before { - content: "\f3a9"; } + content: "\f3a9"; +} .fa-google:before { - content: "\f1a0"; } + content: "\f1a0"; +} .fa-google-drive:before { - content: "\f3aa"; } + content: "\f3aa"; +} .fa-google-play:before { - content: "\f3ab"; } + content: "\f3ab"; +} .fa-google-plus:before { - content: "\f2b3"; } + content: "\f2b3"; +} .fa-google-plus-g:before { - content: "\f0d5"; } + content: "\f0d5"; +} .fa-google-plus-square:before { - content: "\f0d4"; } + content: "\f0d4"; +} .fa-google-wallet:before { - content: "\f1ee"; } + content: "\f1ee"; +} .fa-gopuram:before { - content: "\f664"; } + content: "\f664"; +} .fa-graduation-cap:before { - content: "\f19d"; } + content: "\f19d"; +} .fa-gratipay:before { - content: "\f184"; } + content: "\f184"; +} .fa-grav:before { - content: "\f2d6"; } + content: "\f2d6"; +} .fa-greater-than:before { - content: "\f531"; } + content: "\f531"; +} .fa-greater-than-equal:before { - content: "\f532"; } + content: "\f532"; +} .fa-grimace:before { - content: "\f57f"; } + content: "\f57f"; +} .fa-grin:before { - content: "\f580"; } + content: "\f580"; +} .fa-grin-alt:before { - content: "\f581"; } + content: "\f581"; +} .fa-grin-beam:before { - content: "\f582"; } + content: "\f582"; +} .fa-grin-beam-sweat:before { - content: "\f583"; } + content: "\f583"; +} .fa-grin-hearts:before { - content: "\f584"; } + content: "\f584"; +} .fa-grin-squint:before { - content: "\f585"; } + content: "\f585"; +} .fa-grin-squint-tears:before { - content: "\f586"; } + content: "\f586"; +} .fa-grin-stars:before { - content: "\f587"; } + content: "\f587"; +} .fa-grin-tears:before { - content: "\f588"; } + content: "\f588"; +} .fa-grin-tongue:before { - content: "\f589"; } + content: "\f589"; +} .fa-grin-tongue-squint:before { - content: "\f58a"; } + content: "\f58a"; +} .fa-grin-tongue-wink:before { - content: "\f58b"; } + content: "\f58b"; +} .fa-grin-wink:before { - content: "\f58c"; } + content: "\f58c"; +} .fa-grip-horizontal:before { - content: "\f58d"; } + content: "\f58d"; +} .fa-grip-lines:before { - content: "\f7a4"; } + content: "\f7a4"; +} .fa-grip-lines-vertical:before { - content: "\f7a5"; } + content: "\f7a5"; +} .fa-grip-vertical:before { - content: "\f58e"; } + content: "\f58e"; +} .fa-gripfire:before { - content: "\f3ac"; } + content: "\f3ac"; +} .fa-grunt:before { - content: "\f3ad"; } + content: "\f3ad"; +} .fa-guitar:before { - content: "\f7a6"; } + content: "\f7a6"; +} .fa-gulp:before { - content: "\f3ae"; } + content: "\f3ae"; +} .fa-h-square:before { - content: "\f0fd"; } + content: "\f0fd"; +} .fa-hacker-news:before { - content: "\f1d4"; } + content: "\f1d4"; +} .fa-hacker-news-square:before { - content: "\f3af"; } + content: "\f3af"; +} .fa-hackerrank:before { - content: "\f5f7"; } + content: "\f5f7"; +} .fa-hamburger:before { - content: "\f805"; } + content: "\f805"; +} .fa-hammer:before { - content: "\f6e3"; } + content: "\f6e3"; +} .fa-hamsa:before { - content: "\f665"; } + content: "\f665"; +} .fa-hand-holding:before { - content: "\f4bd"; } + content: "\f4bd"; +} .fa-hand-holding-heart:before { - content: "\f4be"; } + content: "\f4be"; +} .fa-hand-holding-usd:before { - content: "\f4c0"; } + content: "\f4c0"; +} .fa-hand-lizard:before { - content: "\f258"; } + content: "\f258"; +} .fa-hand-middle-finger:before { - content: "\f806"; } + content: "\f806"; +} .fa-hand-paper:before { - content: "\f256"; } + content: "\f256"; +} .fa-hand-peace:before { - content: "\f25b"; } + content: "\f25b"; +} .fa-hand-point-down:before { - content: "\f0a7"; } + content: "\f0a7"; +} .fa-hand-point-left:before { - content: "\f0a5"; } + content: "\f0a5"; +} .fa-hand-point-right:before { - content: "\f0a4"; } + content: "\f0a4"; +} .fa-hand-point-up:before { - content: "\f0a6"; } + content: "\f0a6"; +} .fa-hand-pointer:before { - content: "\f25a"; } + content: "\f25a"; +} .fa-hand-rock:before { - content: "\f255"; } + content: "\f255"; +} .fa-hand-scissors:before { - content: "\f257"; } + content: "\f257"; +} .fa-hand-spock:before { - content: "\f259"; } + content: "\f259"; +} .fa-hands:before { - content: "\f4c2"; } + content: "\f4c2"; +} .fa-hands-helping:before { - content: "\f4c4"; } + content: "\f4c4"; +} .fa-handshake:before { - content: "\f2b5"; } + content: "\f2b5"; +} .fa-hanukiah:before { - content: "\f6e6"; } + content: "\f6e6"; +} .fa-hard-hat:before { - content: "\f807"; } + content: "\f807"; +} .fa-hashtag:before { - content: "\f292"; } + content: "\f292"; +} .fa-hat-cowboy:before { - content: "\f8c0"; } + content: "\f8c0"; +} .fa-hat-cowboy-side:before { - content: "\f8c1"; } + content: "\f8c1"; +} .fa-hat-wizard:before { - content: "\f6e8"; } + content: "\f6e8"; +} .fa-hdd:before { - content: "\f0a0"; } + content: "\f0a0"; +} .fa-heading:before { - content: "\f1dc"; } + content: "\f1dc"; +} .fa-headphones:before { - content: "\f025"; } + content: "\f025"; +} .fa-headphones-alt:before { - content: "\f58f"; } + content: "\f58f"; +} .fa-headset:before { - content: "\f590"; } + content: "\f590"; +} .fa-heart:before { - content: "\f004"; } + content: "\f004"; +} .fa-heart-broken:before { - content: "\f7a9"; } + content: "\f7a9"; +} .fa-heartbeat:before { - content: "\f21e"; } + content: "\f21e"; +} .fa-helicopter:before { - content: "\f533"; } + content: "\f533"; +} .fa-highlighter:before { - content: "\f591"; } + content: "\f591"; +} .fa-hiking:before { - content: "\f6ec"; } + content: "\f6ec"; +} .fa-hippo:before { - content: "\f6ed"; } + content: "\f6ed"; +} .fa-hips:before { - content: "\f452"; } + content: "\f452"; +} .fa-hire-a-helper:before { - content: "\f3b0"; } + content: "\f3b0"; +} .fa-history:before { - content: "\f1da"; } + content: "\f1da"; +} .fa-hockey-puck:before { - content: "\f453"; } + content: "\f453"; +} .fa-holly-berry:before { - content: "\f7aa"; } + content: "\f7aa"; +} .fa-home:before { - content: "\f015"; } + content: "\f015"; +} .fa-hooli:before { - content: "\f427"; } + content: "\f427"; +} .fa-hornbill:before { - content: "\f592"; } + content: "\f592"; +} .fa-horse:before { - content: "\f6f0"; } + content: "\f6f0"; +} .fa-horse-head:before { - content: "\f7ab"; } + content: "\f7ab"; +} .fa-hospital:before { - content: "\f0f8"; } + content: "\f0f8"; +} .fa-hospital-alt:before { - content: "\f47d"; } + content: "\f47d"; +} .fa-hospital-symbol:before { - content: "\f47e"; } + content: "\f47e"; +} .fa-hot-tub:before { - content: "\f593"; } + content: "\f593"; +} .fa-hotdog:before { - content: "\f80f"; } + content: "\f80f"; +} .fa-hotel:before { - content: "\f594"; } + content: "\f594"; +} .fa-hotjar:before { - content: "\f3b1"; } + content: "\f3b1"; +} .fa-hourglass:before { - content: "\f254"; } + content: "\f254"; +} .fa-hourglass-end:before { - content: "\f253"; } + content: "\f253"; +} .fa-hourglass-half:before { - content: "\f252"; } + content: "\f252"; +} .fa-hourglass-start:before { - content: "\f251"; } + content: "\f251"; +} .fa-house-damage:before { - content: "\f6f1"; } + content: "\f6f1"; +} .fa-houzz:before { - content: "\f27c"; } + content: "\f27c"; +} .fa-hryvnia:before { - content: "\f6f2"; } + content: "\f6f2"; +} .fa-html5:before { - content: "\f13b"; } + content: "\f13b"; +} .fa-hubspot:before { - content: "\f3b2"; } + content: "\f3b2"; +} .fa-i-cursor:before { - content: "\f246"; } + content: "\f246"; +} .fa-ice-cream:before { - content: "\f810"; } + content: "\f810"; +} .fa-icicles:before { - content: "\f7ad"; } + content: "\f7ad"; +} .fa-icons:before { - content: "\f86d"; } + content: "\f86d"; +} .fa-id-badge:before { - content: "\f2c1"; } + content: "\f2c1"; +} .fa-id-card:before { - content: "\f2c2"; } + content: "\f2c2"; +} .fa-id-card-alt:before { - content: "\f47f"; } + content: "\f47f"; +} .fa-ideal:before { - content: "\f913"; } + content: "\f913"; +} .fa-igloo:before { - content: "\f7ae"; } + content: "\f7ae"; +} .fa-image:before { - content: "\f03e"; } + content: "\f03e"; +} .fa-images:before { - content: "\f302"; } + content: "\f302"; +} .fa-imdb:before { - content: "\f2d8"; } + content: "\f2d8"; +} .fa-inbox:before { - content: "\f01c"; } + content: "\f01c"; +} .fa-indent:before { - content: "\f03c"; } + content: "\f03c"; +} .fa-industry:before { - content: "\f275"; } + content: "\f275"; +} .fa-infinity:before { - content: "\f534"; } + content: "\f534"; +} .fa-info:before { - content: "\f129"; } + content: "\f129"; +} .fa-info-circle:before { - content: "\f05a"; } + content: "\f05a"; +} .fa-instagram:before { - content: "\f16d"; } + content: "\f16d"; +} .fa-intercom:before { - content: "\f7af"; } + content: "\f7af"; +} .fa-internet-explorer:before { - content: "\f26b"; } + content: "\f26b"; +} .fa-invision:before { - content: "\f7b0"; } + content: "\f7b0"; +} .fa-ioxhost:before { - content: "\f208"; } + content: "\f208"; +} .fa-italic:before { - content: "\f033"; } + content: "\f033"; +} .fa-itch-io:before { - content: "\f83a"; } + content: "\f83a"; +} .fa-itunes:before { - content: "\f3b4"; } + content: "\f3b4"; +} .fa-itunes-note:before { - content: "\f3b5"; } + content: "\f3b5"; +} .fa-java:before { - content: "\f4e4"; } + content: "\f4e4"; +} .fa-jedi:before { - content: "\f669"; } + content: "\f669"; +} .fa-jedi-order:before { - content: "\f50e"; } + content: "\f50e"; +} .fa-jenkins:before { - content: "\f3b6"; } + content: "\f3b6"; +} .fa-jira:before { - content: "\f7b1"; } + content: "\f7b1"; +} .fa-joget:before { - content: "\f3b7"; } + content: "\f3b7"; +} .fa-joint:before { - content: "\f595"; } + content: "\f595"; +} .fa-joomla:before { - content: "\f1aa"; } + content: "\f1aa"; +} .fa-journal-whills:before { - content: "\f66a"; } + content: "\f66a"; +} .fa-js:before { - content: "\f3b8"; } + content: "\f3b8"; +} .fa-js-square:before { - content: "\f3b9"; } + content: "\f3b9"; +} .fa-jsfiddle:before { - content: "\f1cc"; } + content: "\f1cc"; +} .fa-kaaba:before { - content: "\f66b"; } + content: "\f66b"; +} .fa-kaggle:before { - content: "\f5fa"; } + content: "\f5fa"; +} .fa-key:before { - content: "\f084"; } + content: "\f084"; +} .fa-keybase:before { - content: "\f4f5"; } + content: "\f4f5"; +} .fa-keyboard:before { - content: "\f11c"; } + content: "\f11c"; +} .fa-keycdn:before { - content: "\f3ba"; } + content: "\f3ba"; +} .fa-khanda:before { - content: "\f66d"; } + content: "\f66d"; +} .fa-kickstarter:before { - content: "\f3bb"; } + content: "\f3bb"; +} .fa-kickstarter-k:before { - content: "\f3bc"; } + content: "\f3bc"; +} .fa-kiss:before { - content: "\f596"; } + content: "\f596"; +} .fa-kiss-beam:before { - content: "\f597"; } + content: "\f597"; +} .fa-kiss-wink-heart:before { - content: "\f598"; } + content: "\f598"; +} .fa-kiwi-bird:before { - content: "\f535"; } + content: "\f535"; +} .fa-korvue:before { - content: "\f42f"; } + content: "\f42f"; +} .fa-landmark:before { - content: "\f66f"; } + content: "\f66f"; +} .fa-language:before { - content: "\f1ab"; } + content: "\f1ab"; +} .fa-laptop:before { - content: "\f109"; } + content: "\f109"; +} .fa-laptop-code:before { - content: "\f5fc"; } + content: "\f5fc"; +} .fa-laptop-medical:before { - content: "\f812"; } + content: "\f812"; +} .fa-laravel:before { - content: "\f3bd"; } + content: "\f3bd"; +} .fa-lastfm:before { - content: "\f202"; } + content: "\f202"; +} .fa-lastfm-square:before { - content: "\f203"; } + content: "\f203"; +} .fa-laugh:before { - content: "\f599"; } + content: "\f599"; +} .fa-laugh-beam:before { - content: "\f59a"; } + content: "\f59a"; +} .fa-laugh-squint:before { - content: "\f59b"; } + content: "\f59b"; +} .fa-laugh-wink:before { - content: "\f59c"; } + content: "\f59c"; +} .fa-layer-group:before { - content: "\f5fd"; } + content: "\f5fd"; +} .fa-leaf:before { - content: "\f06c"; } + content: "\f06c"; +} .fa-leanpub:before { - content: "\f212"; } + content: "\f212"; +} .fa-lemon:before { - content: "\f094"; } + content: "\f094"; +} .fa-less:before { - content: "\f41d"; } + content: "\f41d"; +} .fa-less-than:before { - content: "\f536"; } + content: "\f536"; +} .fa-less-than-equal:before { - content: "\f537"; } + content: "\f537"; +} .fa-level-down-alt:before { - content: "\f3be"; } + content: "\f3be"; +} .fa-level-up-alt:before { - content: "\f3bf"; } + content: "\f3bf"; +} .fa-life-ring:before { - content: "\f1cd"; } + content: "\f1cd"; +} .fa-lightbulb:before { - content: "\f0eb"; } + content: "\f0eb"; +} .fa-line:before { - content: "\f3c0"; } + content: "\f3c0"; +} .fa-link:before { - content: "\f0c1"; } + content: "\f0c1"; +} .fa-linkedin:before { - content: "\f08c"; } + content: "\f08c"; +} .fa-linkedin-in:before { - content: "\f0e1"; } + content: "\f0e1"; +} .fa-linode:before { - content: "\f2b8"; } + content: "\f2b8"; +} .fa-linux:before { - content: "\f17c"; } + content: "\f17c"; +} .fa-lira-sign:before { - content: "\f195"; } + content: "\f195"; +} .fa-list:before { - content: "\f03a"; } + content: "\f03a"; +} .fa-list-alt:before { - content: "\f022"; } + content: "\f022"; +} .fa-list-ol:before { - content: "\f0cb"; } + content: "\f0cb"; +} .fa-list-ul:before { - content: "\f0ca"; } + content: "\f0ca"; +} .fa-location-arrow:before { - content: "\f124"; } + content: "\f124"; +} .fa-lock:before { - content: "\f023"; } + content: "\f023"; +} .fa-lock-open:before { - content: "\f3c1"; } + content: "\f3c1"; +} .fa-long-arrow-alt-down:before { - content: "\f309"; } + content: "\f309"; +} .fa-long-arrow-alt-left:before { - content: "\f30a"; } + content: "\f30a"; +} .fa-long-arrow-alt-right:before { - content: "\f30b"; } + content: "\f30b"; +} .fa-long-arrow-alt-up:before { - content: "\f30c"; } + content: "\f30c"; +} .fa-low-vision:before { - content: "\f2a8"; } + content: "\f2a8"; +} .fa-luggage-cart:before { - content: "\f59d"; } + content: "\f59d"; +} .fa-lyft:before { - content: "\f3c3"; } + content: "\f3c3"; +} .fa-magento:before { - content: "\f3c4"; } + content: "\f3c4"; +} .fa-magic:before { - content: "\f0d0"; } + content: "\f0d0"; +} .fa-magnet:before { - content: "\f076"; } + content: "\f076"; +} .fa-mail-bulk:before { - content: "\f674"; } + content: "\f674"; +} .fa-mailchimp:before { - content: "\f59e"; } + content: "\f59e"; +} .fa-male:before { - content: "\f183"; } + content: "\f183"; +} .fa-mandalorian:before { - content: "\f50f"; } + content: "\f50f"; +} .fa-map:before { - content: "\f279"; } + content: "\f279"; +} .fa-map-marked:before { - content: "\f59f"; } + content: "\f59f"; +} .fa-map-marked-alt:before { - content: "\f5a0"; } + content: "\f5a0"; +} .fa-map-marker:before { - content: "\f041"; } + content: "\f041"; +} .fa-map-marker-alt:before { - content: "\f3c5"; } + content: "\f3c5"; +} .fa-map-pin:before { - content: "\f276"; } + content: "\f276"; +} .fa-map-signs:before { - content: "\f277"; } + content: "\f277"; +} .fa-markdown:before { - content: "\f60f"; } + content: "\f60f"; +} .fa-marker:before { - content: "\f5a1"; } + content: "\f5a1"; +} .fa-mars:before { - content: "\f222"; } + content: "\f222"; +} .fa-mars-double:before { - content: "\f227"; } + content: "\f227"; +} .fa-mars-stroke:before { - content: "\f229"; } + content: "\f229"; +} .fa-mars-stroke-h:before { - content: "\f22b"; } + content: "\f22b"; +} .fa-mars-stroke-v:before { - content: "\f22a"; } + content: "\f22a"; +} .fa-mask:before { - content: "\f6fa"; } + content: "\f6fa"; +} .fa-mastodon:before { - content: "\f4f6"; } + content: "\f4f6"; +} .fa-maxcdn:before { - content: "\f136"; } + content: "\f136"; +} .fa-mdb:before { - content: "\f8ca"; } + content: "\f8ca"; +} .fa-medal:before { - content: "\f5a2"; } + content: "\f5a2"; +} .fa-medapps:before { - content: "\f3c6"; } + content: "\f3c6"; +} .fa-medium:before { - content: "\f23a"; } + content: "\f23a"; +} .fa-medium-m:before { - content: "\f3c7"; } + content: "\f3c7"; +} .fa-medkit:before { - content: "\f0fa"; } + content: "\f0fa"; +} .fa-medrt:before { - content: "\f3c8"; } + content: "\f3c8"; +} .fa-meetup:before { - content: "\f2e0"; } + content: "\f2e0"; +} .fa-megaport:before { - content: "\f5a3"; } + content: "\f5a3"; +} .fa-meh:before { - content: "\f11a"; } + content: "\f11a"; +} .fa-meh-blank:before { - content: "\f5a4"; } + content: "\f5a4"; +} .fa-meh-rolling-eyes:before { - content: "\f5a5"; } + content: "\f5a5"; +} .fa-memory:before { - content: "\f538"; } + content: "\f538"; +} .fa-mendeley:before { - content: "\f7b3"; } + content: "\f7b3"; +} .fa-menorah:before { - content: "\f676"; } + content: "\f676"; +} .fa-mercury:before { - content: "\f223"; } + content: "\f223"; +} .fa-meteor:before { - content: "\f753"; } + content: "\f753"; +} .fa-microblog:before { - content: "\f91a"; } + content: "\f91a"; +} .fa-microchip:before { - content: "\f2db"; } + content: "\f2db"; +} .fa-microphone:before { - content: "\f130"; } + content: "\f130"; +} .fa-microphone-alt:before { - content: "\f3c9"; } + content: "\f3c9"; +} .fa-microphone-alt-slash:before { - content: "\f539"; } + content: "\f539"; +} .fa-microphone-slash:before { - content: "\f131"; } + content: "\f131"; +} .fa-microscope:before { - content: "\f610"; } + content: "\f610"; +} .fa-microsoft:before { - content: "\f3ca"; } + content: "\f3ca"; +} .fa-minus:before { - content: "\f068"; } + content: "\f068"; +} .fa-minus-circle:before { - content: "\f056"; } + content: "\f056"; +} .fa-minus-square:before { - content: "\f146"; } + content: "\f146"; +} .fa-mitten:before { - content: "\f7b5"; } + content: "\f7b5"; +} .fa-mix:before { - content: "\f3cb"; } + content: "\f3cb"; +} .fa-mixcloud:before { - content: "\f289"; } + content: "\f289"; +} .fa-mizuni:before { - content: "\f3cc"; } + content: "\f3cc"; +} .fa-mobile:before { - content: "\f10b"; } + content: "\f10b"; +} .fa-mobile-alt:before { - content: "\f3cd"; } + content: "\f3cd"; +} .fa-modx:before { - content: "\f285"; } + content: "\f285"; +} .fa-monero:before { - content: "\f3d0"; } + content: "\f3d0"; +} .fa-money-bill:before { - content: "\f0d6"; } + content: "\f0d6"; +} .fa-money-bill-alt:before { - content: "\f3d1"; } + content: "\f3d1"; +} .fa-money-bill-wave:before { - content: "\f53a"; } + content: "\f53a"; +} .fa-money-bill-wave-alt:before { - content: "\f53b"; } + content: "\f53b"; +} .fa-money-check:before { - content: "\f53c"; } + content: "\f53c"; +} .fa-money-check-alt:before { - content: "\f53d"; } + content: "\f53d"; +} .fa-monument:before { - content: "\f5a6"; } + content: "\f5a6"; +} .fa-moon:before { - content: "\f186"; } + content: "\f186"; +} .fa-mortar-pestle:before { - content: "\f5a7"; } + content: "\f5a7"; +} .fa-mosque:before { - content: "\f678"; } + content: "\f678"; +} .fa-motorcycle:before { - content: "\f21c"; } + content: "\f21c"; +} .fa-mountain:before { - content: "\f6fc"; } + content: "\f6fc"; +} .fa-mouse:before { - content: "\f8cc"; } + content: "\f8cc"; +} .fa-mouse-pointer:before { - content: "\f245"; } + content: "\f245"; +} .fa-mug-hot:before { - content: "\f7b6"; } + content: "\f7b6"; +} .fa-music:before { - content: "\f001"; } + content: "\f001"; +} .fa-napster:before { - content: "\f3d2"; } + content: "\f3d2"; +} .fa-neos:before { - content: "\f612"; } + content: "\f612"; +} .fa-network-wired:before { - content: "\f6ff"; } + content: "\f6ff"; +} .fa-neuter:before { - content: "\f22c"; } + content: "\f22c"; +} .fa-newspaper:before { - content: "\f1ea"; } + content: "\f1ea"; +} .fa-nimblr:before { - content: "\f5a8"; } + content: "\f5a8"; +} .fa-node:before { - content: "\f419"; } + content: "\f419"; +} .fa-node-js:before { - content: "\f3d3"; } + content: "\f3d3"; +} .fa-not-equal:before { - content: "\f53e"; } + content: "\f53e"; +} .fa-notes-medical:before { - content: "\f481"; } + content: "\f481"; +} .fa-npm:before { - content: "\f3d4"; } + content: "\f3d4"; +} .fa-ns8:before { - content: "\f3d5"; } + content: "\f3d5"; +} .fa-nutritionix:before { - content: "\f3d6"; } + content: "\f3d6"; +} .fa-object-group:before { - content: "\f247"; } + content: "\f247"; +} .fa-object-ungroup:before { - content: "\f248"; } + content: "\f248"; +} .fa-odnoklassniki:before { - content: "\f263"; } + content: "\f263"; +} .fa-odnoklassniki-square:before { - content: "\f264"; } + content: "\f264"; +} .fa-oil-can:before { - content: "\f613"; } + content: "\f613"; +} .fa-old-republic:before { - content: "\f510"; } + content: "\f510"; +} .fa-om:before { - content: "\f679"; } + content: "\f679"; +} .fa-opencart:before { - content: "\f23d"; } + content: "\f23d"; +} .fa-openid:before { - content: "\f19b"; } + content: "\f19b"; +} .fa-opera:before { - content: "\f26a"; } + content: "\f26a"; +} .fa-optin-monster:before { - content: "\f23c"; } + content: "\f23c"; +} .fa-orcid:before { - content: "\f8d2"; } + content: "\f8d2"; +} .fa-osi:before { - content: "\f41a"; } + content: "\f41a"; +} .fa-otter:before { - content: "\f700"; } + content: "\f700"; +} .fa-outdent:before { - content: "\f03b"; } + content: "\f03b"; +} .fa-page4:before { - content: "\f3d7"; } + content: "\f3d7"; +} .fa-pagelines:before { - content: "\f18c"; } + content: "\f18c"; +} .fa-pager:before { - content: "\f815"; } + content: "\f815"; +} .fa-paint-brush:before { - content: "\f1fc"; } + content: "\f1fc"; +} .fa-paint-roller:before { - content: "\f5aa"; } + content: "\f5aa"; +} .fa-palette:before { - content: "\f53f"; } + content: "\f53f"; +} .fa-palfed:before { - content: "\f3d8"; } + content: "\f3d8"; +} .fa-pallet:before { - content: "\f482"; } + content: "\f482"; +} .fa-paper-plane:before { - content: "\f1d8"; } + content: "\f1d8"; +} .fa-paperclip:before { - content: "\f0c6"; } + content: "\f0c6"; +} .fa-parachute-box:before { - content: "\f4cd"; } + content: "\f4cd"; +} .fa-paragraph:before { - content: "\f1dd"; } + content: "\f1dd"; +} .fa-parking:before { - content: "\f540"; } + content: "\f540"; +} .fa-passport:before { - content: "\f5ab"; } + content: "\f5ab"; +} .fa-pastafarianism:before { - content: "\f67b"; } + content: "\f67b"; +} .fa-paste:before { - content: "\f0ea"; } + content: "\f0ea"; +} .fa-patreon:before { - content: "\f3d9"; } + content: "\f3d9"; +} .fa-pause:before { - content: "\f04c"; } + content: "\f04c"; +} .fa-pause-circle:before { - content: "\f28b"; } + content: "\f28b"; +} .fa-paw:before { - content: "\f1b0"; } + content: "\f1b0"; +} .fa-paypal:before { - content: "\f1ed"; } + content: "\f1ed"; +} .fa-peace:before { - content: "\f67c"; } + content: "\f67c"; +} .fa-pen:before { - content: "\f304"; } + content: "\f304"; +} .fa-pen-alt:before { - content: "\f305"; } + content: "\f305"; +} .fa-pen-fancy:before { - content: "\f5ac"; } + content: "\f5ac"; +} .fa-pen-nib:before { - content: "\f5ad"; } + content: "\f5ad"; +} .fa-pen-square:before { - content: "\f14b"; } + content: "\f14b"; +} .fa-pencil-alt:before { - content: "\f303"; } + content: "\f303"; +} .fa-pencil-ruler:before { - content: "\f5ae"; } + content: "\f5ae"; +} .fa-penny-arcade:before { - content: "\f704"; } + content: "\f704"; +} .fa-people-carry:before { - content: "\f4ce"; } + content: "\f4ce"; +} .fa-pepper-hot:before { - content: "\f816"; } + content: "\f816"; +} .fa-percent:before { - content: "\f295"; } + content: "\f295"; +} .fa-percentage:before { - content: "\f541"; } + content: "\f541"; +} .fa-periscope:before { - content: "\f3da"; } + content: "\f3da"; +} .fa-person-booth:before { - content: "\f756"; } + content: "\f756"; +} .fa-phabricator:before { - content: "\f3db"; } + content: "\f3db"; +} .fa-phoenix-framework:before { - content: "\f3dc"; } + content: "\f3dc"; +} .fa-phoenix-squadron:before { - content: "\f511"; } + content: "\f511"; +} .fa-phone:before { - content: "\f095"; } + content: "\f095"; +} .fa-phone-alt:before { - content: "\f879"; } + content: "\f879"; +} .fa-phone-slash:before { - content: "\f3dd"; } + content: "\f3dd"; +} .fa-phone-square:before { - content: "\f098"; } + content: "\f098"; +} .fa-phone-square-alt:before { - content: "\f87b"; } + content: "\f87b"; +} .fa-phone-volume:before { - content: "\f2a0"; } + content: "\f2a0"; +} .fa-photo-video:before { - content: "\f87c"; } + content: "\f87c"; +} .fa-php:before { - content: "\f457"; } + content: "\f457"; +} .fa-pied-piper:before { - content: "\f2ae"; } + content: "\f2ae"; +} .fa-pied-piper-alt:before { - content: "\f1a8"; } + content: "\f1a8"; +} .fa-pied-piper-hat:before { - content: "\f4e5"; } + content: "\f4e5"; +} .fa-pied-piper-pp:before { - content: "\f1a7"; } + content: "\f1a7"; +} .fa-pied-piper-square:before { - content: "\f91e"; } + content: "\f91e"; +} .fa-piggy-bank:before { - content: "\f4d3"; } + content: "\f4d3"; +} .fa-pills:before { - content: "\f484"; } + content: "\f484"; +} .fa-pinterest:before { - content: "\f0d2"; } + content: "\f0d2"; +} .fa-pinterest-p:before { - content: "\f231"; } + content: "\f231"; +} .fa-pinterest-square:before { - content: "\f0d3"; } + content: "\f0d3"; +} .fa-pizza-slice:before { - content: "\f818"; } + content: "\f818"; +} .fa-place-of-worship:before { - content: "\f67f"; } + content: "\f67f"; +} .fa-plane:before { - content: "\f072"; } + content: "\f072"; +} .fa-plane-arrival:before { - content: "\f5af"; } + content: "\f5af"; +} .fa-plane-departure:before { - content: "\f5b0"; } + content: "\f5b0"; +} .fa-play:before { - content: "\f04b"; } + content: "\f04b"; +} .fa-play-circle:before { - content: "\f144"; } + content: "\f144"; +} .fa-playstation:before { - content: "\f3df"; } + content: "\f3df"; +} .fa-plug:before { - content: "\f1e6"; } + content: "\f1e6"; +} .fa-plus:before { - content: "\f067"; } + content: "\f067"; +} .fa-plus-circle:before { - content: "\f055"; } + content: "\f055"; +} .fa-plus-square:before { - content: "\f0fe"; } + content: "\f0fe"; +} .fa-podcast:before { - content: "\f2ce"; } + content: "\f2ce"; +} .fa-poll:before { - content: "\f681"; } + content: "\f681"; +} .fa-poll-h:before { - content: "\f682"; } + content: "\f682"; +} .fa-poo:before { - content: "\f2fe"; } + content: "\f2fe"; +} .fa-poo-storm:before { - content: "\f75a"; } + content: "\f75a"; +} .fa-poop:before { - content: "\f619"; } + content: "\f619"; +} .fa-portrait:before { - content: "\f3e0"; } + content: "\f3e0"; +} .fa-pound-sign:before { - content: "\f154"; } + content: "\f154"; +} .fa-power-off:before { - content: "\f011"; } + content: "\f011"; +} .fa-pray:before { - content: "\f683"; } + content: "\f683"; +} .fa-praying-hands:before { - content: "\f684"; } + content: "\f684"; +} .fa-prescription:before { - content: "\f5b1"; } + content: "\f5b1"; +} .fa-prescription-bottle:before { - content: "\f485"; } + content: "\f485"; +} .fa-prescription-bottle-alt:before { - content: "\f486"; } + content: "\f486"; +} .fa-print:before { - content: "\f02f"; } + content: "\f02f"; +} .fa-procedures:before { - content: "\f487"; } + content: "\f487"; +} .fa-product-hunt:before { - content: "\f288"; } + content: "\f288"; +} .fa-project-diagram:before { - content: "\f542"; } + content: "\f542"; +} .fa-pushed:before { - content: "\f3e1"; } + content: "\f3e1"; +} .fa-puzzle-piece:before { - content: "\f12e"; } + content: "\f12e"; +} .fa-python:before { - content: "\f3e2"; } + content: "\f3e2"; +} .fa-qq:before { - content: "\f1d6"; } + content: "\f1d6"; +} .fa-qrcode:before { - content: "\f029"; } + content: "\f029"; +} .fa-question:before { - content: "\f128"; } + content: "\f128"; +} .fa-question-circle:before { - content: "\f059"; } + content: "\f059"; +} .fa-quidditch:before { - content: "\f458"; } + content: "\f458"; +} .fa-quinscape:before { - content: "\f459"; } + content: "\f459"; +} .fa-quora:before { - content: "\f2c4"; } + content: "\f2c4"; +} .fa-quote-left:before { - content: "\f10d"; } + content: "\f10d"; +} .fa-quote-right:before { - content: "\f10e"; } + content: "\f10e"; +} .fa-quran:before { - content: "\f687"; } + content: "\f687"; +} .fa-r-project:before { - content: "\f4f7"; } + content: "\f4f7"; +} .fa-radiation:before { - content: "\f7b9"; } + content: "\f7b9"; +} .fa-radiation-alt:before { - content: "\f7ba"; } + content: "\f7ba"; +} .fa-rainbow:before { - content: "\f75b"; } + content: "\f75b"; +} .fa-random:before { - content: "\f074"; } + content: "\f074"; +} .fa-raspberry-pi:before { - content: "\f7bb"; } + content: "\f7bb"; +} .fa-ravelry:before { - content: "\f2d9"; } + content: "\f2d9"; +} .fa-react:before { - content: "\f41b"; } + content: "\f41b"; +} .fa-reacteurope:before { - content: "\f75d"; } + content: "\f75d"; +} .fa-readme:before { - content: "\f4d5"; } + content: "\f4d5"; +} .fa-rebel:before { - content: "\f1d0"; } + content: "\f1d0"; +} .fa-receipt:before { - content: "\f543"; } + content: "\f543"; +} .fa-record-vinyl:before { - content: "\f8d9"; } + content: "\f8d9"; +} .fa-recycle:before { - content: "\f1b8"; } + content: "\f1b8"; +} .fa-red-river:before { - content: "\f3e3"; } + content: "\f3e3"; +} .fa-reddit:before { - content: "\f1a1"; } + content: "\f1a1"; +} .fa-reddit-alien:before { - content: "\f281"; } + content: "\f281"; +} .fa-reddit-square:before { - content: "\f1a2"; } + content: "\f1a2"; +} .fa-redhat:before { - content: "\f7bc"; } + content: "\f7bc"; +} .fa-redo:before { - content: "\f01e"; } + content: "\f01e"; +} .fa-redo-alt:before { - content: "\f2f9"; } + content: "\f2f9"; +} .fa-registered:before { - content: "\f25d"; } + content: "\f25d"; +} .fa-remove-format:before { - content: "\f87d"; } + content: "\f87d"; +} .fa-renren:before { - content: "\f18b"; } + content: "\f18b"; +} .fa-reply:before { - content: "\f3e5"; } + content: "\f3e5"; +} .fa-reply-all:before { - content: "\f122"; } + content: "\f122"; +} .fa-replyd:before { - content: "\f3e6"; } + content: "\f3e6"; +} .fa-republican:before { - content: "\f75e"; } + content: "\f75e"; +} .fa-researchgate:before { - content: "\f4f8"; } + content: "\f4f8"; +} .fa-resolving:before { - content: "\f3e7"; } + content: "\f3e7"; +} .fa-restroom:before { - content: "\f7bd"; } + content: "\f7bd"; +} .fa-retweet:before { - content: "\f079"; } + content: "\f079"; +} .fa-rev:before { - content: "\f5b2"; } + content: "\f5b2"; +} .fa-ribbon:before { - content: "\f4d6"; } + content: "\f4d6"; +} .fa-ring:before { - content: "\f70b"; } + content: "\f70b"; +} .fa-road:before { - content: "\f018"; } + content: "\f018"; +} .fa-robot:before { - content: "\f544"; } + content: "\f544"; +} .fa-rocket:before { - content: "\f135"; } + content: "\f135"; +} .fa-rocketchat:before { - content: "\f3e8"; } + content: "\f3e8"; +} .fa-rockrms:before { - content: "\f3e9"; } + content: "\f3e9"; +} .fa-route:before { - content: "\f4d7"; } + content: "\f4d7"; +} .fa-rss:before { - content: "\f09e"; } + content: "\f09e"; +} .fa-rss-square:before { - content: "\f143"; } + content: "\f143"; +} .fa-ruble-sign:before { - content: "\f158"; } + content: "\f158"; +} .fa-ruler:before { - content: "\f545"; } + content: "\f545"; +} .fa-ruler-combined:before { - content: "\f546"; } + content: "\f546"; +} .fa-ruler-horizontal:before { - content: "\f547"; } + content: "\f547"; +} .fa-ruler-vertical:before { - content: "\f548"; } + content: "\f548"; +} .fa-running:before { - content: "\f70c"; } + content: "\f70c"; +} .fa-rupee-sign:before { - content: "\f156"; } + content: "\f156"; +} .fa-sad-cry:before { - content: "\f5b3"; } + content: "\f5b3"; +} .fa-sad-tear:before { - content: "\f5b4"; } + content: "\f5b4"; +} .fa-safari:before { - content: "\f267"; } + content: "\f267"; +} .fa-salesforce:before { - content: "\f83b"; } + content: "\f83b"; +} .fa-sass:before { - content: "\f41e"; } + content: "\f41e"; +} .fa-satellite:before { - content: "\f7bf"; } + content: "\f7bf"; +} .fa-satellite-dish:before { - content: "\f7c0"; } + content: "\f7c0"; +} .fa-save:before { - content: "\f0c7"; } + content: "\f0c7"; +} .fa-schlix:before { - content: "\f3ea"; } + content: "\f3ea"; +} .fa-school:before { - content: "\f549"; } + content: "\f549"; +} .fa-screwdriver:before { - content: "\f54a"; } + content: "\f54a"; +} .fa-scribd:before { - content: "\f28a"; } + content: "\f28a"; +} .fa-scroll:before { - content: "\f70e"; } + content: "\f70e"; +} .fa-sd-card:before { - content: "\f7c2"; } + content: "\f7c2"; +} .fa-search:before { - content: "\f002"; } + content: "\f002"; +} .fa-search-dollar:before { - content: "\f688"; } + content: "\f688"; +} .fa-search-location:before { - content: "\f689"; } + content: "\f689"; +} .fa-search-minus:before { - content: "\f010"; } + content: "\f010"; +} .fa-search-plus:before { - content: "\f00e"; } + content: "\f00e"; +} .fa-searchengin:before { - content: "\f3eb"; } + content: "\f3eb"; +} .fa-seedling:before { - content: "\f4d8"; } + content: "\f4d8"; +} .fa-sellcast:before { - content: "\f2da"; } + content: "\f2da"; +} .fa-sellsy:before { - content: "\f213"; } + content: "\f213"; +} .fa-server:before { - content: "\f233"; } + content: "\f233"; +} .fa-servicestack:before { - content: "\f3ec"; } + content: "\f3ec"; +} .fa-shapes:before { - content: "\f61f"; } + content: "\f61f"; +} .fa-share:before { - content: "\f064"; } + content: "\f064"; +} .fa-share-alt:before { - content: "\f1e0"; } + content: "\f1e0"; +} .fa-share-alt-square:before { - content: "\f1e1"; } + content: "\f1e1"; +} .fa-share-square:before { - content: "\f14d"; } + content: "\f14d"; +} .fa-shekel-sign:before { - content: "\f20b"; } + content: "\f20b"; +} .fa-shield-alt:before { - content: "\f3ed"; } + content: "\f3ed"; +} .fa-ship:before { - content: "\f21a"; } + content: "\f21a"; +} .fa-shipping-fast:before { - content: "\f48b"; } + content: "\f48b"; +} .fa-shirtsinbulk:before { - content: "\f214"; } + content: "\f214"; +} .fa-shoe-prints:before { - content: "\f54b"; } + content: "\f54b"; +} .fa-shopping-bag:before { - content: "\f290"; } + content: "\f290"; +} .fa-shopping-basket:before { - content: "\f291"; } + content: "\f291"; +} .fa-shopping-cart:before { - content: "\f07a"; } + content: "\f07a"; +} .fa-shopware:before { - content: "\f5b5"; } + content: "\f5b5"; +} .fa-shower:before { - content: "\f2cc"; } + content: "\f2cc"; +} .fa-shuttle-van:before { - content: "\f5b6"; } + content: "\f5b6"; +} .fa-sign:before { - content: "\f4d9"; } + content: "\f4d9"; +} .fa-sign-in-alt:before { - content: "\f2f6"; } + content: "\f2f6"; +} .fa-sign-language:before { - content: "\f2a7"; } + content: "\f2a7"; +} .fa-sign-out-alt:before { - content: "\f2f5"; } + content: "\f2f5"; +} .fa-signal:before { - content: "\f012"; } + content: "\f012"; +} .fa-signature:before { - content: "\f5b7"; } + content: "\f5b7"; +} .fa-sim-card:before { - content: "\f7c4"; } + content: "\f7c4"; +} .fa-simplybuilt:before { - content: "\f215"; } + content: "\f215"; +} .fa-sistrix:before { - content: "\f3ee"; } + content: "\f3ee"; +} .fa-sitemap:before { - content: "\f0e8"; } + content: "\f0e8"; +} .fa-sith:before { - content: "\f512"; } + content: "\f512"; +} .fa-skating:before { - content: "\f7c5"; } + content: "\f7c5"; +} .fa-sketch:before { - content: "\f7c6"; } + content: "\f7c6"; +} .fa-skiing:before { - content: "\f7c9"; } + content: "\f7c9"; +} .fa-skiing-nordic:before { - content: "\f7ca"; } + content: "\f7ca"; +} .fa-skull:before { - content: "\f54c"; } + content: "\f54c"; +} .fa-skull-crossbones:before { - content: "\f714"; } + content: "\f714"; +} .fa-skyatlas:before { - content: "\f216"; } + content: "\f216"; +} .fa-skype:before { - content: "\f17e"; } + content: "\f17e"; +} .fa-slack:before { - content: "\f198"; } + content: "\f198"; +} .fa-slack-hash:before { - content: "\f3ef"; } + content: "\f3ef"; +} .fa-slash:before { - content: "\f715"; } + content: "\f715"; +} .fa-sleigh:before { - content: "\f7cc"; } + content: "\f7cc"; +} .fa-sliders-h:before { - content: "\f1de"; } + content: "\f1de"; +} .fa-slideshare:before { - content: "\f1e7"; } + content: "\f1e7"; +} .fa-smile:before { - content: "\f118"; } + content: "\f118"; +} .fa-smile-beam:before { - content: "\f5b8"; } + content: "\f5b8"; +} .fa-smile-wink:before { - content: "\f4da"; } + content: "\f4da"; +} .fa-smog:before { - content: "\f75f"; } + content: "\f75f"; +} .fa-smoking:before { - content: "\f48d"; } + content: "\f48d"; +} .fa-smoking-ban:before { - content: "\f54d"; } + content: "\f54d"; +} .fa-sms:before { - content: "\f7cd"; } + content: "\f7cd"; +} .fa-snapchat:before { - content: "\f2ab"; } + content: "\f2ab"; +} .fa-snapchat-ghost:before { - content: "\f2ac"; } + content: "\f2ac"; +} .fa-snapchat-square:before { - content: "\f2ad"; } + content: "\f2ad"; +} .fa-snowboarding:before { - content: "\f7ce"; } + content: "\f7ce"; +} .fa-snowflake:before { - content: "\f2dc"; } + content: "\f2dc"; +} .fa-snowman:before { - content: "\f7d0"; } + content: "\f7d0"; +} .fa-snowplow:before { - content: "\f7d2"; } + content: "\f7d2"; +} .fa-socks:before { - content: "\f696"; } + content: "\f696"; +} .fa-solar-panel:before { - content: "\f5ba"; } + content: "\f5ba"; +} .fa-sort:before { - content: "\f0dc"; } + content: "\f0dc"; +} .fa-sort-alpha-down:before { - content: "\f15d"; } + content: "\f15d"; +} .fa-sort-alpha-down-alt:before { - content: "\f881"; } + content: "\f881"; +} .fa-sort-alpha-up:before { - content: "\f15e"; } + content: "\f15e"; +} .fa-sort-alpha-up-alt:before { - content: "\f882"; } + content: "\f882"; +} .fa-sort-amount-down:before { - content: "\f160"; } + content: "\f160"; +} .fa-sort-amount-down-alt:before { - content: "\f884"; } + content: "\f884"; +} .fa-sort-amount-up:before { - content: "\f161"; } + content: "\f161"; +} .fa-sort-amount-up-alt:before { - content: "\f885"; } + content: "\f885"; +} .fa-sort-down:before { - content: "\f0dd"; } + content: "\f0dd"; +} .fa-sort-numeric-down:before { - content: "\f162"; } + content: "\f162"; +} .fa-sort-numeric-down-alt:before { - content: "\f886"; } + content: "\f886"; +} .fa-sort-numeric-up:before { - content: "\f163"; } + content: "\f163"; +} .fa-sort-numeric-up-alt:before { - content: "\f887"; } + content: "\f887"; +} .fa-sort-up:before { - content: "\f0de"; } + content: "\f0de"; +} .fa-soundcloud:before { - content: "\f1be"; } + content: "\f1be"; +} .fa-sourcetree:before { - content: "\f7d3"; } + content: "\f7d3"; +} .fa-spa:before { - content: "\f5bb"; } + content: "\f5bb"; +} .fa-space-shuttle:before { - content: "\f197"; } + content: "\f197"; +} .fa-speakap:before { - content: "\f3f3"; } + content: "\f3f3"; +} .fa-speaker-deck:before { - content: "\f83c"; } + content: "\f83c"; +} .fa-spell-check:before { - content: "\f891"; } + content: "\f891"; +} .fa-spider:before { - content: "\f717"; } + content: "\f717"; +} .fa-spinner:before { - content: "\f110"; } + content: "\f110"; +} .fa-splotch:before { - content: "\f5bc"; } + content: "\f5bc"; +} .fa-spotify:before { - content: "\f1bc"; } + content: "\f1bc"; +} .fa-spray-can:before { - content: "\f5bd"; } + content: "\f5bd"; +} .fa-square:before { - content: "\f0c8"; } + content: "\f0c8"; +} .fa-square-full:before { - content: "\f45c"; } + content: "\f45c"; +} .fa-square-root-alt:before { - content: "\f698"; } + content: "\f698"; +} .fa-squarespace:before { - content: "\f5be"; } + content: "\f5be"; +} .fa-stack-exchange:before { - content: "\f18d"; } + content: "\f18d"; +} .fa-stack-overflow:before { - content: "\f16c"; } + content: "\f16c"; +} .fa-stackpath:before { - content: "\f842"; } + content: "\f842"; +} .fa-stamp:before { - content: "\f5bf"; } + content: "\f5bf"; +} .fa-star:before { - content: "\f005"; } + content: "\f005"; +} .fa-star-and-crescent:before { - content: "\f699"; } + content: "\f699"; +} .fa-star-half:before { - content: "\f089"; } + content: "\f089"; +} .fa-star-half-alt:before { - content: "\f5c0"; } + content: "\f5c0"; +} .fa-star-of-david:before { - content: "\f69a"; } + content: "\f69a"; +} .fa-star-of-life:before { - content: "\f621"; } + content: "\f621"; +} .fa-staylinked:before { - content: "\f3f5"; } + content: "\f3f5"; +} .fa-steam:before { - content: "\f1b6"; } + content: "\f1b6"; +} .fa-steam-square:before { - content: "\f1b7"; } + content: "\f1b7"; +} .fa-steam-symbol:before { - content: "\f3f6"; } + content: "\f3f6"; +} .fa-step-backward:before { - content: "\f048"; } + content: "\f048"; +} .fa-step-forward:before { - content: "\f051"; } + content: "\f051"; +} .fa-stethoscope:before { - content: "\f0f1"; } + content: "\f0f1"; +} .fa-sticker-mule:before { - content: "\f3f7"; } + content: "\f3f7"; +} .fa-sticky-note:before { - content: "\f249"; } + content: "\f249"; +} .fa-stop:before { - content: "\f04d"; } + content: "\f04d"; +} .fa-stop-circle:before { - content: "\f28d"; } + content: "\f28d"; +} .fa-stopwatch:before { - content: "\f2f2"; } + content: "\f2f2"; +} .fa-store:before { - content: "\f54e"; } + content: "\f54e"; +} .fa-store-alt:before { - content: "\f54f"; } + content: "\f54f"; +} .fa-strava:before { - content: "\f428"; } + content: "\f428"; +} .fa-stream:before { - content: "\f550"; } + content: "\f550"; +} .fa-street-view:before { - content: "\f21d"; } + content: "\f21d"; +} .fa-strikethrough:before { - content: "\f0cc"; } + content: "\f0cc"; +} .fa-stripe:before { - content: "\f429"; } + content: "\f429"; +} .fa-stripe-s:before { - content: "\f42a"; } + content: "\f42a"; +} .fa-stroopwafel:before { - content: "\f551"; } + content: "\f551"; +} .fa-studiovinari:before { - content: "\f3f8"; } + content: "\f3f8"; +} .fa-stumbleupon:before { - content: "\f1a4"; } + content: "\f1a4"; +} .fa-stumbleupon-circle:before { - content: "\f1a3"; } + content: "\f1a3"; +} .fa-subscript:before { - content: "\f12c"; } + content: "\f12c"; +} .fa-subway:before { - content: "\f239"; } + content: "\f239"; +} .fa-suitcase:before { - content: "\f0f2"; } + content: "\f0f2"; +} .fa-suitcase-rolling:before { - content: "\f5c1"; } + content: "\f5c1"; +} .fa-sun:before { - content: "\f185"; } + content: "\f185"; +} .fa-superpowers:before { - content: "\f2dd"; } + content: "\f2dd"; +} .fa-superscript:before { - content: "\f12b"; } + content: "\f12b"; +} .fa-supple:before { - content: "\f3f9"; } + content: "\f3f9"; +} .fa-surprise:before { - content: "\f5c2"; } + content: "\f5c2"; +} .fa-suse:before { - content: "\f7d6"; } + content: "\f7d6"; +} .fa-swatchbook:before { - content: "\f5c3"; } + content: "\f5c3"; +} .fa-swift:before { - content: "\f8e1"; } + content: "\f8e1"; +} .fa-swimmer:before { - content: "\f5c4"; } + content: "\f5c4"; +} .fa-swimming-pool:before { - content: "\f5c5"; } + content: "\f5c5"; +} .fa-symfony:before { - content: "\f83d"; } + content: "\f83d"; +} .fa-synagogue:before { - content: "\f69b"; } + content: "\f69b"; +} .fa-sync:before { - content: "\f021"; } + content: "\f021"; +} .fa-sync-alt:before { - content: "\f2f1"; } + content: "\f2f1"; +} .fa-syringe:before { - content: "\f48e"; } + content: "\f48e"; +} .fa-table:before { - content: "\f0ce"; } + content: "\f0ce"; +} .fa-table-tennis:before { - content: "\f45d"; } + content: "\f45d"; +} .fa-tablet:before { - content: "\f10a"; } + content: "\f10a"; +} .fa-tablet-alt:before { - content: "\f3fa"; } + content: "\f3fa"; +} .fa-tablets:before { - content: "\f490"; } + content: "\f490"; +} .fa-tachometer-alt:before { - content: "\f3fd"; } + content: "\f3fd"; +} .fa-tag:before { - content: "\f02b"; } + content: "\f02b"; +} .fa-tags:before { - content: "\f02c"; } + content: "\f02c"; +} .fa-tape:before { - content: "\f4db"; } + content: "\f4db"; +} .fa-tasks:before { - content: "\f0ae"; } + content: "\f0ae"; +} .fa-taxi:before { - content: "\f1ba"; } + content: "\f1ba"; +} .fa-teamspeak:before { - content: "\f4f9"; } + content: "\f4f9"; +} .fa-teeth:before { - content: "\f62e"; } + content: "\f62e"; +} .fa-teeth-open:before { - content: "\f62f"; } + content: "\f62f"; +} .fa-telegram:before { - content: "\f2c6"; } + content: "\f2c6"; +} .fa-telegram-plane:before { - content: "\f3fe"; } + content: "\f3fe"; +} .fa-temperature-high:before { - content: "\f769"; } + content: "\f769"; +} .fa-temperature-low:before { - content: "\f76b"; } + content: "\f76b"; +} .fa-tencent-weibo:before { - content: "\f1d5"; } + content: "\f1d5"; +} .fa-tenge:before { - content: "\f7d7"; } + content: "\f7d7"; +} .fa-terminal:before { - content: "\f120"; } + content: "\f120"; +} .fa-text-height:before { - content: "\f034"; } + content: "\f034"; +} .fa-text-width:before { - content: "\f035"; } + content: "\f035"; +} .fa-th:before { - content: "\f00a"; } + content: "\f00a"; +} .fa-th-large:before { - content: "\f009"; } + content: "\f009"; +} .fa-th-list:before { - content: "\f00b"; } + content: "\f00b"; +} .fa-the-red-yeti:before { - content: "\f69d"; } + content: "\f69d"; +} .fa-theater-masks:before { - content: "\f630"; } + content: "\f630"; +} .fa-themeco:before { - content: "\f5c6"; } + content: "\f5c6"; +} .fa-themeisle:before { - content: "\f2b2"; } + content: "\f2b2"; +} .fa-thermometer:before { - content: "\f491"; } + content: "\f491"; +} .fa-thermometer-empty:before { - content: "\f2cb"; } + content: "\f2cb"; +} .fa-thermometer-full:before { - content: "\f2c7"; } + content: "\f2c7"; +} .fa-thermometer-half:before { - content: "\f2c9"; } + content: "\f2c9"; +} .fa-thermometer-quarter:before { - content: "\f2ca"; } + content: "\f2ca"; +} .fa-thermometer-three-quarters:before { - content: "\f2c8"; } + content: "\f2c8"; +} .fa-think-peaks:before { - content: "\f731"; } + content: "\f731"; +} .fa-thumbs-down:before { - content: "\f165"; } + content: "\f165"; +} .fa-thumbs-up:before { - content: "\f164"; } + content: "\f164"; +} .fa-thumbtack:before { - content: "\f08d"; } + content: "\f08d"; +} .fa-ticket-alt:before { - content: "\f3ff"; } + content: "\f3ff"; +} .fa-times:before { - content: "\f00d"; } + content: "\f00d"; +} .fa-times-circle:before { - content: "\f057"; } + content: "\f057"; +} .fa-tint:before { - content: "\f043"; } + content: "\f043"; +} .fa-tint-slash:before { - content: "\f5c7"; } + content: "\f5c7"; +} .fa-tired:before { - content: "\f5c8"; } + content: "\f5c8"; +} .fa-toggle-off:before { - content: "\f204"; } + content: "\f204"; +} .fa-toggle-on:before { - content: "\f205"; } + content: "\f205"; +} .fa-toilet:before { - content: "\f7d8"; } + content: "\f7d8"; +} .fa-toilet-paper:before { - content: "\f71e"; } + content: "\f71e"; +} .fa-toolbox:before { - content: "\f552"; } + content: "\f552"; +} .fa-tools:before { - content: "\f7d9"; } + content: "\f7d9"; +} .fa-tooth:before { - content: "\f5c9"; } + content: "\f5c9"; +} .fa-torah:before { - content: "\f6a0"; } + content: "\f6a0"; +} .fa-torii-gate:before { - content: "\f6a1"; } + content: "\f6a1"; +} .fa-tractor:before { - content: "\f722"; } + content: "\f722"; +} .fa-trade-federation:before { - content: "\f513"; } + content: "\f513"; +} .fa-trademark:before { - content: "\f25c"; } + content: "\f25c"; +} .fa-traffic-light:before { - content: "\f637"; } + content: "\f637"; +} .fa-trailer:before { - content: "\f941"; } + content: "\f941"; +} .fa-train:before { - content: "\f238"; } + content: "\f238"; +} .fa-tram:before { - content: "\f7da"; } + content: "\f7da"; +} .fa-transgender:before { - content: "\f224"; } + content: "\f224"; +} .fa-transgender-alt:before { - content: "\f225"; } + content: "\f225"; +} .fa-trash:before { - content: "\f1f8"; } + content: "\f1f8"; +} .fa-trash-alt:before { - content: "\f2ed"; } + content: "\f2ed"; +} .fa-trash-restore:before { - content: "\f829"; } + content: "\f829"; +} .fa-trash-restore-alt:before { - content: "\f82a"; } + content: "\f82a"; +} .fa-tree:before { - content: "\f1bb"; } + content: "\f1bb"; +} .fa-trello:before { - content: "\f181"; } + content: "\f181"; +} .fa-tripadvisor:before { - content: "\f262"; } + content: "\f262"; +} .fa-trophy:before { - content: "\f091"; } + content: "\f091"; +} .fa-truck:before { - content: "\f0d1"; } + content: "\f0d1"; +} .fa-truck-loading:before { - content: "\f4de"; } + content: "\f4de"; +} .fa-truck-monster:before { - content: "\f63b"; } + content: "\f63b"; +} .fa-truck-moving:before { - content: "\f4df"; } + content: "\f4df"; +} .fa-truck-pickup:before { - content: "\f63c"; } + content: "\f63c"; +} .fa-tshirt:before { - content: "\f553"; } + content: "\f553"; +} .fa-tty:before { - content: "\f1e4"; } + content: "\f1e4"; +} .fa-tumblr:before { - content: "\f173"; } + content: "\f173"; +} .fa-tumblr-square:before { - content: "\f174"; } + content: "\f174"; +} .fa-tv:before { - content: "\f26c"; } + content: "\f26c"; +} .fa-twitch:before { - content: "\f1e8"; } + content: "\f1e8"; +} .fa-twitter:before { - content: "\f099"; } + content: "\f099"; +} .fa-twitter-square:before { - content: "\f081"; } + content: "\f081"; +} .fa-typo3:before { - content: "\f42b"; } + content: "\f42b"; +} .fa-uber:before { - content: "\f402"; } + content: "\f402"; +} .fa-ubuntu:before { - content: "\f7df"; } + content: "\f7df"; +} .fa-uikit:before { - content: "\f403"; } + content: "\f403"; +} .fa-umbraco:before { - content: "\f8e8"; } + content: "\f8e8"; +} .fa-umbrella:before { - content: "\f0e9"; } + content: "\f0e9"; +} .fa-umbrella-beach:before { - content: "\f5ca"; } + content: "\f5ca"; +} .fa-underline:before { - content: "\f0cd"; } + content: "\f0cd"; +} .fa-undo:before { - content: "\f0e2"; } + content: "\f0e2"; +} .fa-undo-alt:before { - content: "\f2ea"; } + content: "\f2ea"; +} .fa-uniregistry:before { - content: "\f404"; } + content: "\f404"; +} .fa-unity:before { - content: "\f949"; } + content: "\f949"; +} .fa-universal-access:before { - content: "\f29a"; } + content: "\f29a"; +} .fa-university:before { - content: "\f19c"; } + content: "\f19c"; +} .fa-unlink:before { - content: "\f127"; } + content: "\f127"; +} .fa-unlock:before { - content: "\f09c"; } + content: "\f09c"; +} .fa-unlock-alt:before { - content: "\f13e"; } + content: "\f13e"; +} .fa-untappd:before { - content: "\f405"; } + content: "\f405"; +} .fa-upload:before { - content: "\f093"; } + content: "\f093"; +} .fa-ups:before { - content: "\f7e0"; } + content: "\f7e0"; +} .fa-usb:before { - content: "\f287"; } + content: "\f287"; +} .fa-user:before { - content: "\f007"; } + content: "\f007"; +} .fa-user-alt:before { - content: "\f406"; } + content: "\f406"; +} .fa-user-alt-slash:before { - content: "\f4fa"; } + content: "\f4fa"; +} .fa-user-astronaut:before { - content: "\f4fb"; } + content: "\f4fb"; +} .fa-user-check:before { - content: "\f4fc"; } + content: "\f4fc"; +} .fa-user-circle:before { - content: "\f2bd"; } + content: "\f2bd"; +} .fa-user-clock:before { - content: "\f4fd"; } + content: "\f4fd"; +} .fa-user-cog:before { - content: "\f4fe"; } + content: "\f4fe"; +} .fa-user-edit:before { - content: "\f4ff"; } + content: "\f4ff"; +} .fa-user-friends:before { - content: "\f500"; } + content: "\f500"; +} .fa-user-graduate:before { - content: "\f501"; } + content: "\f501"; +} .fa-user-injured:before { - content: "\f728"; } + content: "\f728"; +} .fa-user-lock:before { - content: "\f502"; } + content: "\f502"; +} .fa-user-md:before { - content: "\f0f0"; } + content: "\f0f0"; +} .fa-user-minus:before { - content: "\f503"; } + content: "\f503"; +} .fa-user-ninja:before { - content: "\f504"; } + content: "\f504"; +} .fa-user-nurse:before { - content: "\f82f"; } + content: "\f82f"; +} .fa-user-plus:before { - content: "\f234"; } + content: "\f234"; +} .fa-user-secret:before { - content: "\f21b"; } + content: "\f21b"; +} .fa-user-shield:before { - content: "\f505"; } + content: "\f505"; +} .fa-user-slash:before { - content: "\f506"; } + content: "\f506"; +} .fa-user-tag:before { - content: "\f507"; } + content: "\f507"; +} .fa-user-tie:before { - content: "\f508"; } + content: "\f508"; +} .fa-user-times:before { - content: "\f235"; } + content: "\f235"; +} .fa-users:before { - content: "\f0c0"; } + content: "\f0c0"; +} .fa-users-cog:before { - content: "\f509"; } + content: "\f509"; +} .fa-usps:before { - content: "\f7e1"; } + content: "\f7e1"; +} .fa-ussunnah:before { - content: "\f407"; } + content: "\f407"; +} .fa-utensil-spoon:before { - content: "\f2e5"; } + content: "\f2e5"; +} .fa-utensils:before { - content: "\f2e7"; } + content: "\f2e7"; +} .fa-vaadin:before { - content: "\f408"; } + content: "\f408"; +} .fa-vector-square:before { - content: "\f5cb"; } + content: "\f5cb"; +} .fa-venus:before { - content: "\f221"; } + content: "\f221"; +} .fa-venus-double:before { - content: "\f226"; } + content: "\f226"; +} .fa-venus-mars:before { - content: "\f228"; } + content: "\f228"; +} .fa-viacoin:before { - content: "\f237"; } + content: "\f237"; +} .fa-viadeo:before { - content: "\f2a9"; } + content: "\f2a9"; +} .fa-viadeo-square:before { - content: "\f2aa"; } + content: "\f2aa"; +} .fa-vial:before { - content: "\f492"; } + content: "\f492"; +} .fa-vials:before { - content: "\f493"; } + content: "\f493"; +} .fa-viber:before { - content: "\f409"; } + content: "\f409"; +} .fa-video:before { - content: "\f03d"; } + content: "\f03d"; +} .fa-video-slash:before { - content: "\f4e2"; } + content: "\f4e2"; +} .fa-vihara:before { - content: "\f6a7"; } + content: "\f6a7"; +} .fa-vimeo:before { - content: "\f40a"; } + content: "\f40a"; +} .fa-vimeo-square:before { - content: "\f194"; } + content: "\f194"; +} .fa-vimeo-v:before { - content: "\f27d"; } + content: "\f27d"; +} .fa-vine:before { - content: "\f1ca"; } + content: "\f1ca"; +} .fa-vk:before { - content: "\f189"; } + content: "\f189"; +} .fa-vnv:before { - content: "\f40b"; } + content: "\f40b"; +} .fa-voicemail:before { - content: "\f897"; } + content: "\f897"; +} .fa-volleyball-ball:before { - content: "\f45f"; } + content: "\f45f"; +} .fa-volume-down:before { - content: "\f027"; } + content: "\f027"; +} .fa-volume-mute:before { - content: "\f6a9"; } + content: "\f6a9"; +} .fa-volume-off:before { - content: "\f026"; } + content: "\f026"; +} .fa-volume-up:before { - content: "\f028"; } + content: "\f028"; +} .fa-vote-yea:before { - content: "\f772"; } + content: "\f772"; +} .fa-vr-cardboard:before { - content: "\f729"; } + content: "\f729"; +} .fa-vuejs:before { - content: "\f41f"; } + content: "\f41f"; +} .fa-walking:before { - content: "\f554"; } + content: "\f554"; +} .fa-wallet:before { - content: "\f555"; } + content: "\f555"; +} .fa-warehouse:before { - content: "\f494"; } + content: "\f494"; +} .fa-water:before { - content: "\f773"; } + content: "\f773"; +} .fa-wave-square:before { - content: "\f83e"; } + content: "\f83e"; +} .fa-waze:before { - content: "\f83f"; } + content: "\f83f"; +} .fa-weebly:before { - content: "\f5cc"; } + content: "\f5cc"; +} .fa-weibo:before { - content: "\f18a"; } + content: "\f18a"; +} .fa-weight:before { - content: "\f496"; } + content: "\f496"; +} .fa-weight-hanging:before { - content: "\f5cd"; } + content: "\f5cd"; +} .fa-weixin:before { - content: "\f1d7"; } + content: "\f1d7"; +} .fa-whatsapp:before { - content: "\f232"; } + content: "\f232"; +} .fa-whatsapp-square:before { - content: "\f40c"; } + content: "\f40c"; +} .fa-wheelchair:before { - content: "\f193"; } + content: "\f193"; +} .fa-whmcs:before { - content: "\f40d"; } + content: "\f40d"; +} .fa-wifi:before { - content: "\f1eb"; } + content: "\f1eb"; +} .fa-wikipedia-w:before { - content: "\f266"; } + content: "\f266"; +} .fa-wind:before { - content: "\f72e"; } + content: "\f72e"; +} .fa-window-close:before { - content: "\f410"; } + content: "\f410"; +} .fa-window-maximize:before { - content: "\f2d0"; } + content: "\f2d0"; +} .fa-window-minimize:before { - content: "\f2d1"; } + content: "\f2d1"; +} .fa-window-restore:before { - content: "\f2d2"; } + content: "\f2d2"; +} .fa-windows:before { - content: "\f17a"; } + content: "\f17a"; +} .fa-wine-bottle:before { - content: "\f72f"; } + content: "\f72f"; +} .fa-wine-glass:before { - content: "\f4e3"; } + content: "\f4e3"; +} .fa-wine-glass-alt:before { - content: "\f5ce"; } + content: "\f5ce"; +} .fa-wix:before { - content: "\f5cf"; } + content: "\f5cf"; +} .fa-wizards-of-the-coast:before { - content: "\f730"; } + content: "\f730"; +} .fa-wolf-pack-battalion:before { - content: "\f514"; } + content: "\f514"; +} .fa-won-sign:before { - content: "\f159"; } + content: "\f159"; +} .fa-wordpress:before { - content: "\f19a"; } + content: "\f19a"; +} .fa-wordpress-simple:before { - content: "\f411"; } + content: "\f411"; +} .fa-wpbeginner:before { - content: "\f297"; } + content: "\f297"; +} .fa-wpexplorer:before { - content: "\f2de"; } + content: "\f2de"; +} .fa-wpforms:before { - content: "\f298"; } + content: "\f298"; +} .fa-wpressr:before { - content: "\f3e4"; } + content: "\f3e4"; +} .fa-wrench:before { - content: "\f0ad"; } + content: "\f0ad"; +} .fa-x-ray:before { - content: "\f497"; } + content: "\f497"; +} .fa-xbox:before { - content: "\f412"; } + content: "\f412"; +} .fa-xing:before { - content: "\f168"; } + content: "\f168"; +} .fa-xing-square:before { - content: "\f169"; } + content: "\f169"; +} .fa-y-combinator:before { - content: "\f23b"; } + content: "\f23b"; +} .fa-yahoo:before { - content: "\f19e"; } + content: "\f19e"; +} .fa-yammer:before { - content: "\f840"; } + content: "\f840"; +} .fa-yandex:before { - content: "\f413"; } + content: "\f413"; +} .fa-yandex-international:before { - content: "\f414"; } + content: "\f414"; +} .fa-yarn:before { - content: "\f7e3"; } + content: "\f7e3"; +} .fa-yelp:before { - content: "\f1e9"; } + content: "\f1e9"; +} .fa-yen-sign:before { - content: "\f157"; } + content: "\f157"; +} .fa-yin-yang:before { - content: "\f6ad"; } + content: "\f6ad"; +} .fa-yoast:before { - content: "\f2b1"; } + content: "\f2b1"; +} .fa-youtube:before { - content: "\f167"; } + content: "\f167"; +} .fa-youtube-square:before { - content: "\f431"; } + content: "\f431"; +} .fa-zhihu:before { - content: "\f63f"; } + content: "\f63f"; +} .sr-only { border: 0; @@ -4406,45 +5853,69 @@ readers do not read off random characters that represent icons */ overflow: hidden; padding: 0; position: absolute; - width: 1px; } + width: 1px; +} -.sr-only-focusable:active, .sr-only-focusable:focus { +.sr-only-focusable:active, +.sr-only-focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; - width: auto; } + width: auto; +} @font-face { - font-family: 'Font Awesome 5 Brands'; + font-family: "Font Awesome 5 Brands"; font-style: normal; font-weight: normal; font-display: auto; src: url("../fonts/fa-brands-400.eot"); - src: url("../fonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../fonts/fa-brands-400.woff2") format("woff2"), url("../fonts/fa-brands-400.woff") format("woff"), url("../fonts/fa-brands-400.ttf") format("truetype"), url("../fonts/fa-brands-400.svg#fontawesome") format("svg"); } + src: + url("../fonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), + url("../fonts/fa-brands-400.woff2") format("woff2"), + url("../fonts/fa-brands-400.woff") format("woff"), + url("../fonts/fa-brands-400.ttf") format("truetype"), + url("../fonts/fa-brands-400.svg#fontawesome") format("svg"); +} .fab { - font-family: 'Font Awesome 5 Brands'; } + font-family: "Font Awesome 5 Brands"; +} @font-face { - font-family: 'Font Awesome 5 Free'; + font-family: "Font Awesome 5 Free"; font-style: normal; font-weight: 400; font-display: auto; src: url("../fonts/fa-regular-400.eot"); - src: url("../fonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../fonts/fa-regular-400.woff2") format("woff2"), url("../fonts/fa-regular-400.woff") format("woff"), url("../fonts/fa-regular-400.ttf") format("truetype"), url("../fonts/fa-regular-400.svg#fontawesome") format("svg"); } + src: + url("../fonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), + url("../fonts/fa-regular-400.woff2") format("woff2"), + url("../fonts/fa-regular-400.woff") format("woff"), + url("../fonts/fa-regular-400.ttf") format("truetype"), + url("../fonts/fa-regular-400.svg#fontawesome") format("svg"); +} .far { - font-family: 'Font Awesome 5 Free'; - font-weight: 400; } + font-family: "Font Awesome 5 Free"; + font-weight: 400; +} @font-face { - font-family: 'Font Awesome 5 Free'; + font-family: "Font Awesome 5 Free"; font-style: normal; font-weight: 900; font-display: auto; src: url("../fonts/fa-solid-900.eot"); - src: url("../fonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../fonts/fa-solid-900.woff2") format("woff2"), url("../fonts/fa-solid-900.woff") format("woff"), url("../fonts/fa-solid-900.ttf") format("truetype"), url("../fonts/fa-solid-900.svg#fontawesome") format("svg"); } + src: + url("../fonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), + url("../fonts/fa-solid-900.woff2") format("woff2"), + url("../fonts/fa-solid-900.woff") format("woff"), + url("../fonts/fa-solid-900.ttf") format("truetype"), + url("../fonts/fa-solid-900.svg#fontawesome") format("svg"); +} .fa, .fas { - font-family: 'Font Awesome 5 Free'; - font-weight: 900; } + font-family: "Font Awesome 5 Free"; + font-weight: 900; +} diff --git a/public/css/style.css b/public/css/style.css index 3060098c..652765ff 100755 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,801 +1,804 @@ /* TYPOGRAPHY */ @font-face { - font-family: "Fredoka"; - font-style: normal; - font-weight: 300 700; - font-stretch: 100%; - font-display: swap; - src: url("/fonts/fredoka-v14.woff2") format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, - U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + font-family: "Fredoka"; + font-style: normal; + font-weight: 300 700; + font-stretch: 100%; + font-display: swap; + src: url("/fonts/fredoka-v14.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, + U+2212, U+2215, U+FEFF, U+FFFD; } body { - color: var(--color--black); + color: var(--color--black); } h1, h2, h3 { - font-family: "Fredoka", sans-serif; - font-weight: 400; - font-optical-sizing: auto; - font-weight: 400; - font-style: normal; + font-family: "Fredoka", sans-serif; + font-weight: 400; + font-optical-sizing: auto; + font-weight: 400; + font-style: normal; } .lead { - font-family: "Fredoka", sans-serif; - font-weight: 300; - font-optical-sizing: auto; - font-style: normal; + font-family: "Fredoka", sans-serif; + font-weight: 300; + font-optical-sizing: auto; + font-style: normal; } :root { - --color-purple: hsl(273, 44%, 58%); - --color-purple-dark: hsl(273, 44%, 38%); - --color-purple-lighter: hsl(273, 44%, 86%); - --color-purple-light: hsl(273, 44%, 96%); - --color-red: hsl(359, 100%, 65%); - --color-red-dark: hsl(359, 100%, 45%); - --color-grey-99: hsl(0, 0%, 99%); - --color-grey-97: hsl(0, 0%, 97%); - --color-grey-95: hsl(0, 0%, 95%); - --color-grey-90: hsl(0, 0%, 90%); - --color-grey-85: hsl(0, 0%, 85%); - --color-grey-80: hsl(0, 0%, 80%); - --color-grey-75: hsl(0, 0%, 75%); - --color-grey-70: hsl(0, 0%, 70%); - --color-grey-60: hsl(0, 0%, 60%); - --color-grey-50: hsl(0, 0%, 50%); - --color-grey-40: hsl(0, 0%, 40%); - --color-grey-30: hsl(0, 0%, 30%); - --color-grey-20: hsl(0, 0%, 20%); - --color--black: hsl(0, 0%, 10%); - --color-orange: #ff8c26; - --transition: 0.15s ease-in; + --color-purple: hsl(273, 44%, 58%); + --color-purple-dark: hsl(273, 44%, 38%); + --color-purple-lighter: hsl(273, 44%, 86%); + --color-purple-light: hsl(273, 44%, 96%); + --color-red: hsl(359, 100%, 65%); + --color-red-dark: hsl(359, 100%, 45%); + --color-grey-99: hsl(0, 0%, 99%); + --color-grey-97: hsl(0, 0%, 97%); + --color-grey-95: hsl(0, 0%, 95%); + --color-grey-90: hsl(0, 0%, 90%); + --color-grey-85: hsl(0, 0%, 85%); + --color-grey-80: hsl(0, 0%, 80%); + --color-grey-75: hsl(0, 0%, 75%); + --color-grey-70: hsl(0, 0%, 70%); + --color-grey-60: hsl(0, 0%, 60%); + --color-grey-50: hsl(0, 0%, 50%); + --color-grey-40: hsl(0, 0%, 40%); + --color-grey-30: hsl(0, 0%, 30%); + --color-grey-20: hsl(0, 0%, 20%); + --color--black: hsl(0, 0%, 10%); + --color-orange: #ff8c26; + --transition: 0.15s ease-in; } .flex-gap { - display: flex; - gap: 1rem; + display: flex; + gap: 1rem; } .flex-gap--small { - display: flex; - gap: 0.5rem; + display: flex; + gap: 0.5rem; } .button { - display: inline-block; - height: 2.25rem; - line-height: 2.25rem; - border-radius: 2.25rem; - font-weight: 500; - letter-spacing: 0.35px; - transition: background var(--transition); - text-decoration: none; - border: none; - font-size: 0.95rem; - padding: 0 1rem; - white-space: nowrap; - text-align: center; + display: inline-block; + height: 2.25rem; + line-height: 2.25rem; + border-radius: 2.25rem; + font-weight: 500; + letter-spacing: 0.35px; + transition: background var(--transition); + text-decoration: none; + border: none; + font-size: 0.95rem; + padding: 0 1rem; + white-space: nowrap; + text-align: center; } .button:hover { - text-decoration: none; - color: inherit; + text-decoration: none; + color: inherit; } .button--primary { - background: var(--color-purple); - color: #fff; + background: var(--color-purple); + color: #fff; } .button--primary:hover { - background: var(--color-purple-dark); - color: #fff; + background: var(--color-purple-dark); + color: #fff; } .button--secondary { - background: var(--color-grey-85); - color: var(--color-black); + background: var(--color-grey-85); + color: var(--color-black); } .button--secondary:hover { - background: var(--color-grey-70); + background: var(--color-grey-70); } .button--outline-primary { - background: transparent; - border: 1px solid var(--color-purple); - color: var(--color-purple); + background: transparent; + border: 1px solid var(--color-purple); + color: var(--color-purple); } .button--outline-primary:hover { - border: 1px solid var(--color-purple-dark); - background: var(--color-purple-light); - color: var(--color-purple-dark); + border: 1px solid var(--color-purple-dark); + background: var(--color-purple-light); + color: var(--color-purple-dark); } .button--outline-secondary { - background: transparent; - border: 1px solid var(--color-grey-75); - color: var(--color-grey-30); + background: transparent; + border: 1px solid var(--color-grey-75); + color: var(--color-grey-30); } .button--outline-secondary:hover { - border: 1px solid var(--color-grey-70); - background: var(--color-purple-light); + border: 1px solid var(--color-grey-70); + background: var(--color-purple-light); } .button--sm { - height: 1.75rem; - line-height: 1.65rem; - border-radius: 1.75rem; - font-size: 0.85rem; - padding: 0 0.75rem; + height: 1.75rem; + line-height: 1.65rem; + border-radius: 1.75rem; + font-size: 0.85rem; + padding: 0 0.75rem; } .button--lg { - height: 2.75rem; - line-height: 2.75rem; - border-radius: 2.75rem; - font-size: 1.05rem; - padding: 0 1.25rem; + height: 2.75rem; + line-height: 2.75rem; + border-radius: 2.75rem; + font-size: 1.05rem; + padding: 0 1.25rem; } .button--danger { - background: var(--color-red); - color: #fff; + background: var(--color-red); + color: #fff; } .button--danger:hover { - background: var(--color-red-dark); - color: #fff; + background: var(--color-red-dark); + color: #fff; } .button--primary:disabled { - background: var(--color-purple-lighter); - color: var(--color-grey-50); + background: var(--color-purple-lighter); + color: var(--color-grey-50); } .button--secondary:disabled { - background: var(--color-grey-97); - color: var(--color-grey-75); + background: var(--color-grey-97); + color: var(--color-grey-75); } .button--danger:disabled { - background: #f9d4d4; - color: #ff4d4f; + background: #f9d4d4; + color: #ff4d4f; } .button--outline-primary:disabled { - background: transparent; - border: 1px solid var(--color-purple-lighter); - color: var(--color-purple-lighter); + background: transparent; + border: 1px solid var(--color-purple-lighter); + color: var(--color-purple-lighter); } .button--outline-secondary:disabled { - background: transparent; - border: 1px solid var(--color-grey-85); - color: var(--color-grey-85); + background: transparent; + border: 1px solid var(--color-grey-85); + color: var(--color-grey-85); } .button-stack { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .button-stack > .button:not(:first-child):not(:last-child) { - border-radius: 0; + border-radius: 0; } .button-stack > .button:not(:last-child) { - border-bottom: none; + border-bottom: none; } .button-stack > .button:first-child { - border-radius: 1rem 1rem 0 0; + border-radius: 1rem 1rem 0 0; } .button-stack > .button:last-child { - border-radius: 0 0 1rem 1rem; + border-radius: 0 0 1rem 1rem; } .button-stack > .button.button--sm { - height: 2rem; - line-height: 2rem; + height: 2rem; + line-height: 2rem; } .button-stack > .button.button--sm { - height: 2rem; - line-height: 2rem; + height: 2rem; + line-height: 2rem; } .fediverse-icon img { - width: 70%; + width: 70%; } /* LAYOUT */ html { - width: 100%; + width: 100%; } body { - background: var(--color-grey-97); + background: var(--color-grey-97); } body > #container { - min-height: 100vh; - width: 100%; - max-width: 75rem; - display: grid; - margin: 0 auto; - grid-template-columns: 1fr; - grid-template-rows: min-content auto; - padding: 0; + min-height: 100vh; + width: 100%; + max-width: 75rem; + display: grid; + margin: 0 auto; + grid-template-columns: 1fr; + grid-template-rows: min-content auto; + padding: 0; } #container > #content { - overflow: hidden; - border: 1px solid var(--color-grey-90); - background: #fff; - display: flex; - flex-direction: column; + overflow: hidden; + border: 1px solid var(--color-grey-90); + background: #fff; + display: flex; + flex-direction: column; } #container > #content > main { - position: relative; + position: relative; } #container > #content > main.page { - padding: 1rem; + padding: 1rem; } -#container > #content > main.event > *:not(.event-header-image,.event__editing-banner) { - margin: 0 1rem; +#container + > #content + > main.event + > *:not(.event-header-image, .event__editing-banner) { + margin: 0 1rem; } #container > #content > footer { - margin-top: auto; - border-top: 1px solid #e0e0e0; - text-align: center; - padding: 0.25rem 0; - background: var(--color-grey-99); + margin-top: auto; + border-top: 1px solid #e0e0e0; + text-align: center; + padding: 0.25rem 0; + background: var(--color-grey-99); } #container > #content > footer p { - margin-bottom: 0.25rem; + margin-bottom: 0.25rem; } @media (min-width: 768px) { - body > #container { - padding: 1rem; - grid-template-columns: 1fr 4fr; - grid-template-rows: auto; - gap: 1rem; - } - #container > #content { - border-radius: 1rem; - box-shadow: 0 0 6px rgba(0, 0, 0, 0.1); - } - #container > #content > main.page { - padding: 2rem; - } + body > #container { + padding: 1rem; + grid-template-columns: 1fr 4fr; + grid-template-rows: auto; + gap: 1rem; + } + #container > #content { + border-radius: 1rem; + box-shadow: 0 0 6px rgba(0, 0, 0, 0.1); + } + #container > #content > main.page { + padding: 2rem; + } } @media (min-width: 992px) { - body > #container { - grid-template-columns: 1fr 5fr; - } + body > #container { + grid-template-columns: 1fr 5fr; + } } /* SIDEBAR */ #sidebar h1 { - font-family: "Fredoka", sans-serif; - font-weight: 700; - font-optical-sizing: auto; - font-style: normal; - text-align: center; - letter-spacing: -0.5px; - font-size: 3rem; - color: transparent !important; - margin-bottom: 1rem; + font-family: "Fredoka", sans-serif; + font-weight: 700; + font-optical-sizing: auto; + font-style: normal; + text-align: center; + letter-spacing: -0.5px; + font-size: 3rem; + color: transparent !important; + margin-bottom: 1rem; } #sidebar h1 a { - background: rgb(69, 69, 69); - background-clip: text; - -webkit-background-clip: text; - color: transparent !important; + background: rgb(69, 69, 69); + background-clip: text; + -webkit-background-clip: text; + color: transparent !important; } #sidebar h1 a:hover { - text-decoration: none; - background: linear-gradient( - to right, - rgb(1, 76, 173), - rgb(128, 224, 200), - var(--color-purple) - ); - background-size: 100% 100%; - background-clip: text; - -webkit-background-clip: text; - color: transparent !important; + text-decoration: none; + background: linear-gradient( + to right, + rgb(1, 76, 173), + rgb(128, 224, 200), + var(--color-purple) + ); + background-size: 100% 100%; + background-clip: text; + -webkit-background-clip: text; + color: transparent !important; } ul#sidebar__nav { - padding: 0; - margin: 0; - list-style: none; - display: flex; - flex-direction: row; - gap: 0.5rem; - align-items: center; - justify-content: center; - margin-bottom: 0.5rem; + padding: 0; + margin: 0; + list-style: none; + display: flex; + flex-direction: row; + gap: 0.5rem; + align-items: center; + justify-content: center; + margin-bottom: 0.5rem; } ul#sidebar__nav li { - padding: 0 1rem 0.5rem 1rem; - text-align: center; + padding: 0 1rem 0.5rem 1rem; + text-align: center; } ul#sidebar__nav a { - display: block; - width: 100%; + display: block; + width: 100%; } @media (min-width: 768px) { - #sidebar { - padding-top: 1rem; - } - ul#sidebar__nav { - flex-direction: column; - } - ul#sidebar__nav li { - width: 100%; - padding: 0 0 0.5rem 0; - } - ul#sidebar__nav li:has(a:not(.button)):not(:last-child) { - border-bottom: 1px solid #e0e0e0; - } - - .sidebar-sticky-wrapper { - position: -webkit-sticky; - position: sticky; - top: 1rem; - } + #sidebar { + padding-top: 1rem; + } + ul#sidebar__nav { + flex-direction: column; + } + ul#sidebar__nav li { + width: 100%; + padding: 0 0 0.5rem 0; + } + ul#sidebar__nav li:has(a:not(.button)):not(:last-child) { + border-bottom: 1px solid #e0e0e0; + } + + .sidebar-sticky-wrapper { + position: -webkit-sticky; + position: sticky; + top: 1rem; + } } /* EVENTS */ #container > section#content.content--editing { - border: 6px solid var(--color-orange); + border: 6px solid var(--color-orange); } .event__editing-banner { - background-color: var(--color-orange); - color: #fff; - padding: 0.5rem; - text-align: center; - font-weight: bold; - margin: 0; + background-color: var(--color-orange); + color: #fff; + padding: 0.5rem; + text-align: center; + font-weight: bold; + margin: 0; } #genericEventImageContainer { - height: 150px; - border-radius: 5px; + height: 150px; + border-radius: 5px; } #genericEventImageContainer:before { - content: ""; - background: linear-gradient( - to bottom, - rgba(30, 87, 153, 0) 0%, - rgba(242, 245, 249, 0) 75%, - rgba(255, 255, 255, 1) 95%, - rgba(255, 255, 255, 1) 100% - ); - position: absolute; - width: 100%; - height: 150px; + content: ""; + background: linear-gradient( + to bottom, + rgba(30, 87, 153, 0) 0%, + rgba(242, 245, 249, 0) 75%, + rgba(255, 255, 255, 1) 95%, + rgba(255, 255, 255, 1) 100% + ); + position: absolute; + width: 100%; + height: 150px; } #eventImageContainer { - height: 300px; - background-size: cover; - background-repeat: no-repeat; - background-position: center; - border-radius: 5px; + height: 300px; + background-size: cover; + background-repeat: no-repeat; + background-position: center; + border-radius: 5px; } #eventImageContainer:before { - content: ""; - background: linear-gradient( - to bottom, - rgba(30, 87, 153, 0) 0%, - rgba(242, 245, 249, 0) 85%, - rgba(255, 255, 255, 1) 95%, - rgba(255, 255, 255, 1) 100% - ); - position: absolute; - width: 100%; - height: 300px; + content: ""; + background: linear-gradient( + to bottom, + rgba(30, 87, 153, 0) 0%, + rgba(242, 245, 249, 0) 85%, + rgba(255, 255, 255, 1) 95%, + rgba(255, 255, 255, 1) 100% + ); + position: absolute; + width: 100%; + height: 300px; } #event__basics { - display: grid; - grid-template-columns: 1fr; - gap: 1rem; - margin-top: 1.5rem; - margin-bottom: 1.5rem; + display: grid; + grid-template-columns: 1fr; + gap: 1rem; + margin-top: 1.5rem; + margin-bottom: 1.5rem; } @media (min-width: 992px) { - #event__basics { - grid-template-columns: 3fr 1fr; - } + #event__basics { + grid-template-columns: 3fr 1fr; + } } #eventName { - padding: 0 0 0 10px; - width: 100%; - display: flex; - justify-content: space-between; + padding: 0 0 0 10px; + width: 100%; + display: flex; + justify-content: space-between; } #eventPrivacy { - text-transform: capitalize; + text-transform: capitalize; } #eventFromNow { - padding-left: 25px; + padding-left: 25px; } #eventFromNow::first-letter { - text-transform: capitalize; + text-transform: capitalize; } .attendeesList { - margin: 0; - padding: 0; - list-style-type: none; - display: flex; - flex-wrap: wrap; + margin: 0; + padding: 0; + list-style-type: none; + display: flex; + flex-wrap: wrap; } #event__actions #editEvent, #event__actions #editGroup { - width: 100%; - margin-top: 16px; + width: 100%; + margin-top: 16px; } .attendeesList > li { - border: 4px solid #0ea130; - border-radius: 2em; - padding: 0.5em 1em; - margin-right: 5px; - margin-bottom: 10px; - background: #57b76d; - color: white; - font-size: 0.95em; - font-weight: bold; + border: 4px solid #0ea130; + border-radius: 2em; + padding: 0.5em 1em; + margin-right: 5px; + margin-bottom: 10px; + background: #57b76d; + color: white; + font-size: 0.95em; + font-weight: bold; } .attendeesList > li.hidden-attendee { - border: 4px solid #ccc; - background: #eee; + border: 4px solid #ccc; + background: #eee; } .attendeesList > li.hidden-attendee a { - color: #555; + color: #555; } .hidden-attendees-message { - display: inline-block; - border: 4px solid #ccc; - text-align: center; - border-radius: 2em; - padding: 0.5em 1em; - background: #eee; - color: #555; - font-size: 0.95em; - font-weight: bold; - margin: 0; + display: inline-block; + border: 4px solid #ccc; + text-align: center; + border-radius: 2em; + padding: 0.5em 1em; + background: #eee; + color: #555; + font-size: 0.95em; + font-weight: bold; + margin: 0; } .expand { - -webkit-transition: height 0.2s; - -moz-transition: height 0.2s; - transition: height 0.2s; + -webkit-transition: height 0.2s; + -moz-transition: height 0.2s; + transition: height 0.2s; } .eventInformation { - margin-left: 1.6em; + margin-left: 1.6em; } .eventInformation > li { - margin-bottom: 0.8em; + margin-bottom: 0.8em; } #copyEventLink { - margin-left: 5px; + margin-left: 5px; } .commentContainer { - background: #fafafa; - border-radius: 5px; - padding: 10px; - margin-bottom: 10px; - border: 1px solid #dfdfdf; + background: #fafafa; + border-radius: 5px; + padding: 10px; + margin-bottom: 10px; + border: 1px solid #dfdfdf; } .replyContainer { - display: none; - background: #efefef; - padding: 10px; - border-radius: 0 0 5px 5px; - border-bottom: 1px solid #d2d2d2; - border-left: 1px solid #d2d2d2; - border-right: 1px solid #d2d2d2; - width: 95%; - margin: -10px auto 10px auto; + display: none; + background: #efefef; + padding: 10px; + border-radius: 0 0 5px 5px; + border-bottom: 1px solid #d2d2d2; + border-left: 1px solid #d2d2d2; + border-right: 1px solid #d2d2d2; + width: 95%; + margin: -10px auto 10px auto; } .repliesContainer { - font-size: smaller; - padding-left: 20px; + font-size: smaller; + padding-left: 20px; } .attendee-name { - white-space: nowrap; - overflow: hidden; - text-overflow: ""; - overflow: hidden; - max-width: 62px; - color: #fff; + white-space: nowrap; + overflow: hidden; + text-overflow: ""; + overflow: hidden; + max-width: 62px; + color: #fff; } li.hidden-attendee .attendee-name { - color: #555; + color: #555; } .remove-attendee { - color: #fff; + color: #fff; } .remove-attendee:hover { - color: #016418; + color: #016418; } #eventAttendees h5 { - display: flex; - flex-direction: column; - align-items: flex-start; + display: flex; + flex-direction: column; + align-items: flex-start; } #eventAttendees h5 .button--group { - margin-top: 0.5rem; + margin-top: 0.5rem; } @media (min-width: 576px) { - #eventAttendees h5 { - flex-direction: row; - justify-content: space-between; - align-items: center; - } - #eventAttendees h5 .button--group { - margin-top: 0; - } + #eventAttendees h5 { + flex-direction: row; + justify-content: space-between; + align-items: center; + } + #eventAttendees h5 .button--group { + margin-top: 0; + } } .edit-buttons { - text-align: right; + text-align: right; } @media (max-width: 1199.98px) { - .edit-buttons { - text-align: left; - } + .edit-buttons { + text-align: left; + } } .code { - font-family: "Courier New", Courier, monospace; - overflow-wrap: anywhere; + font-family: "Courier New", Courier, monospace; + overflow-wrap: anywhere; } /* IMAGE UPLOAD FORM */ .image-preview { - max-width: 920px; - width: 100%; - height: 200px; - position: relative; - overflow: hidden; - background-color: #ffffff; - color: #ecf0f1; - border-radius: 5px; - border: 1px dashed #ced4da; + max-width: 920px; + width: 100%; + height: 200px; + position: relative; + overflow: hidden; + background-color: #ffffff; + color: #ecf0f1; + border-radius: 5px; + border: 1px dashed #ced4da; } .image-preview input { - line-height: 200px; - font-size: 200px; - position: absolute; - opacity: 0; - z-index: 10; + line-height: 200px; + font-size: 200px; + position: absolute; + opacity: 0; + z-index: 10; } .image-preview label { - position: absolute; - z-index: 5; - opacity: 0.8; - cursor: pointer; - background-color: #ced4da; - color: #555; - width: 200px; - height: 50px; - font-size: 20px; - line-height: 50px; - text-transform: uppercase; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin: auto; - text-align: center; - border-radius: 5px; + position: absolute; + z-index: 5; + opacity: 0.8; + cursor: pointer; + background-color: #ced4da; + color: #555; + width: 200px; + height: 50px; + font-size: 20px; + line-height: 50px; + text-transform: uppercase; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + text-align: center; + border-radius: 5px; } /* FORMS */ #icsImportLabel { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: #6c757d; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: #6c757d; } .select2-container { - width: 100% !important; + width: 100% !important; } .select2-selection__rendered { - line-height: 2.25rem !important; + line-height: 2.25rem !important; } .select2-selection__arrow { - height: 100% !important; + height: 100% !important; } .select2-container .select2-selection--single { - /* Match Bootstrap 4 styling */ - border: 1px solid #ced4da; - height: 2.25rem; + /* Match Bootstrap 4 styling */ + border: 1px solid #ced4da; + height: 2.25rem; } .select2-results__option[aria-selected="true"] .group-preview__text, .select2-results__option[aria-selected="true"] - .group-preview__text - p.text-muted { - color: white !important; + .group-preview__text + p.text-muted { + color: white !important; } .select2-selection__rendered, .select2-selection--single { - overflow: hidden; + overflow: hidden; } .group-preview { - display: grid; - grid-template-columns: 60px 1fr; - overflow: hidden; - gap: 1rem; + display: grid; + grid-template-columns: 60px 1fr; + overflow: hidden; + gap: 1rem; } .select2-selection__rendered .group-preview { - transform: translateX(-8px); + transform: translateX(-8px); } img.group-preview__image { - width: 100%; - height: 60px; - object-fit: cover; + width: 100%; + height: 60px; + object-fit: cover; } .select2-container .select2-selection--single.group-select-dropdown { - height: 60px; + height: 60px; } .select2-container - .select2-selection--single.group-select-dropdown - .select2-selection__rendered { - height: 100%; - display: flex; - align-items: center; + .select2-selection--single.group-select-dropdown + .select2-selection__rendered { + height: 100%; + display: flex; + align-items: center; } .group-preview__text { - text-decoration: none; - color: #1b1b1b; - overflow: hidden; - padding-right: 1rem; - display: flex; - flex-direction: column; - justify-content: center; - line-height: 1.5; + text-decoration: none; + color: #1b1b1b; + overflow: hidden; + padding-right: 1rem; + display: flex; + flex-direction: column; + justify-content: center; + line-height: 1.5; } .group-preview__text strong, .group-preview__text p { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0; } @keyframes shimmer { - 100% { - transform: translateX(100%); - } + 100% { + transform: translateX(100%); + } } .button--loading { - position: relative; + position: relative; } .button--loading::after { - content: ""; - position: absolute; - left: -45%; - height: 200%; - width: 45%; - background-image: linear-gradient( - to left, - rgba(251, 251, 251, 0), - rgba(251, 251, 251, 0.05), - rgba(251, 251, 251, 0.2), - rgba(251, 251, 251, 0.5), - rgba(251, 251, 251, 0.2), - rgba(251, 251, 251, 0.05), - rgba(251, 251, 251, 0) - ); - animation: loading 1.35s infinite; - z-index: 100; - transform: translateY(-50%); + content: ""; + position: absolute; + left: -45%; + height: 200%; + width: 45%; + background-image: linear-gradient( + to left, + rgba(251, 251, 251, 0), + rgba(251, 251, 251, 0.05), + rgba(251, 251, 251, 0.2), + rgba(251, 251, 251, 0.5), + rgba(251, 251, 251, 0.2), + rgba(251, 251, 251, 0.05), + rgba(251, 251, 251, 0) + ); + animation: loading 1.35s infinite; + z-index: 100; + transform: translateY(-50%); } @keyframes loading { - 0% { - left: -45%; - } - 100% { - left: 100%; - } + 0% { + left: -45%; + } + 100% { + left: 100%; + } } .slider { - height: 0; - opacity: 0; - overflow: hidden; - transition: all 0.45s; - pointer-events: none; + height: 0; + opacity: 0; + overflow: hidden; + transition: all 0.45s; + pointer-events: none; } .slider.slider--open { - opacity: 1; - pointer-events: auto; + opacity: 1; + pointer-events: auto; } /* EVENT AND GROUP LISTS */ .list-group-item-action:hover { - background-color: var(--color-purple-light); + background-color: var(--color-purple-light); } /* STATIC PAGES */ article.static-page header { - margin-bottom: 1rem; - border-bottom: 1px solid #e0e0e0; + margin-bottom: 1rem; + border-bottom: 1px solid #e0e0e0; } diff --git a/public/js/modules/event-edit.js b/public/js/modules/event-edit.js index 313e8f2f..f53e146b 100644 --- a/public/js/modules/event-edit.js +++ b/public/js/modules/event-edit.js @@ -1,113 +1,112 @@ $(document).ready(function () { - $.uploadPreview({ - input_field: "#event-image-upload", - preview_box: "#event-image-preview", - label_field: "#event-image-label", - label_default: "Choose file", - label_selected: "Change file", - no_label: false, - }); - if (window.eventData.image) { - $("#event-image-preview").css( - "background-image", - `url('/events/${window.eventData.image}')`, - ); - $("#event-image-preview").css("background-size", "cover"); - $("#event-image-preview").css("background-position", "center center"); - } + $.uploadPreview({ + input_field: "#event-image-upload", + preview_box: "#event-image-preview", + label_field: "#event-image-label", + label_default: "Choose file", + label_selected: "Change file", + no_label: false, + }); + if (window.eventData.image) { + $("#event-image-preview").css( + "background-image", + `url('/events/${window.eventData.image}')`, + ); + $("#event-image-preview").css("background-size", "cover"); + $("#event-image-preview").css("background-position", "center center"); + } }); -$('#editModal').on('shown.bs.modal', function (e) { - console.log('hii'); - const ta = document.querySelector("#editModal textarea"); - ta.style.display = 'none'; - autosize(ta); - ta.style.display = ''; - // Call the update method to recalculate the size: - autosize.update(ta); +$("#editModal").on("shown.bs.modal", function (e) { + console.log("hii"); + const ta = document.querySelector("#editModal textarea"); + ta.style.display = "none"; + autosize(ta); + ta.style.display = ""; + // Call the update method to recalculate the size: + autosize.update(ta); }); function editEventForm() { - return { - data: { - eventName: window.eventData.name, - eventLocation: window.eventData.location, - eventStart: window.eventData.startForDateInput, - eventEnd: window.eventData.endForDateInput, - timezone: window.eventData.timezone, - eventDescription: window.eventData.description, - eventURL: window.eventData.url, - hostName: window.eventData.hostName, - creatorEmail: window.eventData.creatorEmail, - eventGroupID: window.eventData.eventGroupID, - eventGroupEditToken: window.eventData.eventGroupEditToken, - publicCheckbox: window.eventData.showOnPublicList, - interactionCheckbox: window.eventData.usersCanComment, - joinCheckbox: window.eventData.usersCanAttend, - maxAttendeesCheckbox: window.eventData.maxAttendees !== null, - maxAttendees: window.eventData.maxAttendees, - }, - errors: [], - submitting: false, - init() { - // Set up Select2 - this.select2 = $(this.$refs.timezone).select2(); - this.select2.on("select2:select", (event) => { - this.data.timezone = event.target.value; - }); - this.select2.val(this.data.timezone).trigger("change"); + return { + data: { + eventName: window.eventData.name, + eventLocation: window.eventData.location, + eventStart: window.eventData.startForDateInput, + eventEnd: window.eventData.endForDateInput, + timezone: window.eventData.timezone, + eventDescription: window.eventData.description, + eventURL: window.eventData.url, + hostName: window.eventData.hostName, + creatorEmail: window.eventData.creatorEmail, + eventGroupID: window.eventData.eventGroupID, + eventGroupEditToken: window.eventData.eventGroupEditToken, + publicCheckbox: window.eventData.showOnPublicList, + interactionCheckbox: window.eventData.usersCanComment, + joinCheckbox: window.eventData.usersCanAttend, + maxAttendeesCheckbox: window.eventData.maxAttendees !== null, + maxAttendees: window.eventData.maxAttendees, + }, + errors: [], + submitting: false, + init() { + // Set up Select2 + this.select2 = $(this.$refs.timezone).select2(); + this.select2.on("select2:select", (event) => { + this.data.timezone = event.target.value; + }); + this.select2.val(this.data.timezone).trigger("change"); - // Set checkboxes - this.data.eventGroupCheckbox = !!window.eventData.eventGroupID; - this.data.interactionCheckbox = window.eventData.usersCanComment; - this.data.joinCheckbox = window.eventData.usersCanAttend; - this.data.maxAttendeesCheckbox = - window.eventData.maxAttendees !== null; - this.data.publicCheckbox = window.eventData.showOnPublicList; - }, - updateEventEnd() { - if (this.data.eventEnd === "" || this.data.eventEnd < this.data.eventStart) { - this.data.eventEnd = this.data.eventStart; - } - }, - async submitForm() { - this.submitting = true; - this.errors = []; - const formData = new FormData(); - for (const [key, value] of Object.entries(this.data)) { - formData.append(key, value); - } - formData.append( - "imageUpload", - this.$refs.eventImageUpload.files[0], - ); - formData.append("editToken", window.eventData.editToken); - try { - const response = await fetch(`/event/${window.eventData.id}`, { - method: "PUT", - body: formData, - }); - this.submitting = false; - if (!response.ok) { - if (response.status !== 400) { - this.errors = unexpectedError; - return; - } - const json = await response.json(); - this.errors = json.errors; - // Set Bootstrap validation classes using 'field' property - $("input, textarea").removeClass("is-invalid"); - this.errors.forEach((error) => { - $(`#${error.field}`).addClass("is-invalid"); - }); - return; - } - window.location.reload(); - } catch (error) { - console.log(error); - this.errors = unexpectedError; - this.submitting = false; - } - }, - }; + // Set checkboxes + this.data.eventGroupCheckbox = !!window.eventData.eventGroupID; + this.data.interactionCheckbox = window.eventData.usersCanComment; + this.data.joinCheckbox = window.eventData.usersCanAttend; + this.data.maxAttendeesCheckbox = window.eventData.maxAttendees !== null; + this.data.publicCheckbox = window.eventData.showOnPublicList; + }, + updateEventEnd() { + if ( + this.data.eventEnd === "" || + this.data.eventEnd < this.data.eventStart + ) { + this.data.eventEnd = this.data.eventStart; + } + }, + async submitForm() { + this.submitting = true; + this.errors = []; + const formData = new FormData(); + for (const [key, value] of Object.entries(this.data)) { + formData.append(key, value); + } + formData.append("imageUpload", this.$refs.eventImageUpload.files[0]); + formData.append("editToken", window.eventData.editToken); + try { + const response = await fetch(`/event/${window.eventData.id}`, { + method: "PUT", + body: formData, + }); + this.submitting = false; + if (!response.ok) { + if (response.status !== 400) { + this.errors = unexpectedError; + return; + } + const json = await response.json(); + this.errors = json.errors; + // Set Bootstrap validation classes using 'field' property + $("input, textarea").removeClass("is-invalid"); + this.errors.forEach((error) => { + $(`#${error.field}`).addClass("is-invalid"); + }); + return; + } + window.location.reload(); + } catch (error) { + console.log(error); + this.errors = unexpectedError; + this.submitting = false; + } + }, + }; } diff --git a/public/js/modules/group-edit.js b/public/js/modules/group-edit.js index db2d4110..66cdb90e 100644 --- a/public/js/modules/group-edit.js +++ b/public/js/modules/group-edit.js @@ -1,84 +1,81 @@ $(document).ready(function () { - $.uploadPreview({ - input_field: "#group-image-upload", - preview_box: "#group-image-preview", - label_field: "#group-image-label", - label_default: "Choose file", - label_selected: "Change file", - no_label: false, - }); - if (window.groupData.image) { - $("#group-image-preview").css( - "background-image", - `url('/events/${window.groupData.image}')`, - ); - $("#group-image-preview").css("background-size", "cover"); - $("#group-image-preview").css("background-position", "center center"); - } - $("#timezone").val(window.groupData.timezone).trigger("change"); + $.uploadPreview({ + input_field: "#group-image-upload", + preview_box: "#group-image-preview", + label_field: "#group-image-label", + label_default: "Choose file", + label_selected: "Change file", + no_label: false, + }); + if (window.groupData.image) { + $("#group-image-preview").css( + "background-image", + `url('/events/${window.groupData.image}')`, + ); + $("#group-image-preview").css("background-size", "cover"); + $("#group-image-preview").css("background-position", "center center"); + } + $("#timezone").val(window.groupData.timezone).trigger("change"); }); -$('#editModal').on('shown.bs.modal', function (e) { +$("#editModal").on("shown.bs.modal", function (e) { const ta = document.querySelector("#editModal textarea"); - ta.style.display = 'none'; + ta.style.display = "none"; autosize(ta); - ta.style.display = ''; + ta.style.display = ""; autosize.update(ta); }); function editEventGroupForm() { - return { - data: { - eventGroupName: window.groupData.name, - eventGroupDescription: window.groupData.description, - eventGroupURL: window.groupData.url, - hostName: window.groupData.hostName, - creatorEmail: window.groupData.creatorEmail, - publicCheckbox: window.groupData.showOnPublicList, - }, - init() { - // Set checkboxes - this.data.publicCheckbox = window.groupData.showOnPublicList; - }, - errors: [], - submitting: false, - async submitForm() { - this.submitting = true; - this.errors = []; - const formData = new FormData(); - for (const [key, value] of Object.entries(this.data)) { - formData.append(key, value); - } - formData.append( - "imageUpload", - this.$refs.eventGroupImageUpload.files[0], - ); - formData.append("editToken", window.groupData.editToken); - try { - const response = await fetch(`/group/${window.groupData.id}`, { - method: "PUT", - body: formData, - }); - this.submitting = false; - if (!response.ok) { - if (response.status !== 400) { - this.errors = unexpectedError; - return; - } - const json = await response.json(); - this.errors = json.errors; - $("input, textarea").removeClass("is-invalid"); - this.errors.forEach((error) => { - $(`#${error.field}`).addClass("is-invalid"); - }); - return; - } - window.location.reload(); - } catch (error) { - console.log(error); - this.errors = unexpectedError; - this.submitting = false; - } - }, - }; + return { + data: { + eventGroupName: window.groupData.name, + eventGroupDescription: window.groupData.description, + eventGroupURL: window.groupData.url, + hostName: window.groupData.hostName, + creatorEmail: window.groupData.creatorEmail, + publicCheckbox: window.groupData.showOnPublicList, + }, + init() { + // Set checkboxes + this.data.publicCheckbox = window.groupData.showOnPublicList; + }, + errors: [], + submitting: false, + async submitForm() { + this.submitting = true; + this.errors = []; + const formData = new FormData(); + for (const [key, value] of Object.entries(this.data)) { + formData.append(key, value); + } + formData.append("imageUpload", this.$refs.eventGroupImageUpload.files[0]); + formData.append("editToken", window.groupData.editToken); + try { + const response = await fetch(`/group/${window.groupData.id}`, { + method: "PUT", + body: formData, + }); + this.submitting = false; + if (!response.ok) { + if (response.status !== 400) { + this.errors = unexpectedError; + return; + } + const json = await response.json(); + this.errors = json.errors; + $("input, textarea").removeClass("is-invalid"); + this.errors.forEach((error) => { + $(`#${error.field}`).addClass("is-invalid"); + }); + return; + } + window.location.reload(); + } catch (error) { + console.log(error); + this.errors = unexpectedError; + this.submitting = false; + } + }, + }; } diff --git a/public/js/modules/group-linker.js b/public/js/modules/group-linker.js index a21fec3c..a9296db2 100644 --- a/public/js/modules/group-linker.js +++ b/public/js/modules/group-linker.js @@ -1,131 +1,123 @@ function eventGroupLinker() { - return { - data: { - eventGroupID: "", - eventGroupEditToken: "", - groups: [], - }, - manualGroupInputVisible: false, - eventGroupOptionTemplate(state) { - if (!state.id) { - return state.text; - } - if (!this.data.groups.length) { - return state.text; - } - const group = this.data.groups.find( - (group) => group.id === state.id, - ); - if (!group) { - return state.text; - } - const template = ` + return { + data: { + eventGroupID: "", + eventGroupEditToken: "", + groups: [], + }, + manualGroupInputVisible: false, + eventGroupOptionTemplate(state) { + if (!state.id) { + return state.text; + } + if (!this.data.groups.length) { + return state.text; + } + const group = this.data.groups.find((group) => group.id === state.id); + if (!group) { + return state.text; + } + const template = `

${group.name}

${group.description}

`; - return $(template); - }, - async init() { - this.select2 = $(this.$refs.eventGroupSelect).select2({ - placeholder: "No group selected", - templateResult: this.eventGroupOptionTemplate.bind(this), - templateSelection: this.eventGroupOptionTemplate.bind(this), - selectionCssClass: "group-select-dropdown", - }); - this.select2.on("select2:select", (event) => { - this.selectGroup(event); - }); - this.select2.on("select2:unselect", () => { - this.data.eventGroupID = ""; - this.data.eventGroupEditToken = ""; - }); - this.$watch("data.eventGroupID", () => { - this.$dispatch( - "event-group-id-changed", - this.data.eventGroupID, - ); - const matchingGroup = this.data.groups.find( - (group) => - group.id === this.data.eventGroupID && - group.editToken === this.data.eventGroupEditToken, - ); - if (matchingGroup) { - this.select2.val(matchingGroup.id).trigger("change"); - } else { - this.resetGroupSelector(); - } - }); - this.$watch("data.eventGroupEditToken", () => { - this.$dispatch( - "event-group-edit-token-changed", - this.data.eventGroupEditToken, - ); - const matchingGroup = this.data.groups.find( - (group) => - group.id === this.data.eventGroupID && - group.editToken === this.data.eventGroupEditToken, - ); - if (matchingGroup) { - this.select2.val(matchingGroup.id).trigger("change"); - } else { - this.resetGroupSelector(); - } - }); - this.$watch("data.groups", () => { - this.select2.val(this.data.eventGroupID).trigger("change"); - }); - if (window.eventData && !!window.eventData.eventGroupID) { - this.data.eventGroupID = window.eventData.eventGroupID; - } - if (window.eventData && !!window.eventGroupEditToken) { - this.data.eventGroupEditToken = - window.eventData.eventGroupEditToken; - } - try { - const editTokens = JSON.parse( - localStorage.getItem("editTokens"), - ); - if (!editTokens) { - return; - } - const response = await fetch("/known/groups", { - method: "POST", - body: JSON.stringify(editTokens), - headers: { - "Content-Type": "application/json", - }, - }); - if (!response.ok) { - return; - } - const json = await response.json(); - this.data.groups = json; - } catch (e) { - return false; - } - }, - selectGroup(e) { - const group = this.data.groups.find( - (group) => group.id === e.target.value, - ); - if (!group) { - this.data.eventGroupID = ""; - this.data.eventGroupEditToken = ""; - return; - } - this.data.eventGroupID = group.id; - this.data.eventGroupEditToken = group.editToken; - }, - resetGroupSelector() { - this.select2.val(null).trigger("change"); - }, - }; + return $(template); + }, + async init() { + this.select2 = $(this.$refs.eventGroupSelect).select2({ + placeholder: "No group selected", + templateResult: this.eventGroupOptionTemplate.bind(this), + templateSelection: this.eventGroupOptionTemplate.bind(this), + selectionCssClass: "group-select-dropdown", + }); + this.select2.on("select2:select", (event) => { + this.selectGroup(event); + }); + this.select2.on("select2:unselect", () => { + this.data.eventGroupID = ""; + this.data.eventGroupEditToken = ""; + }); + this.$watch("data.eventGroupID", () => { + this.$dispatch("event-group-id-changed", this.data.eventGroupID); + const matchingGroup = this.data.groups.find( + (group) => + group.id === this.data.eventGroupID && + group.editToken === this.data.eventGroupEditToken, + ); + if (matchingGroup) { + this.select2.val(matchingGroup.id).trigger("change"); + } else { + this.resetGroupSelector(); + } + }); + this.$watch("data.eventGroupEditToken", () => { + this.$dispatch( + "event-group-edit-token-changed", + this.data.eventGroupEditToken, + ); + const matchingGroup = this.data.groups.find( + (group) => + group.id === this.data.eventGroupID && + group.editToken === this.data.eventGroupEditToken, + ); + if (matchingGroup) { + this.select2.val(matchingGroup.id).trigger("change"); + } else { + this.resetGroupSelector(); + } + }); + this.$watch("data.groups", () => { + this.select2.val(this.data.eventGroupID).trigger("change"); + }); + if (window.eventData && !!window.eventData.eventGroupID) { + this.data.eventGroupID = window.eventData.eventGroupID; + } + if (window.eventData && !!window.eventGroupEditToken) { + this.data.eventGroupEditToken = window.eventData.eventGroupEditToken; + } + try { + const editTokens = JSON.parse(localStorage.getItem("editTokens")); + if (!editTokens) { + return; + } + const response = await fetch("/known/groups", { + method: "POST", + body: JSON.stringify(editTokens), + headers: { + "Content-Type": "application/json", + }, + }); + if (!response.ok) { + return; + } + const json = await response.json(); + this.data.groups = json; + } catch (e) { + return false; + } + }, + selectGroup(e) { + const group = this.data.groups.find( + (group) => group.id === e.target.value, + ); + if (!group) { + this.data.eventGroupID = ""; + this.data.eventGroupEditToken = ""; + return; + } + this.data.eventGroupID = group.id; + this.data.eventGroupEditToken = group.editToken; + }, + resetGroupSelector() { + this.select2.val(null).trigger("change"); + }, + }; } diff --git a/public/js/modules/new.js b/public/js/modules/new.js index 70df641d..7dac6926 100644 --- a/public/js/modules/new.js +++ b/public/js/modules/new.js @@ -1,227 +1,212 @@ $(document).ready(function () { - if ($("#icsImportControl")[0].files[0] != null) { - var file = $("#icsImportControl")[0].files[0].name; - $("#icsImportControl") - .next("label") - .html(' ' + file); - } - $("#icsImportControl").change(function () { - var file = $("#icsImportControl")[0].files[0].name; - $(this) - .next("label") - .html(' ' + file); - }); + if ($("#icsImportControl")[0].files[0] != null) { + var file = $("#icsImportControl")[0].files[0].name; + $("#icsImportControl") + .next("label") + .html(' ' + file); + } + $("#icsImportControl").change(function () { + var file = $("#icsImportControl")[0].files[0].name; + $(this) + .next("label") + .html(' ' + file); + }); - $.uploadPreview({ - input_field: "#event-image-upload", - preview_box: "#event-image-preview", - label_field: "#event-image-label", - label_default: "Choose file", - label_selected: "Change file", - no_label: false, - }); - $.uploadPreview({ - input_field: "#group-image-upload", - preview_box: "#group-image-preview", - label_field: "#group-image-label", - label_default: "Choose file", - label_selected: "Change file", - no_label: false, - }); - autosize($("textarea")); + $.uploadPreview({ + input_field: "#event-image-upload", + preview_box: "#event-image-preview", + label_field: "#event-image-label", + label_default: "Choose file", + label_selected: "Change file", + no_label: false, + }); + $.uploadPreview({ + input_field: "#group-image-upload", + preview_box: "#group-image-preview", + label_field: "#group-image-label", + label_default: "Choose file", + label_selected: "Change file", + no_label: false, + }); + autosize($("textarea")); }); function newEventForm() { - return { - data: { - eventName: "", - eventLocation: "", - eventStart: "", - eventEnd: "", - timezone: "", - eventDescription: "", - eventURL: "", - hostName: "", - creatorEmail: "", - eventGroupID: "", - eventGroupEditToken: "", - publicCheckbox: false, - interactionCheckbox: false, - joinCheckbox: false, - maxAttendeesCheckbox: false, - maxAttendees: "", - }, - errors: [], - submitting: false, - init() { - // Set up timezone Select2 - this.select2 = $(this.$refs.timezone).select2(); - this.select2.on("select2:select", (event) => { - this.data.timezone = event.target.value; - }); - this.data.timezone = this.select2.val(); + return { + data: { + eventName: "", + eventLocation: "", + eventStart: "", + eventEnd: "", + timezone: "", + eventDescription: "", + eventURL: "", + hostName: "", + creatorEmail: "", + eventGroupID: "", + eventGroupEditToken: "", + publicCheckbox: false, + interactionCheckbox: false, + joinCheckbox: false, + maxAttendeesCheckbox: false, + maxAttendees: "", + }, + errors: [], + submitting: false, + init() { + // Set up timezone Select2 + this.select2 = $(this.$refs.timezone).select2(); + this.select2.on("select2:select", (event) => { + this.data.timezone = event.target.value; + }); + this.data.timezone = this.select2.val(); - // Reset checkboxes - this.data.eventGroupCheckbox = false; - this.data.interactionCheckbox = false; - this.data.joinCheckbox = false; - this.data.maxAttendeesCheckbox = false; - this.data.publicCheckbox = false; - }, - updateEventEnd() { - if (this.data.eventEnd === "" || this.data.eventEnd < this.data.eventStart) { - this.data.eventEnd = this.data.eventStart; - } - }, - async submitForm() { - this.submitting = true; - this.errors = []; - const formData = new FormData(); - for (const [key, value] of Object.entries(this.data)) { - formData.append(key, value); - } - formData.append( - "imageUpload", - this.$refs.eventImageUpload.files[0], - ); - formData.append( - "magicLinkToken", - this.$refs.magicLinkToken.value, - ); - try { - const response = await fetch("/event", { - method: "POST", - body: formData, - }); - this.submitting = false; - if (!response.ok) { - if (response.status !== 400) { - this.errors = unexpectedError; - return; - } - const json = await response.json(); - this.errors = json.errors; - $("input, textarea").removeClass("is-invalid"); - this.errors.forEach((error) => { - $(`#${error.field}`).addClass("is-invalid"); - }); - return; - } - const json = await response.json(); - window.location.assign(json.url); - } catch (error) { - console.log(error); - this.errors = unexpectedError; - this.submitting = false; - } - }, - }; + // Reset checkboxes + this.data.eventGroupCheckbox = false; + this.data.interactionCheckbox = false; + this.data.joinCheckbox = false; + this.data.maxAttendeesCheckbox = false; + this.data.publicCheckbox = false; + }, + updateEventEnd() { + if ( + this.data.eventEnd === "" || + this.data.eventEnd < this.data.eventStart + ) { + this.data.eventEnd = this.data.eventStart; + } + }, + async submitForm() { + this.submitting = true; + this.errors = []; + const formData = new FormData(); + for (const [key, value] of Object.entries(this.data)) { + formData.append(key, value); + } + formData.append("imageUpload", this.$refs.eventImageUpload.files[0]); + formData.append("magicLinkToken", this.$refs.magicLinkToken.value); + try { + const response = await fetch("/event", { + method: "POST", + body: formData, + }); + this.submitting = false; + if (!response.ok) { + if (response.status !== 400) { + this.errors = unexpectedError; + return; + } + const json = await response.json(); + this.errors = json.errors; + $("input, textarea").removeClass("is-invalid"); + this.errors.forEach((error) => { + $(`#${error.field}`).addClass("is-invalid"); + }); + return; + } + const json = await response.json(); + window.location.assign(json.url); + } catch (error) { + console.log(error); + this.errors = unexpectedError; + this.submitting = false; + } + }, + }; } function newEventGroupForm() { - return { - data: { - eventGroupName: "", - eventGroupDescription: "", - eventGroupURL: "", - hostName: "", - creatorEmail: "", - publicCheckbox: false, - }, - init() { - // Reset checkboxes - this.data.publicCheckbox = false; - }, - errors: [], - submitting: false, - async submitForm() { - this.submitting = true; - this.errors = []; - const formData = new FormData(); - for (const [key, value] of Object.entries(this.data)) { - formData.append(key, value); - } - formData.append( - "imageUpload", - this.$refs.eventGroupImageUpload.files[0], - ); - formData.append( - "magicLinkToken", - this.$refs.magicLinkToken.value, - ); - try { - const response = await fetch("/group", { - method: "POST", - body: formData, - }); - this.submitting = false; - if (!response.ok) { - if (response.status !== 400) { - this.errors = unexpectedError; - return; - } - const json = await response.json(); - this.errors = json.errors; - $("input, textarea").removeClass("is-invalid"); - this.errors.forEach((error) => { - $(`#${error.field}`).addClass("is-invalid"); - }); - return; - } - const json = await response.json(); - window.location.assign(json.url); - } catch (error) { - console.log(error); - this.errors = unexpectedError; - this.submitting = false; - } - }, - }; + return { + data: { + eventGroupName: "", + eventGroupDescription: "", + eventGroupURL: "", + hostName: "", + creatorEmail: "", + publicCheckbox: false, + }, + init() { + // Reset checkboxes + this.data.publicCheckbox = false; + }, + errors: [], + submitting: false, + async submitForm() { + this.submitting = true; + this.errors = []; + const formData = new FormData(); + for (const [key, value] of Object.entries(this.data)) { + formData.append(key, value); + } + formData.append("imageUpload", this.$refs.eventGroupImageUpload.files[0]); + formData.append("magicLinkToken", this.$refs.magicLinkToken.value); + try { + const response = await fetch("/group", { + method: "POST", + body: formData, + }); + this.submitting = false; + if (!response.ok) { + if (response.status !== 400) { + this.errors = unexpectedError; + return; + } + const json = await response.json(); + this.errors = json.errors; + $("input, textarea").removeClass("is-invalid"); + this.errors.forEach((error) => { + $(`#${error.field}`).addClass("is-invalid"); + }); + return; + } + const json = await response.json(); + window.location.assign(json.url); + } catch (error) { + console.log(error); + this.errors = unexpectedError; + this.submitting = false; + } + }, + }; } function importEventForm() { - return { - data: { - creatorEmail: "", - }, - errors: [], - submitting: false, - async submitForm() { - this.submitting = true; - this.errors = []; - const formData = new FormData(); - for (const [key, value] of Object.entries(this.data)) { - formData.append(key, value); - } - formData.append( - "icsImportControl", - this.$refs.icsImportControl.files[0], - ); - formData.append( - "magicLinkToken", - this.$refs.magicLinkToken.value, - ); - try { - const response = await fetch("/import/event", { - method: "POST", - body: formData, - }); - this.submitting = false; - if (!response.ok) { - if (response.status !== 400) { - this.errors = unexpectedError; - return; - } - const json = await response.json(); - this.errors = json.errors; - return; - } - const json = await response.json(); - window.location.assign(json.url); - } catch (error) { - console.log(error); - this.errors = unexpectedError; - this.submitting = false; - } - }, - }; + return { + data: { + creatorEmail: "", + }, + errors: [], + submitting: false, + async submitForm() { + this.submitting = true; + this.errors = []; + const formData = new FormData(); + for (const [key, value] of Object.entries(this.data)) { + formData.append(key, value); + } + formData.append("icsImportControl", this.$refs.icsImportControl.files[0]); + formData.append("magicLinkToken", this.$refs.magicLinkToken.value); + try { + const response = await fetch("/import/event", { + method: "POST", + body: formData, + }); + this.submitting = false; + if (!response.ok) { + if (response.status !== 400) { + this.errors = unexpectedError; + return; + } + const json = await response.json(); + this.errors = json.errors; + return; + } + const json = await response.json(); + window.location.assign(json.url); + } catch (error) { + console.log(error); + this.errors = unexpectedError; + this.submitting = false; + } + }, + }; } diff --git a/public/site.webmanifest b/public/site.webmanifest index de65106f..c00b1733 100755 --- a/public/site.webmanifest +++ b/public/site.webmanifest @@ -1,19 +1,19 @@ { - "name": "", - "short_name": "", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-256x256.png", - "sizes": "256x256", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-256x256.png", + "sizes": "256x256", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" } diff --git a/src/activitypub.js b/src/activitypub.js index a0d5f6b4..358c7d59 100644 --- a/src/activitypub.js +++ b/src/activitypub.js @@ -11,1310 +11,1221 @@ const siteName = config.general.site_name; const isFederated = config.general.is_federated; import Event from "./models/Event.js"; import { - handlePollResponse, - activityPubContentType, - alternateActivityPubContentType, - getEventId, - getNoteRecipient, + handlePollResponse, + activityPubContentType, + alternateActivityPubContentType, + getEventId, + getNoteRecipient, } from "./lib/activitypub.js"; // This alphabet (used to generate all event, group, etc. IDs) is missing '-' // because ActivityPub doesn't like it in IDs const nanoid = customAlphabet( - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_", - 21, + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_", + 21, ); export function createActivityPubActor( - eventID, - domain, - pubkey, - description, - name, - location, - imageFilename, - startUTC, - endUTC, - timezone, + eventID, + domain, + pubkey, + description, + name, + location, + imageFilename, + startUTC, + endUTC, + timezone, ) { - let actor = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - { - toot: "http://joinmastodon.org/ns#", - discoverable: "toot:discoverable", - indexable: "toot:indexable", - }, - ], - indexable: false, - discoverable: false, - id: `https://${domain}/${eventID}`, - type: "Person", - preferredUsername: `${eventID}`, - inbox: `https://${domain}/activitypub/inbox`, - outbox: `https://${domain}/${eventID}/outbox`, - followers: `https://${domain}/${eventID}/followers`, - summary: `

${description}

`, - name: name, - featured: `https://${domain}/${eventID}/featured`, - publicKey: { - id: `https://${domain}/${eventID}#main-key`, - owner: `https://${domain}/${eventID}`, - publicKeyPem: pubkey, - }, + let actor = { + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + toot: "http://joinmastodon.org/ns#", + discoverable: "toot:discoverable", + indexable: "toot:indexable", + }, + ], + indexable: false, + discoverable: false, + id: `https://${domain}/${eventID}`, + type: "Person", + preferredUsername: `${eventID}`, + inbox: `https://${domain}/activitypub/inbox`, + outbox: `https://${domain}/${eventID}/outbox`, + followers: `https://${domain}/${eventID}/followers`, + summary: `

${description}

`, + name: name, + featured: `https://${domain}/${eventID}/featured`, + publicKey: { + id: `https://${domain}/${eventID}#main-key`, + owner: `https://${domain}/${eventID}`, + publicKeyPem: pubkey, + }, + }; + if (location) { + actor.summary += `

Location: ${location}.

`; + } + let displayDate; + if (startUTC && timezone) { + displayDate = moment.tz(startUTC, timezone).format("D MMMM YYYY h:mm a"); + actor.summary += `

Starting ${displayDate} ${timezone}.

`; + } + if (imageFilename) { + actor.icon = { + type: "Image", + mediaType: "image/jpg", + url: `https://${domain}/events/${imageFilename}`, }; - if (location) { - actor.summary += `

Location: ${location}.

`; - } - let displayDate; - if (startUTC && timezone) { - displayDate = moment - .tz(startUTC, timezone) - .format("D MMMM YYYY h:mm a"); - actor.summary += `

Starting ${displayDate} ${timezone}.

`; - } - if (imageFilename) { - actor.icon = { - type: "Image", - mediaType: "image/jpg", - url: `https://${domain}/events/${imageFilename}`, - }; - } - return JSON.stringify(actor); + } + return JSON.stringify(actor); } export function createActivityPubEvent( - name, - startUTC, - endUTC, - timezone, - description, - location, + name, + startUTC, + endUTC, + timezone, + description, + location, ) { - const guid = crypto.randomBytes(16).toString("hex"); - let eventObject = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - { - toot: "http://joinmastodon.org/ns#", - discoverable: "toot:discoverable", - indexable: "toot:indexable", - }, - ], - indexable: false, - discoverable: false, - id: `https://${domain}/${guid}`, - name: name, - type: "Event", - startTime: moment.tz(startUTC, timezone).format(), - endTime: moment.tz(endUTC, timezone).format(), - content: description, - location: location, - }; - return JSON.stringify(eventObject); + const guid = crypto.randomBytes(16).toString("hex"); + let eventObject = { + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + toot: "http://joinmastodon.org/ns#", + discoverable: "toot:discoverable", + indexable: "toot:indexable", + }, + ], + indexable: false, + discoverable: false, + id: `https://${domain}/${guid}`, + name: name, + type: "Event", + startTime: moment.tz(startUTC, timezone).format(), + endTime: moment.tz(endUTC, timezone).format(), + content: description, + location: location, + }; + return JSON.stringify(eventObject); } export function createFeaturedPost( - eventID, - _name, - _startUTC, - _endUTC, - _timezone, - _description, - _location, + eventID, + _name, + _startUTC, + _endUTC, + _timezone, + _description, + _location, ) { - const featured = { - "@context": "https://www.w3.org/ns/activitystreams", - id: `https://${domain}/${eventID}/m/featuredPost`, - type: "Note", - name: "Test", - cc: "https://www.w3.org/ns/activitystreams#Public", - content: `

This is an event that was posted on ${siteName}. If you follow this account, you'll see updates in your timeline about the event. If your software supports polls, you should get a poll in your DMs asking if you want to RSVP. You can reply and RSVP right from there. If your software has an event calendar built in, you should get an event in your inbox that you can RSVP to like you respond to any event.

For more information on how to interact with this, check out this link.

`, - attributedTo: `https://${domain}/${eventID}`, - }; - return featured; + const featured = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${eventID}/m/featuredPost`, + type: "Note", + name: "Test", + cc: "https://www.w3.org/ns/activitystreams#Public", + content: `

This is an event that was posted on ${siteName}. If you follow this account, you'll see updates in your timeline about the event. If your software supports polls, you should get a poll in your DMs asking if you want to RSVP. You can reply and RSVP right from there. If your software has an event calendar built in, you should get an event in your inbox that you can RSVP to like you respond to any event.

For more information on how to interact with this, check out this link.

`, + attributedTo: `https://${domain}/${eventID}`, + }; + return featured; } export function updateActivityPubEvent( - oldEvent, - name, - startUTC, - endUTC, - timezone, - description, - location, + oldEvent, + name, + startUTC, + endUTC, + timezone, + description, + location, ) { - // we want to persist the old ID no matter what happens to the Event itself - const id = oldEvent.id; - let eventObject = { - "@context": "https://www.w3.org/ns/activitystreams", - id: id, - name: name, - type: "Event", - startTime: moment.tz(startUTC, timezone).format(), - endTime: moment.tz(endUTC, timezone).format(), - content: description, - location: location, - }; - return JSON.stringify(eventObject); + // we want to persist the old ID no matter what happens to the Event itself + const id = oldEvent.id; + let eventObject = { + "@context": "https://www.w3.org/ns/activitystreams", + id: id, + name: name, + type: "Event", + startTime: moment.tz(startUTC, timezone).format(), + endTime: moment.tz(endUTC, timezone).format(), + content: description, + location: location, + }; + return JSON.stringify(eventObject); } export function updateActivityPubActor( - actor, - description, - name, - location, - imageFilename, - startUTC, - _endUTC, - timezone, + actor, + description, + name, + location, + imageFilename, + startUTC, + _endUTC, + timezone, ) { - if (!actor) return; - actor.summary = `

${description}

`; - actor.name = name; - if (location) { - actor.summary += `

Location: ${location}.

`; - } - let displayDate; - if (startUTC && timezone) { - displayDate = moment - .tz(startUTC, timezone) - .format("D MMMM YYYY h:mm a"); - actor.summary += `

Starting ${displayDate} ${timezone}.

`; - } - if (imageFilename) { - actor.icon = { - type: "Image", - mediaType: "image/jpg", - url: `https://${domain}/events/${imageFilename}`, - }; - } - return JSON.stringify(actor); + if (!actor) return; + actor.summary = `

${description}

`; + actor.name = name; + if (location) { + actor.summary += `

Location: ${location}.

`; + } + let displayDate; + if (startUTC && timezone) { + displayDate = moment.tz(startUTC, timezone).format("D MMMM YYYY h:mm a"); + actor.summary += `

Starting ${displayDate} ${timezone}.

`; + } + if (imageFilename) { + actor.icon = { + type: "Image", + mediaType: "image/jpg", + url: `https://${domain}/events/${imageFilename}`, + }; + } + return JSON.stringify(actor); } export function signAndSend(message, eventID, targetDomain, inbox, callback) { - if (!isFederated) return; - let inboxFragment = inbox.replace("https://" + targetDomain, ""); - // get the private key - Event.findOne({ - id: eventID, - }).then((event) => { - if (event) { - const digest = crypto - .createHash("sha256") - .update(JSON.stringify(message)) - .digest("base64"); - const privateKey = event.privateKey; - const signer = crypto.createSign("sha256"); - let d = new Date(); - let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}\ndigest: SHA-256=${digest}`; - signer.update(stringToSign); - signer.end(); - const signature = signer.sign(privateKey); - const signature_b64 = signature.toString("base64"); - const algorithm = "rsa-sha256"; - let header = `keyId="https://${domain}/${eventID}",algorithm="${algorithm}",headers="(request-target) host date digest",signature="${signature_b64}"`; - request( - { - url: inbox, - headers: { - Host: targetDomain, - Date: d.toUTCString(), - Signature: header, - Digest: `SHA-256=${digest}`, - "Content-Type": activityPubContentType, - Accept: activityPubContentType, - "User-Agent": `Gathio - ${domain}`, - }, - method: "POST", - json: true, - body: message, - }, - function (error, _response) { - if (error) { - callback(error, null, 500); - } else { - // Add the message to the database - const newMessage = { - id: message.id, - content: JSON.stringify(message), - }; - Event.findOne( - { - id: eventID, - }, - function (err, event) { - if (err) { - callback(error, null, 500); - return; - } - if (!event) return; - event.activityPubMessages.push(newMessage); - // also add the message's object if it has one - if (message.object && message.object.id) { - event.activityPubMessages.push({ - id: message.object.id, - content: JSON.stringify(message.object), - }); - } - event - .save() - .then(() => { - addToLog( - "addActivityPubMessage", - "success", - "ActivityPubMessage added to event " + - eventID, - ); - callback(null, message.id, 200); - }) - .catch((err) => { - addToLog( - "addActivityPubMessage", - "error", - "Attempt to add ActivityPubMessage to event " + - eventID + - " failed with error: " + - err, - ); - callback(err, null, 500); - }); - }, - ); - } - }, + if (!isFederated) return; + let inboxFragment = inbox.replace("https://" + targetDomain, ""); + // get the private key + Event.findOne({ + id: eventID, + }).then((event) => { + if (event) { + const digest = crypto + .createHash("sha256") + .update(JSON.stringify(message)) + .digest("base64"); + const privateKey = event.privateKey; + const signer = crypto.createSign("sha256"); + let d = new Date(); + let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}\ndigest: SHA-256=${digest}`; + signer.update(stringToSign); + signer.end(); + const signature = signer.sign(privateKey); + const signature_b64 = signature.toString("base64"); + const algorithm = "rsa-sha256"; + let header = `keyId="https://${domain}/${eventID}",algorithm="${algorithm}",headers="(request-target) host date digest",signature="${signature_b64}"`; + request( + { + url: inbox, + headers: { + Host: targetDomain, + Date: d.toUTCString(), + Signature: header, + Digest: `SHA-256=${digest}`, + "Content-Type": activityPubContentType, + Accept: activityPubContentType, + "User-Agent": `Gathio - ${domain}`, + }, + method: "POST", + json: true, + body: message, + }, + function (error, _response) { + if (error) { + callback(error, null, 500); + } else { + // Add the message to the database + const newMessage = { + id: message.id, + content: JSON.stringify(message), + }; + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (err) { + callback(error, null, 500); + return; + } + if (!event) return; + event.activityPubMessages.push(newMessage); + // also add the message's object if it has one + if (message.object && message.object.id) { + event.activityPubMessages.push({ + id: message.object.id, + content: JSON.stringify(message.object), + }); + } + event + .save() + .then(() => { + addToLog( + "addActivityPubMessage", + "success", + "ActivityPubMessage added to event " + eventID, + ); + callback(null, message.id, 200); + }) + .catch((err) => { + addToLog( + "addActivityPubMessage", + "error", + "Attempt to add ActivityPubMessage to event " + + eventID + + " failed with error: " + + err, + ); + callback(err, null, 500); + }); + }, ); - } else { - callback(`No record found for ${eventID}.`, null, 404); - } - }); + } + }, + ); + } else { + callback(`No record found for ${eventID}.`, null, 404); + } + }); } // this function sends something to the timeline of every follower in the followers array // it's also an unlisted public message, meaning non-followers can see the message if they look at // the profile but it doesn't spam federated timelines export function broadcastCreateMessage(apObject, followers, eventID) { - if (!isFederated) return; - let guidCreate = crypto.randomBytes(16).toString("hex"); - Event.findOne( - { - id: eventID, - }, - function (_err, event) { - if (event) { - // iterate over followers - for (const follower of followers) { - let actorId = follower.actorId; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - const followerFound = event.followers.find( - (el) => el.actorId === actorId, - ); - if (followerFound) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - const createMessage = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - ], - id: `https://${domain}/${eventID}/m/${guidCreate}`, - type: "Create", - actor: `https://${domain}/${eventID}`, - to: [actorId], - cc: "https://www.w3.org/ns/activitystreams#Public", - object: apObject, - }; - signAndSend( - createMessage, - eventID, - targetDomain, - inbox, - function (err, resp, status) { - if (err) { - console.log( - `Didn't send to ${actorId}, status ${status} with error ${err}`, - ); - } else { - console.log("sent to", actorId); - } - }, - ); - } else { - console.log(`No follower found with the id ${actorId}`); - } - } // end followers - } // end if event - else { - console.log(`No event found with the id ${eventID}`); - } - }, - ); + if (!isFederated) return; + let guidCreate = crypto.randomBytes(16).toString("hex"); + Event.findOne( + { + id: eventID, + }, + function (_err, event) { + if (event) { + // iterate over followers + for (const follower of followers) { + let actorId = follower.actorId; + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + const followerFound = event.followers.find( + (el) => el.actorId === actorId, + ); + if (followerFound) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + const createMessage = { + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + ], + id: `https://${domain}/${eventID}/m/${guidCreate}`, + type: "Create", + actor: `https://${domain}/${eventID}`, + to: [actorId], + cc: "https://www.w3.org/ns/activitystreams#Public", + object: apObject, + }; + signAndSend( + createMessage, + eventID, + targetDomain, + inbox, + function (err, resp, status) { + if (err) { + console.log( + `Didn't send to ${actorId}, status ${status} with error ${err}`, + ); + } else { + console.log("sent to", actorId); + } + }, + ); + } else { + console.log(`No follower found with the id ${actorId}`); + } + } // end followers + } // end if event + else { + console.log(`No event found with the id ${eventID}`); + } + }, + ); } // sends an Announce for the apObject export function broadcastAnnounceMessage(apObject, followers, eventID) { - if (!isFederated) return; - let guidUpdate = crypto.randomBytes(16).toString("hex"); - Event.findOne( - { - id: eventID, - }, - function (err, event) { - if (event) { - // iterate over followers - for (const follower of followers) { - let actorId = follower.actorId; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - const followerFound = event.followers.find( - (el) => el.actorId === actorId, - ); - if (followerFound) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - const announceMessage = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - ], - id: `https://${domain}/${eventID}/m/${guidUpdate}`, - cc: "https://www.w3.org/ns/activitystreams#Public", - type: "Announce", - actor: `https://${domain}/${eventID}`, - object: apObject, - to: actorId, - }; - signAndSend( - announceMessage, - eventID, - targetDomain, - inbox, - function (err, _resp, status) { - if (err) { - console.log( - `Didn't send to ${actorId}, status ${status} with error ${err}`, - ); - } else { - console.log("sent to", actorId); - } - }, - ); - } else { - console.log(`No follower found with the id ${actorId}`); - } - } // end followers - } // end if event - else { - console.log(`No event found with the id ${eventID}`); - } - }, - ); + if (!isFederated) return; + let guidUpdate = crypto.randomBytes(16).toString("hex"); + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (event) { + // iterate over followers + for (const follower of followers) { + let actorId = follower.actorId; + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + const followerFound = event.followers.find( + (el) => el.actorId === actorId, + ); + if (followerFound) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + const announceMessage = { + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + ], + id: `https://${domain}/${eventID}/m/${guidUpdate}`, + cc: "https://www.w3.org/ns/activitystreams#Public", + type: "Announce", + actor: `https://${domain}/${eventID}`, + object: apObject, + to: actorId, + }; + signAndSend( + announceMessage, + eventID, + targetDomain, + inbox, + function (err, _resp, status) { + if (err) { + console.log( + `Didn't send to ${actorId}, status ${status} with error ${err}`, + ); + } else { + console.log("sent to", actorId); + } + }, + ); + } else { + console.log(`No follower found with the id ${actorId}`); + } + } // end followers + } // end if event + else { + console.log(`No event found with the id ${eventID}`); + } + }, + ); } // sends an Update for the apObject export function broadcastUpdateMessage(apObject, followers, eventID) { - if (!isFederated) return; - let guidUpdate = crypto.randomBytes(16).toString("hex"); - // iterate over followers - Event.findOne( - { + if (!isFederated) return; + let guidUpdate = crypto.randomBytes(16).toString("hex"); + // iterate over followers + Event.findOne( + { + id: eventID, + }, + function (_err, event) { + if (event) { + for (const follower of followers) { + let actorId = follower.actorId; + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + const followerFound = event.followers.find( + (el) => el.actorId === actorId, + ); + if (followerFound) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + const createMessage = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${eventID}/m/${guidUpdate}`, + type: "Update", + actor: `https://${domain}/${eventID}`, + object: apObject, + }; + signAndSend( + createMessage, + eventID, + targetDomain, + inbox, + function (err, _resp, status) { + if (err) { + console.log( + `Didn't send to ${actorId}, status ${status} with error ${err}`, + ); + } else { + console.log("sent to", actorId); + } + }, + ); + } else { + console.log(`No follower found with the id ${actorId}`); + } + } // end followers + } else { + console.log(`No event found with the id ${eventID}`); + } + }, + ); +} + +export function broadcastDeleteMessage(apObject, followers, eventID, callback) { + callback = callback || function () {}; + if (!isFederated) { + callback([]); + return; + } + // we need to build an array of promises for each message we're sending, run Promise.all(), and then that will resolve when every message has been sent (or failed) + // per spec, each promise will execute *as it is built*, which is fine, we just need the guarantee that they are all done + let promises = []; + + let guidUpdate = crypto.randomBytes(16).toString("hex"); + // iterate over followers + for (const follower of followers) { + promises.push( + new Promise((resolve, reject) => { + let actorId = follower.actorId; + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + Event.findOne( + { id: eventID, - }, - function (_err, event) { + }, + function (_err, event) { if (event) { - for (const follower of followers) { - let actorId = follower.actorId; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - const followerFound = event.followers.find( - (el) => el.actorId === actorId, - ); - if (followerFound) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - const createMessage = { - "@context": "https://www.w3.org/ns/activitystreams", - id: `https://${domain}/${eventID}/m/${guidUpdate}`, - type: "Update", - actor: `https://${domain}/${eventID}`, - object: apObject, - }; - signAndSend( - createMessage, - eventID, - targetDomain, - inbox, - function (err, _resp, status) { - if (err) { - console.log( - `Didn't send to ${actorId}, status ${status} with error ${err}`, - ); - } else { - console.log("sent to", actorId); - } - }, - ); + const follower = event.followers.find( + (el) => el.actorId === actorId, + ); + if (follower) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + const createMessage = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${eventID}/m/${guidUpdate}`, + type: "Delete", + actor: `https://${domain}/${eventID}`, + object: apObject, + }; + signAndSend( + createMessage, + eventID, + targetDomain, + inbox, + function (err, _resp, status) { + if (err) { + console.log( + `Didn't send to ${actorId}, status ${status} with error ${err}`, + ); + reject( + `Didn't send to ${actorId}, status ${status} with error ${err}`, + ); } else { - console.log(`No follower found with the id ${actorId}`); + console.log("sent to", actorId); + resolve("sent to", actorId); } - } // end followers + }, + ); + } else { + console.log( + `No follower found with the id ${actorId}`, + null, + 404, + ); + reject(`No follower found with the id ${actorId}`, null, 404); + } } else { - console.log(`No event found with the id ${eventID}`); + console.log(`No event found with the id ${eventID}`, null, 404); + reject(`No event found with the id ${eventID}`, null, 404); } - }, + }, + ); // end event + }), ); -} - -export function broadcastDeleteMessage(apObject, followers, eventID, callback) { - callback = callback || function () {}; - if (!isFederated) { - callback([]); - return; - } - // we need to build an array of promises for each message we're sending, run Promise.all(), and then that will resolve when every message has been sent (or failed) - // per spec, each promise will execute *as it is built*, which is fine, we just need the guarantee that they are all done - let promises = []; - - let guidUpdate = crypto.randomBytes(16).toString("hex"); - // iterate over followers - for (const follower of followers) { - promises.push( - new Promise((resolve, reject) => { - let actorId = follower.actorId; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - Event.findOne( - { - id: eventID, - }, - function (_err, event) { - if (event) { - const follower = event.followers.find( - (el) => el.actorId === actorId, - ); - if (follower) { - const actorJson = JSON.parse( - follower.actorJson, - ); - const inbox = actorJson.inbox; - const createMessage = { - "@context": - "https://www.w3.org/ns/activitystreams", - id: `https://${domain}/${eventID}/m/${guidUpdate}`, - type: "Delete", - actor: `https://${domain}/${eventID}`, - object: apObject, - }; - signAndSend( - createMessage, - eventID, - targetDomain, - inbox, - function (err, _resp, status) { - if (err) { - console.log( - `Didn't send to ${actorId}, status ${status} with error ${err}`, - ); - reject( - `Didn't send to ${actorId}, status ${status} with error ${err}`, - ); - } else { - console.log("sent to", actorId); - resolve("sent to", actorId); - } - }, - ); - } else { - console.log( - `No follower found with the id ${actorId}`, - null, - 404, - ); - reject( - `No follower found with the id ${actorId}`, - null, - 404, - ); - } - } else { - console.log( - `No event found with the id ${eventID}`, - null, - 404, - ); - reject( - `No event found with the id ${eventID}`, - null, - 404, - ); - } - }, - ); // end event - }), - ); - } // end followers + } // end followers - Promise.all(promises.map((p) => p.catch((e) => e))).then((statuses) => { - callback(statuses); - }); + Promise.all(promises.map((p) => p.catch((e) => e))).then((statuses) => { + callback(statuses); + }); } // this sends a message "to:" an individual fediverse user export function sendDirectMessage(apObject, actorId, eventID, callback) { - if (!isFederated) return; - callback = callback || function () {}; - const guidCreate = crypto.randomBytes(16).toString("hex"); - const guidObject = crypto.randomBytes(16).toString("hex"); - let d = new Date(); + if (!isFederated) return; + callback = callback || function () {}; + const guidCreate = crypto.randomBytes(16).toString("hex"); + const guidObject = crypto.randomBytes(16).toString("hex"); + let d = new Date(); - apObject.published = d.toISOString(); - apObject.attributedTo = `https://${domain}/${eventID}`; - apObject.to = actorId; - apObject.id = `https://${domain}/${eventID}/m/${guidObject}`; - apObject.content = decodeURI(apObject.content); + apObject.published = d.toISOString(); + apObject.attributedTo = `https://${domain}/${eventID}`; + apObject.to = actorId; + apObject.id = `https://${domain}/${eventID}/m/${guidObject}`; + apObject.content = decodeURI(apObject.content); - let createMessage = { - "@context": "https://www.w3.org/ns/activitystreams", - id: `https://${domain}/${eventID}/m/${guidCreate}`, - type: "Create", - actor: `https://${domain}/${eventID}`, - to: [actorId], - object: apObject, - }; + let createMessage = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${eventID}/m/${guidCreate}`, + type: "Create", + actor: `https://${domain}/${eventID}`, + to: [actorId], + object: apObject, + }; - let myURL = new URL(actorId); - let targetDomain = myURL.hostname; - // get the inbox - Event.findOne( - { - id: eventID, - }, - function (err, event) { - if (err) { - callback(err, null, 500); - } - if (event) { - const follower = event.followers.find( - (el) => el.actorId === actorId, - ); - if (follower) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - signAndSend( - createMessage, - eventID, - targetDomain, - inbox, - callback, - ); - } else { - callback( - `No follower found with the id ${actorId}`, - null, - 404, - ); - } - } else { - callback(`No event found with the id ${eventID}`, null, 404); - } - }, - ); + let myURL = new URL(actorId); + let targetDomain = myURL.hostname; + // get the inbox + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (err) { + callback(err, null, 500); + } + if (event) { + const follower = event.followers.find((el) => el.actorId === actorId); + if (follower) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + signAndSend(createMessage, eventID, targetDomain, inbox, callback); + } else { + callback(`No follower found with the id ${actorId}`, null, 404); + } + } else { + callback(`No event found with the id ${eventID}`, null, 404); + } + }, + ); } export function sendAcceptMessage(thebody, eventID, targetDomain, callback) { - if (!isFederated) return; - callback = callback || function () {}; - const guid = crypto.randomBytes(16).toString("hex"); - const actorId = thebody.actor; - let message = { - "@context": "https://www.w3.org/ns/activitystreams", - id: `https://${domain}/${guid}`, - type: "Accept", - actor: `https://${domain}/${eventID}`, - object: thebody, - }; - // get the inbox - Event.findOne( - { - id: eventID, - }, - function (err, event) { - if (err) { - callback(err, null, 500); - } - if (event) { - const follower = event.followers.find( - (el) => el.actorId === actorId, - ); - if (follower) { - const actorJson = JSON.parse(follower.actorJson); - const inbox = actorJson.inbox; - signAndSend( - message, - eventID, - targetDomain, - inbox, - callback, - ); - } - } else { - callback(`Could not find event ${eventID}`, null, 404); - } - }, - ); + if (!isFederated) return; + callback = callback || function () {}; + const guid = crypto.randomBytes(16).toString("hex"); + const actorId = thebody.actor; + let message = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${guid}`, + type: "Accept", + actor: `https://${domain}/${eventID}`, + object: thebody, + }; + // get the inbox + Event.findOne( + { + id: eventID, + }, + function (err, event) { + if (err) { + callback(err, null, 500); + } + if (event) { + const follower = event.followers.find((el) => el.actorId === actorId); + if (follower) { + const actorJson = JSON.parse(follower.actorJson); + const inbox = actorJson.inbox; + signAndSend(message, eventID, targetDomain, inbox, callback); + } + } else { + callback(`Could not find event ${eventID}`, null, 404); + } + }, + ); } function _handleFollow(req, res) { - const myURL = new URL(req.body.actor); - let targetDomain = myURL.hostname; - let eventID = getEventId(req.body.object); - // Add the user to the DB of accounts that follow the account - // get the follower's username - request( + const myURL = new URL(req.body.actor); + let targetDomain = myURL.hostname; + let eventID = getEventId(req.body.object); + // Add the user to the DB of accounts that follow the account + // get the follower's username + request( + { + url: req.body.actor, + headers: { + Accept: activityPubContentType, + "Content-Type": activityPubContentType, + "User-Agent": `Gathio - ${domain}`, + }, + }, + function (_error, _response, body) { + body = JSON.parse(body); + const name = body.preferredUsername || body.name || body.attributedTo; + const newFollower = { + actorId: req.body.actor, + followId: req.body.id, + name: name, + actorJson: JSON.stringify(body), + }; + Event.findOne( { - url: req.body.actor, - headers: { - Accept: activityPubContentType, - "Content-Type": activityPubContentType, - "User-Agent": `Gathio - ${domain}`, - }, + id: eventID, }, - function (_error, _response, body) { - body = JSON.parse(body); - const name = - body.preferredUsername || body.name || body.attributedTo; - const newFollower = { - actorId: req.body.actor, - followId: req.body.id, - name: name, - actorJson: JSON.stringify(body), - }; - Event.findOne( - { - id: eventID, - }, - function (_err, event) { - // if this account is NOT already in our followers list, add it - if ( - event && - !event.followers - .map((el) => el.actorId) - .includes(req.body.actor) - ) { - event.followers.push(newFollower); - event - .save() - .then(() => { - addToLog( - "addEventFollower", - "success", - "Follower added to event " + eventID, - ); - // Accept the follow request - sendAcceptMessage( - req.body, - eventID, - targetDomain, - function (err, _resp, status) { - if (err) { - console.log( - `Didn't send Accept to ${req.body.actor}, status ${status} with error ${err}`, - ); - } else { - console.log( - "sent Accept to", - req.body.actor, - ); - // ALSO send an ActivityPub Event activity since this person is "interested" in the event, as indicated by the Follow - const jsonEventObject = JSON.parse( - event.activityPubEvent, - ); - // send direct message to user - sendDirectMessage( - jsonEventObject, - newFollower.actorId, - event.id, - ); - - // if users can self-RSVP, send a Question to the new follower - if (event.usersCanAttend) { - const jsonObject = { - "@context": - "https://www.w3.org/ns/activitystreams", - name: `RSVP to ${event.name}`, - type: "Question", - content: `@${name} Will you attend ${event.name}?`, - oneOf: [ - { - type: "Note", - name: "Yes, and show me in the public list", - replies: { - type: "Collection", - totalItems: 0, - }, - }, - { - type: "Note", - name: "Yes, but hide me from the public list", - replies: { - type: "Collection", - totalItems: 0, - }, - }, - { - type: "Note", - name: "No", - replies: { - type: "Collection", - totalItems: 0, - }, - }, - ], - endTime: - event.start.toISOString(), - tag: [ - { - type: "Mention", - href: req.body - .actor, - name: name, - }, - ], - }; - // send direct message to user - sendDirectMessage( - jsonObject, - req.body.actor, - eventID, - function ( - error, - response, - statuscode, - ) { - if (error) { - console.log( - "Error sending direct message:", - error, - ); - return res - .status( - statuscode, - ) - .json(error); - } else { - return res - .status( - statuscode, - ) - .json({ - messageid: - response, - }); - } - }, - ); - } - } - }, - ); - }) - .catch((err) => { - addToLog( - "addEventFollower", - "error", - "Attempt to add follower to event " + - eventID + - " failed with error: " + - err, - ); - return res - .status(500) - .send( - "Database error, please try again :(", - ); - }); + function (_err, event) { + // if this account is NOT already in our followers list, add it + if ( + event && + !event.followers.map((el) => el.actorId).includes(req.body.actor) + ) { + event.followers.push(newFollower); + event + .save() + .then(() => { + addToLog( + "addEventFollower", + "success", + "Follower added to event " + eventID, + ); + // Accept the follow request + sendAcceptMessage( + req.body, + eventID, + targetDomain, + function (err, _resp, status) { + if (err) { + console.log( + `Didn't send Accept to ${req.body.actor}, status ${status} with error ${err}`, + ); } else { - // this person is already a follower so just say "ok" - return res.sendStatus(200); + console.log("sent Accept to", req.body.actor); + // ALSO send an ActivityPub Event activity since this person is "interested" in the event, as indicated by the Follow + const jsonEventObject = JSON.parse( + event.activityPubEvent, + ); + // send direct message to user + sendDirectMessage( + jsonEventObject, + newFollower.actorId, + event.id, + ); + + // if users can self-RSVP, send a Question to the new follower + if (event.usersCanAttend) { + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + name: `RSVP to ${event.name}`, + type: "Question", + content: `@${name} Will you attend ${event.name}?`, + oneOf: [ + { + type: "Note", + name: "Yes, and show me in the public list", + replies: { + type: "Collection", + totalItems: 0, + }, + }, + { + type: "Note", + name: "Yes, but hide me from the public list", + replies: { + type: "Collection", + totalItems: 0, + }, + }, + { + type: "Note", + name: "No", + replies: { + type: "Collection", + totalItems: 0, + }, + }, + ], + endTime: event.start.toISOString(), + tag: [ + { + type: "Mention", + href: req.body.actor, + name: name, + }, + ], + }; + // send direct message to user + sendDirectMessage( + jsonObject, + req.body.actor, + eventID, + function (error, response, statuscode) { + if (error) { + console.log( + "Error sending direct message:", + error, + ); + return res.status(statuscode).json(error); + } else { + return res.status(statuscode).json({ + messageid: response, + }); + } + }, + ); + } } - }, - ); + }, + ); + }) + .catch((err) => { + addToLog( + "addEventFollower", + "error", + "Attempt to add follower to event " + + eventID + + " failed with error: " + + err, + ); + return res + .status(500) + .send("Database error, please try again :("); + }); + } else { + // this person is already a follower so just say "ok" + return res.sendStatus(200); + } }, - ); //end request + ); + }, + ); //end request } function _handleUndoFollow(req, res) { - // get the record of all followers for this account - const eventID = req.body.object.object.replace(`https://${domain}/`, ""); - Event.findOne( - { - id: eventID, - }, - function (_err, event) { - if (!event) return; - // check to see if the Follow object's id matches the id we have on record - // is this even someone who follows us - const indexOfFollower = event.followers.findIndex( - (el) => el.actorId === req.body.object.actor, - ); - if (indexOfFollower !== -1) { - // does the id we have match the id we are being given - if ( - event.followers[indexOfFollower].followId === - req.body.object.id - ) { - // we have a match and can trust the Undo! remove this person from the followers list - event.followers.splice(indexOfFollower, 1); - event - .save() - .then(() => { - addToLog( - "removeEventFollower", - "success", - "Follower removed from event " + eventID, - ); - return res.sendStatus(200); - }) - .catch((err) => { - addToLog( - "removeEventFollower", - "error", - "Attempt to remove follower from event " + - eventID + - " failed with error: " + - err, - ); - return res.send( - "Database error, please try again :(", - ); - }); - } - } - }, - ); + // get the record of all followers for this account + const eventID = req.body.object.object.replace(`https://${domain}/`, ""); + Event.findOne( + { + id: eventID, + }, + function (_err, event) { + if (!event) return; + // check to see if the Follow object's id matches the id we have on record + // is this even someone who follows us + const indexOfFollower = event.followers.findIndex( + (el) => el.actorId === req.body.object.actor, + ); + if (indexOfFollower !== -1) { + // does the id we have match the id we are being given + if (event.followers[indexOfFollower].followId === req.body.object.id) { + // we have a match and can trust the Undo! remove this person from the followers list + event.followers.splice(indexOfFollower, 1); + event + .save() + .then(() => { + addToLog( + "removeEventFollower", + "success", + "Follower removed from event " + eventID, + ); + return res.sendStatus(200); + }) + .catch((err) => { + addToLog( + "removeEventFollower", + "error", + "Attempt to remove follower from event " + + eventID + + " failed with error: " + + err, + ); + return res.send("Database error, please try again :("); + }); + } + } + }, + ); } function _handleAcceptEvent(req, res) { - let { actor } = req.body; - const recipient = getNoteRecipient(req.body); - if (!recipient) { - return res.status(400).send("No recipient found in the object"); - } - const eventID = getEventId(recipient); - if (!eventID) { - return res.status(400).send("No event ID found in the recipient"); - } - Event.findOne( - { - id: eventID, - }, - function (_err, event) { - if (!event) return; - // does the id we got match the id of a thing we sent out - const message = event.activityPubMessages.find( - (el) => el.id === req.body.object, - ); - if (message) { - // it's a match - request( - { - url: actor, - headers: { - Accept: activityPubContentType, - "Content-Type": activityPubContentType, - "User-Agent": `Gathio - ${domain}`, - }, - }, - function (_error, _response, body) { - body = JSON.parse(body); - // if this account is NOT already in our attendees list, add it - if ( - !event.attendees.map((el) => el.id).includes(actor) - ) { - const attendeeName = - body.preferredUsername || body.name || actor; - const newAttendee = { - name: attendeeName, - status: "attending", - id: actor, - number: 1, - }; - event.attendees.push(newAttendee); - event - .save() - .then((fullEvent) => { - addToLog( - "addEventAttendee", - "success", - "Attendee added to event " + - req.params.eventID, - ); - // get the new attendee with its hidden id from the full event - let fullAttendee = fullEvent.attendees.find( - (el) => el.id === actor, - ); - // send a "click here to remove yourself" link back to the user as a DM - const jsonObject = { - "@context": - "https://www.w3.org/ns/activitystreams", - name: `RSVP to ${event.name}`, - type: "Note", - content: `@${newAttendee.name} Thanks for RSVPing! You can remove yourself from the RSVP list by clicking here: https://${domain}/oneclickunattendevent/${event.id}/${fullAttendee._id}`, - tag: [ - { - type: "Mention", - href: newAttendee.id, - name: newAttendee.name, - }, - ], - }; - // send direct message to user - sendDirectMessage( - jsonObject, - newAttendee.id, - event.id, - ); - return res.sendStatus(200); - }) - .catch((err) => { - addToLog( - "addEventAttendee", - "error", - "Attempt to add attendee to event " + - req.params.eventID + - " failed with error: " + - err, - ); - return res - .status(500) - .send( - "Database error, please try again :(", - ); - }); - } else { - // it's a duplicate and this person is already rsvped so just say OK - return res - .status(200) - .send("Attendee is already registered."); - } - }, - ); + let { actor } = req.body; + const recipient = getNoteRecipient(req.body); + if (!recipient) { + return res.status(400).send("No recipient found in the object"); + } + const eventID = getEventId(recipient); + if (!eventID) { + return res.status(400).send("No event ID found in the recipient"); + } + Event.findOne( + { + id: eventID, + }, + function (_err, event) { + if (!event) return; + // does the id we got match the id of a thing we sent out + const message = event.activityPubMessages.find( + (el) => el.id === req.body.object, + ); + if (message) { + // it's a match + request( + { + url: actor, + headers: { + Accept: activityPubContentType, + "Content-Type": activityPubContentType, + "User-Agent": `Gathio - ${domain}`, + }, + }, + function (_error, _response, body) { + body = JSON.parse(body); + // if this account is NOT already in our attendees list, add it + if (!event.attendees.map((el) => el.id).includes(actor)) { + const attendeeName = body.preferredUsername || body.name || actor; + const newAttendee = { + name: attendeeName, + status: "attending", + id: actor, + number: 1, + }; + event.attendees.push(newAttendee); + event + .save() + .then((fullEvent) => { + addToLog( + "addEventAttendee", + "success", + "Attendee added to event " + req.params.eventID, + ); + // get the new attendee with its hidden id from the full event + let fullAttendee = fullEvent.attendees.find( + (el) => el.id === actor, + ); + // send a "click here to remove yourself" link back to the user as a DM + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + name: `RSVP to ${event.name}`, + type: "Note", + content: `@${newAttendee.name} Thanks for RSVPing! You can remove yourself from the RSVP list by clicking here: https://${domain}/oneclickunattendevent/${event.id}/${fullAttendee._id}`, + tag: [ + { + type: "Mention", + href: newAttendee.id, + name: newAttendee.name, + }, + ], + }; + // send direct message to user + sendDirectMessage(jsonObject, newAttendee.id, event.id); + return res.sendStatus(200); + }) + .catch((err) => { + addToLog( + "addEventAttendee", + "error", + "Attempt to add attendee to event " + + req.params.eventID + + " failed with error: " + + err, + ); + return res + .status(500) + .send("Database error, please try again :("); + }); + } else { + // it's a duplicate and this person is already rsvped so just say OK + return res.status(200).send("Attendee is already registered."); } - }, - ); + }, + ); + } + }, + ); } function _handleUndoAcceptEvent(req, _res) { - let { to, actor } = req.body; - if (Array.isArray(to)) { - to = to[0]; - } - const eventID = to.replace(`https://${domain}/`, ""); - Event.findOne( - { - id: eventID, - }, - function (_err, event) { - if (!event) return; - // does the id we got match the id of a thing we sent out - const message = event.activityPubMessages.find( - (el) => el.id === req.body.object.object, - ); - if (message) { - // it's a match - Event.updateOne( - { id: eventID }, - { $pull: { attendees: { id: actor } } }, - ).then(() => { - addToLog( - "oneClickUnattend", - "success", - "Attendee removed via one click unattend " + - req.params.eventID, - ); - }); - } - }, - ); + let { to, actor } = req.body; + if (Array.isArray(to)) { + to = to[0]; + } + const eventID = to.replace(`https://${domain}/`, ""); + Event.findOne( + { + id: eventID, + }, + function (_err, event) { + if (!event) return; + // does the id we got match the id of a thing we sent out + const message = event.activityPubMessages.find( + (el) => el.id === req.body.object.object, + ); + if (message) { + // it's a match + Event.updateOne( + { id: eventID }, + { $pull: { attendees: { id: actor } } }, + ).then(() => { + addToLog( + "oneClickUnattend", + "success", + "Attendee removed via one click unattend " + req.params.eventID, + ); + }); + } + }, + ); } function _handleDelete(req, res) { - // find all events with comments from the author - Event.find( - { - "comments.actorId": req.body.actor, - }, - function (err, events) { - if (!events) { - return res.sendStatus(404); - } + // find all events with comments from the author + Event.find( + { + "comments.actorId": req.body.actor, + }, + function (err, events) { + if (!events) { + return res.sendStatus(404); + } - // find the event with THIS comment from the author - let eventWithComment = events.find((event) => { - let comments = event.comments; - return comments.find((comment) => { - if (!comment.activityJson) { - return false; - } - return ( - JSON.parse(comment.activityJson).object.id === - req.body.object.id - ); - }); - }); + // find the event with THIS comment from the author + let eventWithComment = events.find((event) => { + let comments = event.comments; + return comments.find((comment) => { + if (!comment.activityJson) { + return false; + } + return ( + JSON.parse(comment.activityJson).object.id === req.body.object.id + ); + }); + }); - if (!eventWithComment) { - return res.sendStatus(404); - } + if (!eventWithComment) { + return res.sendStatus(404); + } - // delete the comment - // find the index of the comment, it should have an activityJson field because from an AP server you can only delete an AP-originated comment (and of course it needs to be yours) - let indexOfComment = eventWithComment.comments.findIndex( - (comment) => { - return ( - comment.activityJson && - JSON.parse(comment.activityJson).object.id === - req.body.object.id - ); - }, - ); - eventWithComment.comments.splice(indexOfComment, 1); - eventWithComment - .save() - .then(() => { + // delete the comment + // find the index of the comment, it should have an activityJson field because from an AP server you can only delete an AP-originated comment (and of course it needs to be yours) + let indexOfComment = eventWithComment.comments.findIndex((comment) => { + return ( + comment.activityJson && + JSON.parse(comment.activityJson).object.id === req.body.object.id + ); + }); + eventWithComment.comments.splice(indexOfComment, 1); + eventWithComment + .save() + .then(() => { + addToLog( + "deleteComment", + "success", + "Comment deleted from event " + eventWithComment.id, + ); + return res.sendStatus(200); + }) + .catch((err) => { + addToLog( + "deleteComment", + "error", + "Attempt to delete comment " + + req.body.object.id + + "from event " + + eventWithComment.id + + " failed with error: " + + err, + ); + return res.sendStatus(500); + }); + }, + ); +} + +function _handleCreateNoteComment(req, res) { + // figure out what this is in reply to -- it should be addressed specifically to us + let { to, cc } = req.body.object; + // normalize cc into an array + if (typeof cc === "string") { + cc = [cc]; + } + // normalize to into an array + if (typeof to === "string") { + to = [to]; + } + + // if this is a public message (in the to or cc fields) + if ( + to.includes("https://www.w3.org/ns/activitystreams#Public") || + (Array.isArray(cc) && + cc.includes("https://www.w3.org/ns/activitystreams#Public")) + ) { + // figure out which event(s) of ours it was addressing + // Mastodon seems to put the event ID in the to field, Pleroma in the cc field + // This is because ActivityPub is a mess (love you ActivityPub) + let ourEvents = cc + .concat(to) + .filter((el) => el.includes(`https://${domain}/`)) + .map((el) => el.replace(`https://${domain}/`, "")); + // comments should only be on one event. if more than one, ignore (spam, probably) + if (ourEvents.length === 1) { + let eventID = ourEvents[0]; + // add comment + let commentID = nanoid(); + // get the actor for the commenter + request( + { + url: req.body.actor, + headers: { + Accept: activityPubContentType, + "Content-Type": activityPubContentType, + "User-Agent": `Gathio - ${domain}`, + }, + }, + function (error, _response, actor) { + if (!error) { + const parsedActor = JSON.parse(actor); + const name = + parsedActor.preferredUsername || + parsedActor.name || + req.body.actor; + const newComment = { + id: commentID, + actorId: req.body.actor, + activityId: req.body.object.id, + author: name, + content: sanitizeHtml(req.body.object.content, { + allowedTags: [], + allowedAttributes: {}, + }).replace("@" + eventID, ""), + timestamp: moment(), + activityJson: JSON.stringify(req.body), + actorJson: actor, + }; + + Event.findOne( + { + id: eventID, + }, + function (_err, event) { + if (!event) { + return res.sendStatus(404); + } + if (!event.usersCanComment) { + return res.sendStatus(200); + } + event.comments.push(newComment); + event + .save() + .then(() => { addToLog( - "deleteComment", - "success", - "Comment deleted from event " + eventWithComment.id, + "addEventComment", + "success", + "Comment added to event " + eventID, + ); + // const guidObject = crypto + // .randomBytes(16) + // .toString("hex"); + const jsonObject = req.body.object; + jsonObject.attributedTo = newComment.actorId; + broadcastAnnounceMessage( + jsonObject, + event.followers, + eventID, ); return res.sendStatus(200); - }) - .catch((err) => { + }) + .catch((err) => { addToLog( - "deleteComment", - "error", - "Attempt to delete comment " + - req.body.object.id + - "from event " + - eventWithComment.id + - " failed with error: " + - err, + "addEventComment", + "error", + "Attempt to add comment to event " + + eventID + + " failed with error: " + + err, ); - return res.sendStatus(500); - }); + res + .status(500) + .send("Database error, please try again :(" + err); + }); + }, + ); + } }, - ); + ); + } // end ourevent + } // end public message } -function _handleCreateNoteComment(req, res) { - // figure out what this is in reply to -- it should be addressed specifically to us - let { to, cc } = req.body.object; - // normalize cc into an array - if (typeof cc === "string") { - cc = [cc]; +export function processInbox(req, res) { + if (!isFederated) return res.sendStatus(404); + try { + // if a Follow activity hits the inbox + if (typeof req.body.object === "string" && req.body.type === "Follow") { + console.log("Sending to _handleFollow"); + _handleFollow(req, res); } - // normalize to into an array - if (typeof to === "string") { - to = [to]; + // if an Undo activity with a Follow object hits the inbox + else if ( + req.body && + req.body.type === "Undo" && + req.body.object && + req.body.object.type === "Follow" + ) { + console.log("Sending to _handleUndoFollow"); + _handleUndoFollow(req, res); } - - // if this is a public message (in the to or cc fields) - if ( - to.includes("https://www.w3.org/ns/activitystreams#Public") || - (Array.isArray(cc) && - cc.includes("https://www.w3.org/ns/activitystreams#Public")) + // if an Accept activity with the id of the Event we sent out hits the inbox, it is an affirmative RSVP + else if ( + req.body && + req.body.type === "Accept" && + req.body.object && + typeof req.body.object === "string" ) { - // figure out which event(s) of ours it was addressing - // Mastodon seems to put the event ID in the to field, Pleroma in the cc field - // This is because ActivityPub is a mess (love you ActivityPub) - let ourEvents = cc - .concat(to) - .filter((el) => el.includes(`https://${domain}/`)) - .map((el) => el.replace(`https://${domain}/`, "")); - // comments should only be on one event. if more than one, ignore (spam, probably) - if (ourEvents.length === 1) { - let eventID = ourEvents[0]; - // add comment - let commentID = nanoid(); - // get the actor for the commenter - request( - { - url: req.body.actor, - headers: { - Accept: activityPubContentType, - "Content-Type": activityPubContentType, - "User-Agent": `Gathio - ${domain}`, - }, - }, - function (error, _response, actor) { - if (!error) { - const parsedActor = JSON.parse(actor); - const name = - parsedActor.preferredUsername || - parsedActor.name || - req.body.actor; - const newComment = { - id: commentID, - actorId: req.body.actor, - activityId: req.body.object.id, - author: name, - content: sanitizeHtml(req.body.object.content, { - allowedTags: [], - allowedAttributes: {}, - }).replace("@" + eventID, ""), - timestamp: moment(), - activityJson: JSON.stringify(req.body), - actorJson: actor, - }; - - Event.findOne( - { - id: eventID, - }, - function (_err, event) { - if (!event) { - return res.sendStatus(404); - } - if (!event.usersCanComment) { - return res.sendStatus(200); - } - event.comments.push(newComment); - event - .save() - .then(() => { - addToLog( - "addEventComment", - "success", - "Comment added to event " + eventID, - ); - // const guidObject = crypto - // .randomBytes(16) - // .toString("hex"); - const jsonObject = req.body.object; - jsonObject.attributedTo = - newComment.actorId; - broadcastAnnounceMessage( - jsonObject, - event.followers, - eventID, - ); - return res.sendStatus(200); - }) - .catch((err) => { - addToLog( - "addEventComment", - "error", - "Attempt to add comment to event " + - eventID + - " failed with error: " + - err, - ); - res.status(500).send( - "Database error, please try again :(" + - err, - ); - }); - }, - ); - } - }, - ); - } // end ourevent - } // end public message -} - -export function processInbox(req, res) { - if (!isFederated) return res.sendStatus(404); - try { - // if a Follow activity hits the inbox - if (typeof req.body.object === "string" && req.body.type === "Follow") { - console.log("Sending to _handleFollow"); - _handleFollow(req, res); - } - // if an Undo activity with a Follow object hits the inbox - else if ( - req.body && - req.body.type === "Undo" && - req.body.object && - req.body.object.type === "Follow" - ) { - console.log("Sending to _handleUndoFollow"); - _handleUndoFollow(req, res); - } - // if an Accept activity with the id of the Event we sent out hits the inbox, it is an affirmative RSVP - else if ( - req.body && - req.body.type === "Accept" && - req.body.object && - typeof req.body.object === "string" - ) { - console.log("Sending to _handleAcceptEvent"); - _handleAcceptEvent(req, res); - } - // if an Undo activity containing an Accept containing the id of the Event we sent out hits the inbox, it is an undo RSVP - else if ( - req.body && - req.body.type === "Undo" && - req.body.object && - req.body.object.object && - typeof req.body.object.object === "string" && - req.body.object.type === "Accept" - ) { - console.log("Sending to _handleUndoAcceptEvent"); - _handleUndoAcceptEvent(req, res); - } - // if a Create activity with a Note object hits the inbox, and it's a reply, it might be a vote in a poll - else if ( - req.body && - req.body.type === "Create" && - req.body.object && - req.body.object.type === "Note" && - req.body.object.inReplyTo && - req.body.object.to - ) { - handlePollResponse(req, res); - } - // if a Delete activity hits the inbox, it might a deletion of a comment - else if (req.body && req.body.type === "Delete") { - console.log("Sending to _handleDelete"); - _handleDelete(req, res); - } - // if we are CC'ed on a public or unlisted Create/Note, then this is a comment to us we should boost (Announce) to our followers - else if ( - req.body && - req.body.type === "Create" && - req.body.object && - req.body.object.type === "Note" && - req.body.object.to - ) { - console.log("Sending to _handleCreateNoteComment"); - _handleCreateNoteComment(req, res); - } // CC'ed - else { - console.log("No action taken"); - } - } catch (e) { - console.log("Error in processing inbox:", e); + console.log("Sending to _handleAcceptEvent"); + _handleAcceptEvent(req, res); + } + // if an Undo activity containing an Accept containing the id of the Event we sent out hits the inbox, it is an undo RSVP + else if ( + req.body && + req.body.type === "Undo" && + req.body.object && + req.body.object.object && + typeof req.body.object.object === "string" && + req.body.object.type === "Accept" + ) { + console.log("Sending to _handleUndoAcceptEvent"); + _handleUndoAcceptEvent(req, res); + } + // if a Create activity with a Note object hits the inbox, and it's a reply, it might be a vote in a poll + else if ( + req.body && + req.body.type === "Create" && + req.body.object && + req.body.object.type === "Note" && + req.body.object.inReplyTo && + req.body.object.to + ) { + handlePollResponse(req, res); + } + // if a Delete activity hits the inbox, it might a deletion of a comment + else if (req.body && req.body.type === "Delete") { + console.log("Sending to _handleDelete"); + _handleDelete(req, res); } + // if we are CC'ed on a public or unlisted Create/Note, then this is a comment to us we should boost (Announce) to our followers + else if ( + req.body && + req.body.type === "Create" && + req.body.object && + req.body.object.type === "Note" && + req.body.object.to + ) { + console.log("Sending to _handleCreateNoteComment"); + _handleCreateNoteComment(req, res); + } // CC'ed + else { + console.log("No action taken"); + } + } catch (e) { + console.log("Error in processing inbox:", e); + } } export function createWebfinger(eventID, domain) { - return { - subject: `acct:${eventID}@${domain}`, + return { + subject: `acct:${eventID}@${domain}`, - links: [ - { - rel: "self", - type: alternateActivityPubContentType, - href: `https://${domain}/${eventID}`, - }, - ], - }; + links: [ + { + rel: "self", + type: alternateActivityPubContentType, + href: `https://${domain}/${eventID}`, + }, + ], + }; } diff --git a/src/app.ts b/src/app.ts index 2b1e5498..12c06e03 100755 --- a/src/app.ts +++ b/src/app.ts @@ -29,144 +29,140 @@ const config = getConfig(); // function to construct __dirname with ES module const getLocalesPath = () => { - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - return path.join(__dirname, "..", "locales"); + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + return path.join(__dirname, "..", "locales"); }; async function initializeApp() { - // Cookies // - app.use(cookieParser()); - - // i18next configuration - await i18next - .use(Backend) - .use(LanguageDetector) - .init({ - backend: { - loadPath: path.join(getLocalesPath(), "{{lng}}.json"), - }, - fallbackLng: "en", - preload: ["en", "ja", "de"], - supportedLngs: ["en", "ja", "de"], - nonExplicitSupportedLngs: true, - load: "languageOnly", - debug: false, - detection: { - order: ["header", "cookie"], - lookupHeader: "accept-language", - lookupCookie: "i18next", - caches: ["cookie"], - }, - interpolation: { - escapeValue: false, - }, - }); - - app.use(handle(i18next)); - - // to Switch language - app.use((req, _res, next) => { - const currentLanguage = i18next.language; - i18next.changeLanguage(req.language); - const newLanguage = i18next.language; - if (process.env.DEBUG_I18N) { - console.log("Language Change:", { - header: req.headers["accept-language"], - detected: req.language, - currentLanguage: currentLanguage, - newLanguage: newLanguage, - }); - } - next(); + // Cookies // + app.use(cookieParser()); + + // i18next configuration + await i18next + .use(Backend) + .use(LanguageDetector) + .init({ + backend: { + loadPath: path.join(getLocalesPath(), "{{lng}}.json"), + }, + fallbackLng: "en", + preload: ["en", "ja", "de"], + supportedLngs: ["en", "ja", "de"], + nonExplicitSupportedLngs: true, + load: "languageOnly", + debug: false, + detection: { + order: ["header", "cookie"], + lookupHeader: "accept-language", + lookupCookie: "i18next", + caches: ["cookie"], + }, + interpolation: { + escapeValue: false, + }, }); - if (process.env.DEBUG_I18N) { - app.use((req, _res, next) => { - console.log("Language Detection:", { - header: req.headers["accept-language"], - detected: req.language, - i18next: i18next.language, - }); - next(); - }); - } - - // View engine // - const hbsInstance = createHandlebars({ - defaultLayout: "main", - partialsDir: ["views/partials/"], - layoutsDir: "views/layouts/", - helpers: { - // add i18next helpers - ...getI18nHelpers(), - plural: function (key: string, count: number) { - // Register the plural helper - const translation = i18next.t(key, { count: count }); - return translation; - }, - json: function (context: object) { - return JSON.stringify(context); - }, - }, - }); + app.use(handle(i18next)); - const emailService = new EmailService(config, hbsInstance); - emailService.verify(); - - app.use( - ( - req: express.Request, - _: express.Response, - next: express.NextFunction, - ) => { - req.hbsInstance = hbsInstance; - req.emailService = emailService; - next(); - return; - }, - ); - - // View engine // - app.engine("handlebars", hbsInstance.engine); - app.set("view engine", "handlebars"); - app.set("hbsInstance", hbsInstance); - - // calling i18nextHelper - if (typeof handlebarsI18next === "function") { - handlebarsI18next(hbsInstance.handlebars, i18next); - } else if (typeof handlebarsI18next.default === "function") { - handlebarsI18next.default(hbsInstance.handlebars, i18next); - } else { - console.error("handlebars-i18next helper is not properly loaded"); + // to Switch language + app.use((req, _res, next) => { + const currentLanguage = i18next.language; + i18next.changeLanguage(req.language); + const newLanguage = i18next.language; + if (process.env.DEBUG_I18N) { + console.log("Language Change:", { + header: req.headers["accept-language"], + detected: req.language, + currentLanguage: currentLanguage, + newLanguage: newLanguage, + }); } + next(); + }); - i18next.on("languageChanged", function (lng) { - moment.locale(lng); + if (process.env.DEBUG_I18N) { + app.use((req, _res, next) => { + console.log("Language Detection:", { + header: req.headers["accept-language"], + detected: req.language, + i18next: i18next.language, + }); + next(); }); - - app.engine("handlebars", hbsInstance.engine); - app.set("view engine", "handlebars"); - app.set("hbsInstance", hbsInstance); - - // Static files // - app.use(express.static("public")); - - // Body parser // - // body-parser middleware does not recognise ld+json or activitypub+json - // as JSON content types; the workaround is to use a wildcard. - // (cf. https://github.com/expressjs/body-parser/issues/519#issuecomment-2006306234) - app.use(express.json({ type: [ "application/*+json", "application/json" ] })); - app.use(express.urlencoded({ extended: true })); - - // Router // - app.use("/", staticPages); - app.use("/", frontend); - app.use("/", activitypub); - app.use("/", event); - app.use("/", group); - app.use("/", magicLink); - app.use("/", routes); + } + + // View engine // + const hbsInstance = createHandlebars({ + defaultLayout: "main", + partialsDir: ["views/partials/"], + layoutsDir: "views/layouts/", + helpers: { + // add i18next helpers + ...getI18nHelpers(), + plural: function (key: string, count: number) { + // Register the plural helper + const translation = i18next.t(key, { count: count }); + return translation; + }, + json: function (context: object) { + return JSON.stringify(context); + }, + }, + }); + + const emailService = new EmailService(config, hbsInstance); + emailService.verify(); + + app.use( + (req: express.Request, _: express.Response, next: express.NextFunction) => { + req.hbsInstance = hbsInstance; + req.emailService = emailService; + next(); + return; + }, + ); + + // View engine // + app.engine("handlebars", hbsInstance.engine); + app.set("view engine", "handlebars"); + app.set("hbsInstance", hbsInstance); + + // calling i18nextHelper + if (typeof handlebarsI18next === "function") { + handlebarsI18next(hbsInstance.handlebars, i18next); + } else if (typeof handlebarsI18next.default === "function") { + handlebarsI18next.default(hbsInstance.handlebars, i18next); + } else { + console.error("handlebars-i18next helper is not properly loaded"); + } + + i18next.on("languageChanged", function (lng) { + moment.locale(lng); + }); + + app.engine("handlebars", hbsInstance.engine); + app.set("view engine", "handlebars"); + app.set("hbsInstance", hbsInstance); + + // Static files // + app.use(express.static("public")); + + // Body parser // + // body-parser middleware does not recognise ld+json or activitypub+json + // as JSON content types; the workaround is to use a wildcard. + // (cf. https://github.com/expressjs/body-parser/issues/519#issuecomment-2006306234) + app.use(express.json({ type: ["application/*+json", "application/json"] })); + app.use(express.urlencoded({ extended: true })); + + // Router // + app.use("/", staticPages); + app.use("/", frontend); + app.use("/", activitypub); + app.use("/", event); + app.use("/", group); + app.use("/", magicLink); + app.use("/", routes); } initializeApp().catch(console.error); diff --git a/src/helpers.ts b/src/helpers.ts index 42229d3c..ff071199 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,7 +1,7 @@ -import moment from 'moment-timezone'; -import icalGenerator from 'ical-generator'; -import i18next from 'i18next'; -import handlebars from 'handlebars'; +import moment from "moment-timezone"; +import icalGenerator from "ical-generator"; +import i18next from "i18next"; +import handlebars from "handlebars"; import Log from "./models/Log.js"; import { getConfig } from "./lib/config.js"; import { IEvent } from "./models/Event.js"; @@ -23,15 +23,16 @@ export function addToLog(process: string, status: string, message: string) { }); } -export function exportIcal(events: IEvent | IEvent[], calendarName?: string) { // Ical -> ICal +export function exportIcal(events: IEvent | IEvent[], calendarName?: string) { + // Ical -> ICal // Create a new icalGenerator... generator const cal = icalGenerator({ name: calendarName || siteName, - timezone: 'UTC' + timezone: "UTC", }); const eventArray = Array.isArray(events) ? events : [events]; - eventArray.forEach(event => { + eventArray.forEach((event) => { cal.createEvent({ start: moment.tz(event.start, event.timezone), end: moment.tz(event.end, event.timezone), @@ -40,10 +41,10 @@ export function exportIcal(events: IEvent | IEvent[], calendarName?: string) { description: event.description, organizer: { name: event.hostName || "Anonymous", - email: event.creatorEmail || 'anonymous@anonymous.com', + email: event.creatorEmail || "anonymous@anonymous.com", }, location: event.location, - url: 'https://' + domain + '/' + event.id + url: "https://" + domain + "/" + event.id, }); }); @@ -58,15 +59,18 @@ interface I18nHelpers { export function getI18nHelpers(): I18nHelpers { return { - t: function(key: string, options?: object) { + t: function (key: string, options?: object) { const translation = i18next.t(key, { ...this, ...options }); const template = handlebars.compile(translation); return template(this); }, - tn: function(key: string, options?: object) { - const translation = i18next.t(key, { count: this.count, ...options }); + tn: function (key: string, options?: object) { + const translation = i18next.t(key, { + count: this.count, + ...options, + }); const template = handlebars.compile(translation); return template(this); - } + }, }; } diff --git a/src/index.d.ts b/src/index.d.ts index 4811f7f9..fe63ea36 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -4,14 +4,14 @@ import { EmailService } from "./lib/email.ts"; import { ExpressHandlebars } from "express-handlebars"; interface Locals { - config: GathioConfig; + config: GathioConfig; } declare global { - namespace Express { - interface Request extends Express.Request { - hbsInstance: ExpressHandlebars; - emailService: EmailService; - } + namespace Express { + interface Request extends Express.Request { + hbsInstance: ExpressHandlebars; + emailService: EmailService; } -} \ No newline at end of file + } +} diff --git a/src/lib/activitypub.ts b/src/lib/activitypub.ts index 9e19848d..ed85a8c6 100644 --- a/src/lib/activitypub.ts +++ b/src/lib/activitypub.ts @@ -4,14 +4,14 @@ import { sendDirectMessage } from "../activitypub.js"; import { successfulRSVPResponse } from "./activitypub/templates.js"; interface APObject { - type: "Note"; - actor?: string; - id: string; - to?: string | string[]; - cc?: string | string[]; - attributedTo: string; - inReplyTo: string; - name: string; + type: "Note"; + actor?: string; + id: string; + to?: string | string[]; + cc?: string | string[]; + attributedTo: string; + inReplyTo: string; + name: string; } // From https://www.w3.org/TR/activitypub/#client-to-server-interactions: @@ -21,173 +21,171 @@ interface APObject { // For best compatibility, we always send application/ld+json; profile="https://www.w3.org/ns/activitystreams" // and accept both application/ld+json; profile="https://www.w3.org/ns/activitystreams" and application/activity+json. export const activityPubContentType = - 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; + 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; export const alternateActivityPubContentType = "application/activity+json"; // Cf. https://www.w3.org/TR/activitypub/#retrieving-objects export const acceptsActivityPub = (req: Request) => { - const validAcceptHeaders = [ - activityPubContentType, - alternateActivityPubContentType, - ]; - return validAcceptHeaders.some((header) => - req.headers.accept?.includes(header), - ); + const validAcceptHeaders = [ + activityPubContentType, + alternateActivityPubContentType, + ]; + return validAcceptHeaders.some((header) => + req.headers.accept?.includes(header), + ); }; // At least for poll responses, Mastodon stores the recipient (the poll-maker) // in the 'to' field, while Pleroma stores it in 'cc' export const getNoteRecipient = (object: APObject): string | null => { - const { to, cc } = object; - if (!to && !cc) { - return ""; + const { to, cc } = object; + if (!to && !cc) { + return ""; + } + if (to && to.length > 0) { + if (Array.isArray(to)) { + return to[0]; } - if (to && to.length > 0) { - if (Array.isArray(to)) { - return to[0]; - } - if (typeof to === "string") { - return to; - } - return null; - } else if (cc && cc.length > 0) { - if (Array.isArray(cc)) { - return cc[0]; - } - return cc; + if (typeof to === "string") { + return to; } return null; + } else if (cc && cc.length > 0) { + if (Array.isArray(cc)) { + return cc[0]; + } + return cc; + } + return null; }; // Returns the event ID from a URL like http://localhost:3000/123abc // or https://gath.io/123abc export const getEventId = (url: string): string => { - try { - return new URL(url).pathname.replace("/", ""); - } catch { - // Apparently not a URL so maybe it's just the ID - return url; - } + try { + return new URL(url).pathname.replace("/", ""); + } catch { + // Apparently not a URL so maybe it's just the ID + return url; + } }; export const handlePollResponse = async (req: Request, res: Response) => { - try { - // figure out what this is in reply to -- it should be addressed specifically to us - const { attributedTo, inReplyTo, name } = req.body.object as APObject; - const recipient = getNoteRecipient(req.body.object); - if (!recipient) throw new Error("No recipient found"); - - const eventID = getEventId(recipient); - const event = await Event.findOne({ id: eventID }); - if (!event) throw new Error("Event not found"); - - // make sure this person is actually a follower of the event - const senderAlreadyFollows = event.followers?.some( - (el) => el.actorId === attributedTo, - ); - if (!senderAlreadyFollows) { - throw new Error("Poll response sender does not follow event"); - } - - // compare the inReplyTo to its stored message, if it exists and - // it's going to the right follower then this is a valid reply - const matchingMessage = event.activityPubMessages?.find((el) => { - const content = JSON.parse(el.content || ""); - return inReplyTo === content?.object?.id; - }); - if (!matchingMessage) throw new Error("No matching message found"); - const messageContent = JSON.parse(matchingMessage.content || ""); - // check if the message we sent out was sent to the actor this incoming - // message is attributedTo - const messageRecipient = getNoteRecipient(messageContent.object); - if (!messageRecipient || messageRecipient !== attributedTo) { - throw new Error("Message recipient does not match attributedTo"); - } - - // it's a match, this is a valid poll response, add RSVP to database - - // 'name' is the poll response - // - "Yes, and show me in the public list", - // - "Yes, but hide me from the public list", - // - "No" - if ( - name !== "Yes, and show me in the public list" && - name !== "Yes, but hide me from the public list" && - name !== "No" - ) { - throw new Error("Invalid poll response"); - } - - if (name === "No") { - // Why did you even respond? - return res.status(200).send("Thanks I guess?"); - } - - const visibility = - name === "Yes, and show me in the public list" - ? "public" - : "private"; - - // fetch the profile information of the user - const response = await fetch(attributedTo, { - headers: { - Accept: activityPubContentType, - "Content-Type": activityPubContentType, - }, - }); - if (!response.ok) throw new Error("Actor not found"); - const apActor = await response.json(); - - // If the actor is not already attending the event, add them - if (!event.attendees?.some((el) => el.id === attributedTo)) { - const attendeeName = - apActor.preferredUsername || apActor.name || attributedTo; - const newAttendee: Pick< - IAttendee, - "name" | "status" | "id" | "number" | "visibility" - > = { - name: attendeeName, - status: "attending", - id: attributedTo, - number: 1, - visibility, - }; - const updatedEvent = await Event.findOneAndUpdate( - { id: eventID }, - { $push: { attendees: newAttendee } }, - { new: true }, - ).exec(); - const fullAttendee = updatedEvent?.attendees?.find( - (el) => el.id === attributedTo, - ); - if (!fullAttendee) throw new Error("Full attendee not found"); - - // send a "click here to remove yourself" link back to the user as a DM - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - name: `RSVP to ${event.name}`, - type: "Note", - content: successfulRSVPResponse({ - event, - newAttendee, - fullAttendee, - }), - tag: [ - { - type: "Mention", - href: newAttendee.id, - name: newAttendee.name, - }, - ], - }; - // send direct message to user - sendDirectMessage(jsonObject, newAttendee.id, event.id); - return res.sendStatus(200); - } else { - return res.status(200).send("Attendee is already registered."); - } - } catch (error) { - console.error(error); - return res.status(500).send("An unexpected error occurred."); + try { + // figure out what this is in reply to -- it should be addressed specifically to us + const { attributedTo, inReplyTo, name } = req.body.object as APObject; + const recipient = getNoteRecipient(req.body.object); + if (!recipient) throw new Error("No recipient found"); + + const eventID = getEventId(recipient); + const event = await Event.findOne({ id: eventID }); + if (!event) throw new Error("Event not found"); + + // make sure this person is actually a follower of the event + const senderAlreadyFollows = event.followers?.some( + (el) => el.actorId === attributedTo, + ); + if (!senderAlreadyFollows) { + throw new Error("Poll response sender does not follow event"); + } + + // compare the inReplyTo to its stored message, if it exists and + // it's going to the right follower then this is a valid reply + const matchingMessage = event.activityPubMessages?.find((el) => { + const content = JSON.parse(el.content || ""); + return inReplyTo === content?.object?.id; + }); + if (!matchingMessage) throw new Error("No matching message found"); + const messageContent = JSON.parse(matchingMessage.content || ""); + // check if the message we sent out was sent to the actor this incoming + // message is attributedTo + const messageRecipient = getNoteRecipient(messageContent.object); + if (!messageRecipient || messageRecipient !== attributedTo) { + throw new Error("Message recipient does not match attributedTo"); + } + + // it's a match, this is a valid poll response, add RSVP to database + + // 'name' is the poll response + // - "Yes, and show me in the public list", + // - "Yes, but hide me from the public list", + // - "No" + if ( + name !== "Yes, and show me in the public list" && + name !== "Yes, but hide me from the public list" && + name !== "No" + ) { + throw new Error("Invalid poll response"); + } + + if (name === "No") { + // Why did you even respond? + return res.status(200).send("Thanks I guess?"); + } + + const visibility = + name === "Yes, and show me in the public list" ? "public" : "private"; + + // fetch the profile information of the user + const response = await fetch(attributedTo, { + headers: { + Accept: activityPubContentType, + "Content-Type": activityPubContentType, + }, + }); + if (!response.ok) throw new Error("Actor not found"); + const apActor = await response.json(); + + // If the actor is not already attending the event, add them + if (!event.attendees?.some((el) => el.id === attributedTo)) { + const attendeeName = + apActor.preferredUsername || apActor.name || attributedTo; + const newAttendee: Pick< + IAttendee, + "name" | "status" | "id" | "number" | "visibility" + > = { + name: attendeeName, + status: "attending", + id: attributedTo, + number: 1, + visibility, + }; + const updatedEvent = await Event.findOneAndUpdate( + { id: eventID }, + { $push: { attendees: newAttendee } }, + { new: true }, + ).exec(); + const fullAttendee = updatedEvent?.attendees?.find( + (el) => el.id === attributedTo, + ); + if (!fullAttendee) throw new Error("Full attendee not found"); + + // send a "click here to remove yourself" link back to the user as a DM + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + name: `RSVP to ${event.name}`, + type: "Note", + content: successfulRSVPResponse({ + event, + newAttendee, + fullAttendee, + }), + tag: [ + { + type: "Mention", + href: newAttendee.id, + name: newAttendee.name, + }, + ], + }; + // send direct message to user + sendDirectMessage(jsonObject, newAttendee.id, event.id); + return res.sendStatus(200); + } else { + return res.status(200).send("Attendee is already registered."); } + } catch (error) { + console.error(error); + return res.status(500).send("An unexpected error occurred."); + } }; diff --git a/src/lib/activitypub/templates.ts b/src/lib/activitypub/templates.ts index d0c8cc2d..48b48541 100644 --- a/src/lib/activitypub/templates.ts +++ b/src/lib/activitypub/templates.ts @@ -3,12 +3,12 @@ import getConfig from "../config.js"; const config = getConfig(); export const successfulRSVPResponse = ({ - event, - newAttendee, - fullAttendee, + event, + newAttendee, + fullAttendee, }: { - event: IEvent; - newAttendee: { id?: string; name: string }; - fullAttendee: { _id: string }; + event: IEvent; + newAttendee: { id?: string; name: string }; + fullAttendee: { _id: string }; }) => - `@${newAttendee.name} Thanks for RSVPing! You can remove yourself from the RSVP list by clicking here.`; + `@${newAttendee.name} Thanks for RSVPing! You can remove yourself from the RSVP list by clicking here.`; diff --git a/src/lib/config.ts b/src/lib/config.ts index ca6df769..d4734cf2 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -6,223 +6,223 @@ import { markdownToSanitizedHTML } from "../util/markdown.js"; import i18next from "i18next"; interface StaticPage { - title: string; - path: string; - filename: string; + title: string; + path: string; + filename: string; } export interface GathioConfig { - general: { - domain: string; - port: string; - email: string; - site_name: string; - delete_after_days: number; - is_federated: boolean; - email_logo_url: string; - show_kofi: boolean; - show_public_event_list: boolean; - mail_service: "nodemailer" | "sendgrid" | "mailgun" | "none"; - creator_email_addresses: string[]; - }; - database: { - mongodb_url: string; - }; - nodemailer?: { - smtp_url?: string; - smtp_server: string; - smtp_port: string; - smtp_username: string; - smtp_password: string; - }; - sendgrid?: { - api_key: string; - }; - mailgun?: { - api_key: string; - api_url: string; - domain: string; - }; - static_pages?: StaticPage[]; + general: { + domain: string; + port: string; + email: string; + site_name: string; + delete_after_days: number; + is_federated: boolean; + email_logo_url: string; + show_kofi: boolean; + show_public_event_list: boolean; + mail_service: "nodemailer" | "sendgrid" | "mailgun" | "none"; + creator_email_addresses: string[]; + }; + database: { + mongodb_url: string; + }; + nodemailer?: { + smtp_url?: string; + smtp_server: string; + smtp_port: string; + smtp_username: string; + smtp_password: string; + }; + sendgrid?: { + api_key: string; + }; + mailgun?: { + api_key: string; + api_url: string; + domain: string; + }; + static_pages?: StaticPage[]; } interface FrontendConfig { - domain: string; - siteName: string; - isFederated: boolean; - emailLogoUrl: string; - showKofi: boolean; - showPublicEventList: boolean; - showInstanceInformation: boolean; - staticPages?: StaticPage[]; - version: string; + domain: string; + siteName: string; + isFederated: boolean; + emailLogoUrl: string; + showKofi: boolean; + showPublicEventList: boolean; + showInstanceInformation: boolean; + staticPages?: StaticPage[]; + version: string; } const defaultConfig: GathioConfig = { - general: { - domain: "localhost:3000", - email: "contact@example.com", - port: "3000", - site_name: "gathio", - is_federated: true, - delete_after_days: 7, - email_logo_url: "", - show_public_event_list: false, - show_kofi: false, - mail_service: "none", - creator_email_addresses: [], - }, - database: { - mongodb_url: "mongodb://localhost:27017/gathio", - }, + general: { + domain: "localhost:3000", + email: "contact@example.com", + port: "3000", + site_name: "gathio", + is_federated: true, + delete_after_days: 7, + email_logo_url: "", + show_public_event_list: false, + show_kofi: false, + mail_service: "none", + creator_email_addresses: [], + }, + database: { + mongodb_url: "mongodb://localhost:27017/gathio", + }, }; export const frontendConfig = (res: Response): FrontendConfig => { - const config = res.locals.config; - if (!config) { - return { - domain: defaultConfig.general.domain, - siteName: defaultConfig.general.site_name, - isFederated: defaultConfig.general.is_federated, - emailLogoUrl: defaultConfig.general.email_logo_url, - showPublicEventList: defaultConfig.general.show_public_event_list, - showKofi: defaultConfig.general.show_kofi, - showInstanceInformation: false, - staticPages: [], - version: process.env.npm_package_version || "unknown", - }; - } + const config = res.locals.config; + if (!config) { return { - domain: config.general.domain, - siteName: config.general.site_name, - isFederated: !!config.general.is_federated, - emailLogoUrl: config.general.email_logo_url, - showPublicEventList: !!config.general.show_public_event_list, - showKofi: !!config.general.show_kofi, - showInstanceInformation: !!config.static_pages?.length, - staticPages: config.static_pages, - version: process.env.npm_package_version || "unknown", + domain: defaultConfig.general.domain, + siteName: defaultConfig.general.site_name, + isFederated: defaultConfig.general.is_federated, + emailLogoUrl: defaultConfig.general.email_logo_url, + showPublicEventList: defaultConfig.general.show_public_event_list, + showKofi: defaultConfig.general.show_kofi, + showInstanceInformation: false, + staticPages: [], + version: process.env.npm_package_version || "unknown", }; + } + return { + domain: config.general.domain, + siteName: config.general.site_name, + isFederated: !!config.general.is_federated, + emailLogoUrl: config.general.email_logo_url, + showPublicEventList: !!config.general.show_public_event_list, + showKofi: !!config.general.show_kofi, + showInstanceInformation: !!config.static_pages?.length, + staticPages: config.static_pages, + version: process.env.npm_package_version || "unknown", + }; }; interface InstanceRule { - icon: string; - text: string; + icon: string; + text: string; } export const instanceRules = (): InstanceRule[] => { - const config = getConfig(); - const rules = []; - rules.push( - config.general.show_public_event_list - ? { - text: i18next.t("config.instancerule.showpubliceventlist-true"), - icon: "fas fa-eye", - } - : { - text: i18next.t("config.instancerule.showpubliceventlist-false"), - icon: "fas fa-eye-slash", - }, - ); - rules.push( - config.general.creator_email_addresses?.length - ? { - text: i18next.t("config.instancerule.creatoremail-true"), - icon: "fas fa-user-check", - } - : { - text: i18next.t("config.instancerule.creatoremail-false"), - icon: "fas fa-users", - }, - ); - rules.push( - config.general.delete_after_days > 0 - ? { - text: i18next.t("config.instancerule.deleteafterdays-true", { days: config.general.delete_after_days } ), - icon: "far fa-calendar-times", - } - : { - text: i18next.t("config.instancerule.deleteafterdays-false"), - icon: "far fa-calendar-check", - }, - ); - rules.push( - config.general.is_federated - ? { - text: i18next.t("config.instancerule.isfederated-true"), - icon: "fas fa-globe", - } - : { - text: i18next.t("config.instancerule.isfederated-false"), - icon: "fas fa-globe", - }, - ); - return rules; + const config = getConfig(); + const rules = []; + rules.push( + config.general.show_public_event_list + ? { + text: i18next.t("config.instancerule.showpubliceventlist-true"), + icon: "fas fa-eye", + } + : { + text: i18next.t("config.instancerule.showpubliceventlist-false"), + icon: "fas fa-eye-slash", + }, + ); + rules.push( + config.general.creator_email_addresses?.length + ? { + text: i18next.t("config.instancerule.creatoremail-true"), + icon: "fas fa-user-check", + } + : { + text: i18next.t("config.instancerule.creatoremail-false"), + icon: "fas fa-users", + }, + ); + rules.push( + config.general.delete_after_days > 0 + ? { + text: i18next.t("config.instancerule.deleteafterdays-true", { + days: config.general.delete_after_days, + }), + icon: "far fa-calendar-times", + } + : { + text: i18next.t("config.instancerule.deleteafterdays-false"), + icon: "far fa-calendar-check", + }, + ); + rules.push( + config.general.is_federated + ? { + text: i18next.t("config.instancerule.isfederated-true"), + icon: "fas fa-globe", + } + : { + text: i18next.t("config.instancerule.isfederated-false"), + icon: "fas fa-globe", + }, + ); + return rules; }; export const instanceDescription = (): string => { - const config = getConfig(); - const defaultInstanceDescription = markdownToSanitizedHTML( - i18next.t("config.defaultinstancedesc", "Welcome to this Gathio instance!") + const config = getConfig(); + const defaultInstanceDescription = markdownToSanitizedHTML( + i18next.t("config.defaultinstancedesc", "Welcome to this Gathio instance!"), + ); + let instanceDescription = defaultInstanceDescription; + const instancedescfile = + "./static/instance-description-" + i18next.language + ".md"; + try { + console.log( + "Attempting to load instance description file: " + instancedescfile, ); - let instanceDescription = defaultInstanceDescription; - const instancedescfile = "./static/instance-description-" + i18next.language + ".md"; - try { - console.log("Attempting to load instance description file: "+instancedescfile); - if (fs.existsSync(instancedescfile)) { - const fileBody = fs.readFileSync( - instancedescfile, - "utf-8", - ); - instanceDescription = markdownToSanitizedHTML(fileBody); - } - // Replace {{siteName}} with the instance name - instanceDescription = instanceDescription.replace( - /\{\{ ?siteName ?\}\}/g, - config?.general.site_name, - ); - return instanceDescription; - } catch (err) { - console.log(err); - return defaultInstanceDescription; + if (fs.existsSync(instancedescfile)) { + const fileBody = fs.readFileSync(instancedescfile, "utf-8"); + instanceDescription = markdownToSanitizedHTML(fileBody); } + // Replace {{siteName}} with the instance name + instanceDescription = instanceDescription.replace( + /\{\{ ?siteName ?\}\}/g, + config?.general.site_name, + ); + return instanceDescription; + } catch (err) { + console.log(err); + return defaultInstanceDescription; + } }; let _resolvedConfig: GathioConfig | null = null; // Attempt to load our global config. Will stop the app if the config file // cannot be read (there's no point trying to continue!) export const getConfig = (): GathioConfig => { - if (_resolvedConfig) { - return _resolvedConfig; - } + if (_resolvedConfig) { + return _resolvedConfig; + } - try { - const config = toml.parse( - fs.readFileSync("./config/config.toml", "utf-8"), - ) as GathioConfig; - const resolvedConfig = { - ...defaultConfig, - ...config, - } - if (process.env.CYPRESS || process.env.CI) { - config.general.mail_service = "none"; - console.log( - "Running in Cypress or CI, not initializing email service.", - ); - } else if (config.general.mail_service === "none") { - console.warn( - "You have not configured this Gathio instance to send emails! This means that event creators will not receive emails when their events are created, which means they may end up locked out of editing events. Consider setting up an email service.", - ); - } - - _resolvedConfig = resolvedConfig; - return resolvedConfig; - } catch { - exitWithError( - "Configuration file not found! Have you renamed './config/config-example.toml' to './config/config.toml'?", - ); - return process.exit(1); + try { + const config = toml.parse( + fs.readFileSync("./config/config.toml", "utf-8"), + ) as GathioConfig; + const resolvedConfig = { + ...defaultConfig, + ...config, + }; + if (process.env.CYPRESS || process.env.CI) { + config.general.mail_service = "none"; + console.log("Running in Cypress or CI, not initializing email service."); + } else if (config.general.mail_service === "none") { + console.warn( + "You have not configured this Gathio instance to send emails! This means that event creators will not receive emails when their events are created, which means they may end up locked out of editing events. Consider setting up an email service.", + ); } + + _resolvedConfig = resolvedConfig; + return resolvedConfig; + } catch { + exitWithError( + "Configuration file not found! Have you renamed './config/config-example.toml' to './config/config.toml'?", + ); + return process.exit(1); + } }; export default getConfig; diff --git a/src/lib/email.ts b/src/lib/email.ts index c96edb8d..a4a87176 100644 --- a/src/lib/email.ts +++ b/src/lib/email.ts @@ -12,253 +12,249 @@ type ResponseBodyError = Error & { response: { body: string } }; const config = getConfig(); type EmailTemplateName = - | "addEventAttendee" - | "addEventComment" - | "createEvent" - | "createEventGroup" - | "createEventMagicLink" - | "deleteEvent" - | "editEvent" - | "eventGroupUpdated" - | "removeEventAttendee" - | "subscribed" - | "unattendEvent"; + | "addEventAttendee" + | "addEventComment" + | "createEvent" + | "createEventGroup" + | "createEventMagicLink" + | "deleteEvent" + | "editEvent" + | "eventGroupUpdated" + | "removeEventAttendee" + | "subscribed" + | "unattendEvent"; function isResponseBodyError(err: unknown): err is ResponseBodyError { - if (err && typeof err === "object" && "response" in err) { - if ( - err.response && - typeof err.response === "object" && - "body" in err.response - ) { - return true; - } + if (err && typeof err === "object" && "response" in err) { + if ( + err.response && + typeof err.response === "object" && + "body" in err.response + ) { + return true; } - return false; + } + return false; } export class EmailService { - nodemailerTransporter: Transporter | undefined = undefined; - sgMail: typeof sgMail | undefined = undefined; - mailgunClient: IMailgunClient | undefined = undefined; - hbs: ExpressHandlebars; + nodemailerTransporter: Transporter | undefined = undefined; + sgMail: typeof sgMail | undefined = undefined; + mailgunClient: IMailgunClient | undefined = undefined; + hbs: ExpressHandlebars; - public constructor(config: GathioConfig, hbs: ExpressHandlebars) { - this.hbs = hbs; - switch (config.general.mail_service) { - case "sendgrid": { - if (!config.sendgrid?.api_key) { - return exitWithError( - "Sendgrid is configured as the email service, but no API key is provided. Please provide an API key in the config file.", - ); - } - this.sgMail = sgMail; - this.sgMail.setApiKey(config.sendgrid.api_key); - console.log("Sendgrid is ready to send emails."); - break; - } - case "mailgun": { - if ( - !config.mailgun?.api_key || - !config.mailgun?.api_url || - !config.mailgun?.domain - ) { - return exitWithError( - "Mailgun is configured as the email service, but not all required fields are provided. Please provide all required fields in the config file.", - ); - } - const mailgun = new Mailgun(FormData); - this.mailgunClient = mailgun.client({ - username: "api", - key: config.mailgun.api_key, - url: config.mailgun.api_url, - }); - // TODO: Can we verify the Mailgun connection? - console.log("Mailgun is ready to send emails."); - break; - } - case "nodemailer": { - if (config.nodemailer?.smtp_url) { - this.nodemailerTransporter = nodemailer.createTransport( - config.nodemailer?.smtp_url, - ); - } else { - if ( - !config.nodemailer?.smtp_server || - !config.nodemailer?.smtp_port - ) { - return exitWithError( - "Nodemailer is configured as the email service, but not all required fields are provided. Please provide all required fields in the config file.", - ); - } - const nodemailerConfig = { - host: config.nodemailer?.smtp_server, - port: Number(config.nodemailer?.smtp_port) || 587, - tls: { - // do not fail on invalid certs - rejectUnauthorized: false, - }, - } as SMTPTransport.Options; + public constructor(config: GathioConfig, hbs: ExpressHandlebars) { + this.hbs = hbs; + switch (config.general.mail_service) { + case "sendgrid": { + if (!config.sendgrid?.api_key) { + return exitWithError( + "Sendgrid is configured as the email service, but no API key is provided. Please provide an API key in the config file.", + ); + } + this.sgMail = sgMail; + this.sgMail.setApiKey(config.sendgrid.api_key); + console.log("Sendgrid is ready to send emails."); + break; + } + case "mailgun": { + if ( + !config.mailgun?.api_key || + !config.mailgun?.api_url || + !config.mailgun?.domain + ) { + return exitWithError( + "Mailgun is configured as the email service, but not all required fields are provided. Please provide all required fields in the config file.", + ); + } + const mailgun = new Mailgun(FormData); + this.mailgunClient = mailgun.client({ + username: "api", + key: config.mailgun.api_key, + url: config.mailgun.api_url, + }); + // TODO: Can we verify the Mailgun connection? + console.log("Mailgun is ready to send emails."); + break; + } + case "nodemailer": { + if (config.nodemailer?.smtp_url) { + this.nodemailerTransporter = nodemailer.createTransport( + config.nodemailer?.smtp_url, + ); + } else { + if ( + !config.nodemailer?.smtp_server || + !config.nodemailer?.smtp_port + ) { + return exitWithError( + "Nodemailer is configured as the email service, but not all required fields are provided. Please provide all required fields in the config file.", + ); + } + const nodemailerConfig = { + host: config.nodemailer?.smtp_server, + port: Number(config.nodemailer?.smtp_port) || 587, + tls: { + // do not fail on invalid certs + rejectUnauthorized: false, + }, + } as SMTPTransport.Options; - if (config.nodemailer?.smtp_username) { - nodemailerConfig.auth = { - user: config.nodemailer?.smtp_username, - pass: config.nodemailer?.smtp_password, - }; - } - this.nodemailerTransporter = - nodemailer.createTransport(nodemailerConfig); - } - } + if (config.nodemailer?.smtp_username) { + nodemailerConfig.auth = { + user: config.nodemailer?.smtp_username, + pass: config.nodemailer?.smtp_password, + }; + } + this.nodemailerTransporter = + nodemailer.createTransport(nodemailerConfig); } + } } + } - public async verify(): Promise { - if (this.nodemailerTransporter) { - const nodemailerVerified = - await this.nodemailerTransporter.verify(); - if (nodemailerVerified) { - console.log("Nodemailer is ready to send emails."); - return true; - } else { - return exitWithError( - "Error verifying Nodemailer transporter. Please check your Nodemailer configuration.", - ); - } - } + public async verify(): Promise { + if (this.nodemailerTransporter) { + const nodemailerVerified = await this.nodemailerTransporter.verify(); + if (nodemailerVerified) { + console.log("Nodemailer is ready to send emails."); return true; + } else { + return exitWithError( + "Error verifying Nodemailer transporter. Please check your Nodemailer configuration.", + ); + } } + return true; + } - public async sendEmail({ - to, - bcc, - subject, - text, - html, - }: { - to: string | string[]; - bcc?: string | string[]; - subject: string; - text: string; - html?: string; - }): Promise { - if (this.sgMail) { - try { - await this.sgMail.send({ - to, - bcc, - from: config.general.email, - subject, - text, - html, - }); - return true; - } catch (e: unknown | sgHelpers.classes.ResponseError) { - if (e instanceof sgHelpers.classes.ResponseError) { - console.error("sendgrid error", e.response.body); - } else { - console.error("sendgrid error", e); - } - return false; - } - } else if (this.mailgunClient) { - try { - if (!config.mailgun?.domain) { - return exitWithError( - "Mailgun is configured as the email service, but no domain is provided. Please provide a domain in the config file.", - ); - } - await this.mailgunClient.messages.create( - config.mailgun.domain, - { - from: config.general.email, - to, - bcc, - subject: `${config.general.site_name}: ${subject}`, - text, - html, - }, - ); - return true; - } catch (e) { - if (isResponseBodyError(e)) { - console.error("mailgun error", e.response.body); - } else { - console.error("mailgun error", e); - } - return false; - } - } else if (this.nodemailerTransporter) { - try { - await this.nodemailerTransporter.sendMail({ - from: config.general.email, - to, - bcc, - subject, - text, - html, - }); - return true; - } catch (e) { - console.error(e); - return false; - } + public async sendEmail({ + to, + bcc, + subject, + text, + html, + }: { + to: string | string[]; + bcc?: string | string[]; + subject: string; + text: string; + html?: string; + }): Promise { + if (this.sgMail) { + try { + await this.sgMail.send({ + to, + bcc, + from: config.general.email, + subject, + text, + html, + }); + return true; + } catch (e: unknown | sgHelpers.classes.ResponseError) { + if (e instanceof sgHelpers.classes.ResponseError) { + console.error("sendgrid error", e.response.body); + } else { + console.error("sendgrid error", e); + } + return false; + } + } else if (this.mailgunClient) { + try { + if (!config.mailgun?.domain) { + return exitWithError( + "Mailgun is configured as the email service, but no domain is provided. Please provide a domain in the config file.", + ); + } + await this.mailgunClient.messages.create(config.mailgun.domain, { + from: config.general.email, + to, + bcc, + subject: `${config.general.site_name}: ${subject}`, + text, + html, + }); + return true; + } catch (e) { + if (isResponseBodyError(e)) { + console.error("mailgun error", e.response.body); } else { - // no mailer, so noop - return true; + console.error("mailgun error", e); } + return false; + } + } else if (this.nodemailerTransporter) { + try { + await this.nodemailerTransporter.sendMail({ + from: config.general.email, + to, + bcc, + subject, + text, + html, + }); + return true; + } catch (e) { + console.error(e); + return false; + } + } else { + // no mailer, so noop + return true; } + } - public async sendEmailFromTemplate({ - to, - bcc = "", - subject, - templateName, - templateData = {}, - }: { - to: string | string[]; - bcc?: string | string[] | undefined; - subject: string; - templateName: EmailTemplateName; - templateData?: object; - }): Promise { - const [html, text] = await Promise.all([ - this.hbs.renderView( - `./views/emails/${templateName}/${templateName}Html.handlebars`, - { - domain: config.general.domain, - contactEmail: config.general.email, - siteName: config.general.site_name, - mailService: config.general.mail_service, - siteLogo: config.general.email_logo_url, - isFederated: config.general.is_federated || true, - cache: true, - layout: "email.handlebars", - ...templateData, - }, - ), - this.hbs.renderView( - `./views/emails/${templateName}/${templateName}Text.handlebars`, - { - domain: config.general.domain, - contactEmail: config.general.email, - siteName: config.general.site_name, - mailService: config.general.mail_service, - siteLogo: config.general.email_logo_url, - isFederated: config.general.is_federated || true, - cache: true, - layout: "email.handlebars", - ...templateData, - }, - ), - ]); + public async sendEmailFromTemplate({ + to, + bcc = "", + subject, + templateName, + templateData = {}, + }: { + to: string | string[]; + bcc?: string | string[] | undefined; + subject: string; + templateName: EmailTemplateName; + templateData?: object; + }): Promise { + const [html, text] = await Promise.all([ + this.hbs.renderView( + `./views/emails/${templateName}/${templateName}Html.handlebars`, + { + domain: config.general.domain, + contactEmail: config.general.email, + siteName: config.general.site_name, + mailService: config.general.mail_service, + siteLogo: config.general.email_logo_url, + isFederated: config.general.is_federated || true, + cache: true, + layout: "email.handlebars", + ...templateData, + }, + ), + this.hbs.renderView( + `./views/emails/${templateName}/${templateName}Text.handlebars`, + { + domain: config.general.domain, + contactEmail: config.general.email, + siteName: config.general.site_name, + mailService: config.general.mail_service, + siteLogo: config.general.email_logo_url, + isFederated: config.general.is_federated || true, + cache: true, + layout: "email.handlebars", + ...templateData, + }, + ), + ]); - return this.sendEmail({ - to, - bcc, - subject: `${config.general.site_name}: ${subject}`, - text, - html, - }); - } + return this.sendEmail({ + to, + bcc, + subject: `${config.general.site_name}: ${subject}`, + text, + html, + }); + } } diff --git a/src/lib/event.ts b/src/lib/event.ts index 091a6223..9e76e896 100644 --- a/src/lib/event.ts +++ b/src/lib/event.ts @@ -2,35 +2,35 @@ import i18next from "i18next"; import { IEventGroup } from "../models/EventGroup.js"; export interface EventListEvent { - id: string; - name: string; - location: string; - displayDate: string; - eventHasConcluded: boolean; - startMoment: moment.Moment; - endMoment: moment.Moment; - eventGroup?: IEventGroup; + id: string; + name: string; + location: string; + displayDate: string; + eventHasConcluded: boolean; + startMoment: moment.Moment; + endMoment: moment.Moment; + eventGroup?: IEventGroup; } interface EventBucket { - title: string; - events: EventListEvent[]; + title: string; + events: EventListEvent[]; } export const bucketEventsByMonth = ( - acc: EventBucket[], - event: EventListEvent, + acc: EventBucket[], + event: EventListEvent, ) => { - event.startMoment.locale(i18next.language); - const month = event.startMoment.format(i18next.t("common.year-month-format" )); - const matchingBucket = acc.find((bucket) => bucket.title === month); - if (!matchingBucket) { - acc.push({ - title: month, - events: [event], - }); - } else { - matchingBucket.events.push(event); - } - return acc; + event.startMoment.locale(i18next.language); + const month = event.startMoment.format(i18next.t("common.year-month-format")); + const matchingBucket = acc.find((bucket) => bucket.title === month); + if (!matchingBucket) { + acc.push({ + title: month, + events: [event], + }); + } else { + matchingBucket.events.push(event); + } + return acc; }; diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts index b5851e54..20c37df3 100644 --- a/src/lib/middleware.ts +++ b/src/lib/middleware.ts @@ -4,67 +4,67 @@ import { getConfig } from "../lib/config.js"; import { merge as deepMerge } from "ts-deepmerge"; export const checkMagicLink = async ( - req: Request, - res: Response, - next: NextFunction, + req: Request, + res: Response, + next: NextFunction, ) => { - const config = getConfig(); - if (!config.general.creator_email_addresses?.length) { - // No creator email addresses are configured, so skip the magic link check - return next(); - } - if (!req.body.magicLinkToken) { - return res.status(400).json({ - errors: [ - { - message: "No magic link token was provided.", - }, - ], - }); - } - if (!req.body.creatorEmail) { - return res.status(400).json({ - errors: [ - { - message: "No creator email was provided.", - }, - ], - }); - } - const magicLink = await MagicLink.findOne({ - token: req.body.magicLinkToken, - email: req.body.creatorEmail, - expiryTime: { $gt: new Date() }, - permittedActions: "createEvent", + const config = getConfig(); + if (!config.general.creator_email_addresses?.length) { + // No creator email addresses are configured, so skip the magic link check + return next(); + } + if (!req.body.magicLinkToken) { + return res.status(400).json({ + errors: [ + { + message: "No magic link token was provided.", + }, + ], + }); + } + if (!req.body.creatorEmail) { + return res.status(400).json({ + errors: [ + { + message: "No creator email was provided.", + }, + ], + }); + } + const magicLink = await MagicLink.findOne({ + token: req.body.magicLinkToken, + email: req.body.creatorEmail, + expiryTime: { $gt: new Date() }, + permittedActions: "createEvent", + }); + if (!magicLink || magicLink.email !== req.body.creatorEmail) { + return res.status(400).json({ + errors: [ + { + message: + "Magic link is invalid or has expired. Get a new one here.", + }, + ], }); - if (!magicLink || magicLink.email !== req.body.creatorEmail) { - return res.status(400).json({ - errors: [ - { - message: - "Magic link is invalid or has expired. Get a new one here.", - }, - ], - }); - } - next(); + } + next(); }; // Route-specific middleware which injects the config into the request object // It can also be used to modify the config based on the request, which // we use for Cypress testing. export const getConfigMiddleware = ( - req: Request, - res: Response, - next: NextFunction, + req: Request, + res: Response, + next: NextFunction, ) => { - const config = getConfig(); - if (process.env.CYPRESS === "true" && req.cookies?.cypressConfigOverride) { - console.log("Overriding config with Cypress config"); - const override = JSON.parse(req.cookies.cypressConfigOverride); - res.locals.config = deepMerge(config, override); - return next(); - } - res.locals.config = config; + const config = getConfig(); + if (process.env.CYPRESS === "true" && req.cookies?.cypressConfigOverride) { + console.log("Overriding config with Cypress config"); + const override = JSON.parse(req.cookies.cypressConfigOverride); + res.locals.config = deepMerge(config, override); return next(); + } + res.locals.config = config; + return next(); }; diff --git a/src/lib/process.ts b/src/lib/process.ts index d43b3c71..363a6fcb 100644 --- a/src/lib/process.ts +++ b/src/lib/process.ts @@ -1,4 +1,4 @@ export const exitWithError = (message: string) => { - console.error(`\x1b[31m${message}`); - process.exit(1); + console.error(`\x1b[31m${message}`); + process.exit(1); }; diff --git a/src/models/Event.ts b/src/models/Event.ts index 57316803..2d2325dc 100644 --- a/src/models/Event.ts +++ b/src/models/Event.ts @@ -1,344 +1,344 @@ import mongoose from "mongoose"; export interface IAttendee { - name: string; - status?: string; - email?: string; - removalPassword?: string; - id?: string; - number?: number; - created?: Date; - _id: string; - visibility?: "public" | "private"; + name: string; + status?: string; + email?: string; + removalPassword?: string; + id?: string; + number?: number; + created?: Date; + _id: string; + visibility?: "public" | "private"; } export interface IReply { - id: string; - author: string; - content: string; - timestamp: Date; + id: string; + author: string; + content: string; + timestamp: Date; } export interface IComment { - id: string; - author: string; - content: string; - timestamp: Date; - activityJson?: string; - actorJson?: string; - activityId?: string; - actorId?: string; - replies?: IReply[]; + id: string; + author: string; + content: string; + timestamp: Date; + activityJson?: string; + actorJson?: string; + activityId?: string; + actorId?: string; + replies?: IReply[]; } export interface IFollower { - followId?: string; - actorId?: string; - actorJson?: string; - name?: string; + followId?: string; + actorId?: string; + actorJson?: string; + name?: string; } export interface IActivityPubMessage { - id?: string; - content?: string; + id?: string; + content?: string; } export interface IEvent extends mongoose.Document { - id: string; - type: string; - name: string; - location: string; - start: Date; - end: Date; - timezone: string; - description: string; - image?: string; - url?: string; - creatorEmail?: string; - hostName?: string; - viewPassword?: string; - editPassword?: string; - editToken?: string; - eventGroup?: mongoose.Types.ObjectId; - usersCanAttend?: boolean; - showUsersList?: boolean; - usersCanComment?: boolean; - firstLoad?: boolean; - attendees?: IAttendee[]; - maxAttendees?: number; - comments?: IComment[]; - activityPubActor?: string; - activityPubEvent?: string; - publicKey?: string; - privateKey?: string; - followers?: IFollower[]; - activityPubMessages?: IActivityPubMessage[]; - showOnPublicList?: boolean; + id: string; + type: string; + name: string; + location: string; + start: Date; + end: Date; + timezone: string; + description: string; + image?: string; + url?: string; + creatorEmail?: string; + hostName?: string; + viewPassword?: string; + editPassword?: string; + editToken?: string; + eventGroup?: mongoose.Types.ObjectId; + usersCanAttend?: boolean; + showUsersList?: boolean; + usersCanComment?: boolean; + firstLoad?: boolean; + attendees?: IAttendee[]; + maxAttendees?: number; + comments?: IComment[]; + activityPubActor?: string; + activityPubEvent?: string; + publicKey?: string; + privateKey?: string; + followers?: IFollower[]; + activityPubMessages?: IActivityPubMessage[]; + showOnPublicList?: boolean; } const Attendees = new mongoose.Schema({ - name: { - type: String, - trim: true, - }, - status: { - type: String, - trim: true, - }, - email: { - type: String, - trim: true, - }, - removalPassword: { - type: String, - trim: true, - unique: true, - sparse: true, - }, - id: { - type: String, - trim: true, - unique: true, - sparse: true, - }, - // The number of people that are attending under one 'attendee' object - number: { - type: Number, - trim: true, - default: 1, - }, - visibility: { - type: String, - trim: true, - default: "public", - }, - created: Date, + name: { + type: String, + trim: true, + }, + status: { + type: String, + trim: true, + }, + email: { + type: String, + trim: true, + }, + removalPassword: { + type: String, + trim: true, + unique: true, + sparse: true, + }, + id: { + type: String, + trim: true, + unique: true, + sparse: true, + }, + // The number of people that are attending under one 'attendee' object + number: { + type: Number, + trim: true, + default: 1, + }, + visibility: { + type: String, + trim: true, + default: "public", + }, + created: Date, }); const Followers = new mongoose.Schema( - { - // this is the id of the original follow *request*, which we use to validate Undo events - followId: { - type: String, - trim: true, - }, - // this is the actual remote user profile id - actorId: { - type: String, - trim: true, - }, - // this is the stringified JSON of the entire user profile - actorJson: { - type: String, - trim: true, - }, - name: { - type: String, - trim: true, - }, + { + // this is the id of the original follow *request*, which we use to validate Undo events + followId: { + type: String, + trim: true, }, - { _id: false }, -); - -const ReplySchema = new mongoose.Schema({ - id: { - type: String, - required: true, - unique: true, - sparse: true, - }, - author: { - type: String, - trim: true, - required: true, + // this is the actual remote user profile id + actorId: { + type: String, + trim: true, }, - content: { - type: String, - trim: true, - required: true, + // this is the stringified JSON of the entire user profile + actorJson: { + type: String, + trim: true, }, - timestamp: { - type: Date, - trim: true, - required: true, + name: { + type: String, + trim: true, }, + }, + { _id: false }, +); + +const ReplySchema = new mongoose.Schema({ + id: { + type: String, + required: true, + unique: true, + sparse: true, + }, + author: { + type: String, + trim: true, + required: true, + }, + content: { + type: String, + trim: true, + required: true, + }, + timestamp: { + type: Date, + trim: true, + required: true, + }, }); const ActivityPubMessages = new mongoose.Schema({ - id: { - type: String, - required: true, - unique: true, - sparse: true, - }, - content: { - type: String, - trim: true, - required: true, - }, + id: { + type: String, + required: true, + unique: true, + sparse: true, + }, + content: { + type: String, + trim: true, + required: true, + }, }); const CommentSchema = new mongoose.Schema({ - id: { - type: String, - required: true, - unique: true, - sparse: true, - }, - author: { - type: String, - trim: true, - required: true, - }, - content: { - type: String, - trim: true, - required: true, - }, - timestamp: { - type: Date, - trim: true, - required: true, - }, - activityJson: { - type: String, - trim: true, - }, - actorJson: { - type: String, - trim: true, - }, - activityId: { - type: String, - trim: true, - }, - actorId: { - type: String, - trim: true, - }, - replies: [ReplySchema], + id: { + type: String, + required: true, + unique: true, + sparse: true, + }, + author: { + type: String, + trim: true, + required: true, + }, + content: { + type: String, + trim: true, + required: true, + }, + timestamp: { + type: Date, + trim: true, + required: true, + }, + activityJson: { + type: String, + trim: true, + }, + actorJson: { + type: String, + trim: true, + }, + activityId: { + type: String, + trim: true, + }, + actorId: { + type: String, + trim: true, + }, + replies: [ReplySchema], }); const EventSchema = new mongoose.Schema({ - id: { - type: String, - required: true, - unique: true, - }, - type: { - type: String, - trim: true, - required: true, - }, - name: { - type: String, - trim: true, - required: true, - }, - location: { - type: String, - trim: true, - required: true, - }, - start: { - // Stored as a UTC timestamp - type: Date, - trim: true, - required: true, - }, - end: { - // Stored as a UTC timestamp - type: Date, - trim: true, - required: true, - }, - timezone: { - type: String, - default: "Etc/UTC", - }, - description: { - type: String, - trim: true, - required: true, - }, - image: { - type: String, - trim: true, - }, - url: { - type: String, - trim: true, - }, - creatorEmail: { - type: String, - trim: true, - }, - hostName: { - type: String, - trim: true, - }, - viewPassword: { - type: String, - trim: true, - }, - editPassword: { - type: String, - trim: true, - }, - editToken: { - type: String, - trim: true, - minlength: 32, - maxlength: 32, - }, - eventGroup: { type: mongoose.Schema.Types.ObjectId, ref: "EventGroup" }, - usersCanAttend: { - type: Boolean, - trim: true, - default: false, - }, - showUsersList: { - type: Boolean, - trim: true, - default: false, - }, - usersCanComment: { - type: Boolean, - trim: true, - default: false, - }, - firstLoad: { - type: Boolean, - trim: true, - default: true, - }, - attendees: [Attendees], - maxAttendees: { - type: Number, - }, - comments: [CommentSchema], - activityPubActor: { - type: String, - trim: true, - }, - activityPubEvent: { - type: String, - trim: true, - }, - publicKey: { - type: String, - trim: true, - }, - privateKey: { - type: String, - trim: true, - }, - followers: [Followers], - activityPubMessages: [ActivityPubMessages], - showOnPublicList: { - type: Boolean, - default: false, - }, + id: { + type: String, + required: true, + unique: true, + }, + type: { + type: String, + trim: true, + required: true, + }, + name: { + type: String, + trim: true, + required: true, + }, + location: { + type: String, + trim: true, + required: true, + }, + start: { + // Stored as a UTC timestamp + type: Date, + trim: true, + required: true, + }, + end: { + // Stored as a UTC timestamp + type: Date, + trim: true, + required: true, + }, + timezone: { + type: String, + default: "Etc/UTC", + }, + description: { + type: String, + trim: true, + required: true, + }, + image: { + type: String, + trim: true, + }, + url: { + type: String, + trim: true, + }, + creatorEmail: { + type: String, + trim: true, + }, + hostName: { + type: String, + trim: true, + }, + viewPassword: { + type: String, + trim: true, + }, + editPassword: { + type: String, + trim: true, + }, + editToken: { + type: String, + trim: true, + minlength: 32, + maxlength: 32, + }, + eventGroup: { type: mongoose.Schema.Types.ObjectId, ref: "EventGroup" }, + usersCanAttend: { + type: Boolean, + trim: true, + default: false, + }, + showUsersList: { + type: Boolean, + trim: true, + default: false, + }, + usersCanComment: { + type: Boolean, + trim: true, + default: false, + }, + firstLoad: { + type: Boolean, + trim: true, + default: true, + }, + attendees: [Attendees], + maxAttendees: { + type: Number, + }, + comments: [CommentSchema], + activityPubActor: { + type: String, + trim: true, + }, + activityPubEvent: { + type: String, + trim: true, + }, + publicKey: { + type: String, + trim: true, + }, + privateKey: { + type: String, + trim: true, + }, + followers: [Followers], + activityPubMessages: [ActivityPubMessages], + showOnPublicList: { + type: Boolean, + default: false, + }, }); export default mongoose.model("Event", EventSchema); diff --git a/src/models/EventGroup.ts b/src/models/EventGroup.ts index de7187d4..9269f9c1 100755 --- a/src/models/EventGroup.ts +++ b/src/models/EventGroup.ts @@ -1,80 +1,80 @@ import mongoose from "mongoose"; export interface ISubscriber { - email?: string; + email?: string; } export interface IEventGroup extends mongoose.Document { - id: string; - name: string; - description: string; - image?: string; - url?: string; - creatorEmail?: string; - hostName?: string; - editToken?: string; - firstLoad?: boolean; - events?: mongoose.Types.ObjectId[]; - subscribers?: ISubscriber[]; - showOnPublicList?: boolean; + id: string; + name: string; + description: string; + image?: string; + url?: string; + creatorEmail?: string; + hostName?: string; + editToken?: string; + firstLoad?: boolean; + events?: mongoose.Types.ObjectId[]; + subscribers?: ISubscriber[]; + showOnPublicList?: boolean; } const Subscriber = new mongoose.Schema({ - email: { - type: String, - trim: true, - }, + email: { + type: String, + trim: true, + }, }); const EventGroupSchema = new mongoose.Schema({ - id: { - type: String, - required: true, - unique: true, - }, - name: { - type: String, - trim: true, - required: true, - }, - description: { - type: String, - trim: true, - required: true, - }, - image: { - type: String, - trim: true, - }, - url: { - type: String, - trim: true, - }, - creatorEmail: { - type: String, - trim: true, - }, - hostName: { - type: String, - trim: true, - }, - editToken: { - type: String, - trim: true, - minlength: 32, - maxlength: 32, - }, - firstLoad: { - type: Boolean, - trim: true, - default: true, - }, - events: [{ type: mongoose.Schema.Types.ObjectId, ref: "Event" }], - subscribers: [Subscriber], - showOnPublicList: { - type: Boolean, - default: false, - }, + id: { + type: String, + required: true, + unique: true, + }, + name: { + type: String, + trim: true, + required: true, + }, + description: { + type: String, + trim: true, + required: true, + }, + image: { + type: String, + trim: true, + }, + url: { + type: String, + trim: true, + }, + creatorEmail: { + type: String, + trim: true, + }, + hostName: { + type: String, + trim: true, + }, + editToken: { + type: String, + trim: true, + minlength: 32, + maxlength: 32, + }, + firstLoad: { + type: Boolean, + trim: true, + default: true, + }, + events: [{ type: mongoose.Schema.Types.ObjectId, ref: "Event" }], + subscribers: [Subscriber], + showOnPublicList: { + type: Boolean, + default: false, + }, }); export default mongoose.model("EventGroup", EventGroupSchema); diff --git a/src/models/Log.ts b/src/models/Log.ts index 24991d49..8f905fd7 100755 --- a/src/models/Log.ts +++ b/src/models/Log.ts @@ -1,33 +1,33 @@ import mongoose from "mongoose"; export interface ILog extends mongoose.Document { - status: string; - process: string; - message: string; - timestamp: Date; + status: string; + process: string; + message: string; + timestamp: Date; } const LogSchema = new mongoose.Schema({ - status: { - type: String, - trim: true, - required: true, - }, - process: { - type: String, - trim: true, - required: true, - }, - message: { - type: String, - trim: true, - required: true, - }, - timestamp: { - type: Date, - trim: true, - required: true, - }, + status: { + type: String, + trim: true, + required: true, + }, + process: { + type: String, + trim: true, + required: true, + }, + message: { + type: String, + trim: true, + required: true, + }, + timestamp: { + type: Date, + trim: true, + required: true, + }, }); export default mongoose.model("Log", LogSchema); diff --git a/src/models/MagicLink.ts b/src/models/MagicLink.ts index fa24c339..77b70ea8 100644 --- a/src/models/MagicLink.ts +++ b/src/models/MagicLink.ts @@ -3,33 +3,33 @@ import mongoose from "mongoose"; export type MagicLinkAction = "createEvent"; export interface MagicLink { - id: string; - email: string; - token: string; - expiryTime: Date; - permittedActions: MagicLinkAction[]; + id: string; + email: string; + token: string; + expiryTime: Date; + permittedActions: MagicLinkAction[]; } const MagicLinkSchema = new mongoose.Schema({ - email: { - type: String, - trim: true, - required: true, - }, - token: { - type: String, - trim: true, - required: true, - }, - expiryTime: { - type: Date, - trim: true, - required: true, - }, - permittedActions: { - type: [String], - required: true, - }, + email: { + type: String, + trim: true, + required: true, + }, + token: { + type: String, + trim: true, + required: true, + }, + expiryTime: { + type: Date, + trim: true, + required: true, + }, + permittedActions: { + type: [String], + required: true, + }, }); export default mongoose.model("MagicLink", MagicLinkSchema); diff --git a/src/routes.js b/src/routes.js index 218fc506..8889f391 100755 --- a/src/routes.js +++ b/src/routes.js @@ -10,9 +10,9 @@ import niceware from "niceware"; import fileUpload from "express-fileupload"; import schedule from "node-schedule"; import { - broadcastCreateMessage, - broadcastDeleteMessage, - processInbox, + broadcastCreateMessage, + broadcastDeleteMessage, + processInbox, } from "./activitypub.js"; import Event from "./models/Event.js"; import EventGroup from "./models/EventGroup.js"; @@ -28,8 +28,8 @@ const isFederated = config.general.is_federated || true; // This alphabet (used to generate all event, group, etc. IDs) is missing '-' // because ActivityPub doesn't like it in IDs const nanoid = customAlphabet( - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_", - 21, + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_", + 21, ); const router = express.Router(); @@ -37,1195 +37,1138 @@ router.use(fileUpload()); // SCHEDULED DELETION schedule.scheduleJob("59 23 * * *", function (_fireDate) { - const deleteAfterDays = config.general.delete_after_days; - if (!deleteAfterDays || deleteAfterDays <= 0) { - // Deletion is disabled - return; - } - - const too_old = moment - .tz("Etc/UTC") - .subtract(deleteAfterDays, "days") - .toDate(); - console.log( - "Old event deletion running! Deleting all events concluding before ", - too_old, - ); + const deleteAfterDays = config.general.delete_after_days; + if (!deleteAfterDays || deleteAfterDays <= 0) { + // Deletion is disabled + return; + } - Event.find({ end: { $lte: too_old } }) - .then((oldEvents) => { - oldEvents.forEach((event) => { - const deleteEventFromDB = (id) => { - Event.remove({ _id: id }) - .then((_response) => { - addToLog( - "deleteOldEvents", - "success", - "Old event " + id + " deleted", - ); - }) - .catch((err) => { - addToLog( - "deleteOldEvents", - "error", - "Attempt to delete old event " + - id + - " failed with error: " + - err, - ); - }); - }; + const too_old = moment + .tz("Etc/UTC") + .subtract(deleteAfterDays, "days") + .toDate(); + console.log( + "Old event deletion running! Deleting all events concluding before ", + too_old, + ); - if (event.image) { - fs.unlink( - path.join( - process.cwd(), - "/public/events/" + event.image, - ), - (err) => { - if (err) { - addToLog( - "deleteOldEvents", - "error", - "Attempt to delete event image for old event " + - event.id + - " failed with error: " + - err, - ); - } - // Image removed - addToLog( - "deleteOldEvents", - "error", - "Image deleted for old event " + event.id, - ); - }, - ); - } - // Check if event has ActivityPub fields - if (event.activityPubActor && event.activityPubEvent) { - // Broadcast a Delete profile message to all followers so that at least Mastodon servers will delete their local profile information - // const guidUpdateObject = crypto - // .randomBytes(16) - // .toString("hex"); - const jsonUpdateObject = JSON.parse(event.activityPubActor); - const jsonEventObject = JSON.parse(event.activityPubEvent); - // first broadcast AP messages, THEN delete from DB - broadcastDeleteMessage( - jsonUpdateObject, - event.followers, - event.id, - function (_statuses) { - broadcastDeleteMessage( - jsonEventObject, - event.followers, - event.id, - function (_statuses) { - deleteEventFromDB(event._id); - }, - ); - }, - ); - } else { - // No ActivityPub data - simply delete the event - deleteEventFromDB(event._id); - } + Event.find({ end: { $lte: too_old } }) + .then((oldEvents) => { + oldEvents.forEach((event) => { + const deleteEventFromDB = (id) => { + Event.remove({ _id: id }) + .then((_response) => { + addToLog( + "deleteOldEvents", + "success", + "Old event " + id + " deleted", + ); + }) + .catch((err) => { + addToLog( + "deleteOldEvents", + "error", + "Attempt to delete old event " + + id + + " failed with error: " + + err, + ); }); - }) - .catch((err) => { - addToLog( + }; + + if (event.image) { + fs.unlink( + path.join(process.cwd(), "/public/events/" + event.image), + (err) => { + if (err) { + addToLog( + "deleteOldEvents", + "error", + "Attempt to delete event image for old event " + + event.id + + " failed with error: " + + err, + ); + } + // Image removed + addToLog( "deleteOldEvents", "error", - "Attempt to delete old event failed with error: " + err, - ); - }); + "Image deleted for old event " + event.id, + ); + }, + ); + } + // Check if event has ActivityPub fields + if (event.activityPubActor && event.activityPubEvent) { + // Broadcast a Delete profile message to all followers so that at least Mastodon servers will delete their local profile information + // const guidUpdateObject = crypto + // .randomBytes(16) + // .toString("hex"); + const jsonUpdateObject = JSON.parse(event.activityPubActor); + const jsonEventObject = JSON.parse(event.activityPubEvent); + // first broadcast AP messages, THEN delete from DB + broadcastDeleteMessage( + jsonUpdateObject, + event.followers, + event.id, + function (_statuses) { + broadcastDeleteMessage( + jsonEventObject, + event.followers, + event.id, + function (_statuses) { + deleteEventFromDB(event._id); + }, + ); + }, + ); + } else { + // No ActivityPub data - simply delete the event + deleteEventFromDB(event._id); + } + }); + }) + .catch((err) => { + addToLog( + "deleteOldEvents", + "error", + "Attempt to delete old event failed with error: " + err, + ); + }); - // TODO: While we're here, also remove all provisioned event attendees over a day - // old (they're not going to become active) + // TODO: While we're here, also remove all provisioned event attendees over a day + // old (they're not going to become active) }); // BACKEND ROUTES router.post("/verifytoken/event/:eventID", (req, res) => { - Event.findOne({ - id: req.params.eventID, - editToken: req.body.editToken, - }).then((event) => { - if (event) return res.sendStatus(200); - return res.sendStatus(404); - }); + Event.findOne({ + id: req.params.eventID, + editToken: req.body.editToken, + }).then((event) => { + if (event) return res.sendStatus(200); + return res.sendStatus(404); + }); }); router.post("/verifytoken/group/:eventGroupID", (req, res) => { - EventGroup.findOne({ - id: req.params.eventGroupID, - editToken: req.body.editToken, - }).then((group) => { - if (group) return res.sendStatus(200); - return res.sendStatus(404); - }); + EventGroup.findOne({ + id: req.params.eventGroupID, + editToken: req.body.editToken, + }).then((group) => { + if (group) return res.sendStatus(200); + return res.sendStatus(404); + }); }); router.post("/deleteimage/:eventID/:editToken", (req, res) => { - let submittedEditToken = req.params.editToken; - let eventImage; - Event.findOne({ - id: req.params.eventID, - }).then((event) => { - if (event.editToken === submittedEditToken) { - // Token matches - if (event.image) { - eventImage = event.image; - } else { - res.status(500).send( - "This event doesn't have a linked image. What are you even doing", - ); - } - fs.unlink( - path.join(process.cwd(), "/public/events/" + eventImage), - (err) => { - if (err) { - res.status(500).send(err); - addToLog( - "deleteEventImage", - "error", - "Attempt to delete event image for event " + - req.params.eventID + - " failed with error: " + - err, - ); - } - // Image removed - addToLog( - "deleteEventImage", - "success", - "Image for event " + req.params.eventID + " deleted", - ); - event.image = ""; - event - .save() - .then((_response) => { - res.status(200).send("Success"); - }) - .catch((err) => { - res.status(500).send(err); - addToLog( - "deleteEventImage", - "error", - "Attempt to delete event image for event " + - req.params.eventID + - " failed with error: " + - err, - ); - }); - }, + let submittedEditToken = req.params.editToken; + let eventImage; + Event.findOne({ + id: req.params.eventID, + }).then((event) => { + if (event.editToken === submittedEditToken) { + // Token matches + if (event.image) { + eventImage = event.image; + } else { + res + .status(500) + .send( + "This event doesn't have a linked image. What are you even doing", + ); + } + fs.unlink( + path.join(process.cwd(), "/public/events/" + eventImage), + (err) => { + if (err) { + res.status(500).send(err); + addToLog( + "deleteEventImage", + "error", + "Attempt to delete event image for event " + + req.params.eventID + + " failed with error: " + + err, ); - } - }); + } + // Image removed + addToLog( + "deleteEventImage", + "success", + "Image for event " + req.params.eventID + " deleted", + ); + event.image = ""; + event + .save() + .then((_response) => { + res.status(200).send("Success"); + }) + .catch((err) => { + res.status(500).send(err); + addToLog( + "deleteEventImage", + "error", + "Attempt to delete event image for event " + + req.params.eventID + + " failed with error: " + + err, + ); + }); + }, + ); + } + }); }); router.post("/deleteevent/:eventID/:editToken", (req, res) => { - let submittedEditToken = req.params.editToken; - Event.findOne({ - id: req.params.eventID, - }) - .then((event) => { - if (event.editToken === submittedEditToken) { - // Token matches - - let eventImage; - if (event.image) { - eventImage = event.image; - } + let submittedEditToken = req.params.editToken; + Event.findOne({ + id: req.params.eventID, + }) + .then((event) => { + if (event.editToken === submittedEditToken) { + // Token matches - // broadcast a Delete profile message to all followers so that at least Mastodon servers will delete their local profile information - // const guidUpdateObject = crypto.randomBytes(16).toString("hex"); - const jsonUpdateObject = JSON.parse(event.activityPubActor); - // first broadcast AP messages, THEN delete from DB - broadcastDeleteMessage( - jsonUpdateObject, - event.followers, - req.params.eventID, - function (_statuses) { - Event.deleteOne( - { id: req.params.eventID }, - function (err, _raw) { - if (err) { - res.send(err); - addToLog( - "deleteEvent", - "error", - "Attempt to delete event " + - req.params.eventID + - " failed with error: " + - err, - ); - } - }, - ) - .then(() => { - // Delete image - if (eventImage) { - fs.unlink( - path.join( - process.cwd(), - "/public/events/" + eventImage, - ), - (err) => { - if (err) { - res.send(err); - addToLog( - "deleteEvent", - "error", - "Attempt to delete event image for event " + - req.params.eventID + - " failed with error: " + - err, - ); - } - // Image removed - addToLog( - "deleteEvent", - "success", - "Event " + - req.params.eventID + - " deleted", - ); - }, - ); - } - res.writeHead(302, { - Location: "/", - }); - res.end(); + let eventImage; + if (event.image) { + eventImage = event.image; + } - const attendeeEmails = - event?.attendees - ?.filter( - (o) => - o.status === "attending" && - o.email, - ) - .map((o) => o.email || "") || []; - if (attendeeEmails.length) { - console.log( - "Sending emails to: " + attendeeEmails, - ); - req.emailService - .sendEmailFromTemplate({ - to: attendeeEmails, - subject: i18next.t( - "routes.deleteeventsubject", - { eventName: event?.name }, - ), - templateName: "deleteEvent", - templateData: { - eventName: event?.name, - }, - }) - .catch((e) => { - console.error( - "error sending attendee email", - e.toString(), - ); - res.status(500).end(); - }); - } else { - console.log("Nothing to send!"); - } - }) - .catch((err) => { - res.send( - "Sorry! Something went wrong (error deleting): " + - err, - ); - addToLog( - "deleteEvent", - "error", - "Attempt to delete event " + - req.params.eventID + - " failed with error: " + - err, - ); - }); + // broadcast a Delete profile message to all followers so that at least Mastodon servers will delete their local profile information + // const guidUpdateObject = crypto.randomBytes(16).toString("hex"); + const jsonUpdateObject = JSON.parse(event.activityPubActor); + // first broadcast AP messages, THEN delete from DB + broadcastDeleteMessage( + jsonUpdateObject, + event.followers, + req.params.eventID, + function (_statuses) { + Event.deleteOne({ id: req.params.eventID }, function (err, _raw) { + if (err) { + res.send(err); + addToLog( + "deleteEvent", + "error", + "Attempt to delete event " + + req.params.eventID + + " failed with error: " + + err, + ); + } + }) + .then(() => { + // Delete image + if (eventImage) { + fs.unlink( + path.join(process.cwd(), "/public/events/" + eventImage), + (err) => { + if (err) { + res.send(err); + addToLog( + "deleteEvent", + "error", + "Attempt to delete event image for event " + + req.params.eventID + + " failed with error: " + + err, + ); + } + // Image removed + addToLog( + "deleteEvent", + "success", + "Event " + req.params.eventID + " deleted", + ); }, + ); + } + res.writeHead(302, { + Location: "/", + }); + res.end(); + + const attendeeEmails = + event?.attendees + ?.filter((o) => o.status === "attending" && o.email) + .map((o) => o.email || "") || []; + if (attendeeEmails.length) { + console.log("Sending emails to: " + attendeeEmails); + req.emailService + .sendEmailFromTemplate({ + to: attendeeEmails, + subject: i18next.t("routes.deleteeventsubject", { + eventName: event?.name, + }), + templateName: "deleteEvent", + templateData: { + eventName: event?.name, + }, + }) + .catch((e) => { + console.error( + "error sending attendee email", + e.toString(), + ); + res.status(500).end(); + }); + } else { + console.log("Nothing to send!"); + } + }) + .catch((err) => { + res.send( + "Sorry! Something went wrong (error deleting): " + err, ); - } else { - // Token doesn't match - res.send("Sorry! Something went wrong"); addToLog( - "deleteEvent", - "error", - "Attempt to delete event " + - req.params.eventID + - " failed with error: token does not match", - ); - } - }) - .catch((err) => { - res.send("Sorry! Something went wrong: " + err); - addToLog( - "deleteEvent", - "error", - "Attempt to delete event " + + "deleteEvent", + "error", + "Attempt to delete event " + req.params.eventID + " failed with error: " + err, - ); - }); + ); + }); + }, + ); + } else { + // Token doesn't match + res.send("Sorry! Something went wrong"); + addToLog( + "deleteEvent", + "error", + "Attempt to delete event " + + req.params.eventID + + " failed with error: token does not match", + ); + } + }) + .catch((err) => { + res.send("Sorry! Something went wrong: " + err); + addToLog( + "deleteEvent", + "error", + "Attempt to delete event " + + req.params.eventID + + " failed with error: " + + err, + ); + }); }); router.post("/deleteeventgroup/:eventGroupID/:editToken", (req, res) => { - let submittedEditToken = req.params.editToken; - EventGroup.findOne({ - id: req.params.eventGroupID, - }) - .then(async (eventGroup) => { - if (eventGroup.editToken === submittedEditToken) { - // Token matches + let submittedEditToken = req.params.editToken; + EventGroup.findOne({ + id: req.params.eventGroupID, + }) + .then(async (eventGroup) => { + if (eventGroup.editToken === submittedEditToken) { + // Token matches - let linkedEvents = await Event.find({ - eventGroup: eventGroup._id, - }); + let linkedEvents = await Event.find({ + eventGroup: eventGroup._id, + }); - let linkedEventIDs = linkedEvents.map((event) => event._id); - let eventGroupImage = false; - if (eventGroup.image) { - eventGroupImage = eventGroup.image; - } + let linkedEventIDs = linkedEvents.map((event) => event._id); + let eventGroupImage = false; + if (eventGroup.image) { + eventGroupImage = eventGroup.image; + } - EventGroup.deleteOne( - { id: req.params.eventGroupID }, - function (err, _raw) { - if (err) { - res.send(err); - addToLog( - "deleteEventGroup", - "error", - "Attempt to delete event group " + - req.params.eventGroupID + - " failed with error: " + - err, - ); - } - }, - ) - .then(() => { - // Delete image - if (eventGroupImage) { - fs.unlink( - path.join( - process.cwd(), - "/public/events/" + eventGroupImage, - ), - (err) => { - if (err) { - res.send(err); - addToLog( - "deleteEventGroup", - "error", - "Attempt to delete event image for event group " + - req.params.eventGroupID + - " failed with error: " + - err, - ); - } - }, - ); - } - Event.updateOne( - { _id: { $in: linkedEventIDs } }, - { $set: { eventGroup: null } }, - { multi: true }, - ) - .then((_response) => { - addToLog( - "deleteEventGroup", - "success", - "Event group " + - req.params.eventGroupID + - " deleted", - ); - res.writeHead(302, { - Location: "/", - }); - res.end(); - }) - .catch((err) => { - res.send( - "Sorry! Something went wrong (error deleting): " + - err, - ); - addToLog( - "deleteEventGroup", - "error", - "Attempt to delete event group " + - req.params.eventGroupID + - " failed with error: " + - err, - ); - }); - }) - .catch((err) => { - res.send( - "Sorry! Something went wrong (error deleting): " + - err, - ); - addToLog( - "deleteEventGroup", - "error", - "Attempt to delete event group " + - req.params.eventGroupID + - " failed with error: " + - err, - ); - }); - } else { - // Token doesn't match - res.send("Sorry! Something went wrong"); - addToLog( - "deleteEventGroup", - "error", - "Attempt to delete event group " + - req.params.eventGroupID + - " failed with error: token does not match", - ); - } - }) - .catch((err) => { - res.send("Sorry! Something went wrong: " + err); - addToLog( + EventGroup.deleteOne( + { id: req.params.eventGroupID }, + function (err, _raw) { + if (err) { + res.send(err); + addToLog( "deleteEventGroup", "error", "Attempt to delete event group " + + req.params.eventGroupID + + " failed with error: " + + err, + ); + } + }, + ) + .then(() => { + // Delete image + if (eventGroupImage) { + fs.unlink( + path.join(process.cwd(), "/public/events/" + eventGroupImage), + (err) => { + if (err) { + res.send(err); + addToLog( + "deleteEventGroup", + "error", + "Attempt to delete event image for event group " + + req.params.eventGroupID + + " failed with error: " + + err, + ); + } + }, + ); + } + Event.updateOne( + { _id: { $in: linkedEventIDs } }, + { $set: { eventGroup: null } }, + { multi: true }, + ) + .then((_response) => { + addToLog( + "deleteEventGroup", + "success", + "Event group " + req.params.eventGroupID + " deleted", + ); + res.writeHead(302, { + Location: "/", + }); + res.end(); + }) + .catch((err) => { + res.send( + "Sorry! Something went wrong (error deleting): " + err, + ); + addToLog( + "deleteEventGroup", + "error", + "Attempt to delete event group " + req.params.eventGroupID + " failed with error: " + err, + ); + }); + }) + .catch((err) => { + res.send("Sorry! Something went wrong (error deleting): " + err); + addToLog( + "deleteEventGroup", + "error", + "Attempt to delete event group " + + req.params.eventGroupID + + " failed with error: " + + err, ); - }); + }); + } else { + // Token doesn't match + res.send("Sorry! Something went wrong"); + addToLog( + "deleteEventGroup", + "error", + "Attempt to delete event group " + + req.params.eventGroupID + + " failed with error: token does not match", + ); + } + }) + .catch((err) => { + res.send("Sorry! Something went wrong: " + err); + addToLog( + "deleteEventGroup", + "error", + "Attempt to delete event group " + + req.params.eventGroupID + + " failed with error: " + + err, + ); + }); }); router.post("/attendee/provision", async (req, res) => { - const removalPassword = niceware.generatePassphrase(6).join("-"); - const newAttendee = { - status: "provisioned", - removalPassword, - created: Date.now(), - }; + const removalPassword = niceware.generatePassphrase(6).join("-"); + const newAttendee = { + status: "provisioned", + removalPassword, + created: Date.now(), + }; - const event = await Event.findOne({ id: req.query.eventID }).catch((e) => { - addToLog( - "provisionEventAttendee", - "error", - "Attempt to provision attendee in event " + - req.query.eventID + - " failed with error: " + - e, - ); - return res.sendStatus(500); - }); + const event = await Event.findOne({ id: req.query.eventID }).catch((e) => { + addToLog( + "provisionEventAttendee", + "error", + "Attempt to provision attendee in event " + + req.query.eventID + + " failed with error: " + + e, + ); + return res.sendStatus(500); + }); - if (!event) { - return res.sendStatus(404); - } + if (!event) { + return res.sendStatus(404); + } - event.attendees.push(newAttendee); - await event.save().catch((e) => { - console.log(e); - addToLog( - "provisionEventAttendee", - "error", - "Attempt to provision attendee in event " + - req.query.eventID + - " failed with error: " + - e, - ); - return res.sendStatus(500); - }); + event.attendees.push(newAttendee); + await event.save().catch((e) => { + console.log(e); addToLog( - "provisionEventAttendee", - "success", - "Attendee provisioned in event " + req.query.eventID, + "provisionEventAttendee", + "error", + "Attempt to provision attendee in event " + + req.query.eventID + + " failed with error: " + + e, ); + return res.sendStatus(500); + }); + addToLog( + "provisionEventAttendee", + "success", + "Attendee provisioned in event " + req.query.eventID, + ); - // Return the removal password and the number of free spots remaining - let freeSpots; - if (event.maxAttendees !== null && event.maxAttendees !== undefined) { - freeSpots = - event.maxAttendees - - event.attendees.reduce( - (acc, a) => - acc + (a.status === "attending" ? a.number || 1 : 0), - 0, - ); - } else { - freeSpots = undefined; - } - return res.json({ removalPassword, freeSpots }); + // Return the removal password and the number of free spots remaining + let freeSpots; + if (event.maxAttendees !== null && event.maxAttendees !== undefined) { + freeSpots = + event.maxAttendees - + event.attendees.reduce( + (acc, a) => acc + (a.status === "attending" ? a.number || 1 : 0), + 0, + ); + } else { + freeSpots = undefined; + } + return res.json({ removalPassword, freeSpots }); }); router.post("/attendevent/:eventID", async (req, res) => { - // Do not allow empty removal passwords - if (!req.body.removalPassword) { - return res.sendStatus(500); - } - const event = await Event.findOne({ id: req.params.eventID }).catch((e) => { - addToLog( - "attendEvent", - "error", - "Attempt to attend event " + - req.params.eventID + - " failed with error: " + - e, - ); - return res.sendStatus(500); - }); - if (!event) { - return res.sendStatus(404); - } - const attendee = event.attendees.find( - (a) => a.removalPassword === req.body.removalPassword, + // Do not allow empty removal passwords + if (!req.body.removalPassword) { + return res.sendStatus(500); + } + const event = await Event.findOne({ id: req.params.eventID }).catch((e) => { + addToLog( + "attendEvent", + "error", + "Attempt to attend event " + + req.params.eventID + + " failed with error: " + + e, ); - if (!attendee) { - return res.sendStatus(404); - } - // Do we have enough free spots in this event to accomodate this attendee? - // First, check if the event has a max number of attendees - if (event.maxAttendees !== null && event.maxAttendees !== undefined) { - const freeSpots = - event.maxAttendees - - event.attendees.reduce( - (acc, a) => - acc + (a.status === "attending" ? a.number || 1 : 0), - 0, - ); - if (req.body.attendeeNumber > freeSpots) { - return res.sendStatus(403); - } + return res.sendStatus(500); + }); + if (!event) { + return res.sendStatus(404); + } + const attendee = event.attendees.find( + (a) => a.removalPassword === req.body.removalPassword, + ); + if (!attendee) { + return res.sendStatus(404); + } + // Do we have enough free spots in this event to accomodate this attendee? + // First, check if the event has a max number of attendees + if (event.maxAttendees !== null && event.maxAttendees !== undefined) { + const freeSpots = + event.maxAttendees - + event.attendees.reduce( + (acc, a) => acc + (a.status === "attending" ? a.number || 1 : 0), + 0, + ); + if (req.body.attendeeNumber > freeSpots) { + return res.sendStatus(403); } + } - Event.findOneAndUpdate( - { - id: req.params.eventID, - "attendees.removalPassword": req.body.removalPassword, - }, - { - $set: { - "attendees.$.status": "attending", - "attendees.$.name": req.body.attendeeName, - "attendees.$.email": req.body.attendeeEmail, - "attendees.$.number": req.body.attendeeNumber, - "attendees.$.visibility": req.body.attendeeVisible - ? "public" - : "private", - }, - }, - ) - .then((event) => { - if (!event) { - return res.sendStatus(404); - } + Event.findOneAndUpdate( + { + id: req.params.eventID, + "attendees.removalPassword": req.body.removalPassword, + }, + { + $set: { + "attendees.$.status": "attending", + "attendees.$.name": req.body.attendeeName, + "attendees.$.email": req.body.attendeeEmail, + "attendees.$.number": req.body.attendeeNumber, + "attendees.$.visibility": req.body.attendeeVisible + ? "public" + : "private", + }, + }, + ) + .then((event) => { + if (!event) { + return res.sendStatus(404); + } - addToLog( - "addEventAttendee", - "success", - "Attendee added to event " + req.params.eventID, - ); - if (req.body.attendeeEmail) { - req.emailService - .sendEmailFromTemplate({ - to: req.body.attendeeEmail, - subject: i18next.t("routes.addeventattendeesubject", { - eventName: event?.name, - }), - templateName: "addEventAttendee", - templateData: { - eventID: req.params.eventID, - removalPassword: req.body.removalPassword, - removalPasswordHash: hashString( - req.body.removalPassword, - ), - }, - }) - .catch((e) => { - console.error( - "error sending addEventAttendee email", - e.toString(), - ); - res.status(500).end(); - }); - } - res.redirect(`/${req.params.eventID}`); - }) - .catch((error) => { - res.send("Database error, please try again :("); - addToLog( - "addEventAttendee", - "error", - "Attempt to add attendee to event " + - req.params.eventID + - " failed with error: " + - error, - ); - }); + addToLog( + "addEventAttendee", + "success", + "Attendee added to event " + req.params.eventID, + ); + if (req.body.attendeeEmail) { + req.emailService + .sendEmailFromTemplate({ + to: req.body.attendeeEmail, + subject: i18next.t("routes.addeventattendeesubject", { + eventName: event?.name, + }), + templateName: "addEventAttendee", + templateData: { + eventID: req.params.eventID, + removalPassword: req.body.removalPassword, + removalPasswordHash: hashString(req.body.removalPassword), + }, + }) + .catch((e) => { + console.error("error sending addEventAttendee email", e.toString()); + res.status(500).end(); + }); + } + res.redirect(`/${req.params.eventID}`); + }) + .catch((error) => { + res.send("Database error, please try again :("); + addToLog( + "addEventAttendee", + "error", + "Attempt to add attendee to event " + + req.params.eventID + + " failed with error: " + + error, + ); + }); }); // this is a one-click unattend that requires a secret URL that only the person who RSVPed over // activitypub knows router.get("/oneclickunattendevent/:eventID/:attendeeID", (req, res) => { - // Mastodon and Pleroma will "click" links that sent to its users, presumably as a prefetch? - // Anyway, this ignores the automated clicks that are done without the user's knowledge - if ( - req.headers["user-agent"] && - (req.headers["user-agent"].toLowerCase().includes("mastodon") || - req.headers["user-agent"].toLowerCase().includes("pleroma")) - ) { - return res.sendStatus(200); - } - Event.findOneAndUpdate( - { id: req.params.eventID }, - { $pull: { attendees: { _id: req.params.attendeeID } } }, - ) - .then((event) => { - if (!event) { - return res.sendStatus(404); - } - addToLog( - "oneClickUnattend", - "success", - "Attendee removed via one click unattend " + req.params.eventID, - ); - // currently this is never called because we don't have the email address - if (req.body.attendeeEmail) { - req.emailService - .sendEmailFromTemplate({ - to: req.body.attendeeEmail, - subject: i18next.t("routes.removeeventattendeesubject"), - templateName: "removeEventAttendee", - templateData: { - eventName: event.name, - }, - }) - .catch((e) => { - console.error( - "error sending removeEventAttendeeHtml email", - e.toString(), - ); - res.status(500).end(); - }); - } - res.writeHead(302, { - Location: "/" + req.params.eventID, - }); - res.end(); - }) - .catch((err) => { - res.send("Database error, please try again :("); - addToLog( - "removeEventAttendee", - "error", - "Attempt to remove attendee by admin from event " + - req.params.eventID + - " failed with error: " + - err, + // Mastodon and Pleroma will "click" links that sent to its users, presumably as a prefetch? + // Anyway, this ignores the automated clicks that are done without the user's knowledge + if ( + req.headers["user-agent"] && + (req.headers["user-agent"].toLowerCase().includes("mastodon") || + req.headers["user-agent"].toLowerCase().includes("pleroma")) + ) { + return res.sendStatus(200); + } + Event.findOneAndUpdate( + { id: req.params.eventID }, + { $pull: { attendees: { _id: req.params.attendeeID } } }, + ) + .then((event) => { + if (!event) { + return res.sendStatus(404); + } + addToLog( + "oneClickUnattend", + "success", + "Attendee removed via one click unattend " + req.params.eventID, + ); + // currently this is never called because we don't have the email address + if (req.body.attendeeEmail) { + req.emailService + .sendEmailFromTemplate({ + to: req.body.attendeeEmail, + subject: i18next.t("routes.removeeventattendeesubject"), + templateName: "removeEventAttendee", + templateData: { + eventName: event.name, + }, + }) + .catch((e) => { + console.error( + "error sending removeEventAttendeeHtml email", + e.toString(), ); - }); + res.status(500).end(); + }); + } + res.writeHead(302, { + Location: "/" + req.params.eventID, + }); + res.end(); + }) + .catch((err) => { + res.send("Database error, please try again :("); + addToLog( + "removeEventAttendee", + "error", + "Attempt to remove attendee by admin from event " + + req.params.eventID + + " failed with error: " + + err, + ); + }); }); router.post("/removeattendee/:eventID/:attendeeID", (req, res) => { - Event.findOneAndUpdate( - { id: req.params.eventID }, - { $pull: { attendees: { _id: req.params.attendeeID } } }, - ) - .then((event) => { - if (!event) { - return res.sendStatus(404); - } - addToLog( - "removeEventAttendee", - "success", - "Attendee removed by admin from event " + req.params.eventID, - ); - // currently this is never called because we don't have the email address - if (req.body.attendeeEmail) { - req.emailService - .sendEmailFromTemplate({ - to: req.body.attendeeEmail, - subject: i18next.t("routes.removeeventattendeesubject"), - templateName: "removeEventAttendee", - templateData: { - eventName: event.name, - }, - }) - .catch((e) => { - console.error( - "error sending removeEventAttendeeHtml email", - e.toString(), - ); - res.status(500).end(); - }); - } - res.writeHead(302, { - Location: "/" + req.params.eventID, - }); - res.end(); - }) - .catch((err) => { - res.send("Database error, please try again :("); - addToLog( - "removeEventAttendee", - "error", - "Attempt to remove attendee by admin from event " + - req.params.eventID + - " failed with error: " + - err, + Event.findOneAndUpdate( + { id: req.params.eventID }, + { $pull: { attendees: { _id: req.params.attendeeID } } }, + ) + .then((event) => { + if (!event) { + return res.sendStatus(404); + } + addToLog( + "removeEventAttendee", + "success", + "Attendee removed by admin from event " + req.params.eventID, + ); + // currently this is never called because we don't have the email address + if (req.body.attendeeEmail) { + req.emailService + .sendEmailFromTemplate({ + to: req.body.attendeeEmail, + subject: i18next.t("routes.removeeventattendeesubject"), + templateName: "removeEventAttendee", + templateData: { + eventName: event.name, + }, + }) + .catch((e) => { + console.error( + "error sending removeEventAttendeeHtml email", + e.toString(), ); - }); + res.status(500).end(); + }); + } + res.writeHead(302, { + Location: "/" + req.params.eventID, + }); + res.end(); + }) + .catch((err) => { + res.send("Database error, please try again :("); + addToLog( + "removeEventAttendee", + "error", + "Attempt to remove attendee by admin from event " + + req.params.eventID + + " failed with error: " + + err, + ); + }); }); /* * Create an email subscription on an event group. */ router.post("/subscribe/:eventGroupID", (req, res) => { - const subscriber = { - email: req.body.emailAddress, - }; - if (!subscriber.email) { - return res.sendStatus(500); - } - - EventGroup.findOne({ - id: req.params.eventGroupID, - }) - .then((eventGroup) => { - if (!eventGroup) { - return res.sendStatus(404); - } - eventGroup.subscribers.push(subscriber); - eventGroup.save(); - req.emailService - .sendEmailFromTemplate({ - to: subscriber.email, - subject: i18next.t("routes.subscribedsubject"), - templateName: "subscribed", - templateData: { - eventGroupName: eventGroup.name, - eventGroupID: eventGroup.id, - emailAddress: encodeURIComponent(subscriber.email), - }, - }) - .catch((e) => { - console.error( - "error sending removeEventAttendeeHtml email", - e.toString(), - ); - res.status(500).end(); - }); + const subscriber = { + email: req.body.emailAddress, + }; + if (!subscriber.email) { + return res.sendStatus(500); + } - return res.redirect(`/group/${eventGroup.id}`); + EventGroup.findOne({ + id: req.params.eventGroupID, + }) + .then((eventGroup) => { + if (!eventGroup) { + return res.sendStatus(404); + } + eventGroup.subscribers.push(subscriber); + eventGroup.save(); + req.emailService + .sendEmailFromTemplate({ + to: subscriber.email, + subject: i18next.t("routes.subscribedsubject"), + templateName: "subscribed", + templateData: { + eventGroupName: eventGroup.name, + eventGroupID: eventGroup.id, + emailAddress: encodeURIComponent(subscriber.email), + }, }) - .catch((error) => { - addToLog( - "addSubscription", - "error", - "Attempt to subscribe " + - req.body.emailAddress + - " to event group " + - req.params.eventGroupID + - " failed with error: " + - error, - ); - return res.sendStatus(500); + .catch((e) => { + console.error( + "error sending removeEventAttendeeHtml email", + e.toString(), + ); + res.status(500).end(); }); + + return res.redirect(`/group/${eventGroup.id}`); + }) + .catch((error) => { + addToLog( + "addSubscription", + "error", + "Attempt to subscribe " + + req.body.emailAddress + + " to event group " + + req.params.eventGroupID + + " failed with error: " + + error, + ); + return res.sendStatus(500); + }); }); /* * Delete an existing email subscription on an event group. */ router.get("/unsubscribe/:eventGroupID", (req, res) => { - const email = req.query.email; - if (!email) { - return res.sendStatus(500); - } + const email = req.query.email; + if (!email) { + return res.sendStatus(500); + } - EventGroup.updateOne( - { id: req.params.eventGroupID }, - { $pull: { subscribers: { email } } }, - ) - .then((_response) => { - return res.redirect("/"); - }) - .catch((error) => { - addToLog( - "removeSubscription", - "error", - "Attempt to unsubscribe " + - req.query.email + - " from event group " + - req.params.eventGroupID + - " failed with error: " + - error, - ); - return res.sendStatus(500); - }); + EventGroup.updateOne( + { id: req.params.eventGroupID }, + { $pull: { subscribers: { email } } }, + ) + .then((_response) => { + return res.redirect("/"); + }) + .catch((error) => { + addToLog( + "removeSubscription", + "error", + "Attempt to unsubscribe " + + req.query.email + + " from event group " + + req.params.eventGroupID + + " failed with error: " + + error, + ); + return res.sendStatus(500); + }); }); router.post("/post/comment/:eventID", (req, res) => { - let commentID = nanoid(); - const newComment = { - id: commentID, - author: req.body.commentAuthor, - content: req.body.commentContent, - timestamp: moment(), - }; + let commentID = nanoid(); + const newComment = { + id: commentID, + author: req.body.commentAuthor, + content: req.body.commentContent, + timestamp: moment(), + }; - Event.findOne( - { - id: req.params.eventID, - }, - function (err, event) { - if (!event) { - return res.sendStatus(404); - } - event.comments.push(newComment); - event - .save() - .then(() => { - addToLog( - "addEventComment", - "success", - "Comment added to event " + req.params.eventID, - ); - // broadcast an identical message to all followers, will show in their home timeline - // and in the home timeline of the event - const guidObject = crypto.randomBytes(16).toString("hex"); - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - id: `https://${domain}/${req.params.eventID}/m/${guidObject}`, - name: `Comment on ${event.name}`, - type: "Note", - cc: "https://www.w3.org/ns/activitystreams#Public", - content: `

${req.body.commentAuthor} commented: ${req.body.commentContent}.

See the full conversation here.

`, - }; - broadcastCreateMessage( - jsonObject, - event.followers, - req.params.eventID, - ); - if (!event) { - return res.sendStatus(404); - } + Event.findOne( + { + id: req.params.eventID, + }, + function (err, event) { + if (!event) { + return res.sendStatus(404); + } + event.comments.push(newComment); + event + .save() + .then(() => { + addToLog( + "addEventComment", + "success", + "Comment added to event " + req.params.eventID, + ); + // broadcast an identical message to all followers, will show in their home timeline + // and in the home timeline of the event + const guidObject = crypto.randomBytes(16).toString("hex"); + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${req.params.eventID}/m/${guidObject}`, + name: `Comment on ${event.name}`, + type: "Note", + cc: "https://www.w3.org/ns/activitystreams#Public", + content: `

${req.body.commentAuthor} commented: ${req.body.commentContent}.

See the full conversation here.

`, + }; + broadcastCreateMessage( + jsonObject, + event.followers, + req.params.eventID, + ); + if (!event) { + return res.sendStatus(404); + } - Event.findOne({ id: req.params.eventID }).then((event) => { - const attendeeEmails = - event.attendees - .filter( - (o) => o.status === "attending" && o.email, - ) - .map((o) => o.email || "") || []; - if (attendeeEmails.length) { - console.log("Sending emails to: " + attendeeEmails); - req.emailService - .sendEmailFromTemplate({ - to: - event?.creatorEmail || - config.general.email, - bcc: attendeeEmails, - subject: i18next.t( - "routes.addeventcommentsubject", - { eventName: event?.name }, - ), - templateName: "addEventComment", - templateData: { - eventID: req.params.eventID, - commentAuthor: req.body.commentAuthor, - }, - }) - .catch((e) => { - console.error( - "error sending removeEventAttendeeHtml email", - e.toString(), - ); - res.status(500).end(); - }); - } else { - console.log("Nothing to send!"); - } - }); - res.writeHead(302, { - Location: "/" + req.params.eventID, - }); - res.end(); + Event.findOne({ id: req.params.eventID }).then((event) => { + const attendeeEmails = + event.attendees + .filter((o) => o.status === "attending" && o.email) + .map((o) => o.email || "") || []; + if (attendeeEmails.length) { + console.log("Sending emails to: " + attendeeEmails); + req.emailService + .sendEmailFromTemplate({ + to: event?.creatorEmail || config.general.email, + bcc: attendeeEmails, + subject: i18next.t("routes.addeventcommentsubject", { + eventName: event?.name, + }), + templateName: "addEventComment", + templateData: { + eventID: req.params.eventID, + commentAuthor: req.body.commentAuthor, + }, }) - .catch((err) => { - res.send("Database error, please try again :(" + err); - addToLog( - "addEventComment", - "error", - "Attempt to add comment to event " + - req.params.eventID + - " failed with error: " + - err, - ); + .catch((e) => { + console.error( + "error sending removeEventAttendeeHtml email", + e.toString(), + ); + res.status(500).end(); }); - }, - ); + } else { + console.log("Nothing to send!"); + } + }); + res.writeHead(302, { + Location: "/" + req.params.eventID, + }); + res.end(); + }) + .catch((err) => { + res.send("Database error, please try again :(" + err); + addToLog( + "addEventComment", + "error", + "Attempt to add comment to event " + + req.params.eventID + + " failed with error: " + + err, + ); + }); + }, + ); }); router.post("/post/reply/:eventID/:commentID", (req, res) => { - let replyID = nanoid(); - let commentID = req.params.commentID; - const newReply = { - id: replyID, - author: req.body.replyAuthor, - content: req.body.replyContent, - timestamp: moment(), - }; - Event.findOne( - { - id: req.params.eventID, - }, - function (err, event) { + let replyID = nanoid(); + let commentID = req.params.commentID; + const newReply = { + id: replyID, + author: req.body.replyAuthor, + content: req.body.replyContent, + timestamp: moment(), + }; + Event.findOne( + { + id: req.params.eventID, + }, + function (err, event) { + if (!event) { + return res.sendStatus(404); + } + var parentComment = event.comments.id(commentID); + parentComment.replies.push(newReply); + event + .save() + .then(() => { + addToLog( + "addEventReply", + "success", + "Reply added to comment " + + commentID + + " in event " + + req.params.eventID, + ); + // broadcast an identical message to all followers, will show in their home timeline + const guidObject = crypto.randomBytes(16).toString("hex"); + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${domain}/${req.params.eventID}/m/${guidObject}`, + name: `Comment on ${event.name}`, + type: "Note", + cc: "https://www.w3.org/ns/activitystreams#Public", + content: `

${req.body.replyAuthor} commented: ${req.body.replyContent}

See the full conversation here.

`, + }; + broadcastCreateMessage( + jsonObject, + event.followers, + req.params.eventID, + ); + Event.findOne({ id: req.params.eventID }).then((event) => { if (!event) { - return res.sendStatus(404); + return res.sendStatus(404); } - var parentComment = event.comments.id(commentID); - parentComment.replies.push(newReply); - event - .save() - .then(() => { - addToLog( - "addEventReply", - "success", - "Reply added to comment " + - commentID + - " in event " + - req.params.eventID, - ); - // broadcast an identical message to all followers, will show in their home timeline - const guidObject = crypto.randomBytes(16).toString("hex"); - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - id: `https://${domain}/${req.params.eventID}/m/${guidObject}`, - name: `Comment on ${event.name}`, - type: "Note", - cc: "https://www.w3.org/ns/activitystreams#Public", - content: `

${req.body.replyAuthor} commented: ${req.body.replyContent}

See the full conversation here.

`, - }; - broadcastCreateMessage( - jsonObject, - event.followers, - req.params.eventID, - ); - Event.findOne({ id: req.params.eventID }).then((event) => { - if (!event) { - return res.sendStatus(404); - } - const attendeeEmails = - event.attendees - .filter( - (o) => o.status === "attending" && o.email, - ) - .map((o) => o.email || "") || []; - if (attendeeEmails.length) { - console.log("Sending emails to: " + attendeeEmails); - req.emailService - .sendEmailFromTemplate({ - to: - event?.creatorEmail || - config.general.email, - bcc: attendeeEmails, - subject: i18next.t( - "routes.addeventcommentsubject", - { eventName: event.name }, - ), - templateName: "addEventComment", - templateData: { - eventID: req.params.eventID, - commentAuthor: req.body.replyAuthor, - }, - }) - .catch((e) => { - console.error( - "error sending removeEventAttendeeHtml email", - e.toString(), - ); - res.status(500).end(); - }); - } else { - console.log("Nothing to send!"); - } - }); - res.writeHead(302, { - Location: "/" + req.params.eventID, - }); - res.end(); + const attendeeEmails = + event.attendees + .filter((o) => o.status === "attending" && o.email) + .map((o) => o.email || "") || []; + if (attendeeEmails.length) { + console.log("Sending emails to: " + attendeeEmails); + req.emailService + .sendEmailFromTemplate({ + to: event?.creatorEmail || config.general.email, + bcc: attendeeEmails, + subject: i18next.t("routes.addeventcommentsubject", { + eventName: event.name, + }), + templateName: "addEventComment", + templateData: { + eventID: req.params.eventID, + commentAuthor: req.body.replyAuthor, + }, }) - .catch((err) => { - res.send("Database error, please try again :("); - addToLog( - "addEventReply", - "error", - "Attempt to add reply to comment " + - commentID + - " in event " + - req.params.eventID + - " failed with error: " + - err, - ); + .catch((e) => { + console.error( + "error sending removeEventAttendeeHtml email", + e.toString(), + ); + res.status(500).end(); }); - }, - ); -}); - -router.post("/deletecomment/:eventID/:commentID/:editToken", (req, res) => { - let submittedEditToken = req.params.editToken; - Event.findOne({ - id: req.params.eventID, - }) - .then((event) => { - if (event.editToken === submittedEditToken) { - // Token matches - event.comments.id(req.params.commentID).remove(); - event - .save() - .then(() => { - addToLog( - "deleteComment", - "success", - "Comment deleted from event " + req.params.eventID, - ); - res.writeHead(302, { - Location: - "/" + - req.params.eventID + - "?e=" + - req.params.editToken, - }); - res.end(); - }) - .catch((err) => { - res.send( - "Sorry! Something went wrong (error deleting): " + - err, - ); - addToLog( - "deleteComment", - "error", - "Attempt to delete comment " + - req.params.commentID + - "from event " + - req.params.eventID + - " failed with error: " + - err, - ); - }); } else { - // Token doesn't match - res.send("Sorry! Something went wrong"); - addToLog( - "deleteComment", - "error", - "Attempt to delete comment " + - req.params.commentID + - "from event " + - req.params.eventID + - " failed with error: token does not match", - ); + console.log("Nothing to send!"); } + }); + res.writeHead(302, { + Location: "/" + req.params.eventID, + }); + res.end(); }) .catch((err) => { - res.send("Sorry! Something went wrong: " + err); + res.send("Database error, please try again :("); + addToLog( + "addEventReply", + "error", + "Attempt to add reply to comment " + + commentID + + " in event " + + req.params.eventID + + " failed with error: " + + err, + ); + }); + }, + ); +}); + +router.post("/deletecomment/:eventID/:commentID/:editToken", (req, res) => { + let submittedEditToken = req.params.editToken; + Event.findOne({ + id: req.params.eventID, + }) + .then((event) => { + if (event.editToken === submittedEditToken) { + // Token matches + event.comments.id(req.params.commentID).remove(); + event + .save() + .then(() => { addToLog( - "deleteComment", - "error", - "Attempt to delete comment " + - req.params.commentID + - "from event " + - req.params.eventID + - " failed with error: " + - err, + "deleteComment", + "success", + "Comment deleted from event " + req.params.eventID, ); - }); + res.writeHead(302, { + Location: "/" + req.params.eventID + "?e=" + req.params.editToken, + }); + res.end(); + }) + .catch((err) => { + res.send("Sorry! Something went wrong (error deleting): " + err); + addToLog( + "deleteComment", + "error", + "Attempt to delete comment " + + req.params.commentID + + "from event " + + req.params.eventID + + " failed with error: " + + err, + ); + }); + } else { + // Token doesn't match + res.send("Sorry! Something went wrong"); + addToLog( + "deleteComment", + "error", + "Attempt to delete comment " + + req.params.commentID + + "from event " + + req.params.eventID + + " failed with error: token does not match", + ); + } + }) + .catch((err) => { + res.send("Sorry! Something went wrong: " + err); + addToLog( + "deleteComment", + "error", + "Attempt to delete comment " + + req.params.commentID + + "from event " + + req.params.eventID + + " failed with error: " + + err, + ); + }); }); router.post("/activitypub/inbox", (req, res) => { - if (!isFederated) return res.sendStatus(404); - // validate the incoming message - const signature = req.get("signature"); - if (!signature) { - return res.status(401).send("No signature provided."); - } - let signature_header = signature - .split(",") - .map((pair) => { - return pair.split("=").map((value) => { - return value.replace(/^"/g, "").replace(/"$/g, ""); - }); - }) - .reduce((acc, el) => { - acc[el[0]] = el[1]; - return acc; - }, {}); - // get the actor - // TODO if this is a Delete for an Actor this won't work - request( - { - url: signature_header.keyId, - headers: { - Accept: activityPubContentType, - "Content-Type": activityPubContentType, - "User-Agent": `Gathio - ${domain}`, - }, - }, - function (error, response, actor) { - let publicKey = ""; + if (!isFederated) return res.sendStatus(404); + // validate the incoming message + const signature = req.get("signature"); + if (!signature) { + return res.status(401).send("No signature provided."); + } + let signature_header = signature + .split(",") + .map((pair) => { + return pair.split("=").map((value) => { + return value.replace(/^"/g, "").replace(/"$/g, ""); + }); + }) + .reduce((acc, el) => { + acc[el[0]] = el[1]; + return acc; + }, {}); + // get the actor + // TODO if this is a Delete for an Actor this won't work + request( + { + url: signature_header.keyId, + headers: { + Accept: activityPubContentType, + "Content-Type": activityPubContentType, + "User-Agent": `Gathio - ${domain}`, + }, + }, + function (error, response, actor) { + let publicKey = ""; - try { - if (JSON.parse(actor).publicKey) { - publicKey = JSON.parse(actor).publicKey.publicKeyPem; - } - } catch (err) { - return res.status(500).send("Actor could not be parsed" + err); - } + try { + if (JSON.parse(actor).publicKey) { + publicKey = JSON.parse(actor).publicKey.publicKeyPem; + } + } catch (err) { + return res.status(500).send("Actor could not be parsed" + err); + } - let comparison_string = signature_header.headers - .split(" ") - .map((header) => { - if (header === "(request-target)") { - return "(request-target): post /activitypub/inbox"; - } else { - return `${header}: ${req.get(header)}`; - } - }) - .join("\n"); - const verifier = crypto.createVerify("RSA-SHA256"); - verifier.update(comparison_string, "ascii"); - const publicKeyBuf = Buffer.from(publicKey, "ascii"); - const signatureBuf = Buffer.from( - signature_header.signature, - "base64", - ); - try { - const result = verifier.verify(publicKeyBuf, signatureBuf); - if (result) { - // actually process the ActivityPub message now that it's been verified - processInbox(req, res); - } else { - return res - .status(401) - .send("Signature could not be verified."); - } - } catch (err) { - return res - .status(401) - .send("Signature could not be verified: " + err); - } - }, - ); + let comparison_string = signature_header.headers + .split(" ") + .map((header) => { + if (header === "(request-target)") { + return "(request-target): post /activitypub/inbox"; + } else { + return `${header}: ${req.get(header)}`; + } + }) + .join("\n"); + const verifier = crypto.createVerify("RSA-SHA256"); + verifier.update(comparison_string, "ascii"); + const publicKeyBuf = Buffer.from(publicKey, "ascii"); + const signatureBuf = Buffer.from(signature_header.signature, "base64"); + try { + const result = verifier.verify(publicKeyBuf, signatureBuf); + if (result) { + // actually process the ActivityPub message now that it's been verified + processInbox(req, res); + } else { + return res.status(401).send("Signature could not be verified."); + } + } catch (err) { + return res.status(401).send("Signature could not be verified: " + err); + } + }, + ); }); router.use(function (req, res, _next) { - return res.status(404).render("404", frontendConfig(res)); + return res.status(404).render("404", frontendConfig(res)); }); addToLog("startup", "success", "Started up successfully"); diff --git a/src/routes/activitypub.ts b/src/routes/activitypub.ts index d9bf6a36..9891a269 100644 --- a/src/routes/activitypub.ts +++ b/src/routes/activitypub.ts @@ -11,243 +11,237 @@ const router = Router(); router.use(getConfigMiddleware); const send404IfNotFederated = ( - req: Request, - res: Response, - next: NextFunction, + req: Request, + res: Response, + next: NextFunction, ) => { - if (!res.locals.config?.general.is_federated) { - return res.status(404).render("404", frontendConfig(res)); - } - next(); + if (!res.locals.config?.general.is_federated) { + return res.status(404).render("404", frontendConfig(res)); + } + next(); }; // return the JSON for the featured/pinned post for this event router.get( - "/:eventID/featured", - send404IfNotFederated, - (req: Request, res: Response) => { - const { eventID } = req.params; - const featured = { - "@context": "https://www.w3.org/ns/activitystreams", - id: `https://${res.locals.config?.general.domain}/${eventID}/featured`, - type: "OrderedCollection", - orderedItems: [createFeaturedPost(eventID)], - }; - if (acceptsActivityPub(req)) { - res.header("Content-Type", "application/activity+json").send( - featured, - ); - } else { - res.header("Content-Type", "application/json").send(featured); - } - }, + "/:eventID/featured", + send404IfNotFederated, + (req: Request, res: Response) => { + const { eventID } = req.params; + const featured = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${res.locals.config?.general.domain}/${eventID}/featured`, + type: "OrderedCollection", + orderedItems: [createFeaturedPost(eventID)], + }; + if (acceptsActivityPub(req)) { + res.header("Content-Type", "application/activity+json").send(featured); + } else { + res.header("Content-Type", "application/json").send(featured); + } + }, ); // return the JSON for a given activitypub message router.get( - "/:eventID/m/:hash", - send404IfNotFederated, - async (req: Request, res: Response) => { - const { hash, eventID } = req.params; - const id = `https://${res.locals.config?.general.domain}/${eventID}/m/${hash}`; - - try { - const event = await Event.findOne({ - id: eventID, - }); - if (!event) { - return res.status(404).render("404", frontendConfig(res)); - } else { - if (!event.activityPubMessages) { - return res.status(404).render("404", frontendConfig(res)); - } - const message = event.activityPubMessages.find( - (el) => el.id === id, - ); - if (message) { - if (acceptsActivityPub(req)) { - res.header( - "Content-Type", - "application/activity+json", - ).send(JSON.parse(message.content || "{}")); - } else { - res.header("Content-Type", "application/json").send( - JSON.parse(message.content || "{}"), - ); - } - } else { - return res.status(404).render("404", frontendConfig(res)); - } - } - } catch (err) { - addToLog( - "getActivityPubMessage", - "error", - "Attempt to get Activity Pub Message for " + - id + - " failed with error: " + - err, - ); - return res.status(404).render("404", frontendConfig(res)); + "/:eventID/m/:hash", + send404IfNotFederated, + async (req: Request, res: Response) => { + const { hash, eventID } = req.params; + const id = `https://${res.locals.config?.general.domain}/${eventID}/m/${hash}`; + + try { + const event = await Event.findOne({ + id: eventID, + }); + if (!event) { + return res.status(404).render("404", frontendConfig(res)); + } else { + if (!event.activityPubMessages) { + return res.status(404).render("404", frontendConfig(res)); + } + const message = event.activityPubMessages.find((el) => el.id === id); + if (message) { + if (acceptsActivityPub(req)) { + res + .header("Content-Type", "application/activity+json") + .send(JSON.parse(message.content || "{}")); + } else { + res + .header("Content-Type", "application/json") + .send(JSON.parse(message.content || "{}")); + } + } else { + return res.status(404).render("404", frontendConfig(res)); } - }, + } + } catch (err) { + addToLog( + "getActivityPubMessage", + "error", + "Attempt to get Activity Pub Message for " + + id + + " failed with error: " + + err, + ); + return res.status(404).render("404", frontendConfig(res)); + } + }, ); router.get("/.well-known/nodeinfo", send404IfNotFederated, (req, res) => { + if (!res.locals.config?.general.is_federated) { + return res.status(404).render("404", frontendConfig(res)); + } + const nodeInfo = { + links: [ + { + rel: "http://nodeinfo.diaspora.software/ns/schema/2.2", + href: `https://${res.locals.config?.general.domain}/.well-known/nodeinfo/2.2`, + }, + ], + }; + res + .header( + "Content-Type", + 'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.1#"', + ) + .send(nodeInfo); +}); + +router.get( + "/.well-known/nodeinfo/2.2", + send404IfNotFederated, + async (req, res) => { + const eventCount = await Event.countDocuments(); + if (!res.locals.config?.general.is_federated) { - return res.status(404).render("404", frontendConfig(res)); + return res.status(404).render("404", frontendConfig(res)); } const nodeInfo = { - links: [ - { - rel: "http://nodeinfo.diaspora.software/ns/schema/2.2", - href: `https://${res.locals.config?.general.domain}/.well-known/nodeinfo/2.2`, - }, - ], + version: "2.2", + instance: { + name: res.locals.config?.general.site_name, + description: + "Federated, no-registration, privacy-respecting event hosting.", + }, + software: { + name: "Gathio", + version: process.env.npm_package_version || "unknown", + repository: "https://github.com/lowercasename/gathio", + homepage: "https://gath.io", + }, + protocols: ["activitypub"], + services: { + inbound: [], + outbound: [], + }, + openRegistrations: true, + usage: { + users: { + total: eventCount, + }, + }, }; - res.header( + res + .header( "Content-Type", 'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.1#"', - ).send(nodeInfo); -}); - -router.get( - "/.well-known/nodeinfo/2.2", - send404IfNotFederated, - async (req, res) => { - const eventCount = await Event.countDocuments(); - - if (!res.locals.config?.general.is_federated) { - return res.status(404).render("404", frontendConfig(res)); - } - const nodeInfo = { - version: "2.2", - instance: { - name: res.locals.config?.general.site_name, - description: - "Federated, no-registration, privacy-respecting event hosting.", - }, - software: { - name: "Gathio", - version: process.env.npm_package_version || "unknown", - repository: "https://github.com/lowercasename/gathio", - homepage: "https://gath.io", - }, - protocols: ["activitypub"], - services: { - inbound: [], - outbound: [], - }, - openRegistrations: true, - usage: { - users: { - total: eventCount, - }, - }, - }; - res.header( - "Content-Type", - 'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.1#"', - ).send(nodeInfo); - }, + ) + .send(nodeInfo); + }, ); router.get( - "/.well-known/webfinger", - send404IfNotFederated, - async (req, res) => { - const resource = req.query.resource as string; - if (!resource || !resource.includes("acct:")) { - return res - .status(400) - .send( - 'Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.', - ); - } else { - // "foo@domain" - const activityPubAccount = resource.replace("acct:", ""); - // "foo" - const eventID = activityPubAccount.replace(/@.*/, ""); - - try { - const event = await Event.findOne({ id: eventID }); - - if (!event) { - return res.status(404).render("404", frontendConfig(res)); - } else { - if (acceptsActivityPub(req)) { - res.header( - "Content-Type", - "application/activity+json", - ).send( - createWebfinger( - eventID, - res.locals.config?.general.domain, - ), - ); - } else { - res.header("Content-Type", "application/json").send( - createWebfinger( - eventID, - res.locals.config?.general.domain, - ), - ); - } - } - } catch (err) { - addToLog( - "renderWebfinger", - "error", - `Attempt to render webfinger for ${resource} failed with error: ${err}`, - ); - return res.status(404).render("404", frontendConfig(res)); - } - } - }, -); - -router.get("/:eventID/followers", send404IfNotFederated, async (req, res) => { - const eventID = req.params.eventID; + "/.well-known/webfinger", + send404IfNotFederated, + async (req, res) => { + const resource = req.query.resource as string; + if (!resource || !resource.includes("acct:")) { + return res + .status(400) + .send( + 'Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.', + ); + } else { + // "foo@domain" + const activityPubAccount = resource.replace("acct:", ""); + // "foo" + const eventID = activityPubAccount.replace(/@.*/, ""); - try { + try { const event = await Event.findOne({ id: eventID }); - if (event && event.followers) { - const followers = event.followers.map((el) => el.actorId); - const followersCollection = { - type: "OrderedCollection", - totalItems: followers.length, - id: `https://${res.locals.config?.general.domain}/${eventID}/followers`, - first: { - type: "OrderedCollectionPage", - totalItems: followers.length, - partOf: `https://${res.locals.config?.general.domain}/${eventID}/followers`, - orderedItems: followers, - id: `https://${res.locals.config?.general.domain}/${eventID}/followers?page=1`, - }, - "@context": ["https://www.w3.org/ns/activitystreams"], - }; - - if (acceptsActivityPub(req)) { - return res - .header("Content-Type", "application/activity+json") - .send(followersCollection); - } else { - return res - .header("Content-Type", "application/json") - .send(followersCollection); - } + if (!event) { + return res.status(404).render("404", frontendConfig(res)); } else { - return res.status(400).send("Bad request."); + if (acceptsActivityPub(req)) { + res + .header("Content-Type", "application/activity+json") + .send( + createWebfinger(eventID, res.locals.config?.general.domain), + ); + } else { + res + .header("Content-Type", "application/json") + .send( + createWebfinger(eventID, res.locals.config?.general.domain), + ); + } } - } catch (err) { + } catch (err) { addToLog( - "renderFollowers", - "error", - `Attempt to render followers for ${eventID} failed with error: ${err}`, + "renderWebfinger", + "error", + `Attempt to render webfinger for ${resource} failed with error: ${err}`, ); return res.status(404).render("404", frontendConfig(res)); + } + } + }, +); + +router.get("/:eventID/followers", send404IfNotFederated, async (req, res) => { + const eventID = req.params.eventID; + + try { + const event = await Event.findOne({ id: eventID }); + + if (event && event.followers) { + const followers = event.followers.map((el) => el.actorId); + const followersCollection = { + type: "OrderedCollection", + totalItems: followers.length, + id: `https://${res.locals.config?.general.domain}/${eventID}/followers`, + first: { + type: "OrderedCollectionPage", + totalItems: followers.length, + partOf: `https://${res.locals.config?.general.domain}/${eventID}/followers`, + orderedItems: followers, + id: `https://${res.locals.config?.general.domain}/${eventID}/followers?page=1`, + }, + "@context": ["https://www.w3.org/ns/activitystreams"], + }; + + if (acceptsActivityPub(req)) { + return res + .header("Content-Type", "application/activity+json") + .send(followersCollection); + } else { + return res + .header("Content-Type", "application/json") + .send(followersCollection); + } + } else { + return res.status(400).send("Bad request."); } + } catch (err) { + addToLog( + "renderFollowers", + "error", + `Attempt to render followers for ${eventID} failed with error: ${err}`, + ); + return res.status(404).render("404", frontendConfig(res)); + } }); export default router; diff --git a/src/routes/event.ts b/src/routes/event.ts index be27590f..30d0a4a8 100644 --- a/src/routes/event.ts +++ b/src/routes/event.ts @@ -3,24 +3,24 @@ import multer from "multer"; import Jimp from "jimp"; import moment from "moment-timezone"; import { - generateEditToken, - generateEventID, - generateRSAKeypair, - hashString, + generateEditToken, + generateEventID, + generateRSAKeypair, + hashString, } from "../util/generator.js"; import { validateEventData } from "../util/validation.js"; import { addToLog } from "../helpers.js"; import Event from "../models/Event.js"; import EventGroup from "../models/EventGroup.js"; import { - broadcastCreateMessage, - broadcastUpdateMessage, - createActivityPubActor, - createActivityPubEvent, - createFeaturedPost, - sendDirectMessage, - updateActivityPubActor, - updateActivityPubEvent, + broadcastCreateMessage, + broadcastUpdateMessage, + createActivityPubActor, + createActivityPubEvent, + createFeaturedPost, + sendDirectMessage, + updateActivityPubActor, + updateActivityPubEvent, } from "../activitypub.js"; import crypto from "crypto"; import ical from "ical"; @@ -28,33 +28,33 @@ import { markdownToSanitizedHTML } from "../util/markdown.js"; import { checkMagicLink, getConfigMiddleware } from "../lib/middleware.js"; import { getConfig } from "../lib/config.js"; import i18next from "i18next"; -moment.locale(i18next.language); +moment.locale(i18next.language); const config = getConfig(); const storage = multer.memoryStorage(); // Accept only JPEG, GIF or PNG images, up to 10MB const upload = multer({ - storage: storage, - limits: { fileSize: 10 * 1024 * 1024 }, - fileFilter: function (_, file, cb) { - const filetypes = /jpeg|jpg|png|gif/; - const mimetype = filetypes.test(file.mimetype); - if (!mimetype) { - return cb(new Error("Only JPEG, PNG and GIF images are allowed.")); - } - cb(null, true); - }, + storage: storage, + limits: { fileSize: 10 * 1024 * 1024 }, + fileFilter: function (_, file, cb) { + const filetypes = /jpeg|jpg|png|gif/; + const mimetype = filetypes.test(file.mimetype); + if (!mimetype) { + return cb(new Error("Only JPEG, PNG and GIF images are allowed.")); + } + cb(null, true); + }, }); const icsUpload = multer({ - storage: storage, - limits: { fileSize: 10 * 1024 * 1024 }, - fileFilter: function (_, file, cb) { - const filetype = "text/calendar"; - if (file.mimetype !== filetype) { - return cb(new Error("Only ICS files are allowed.")); - } - cb(null, true); - }, + storage: storage, + limits: { fileSize: 10 * 1024 * 1024 }, + fileFilter: function (_, file, cb) { + const filetype = "text/calendar"; + if (file.mimetype !== filetype) { + return cb(new Error("Only ICS files are allowed.")); + } + cb(null, true); + }, }); const router = Router(); @@ -62,675 +62,684 @@ const router = Router(); router.use(getConfigMiddleware); router.post( - "/event", - upload.single("imageUpload"), - checkMagicLink, - async (req: Request, res: Response) => { - const { data: eventData, errors } = validateEventData(req.body); - if (errors && errors.length > 0) { - return res.status(400).json({ errors }); - } - if (!eventData) { - return res.status(400).json({ - errors: [ - { - message: "No event data was provided.", - }, - ], - }); - } + "/event", + upload.single("imageUpload"), + checkMagicLink, + async (req: Request, res: Response) => { + const { data: eventData, errors } = validateEventData(req.body); + if (errors && errors.length > 0) { + return res.status(400).json({ errors }); + } + if (!eventData) { + return res.status(400).json({ + errors: [ + { + message: "No event data was provided.", + }, + ], + }); + } - const eventID = generateEventID(); - const editToken = generateEditToken(); - let eventImageFilename; - let isPartOfEventGroup = false; + const eventID = generateEventID(); + const editToken = generateEditToken(); + let eventImageFilename; + let isPartOfEventGroup = false; - if (req.file?.buffer) { - eventImageFilename = await Jimp.read(req.file.buffer) - .then((img) => { - img.resize(920, Jimp.AUTO) // resize - .quality(80) // set JPEG quality - .write("./public/events/" + eventID + ".jpg"); // save - return eventID + ".jpg"; - }) - .catch((err) => { - addToLog( - "Jimp", - "error", - "Attempt to edit image failed with error: " + err, - ); - }); - } + if (req.file?.buffer) { + eventImageFilename = await Jimp.read(req.file.buffer) + .then((img) => { + img + .resize(920, Jimp.AUTO) // resize + .quality(80) // set JPEG quality + .write("./public/events/" + eventID + ".jpg"); // save + return eventID + ".jpg"; + }) + .catch((err) => { + addToLog( + "Jimp", + "error", + "Attempt to edit image failed with error: " + err, + ); + }); + } - const startUTC = moment.tz(eventData.eventStart, eventData.timezone); - const endUTC = moment.tz(eventData.eventEnd, eventData.timezone); - let eventGroup; - if (eventData?.eventGroupBoolean) { - try { - eventGroup = await EventGroup.findOne({ - id: eventData.eventGroupID, - editToken: eventData.eventGroupEditToken, - }); - if (eventGroup) { - isPartOfEventGroup = true; - } - } catch (err) { - console.error(err); - addToLog( - "createEvent", - "error", - "Attempt to find event group failed with error: " + err, - ); - } + const startUTC = moment.tz(eventData.eventStart, eventData.timezone); + const endUTC = moment.tz(eventData.eventEnd, eventData.timezone); + let eventGroup; + if (eventData?.eventGroupBoolean) { + try { + eventGroup = await EventGroup.findOne({ + id: eventData.eventGroupID, + editToken: eventData.eventGroupEditToken, + }); + if (eventGroup) { + isPartOfEventGroup = true; } + } catch (err) { + console.error(err); + addToLog( + "createEvent", + "error", + "Attempt to find event group failed with error: " + err, + ); + } + } - // generate RSA keypair for ActivityPub - const { publicKey, privateKey } = generateRSAKeypair(); + // generate RSA keypair for ActivityPub + const { publicKey, privateKey } = generateRSAKeypair(); - const event = new Event({ - id: eventID, - type: "public", // This is for backwards compatibility - name: eventData.eventName, - location: eventData.eventLocation, - start: startUTC, - end: endUTC, - timezone: eventData.timezone, - description: eventData.eventDescription, - image: eventImageFilename, - creatorEmail: eventData.creatorEmail, - url: eventData.eventURL, - hostName: eventData.hostName, - viewPassword: "", // Backwards compatibility - editPassword: "", // Backwards compatibility - editToken: editToken, - showOnPublicList: eventData?.publicBoolean, - eventGroup: isPartOfEventGroup ? eventGroup?._id : null, - usersCanAttend: eventData.joinBoolean ? true : false, - showUsersList: false, // Backwards compatibility - usersCanComment: eventData.interactionBoolean ? true : false, - maxAttendees: eventData.maxAttendees, - firstLoad: true, - activityPubActor: createActivityPubActor( - eventID, - res.locals.config?.general.domain, - publicKey, - markdownToSanitizedHTML(eventData.eventDescription), - eventData.eventName, - eventData.eventLocation, - eventImageFilename, - startUTC, - endUTC, - eventData.timezone, + const event = new Event({ + id: eventID, + type: "public", // This is for backwards compatibility + name: eventData.eventName, + location: eventData.eventLocation, + start: startUTC, + end: endUTC, + timezone: eventData.timezone, + description: eventData.eventDescription, + image: eventImageFilename, + creatorEmail: eventData.creatorEmail, + url: eventData.eventURL, + hostName: eventData.hostName, + viewPassword: "", // Backwards compatibility + editPassword: "", // Backwards compatibility + editToken: editToken, + showOnPublicList: eventData?.publicBoolean, + eventGroup: isPartOfEventGroup ? eventGroup?._id : null, + usersCanAttend: eventData.joinBoolean ? true : false, + showUsersList: false, // Backwards compatibility + usersCanComment: eventData.interactionBoolean ? true : false, + maxAttendees: eventData.maxAttendees, + firstLoad: true, + activityPubActor: createActivityPubActor( + eventID, + res.locals.config?.general.domain, + publicKey, + markdownToSanitizedHTML(eventData.eventDescription), + eventData.eventName, + eventData.eventLocation, + eventImageFilename, + startUTC, + endUTC, + eventData.timezone, + ), + activityPubEvent: createActivityPubEvent( + eventData.eventName, + startUTC, + endUTC, + eventData.timezone, + eventData.eventDescription, + eventData.eventLocation, + ), + activityPubMessages: [ + { + id: `https://${res.locals.config?.general.domain}/${eventID}/m/featuredPost`, + content: JSON.stringify( + createFeaturedPost( + eventID, + eventData.eventName, + startUTC, + endUTC, + eventData.timezone, + eventData.eventDescription, + eventData.eventLocation, ), - activityPubEvent: createActivityPubEvent( - eventData.eventName, - startUTC, - endUTC, - eventData.timezone, - eventData.eventDescription, - eventData.eventLocation, - ), - activityPubMessages: [ - { - id: `https://${res.locals.config?.general.domain}/${eventID}/m/featuredPost`, - content: JSON.stringify( - createFeaturedPost( - eventID, - eventData.eventName, - startUTC, - endUTC, - eventData.timezone, - eventData.eventDescription, - eventData.eventLocation, - ), - ), - }, - ], - publicKey, - privateKey, + ), + }, + ], + publicKey, + privateKey, + }); + try { + await event.save(); + addToLog("createEvent", "success", "Event " + eventID + "created"); + // Send email with edit link + if (eventData.creatorEmail) { + req.emailService.sendEmailFromTemplate({ + to: eventData.creatorEmail, + subject: eventData.eventName, + templateName: "createEvent", + templateData: { + eventID, + editToken, + }, }); + } + // If the event was added to a group, send an email to any group + // subscribers + if (event.eventGroup) { try { - await event.save(); - addToLog("createEvent", "success", "Event " + eventID + "created"); - // Send email with edit link - if (eventData.creatorEmail) { - req.emailService.sendEmailFromTemplate({ - to: eventData.creatorEmail, - subject: eventData.eventName, - templateName: "createEvent", - templateData: { - eventID, - editToken, - }, - }); - } - // If the event was added to a group, send an email to any group - // subscribers - if (event.eventGroup) { - try { - const eventGroup = await EventGroup.findOne({ - _id: event.eventGroup.toString(), - }); - if (!eventGroup) { - throw new Error( - "Event group not found for event " + eventID, - ); - } - const subscribers = eventGroup?.subscribers?.reduce( - (acc: string[], current) => { - if (current.email && !acc.includes(current.email)) { - return [current.email, ...acc]; - } - return acc; - }, - [] as string[], - ); - subscribers?.forEach((emailAddress) => { - req.emailService.sendEmailFromTemplate({ - to: emailAddress, - subject: `New event in ${eventGroup.name}`, - templateName: "eventGroupUpdated", - templateData: { - eventGroupName: eventGroup.name, - eventName: event.name, - eventID: event.id, - eventGroupID: eventGroup.id, - emailAddress: encodeURIComponent(emailAddress), - }, - }); - }); - } catch (err) { - console.error(err); - addToLog( - "createEvent", - "error", - "Attempt to send event group emails failed with error: " + - err, - ); - } - } - return res.json({ - eventID: eventID, - editToken: editToken, - url: `/${eventID}?e=${editToken}`, + const eventGroup = await EventGroup.findOne({ + _id: event.eventGroup.toString(), + }); + if (!eventGroup) { + throw new Error("Event group not found for event " + eventID); + } + const subscribers = eventGroup?.subscribers?.reduce( + (acc: string[], current) => { + if (current.email && !acc.includes(current.email)) { + return [current.email, ...acc]; + } + return acc; + }, + [] as string[], + ); + subscribers?.forEach((emailAddress) => { + req.emailService.sendEmailFromTemplate({ + to: emailAddress, + subject: `New event in ${eventGroup.name}`, + templateName: "eventGroupUpdated", + templateData: { + eventGroupName: eventGroup.name, + eventName: event.name, + eventID: event.id, + eventGroupID: eventGroup.id, + emailAddress: encodeURIComponent(emailAddress), + }, }); + }); } catch (err) { - console.error(err); - addToLog( - "createEvent", - "error", - "Attempt to create event failed with error: " + err, - ); - return res.status(500).json({ - errors: [ - { - message: err, - }, - ], - }); + console.error(err); + addToLog( + "createEvent", + "error", + "Attempt to send event group emails failed with error: " + err, + ); } - }, + } + return res.json({ + eventID: eventID, + editToken: editToken, + url: `/${eventID}?e=${editToken}`, + }); + } catch (err) { + console.error(err); + addToLog( + "createEvent", + "error", + "Attempt to create event failed with error: " + err, + ); + return res.status(500).json({ + errors: [ + { + message: err, + }, + ], + }); + } + }, ); router.put( - "/event/:eventID", - upload.single("imageUpload"), - async (req: Request, res: Response) => { - const { data: eventData, errors } = validateEventData(req.body); - if (errors && errors.length > 0) { - return res.status(400).json({ errors }); - } - if (!eventData) { - return res.status(400).json({ - errors: [ - { - message: "No event data was provided.", - }, - ], - }); - } + "/event/:eventID", + upload.single("imageUpload"), + async (req: Request, res: Response) => { + const { data: eventData, errors } = validateEventData(req.body); + if (errors && errors.length > 0) { + return res.status(400).json({ errors }); + } + if (!eventData) { + return res.status(400).json({ + errors: [ + { + message: "No event data was provided.", + }, + ], + }); + } - try { - const submittedEditToken = req.body.editToken; - const event = await Event.findOne({ - id: req.params.eventID, - }); - if (!event) { - return res.status(404).json({ - errors: [ - { - message: "Event not found.", - }, - ], - }); - } - if (event.editToken !== submittedEditToken) { - // Token doesn't match - addToLog( - "editEvent", - "error", - `Attempt to edit event ${req.params.eventID} failed with error: token does not match`, - ); - return res.status(403).json({ - errors: [ - { - message: "Edit token is invalid.", - }, - ], - }); - } - // Token matches - // If there is a new image, upload that first - const eventID = req.params.eventID; - let eventImageFilename = event.image; - if (req.file?.buffer) { - Jimp.read(req.file.buffer) - .then((img) => { - img.resize(920, Jimp.AUTO) // resize - .quality(80) // set JPEG quality - .write(`./public/events/${eventID}.jpg`); // save - }) - .catch((err) => { - addToLog( - "Jimp", - "error", - "Attempt to edit image failed with error: " + err, - ); - }); - eventImageFilename = eventID + ".jpg"; - } - - const startUTC = moment.tz( - eventData.eventStart, - eventData.timezone, - ); - const endUTC = moment.tz(eventData.eventEnd, eventData.timezone); - - let isPartOfEventGroup = false; - let eventGroup; - if (eventData.eventGroupBoolean) { - eventGroup = await EventGroup.findOne({ - id: eventData.eventGroupID, - editToken: eventData.eventGroupEditToken, - }); - if (eventGroup) { - isPartOfEventGroup = true; - } - } - const updatedEvent = { - name: eventData.eventName, - location: eventData.eventLocation, - start: startUTC.toDate(), - end: endUTC.toDate(), - timezone: eventData.timezone, - description: eventData.eventDescription, - url: eventData.eventURL, - hostName: eventData.hostName, - image: eventImageFilename, - showOnPublicList: eventData.publicBoolean, - usersCanAttend: eventData.joinBoolean, - showUsersList: false, // Backwards compatibility - usersCanComment: eventData.interactionBoolean, - maxAttendees: eventData.maxAttendeesBoolean - ? eventData.maxAttendees - : undefined, - eventGroup: isPartOfEventGroup ? eventGroup?._id : null, - activityPubActor: event.activityPubActor - ? updateActivityPubActor( - JSON.parse(event.activityPubActor), - eventData.eventDescription, - eventData.eventName, - eventData.eventLocation, - eventImageFilename, - startUTC, - endUTC, - eventData.timezone, - ) - : undefined, - activityPubEvent: event.activityPubEvent - ? updateActivityPubEvent( - JSON.parse(event.activityPubEvent), - eventData.eventName, - startUTC, - endUTC, - eventData.timezone, - ) - : undefined, - }; - let diffText = - "

" + i18next.t("routes.event.difftext") + "

    "; - let displayDate; - if (event.name !== updatedEvent.name) { - diffText += `
  • ` + i18next.t("routes.event.namechanged", { eventname: updatedEvent.name} ) + `
  • `; - } - if (event.location !== updatedEvent.location) { - diffText += `
  • ` + i18next.t("routes.event.locationchanged", { location: updatedEvent.location} ) + `
  • `; - } - if ( - event.start.toISOString() !== updatedEvent.start.toISOString() - ) { - displayDate = moment - .tz(updatedEvent.start, updatedEvent.timezone) - .format(i18next.t("common.datetimeformat")); - diffText += `
  • ` + i18next.t("routes.event.starttimechanged", { starttime: displayDate }) + `
  • `; - } - if (event.end.toISOString() !== updatedEvent.end.toISOString()) { - displayDate = moment - .tz(updatedEvent.end, updatedEvent.timezone) - .format(i18next.t("common.datetimeformat")); - diffText += `
  • ` + i18next.t("routes.event.endtimechanged", { endtime: displayDate }) + `
  • `; - } - if (event.timezone !== updatedEvent.timezone) { - diffText += `
  • ` + i18next.t("routes.event.timezonechanged", { timezone: updatedEvent.timezone }) + `
  • `; - } - if (event.description !== updatedEvent.description) { - diffText += `
  • ` + i18next.t("routes.event.descriptionchanged") + `
  • `; - } - diffText += `
`; - const updatedEventObject = await Event.findOneAndUpdate( - { id: req.params.eventID }, - updatedEvent, - { new: true }, - ); - if (!updatedEventObject) { - throw new Error("Event not found"); - } + try { + const submittedEditToken = req.body.editToken; + const event = await Event.findOne({ + id: req.params.eventID, + }); + if (!event) { + return res.status(404).json({ + errors: [ + { + message: "Event not found.", + }, + ], + }); + } + if (event.editToken !== submittedEditToken) { + // Token doesn't match + addToLog( + "editEvent", + "error", + `Attempt to edit event ${req.params.eventID} failed with error: token does not match`, + ); + return res.status(403).json({ + errors: [ + { + message: "Edit token is invalid.", + }, + ], + }); + } + // Token matches + // If there is a new image, upload that first + const eventID = req.params.eventID; + let eventImageFilename = event.image; + if (req.file?.buffer) { + Jimp.read(req.file.buffer) + .then((img) => { + img + .resize(920, Jimp.AUTO) // resize + .quality(80) // set JPEG quality + .write(`./public/events/${eventID}.jpg`); // save + }) + .catch((err) => { addToLog( - "editEvent", - "success", - "Event " + req.params.eventID + " edited", + "Jimp", + "error", + "Attempt to edit image failed with error: " + err, ); - // send update to ActivityPub subscribers - const attendees = updatedEventObject.attendees?.filter( - (el) => el.id, - ); - // broadcast an identical message to all followers, will show in home timeline - const guidObject = crypto.randomBytes(16).toString("hex"); - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - id: `https://${res.locals.config?.general.domain}/${req.params.eventID}/m/${guidObject}`, - name: `RSVP to ${event.name}`, - type: "Note", - cc: "https://www.w3.org/ns/activitystreams#Public", - content: `${diffText} See here: https://${res.locals.config?.general.domain}/${req.params.eventID}`, - }; - broadcastCreateMessage(jsonObject, event.followers, eventID); - // also broadcast an Update profile message to all followers so that at least Mastodon servers will update the local profile information - const jsonUpdateObject = JSON.parse(event.activityPubActor || "{}"); - broadcastUpdateMessage(jsonUpdateObject, event.followers, eventID); - // also broadcast an Update/Event for any calendar apps that are consuming our Events - const jsonEventObject = JSON.parse(event.activityPubEvent || "{}"); - broadcastUpdateMessage(jsonEventObject, event.followers, eventID); + }); + eventImageFilename = eventID + ".jpg"; + } - // DM to attendees - if (attendees?.length) { - for (const attendee of attendees) { - const jsonObject = { - "@context": "https://www.w3.org/ns/activitystreams", - name: `RSVP to ${event.name}`, - type: "Note", - content: `@${attendee.name} ${diffText} See here: https://${res.locals.config?.general.domain}/${req.params.eventID}`, - tag: [ - { - type: "Mention", - href: attendee.id, - name: attendee.name, - }, - ], - }; - // send direct message to user - sendDirectMessage(jsonObject, attendee.id, eventID); - } - } - // Send update to all attendees - const attendeeEmails = event.attendees - ?.filter((o) => o.status === "attending" && o.email) - .map((o) => o.email!); - if (attendeeEmails?.length) { - req.emailService.sendEmailFromTemplate({ - to: config.general.email, - bcc: attendeeEmails, - subject: i18next.t("routes.event.editedsubject", { eventname: event.name}), - templateName: "editEvent", - templateData: { - diffText, - eventID: req.params.eventID, - }, - }); - } - res.sendStatus(200); - } catch (err) { - console.error(err); - addToLog( - "editEvent", - "error", - "Attempt to edit event " + - req.params.eventID + - " failed with error: " + - err, - ); - return res.status(500).json({ - errors: [ - { - message: err, - }, - ], - }); + const startUTC = moment.tz(eventData.eventStart, eventData.timezone); + const endUTC = moment.tz(eventData.eventEnd, eventData.timezone); + + let isPartOfEventGroup = false; + let eventGroup; + if (eventData.eventGroupBoolean) { + eventGroup = await EventGroup.findOne({ + id: eventData.eventGroupID, + editToken: eventData.eventGroupEditToken, + }); + if (eventGroup) { + isPartOfEventGroup = true; } - }, + } + const updatedEvent = { + name: eventData.eventName, + location: eventData.eventLocation, + start: startUTC.toDate(), + end: endUTC.toDate(), + timezone: eventData.timezone, + description: eventData.eventDescription, + url: eventData.eventURL, + hostName: eventData.hostName, + image: eventImageFilename, + showOnPublicList: eventData.publicBoolean, + usersCanAttend: eventData.joinBoolean, + showUsersList: false, // Backwards compatibility + usersCanComment: eventData.interactionBoolean, + maxAttendees: eventData.maxAttendeesBoolean + ? eventData.maxAttendees + : undefined, + eventGroup: isPartOfEventGroup ? eventGroup?._id : null, + activityPubActor: event.activityPubActor + ? updateActivityPubActor( + JSON.parse(event.activityPubActor), + eventData.eventDescription, + eventData.eventName, + eventData.eventLocation, + eventImageFilename, + startUTC, + endUTC, + eventData.timezone, + ) + : undefined, + activityPubEvent: event.activityPubEvent + ? updateActivityPubEvent( + JSON.parse(event.activityPubEvent), + eventData.eventName, + startUTC, + endUTC, + eventData.timezone, + ) + : undefined, + }; + let diffText = "

" + i18next.t("routes.event.difftext") + "

    "; + let displayDate; + if (event.name !== updatedEvent.name) { + diffText += + `
  • ` + + i18next.t("routes.event.namechanged", { + eventname: updatedEvent.name, + }) + + `
  • `; + } + if (event.location !== updatedEvent.location) { + diffText += + `
  • ` + + i18next.t("routes.event.locationchanged", { + location: updatedEvent.location, + }) + + `
  • `; + } + if (event.start.toISOString() !== updatedEvent.start.toISOString()) { + displayDate = moment + .tz(updatedEvent.start, updatedEvent.timezone) + .format(i18next.t("common.datetimeformat")); + diffText += + `
  • ` + + i18next.t("routes.event.starttimechanged", { + starttime: displayDate, + }) + + `
  • `; + } + if (event.end.toISOString() !== updatedEvent.end.toISOString()) { + displayDate = moment + .tz(updatedEvent.end, updatedEvent.timezone) + .format(i18next.t("common.datetimeformat")); + diffText += + `
  • ` + + i18next.t("routes.event.endtimechanged", { + endtime: displayDate, + }) + + `
  • `; + } + if (event.timezone !== updatedEvent.timezone) { + diffText += + `
  • ` + + i18next.t("routes.event.timezonechanged", { + timezone: updatedEvent.timezone, + }) + + `
  • `; + } + if (event.description !== updatedEvent.description) { + diffText += + `
  • ` + i18next.t("routes.event.descriptionchanged") + `
  • `; + } + diffText += `
`; + const updatedEventObject = await Event.findOneAndUpdate( + { id: req.params.eventID }, + updatedEvent, + { new: true }, + ); + if (!updatedEventObject) { + throw new Error("Event not found"); + } + addToLog( + "editEvent", + "success", + "Event " + req.params.eventID + " edited", + ); + // send update to ActivityPub subscribers + const attendees = updatedEventObject.attendees?.filter((el) => el.id); + // broadcast an identical message to all followers, will show in home timeline + const guidObject = crypto.randomBytes(16).toString("hex"); + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + id: `https://${res.locals.config?.general.domain}/${req.params.eventID}/m/${guidObject}`, + name: `RSVP to ${event.name}`, + type: "Note", + cc: "https://www.w3.org/ns/activitystreams#Public", + content: `${diffText} See here: https://${res.locals.config?.general.domain}/${req.params.eventID}`, + }; + broadcastCreateMessage(jsonObject, event.followers, eventID); + // also broadcast an Update profile message to all followers so that at least Mastodon servers will update the local profile information + const jsonUpdateObject = JSON.parse(event.activityPubActor || "{}"); + broadcastUpdateMessage(jsonUpdateObject, event.followers, eventID); + // also broadcast an Update/Event for any calendar apps that are consuming our Events + const jsonEventObject = JSON.parse(event.activityPubEvent || "{}"); + broadcastUpdateMessage(jsonEventObject, event.followers, eventID); + + // DM to attendees + if (attendees?.length) { + for (const attendee of attendees) { + const jsonObject = { + "@context": "https://www.w3.org/ns/activitystreams", + name: `RSVP to ${event.name}`, + type: "Note", + content: `@${attendee.name} ${diffText} See here: https://${res.locals.config?.general.domain}/${req.params.eventID}`, + tag: [ + { + type: "Mention", + href: attendee.id, + name: attendee.name, + }, + ], + }; + // send direct message to user + sendDirectMessage(jsonObject, attendee.id, eventID); + } + } + // Send update to all attendees + const attendeeEmails = event.attendees + ?.filter((o) => o.status === "attending" && o.email) + .map((o) => o.email!); + if (attendeeEmails?.length) { + req.emailService.sendEmailFromTemplate({ + to: config.general.email, + bcc: attendeeEmails, + subject: i18next.t("routes.event.editedsubject", { + eventname: event.name, + }), + templateName: "editEvent", + templateData: { + diffText, + eventID: req.params.eventID, + }, + }); + } + res.sendStatus(200); + } catch (err) { + console.error(err); + addToLog( + "editEvent", + "error", + "Attempt to edit event " + + req.params.eventID + + " failed with error: " + + err, + ); + return res.status(500).json({ + errors: [ + { + message: err, + }, + ], + }); + } + }, ); router.post( - "/import/event", - icsUpload.single("icsImportControl"), - checkMagicLink, - async (req: Request, res: Response) => { - if (!req.file) { - return res.status(400).json({ - errors: [ - { - message: "No file was provided.", - }, - ], - }); - } + "/import/event", + icsUpload.single("icsImportControl"), + checkMagicLink, + async (req: Request, res: Response) => { + if (!req.file) { + return res.status(400).json({ + errors: [ + { + message: "No file was provided.", + }, + ], + }); + } - const eventID = generateEventID(); - const editToken = generateEditToken(); + const eventID = generateEventID(); + const editToken = generateEditToken(); - const iCalObject = ical.parseICS(req.file.buffer.toString("utf8")); + const iCalObject = ical.parseICS(req.file.buffer.toString("utf8")); - const importedEventData = iCalObject[Object.keys(iCalObject)[0]]; + const importedEventData = iCalObject[Object.keys(iCalObject)[0]]; - let creatorEmail: string | undefined; - if (req.body.creatorEmail) { - creatorEmail = req.body.creatorEmail; - } else if (importedEventData.organizer) { - if (typeof importedEventData.organizer === "string") { - creatorEmail = importedEventData.organizer.replace( - "MAILTO:", - "", - ); - } else { - creatorEmail = importedEventData.organizer.val.replace( - "MAILTO:", - "", - ); - } - } + let creatorEmail: string | undefined; + if (req.body.creatorEmail) { + creatorEmail = req.body.creatorEmail; + } else if (importedEventData.organizer) { + if (typeof importedEventData.organizer === "string") { + creatorEmail = importedEventData.organizer.replace("MAILTO:", ""); + } else { + creatorEmail = importedEventData.organizer.val.replace("MAILTO:", ""); + } + } - let hostName: string | undefined; - if (importedEventData.organizer) { - if (typeof importedEventData.organizer === "string") { - hostName = importedEventData.organizer.replace(/["]+/g, ""); - } else { - hostName = importedEventData.organizer.params.CN.replace( - /["]+/g, - "", - ); - } - } + let hostName: string | undefined; + if (importedEventData.organizer) { + if (typeof importedEventData.organizer === "string") { + hostName = importedEventData.organizer.replace(/["]+/g, ""); + } else { + hostName = importedEventData.organizer.params.CN.replace(/["]+/g, ""); + } + } - const event = new Event({ - id: eventID, - type: "public", - name: importedEventData.summary, - location: importedEventData.location, - start: importedEventData.start, - end: importedEventData.end, - timezone: "Etc/UTC", // TODO: get timezone from ics file - description: importedEventData.description, - image: "", - creatorEmail, - url: "", - hostName, - viewPassword: "", - editPassword: "", - editToken: editToken, - usersCanAttend: false, - showUsersList: false, - usersCanComment: false, - firstLoad: true, + const event = new Event({ + id: eventID, + type: "public", + name: importedEventData.summary, + location: importedEventData.location, + start: importedEventData.start, + end: importedEventData.end, + timezone: "Etc/UTC", // TODO: get timezone from ics file + description: importedEventData.description, + image: "", + creatorEmail, + url: "", + hostName, + viewPassword: "", + editPassword: "", + editToken: editToken, + usersCanAttend: false, + showUsersList: false, + usersCanComment: false, + firstLoad: true, + }); + try { + await event.save(); + addToLog("createEvent", "success", `Event ${eventID} created`); + // Send email with edit link + if (creatorEmail) { + req.emailService.sendEmailFromTemplate({ + to: creatorEmail, + subject: importedEventData.summary || "", + templateName: "createEvent", + templateData: { + eventID, + editToken, + }, }); - try { - await event.save(); - addToLog("createEvent", "success", `Event ${eventID} created`); - // Send email with edit link - if (creatorEmail) { - req.emailService.sendEmailFromTemplate({ - to: creatorEmail, - subject: importedEventData.summary || "", - templateName: "createEvent", - templateData: { - eventID, - editToken, - }, - }); - } - return res.json({ - eventID: eventID, - editToken: editToken, - url: `/${eventID}?e=${editToken}`, - }); - } catch (err) { - console.error(err); - addToLog( - "createEvent", - "error", - "Attempt to create event failed with error: " + err, - ); - return res.status(500).json({ - errors: [ - { - message: err, - }, - ], - }); - } - }, + } + return res.json({ + eventID: eventID, + editToken: editToken, + url: `/${eventID}?e=${editToken}`, + }); + } catch (err) { + console.error(err); + addToLog( + "createEvent", + "error", + "Attempt to create event failed with error: " + err, + ); + return res.status(500).json({ + errors: [ + { + message: err, + }, + ], + }); + } + }, ); router.delete( - "/event/attendee/:eventID", - async (req: Request, res: Response) => { - const removalPassword = req.query.p; - if (!removalPassword) { - return res - .status(400) - .json({ error: "Please provide a removal password." }); - } - try { - const response = await Event.findOne({ - id: req.params.eventID, - "attendees.removalPassword": removalPassword, - }); - if (!response) { - return res.status(404).json({ - error: "No attendee found with that removal password.", - }); - } - const attendee = response?.attendees?.find( - (a) => a.removalPassword === removalPassword, - ); - if (!attendee) { - return res.status(404).json({ - error: "No attendee found with that removal password.", - }); - } - const attendeeEmail = attendee.email; - const removalResponse = await Event.updateOne( - { id: req.params.eventID }, - { $pull: { attendees: { removalPassword } } }, - ); - if (removalResponse.nModified === 0) { - return res.status(404).json({ - error: "No attendee found with that removal password.", - }); - } - addToLog( - "unattendEvent", - "success", - `Attendee removed self from event ${req.params.eventID}`, - ); - if (attendeeEmail) { - await req.emailService.sendEmailFromTemplate({ - to: attendeeEmail, - subject: i18next.t("routes.removeeventattendeesubject"), - templateName: "unattendEvent", - templateData: { - eventID: req.params.eventID, - }, - }); - } - res.sendStatus(200); - } catch (e) { - addToLog( - "removeEventAttendee", - "error", - `Attempt to remove attendee from event ${req.params.eventID} failed with error: ${e}`, - ); - return res.status(500).json({ - error: "There has been an unexpected error. Please try again.", - }); - } - }, + "/event/attendee/:eventID", + async (req: Request, res: Response) => { + const removalPassword = req.query.p; + if (!removalPassword) { + return res + .status(400) + .json({ error: "Please provide a removal password." }); + } + try { + const response = await Event.findOne({ + id: req.params.eventID, + "attendees.removalPassword": removalPassword, + }); + if (!response) { + return res.status(404).json({ + error: "No attendee found with that removal password.", + }); + } + const attendee = response?.attendees?.find( + (a) => a.removalPassword === removalPassword, + ); + if (!attendee) { + return res.status(404).json({ + error: "No attendee found with that removal password.", + }); + } + const attendeeEmail = attendee.email; + const removalResponse = await Event.updateOne( + { id: req.params.eventID }, + { $pull: { attendees: { removalPassword } } }, + ); + if (removalResponse.nModified === 0) { + return res.status(404).json({ + error: "No attendee found with that removal password.", + }); + } + addToLog( + "unattendEvent", + "success", + `Attendee removed self from event ${req.params.eventID}`, + ); + if (attendeeEmail) { + await req.emailService.sendEmailFromTemplate({ + to: attendeeEmail, + subject: i18next.t("routes.removeeventattendeesubject"), + templateName: "unattendEvent", + templateData: { + eventID: req.params.eventID, + }, + }); + } + res.sendStatus(200); + } catch (e) { + addToLog( + "removeEventAttendee", + "error", + `Attempt to remove attendee from event ${req.params.eventID} failed with error: ${e}`, + ); + return res.status(500).json({ + error: "There has been an unexpected error. Please try again.", + }); + } + }, ); // Used to one-click unattend an event from an email. router.get( - "/event/:eventID/unattend/:removalPasswordHash", - async (req: Request, res: Response) => { - // Find the attendee by the unattendPasswordHash - const event = await Event.findOne({ id: req.params.eventID }); - if (!event) { - return res.redirect("/404"); - } - const attendee = event.attendees?.find( - (o) => - hashString(o.removalPassword || "") === - req.params.removalPasswordHash, - ); - if (!attendee) { - return res.redirect(`/${req.params.eventID}`); - } - // Remove the attendee from the event - event.attendees = event.attendees?.filter( - (o) => o.removalPassword !== attendee.removalPassword, - ); - await event.save(); - // Send email to the attendee - if (attendee.email) { - req.emailService.sendEmailFromTemplate({ - to: attendee.email, - subject: `You have been removed from ${event.name}`, - templateName: "unattendEvent", - templateData: { - event, - }, - }); - } - return res.redirect(`/${req.params.eventID}?m=unattend`); - }, + "/event/:eventID/unattend/:removalPasswordHash", + async (req: Request, res: Response) => { + // Find the attendee by the unattendPasswordHash + const event = await Event.findOne({ id: req.params.eventID }); + if (!event) { + return res.redirect("/404"); + } + const attendee = event.attendees?.find( + (o) => + hashString(o.removalPassword || "") === req.params.removalPasswordHash, + ); + if (!attendee) { + return res.redirect(`/${req.params.eventID}`); + } + // Remove the attendee from the event + event.attendees = event.attendees?.filter( + (o) => o.removalPassword !== attendee.removalPassword, + ); + await event.save(); + // Send email to the attendee + if (attendee.email) { + req.emailService.sendEmailFromTemplate({ + to: attendee.email, + subject: `You have been removed from ${event.name}`, + templateName: "unattendEvent", + templateData: { + event, + }, + }); + } + return res.redirect(`/${req.params.eventID}?m=unattend`); + }, ); export default router; diff --git a/src/routes/frontend.ts b/src/routes/frontend.ts index 20d56340..3bdf91a2 100644 --- a/src/routes/frontend.ts +++ b/src/routes/frontend.ts @@ -3,16 +3,16 @@ import moment from "moment-timezone"; import { marked } from "marked"; import { markdownToSanitizedHTML, renderPlain } from "../util/markdown.js"; import { - frontendConfig, - instanceDescription, - instanceRules, + frontendConfig, + instanceDescription, + instanceRules, } from "../lib/config.js"; import { addToLog, exportIcal } from "../helpers.js"; import Event from "../models/Event.js"; import EventGroup, { IEventGroup } from "../models/EventGroup.js"; import { - acceptsActivityPub, - activityPubContentType, + acceptsActivityPub, + activityPubContentType, } from "../lib/activitypub.js"; import MagicLink from "../models/MagicLink.js"; import { getConfigMiddleware } from "../lib/middleware.js"; @@ -26,607 +26,563 @@ const router = Router(); router.use(getConfigMiddleware); router.get("/", (_, res) => { - if (res.locals.config?.general.show_public_event_list) { - return res.redirect("/events"); - } - return res.render("home", { - ...frontendConfig(res), - instanceRules: instanceRules(), - instanceDescription: instanceDescription(), - }); + if (res.locals.config?.general.show_public_event_list) { + return res.redirect("/events"); + } + return res.render("home", { + ...frontendConfig(res), + instanceRules: instanceRules(), + instanceDescription: instanceDescription(), + }); }); router.get("/about", (_: Request, res: Response) => { - return res.render("home", { - ...frontendConfig(res), - instanceRules: instanceRules(), - instanceDescription: instanceDescription(), - }); + return res.render("home", { + ...frontendConfig(res), + instanceRules: instanceRules(), + instanceDescription: instanceDescription(), + }); }); router.get("/new", (_: Request, res: Response) => { - if (res.locals.config?.general.creator_email_addresses?.length) { - return res.render("createEventMagicLink", frontendConfig(res)); - } - return res.render("newevent", { - title: i18next.t("frontend.newevent"), - ...frontendConfig(res), - }); + if (res.locals.config?.general.creator_email_addresses?.length) { + return res.render("createEventMagicLink", frontendConfig(res)); + } + return res.render("newevent", { + title: i18next.t("frontend.newevent"), + ...frontendConfig(res), + }); }); router.get("/new/:magicLinkToken", async (req: Request, res: Response) => { - // If we don't have any creator email addresses, we don't need to check the magic link - // so we can just redirect to the new event page - if (!res.locals.config?.general.creator_email_addresses?.length) { - return res.redirect("/new"); - } - const magicLink = await MagicLink.findOne({ - token: req.params.magicLinkToken, - expiryTime: { $gt: new Date() }, - permittedActions: "createEvent", - }); - if (!magicLink) { - return res.render("createEventMagicLink", { - ...frontendConfig(res), - message: { - type: "danger", - text: i18next.t("routes.magiclink-invalid"), - }, - }); - } - res.render("newevent", { - title: i18next.t("frontend.newevent"), - ...frontendConfig(res), - magicLinkToken: req.params.magicLinkToken, - creatorEmail: magicLink.email, + // If we don't have any creator email addresses, we don't need to check the magic link + // so we can just redirect to the new event page + if (!res.locals.config?.general.creator_email_addresses?.length) { + return res.redirect("/new"); + } + const magicLink = await MagicLink.findOne({ + token: req.params.magicLinkToken, + expiryTime: { $gt: new Date() }, + permittedActions: "createEvent", + }); + if (!magicLink) { + return res.render("createEventMagicLink", { + ...frontendConfig(res), + message: { + type: "danger", + text: i18next.t("routes.magiclink-invalid"), + }, }); + } + res.render("newevent", { + title: i18next.t("frontend.newevent"), + ...frontendConfig(res), + magicLinkToken: req.params.magicLinkToken, + creatorEmail: magicLink.email, + }); }); router.get("/events", async (_: Request, res: Response) => { - if (!res.locals.config?.general.show_public_event_list) { - return res.status(404).render("404", frontendConfig(res)); - } - const events = await Event.find({ showOnPublicList: true }) - .populate("eventGroup") - .lean() - .sort("start"); - const updatedEvents: EventListEvent[] = events.map((event) => { - const startMoment = moment.tz(event.start, event.timezone); - const endMoment = moment.tz(event.end, event.timezone); - const isSameDay = startMoment.isSame(endMoment, "day"); + if (!res.locals.config?.general.show_public_event_list) { + return res.status(404).render("404", frontendConfig(res)); + } + const events = await Event.find({ showOnPublicList: true }) + .populate("eventGroup") + .lean() + .sort("start"); + const updatedEvents: EventListEvent[] = events.map((event) => { + const startMoment = moment.tz(event.start, event.timezone); + const endMoment = moment.tz(event.end, event.timezone); + const isSameDay = startMoment.isSame(endMoment, "day"); - return { - id: event.id, - name: event.name, - location: event.location, - displayDate: isSameDay - ? startMoment.format("LL") - : `${startMoment.format("LL")} - ${endMoment.format( - "LL", - )}`, - eventHasConcluded: endMoment.isBefore(moment.tz(event.timezone)), - eventGroup: event.eventGroup as unknown as IEventGroup, - startMoment, - endMoment, - }; - }); - const upcomingEventsInMonthBuckets = updatedEvents - .filter((event) => event.eventHasConcluded === false) - .reduce(bucketEventsByMonth, []); - const pastEventsInMonthBuckets = updatedEvents - .filter((event) => event.eventHasConcluded === true) - .reduce(bucketEventsByMonth, []); - const eventGroups = await EventGroup.find({ - showOnPublicList: true, - }).lean(); - const updatedEventGroups = eventGroups.map((eventGroup) => { - return { - id: eventGroup.id, - name: eventGroup.name, - numberOfEvents: updatedEvents.filter( - (event) => - event.eventGroup?._id.toString() === - eventGroup._id.toString(), - ).length, - }; - }); + return { + id: event.id, + name: event.name, + location: event.location, + displayDate: isSameDay + ? startMoment.format("LL") + : `${startMoment.format("LL")} - ${endMoment.format("LL")}`, + eventHasConcluded: endMoment.isBefore(moment.tz(event.timezone)), + eventGroup: event.eventGroup as unknown as IEventGroup, + startMoment, + endMoment, + }; + }); + const upcomingEventsInMonthBuckets = updatedEvents + .filter((event) => event.eventHasConcluded === false) + .reduce(bucketEventsByMonth, []); + const pastEventsInMonthBuckets = updatedEvents + .filter((event) => event.eventHasConcluded === true) + .reduce(bucketEventsByMonth, []); + const eventGroups = await EventGroup.find({ + showOnPublicList: true, + }).lean(); + const updatedEventGroups = eventGroups.map((eventGroup) => { + return { + id: eventGroup.id, + name: eventGroup.name, + numberOfEvents: updatedEvents.filter( + (event) => + event.eventGroup?._id.toString() === eventGroup._id.toString(), + ).length, + }; + }); - res.render("publicEventList", { - title: i18next.t("frontend.publicevents"), - upcomingEvents: upcomingEventsInMonthBuckets, - pastEvents: pastEventsInMonthBuckets, - eventGroups: updatedEventGroups, - instanceDescription: instanceDescription(), - instanceRules: instanceRules(), - ...frontendConfig(res), - }); + res.render("publicEventList", { + title: i18next.t("frontend.publicevents"), + upcomingEvents: upcomingEventsInMonthBuckets, + pastEvents: pastEventsInMonthBuckets, + eventGroups: updatedEventGroups, + instanceDescription: instanceDescription(), + instanceRules: instanceRules(), + ...frontendConfig(res), + }); }); router.get("/:eventID", async (req: Request, res: Response) => { - try { - const event = await Event.findOne({ - id: req.params.eventID, - }) - .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is - .populate("eventGroup"); - if (!event) { - return res.status(404).render("404", frontendConfig(res)); - } - const parsedLocation = event.location.replace(/\s+/g, "+"); - let displayDate; - const dateformat = i18next.t("frontend.dateformat"); - const timeformat = i18next.t('frontend.timeformat'); - if (moment.tz(event.end, event.timezone).isSame(event.start, "day")) { - // Happening during one day - displayDate = i18next.t("frontend.displaydate-sameday", - { - startdate: - moment - .tz(event.start, event.timezone) - .format(dateformat), - starttime: - moment - .tz(event.start, event.timezone) - .format(timeformat), - endtime: - moment - .tz(event.end, event.timezone) - .format(timeformat), - timezone: - moment - .tz(event.end, event.timezone) - .format('(z)',) - }); - } else { - displayDate = i18next.t("frontend.displaydate-days", - { - startdate: - moment - .tz(event.start, event.timezone) - .format(dateformat), - starttime: - moment - .tz(event.start, event.timezone) - .format(timeformat), - enddate: - moment - .tz(event.end, event.timezone) - .format(dateformat), - endtime: - moment - .tz(event.end, event.timezone) - .format(timeformat), - timezone: - moment - .tz(event.end, event.timezone) - .format('(z)',) - }); - } - const eventStartISO = moment.tz(event.start, "Etc/UTC").toISOString(); - const eventEndISO = moment.tz(event.end, "Etc/UTC").toISOString(); - const parsedStart = moment - .tz(event.start, event.timezone) - .format("YYYYMMDD[T]HHmmss"); - const parsedEnd = moment - .tz(event.end, event.timezone) - .format("YYYYMMDD[T]HHmmss"); - // See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local - const parsedStartForDateInput = moment - .tz(event.start, event.timezone) - .format("YYYY-MM-DDTHH:mm"); - const parsedEndForDateInput = moment - .tz(event.end, event.timezone) - .format("YYYY-MM-DDTHH:mm"); - let eventHasConcluded = false; - if ( - moment - .tz(event.end, event.timezone) - .isBefore(moment.tz(event.timezone)) - ) { - eventHasConcluded = true; - } - let eventHasBegun = false; - if ( - moment - .tz(event.start, event.timezone) - .isBefore(moment.tz(event.timezone)) - ) { - eventHasBegun = true; - } - const fromNow = moment.tz(event.start, event.timezone).fromNow(); - const parsedDescription = markdownToSanitizedHTML(event.description); - const eventEditToken = event.editToken; + try { + const event = await Event.findOne({ + id: req.params.eventID, + }) + .lean() // Required, see: https://stackoverflow.com/questions/59690923/handlebars-access-has-been-denied-to-resolve-the-property-from-because-it-is + .populate("eventGroup"); + if (!event) { + return res.status(404).render("404", frontendConfig(res)); + } + const parsedLocation = event.location.replace(/\s+/g, "+"); + let displayDate; + const dateformat = i18next.t("frontend.dateformat"); + const timeformat = i18next.t("frontend.timeformat"); + if (moment.tz(event.end, event.timezone).isSame(event.start, "day")) { + // Happening during one day + displayDate = i18next.t("frontend.displaydate-sameday", { + startdate: moment.tz(event.start, event.timezone).format(dateformat), + starttime: moment.tz(event.start, event.timezone).format(timeformat), + endtime: moment.tz(event.end, event.timezone).format(timeformat), + timezone: moment.tz(event.end, event.timezone).format("(z)"), + }); + } else { + displayDate = i18next.t("frontend.displaydate-days", { + startdate: moment.tz(event.start, event.timezone).format(dateformat), + starttime: moment.tz(event.start, event.timezone).format(timeformat), + enddate: moment.tz(event.end, event.timezone).format(dateformat), + endtime: moment.tz(event.end, event.timezone).format(timeformat), + timezone: moment.tz(event.end, event.timezone).format("(z)"), + }); + } + const eventStartISO = moment.tz(event.start, "Etc/UTC").toISOString(); + const eventEndISO = moment.tz(event.end, "Etc/UTC").toISOString(); + const parsedStart = moment + .tz(event.start, event.timezone) + .format("YYYYMMDD[T]HHmmss"); + const parsedEnd = moment + .tz(event.end, event.timezone) + .format("YYYYMMDD[T]HHmmss"); + // See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local + const parsedStartForDateInput = moment + .tz(event.start, event.timezone) + .format("YYYY-MM-DDTHH:mm"); + const parsedEndForDateInput = moment + .tz(event.end, event.timezone) + .format("YYYY-MM-DDTHH:mm"); + let eventHasConcluded = false; + if ( + moment.tz(event.end, event.timezone).isBefore(moment.tz(event.timezone)) + ) { + eventHasConcluded = true; + } + let eventHasBegun = false; + if ( + moment.tz(event.start, event.timezone).isBefore(moment.tz(event.timezone)) + ) { + eventHasBegun = true; + } + const fromNow = moment.tz(event.start, event.timezone).fromNow(); + const parsedDescription = markdownToSanitizedHTML(event.description); + const eventEditToken = event.editToken; - const escapedName = encodeURIComponent(event.name); + const escapedName = encodeURIComponent(event.name); - let eventHasCoverImage = false; - if (event.image) { - eventHasCoverImage = true; - } else { - eventHasCoverImage = false; - } - let eventHasHost = false; - if (event.hostName) { - eventHasHost = true; + let eventHasCoverImage = false; + if (event.image) { + eventHasCoverImage = true; + } else { + eventHasCoverImage = false; + } + let eventHasHost = false; + if (event.hostName) { + eventHasHost = true; + } else { + eventHasHost = false; + } + let firstLoad = false; + if (event.firstLoad === true) { + firstLoad = true; + await Event.findOneAndUpdate( + { id: req.params.eventID }, + { firstLoad: false }, + ); + } + let editingEnabled = false; + if (Object.keys(req.query).length !== 0) { + if (!req.query.e) { + editingEnabled = false; + console.log("No edit token set"); + } else { + if (req.query.e === eventEditToken) { + editingEnabled = true; } else { - eventHasHost = false; + editingEnabled = false; } - let firstLoad = false; - if (event.firstLoad === true) { - firstLoad = true; - await Event.findOneAndUpdate( - { id: req.params.eventID }, - { firstLoad: false }, - ); - } - let editingEnabled = false; - if (Object.keys(req.query).length !== 0) { - if (!req.query.e) { - editingEnabled = false; - console.log("No edit token set"); - } else { - if (req.query.e === eventEditToken) { - editingEnabled = true; - } else { - editingEnabled = false; - } - } + } + } + const eventAttendees = event.attendees + ?.sort((a, b) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0)) + .map((el) => { + if (!el.id) { + el.id = el._id; } - const eventAttendees = event.attendees - ?.sort((a, b) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0)) - .map((el) => { - if (!el.id) { - el.id = el._id; - } - if (el.number && el.number > 1) { - el.name = `${el.name} ${i18next.t("frontend.elnumber", { count: el.number })}`; - } - return { - ...el, - // Backwards compatibility - if visibility is not set, default to public - visibility: el.visibility || "public", - }; - }) - .filter((obj, pos, arr) => { - return ( - obj.status === "attending" && - arr.map((mapObj) => mapObj.id).indexOf(obj.id) === pos - ); - }); - - let spotsRemaining, noMoreSpots; - const numberOfAttendees = - eventAttendees?.reduce((acc, attendee) => { - if (attendee.status === "attending") { - return acc + (attendee.number || 1); - } - return acc; - }, 0) || 0; - const visibleAttendees = eventAttendees?.filter( - (attendee) => attendee.visibility === "public", - ); - const hiddenAttendees = eventAttendees?.filter( - (attendee) => attendee.visibility === "private", - ); - const numberOfHiddenAttendees = eventAttendees?.reduce( - (acc, attendee) => { - if ( - attendee.status === "attending" && - attendee.visibility === "private" - ) { - return acc + (attendee.number || 1); - } - return acc; - }, - 0, - ); - if (event.maxAttendees) { - spotsRemaining = event.maxAttendees - numberOfAttendees; - if (spotsRemaining <= 0) { - noMoreSpots = true; - } + if (el.number && el.number > 1) { + el.name = `${el.name} ${i18next.t("frontend.elnumber", { count: el.number })}`; } - const metadata = { - title: event.name, - description: ( - marked.parse(event.description, { - renderer: renderPlain(), - }) as string - ) - .split(" ") - .splice(0, 40) - .join(" ") - .trim(), - image: eventHasCoverImage - ? `https://${res.locals.config?.general.domain}/events/` + - event.image - : null, - url: - `https://${res.locals.config?.general.domain}/` + - req.params.eventID, + return { + ...el, + // Backwards compatibility - if visibility is not set, default to public + visibility: el.visibility || "public", }; - if (acceptsActivityPub(req)) { - res.header("Content-Type", activityPubContentType).send( - JSON.parse(event.activityPubActor || "{}"), - ); - } else { - res.set("X-Robots-Tag", "noindex"); - res.render("event", { - ...frontendConfig(res), - title: event.name, - escapedName: escapedName, - eventData: event, - visibleAttendees, - hiddenAttendees, - numberOfAttendees, - numberOfHiddenAttendees, - spotsRemaining: spotsRemaining, - noMoreSpots: noMoreSpots, - eventStartISO: eventStartISO, - eventEndISO: eventEndISO, - parsedLocation: parsedLocation, - parsedStart: parsedStart, - parsedEnd: parsedEnd, - parsedStartForDateInput, - parsedEndForDateInput, - displayDate: displayDate, - fromNow: fromNow, - timezone: event.timezone, - parsedDescription: parsedDescription, - editingEnabled: editingEnabled, - eventHasCoverImage: eventHasCoverImage, - eventHasHost: eventHasHost, - firstLoad: firstLoad, - eventHasConcluded: eventHasConcluded, - eventHasBegun: eventHasBegun, - eventWillBeDeleted: - (res.locals.config?.general.delete_after_days || 0) > 0, - daysUntilDeletion: moment - .tz(event.end, event.timezone) - .add(res.locals.config?.general.delete_after_days, "days") - .fromNow(), - metadata: metadata, - jsonData: { - name: event.name, - id: event.id, - description: event.description, - location: event.location, - timezone: event.timezone, - url: event.url, - hostName: event.hostName, - creatorEmail: event.creatorEmail, - showOnPublicList: event.showOnPublicList, - eventGroupID: event.eventGroup - ? (event.eventGroup as unknown as IEventGroup).id - : null, - eventGroupEditToken: event.eventGroup - ? (event.eventGroup as unknown as IEventGroup).editToken - : null, - usersCanAttend: event.usersCanAttend, - usersCanComment: event.usersCanComment, - maxAttendees: event.maxAttendees, - startISO: eventStartISO, - endISO: eventEndISO, - startForDateInput: parsedStartForDateInput, - endForDateInput: parsedEndForDateInput, - image: event.image, - editToken: editingEnabled ? eventEditToken : null, - }, - message: getMessage(req.query.m as string), - }); - } - } catch (err) { - addToLog( - "displayEvent", - "error", - "Attempt to display event " + - req.params.eventID + - " failed with error: " + - err, + }) + .filter((obj, pos, arr) => { + return ( + obj.status === "attending" && + arr.map((mapObj) => mapObj.id).indexOf(obj.id) === pos ); - console.log(err); - return res.status(404).render("404", frontendConfig(res)); + }); + + let spotsRemaining, noMoreSpots; + const numberOfAttendees = + eventAttendees?.reduce((acc, attendee) => { + if (attendee.status === "attending") { + return acc + (attendee.number || 1); + } + return acc; + }, 0) || 0; + const visibleAttendees = eventAttendees?.filter( + (attendee) => attendee.visibility === "public", + ); + const hiddenAttendees = eventAttendees?.filter( + (attendee) => attendee.visibility === "private", + ); + const numberOfHiddenAttendees = eventAttendees?.reduce((acc, attendee) => { + if ( + attendee.status === "attending" && + attendee.visibility === "private" + ) { + return acc + (attendee.number || 1); + } + return acc; + }, 0); + if (event.maxAttendees) { + spotsRemaining = event.maxAttendees - numberOfAttendees; + if (spotsRemaining <= 0) { + noMoreSpots = true; + } } + const metadata = { + title: event.name, + description: ( + marked.parse(event.description, { + renderer: renderPlain(), + }) as string + ) + .split(" ") + .splice(0, 40) + .join(" ") + .trim(), + image: eventHasCoverImage + ? `https://${res.locals.config?.general.domain}/events/` + event.image + : null, + url: `https://${res.locals.config?.general.domain}/` + req.params.eventID, + }; + if (acceptsActivityPub(req)) { + res + .header("Content-Type", activityPubContentType) + .send(JSON.parse(event.activityPubActor || "{}")); + } else { + res.set("X-Robots-Tag", "noindex"); + res.render("event", { + ...frontendConfig(res), + title: event.name, + escapedName: escapedName, + eventData: event, + visibleAttendees, + hiddenAttendees, + numberOfAttendees, + numberOfHiddenAttendees, + spotsRemaining: spotsRemaining, + noMoreSpots: noMoreSpots, + eventStartISO: eventStartISO, + eventEndISO: eventEndISO, + parsedLocation: parsedLocation, + parsedStart: parsedStart, + parsedEnd: parsedEnd, + parsedStartForDateInput, + parsedEndForDateInput, + displayDate: displayDate, + fromNow: fromNow, + timezone: event.timezone, + parsedDescription: parsedDescription, + editingEnabled: editingEnabled, + eventHasCoverImage: eventHasCoverImage, + eventHasHost: eventHasHost, + firstLoad: firstLoad, + eventHasConcluded: eventHasConcluded, + eventHasBegun: eventHasBegun, + eventWillBeDeleted: + (res.locals.config?.general.delete_after_days || 0) > 0, + daysUntilDeletion: moment + .tz(event.end, event.timezone) + .add(res.locals.config?.general.delete_after_days, "days") + .fromNow(), + metadata: metadata, + jsonData: { + name: event.name, + id: event.id, + description: event.description, + location: event.location, + timezone: event.timezone, + url: event.url, + hostName: event.hostName, + creatorEmail: event.creatorEmail, + showOnPublicList: event.showOnPublicList, + eventGroupID: event.eventGroup + ? (event.eventGroup as unknown as IEventGroup).id + : null, + eventGroupEditToken: event.eventGroup + ? (event.eventGroup as unknown as IEventGroup).editToken + : null, + usersCanAttend: event.usersCanAttend, + usersCanComment: event.usersCanComment, + maxAttendees: event.maxAttendees, + startISO: eventStartISO, + endISO: eventEndISO, + startForDateInput: parsedStartForDateInput, + endForDateInput: parsedEndForDateInput, + image: event.image, + editToken: editingEnabled ? eventEditToken : null, + }, + message: getMessage(req.query.m as string), + }); + } + } catch (err) { + addToLog( + "displayEvent", + "error", + "Attempt to display event " + + req.params.eventID + + " failed with error: " + + err, + ); + console.log(err); + return res.status(404).render("404", frontendConfig(res)); + } }); router.get("/group/:eventGroupID", async (req: Request, res: Response) => { - try { - const eventGroup = await EventGroup.findOne({ - id: req.params.eventGroupID, - }).lean(); + try { + const eventGroup = await EventGroup.findOne({ + id: req.params.eventGroupID, + }).lean(); - if (!eventGroup) { - return res.status(404).render("404", frontendConfig(res)); - } - const parsedDescription = markdownToSanitizedHTML( - eventGroup.description, - ); - const eventGroupEditToken = eventGroup.editToken; - const escapedName = eventGroup.name.replace(/\s+/g, "+"); - const eventGroupHasCoverImage = !!eventGroup.image; - const eventGroupHasHost = !!eventGroup.hostName; + if (!eventGroup) { + return res.status(404).render("404", frontendConfig(res)); + } + const parsedDescription = markdownToSanitizedHTML(eventGroup.description); + const eventGroupEditToken = eventGroup.editToken; + const escapedName = eventGroup.name.replace(/\s+/g, "+"); + const eventGroupHasCoverImage = !!eventGroup.image; + const eventGroupHasHost = !!eventGroup.hostName; - const events = await Event.find({ eventGroup: eventGroup._id }) - .lean() - .sort("start"); + const events = await Event.find({ eventGroup: eventGroup._id }) + .lean() + .sort("start"); - const updatedEvents: EventListEvent[] = events.map((event) => { - const startMoment = moment.tz(event.start, event.timezone).locale(i18next.language); - const endMoment = moment.tz(event.end, event.timezone).locale(i18next.language); - const isSameDay = startMoment.isSame(endMoment, "day"); + const updatedEvents: EventListEvent[] = events.map((event) => { + const startMoment = moment + .tz(event.start, event.timezone) + .locale(i18next.language); + const endMoment = moment + .tz(event.end, event.timezone) + .locale(i18next.language); + const isSameDay = startMoment.isSame(endMoment, "day"); - return { - id: event.id, - name: event.name, - location: event.location, - displayDate: isSameDay - ? startMoment.format("LL") - : `${startMoment.format("LL")} - ${endMoment.format("LL")}`, - eventHasConcluded: endMoment.isBefore( - moment.tz(event.timezone), - ), - startMoment, - endMoment, - }; - }); + return { + id: event.id, + name: event.name, + location: event.location, + displayDate: isSameDay + ? startMoment.format("LL") + : `${startMoment.format("LL")} - ${endMoment.format("LL")}`, + eventHasConcluded: endMoment.isBefore(moment.tz(event.timezone)), + startMoment, + endMoment, + }; + }); - const upcomingEventsInMonthBuckets = updatedEvents - .filter((event) => !event.eventHasConcluded) - .reduce(bucketEventsByMonth, []); + const upcomingEventsInMonthBuckets = updatedEvents + .filter((event) => !event.eventHasConcluded) + .reduce(bucketEventsByMonth, []); - const pastEventsInMonthBuckets = updatedEvents - .filter((event) => event.eventHasConcluded) - .reduce(bucketEventsByMonth, []); + const pastEventsInMonthBuckets = updatedEvents + .filter((event) => event.eventHasConcluded) + .reduce(bucketEventsByMonth, []); - let firstLoad = false; - if (eventGroup.firstLoad === true) { - firstLoad = true; - await EventGroup.findOneAndUpdate( - { id: req.params.eventGroupID }, - { firstLoad: false }, - ); - } + let firstLoad = false; + if (eventGroup.firstLoad === true) { + firstLoad = true; + await EventGroup.findOneAndUpdate( + { id: req.params.eventGroupID }, + { firstLoad: false }, + ); + } - let editingEnabled = false; - if (Object.keys(req.query).length !== 0) { - if (!req.query.e) { - editingEnabled = false; - } else { - editingEnabled = req.query.e === eventGroupEditToken; - } - } + let editingEnabled = false; + if (Object.keys(req.query).length !== 0) { + if (!req.query.e) { + editingEnabled = false; + } else { + editingEnabled = req.query.e === eventGroupEditToken; + } + } - const metadata = { - title: eventGroup.name, - description: ( - marked.parse(eventGroup.description, { - renderer: renderPlain(), - }) as string - ) - .split(" ") - .splice(0, 40) - .join(" ") - .trim(), - image: eventGroupHasCoverImage - ? `https://${res.locals.config?.general.domain}/events/` + - eventGroup.image - : null, - url: - `https://${res.locals.config?.general.domain}/` + - req.params.eventID, - }; + const metadata = { + title: eventGroup.name, + description: ( + marked.parse(eventGroup.description, { + renderer: renderPlain(), + }) as string + ) + .split(" ") + .splice(0, 40) + .join(" ") + .trim(), + image: eventGroupHasCoverImage + ? `https://${res.locals.config?.general.domain}/events/` + + eventGroup.image + : null, + url: `https://${res.locals.config?.general.domain}/` + req.params.eventID, + }; - res.set("X-Robots-Tag", "noindex"); - res.render("eventgroup", { - ...frontendConfig(res), - domain: res.locals.config?.general.domain, - title: eventGroup.name, - eventGroupData: eventGroup, - escapedName: escapedName, - upcomingEvents: upcomingEventsInMonthBuckets, - pastEvents: pastEventsInMonthBuckets, - parsedDescription: parsedDescription, - editingEnabled: editingEnabled, - eventGroupHasCoverImage: eventGroupHasCoverImage, - eventGroupHasHost: eventGroupHasHost, - firstLoad: firstLoad, - metadata: metadata, - jsonData: { - name: eventGroup.name, - id: eventGroup.id, - description: eventGroup.description, - url: eventGroup.url, - hostName: eventGroup.hostName, - creatorEmail: eventGroup.creatorEmail, - image: eventGroup.image, - editToken: editingEnabled ? eventGroupEditToken : null, - showOnPublicList: eventGroup.showOnPublicList, - }, - }); - } catch (err) { - addToLog( - "displayEventGroup", - "error", - `Attempt to display event group ${req.params.eventGroupID} failed with error: ${err}`, - ); - console.log(err); - return res.status(404).render("404", frontendConfig(res)); - } + res.set("X-Robots-Tag", "noindex"); + res.render("eventgroup", { + ...frontendConfig(res), + domain: res.locals.config?.general.domain, + title: eventGroup.name, + eventGroupData: eventGroup, + escapedName: escapedName, + upcomingEvents: upcomingEventsInMonthBuckets, + pastEvents: pastEventsInMonthBuckets, + parsedDescription: parsedDescription, + editingEnabled: editingEnabled, + eventGroupHasCoverImage: eventGroupHasCoverImage, + eventGroupHasHost: eventGroupHasHost, + firstLoad: firstLoad, + metadata: metadata, + jsonData: { + name: eventGroup.name, + id: eventGroup.id, + description: eventGroup.description, + url: eventGroup.url, + hostName: eventGroup.hostName, + creatorEmail: eventGroup.creatorEmail, + image: eventGroup.image, + editToken: editingEnabled ? eventGroupEditToken : null, + showOnPublicList: eventGroup.showOnPublicList, + }, + }); + } catch (err) { + addToLog( + "displayEventGroup", + "error", + `Attempt to display event group ${req.params.eventGroupID} failed with error: ${err}`, + ); + console.log(err); + return res.status(404).render("404", frontendConfig(res)); + } }); router.get( - "/group/:eventGroupID/feed.ics", - async (req: Request, res: Response) => { - try { - const eventGroup = await EventGroup.findOne({ - id: req.params.eventGroupID, - }).lean(); + "/group/:eventGroupID/feed.ics", + async (req: Request, res: Response) => { + try { + const eventGroup = await EventGroup.findOne({ + id: req.params.eventGroupID, + }).lean(); - if (eventGroup) { - const events = await Event.find({ - eventGroup: eventGroup._id, - }).sort("start"); - const string = exportIcal(events, eventGroup.name); - res.set("Content-Type", "text/calendar").send(string); - } - } catch (err) { - addToLog( - "eventGroupFeed", - "error", - `Attempt to display event group feed for ${req.params.eventGroupID} failed with error: ${err}`, - ); - console.log(err); - return res.status(404).render("404", frontendConfig(res)); - } - }, + if (eventGroup) { + const events = await Event.find({ + eventGroup: eventGroup._id, + }).sort("start"); + const string = exportIcal(events, eventGroup.name); + res.set("Content-Type", "text/calendar").send(string); + } + } catch (err) { + addToLog( + "eventGroupFeed", + "error", + `Attempt to display event group feed for ${req.params.eventGroupID} failed with error: ${err}`, + ); + console.log(err); + return res.status(404).render("404", frontendConfig(res)); + } + }, ); router.get("/export/event/:eventID", async (req: Request, res: Response) => { - try { - const event = await Event.findOne({ - id: req.params.eventID, - }).populate("eventGroup"); + try { + const event = await Event.findOne({ + id: req.params.eventID, + }).populate("eventGroup"); - if (event) { - const string = exportIcal([event], event.name); - res.set("Content-Type", "text/calendar").send(string); - } - } catch (err) { - addToLog( - "exportEvent", - "error", - `Attempt to export event ${req.params.eventID} failed with error: ${err}`, - ); - console.log(err); - return res.status(404).render("404", frontendConfig(res)); + if (event) { + const string = exportIcal([event], event.name); + res.set("Content-Type", "text/calendar").send(string); } + } catch (err) { + addToLog( + "exportEvent", + "error", + `Attempt to export event ${req.params.eventID} failed with error: ${err}`, + ); + console.log(err); + return res.status(404).render("404", frontendConfig(res)); + } }); router.get( - "/export/group/:eventGroupID", - async (req: Request, res: Response) => { - try { - const eventGroup = await EventGroup.findOne({ - id: req.params.eventGroupID, - }).lean(); + "/export/group/:eventGroupID", + async (req: Request, res: Response) => { + try { + const eventGroup = await EventGroup.findOne({ + id: req.params.eventGroupID, + }).lean(); - if (eventGroup) { - const events = await Event.find({ - eventGroup: eventGroup._id, - }).sort("start"); - const string = exportIcal(events, eventGroup.name); - res.set("Content-Type", "text/calendar").send(string); - } - } catch (err) { - addToLog( - "exportEvent", - "error", - `Attempt to export event group ${req.params.eventGroupID} failed with error: ${err}`, - ); - console.log(err); - return res.status(404).render("404", frontendConfig(res)); - } - }, + if (eventGroup) { + const events = await Event.find({ + eventGroup: eventGroup._id, + }).sort("start"); + const string = exportIcal(events, eventGroup.name); + res.set("Content-Type", "text/calendar").send(string); + } + } catch (err) { + addToLog( + "exportEvent", + "error", + `Attempt to export event group ${req.params.eventGroupID} failed with error: ${err}`, + ); + console.log(err); + return res.status(404).render("404", frontendConfig(res)); + } + }, ); export default router; diff --git a/src/routes/group.ts b/src/routes/group.ts index cc53976d..4965b545 100644 --- a/src/routes/group.ts +++ b/src/routes/group.ts @@ -12,16 +12,16 @@ import { checkMagicLink, getConfigMiddleware } from "../lib/middleware.js"; const storage = multer.memoryStorage(); // Accept only JPEG, GIF or PNG images, up to 10MB const upload = multer({ - storage: storage, - limits: { fileSize: 10 * 1024 * 1024 }, - fileFilter: function (_, file, cb) { - const filetypes = /jpeg|jpg|png|gif/; - const mimetype = filetypes.test(file.mimetype); - if (!mimetype) { - return cb(new Error("Only JPEG, PNG and GIF images are allowed.")); - } - cb(null, true); - }, + storage: storage, + limits: { fileSize: 10 * 1024 * 1024 }, + fileFilter: function (_, file, cb) { + const filetypes = /jpeg|jpg|png|gif/; + const mimetype = filetypes.test(file.mimetype); + if (!mimetype) { + return cb(new Error("Only JPEG, PNG and GIF images are allowed.")); + } + cb(null, true); + }, }); const router = Router(); @@ -29,271 +29,273 @@ const router = Router(); router.use(getConfigMiddleware); router.post( - "/group", - upload.single("imageUpload"), - checkMagicLink, - async (req: Request, res: Response) => { - const { data: groupData, errors } = validateGroupData(req.body); - if (errors && errors.length > 0) { - return res.status(400).json({ errors }); - } - if (!groupData) { - return res.status(400).json({ - errors: [ - { - message: "No group data was provided.", - }, - ], - }); - } + "/group", + upload.single("imageUpload"), + checkMagicLink, + async (req: Request, res: Response) => { + const { data: groupData, errors } = validateGroupData(req.body); + if (errors && errors.length > 0) { + return res.status(400).json({ errors }); + } + if (!groupData) { + return res.status(400).json({ + errors: [ + { + message: "No group data was provided.", + }, + ], + }); + } - try { - const groupID = generateEventID(); - const editToken = generateEditToken(); - let groupImageFilename; + try { + const groupID = generateEventID(); + const editToken = generateEditToken(); + let groupImageFilename; - if (req.file?.buffer) { - groupImageFilename = await Jimp.read(req.file.buffer) - .then((img) => { - img.resize(920, Jimp.AUTO) // resize - .quality(80) // set JPEG quality - .write("./public/events/" + groupID + ".jpg"); // save - return groupID + ".jpg"; - }) - .catch((err) => { - addToLog( - "Jimp", - "error", - "Attempt to edit image failed with error: " + err, - ); - }); - } + if (req.file?.buffer) { + groupImageFilename = await Jimp.read(req.file.buffer) + .then((img) => { + img + .resize(920, Jimp.AUTO) // resize + .quality(80) // set JPEG quality + .write("./public/events/" + groupID + ".jpg"); // save + return groupID + ".jpg"; + }) + .catch((err) => { + addToLog( + "Jimp", + "error", + "Attempt to edit image failed with error: " + err, + ); + }); + } - const eventGroup = new EventGroup({ - id: groupID, - name: groupData.eventGroupName, - description: groupData.eventGroupDescription, - image: groupImageFilename, - creatorEmail: groupData.creatorEmail, - url: groupData.eventGroupURL, - hostName: groupData.hostName, - editToken: editToken, - firstLoad: true, - showOnPublicList: groupData.publicBoolean, - }); + const eventGroup = new EventGroup({ + id: groupID, + name: groupData.eventGroupName, + description: groupData.eventGroupDescription, + image: groupImageFilename, + creatorEmail: groupData.creatorEmail, + url: groupData.eventGroupURL, + hostName: groupData.hostName, + editToken: editToken, + firstLoad: true, + showOnPublicList: groupData.publicBoolean, + }); - await eventGroup.save(); + await eventGroup.save(); - addToLog( - "createEventGroup", - "success", - "Event group " + groupID + " created", - ); + addToLog( + "createEventGroup", + "success", + "Event group " + groupID + " created", + ); - // Send email with edit link - if (groupData.creatorEmail) { - req.emailService.sendEmailFromTemplate({ - to: groupData.creatorEmail, - subject: eventGroup.name, - templateName: "createEventGroup", - templateData: { - eventGroupID: eventGroup.id, - editToken: eventGroup.editToken, - }, - }); - } + // Send email with edit link + if (groupData.creatorEmail) { + req.emailService.sendEmailFromTemplate({ + to: groupData.creatorEmail, + subject: eventGroup.name, + templateName: "createEventGroup", + templateData: { + eventGroupID: eventGroup.id, + editToken: eventGroup.editToken, + }, + }); + } - res.status(200).json({ - id: groupID, - editToken: editToken, - url: `/group/${groupID}?e=${editToken}`, - }); - } catch (err) { - console.error(err); - addToLog( - "createEvent", - "error", - "Attempt to create event failed with error: " + err, - ); - return res.status(500).json({ - errors: [ - { - message: err, - }, - ], - }); - } - }, + res.status(200).json({ + id: groupID, + editToken: editToken, + url: `/group/${groupID}?e=${editToken}`, + }); + } catch (err) { + console.error(err); + addToLog( + "createEvent", + "error", + "Attempt to create event failed with error: " + err, + ); + return res.status(500).json({ + errors: [ + { + message: err, + }, + ], + }); + } + }, ); router.put( - "/group/:eventGroupID", - upload.single("imageUpload"), - async (req: Request, res: Response) => { - const { data: groupData, errors } = validateGroupData(req.body); - if (errors && errors.length > 0) { - return res.status(400).json({ errors }); - } - if (!groupData) { - return res.status(400).json({ - errors: [ - { - message: "No group data was provided.", - }, - ], - }); - } + "/group/:eventGroupID", + upload.single("imageUpload"), + async (req: Request, res: Response) => { + const { data: groupData, errors } = validateGroupData(req.body); + if (errors && errors.length > 0) { + return res.status(400).json({ errors }); + } + if (!groupData) { + return res.status(400).json({ + errors: [ + { + message: "No group data was provided.", + }, + ], + }); + } - try { - const submittedEditToken = req.body.editToken; - const eventGroup = await EventGroup.findOne({ - id: req.params.eventGroupID, - }); - if (!eventGroup) { - return res.status(404).json({ - errors: [ - { - message: "Event group not found.", - }, - ], - }); - } + try { + const submittedEditToken = req.body.editToken; + const eventGroup = await EventGroup.findOne({ + id: req.params.eventGroupID, + }); + if (!eventGroup) { + return res.status(404).json({ + errors: [ + { + message: "Event group not found.", + }, + ], + }); + } - if (eventGroup.editToken !== submittedEditToken) { - // Token doesn't match - addToLog( - "editEventGroup", - "error", - `Attempt to edit event group ${req.params.eventGroupID} failed with error: token does not match`, - ); - return res.status(403).json({ - errors: [ - { - message: "Edit token is invalid.", - }, - ], - }); - } - // Token matches - // If there is a new image, upload that first - const eventGroupID = req.params.eventGroupID; - let eventGroupImageFilename = eventGroup.image; - if (req.file?.buffer) { - Jimp.read(req.file.buffer) - .then((img) => { - img.resize(920, Jimp.AUTO) // resize - .quality(80) // set JPEG quality - .write(`./public/events/${eventGroupID}.jpg`); // save - }) - .catch((err) => { - addToLog( - "Jimp", - "error", - "Attempt to edit image failed with error: " + err, - ); - }); - eventGroupImageFilename = eventGroupID + ".jpg"; - } + if (eventGroup.editToken !== submittedEditToken) { + // Token doesn't match + addToLog( + "editEventGroup", + "error", + `Attempt to edit event group ${req.params.eventGroupID} failed with error: token does not match`, + ); + return res.status(403).json({ + errors: [ + { + message: "Edit token is invalid.", + }, + ], + }); + } + // Token matches + // If there is a new image, upload that first + const eventGroupID = req.params.eventGroupID; + let eventGroupImageFilename = eventGroup.image; + if (req.file?.buffer) { + Jimp.read(req.file.buffer) + .then((img) => { + img + .resize(920, Jimp.AUTO) // resize + .quality(80) // set JPEG quality + .write(`./public/events/${eventGroupID}.jpg`); // save + }) + .catch((err) => { + addToLog( + "Jimp", + "error", + "Attempt to edit image failed with error: " + err, + ); + }); + eventGroupImageFilename = eventGroupID + ".jpg"; + } - const updatedEventGroup = { - name: req.body.eventGroupName, - description: req.body.eventGroupDescription, - url: req.body.eventGroupURL, - hostName: req.body.hostName, - image: eventGroupImageFilename, - showOnPublicList: groupData.publicBoolean, - }; + const updatedEventGroup = { + name: req.body.eventGroupName, + description: req.body.eventGroupDescription, + url: req.body.eventGroupURL, + hostName: req.body.hostName, + image: eventGroupImageFilename, + showOnPublicList: groupData.publicBoolean, + }; - await EventGroup.findOneAndUpdate( - { id: req.params.eventGroupID }, - updatedEventGroup, - ); + await EventGroup.findOneAndUpdate( + { id: req.params.eventGroupID }, + updatedEventGroup, + ); - addToLog( - "editEventGroup", - "success", - "Event group " + req.params.eventGroupID + " edited", - ); + addToLog( + "editEventGroup", + "success", + "Event group " + req.params.eventGroupID + " edited", + ); - res.sendStatus(200); - } catch (err) { - console.error(err); - addToLog( - "editEventGroup", - "error", - "Attempt to edit event group " + - req.params.eventGroupID + - " failed with error: " + - err, - ); - return res.status(500).json({ - errors: [ - { - message: err, - }, - ], - }); - } - }, + res.sendStatus(200); + } catch (err) { + console.error(err); + addToLog( + "editEventGroup", + "error", + "Attempt to edit event group " + + req.params.eventGroupID + + " failed with error: " + + err, + ); + return res.status(500).json({ + errors: [ + { + message: err, + }, + ], + }); + } + }, ); // Accepts a JSON object of event/group IDs mapped to edit tokens. // Returns an object of basic group data for each of the IDs // which are valid groups and have an edit token which matches. router.post("/known/groups", async (req: Request, res: Response) => { - const known = req.body; - if (!known) { - return res.status(400).json({ - errors: [ - { - message: "No known IDs were provided.", - }, - ], - }); - } + const known = req.body; + if (!known) { + return res.status(400).json({ + errors: [ + { + message: "No known IDs were provided.", + }, + ], + }); + } - try { - const knownIDs = Object.keys(known); - const groups = await EventGroup.find({ - id: { $in: knownIDs }, - }); - const knownGroups = groups.filter((group) => { - return group.editToken === known[group.id]; - }); - const groupData = knownGroups.map((group) => { - return { - id: group.id, - name: group.name, - description: ( - marked.parse(group.description, { - renderer: renderPlain(), - }) as string - ) - .split(" ") - .splice(0, 40) - .join(" ") - .trim(), - image: group.image, - editToken: group.editToken, - url: `/group/${group.id}`, - }; - }); - return res.status(200).json(groupData); - } catch (err) { - console.error(err); - addToLog( - "getKnownGroups", - "error", - "Attempt to get known groups failed with error: " + err, - ); - return res.status(500).json({ - errors: [ - { - message: err, - }, - ], - }); - } + try { + const knownIDs = Object.keys(known); + const groups = await EventGroup.find({ + id: { $in: knownIDs }, + }); + const knownGroups = groups.filter((group) => { + return group.editToken === known[group.id]; + }); + const groupData = knownGroups.map((group) => { + return { + id: group.id, + name: group.name, + description: ( + marked.parse(group.description, { + renderer: renderPlain(), + }) as string + ) + .split(" ") + .splice(0, 40) + .join(" ") + .trim(), + image: group.image, + editToken: group.editToken, + url: `/group/${group.id}`, + }; + }); + return res.status(200).json(groupData); + } catch (err) { + console.error(err); + addToLog( + "getKnownGroups", + "error", + "Attempt to get known groups failed with error: " + err, + ); + return res.status(500).json({ + errors: [ + { + message: err, + }, + ], + }); + } }); export default router; diff --git a/src/routes/magicLink.ts b/src/routes/magicLink.ts index 1e0f87b0..69fb20d3 100644 --- a/src/routes/magicLink.ts +++ b/src/routes/magicLink.ts @@ -10,59 +10,59 @@ const router = Router(); router.use(getConfigMiddleware); router.post("/magic-link/event/create", async (req: Request, res: Response) => { - const { email } = req.body; - if (!email) { - res.render("createEventMagicLink", { - ...frontendConfig(res), - message: { - type: "danger", - text: i18next.t("routes.magiclink.provideemail"), - }, - }); - return; - } - const allowedEmails = res.locals.config?.general.creator_email_addresses; - if (!allowedEmails?.length) { - // No creator email addresses are configured, so skip the magic link check - return res.redirect("/new"); - } - if (!allowedEmails.includes(email)) { - res.render("createEventMagicLink", { - ...frontendConfig(res), - message: { - type: "success", - text: i18next.t("routes.magiclink.thanks"), - }, - }); - return; - } - const token = generateMagicLinkToken(); - const magicLink = new MagicLink({ - email, - token, - expiryTime: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours - permittedActions: ["createEvent"], - }); - await magicLink.save(); - - // Take this opportunity to delete any expired magic links - await MagicLink.deleteMany({ expiryTime: { $lt: new Date() } }); - - req.emailService.sendEmailFromTemplate({ - to: email, - subject: i18next.t("routes.magiclink.mailsubject"), - templateName: "createEventMagicLink", - templateData: { - token - }, + const { email } = req.body; + if (!email) { + res.render("createEventMagicLink", { + ...frontendConfig(res), + message: { + type: "danger", + text: i18next.t("routes.magiclink.provideemail"), + }, }); + return; + } + const allowedEmails = res.locals.config?.general.creator_email_addresses; + if (!allowedEmails?.length) { + // No creator email addresses are configured, so skip the magic link check + return res.redirect("/new"); + } + if (!allowedEmails.includes(email)) { res.render("createEventMagicLink", { - ...frontendConfig(res), - message: { - type: "success", - text: i18next.t("routes.magiclink.thanks"), - }, + ...frontendConfig(res), + message: { + type: "success", + text: i18next.t("routes.magiclink.thanks"), + }, }); + return; + } + const token = generateMagicLinkToken(); + const magicLink = new MagicLink({ + email, + token, + expiryTime: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours + permittedActions: ["createEvent"], + }); + await magicLink.save(); + + // Take this opportunity to delete any expired magic links + await MagicLink.deleteMany({ expiryTime: { $lt: new Date() } }); + + req.emailService.sendEmailFromTemplate({ + to: email, + subject: i18next.t("routes.magiclink.mailsubject"), + templateName: "createEventMagicLink", + templateData: { + token, + }, + }); + res.render("createEventMagicLink", { + ...frontendConfig(res), + message: { + type: "success", + text: i18next.t("routes.magiclink.thanks"), + }, + }); }); export default router; diff --git a/src/routes/static.ts b/src/routes/static.ts index 6670214c..3423bc45 100644 --- a/src/routes/static.ts +++ b/src/routes/static.ts @@ -10,30 +10,30 @@ const router = Router(); router.use(getConfigMiddleware); if (config.static_pages?.length) { - config.static_pages - .filter((page) => page.path?.startsWith("/") && page.filename) - .forEach((page) => { - router.get(page.path, (_: Request, res: Response) => { - try { - if (fs.existsSync(`./static/${page.filename}`)) { - const fileBody = fs.readFileSync( - `./static/${page.filename}`, - "utf-8", - ); - const parsed = markdownToSanitizedHTML(fileBody); - return res.render("static", { - title: page.title, - content: parsed, - ...frontendConfig(res), - }); - } - return res.status(404).render("404", frontendConfig(res)); - } catch (err) { - console.error(err); - return res.status(404).render("404", frontendConfig(res)); - } + config.static_pages + .filter((page) => page.path?.startsWith("/") && page.filename) + .forEach((page) => { + router.get(page.path, (_: Request, res: Response) => { + try { + if (fs.existsSync(`./static/${page.filename}`)) { + const fileBody = fs.readFileSync( + `./static/${page.filename}`, + "utf-8", + ); + const parsed = markdownToSanitizedHTML(fileBody); + return res.render("static", { + title: page.title, + content: parsed, + ...frontendConfig(res), }); - }); + } + return res.status(404).render("404", frontendConfig(res)); + } catch (err) { + console.error(err); + return res.status(404).render("404", frontendConfig(res)); + } + }); + }); } export default router; diff --git a/src/start.ts b/src/start.ts index b30d3311..40c1de5d 100755 --- a/src/start.ts +++ b/src/start.ts @@ -5,22 +5,22 @@ import app from "./app.js"; const config = getConfig(); mongoose.connect(config.database.mongodb_url, { - useNewUrlParser: true, - useUnifiedTopology: true, + useNewUrlParser: true, + useUnifiedTopology: true, }); mongoose.set("useCreateIndex", true); mongoose.set("useFindAndModify", false); mongoose.Promise = global.Promise; mongoose.connection - .on("connected", () => { - console.log("Mongoose connection open!"); - }) - .on("error", (err: Error) => { - console.log(`Connection error: ${err.message}`); - }); + .on("connected", () => { + console.log("Mongoose connection open!"); + }) + .on("error", (err: Error) => { + console.log(`Connection error: ${err.message}`); + }); app.listen(config.general.port, () => { - console.log( - `Welcome to gathio! The app is now running on http://localhost:${config.general.port}`, - ); + console.log( + `Welcome to gathio! The app is now running on http://localhost:${config.general.port}`, + ); }); diff --git a/src/types/i18next-fs-backend.d.ts b/src/types/i18next-fs-backend.d.ts index 33714e7d..56a4ea77 100644 --- a/src/types/i18next-fs-backend.d.ts +++ b/src/types/i18next-fs-backend.d.ts @@ -1,5 +1,5 @@ -declare module 'i18next-fs-backend' { - import { BackendModule } from 'i18next'; - const backend: BackendModule; - export default backend; -} \ No newline at end of file +declare module "i18next-fs-backend" { + import { BackendModule } from "i18next"; + const backend: BackendModule; + export default backend; +} diff --git a/src/util/generator.ts b/src/util/generator.ts index 0755d8ea..2545f431 100644 --- a/src/util/generator.ts +++ b/src/util/generator.ts @@ -4,15 +4,15 @@ import { customAlphabet } from "nanoid"; // This alphabet (used to generate all event, group, etc. IDs) is missing '-' // because ActivityPub doesn't like it in IDs const nanoid = customAlphabet( - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_", - 21, + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_", + 21, ); const generateAlphanumericString = (length: number) => { - return Array(length) - .fill(0) - .map(() => Math.random().toString(36).charAt(2)) - .join(""); + return Array(length) + .fill(0) + .map(() => Math.random().toString(36).charAt(2)) + .join(""); }; export const generateEventID = () => nanoid(); @@ -22,18 +22,18 @@ export const generateEditToken = () => generateAlphanumericString(32); export const generateMagicLinkToken = () => generateAlphanumericString(32); export const generateRSAKeypair = () => { - return crypto.generateKeyPairSync("rsa", { - modulusLength: 4096, - publicKeyEncoding: { - type: "spki", - format: "pem", - }, - privateKeyEncoding: { - type: "pkcs8", - format: "pem", - }, - }); + return crypto.generateKeyPairSync("rsa", { + modulusLength: 4096, + publicKeyEncoding: { + type: "spki", + format: "pem", + }, + privateKeyEncoding: { + type: "pkcs8", + format: "pem", + }, + }); }; export const hashString = (input: string) => - crypto.createHash("sha256").update(input).digest("hex"); + crypto.createHash("sha256").update(input).digest("hex"); diff --git a/src/util/markdown.ts b/src/util/markdown.ts index 6e7203cb..6fa690c3 100644 --- a/src/util/markdown.ts +++ b/src/util/markdown.ts @@ -4,51 +4,51 @@ import DOMPurify from "dompurify"; // ? to ? helper function htmlEscapeToText(text: string) { - return text.replace(/&#[0-9]*;|&/g, function (escapeCode) { - if (escapeCode.match(/amp/)) { - return "&"; - } - const code = escapeCode.match(/[0-9]+/); - return String.fromCharCode(Number(code)); - }); + return text.replace(/&#[0-9]*;|&/g, function (escapeCode) { + if (escapeCode.match(/amp/)) { + return "&"; + } + const code = escapeCode.match(/[0-9]+/); + return String.fromCharCode(Number(code)); + }); } // Extra marked renderer (used to render plaintext event description for page metadata) // Adapted from https://dustinpfister.github.io/2017/11/19/nodejs-marked/ export const renderPlain = () => { - const render = new marked.Renderer(); - // render just the text of a link, strong, em - render.link = function (href, title, text) { - return text; - }; - render.strong = function (text) { - return text; - }; - render.em = function (text) { - return text; - }; - // render just the text of a paragraph - render.paragraph = function (text) { - return htmlEscapeToText(text) + "\r\n"; - }; - // render nothing for headings, images, and br - render.heading = function (_text, _level) { - return ""; - }; - render.image = function (_href, _title, _text) { - return ""; - }; - render.br = function () { - return ""; - }; - return render; + const render = new marked.Renderer(); + // render just the text of a link, strong, em + render.link = function (href, title, text) { + return text; + }; + render.strong = function (text) { + return text; + }; + render.em = function (text) { + return text; + }; + // render just the text of a paragraph + render.paragraph = function (text) { + return htmlEscapeToText(text) + "\r\n"; + }; + // render nothing for headings, images, and br + render.heading = function (_text, _level) { + return ""; + }; + render.image = function (_href, _title, _text) { + return ""; + }; + render.br = function () { + return ""; + }; + return render; }; export const markdownToSanitizedHTML = (markdown: string) => { - const html = marked.parse(markdown) as string; - const window = new JSDOM("").window; - const purify = DOMPurify(window); - const clean = purify.sanitize(html); - return clean; + const html = marked.parse(markdown) as string; + const window = new JSDOM("").window; + const purify = DOMPurify(window); + const clean = purify.sanitize(html); + return clean; }; diff --git a/src/util/messages.ts b/src/util/messages.ts index ae1568cb..fcd1a2c9 100644 --- a/src/util/messages.ts +++ b/src/util/messages.ts @@ -1,9 +1,9 @@ type MessageId = "unattend"; const queryStringMessages: Record = { - unattend: `You have been removed from this event.`, + unattend: `You have been removed from this event.`, }; export const getMessage = (id?: string) => { - return queryStringMessages[id as MessageId] || ""; + return queryStringMessages[id as MessageId] || ""; }; diff --git a/src/util/validation.ts b/src/util/validation.ts index 8eae2cb0..30b4fa6f 100644 --- a/src/util/validation.ts +++ b/src/util/validation.ts @@ -2,269 +2,269 @@ import i18next from "i18next"; import moment from "moment-timezone"; type Error = { - message?: string; - field?: string; + message?: string; + field?: string; }; type EventValidationResponse = { - data?: ValidatedEventData; - errors?: Error[]; + data?: ValidatedEventData; + errors?: Error[]; }; type EventGroupValidationResponse = { - data?: ValidatedEventGroupData; - errors?: Error[]; + data?: ValidatedEventGroupData; + errors?: Error[]; }; interface EventData { - eventName: string; - eventLocation: string; - eventStart: string; - eventEnd: string; - timezone: string; - eventDescription: string; - eventURL: string; - imagePath: string; - hostName: string; - creatorEmail: string; - publicCheckbox: string; - eventGroupCheckbox: string; - eventGroupID: string; - eventGroupEditToken: string; - interactionCheckbox: string; - joinCheckbox: string; - maxAttendeesCheckbox: string; - maxAttendees: number; + eventName: string; + eventLocation: string; + eventStart: string; + eventEnd: string; + timezone: string; + eventDescription: string; + eventURL: string; + imagePath: string; + hostName: string; + creatorEmail: string; + publicCheckbox: string; + eventGroupCheckbox: string; + eventGroupID: string; + eventGroupEditToken: string; + interactionCheckbox: string; + joinCheckbox: string; + maxAttendeesCheckbox: string; + maxAttendees: number; } // EventData without the 'checkbox' fields export type ValidatedEventData = Omit< - EventData, - | "publicCheckbox" - | "eventGroupCheckbox" - | "interactionCheckbox" - | "joinCheckbox" - | "maxAttendeesCheckbox" + EventData, + | "publicCheckbox" + | "eventGroupCheckbox" + | "interactionCheckbox" + | "joinCheckbox" + | "maxAttendeesCheckbox" > & { - publicBoolean: boolean; - eventGroupBoolean: boolean; - interactionBoolean: boolean; - joinBoolean: boolean; - maxAttendeesBoolean: boolean; + publicBoolean: boolean; + eventGroupBoolean: boolean; + interactionBoolean: boolean; + joinBoolean: boolean; + maxAttendeesBoolean: boolean; }; interface EventGroupData { - eventGroupName: string; - eventGroupDescription: string; - eventGroupURL: string; - hostName: string; - creatorEmail: string; - publicCheckbox: string; + eventGroupName: string; + eventGroupDescription: string; + eventGroupURL: string; + hostName: string; + creatorEmail: string; + publicCheckbox: string; } export type ValidatedEventGroupData = Omit & { - publicBoolean: boolean; + publicBoolean: boolean; }; const validateEmail = (email: string) => { - if (!email || email.length === 0 || typeof email !== "string") { - return false; - } - const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return re.test(email); + if (!email || email.length === 0 || typeof email !== "string") { + return false; + } + const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return re.test(email); }; // From https://stackoverflow.com/a/43467144 const validateUrl = (url: string) => { - if (!url) { - return false; - } - let validUrl; - try { - validUrl = new URL(url); - } catch { - return false; - } - return validUrl.protocol === "http:" || validUrl.protocol === "https:"; + if (!url) { + return false; + } + let validUrl; + try { + validUrl = new URL(url); + } catch { + return false; + } + return validUrl.protocol === "http:" || validUrl.protocol === "https:"; }; export const validateEventTime = (start: Date, end: Date): Error | boolean => { - if (moment(start).isAfter(moment(end))) { - return { - message: i18next.t('util.validation.eventtime.startisafter'), - field: "eventStart", - }; - } - if (moment(start).isBefore(moment())) { - return { - message: i18next.t('util.validation.eventtime.startisbefore'), - field: "eventStart", - }; - } - if (moment(end).isBefore(moment())) { - return { - message: i18next.t('util.validation.eventtime.endisbefore'), - field: "eventEnd", - }; - } - // Duration cannot be longer than 1 year - if (moment(end).diff(moment(start), "years") > 1) { - return { - message: i18next.t("util.validation.eventtime.endyears"), - field: "eventEnd", - }; - } - return true; + if (moment(start).isAfter(moment(end))) { + return { + message: i18next.t("util.validation.eventtime.startisafter"), + field: "eventStart", + }; + } + if (moment(start).isBefore(moment())) { + return { + message: i18next.t("util.validation.eventtime.startisbefore"), + field: "eventStart", + }; + } + if (moment(end).isBefore(moment())) { + return { + message: i18next.t("util.validation.eventtime.endisbefore"), + field: "eventEnd", + }; + } + // Duration cannot be longer than 1 year + if (moment(end).diff(moment(start), "years") > 1) { + return { + message: i18next.t("util.validation.eventtime.endyears"), + field: "eventEnd", + }; + } + return true; }; export const validateEventData = ( - eventData: EventData, + eventData: EventData, ): EventValidationResponse => { - const validatedData: ValidatedEventData = { - ...eventData, - publicBoolean: eventData.publicCheckbox === "true", - eventGroupBoolean: eventData.eventGroupCheckbox === "true", - interactionBoolean: eventData.interactionCheckbox === "true", - joinBoolean: eventData.joinCheckbox === "true", - maxAttendeesBoolean: eventData.maxAttendeesCheckbox === "true", - }; - const errors: Error[] = []; - if (!validatedData.eventName) { - errors.push({ - message: i18next.t('util.validation.eventdata.eventname'), - field: "eventName", - }); - } - if (!validatedData.eventLocation) { - errors.push({ - message: i18next.t("util.validation.eventdata.eventlocation"), - field: "eventLocation", - }); - } - if (!validatedData.eventStart) { - errors.push({ - message: i18next.t("util.validation.eventdata.eventstart"), - field: "eventStart", - }); - } - if (!validatedData.eventEnd) { - errors.push({ - message: i18next.t("util.validation.eventdata.eventend"), - field: "eventEnd", - }); - } - const timeValidation = validateEventTime( - new Date(validatedData.eventStart), - new Date(validatedData.eventEnd), - ); - if (timeValidation !== true && timeValidation !== false) { - errors.push({ - message: timeValidation.message, - }); - } - if (!validatedData.timezone) { - errors.push({ - message: i18next.t("util.validation.eventdata.timezone"), - field: "timezone", - }); + const validatedData: ValidatedEventData = { + ...eventData, + publicBoolean: eventData.publicCheckbox === "true", + eventGroupBoolean: eventData.eventGroupCheckbox === "true", + interactionBoolean: eventData.interactionCheckbox === "true", + joinBoolean: eventData.joinCheckbox === "true", + maxAttendeesBoolean: eventData.maxAttendeesCheckbox === "true", + }; + const errors: Error[] = []; + if (!validatedData.eventName) { + errors.push({ + message: i18next.t("util.validation.eventdata.eventname"), + field: "eventName", + }); + } + if (!validatedData.eventLocation) { + errors.push({ + message: i18next.t("util.validation.eventdata.eventlocation"), + field: "eventLocation", + }); + } + if (!validatedData.eventStart) { + errors.push({ + message: i18next.t("util.validation.eventdata.eventstart"), + field: "eventStart", + }); + } + if (!validatedData.eventEnd) { + errors.push({ + message: i18next.t("util.validation.eventdata.eventend"), + field: "eventEnd", + }); + } + const timeValidation = validateEventTime( + new Date(validatedData.eventStart), + new Date(validatedData.eventEnd), + ); + if (timeValidation !== true && timeValidation !== false) { + errors.push({ + message: timeValidation.message, + }); + } + if (!validatedData.timezone) { + errors.push({ + message: i18next.t("util.validation.eventdata.timezone"), + field: "timezone", + }); + } + if (!validatedData.eventDescription) { + errors.push({ + message: i18next.t("util.validation.eventdata.eventdescription"), + field: "eventDescription", + }); + } + if (validatedData.eventGroupBoolean) { + if (!validatedData.eventGroupID) { + errors.push({ + message: i18next.t("util.validation.eventdata.eventgroupboolean"), + field: "eventGroupID", + }); } - if (!validatedData.eventDescription) { - errors.push({ - message: i18next.t("util.validation.eventdata.eventdescription"), - field: "eventDescription", - }); + if (!validatedData.eventGroupEditToken) { + errors.push({ + message: i18next.t("util.validation.eventdata.eventgroupedittoken"), + field: "eventGroupEditToken", + }); } - if (validatedData.eventGroupBoolean) { - if (!validatedData.eventGroupID) { - errors.push({ - message: i18next.t("util.validation.eventdata.eventgroupboolean"), - field: "eventGroupID", - }); - } - if (!validatedData.eventGroupEditToken) { - errors.push({ - message: i18next.t("util.validation.eventdata.eventgroupedittoken"), - field: "eventGroupEditToken", - }); - } + } + if (validatedData.maxAttendeesBoolean) { + if (!validatedData.maxAttendees) { + errors.push({ + message: i18next.t("util.validation.eventdata.maxattendeesboolean"), + field: "maxAttendees", + }); } - if (validatedData.maxAttendeesBoolean) { - if (!validatedData.maxAttendees) { - errors.push({ - message: i18next.t("util.validation.eventdata.maxattendeesboolean"), - field: "maxAttendees", - }); - } - if (isNaN(validatedData.maxAttendees)) { - errors.push({ - message: i18next.t("util.validation.eventdata.maxattendees"), - field: "maxAttendees", - }); - } + if (isNaN(validatedData.maxAttendees)) { + errors.push({ + message: i18next.t("util.validation.eventdata.maxattendees"), + field: "maxAttendees", + }); } - if (validatedData.creatorEmail) { - if (!validateEmail(validatedData.creatorEmail)) { - errors.push({ - message: i18next.t("util.validation.eventdata.creatoremail"), - field: "creatorEmail", - }); - } + } + if (validatedData.creatorEmail) { + if (!validateEmail(validatedData.creatorEmail)) { + errors.push({ + message: i18next.t("util.validation.eventdata.creatoremail"), + field: "creatorEmail", + }); } - if (validatedData.eventURL) { - if (!validateUrl(validatedData.eventURL)) { - errors.push({ - message: i18next.t("util.validation.eventdata.eventurl"), - field: "eventURL", - }); - } + } + if (validatedData.eventURL) { + if (!validateUrl(validatedData.eventURL)) { + errors.push({ + message: i18next.t("util.validation.eventdata.eventurl"), + field: "eventURL", + }); } + } - return { - data: validatedData, - errors: errors, - }; + return { + data: validatedData, + errors: errors, + }; }; export const validateGroupData = ( - groupData: EventGroupData, + groupData: EventGroupData, ): EventGroupValidationResponse => { - const errors: Error[] = []; - if (!groupData.eventGroupName) { - errors.push({ - message: i18next.t("util.validation.groupdata.eventgroupname"), - field: "eventGroupName", - }); - } - if (!groupData.eventGroupDescription) { - errors.push({ - message: i18next.t("util.validation.groupdata.eventgroupdescription"), - field: "eventGroupDescription", - }); - } - if (groupData.creatorEmail) { - if (!validateEmail(groupData.creatorEmail)) { - errors.push({ - message: i18next.t("util.validation.groupdata.creatoremail"), - field: "creatorEmail", - }); - } + const errors: Error[] = []; + if (!groupData.eventGroupName) { + errors.push({ + message: i18next.t("util.validation.groupdata.eventgroupname"), + field: "eventGroupName", + }); + } + if (!groupData.eventGroupDescription) { + errors.push({ + message: i18next.t("util.validation.groupdata.eventgroupdescription"), + field: "eventGroupDescription", + }); + } + if (groupData.creatorEmail) { + if (!validateEmail(groupData.creatorEmail)) { + errors.push({ + message: i18next.t("util.validation.groupdata.creatoremail"), + field: "creatorEmail", + }); } - if (groupData.eventGroupURL) { - if (!validateUrl(groupData.eventGroupURL)) { - errors.push({ - message: i18next.t("util.validation.groupdata.eventgroupurl"), - field: "eventGroupURL", - }); - } + } + if (groupData.eventGroupURL) { + if (!validateUrl(groupData.eventGroupURL)) { + errors.push({ + message: i18next.t("util.validation.groupdata.eventgroupurl"), + field: "eventGroupURL", + }); } + } - const validatedData: ValidatedEventGroupData = { - ...groupData, - publicBoolean: groupData.publicCheckbox === "true", - }; + const validatedData: ValidatedEventGroupData = { + ...groupData, + publicBoolean: groupData.publicCheckbox === "true", + }; - return { - data: validatedData, - errors: errors, - }; + return { + data: validatedData, + errors: errors, + }; }; diff --git a/static/instance-description-ja.md b/static/instance-description-ja.md index 78cfe3e2..2e621124 100644 --- a/static/instance-description-ja.md +++ b/static/instance-description-ja.md @@ -1 +1 @@ -**{{ siteName }}** は Gathio――簡単に、プライバシーファーストで、連合プロトコルにも対応したイベントの主催と参加、共有を支援するプラットフォームで稼働しています。 \ No newline at end of file +**{{ siteName }}** は Gathio――簡単に、プライバシーファーストで、連合プロトコルにも対応したイベントの主催と参加、共有を支援するプラットフォームで稼働しています。 diff --git a/tsconfig.json b/tsconfig.json index 3cd8df75..abb01b52 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,10 +6,7 @@ "checkJs": false, "removeComments": true, "resolveJsonModule": true, - "typeRoots": [ - "./node_modules/@types", - "./src/types" - ], + "typeRoots": ["./node_modules/@types", "./src/types"], "sourceMap": true, "outDir": "dist", "strict": true, @@ -21,13 +18,7 @@ "moduleResolution": "NodeNext", "skipLibCheck": true }, - "include": [ - "./src/**/*.ts" - ], - "exclude": [ - "./src/**/*.js" - ], - "files": [ - "./src/index.d.ts" - ] + "include": ["./src/**/*.ts"], + "exclude": ["./src/**/*.js"], + "files": ["./src/index.d.ts"] }