From 871da9cc1114a676fb192a91cab88347054c5258 Mon Sep 17 00:00:00 2001 From: Patryk Kubiak Date: Sun, 4 Oct 2026 22:27:45 +0200 Subject: [PATCH 1/3] dmvs_html: HTML and CSS into dmvsi documents --- .claude/skills/dmod-boot/SKILL.md | 65 + .../skills/dmod-coding-conventions/SKILL.md | 90 + .claude/skills/dmod-ecosystem/SKILL.md | 118 + .claude/skills/dmod-hardware-ports/SKILL.md | 45 + .claude/skills/dmod-module-structure/SKILL.md | 49 + .github/workflows/ci.yml | 42 + .github/workflows/release.yml | 279 ++ .gitignore | 32 +- .vscode/settings.json | 5 + .vscode/tasks.json | 55 + CMakeLists.txt | 83 + Makefile | 49 + dmvs_html.dmr | 35 + include/.gitkeep | 0 manifest.dmm | 5 + scripts/sync-claude.sh | 117 + src/css.c | 1068 ++++++++ src/dmvs_html.c | 524 ++++ src/html.c | 460 ++++ src/layout.c | 1700 ++++++++++++ src/paint.c | 681 +++++ src/private.h | 473 ++++ src/style.c | 2278 +++++++++++++++++ src/tailwind.c | 1250 +++++++++ tests/CMakeLists.txt | 34 + tests/dmvs_html_test.c | 8 + 26 files changed, 9527 insertions(+), 18 deletions(-) create mode 100644 .claude/skills/dmod-boot/SKILL.md create mode 100644 .claude/skills/dmod-coding-conventions/SKILL.md create mode 100644 .claude/skills/dmod-ecosystem/SKILL.md create mode 100644 .claude/skills/dmod-hardware-ports/SKILL.md create mode 100644 .claude/skills/dmod-module-structure/SKILL.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 dmvs_html.dmr create mode 100644 include/.gitkeep create mode 100644 manifest.dmm create mode 100755 scripts/sync-claude.sh create mode 100644 src/css.c create mode 100644 src/dmvs_html.c create mode 100644 src/html.c create mode 100644 src/layout.c create mode 100644 src/paint.c create mode 100644 src/private.h create mode 100644 src/style.c create mode 100644 src/tailwind.c create mode 100644 tests/CMakeLists.txt create mode 100644 tests/dmvs_html_test.c diff --git a/.claude/skills/dmod-boot/SKILL.md b/.claude/skills/dmod-boot/SKILL.md new file mode 100644 index 0000000..efe6bb1 --- /dev/null +++ b/.claude/skills/dmod-boot/SKILL.md @@ -0,0 +1,65 @@ +--- +name: dmod-boot +description: Explains dmod-boot, the firmware entry point that initializes hardware/FreeRTOS/heap/VFS/logging and boots into a loaded module package — how module packaging (modules.dmp) works, how to test a locally-built .dmf on real hardware without a release, and the flash/connect/monitor CMake targets for working with real boards. Load this when working in the dmod-boot repo, tracing how firmware boots into a usable system, or flashing/debugging a board. Companion to the dmod-ecosystem skill. +--- + +# `dmod-boot`: the entry point + +`dmod-boot` is the bootloader/firmware entry point for a real board. It +initializes hardware, FreeRTOS (via `dmosi-freertos`), heap (`dmheap`), VFS +(`dmvfs`, mounting `dmramfs` on `/`, `dmdevfs` on `/dev`), logging (`dmlog`), +then loads a package of modules embedded directly in ROM at link time and +starts a main module — typically the shell, `dmell`. If you're tracing "how +does firmware boot into a usable system", `dmod-boot/src/main.c` is the +starting point. + +**Module packaging.** During the build, `dmod-boot` bundles the modules it +finds in `/build/dmf/` into a single `modules.dmp` package, which +is then embedded in flash alongside the rest of the firmware (`modules/modules.dmd` +is the manifest that determines which modules get fetched/built into +`build/dmf/` in the first place). A module can be present there in two forms: +`.dmf` (plain) and `.dmfc` (the same module, fastlz-compressed). +`dmf-get` normally downloads both, and **the `.dmfc` takes precedence when the +module is loaded**. + +**Testing a locally-built module on real hardware, without a release:** drop +the `.dmf` binary straight into `/build/dmf/`, **delete that +module's `.dmfc` from the same directory**, then delete +`/build/modules.dmp` and `/build/__modules_dmp.o` so the +next build is forced to regenerate the package (an incremental build won't +notice a `.dmf` was added/changed in place otherwise). + +Skipping the `.dmfc` step is the classic trap: the build succeeds, the new +`.dmf` really is in `build/dmf/`, and the board still silently runs the old +released module. It usually surfaces as a *dependency* error rather than an +obvious "stale module" one — the released build was linked against older +versions of its dependencies, so you get +`Module version mismatch: != ` and +`API '@:/' is not connected`, followed by the module failing +to enable. Two things make this hard to see: `strings build/modules.dmp` will +*not* find the offending signature (it is compressed inside the `.dmfc`), and +comparing `.dmf`/`.dmfc` timestamps in `build/dmf/` is the quickest way to +spot it — every module should have a matching pair, so the one module whose +two files disagree is the culprit. + +**Beware of reconfiguring while testing a local module.** Re-running +`cmake -S . -B ` (as opposed to `cmake --build `) re-runs +`dmf-get`, which re-downloads *every* module into `build/dmf/` and overwrites +any `.dmf` you dropped there by hand — and restores the `.dmfc` you deleted. +After any reconfigure, redo the steps above. Note also that `dmf-get` resolves +each repo's dependencies independently, so a refresh can legitimately leave +the set internally inconsistent (e.g. a command module still built against an +older version of a library module it uses); such a module fails to bind on its +own, which is harmless to the rest of the boot, but it is worth telling apart +from the stale-`.dmfc` case above. + +**Flashing and debugging** (from `/build`): +- `cmake --build . --target install-firmware` — flash the firmware to the board. +- `cmake --build . --target connect` — attach OpenOCD to the target. +- `cmake --build . --target monitor` — attach a console to `dmell` over the + existing OpenOCD connection (run `connect` first). This gives you stdin/stdout + independent of any firmware-level driver — you get a working shell even + before a UART driver is loaded, since it doesn't go through `dmuart`/`dmtty` + at all. Log output for this relies on `dmlog`, the base/kernel-level logging + system used from very early boot (before most modules are up), which is why + `dmod-boot` depends on it directly rather than through a loaded module. diff --git a/.claude/skills/dmod-coding-conventions/SKILL.md b/.claude/skills/dmod-coding-conventions/SKILL.md new file mode 100644 index 0000000..bdda94b --- /dev/null +++ b/.claude/skills/dmod-coding-conventions/SKILL.md @@ -0,0 +1,90 @@ +--- +name: dmod-coding-conventions +description: Coding conventions for DMOD module C code — heap-allocate strings/lists instead of fixed-size buffers (Dmod_StrDup, dmlist), never statically link a dependency into a module (always dmod_link_modules), keep functions under ~50 lines, and the _create/_destroy opaque-context pattern with a magic-field guard. Load this when writing or reviewing module implementation code (.c/.h files) in a dmod-* repo. Companion to the dmod-ecosystem skill. +--- + +# DMOD Module Coding Conventions + +## Strings: prefer the heap over fixed-size buffers + +The target is embedded (STM32F4/F7, ESP32-S3) — RAM is scarce and every byte +of it matters, so default to heap-allocating rather than reserving fixed +static/stack space "just in case". This applies to strings in general, not +just paths: don't declare a fixed-size `char buf[N]` for a string whose +length isn't tightly bounded — heap-allocate it instead. Beyond the general +RAM-budget argument, fixed-size stack/static buffers silently truncate or +overflow once a value gets longer than whoever wrote `N` expected, which is a +real risk for anything composed at runtime (paths, module names, mount +points under `/`, `.dmr`/`.dmm` entries, log/error messages, etc.). A static +fixed-size array is only acceptable when you can genuinely guarantee the +string will never exceed ~20 bytes (e.g. a short fixed enum-like tag) — +paths and other composed/runtime strings do not qualify. + +When you need an owned copy of a string (not just a borrowed pointer), copy +it with `Dmod_StrDup` rather than `malloc` + `strcpy`/`memcpy`, and `free` it +when done. + +The same reasoning applies to lists: don't hand-roll a fixed-size static +array for a collection whose size grows/shrinks at runtime. Use the `dmlist` +module instead — pull it in with `dmod_link_modules(${DMOD_MODULE_NAME} +dmlist)` in `CMakeLists.txt` (see `dmtty/CMakeLists.txt` for a working +example) rather than reimplementing a dynamic list. + +## Never statically link libraries into a module + +Always link a module against another module's `_if` target dynamically +(`dmod_link_modules`, the standard Built-in API / MAL / DIF mechanisms) — +never statically link a library's object code directly into a module. DMOD +modules are loaded/unloaded independently at runtime; statically linking a +dependency into a module defeats that (bloats the `.dmf`, duplicates the +dependency's RAM/flash footprint across every module that links it, and +breaks the loader's ability to resolve/share a single instance of it). If +you find yourself reaching for `target_link_libraries` with anything other +than a module's own `_if` interface target, stop and use +`dmod_link_modules` instead. + +## Keep functions short and extract shared logic + +Functions should stay short — **max ~50 lines**. If a function is growing +past that, it's a signal to split it, not to keep going. Always look for the +common part between two similar code paths (two callers doing almost the +same thing, two branches with overlapping logic, ...) and pull it into its +own function rather than duplicating it inline. + +## Prefer the `_create`/`_destroy` context pattern + +The preferred way to manage a piece of stateful data is a context object: +a `_create` function allocates and initializes a context struct holding all +the data that logic needs, callers thread that context pointer through the +rest of the API, and a `_destroy` function frees it once it's no longer +needed. See `dm_sw_ring_create()`/`dm_sw_ring_destroy()` in +`dm_sw_ring/include/dm_sw_ring.h` for a worked example of this shape. + +**Context structs must be opaque/private.** Declare only the typedef in the +public/interface header, and define the actual struct body in the private +source file: + +```c +/* public header (e.g. include/.h) */ +typedef struct context context_t; +``` + +```c +/* private file (e.g. src/.c or a private header) */ +struct context +{ + ... +}; +``` + +This keeps callers from reaching into the struct directly or depending on +its layout — all access must go through the module's API functions. + +**Guard context structs with a magic field.** Give the struct a `magic` +field (`uint32_t`) set in `_create` to a fixed value derived from a short +form of the module's name (e.g. a 4-char tag packed into a `uint32_t`), and +check it at the top of every function that receives the context pointer, +before the context is dereferenced (or used) — the check should be present +in every entry point (`_destroy` included), not just a subset. This catches +use of a stale/freed, corrupted, or wrong-type pointer early instead of +leading to undefined behavior further down. diff --git a/.claude/skills/dmod-ecosystem/SKILL.md b/.claude/skills/dmod-ecosystem/SKILL.md new file mode 100644 index 0000000..b267107 --- /dev/null +++ b/.claude/skills/dmod-ecosystem/SKILL.md @@ -0,0 +1,118 @@ +--- +name: dmod-ecosystem +description: Entry-point map of the DMOD (Dynamic Modules) embedded ecosystem architecture — the core loader, the three inter-module communication mechanisms (Built-in API, MAL, DIF), and the shared CMake/Make build system. Load this first when working in any dmod-* repository (dmod, dmod-boot, dmuart, dmtty, dmfmc, dmell, dm_sw_ring, dmclk, dmgpio, dmini, dmheap, dmvfs, dmffs, dmdevfs, dmdrvi, dmhaman, dmosi*, or a new module created from this template) to understand how the repo fits into the larger system before making changes. Points to companion skills (dmod-module-structure, dmod-boot, dmod-hardware-ports, dmod-coding-conventions) for deeper topics. +--- + +# DMOD Ecosystem + +DMOD ("Dynamic Modules") is a framework for dynamically loading and running code +on embedded microcontrollers (STM32F4/F7, ESP32-S3 seen so far), similar in spirit +to shared libraries on a desktop OS but built for firmware. A "module" is a +self-contained unit of code (library or application) compiled to a `.dmf` +(DMOD File) — optionally compressed to `.dmfc` — that a loader can load, run, +and unload at runtime without recompiling or resetting the whole firmware. + +This ecosystem is spread across many small, independent git repositories under +one parent directory (each repo = one module, or the core framework). There is +no monorepo and no shared parent git tree — treat each repo you open as +self-contained, but expect it to `FetchContent` the `dmod` repo at configure time. + +**Naming convention: snake_case everywhere** — module names, file names, +functions, types (`dm_sw_ring`, `dm_sw_ring_create`, `dm_sw_ring_t`, +`dmfmc_configure_sdram`, ...). Don't introduce camelCase or PascalCase names +in module code (the handful of `Dmod_*`/`Pascal_Case` symbols you'll see, like +`Dmod_Printf`, are dmod's own SAL/core API, not a pattern to copy in module code). + +## The core repo: `dmod` + +`dmod` is the SDK and build system every other repo depends on. It provides: + +- **The loader/runtime** (`src/system/`, `src/module/`) — loads `.dmf`/`.dmfc`/`.dmp` + files, manages module contexts, resolves inter-module calls. +- **Two build modes**, selected via `DMOD_MODE`: + - `DMOD_SYSTEM` — builds the loader itself, embedded in firmware. + - `DMOD_MODULE` — builds a single loadable module (`.dmf`). +- **CMake functions** every module repo uses (see `dmod/docs/cmake-functions.md` + for the full reference): + - `dmod_add_library(name version sources...)` / `dmod_add_executable(...)` — + create a module. Reads `DMOD_MODULE_NAME`, `DMOD_MODULE_VERSION`, + `DMOD_AUTHOR_NAME`, `DMOD_STACK_SIZE`, optionally `DMOD_PRIORITY`, + `DMOD_MAL_IMPLS`, `DMOD_DIF_IMPLS`, `DMOD_DMR_PATH` (set before calling). + Also creates a `_if` INTERFACE target for consumers that only need headers. + - `dmod_link_modules(target [PRIVATE|PUBLIC|INTERFACE] module[@version]...)` — + downloads another module's headers via `dmf-get` at configure time and adds + them as include dirs. This is how modules depend on each other without + vendoring source. + - `dmod_setup_external_module()` — call after `project()` in a standalone repo + to wire up DMOD's CMake machinery. +- **The package manager**: `dmf-get` (installs/downloads modules), plus + `todmfc`/`todmd`/`todmm`/`todmp`/`mkdmrpkg`/`dmf-man` tools and three manifest + file formats, all documented under `dmod/docs/`: + - `.dmm` (manifest) — registry mapping module name → download URL. + - `.dmr` (resource file) — declares what files go where when a module is + installed/packaged (`docs/dmr-file-format.md`). + - `.dmd` (dependencies) — generated list of a module's dependency versions. + +## Three ways modules talk to each other + +1. **Built-in / Module API** — a module's own public functions, declared in + its header with the `dmod__api(version, ret, _suffix, (args))` + macro and defined in the `.c` file with the matching + `dmod__api_declaration(version, ret, _suffix, (args))` macro (both + auto-generated per-module into `_defs.h` by the dmod core build + from `dmod/scripts/api.h.in` — you only follow the naming convention, never + hand-write the macro itself). **This is not optional boilerplate**: a + plain C function prototype/definition in a module's public header will + compile but **fail to link** (or simply never be found), because the + loader resolves these calls dynamically rather than through normal static + linkage. Use this as the default way to expose any function another module + (or this module's own `tests/`) should be able to call — see + `dm_sw_ring/include/dm_sw_ring.h` + `dm_sw_ring/src/dm_sw_ring.c` for a + fully worked example, and the `dmod__port_api(...)` port variant + described below. + - A `dmod__global_api(...)` / `_global_api_declaration(...)` + variant also exists, declaring the function in the *global* namespace + instead of the module's own — reserved for exceptional cases (e.g. + defining a `printf`-style utility meant to be called unqualified from + anywhere). Default to the plain `_api`/`_api_declaration` pair unless you + have a specific reason to go global. +2. **MAL (Module Abstraction Layer)** — 1:1 inversion of control. One module + defines an interface; another module implements it and registers via + `DMOD_MAL_IMPLS` in its `CMakeLists.txt`/`Makefile`. Lets you swap an + implementation (e.g. which UART driver backs a generic "serial" interface) + without touching the caller. +3. **DIF (Dmod Interface)** — 1:N. Multiple modules implement the same + interface (e.g. several SPI/UART drivers) and are discovered dynamically at + runtime via `Dmod_GetNextDifModule()`/`Dmod_GetDifFunction()`, registered + through `DMOD_DIF_IMPLS`. + +Pick Built-in API for a hard compile-time dependency, MAL for "one pluggable +backend", DIF for "many interchangeable backends discovered at runtime". + +## Companion skills for deeper topics + +This skill is the map, not the territory — load these alongside it as the +task calls for them, rather than assuming everything is covered here: + +- **`dmod-module-structure`** — the standard module repo layout + (`CMakeLists.txt`, `manifest.dmm`, `docs/`, `tests/`), scaffolding a new + module with `dmod/scripts/new-module.sh`, and when to extract a reusable + library out of an application module. Load when creating or restructuring + a module repo. +- **`dmod-boot`** — the firmware entry point (`dmod-boot`): boot sequence, + module packaging (`modules.dmp`), testing a locally-built `.dmf` on real + hardware, and the flash/connect/monitor CMake targets. Load when working + in `dmod-boot` or tracing how firmware boots into a usable system. +- **`dmod-hardware-ports`** — the driver+port split used by hardware + modules (`dmuart`, `dmfmc`, ...), `DMOD_CPU_FAMILY`, and the + `dmod__port_api` macro. Load when writing or modifying a module + that talks to real hardware/peripherals. +- **`dmod-coding-conventions`** — module C code style: heap over + fixed-size buffers, never statically link a dependency into a module, + keep functions short, and the `_create`/`_destroy` opaque-context pattern + with a magic-field guard. Load when writing or reviewing module + implementation code. + +For a specific module's exact API, config keys, or behavior, read that +repo's `docs/api-reference.md` and `include/.h` directly rather than +guessing from any of these skills — module APIs evolve independently of them. diff --git a/.claude/skills/dmod-hardware-ports/SKILL.md b/.claude/skills/dmod-hardware-ports/SKILL.md new file mode 100644 index 0000000..9d9c9ef --- /dev/null +++ b/.claude/skills/dmod-hardware-ports/SKILL.md @@ -0,0 +1,45 @@ +--- +name: dmod-hardware-ports +description: Explains the driver+port pattern used by DMOD hardware modules (dmuart, dmfmc, dmgpio, dmclk, and any new peripheral driver) — splitting a module into a hardware-independent core and an architecture-specific "_port" module selected via DMOD_CPU_FAMILY, the dmod__port_api macro, and sharing logic across MCU families via src/port/_common/. Load this when writing or modifying a module that talks to real hardware/peripherals. Companion to the dmod-ecosystem skill. +--- + +# Driver + port pattern (hardware modules) + +Modules that touch real hardware (`dmuart`, `dmfmc`, and any new peripheral +driver) split into **two independent DMOD modules in one repo**: + +- `` — the core module: public API, config parsing (via `dmini`), + hardware-independent logic. Links against `_port_if`. +- `_port` — a second module, built from `src/port/CMakeLists.txt`, + containing the architecture-specific implementation. One subdirectory per + MCU family under `src/port//` (e.g. `src/port/stm32f7/`), each with a + `config.cmake` that sets `DMOD_TOOLS_NAME` (e.g. + `"arch/armv7/cortex-m7"`) and a `port.c` implementing the lifecycle + (`dmod_init`/`dmod_deinit`) plus any `DMOD_IRQ_HANDLER(...)`. + +The active architecture is selected via **`DMOD_CPU_FAMILY`** (a cache +variable already defined in `dmod/dmod-cfg.cmake`, consumed by `dmf-get +--cpu-family` for package resolution) — set it directly in the module's +`CMakeLists.txt` before `FetchContent_MakeAvailable(dmod)`: + +```cmake +set(DMOD_CPU_FAMILY "stm32f7" CACHE STRING "Target CPU family") +include(${CMAKE_CURRENT_SOURCE_DIR}/src/port/${DMOD_CPU_FAMILY}/config.cmake) +``` + +Do **not** invent a module-specific variable like `_MCU_SERIES` for +this — `DMOD_CPU_FAMILY` already exists and is understood by the rest of the +toolchain (`dmf-get`, package naming). `dmfmc`'s `DMFMC_MCU_SERIES` predates +this and is legacy, not a pattern to copy in new modules. + +The port's public API is declared with the `dmod__port_api(version, +return_type, _suffix, (args))` macro — this macro is generated automatically +by the dmod core build from `dmod/scripts/api.h.in` into +`_port_defs.h`; you only need to follow the naming convention, not +hand-write the macro. See `dmfmc/include/dmfmc_port.h` for a full example, or +`dmuart/include/dmuart_port.h` for a simpler one. + +If a peripheral IP block is identical across MCU families, keep the shared +logic in `src/port/_common/` and make each `src/port//port.c` a +thin wrapper (lifecycle + IRQ only) — see `dmfmc/docs/port-implementation.md` +for the exact recipe used there. diff --git a/.claude/skills/dmod-module-structure/SKILL.md b/.claude/skills/dmod-module-structure/SKILL.md new file mode 100644 index 0000000..77446b2 --- /dev/null +++ b/.claude/skills/dmod-module-structure/SKILL.md @@ -0,0 +1,49 @@ +--- +name: dmod-module-structure +description: Explains the standard DMOD module repo layout (CMakeLists.txt/Makefile skeleton, manifest.dmm, docs/, tests/), how to scaffold a new module with dmod/scripts/new-module.sh, and when to extract a reusable library out of an application module. Load this when creating a new dmod-* module repo, restructuring an existing one, or deciding whether functionality should become its own library module. Companion to the dmod-ecosystem skill. +--- + +# DMOD Module Structure + +Every real module repo (`dmuart`, `dmtty`, `dmfmc`, `dm_sw_ring`, `dmell`, ...) +follows the same shape: + +``` +/ +├── CMakeLists.txt # FetchContent's dmod, then dmod_add_library/_executable +├── Makefile # Make-based equivalent build +├── manifest.dmm # $include .../versions.dmm + this module's download URL +├── .dmr # install/package resource mapping +├── include/.h # public API +├── src/.c # implementation + dmod_init()/dmod_deinit() +├── docs/ +│ ├── README.md # doc index +│ ├── api-reference.md # full API reference +│ └── (examples.md, configuration.md, port-implementation.md as needed) +├── tests/ +│ ├── CMakeLists.txt # dmod_add_test(...) +│ └── _test.c # #include "dmod_test.h", DMOD_TEST_STEP(...) macros +└── README.md # includes a "Project Structure" tree section +``` + +Every `CMakeLists.txt` in a standalone module repo follows the same skeleton — +`FetchContent_Declare(dmod GIT_REPOSITORY .../dmod.git GIT_TAG develop)` + +`FetchContent_MakeAvailable(dmod)` + `include(${DMOD_DIR}/paths.cmake)` + +`dmod_setup_external_module()` — rather than assuming a local checkout path. +Look at `dm_sw_ring/CMakeLists.txt` or `dmtty/CMakeLists.txt` for a clean example. + +New modules should be scaffolded with `dmod/scripts/new-module.sh` (see +`--help` for `--type library|application`, `--port`, `--dif`, `--mal`), which +generates this exact layout. + +## Extracting a library out of an application module + +Sometimes an `application`-type module accumulates functionality that other +modules need to call — not just as an implementation detail, but as an API +other modules should link against. In that case, split the reusable part +out into its own `library`-type DMOD module rather than exposing the +application module's internals directly, and give that library module a +`lib` prefix. E.g. if the application module is `systemd`, the extracted +library becomes `libsystemd`: `systemd` itself becomes a consumer of +`libsystemd` (via `dmod_link_modules`) alongside every other module that +needs that API, rather than a special case other modules reach into. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4a584ee --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +on: + push: + branches: [ main, master, develop ] + pull_request: + branches: [ main, master, develop ] + +jobs: + build-and-test: + name: Build and Test + runs-on: ubuntu-latest + permissions: + contents: read + container: + image: chocotechnologies/dmod:1.0.4 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Build dmvs_html + run: | + mkdir -p build + cd build + cmake .. -DDMOD_MODE=DMOD_MODULE + cmake --build . + + - name: Verify module files + run: | + echo "Checking for module files..." + ls -lh build/dmf/ + test -f build/dmf/dmvs_html.dmf + test -f build/dmf/dmvs_html_version.txt + test -f build/dmf/test_dmvs_html.dmf + echo "Module files present" + + - name: Run tests with dmod_loader + run: | + export DMOD_DMF_DIR=$(pwd)/build/dmf + dmf-get install -d ${DMOD_DMF_DIR}/test_dmvs_html-local.dmd -y + dmod_loader build/dmf/test_dmvs_html.dmf diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a15803b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,279 @@ +name: Release + +on: + release: + types: [created] + +jobs: + discover-architectures: + name: Discover Architectures + runs-on: ubuntu-latest + permissions: + contents: read + container: + image: chocotechnologies/dmod:1.0.4 + outputs: + architectures: ${{ steps.list-archs.outputs.architectures }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Fetch dmod to discover architectures + run: | + # A plain `git clone` here (rather than configuring dmvs_html's + # own CMakeLists.txt via FetchContent, as before) so this step only + # ever depends on dmod itself - not on dmvs_html's + # dmod_link_modules() fetches of other modules, which this step has + # no business needing just to list dmod's supported architectures, + # and which previously made this discovery job (and therefore the + # whole release) fail outright whenever any of those unrelated + # external fetches hiccuped. + git clone --depth 1 https://github.com/choco-technologies/dmod.git dmod_discovery + + - name: List available architectures + id: list-archs + run: | + # create JSON array + ARCHS=$(find dmod_discovery/configs/arch -name "tools-cfg.cmake" | \ + sed "s|dmod_discovery/configs/arch/||g" | \ + sed 's|/tools-cfg.cmake||g' | \ + jq -R -s -c 'split("\n") | map(select(length > 0))') + + echo "Found architectures: $ARCHS" + echo "architectures=$ARCHS" >> $GITHUB_OUTPUT + + build-release: + name: Build Release for ${{ matrix.arch_name }} + needs: discover-architectures + runs-on: ubuntu-latest + permissions: + contents: write + strategy: + matrix: + arch_name: ${{ fromJson(needs.discover-architectures.outputs.architectures) }} + + container: + image: chocotechnologies/dmod:1.0.4 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Extract version from tag + id: get_version + run: | + # Extract version from tag (e.g., v1.2 -> 1.2) + VERSION="${{ github.event.release.tag_name }}" + VERSION="${VERSION#v}" # Remove 'v' prefix if present + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Build dmvs_html for ${{ matrix.arch_name }} + run: | + set -e + ARCH_DIR_NAME=$(echo "${{ matrix.arch_name }}" | sed 's|/|-|') + echo "ARCH_DIR_NAME=$ARCH_DIR_NAME" >> $GITHUB_ENV + echo "ARTIFACT_NAME=release-$ARCH_DIR_NAME" >> $GITHUB_ENV + mkdir -p build_$ARCH_DIR_NAME + cd build_$ARCH_DIR_NAME + + # DMOD_DMR_PATH is already set in CMakeLists.txt, so this build also + # assembles build_$ARCH_DIR_NAME/packages// for every module + # defined in this repo's CMake config - nothing left to do here but + # discover whatever got packaged, add release notes, and zip it. + cmake .. -DDMOD_TOOLS_NAME=arch/${{ matrix.arch_name }} -DDMOD_MODULE_VERSION="${{ steps.get_version.outputs.version }}" + cmake --build . + + echo "Build completed for ${{ matrix.arch_name }}" + ls -la packages/ + + - name: Add release notes and create release archives + run: | + set -e + BUILD_DIR="build_$ARCH_DIR_NAME" + TAG="${{ github.event.release.tag_name }}" + PACKAGES_DIR="$BUILD_DIR/packages" + + if [ ! -d "$PACKAGES_DIR" ] || [ -z "$(ls -A "$PACKAGES_DIR")" ]; then + echo "Error: no packages found in $PACKAGES_DIR" + exit 1 + fi + + # A repo may define more than one module (e.g. a core + port split, + # or several independent modules) - package every subdirectory + # found under packages/, not just dmvs_html. + : > package-names.txt + for pkg_dir in "$PACKAGES_DIR"/*/; do + pkg=$(basename "$pkg_dir") + echo "$pkg" >> package-names.txt + + echo "${{ github.event.release.body }}" > "${pkg_dir}RELEASE_NOTES.txt" + (cd "$pkg_dir" && zip -r "${OLDPWD}/${pkg}-${TAG}-${ARCH_DIR_NAME}.zip" .) + done + + echo "Created archives:" + ls -lh *-"${TAG}"-"${ARCH_DIR_NAME}".zip + echo "Packages found:" + cat package-names.txt + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.ARTIFACT_NAME }} + path: | + *.zip + package-names.txt + retention-days: 1 + + generate-versions-manifest: + name: Generate versions.dmm + needs: [discover-architectures, build-release] + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history to get all tags + + - name: Download release artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Generate versions.dmm + run: | + set -e + echo "# List of available versions for dmvs_html modules" > versions.dmm + echo "# Generated automatically by CI" >> versions.dmm + echo "" >> versions.dmm + + # Get all version tags (starting with 'v') and extract version numbers + VERSIONS=$(git tag -l 'v*' | sed 's/^v//' | sort -V | tr '\n' ' ' | sed 's/ $//') + + # Remove vlatest from the list if present + VERSIONS=$(echo $VERSIONS | sed 's/\blatest\b//g' | xargs) + + if [ -z "$VERSIONS" ]; then + echo "Warning: No version tags found" + VERSIONS="${{ github.event.release.tag_name }}" + VERSIONS="${VERSIONS#v}" + fi + + echo "Found versions: $VERSIONS" + + # Union of package names discovered across every architecture build + # (the same set is expected everywhere, but union is safe even if + # some packages only exist for a subset of architectures). + PACKAGES=$(cat artifacts/release-*/package-names.txt | sort -u) + echo "Found packages: $PACKAGES" + + for pkg in $PACKAGES; do + echo "\$version-available $pkg $VERSIONS" >> versions.dmm + done + + echo "Generated versions.dmm:" + cat versions.dmm + + - name: Upload versions.dmm as artifact + uses: actions/upload-artifact@v4 + with: + name: versions-manifest + path: versions.dmm + retention-days: 1 + + upload-release-assets: + name: Upload Release Assets + needs: [build-release, generate-versions-manifest] + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Display artifact structure + run: | + echo "Downloaded artifacts:" + ls -lR artifacts/ + + - name: Upload release assets to versioned tag + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -e + shopt -s nullglob + zip_files=(artifacts/release-*/*.zip) + + if [ ${#zip_files[@]} -eq 0 ]; then + echo "Error: No artifacts found to upload" + exit 1 + fi + + for zip_file in "${zip_files[@]}"; do + echo "Uploading $zip_file to ${{ github.event.release.tag_name }}..." + gh release upload ${{ github.event.release.tag_name }} \ + "$zip_file" \ + --repo ${{ github.repository }} \ + --clobber + done + + if [ -f artifacts/versions-manifest/versions.dmm ]; then + echo "Uploading versions.dmm to ${{ github.event.release.tag_name }}..." + gh release upload ${{ github.event.release.tag_name }} \ + artifacts/versions-manifest/versions.dmm \ + --repo ${{ github.repository }} \ + --clobber + fi + + echo "Successfully uploaded ${#zip_files[@]} artifact(s) to ${{ github.event.release.tag_name }}" + + - name: Create or update latest release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -e + if gh release view vlatest --repo ${{ github.repository }} >/dev/null 2>&1; then + echo "Release vlatest exists, deleting it..." + gh release delete vlatest --repo ${{ github.repository }} --yes + fi + + echo "Creating vlatest release..." + gh release create vlatest \ + --repo ${{ github.repository }} \ + --title "Latest Release (based on ${{ github.event.release.tag_name }})" \ + --notes "This release always points to the latest stable version. Currently based on ${{ github.event.release.tag_name }}." + + - name: Upload release assets to latest tag + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -e + shopt -s nullglob + zip_files=(artifacts/release-*/*.zip) + + for zip_file in "${zip_files[@]}"; do + echo "Uploading $zip_file to vlatest..." + gh release upload vlatest \ + "$zip_file" \ + --repo ${{ github.repository }} \ + --clobber + done + + if [ -f artifacts/versions-manifest/versions.dmm ]; then + echo "Uploading versions.dmm to vlatest..." + gh release upload vlatest \ + artifacts/versions-manifest/versions.dmm \ + --repo ${{ github.repository }} \ + --clobber + fi + + echo "Successfully uploaded ${#zip_files[@]} artifact(s) to vlatest" diff --git a/.gitignore b/.gitignore index 1f99f9d..c2af1f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,15 @@ -CMakeLists.txt.user -CMakeCache.txt -CMakeFiles -CMakeScripts -Testing -Makefile -cmake_install.cmake -install_manifest.txt -compile_commands.json -CTestTestfile.cmake -_deps -CMakeUserPresets.json +# Build directories +build/ +*.dmf +*.dmfc -# CLion -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#cmake-build-* +# IDE +# .vscode/tasks.json and .vscode/settings.json are committed (see every real +# module repo) so the build tasks are shared with the team - don't ignore +# .vscode/ wholesale. +.idea/ + +# Temporary files +*.tmp +*.swp +*~ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9fb722c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json", + "C_Cpp.intelliSenseEngine": "default", + "C_Cpp.errorSquiggles": "enabled" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..2a66b75 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,55 @@ +{ + // See https://code.visualstudio.com/docs/editor/tasks for more information + "version": "2.0.0", + "tasks": [ + { + "label": "CMake: Configure", + "type": "shell", + "command": "cmake", + "args": [ + "-S", + "${workspaceFolder}", + "-B", + "${workspaceFolder}/build", + "-DDMOD_DIR=${input:dmodDir}" + ], + "group": "build", + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "CMake: Build", + "type": "shell", + "command": "cmake", + "args": [ + "--build", + "${workspaceFolder}/build" + ], + "group": "build", + "problemMatcher": [] + }, + { + "label": "CMake: Clean", + "type": "shell", + "command": "cmake", + "args": [ + "--build", + "${workspaceFolder}/build", + "--target", + "clean" + ], + "group": "build", + "problemMatcher": [] + } + ], + "inputs": [ + { + "id": "dmodDir", + "description": "Path to a local dmod checkout (leave blank to fetch dmod's develop branch from GitHub instead)", + "default": "${workspaceFolder}/../dmod", + "type": "promptString" + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9b98a58 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 3.18) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +if(NOT DEFINED DMOD_MODULE_VERSION) + set(DMOD_MODULE_VERSION "0.1" CACHE STRING "DMOD module version") +endif() + +# If a local dmod checkout is provided, use it instead of fetching from git +if(DEFINED DMOD_DIR AND NOT "${DMOD_DIR}" STREQUAL "") + set(FETCHCONTENT_SOURCE_DIR_DMOD "${DMOD_DIR}" CACHE PATH "" FORCE) +endif() + +include(FetchContent) +FetchContent_Declare( + dmod + GIT_REPOSITORY https://github.com/choco-technologies/dmod.git + GIT_TAG develop +) + +set(DMOD_MODE "DMOD_MODULE" CACHE STRING "DMOD build mode") +set(DMOD_BUILD_TESTS OFF CACHE BOOL "Build tests") +set(DMOD_BUILD_EXAMPLES OFF CACHE BOOL "Build examples") +set(DMOD_BUILD_TOOLS OFF CACHE BOOL "Build tools") +set(DMOD_BUILD_TEMPLATES OFF CACHE BOOL "Build templates") + +# project() before FetchContent_MakeAvailable(dmod): the compiler test runs +# before dmod's tools-cfg.cmake adds its include directories (ARG_MAX on CI) +project(dmvs_html VERSION ${DMOD_MODULE_VERSION} + DESCRIPTION "HTML and CSS converter for dmvsi" + LANGUAGES C CXX) + +FetchContent_MakeAvailable(dmod) + +set(DMOD_DIR ${dmod_SOURCE_DIR} CACHE PATH "DMOD source directory") +set(DMOD_SCRIPTS_DIR ${DMOD_DIR}/scripts CACHE PATH "DMOD scripts directory") +include(${DMOD_DIR}/paths.cmake) +dmod_setup_external_module() + +# Name of the module +set(DMOD_MODULE_NAME dmvs_html) + +# Author (should be string) +set(DMOD_AUTHOR_NAME "Patryk Kubiak") + +# Stack size for the module (should be integer) +set(DMOD_STACK_SIZE 16384) + +# Path to the .dmr file - enables release package creation in build/packages/ +set(DMOD_DMR_PATH ${CMAKE_CURRENT_SOURCE_DIR}/dmvs_html.dmr) + +# List of MAL interfaces implemented by this module (leave empty if none) +set(DMOD_MAL_IMPLS ) + +# List of DIF interfaces implemented by this module +set(DMOD_DIF_IMPLS dmvsi) + +# +# dmod_add_library - create a library module +# it has the same signature as add_library +# and can be used in the same way after the creation +# (for example, to link libraries) +# +dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} + src/dmvs_html.c + src/html.c + src/css.c + src/tailwind.c + src/style.c + src/layout.c + src/paint.c +) + +dmod_link_modules(${DMOD_MODULE_NAME} + dmvsi@>=0.1 +) + +target_include_directories(${DMOD_MODULE_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +enable_testing() +add_subdirectory(tests) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4b35448 --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +# ############################################################################# +# +# dmvs_html - the HTML and CSS converter of dmvsi (a library module). +# +# ############################################################################# +DMOD_DIR=@DMOD_DIR@ + +# ----------------------------------------------------------------------------- +# Paths initialization +# ----------------------------------------------------------------------------- +include $(DMOD_DIR)/paths.mk + +# ----------------------------------------------------------------------------- +# Module configuration +# ----------------------------------------------------------------------------- + +# The name of the module +DMOD_MODULE_NAME=dmvs_html + +# The version of the module +DMOD_MODULE_VERSION=0.1 + +# The name of the author +DMOD_AUTHOR_NAME=Patryk Kubiak + +# The list of C sources +DMOD_CSOURCES=src/dmvs_html.c src/html.c src/css.c src/tailwind.c src/style.c src/layout.c src/paint.c + +# The list of C++ sources +DMOD_CXXSOURCES= + +# The list of include directories +DMOD_INC_DIRS=src + +# The list of libraries to link +DMOD_LIBS= + +# The list of definitions +DMOD_DEFINITIONS= + +# ----------------------------------------------------------------------------- +# List of MAL interfaces implemented by the module +# ----------------------------------------------------------------------------- +DMOD_MAL_IMPLS= + +# ----------------------------------------------------------------------------- +# Include the dmod app makefile +# ----------------------------------------------------------------------------- +include $(DMOD_DMF_LIB_FILE_PATH) diff --git a/dmvs_html.dmr b/dmvs_html.dmr new file mode 100644 index 0000000..57ef339 --- /dev/null +++ b/dmvs_html.dmr @@ -0,0 +1,35 @@ +# DMOD Resource File for dmvs_html +# This file specifies where resources should be installed + +# === Core Module === +# Main module file - always installed +dmf=./${module}.dmf => ${destination}/${module}.dmf [origin=${dmf_dir}/${module}.dmf] + +# Compressed module +dmfc=./${module}.dmfc => ${destination}/${module}.dmfc [origin=${build_dir}/dmfc/${module}.dmfc] + +# Dependencies file (if exists) +dmd=./${module}.dmd => ${destination}/${module}.dmd [origin=${dmf_dir}/${module}.dmd] + +# Version information +version=./${module}_version.txt => ${destination}/${module}_version.txt [origin=${dmf_dir}/${module}_version.txt] + +# === Documentation === +# Module documentation in markdown format for dmf-man tool +docs=./docs => ${destination}/${module}/docs [origin=${repo_dir}/docs] + +# README file +readme=./README.md => ${destination}/${module}/README.md [origin=${repo_dir}/README.md] + +# === Header Files === +# Include directory with all headers (public header + generated _defs.h) +inc=./include => ${destination}/${module}/include [origin=${repo_dir}/include] [origin=${build_dir}/${module}_defs.h] + +# === License === +# License file +license=./LICENSE => ${destination}/${module}/LICENSE [origin=${repo_dir}/LICENSE] + +# pngle (MIT), which the decoder is built on + +# Adding itself +dmr=./${module}.dmr => ${destination}/${module}/${module}.dmr [origin=${repo_dir}/${module}.dmr] diff --git a/include/.gitkeep b/include/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/manifest.dmm b/manifest.dmm new file mode 100644 index 0000000..8dbe070 --- /dev/null +++ b/manifest.dmm @@ -0,0 +1,5 @@ +# Include dynamically generated versions list from latest release +$include https://github.com/choco-technologies/dmvs_html/releases/download/vlatest/versions.dmm + +# Module entry with version placeholder - expanded by $version-available +dmvs_html https://github.com/choco-technologies/dmvs_html/releases/download/v/dmvs_html-v-.zip diff --git a/scripts/sync-claude.sh b/scripts/sync-claude.sh new file mode 100755 index 0000000..8ce1006 --- /dev/null +++ b/scripts/sync-claude.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +# ############################################################################## +# +# Syncs Claude Code skills (.claude/skills/) from the dmod repository into the +# current module repository. Run this explicitly whenever you want the latest +# ecosystem knowledge available to Claude Code - it is not run automatically +# by CMake or by any build step, on purpose: the result is meant to be +# reviewed and committed like any other file, not regenerated silently. +# +# Only .claude/skills/ is touched. Local .claude/settings.json, hooks, or +# other skills you added under a different name are left alone. +# +# Usage: +# ./scripts/sync-claude.sh [--dmod-dir PATH] [--ref REF] +# +# Options: +# --dmod-dir PATH Use a local dmod checkout instead of cloning from GitHub +# --ref REF Git branch/tag to clone when no --dmod-dir is given +# (default: develop) +# --help Show this help message +# +# ############################################################################## + +set -e + +DMOD_DIR="" +GIT_REF="develop" +DMOD_REPO_URL="https://github.com/choco-technologies/dmod.git" + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +print_error() { + echo -e "${RED}Error: $1${NC}" >&2 +} + +print_success() { + echo -e "${GREEN}$1${NC}" +} + +print_help() { + cat << EOF +Sync Claude Code skills from dmod + +Usage: + ./scripts/sync-claude.sh [--dmod-dir PATH] [--ref REF] + +Options: + --dmod-dir PATH Use a local dmod checkout instead of cloning from GitHub + --ref REF Git branch/tag to clone when no --dmod-dir is given (default: develop) + --help Show this help message +EOF +} + +while [[ $# -gt 0 ]]; do + case $1 in + --dmod-dir) + DMOD_DIR="$2" + shift 2 + ;; + --ref) + GIT_REF="$2" + shift 2 + ;; + --help) + print_help + exit 0 + ;; + *) + print_error "Unknown parameter: $1" + print_help + exit 1 + ;; + esac +done + +TMP_CLONE_DIR="" + +cleanup() { + if [[ -n "${TMP_CLONE_DIR}" && -d "${TMP_CLONE_DIR}" ]]; then + rm -rf "${TMP_CLONE_DIR}" + fi +} +trap cleanup EXIT + +if [[ -n "${DMOD_DIR}" ]]; then + if [[ ! -d "${DMOD_DIR}" ]]; then + print_error "--dmod-dir does not exist: ${DMOD_DIR}" + exit 1 + fi + SOURCE_SKILLS_DIR="${DMOD_DIR}/.claude/skills" + echo "Using local dmod checkout: ${DMOD_DIR}" +else + TMP_CLONE_DIR="$(mktemp -d)" + echo "Cloning dmod (branch: ${GIT_REF}) ..." + git clone --depth 1 --branch "${GIT_REF}" "${DMOD_REPO_URL}" "${TMP_CLONE_DIR}" > /dev/null 2>&1 + SOURCE_SKILLS_DIR="${TMP_CLONE_DIR}/.claude/skills" +fi + +if [[ ! -d "${SOURCE_SKILLS_DIR}" ]]; then + print_error "No .claude/skills directory found at ${SOURCE_SKILLS_DIR}" + exit 1 +fi + +mkdir -p .claude/skills +cp -r "${SOURCE_SKILLS_DIR}/." .claude/skills/ + +echo "" +print_success "Synced Claude Code skills:" +for skill_dir in .claude/skills/*/; do + [[ -d "${skill_dir}" ]] || continue + echo " - $(basename "${skill_dir}")" +done +echo "" +echo "Review the changes with 'git status'/'git diff' and commit them like any other file." diff --git a/src/css.c b/src/css.c new file mode 100644 index 0000000..f506056 --- /dev/null +++ b/src/css.c @@ -0,0 +1,1068 @@ +#include "private.h" +#include + +/* + * CSS: style sheets into rules. Selectors - types, ids, classes, + * attributes, the combinators, :first-child and friends, :not(), :is(), + * :where(), ::before / ::after; a pseudo-class of interaction (:hover, + * :active, :focus, ...) never matches a still page. At-rules: @media + * (evaluated against the viewport), @supports and @layer (their rules + * taken), @import, @font-face; the others are skipped. + * + * Rules are kept by their last compound's id, first class or type, so an + * element is matched only against the rules that can match it - a style + * sheet like Font Awesome's has thousands of them. + */ + +#define MAX_PARTS 16u +#define MAX_SELECTORS 64u +#define MAX_DECLS 128u +#define MAX_IMPORT_DEPTH 4u + +static bool is_space(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'; } +static char lower(char c) { return (c >= 'A' && c <= 'Z') ? (char)(c - 'A' + 'a') : c; } +static bool is_name_char(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || + (uint8_t)c >= 0x80u || c == '\\'; +} + +static bool ieq(const char* a, size_t n, const char* b) +{ + if (strlen(b) != n) + return false; + for (size_t i = 0; i < n; i++) + { + if (lower(a[i]) != b[i]) + return false; + } + return true; +} + +/* Past whitespace and comments */ +static const char* skip(const char* s, const char* end) +{ + for (;;) + { + while (s < end && is_space(*s)) + s++; + if (s + 1 < end && s[0] == '/' && s[1] == '*') + { + s += 2; + while (s + 1 < end && !(s[0] == '*' && s[1] == '/')) + s++; + s = (s + 1 < end) ? s + 2 : end; + continue; + } + if (end - s >= 4 && strncmp(s, "", 3) == 0) + { + s += 3; + continue; + } + return s; + } +} + +/* Past a string starting at s (its quote) */ +static const char* skip_string(const char* s, const char* end) +{ + char q = *s++; + while (s < end && *s != q) + { + if (*s == '\\' && s + 1 < end) + s++; + s++; + } + return (s < end) ? s + 1 : end; +} + +/* The first of `stops` at the top level (not in a string, a comment, brackets), or end */ +static const char* find_top(const char* s, const char* end, const char* stops) +{ + int depth = 0; + while (s < end) + { + char ch = *s; + if (ch == '"' || ch == '\'') + { + s = skip_string(s, end); + continue; + } + if (ch == '/' && s + 1 < end && s[1] == '*') + { + s = skip(s, end); + continue; + } + if (ch == '\\' && s + 1 < end) + { + s += 2; + continue; + } + if (depth == 0 && strchr(stops, ch) != NULL) + return s; + if (ch == '(' || ch == '[' || ch == '{') + depth++; + else if ((ch == ')' || ch == ']' || ch == '}') && depth > 0) + depth--; + s++; + } + return end; +} + +uint32_t css_unescape(const char** p, const char* end) +{ + const char* s = *p; + uint32_t c = 0; + s++; /* '\\' */ + uint32_t digits = 0; + while (s < end && digits < 6U) + { + char d = *s; + uint32_t v = (d >= '0' && d <= '9') ? (uint32_t)(d - '0') : (d >= 'a' && d <= 'f') ? (uint32_t)(d - 'a' + 10) + : (d >= 'A' && d <= 'F') ? (uint32_t)(d - 'A' + 10) : 99U; + if (v == 99U) + break; + c = c * 16U + v; + s++; + digits++; + } + if (digits > 0) + { + if (s < end && is_space(*s)) + s++; + *p = s; + return (c == 0 || c > 0x10FFFFu) ? 0xFFFDu : c; + } + if (s < end) + { + const char* q = s; + c = dmvsi_utf8_next(&q, end); + *p = q; + return c; + } + *p = s; + return 0xFFFDu; +} + +static size_t put_utf8(char* out, uint32_t c) +{ + if (c < 0x80u) + { + out[0] = (char)c; + return 1; + } + if (c < 0x800u) + { + out[0] = (char)(0xC0u | (c >> 6)); + out[1] = (char)(0x80u | (c & 0x3Fu)); + return 2; + } + if (c < 0x10000u) + { + out[0] = (char)(0xE0u | (c >> 12)); + out[1] = (char)(0x80u | ((c >> 6) & 0x3Fu)); + out[2] = (char)(0x80u | (c & 0x3Fu)); + return 3; + } + out[0] = (char)(0xF0u | (c >> 18)); + out[1] = (char)(0x80u | ((c >> 12) & 0x3Fu)); + out[2] = (char)(0x80u | ((c >> 6) & 0x3Fu)); + out[3] = (char)(0x80u | (c & 0x3Fu)); + return 4; +} + +/* An identifier at *p (escapes decoded), or NULL */ +static char* identifier(conv_t* c, const char** p, const char* end, bool lowercase) +{ + const char* s = *p; + char buffer[256]; + size_t n = 0; + while (s < end && is_name_char(*s) && n + 4U < sizeof(buffer)) + { + if (*s == '\\') + { + n += put_utf8(buffer + n, css_unescape(&s, end)); + continue; + } + buffer[n++] = lowercase ? lower(*s) : *s; + s++; + } + *p = s; + return (n == 0) ? NULL : arena_strndup(&c->arena, buffer, n); +} + +/* ---- Selectors ---- */ + +static bool parse_compound(conv_t* c, const char** p, const char* end, compound_t* out, uint32_t* spec, uint8_t* pseudo_element); + +/* The selectors in :not(), :is(), :where() - compounds only */ +static uint8_t parse_compound_list(conv_t* c, const char* s, const char* end, compound_t** out, uint32_t* spec) +{ + compound_t list[8]; + uint8_t n = 0; + uint32_t best = 0; + while (s < end && n < 8U) + { + s = skip(s, end); + uint32_t sp = 0; + uint8_t pe = PSEUDO_NONE; + memset(&list[n], 0, sizeof(list[n])); + if (!parse_compound(c, &s, end, &list[n], &sp, &pe)) + return 0; + best = (sp > best) ? sp : best; + n++; + s = skip(s, end); + if (s < end && *s == ',') + s++; + else if (s < end) + return 0; /* A complex selector: not supported here */ + } + *out = arena_alloc(&c->arena, n * sizeof(compound_t)); + if (*out == NULL) + return 0; + memcpy(*out, list, n * sizeof(compound_t)); + *spec = best; + return n; +} + +static bool parse_attribute(conv_t* c, const char** p, const char* end, attr_sel_t* a) +{ + const char* s = skip(*p + 1, end); /* '[' */ + memset(a, 0, sizeof(*a)); + if ((a->name = identifier(c, &s, end, true)) == NULL) + return false; + s = skip(s, end); + a->op = ATTR_EXISTS; + if (s < end && *s != ']') + { + static const char ops[] = "=~|^$*"; + const char* o = strchr(ops, *s); + if (o == NULL || *o == '\0') + return false; + a->op = (*s == '=') ? ATTR_EQUALS : (*s == '~') ? ATTR_INCLUDES : (*s == '|') ? ATTR_DASH + : (*s == '^') ? ATTR_PREFIX : (*s == '$') ? ATTR_SUFFIX : ATTR_CONTAINS; + s += (*s == '=') ? 1 : 2; + s = skip(s, end); + if (s < end && (*s == '"' || *s == '\'')) + { + const char* q = skip_string(s, end); + a->value = arena_strndup(&c->arena, s + 1, (size_t)(q - s - 2)); + s = q; + } + else + a->value = identifier(c, &s, end, false); + s = skip(s, end); + if (s < end && (*s == 'i' || *s == 'I')) + { + a->icase = true; + s = skip(s + 1, end); + } + } + if (s >= end || *s != ']') + return false; + *p = s + 1; + return true; +} + +static bool parse_pseudo(conv_t* c, const char** p, const char* end, compound_t* out, uint32_t* spec, uint8_t* pseudo_element) +{ + const char* s = *p + 1; + bool element = false; + if (s < end && *s == ':') + { + element = true; + s++; + } + const char* name = s; + while (s < end && is_name_char(*s)) + s++; + size_t n = (size_t)(s - name); + const char* args = NULL; + const char* args_end = NULL; + if (s < end && *s == '(') + { + args = s + 1; + args_end = find_top(args, end, ")"); + s = (args_end < end) ? args_end + 1 : end; + } + *p = s; + + if (ieq(name, n, "before") || ieq(name, n, "after")) + { + *pseudo_element = ieq(name, n, "before") ? PSEUDO_BEFORE : PSEUDO_AFTER; + *spec += 1U; + return true; + } + if (element) + return false; /* ::placeholder, ::selection, ...: never drawn */ + *spec += 1U << 8; + if (ieq(name, n, "first-child")) + out->pseudo |= PC_FIRST_CHILD; + else if (ieq(name, n, "last-child")) + out->pseudo |= PC_LAST_CHILD; + else if (ieq(name, n, "only-child")) + out->pseudo |= PC_ONLY_CHILD; + else if (ieq(name, n, "root")) + out->pseudo |= PC_ROOT; + else if (ieq(name, n, "empty")) + out->pseudo |= PC_EMPTY; + else if (args != NULL && (ieq(name, n, "not") || ieq(name, n, "is") || ieq(name, n, "where") || ieq(name, n, "matches"))) + { + uint32_t inner = 0; + compound_t* list = NULL; + uint8_t count = parse_compound_list(c, args, args_end, &list, &inner); + if (count == 0) + return false; + *spec -= 1U << 8; + if (ieq(name, n, "not")) + { + out->nots = list; + out->not_count = count; + } + else + { + out->ises = list; + out->is_count = count; + } + if (!ieq(name, n, "where")) + *spec += inner; + } + else + out->pseudo |= PC_NEVER; /* :hover, :focus, :host, ... */ + return true; +} + +static bool parse_compound(conv_t* c, const char** p, const char* end, compound_t* out, uint32_t* spec, uint8_t* pseudo_element) +{ + const char* s = *p; + char* classes[16]; + attr_sel_t attrs[8]; + uint8_t nc = 0, na = 0; + bool any = false; + + if (s < end && *s == '*') + { + s++; + any = true; + } + else if (s < end && is_name_char(*s)) + { + out->tag = identifier(c, &s, end, true); + *spec += 1U; + any = true; + } + while (s < end) + { + if (*s == '#') + { + s++; + out->id = identifier(c, &s, end, false); + if (out->id == NULL) + return false; + *spec += 1U << 16; + } + else if (*s == '.') + { + s++; + char* name = identifier(c, &s, end, false); + if (name == NULL || nc >= 16U) + return false; + classes[nc++] = name; + *spec += 1U << 8; + } + else if (*s == '[') + { + if (na >= 8U || !parse_attribute(c, &s, end, &attrs[na])) + return false; + na++; + *spec += 1U << 8; + } + else if (*s == ':') + { + if (!parse_pseudo(c, &s, end, out, spec, pseudo_element)) + return false; + } + else + break; + any = true; + } + if (!any) + return false; + if (nc > 0) + { + out->classes = arena_alloc(&c->arena, nc * sizeof(char*)); + if (out->classes == NULL) + return false; + memcpy(out->classes, classes, nc * sizeof(char*)); + out->class_count = nc; + } + if (na > 0) + { + out->attrs = arena_alloc(&c->arena, na * sizeof(attr_sel_t)); + if (out->attrs == NULL) + return false; + memcpy(out->attrs, attrs, na * sizeof(attr_sel_t)); + out->attr_count = na; + } + *p = s; + return true; +} + +/* One selector of a list into `rule` (its parts, specificity, pseudo-element) */ +static bool parse_selector(conv_t* c, const char* s, const char* end, rule_t* rule) +{ + compound_t parts[MAX_PARTS]; + uint8_t n = 0; + uint8_t comb = COMB_NONE; + uint32_t spec = 0; + uint8_t pseudo_element = PSEUDO_NONE; + + s = skip(s, end); + while (s < end) + { + if (n >= MAX_PARTS || pseudo_element != PSEUDO_NONE) + return false; /* Nothing may follow a pseudo-element */ + memset(&parts[n], 0, sizeof(parts[n])); + parts[n].comb = comb; + if (!parse_compound(c, &s, end, &parts[n], &spec, &pseudo_element)) + return false; + n++; + const char* before = s; + s = skip(s, end); + if (s >= end) + break; + if (*s == '>' || *s == '+' || *s == '~') + { + comb = (*s == '>') ? COMB_CHILD : (*s == '+') ? COMB_NEXT : COMB_SIBLING; + s = skip(s + 1, end); + } + else if (s > before) + comb = COMB_DESCENDANT; + else + return false; + } + if (n == 0) + return false; + rule->parts = arena_alloc(&c->arena, n * sizeof(compound_t)); + if (rule->parts == NULL) + return false; + memcpy(rule->parts, parts, n * sizeof(compound_t)); + rule->count = n; + rule->specificity = spec; + rule->pseudo_element = pseudo_element; + return true; +} + +/* ---- Matching ---- */ + +static bool attr_matches(const attr_sel_t* a, const node_t* n) +{ + const char* v = node_attr(n, a->name); + if (v == NULL) + return false; + if (a->op == ATTR_EXISTS) + return true; + const char* want = (a->value != NULL) ? a->value : ""; + size_t vl = strlen(v), wl = strlen(want); + switch (a->op) + { + case ATTR_EQUALS: + if (a->icase) + { + if (vl != wl) + return false; + for (size_t i = 0; i < vl; i++) + { + if (lower(v[i]) != lower(want[i])) + return false; + } + return true; + } + return strcmp(v, want) == 0; + case ATTR_PREFIX: + return wl > 0 && strncmp(v, want, wl) == 0; + case ATTR_SUFFIX: + return wl > 0 && vl >= wl && strcmp(v + vl - wl, want) == 0; + case ATTR_DASH: + return strcmp(v, want) == 0 || (strncmp(v, want, wl) == 0 && v[wl] == '-'); + case ATTR_CONTAINS: + case ATTR_INCLUDES: + for (size_t i = 0; wl > 0 && i + wl <= vl; i++) + { + if (strncmp(v + i, want, wl) != 0) + continue; + if (a->op == ATTR_CONTAINS) + return true; + if ((i == 0 || is_space(v[i - 1])) && (v[i + wl] == '\0' || is_space(v[i + wl]))) + return true; + } + return false; + default: + return false; + } +} + +static const node_t* prev_element(const node_t* n) +{ + for (n = n->prev; n != NULL; n = n->prev) + { + if (n->kind == NODE_ELEMENT && n->pseudo == PSEUDO_NONE) + return n; + } + return NULL; +} + +static const node_t* next_element(const node_t* n) +{ + for (n = n->next; n != NULL; n = n->next) + { + if (n->kind == NODE_ELEMENT && n->pseudo == PSEUDO_NONE) + return n; + } + return NULL; +} + +static bool compound_matches(const compound_t* k, const node_t* n) +{ + if (n == NULL || n->kind != NODE_ELEMENT || n->pseudo != PSEUDO_NONE) + return false; + if ((k->pseudo & PC_NEVER) != 0) + return false; + if (k->tag != NULL && strcmp(k->tag, n->tag) != 0) + return false; + if (k->id != NULL && (n->id == NULL || strcmp(k->id, n->id) != 0)) + return false; + for (uint8_t i = 0; i < k->class_count; i++) + { + bool found = false; + for (uint32_t j = 0; j < n->class_count && !found; j++) + found = strcmp(n->classes[j], k->classes[i]) == 0; + if (!found) + return false; + } + for (uint8_t i = 0; i < k->attr_count; i++) + { + if (!attr_matches(&k->attrs[i], n)) + return false; + } + if ((k->pseudo & (PC_FIRST_CHILD | PC_ONLY_CHILD)) != 0 && prev_element(n) != NULL) + return false; + if ((k->pseudo & (PC_LAST_CHILD | PC_ONLY_CHILD)) != 0 && next_element(n) != NULL) + return false; + if ((k->pseudo & PC_ROOT) != 0 && (n->parent == NULL || n->parent->kind != NODE_DOCUMENT)) + return false; + if ((k->pseudo & PC_EMPTY) != 0 && n->first != NULL) + return false; + for (uint8_t i = 0; i < k->not_count; i++) + { + if (compound_matches(&k->nots[i], n)) + return false; + } + if (k->is_count > 0) + { + bool any = false; + for (uint8_t i = 0; i < k->is_count && !any; i++) + any = compound_matches(&k->ises[i], n); + if (!any) + return false; + } + return true; +} + +/* Parts 0 ... i, with part i on n */ +static bool matches_from(const rule_t* r, int i, const node_t* n) +{ + if (!compound_matches(&r->parts[i], n)) + return false; + if (i == 0) + return true; + switch (r->parts[i].comb) + { + case COMB_CHILD: + return matches_from(r, i - 1, n->parent); + case COMB_DESCENDANT: + for (const node_t* a = n->parent; a != NULL && a->kind == NODE_ELEMENT; a = a->parent) + { + if (matches_from(r, i - 1, a)) + return true; + } + return false; + case COMB_NEXT: + return matches_from(r, i - 1, prev_element(n)); + case COMB_SIBLING: + for (const node_t* s = prev_element(n); s != NULL; s = prev_element(s)) + { + if (matches_from(r, i - 1, s)) + return true; + } + return false; + default: + return false; + } +} + +bool css_matches(const rule_t* rule, const node_t* n) +{ + return rule->count > 0 && matches_from(rule, (int)rule->count - 1, n); +} + +/* ---- Rules ---- */ + +uint16_t css_parse_decls(conv_t* c, const char* text, size_t length, decl_t** decls); + +static uint32_t hash(const char* prefix, const char* s) +{ + uint32_t h = 2166136261u; + for (; *prefix != '\0'; prefix++) + h = (h ^ (uint8_t)*prefix) * 16777619u; + for (; *s != '\0'; s++) + h = (h ^ (uint8_t)*s) * 16777619u; + return h % RULE_BUCKETS; +} + +void css_add_rule(conv_t* c, rule_t* rule) +{ + rule_ref_t* ref = arena_alloc(&c->arena, sizeof(*ref)); + if (ref == NULL) + return; + ref->rule = rule; + const compound_t* last = &rule->parts[rule->count - 1U]; + rule_ref_t** list = &c->universal; + if (last->id != NULL) + list = &c->buckets[hash("#", last->id)]; + else if (last->class_count > 0) + list = &c->buckets[hash(".", last->classes[0])]; + else if (last->tag != NULL) + list = &c->buckets[hash("", last->tag)]; + ref->next = *list; + *list = ref; + c->rule_count++; +} + +const rule_ref_t* css_bucket(const conv_t* c, const char* prefix, const char* name) +{ + return c->buckets[hash(prefix, name)]; +} + +/* "name: value; ..." -> declarations (into the arena); returns how many */ +static uint16_t parse_decls(conv_t* c, const char* s, const char* end, decl_t** out) +{ + decl_t decls[MAX_DECLS]; + uint16_t n = 0; + while (s < end && n < MAX_DECLS) + { + s = skip(s, end); + const char* stop = find_top(s, end, ";"); + const char* colon = find_top(s, stop, ":"); + if (colon < stop) + { + const char* name_end = colon; + while (name_end > s && is_space(name_end[-1])) + name_end--; + const char* v = skip(colon + 1, stop); + const char* v_end = stop; + while (v_end > v && is_space(v_end[-1])) + v_end--; + bool important = false; + /* "... !important" */ + const char* bang = v_end; + while (bang > v && bang[-1] != '!') + bang--; + if (bang > v) + { + const char* w = skip(bang, v_end); + if (ieq(w, (size_t)(v_end - w), "important")) + { + important = true; + v_end = bang - 1; + while (v_end > v && is_space(v_end[-1])) + v_end--; + } + } + bool custom = name_end - s > 2 && s[0] == '-' && s[1] == '-'; + char* name = arena_strndup(&c->arena, s, (size_t)(name_end - s)); + if (name != NULL && !custom) + { + for (char* t = name; *t != '\0'; t++) + *t = lower(*t); + } + decls[n].name = name; + decls[n].value = arena_strndup(&c->arena, v, (size_t)(v_end - v)); + decls[n].important = important; + if (name != NULL && decls[n].value != NULL && name[0] != '\0') + n++; + } + s = (stop < end) ? stop + 1 : end; + } + *out = (n > 0) ? arena_alloc(&c->arena, n * sizeof(decl_t)) : NULL; + if (*out != NULL) + memcpy(*out, decls, n * sizeof(decl_t)); + return (*out != NULL) ? n : 0; +} + +/* ---- @font-face ---- */ + +static uint16_t parse_weight(const char* v, const char** next) +{ + const char* s = v; + while (is_space(*s)) + s++; + uint32_t w = 0; + if (strncmp(s, "bold", 4) == 0) + { + w = 700; + s += 4; + } + else if (strncmp(s, "normal", 6) == 0) + { + w = 400; + s += 6; + } + else + { + for (; *s >= '0' && *s <= '9'; s++) + w = w * 10U + (uint32_t)(*s - '0'); + } + if (next != NULL) + *next = s; + return (w >= 1U && w <= 1000U) ? (uint16_t)w : 400U; +} + +static bool font_file(const char* url, size_t n) +{ + /* The extension, before a query or a fragment */ + for (size_t k = 0; k + 4U <= n; k++) + { + bool end = k + 4U == n || url[k + 4] == '?' || url[k + 4] == '#'; + if (end && (ieq(url + k, 4, ".ttf") || ieq(url + k, 4, ".otf") || ieq(url + k, 4, ".ttc"))) + return true; + } + return false; +} + +/* The first url() of src that is a TrueType / OpenType font */ +static char* pick_src(conv_t* c, const char* v, const char* base) +{ + const char* end = v + strlen(v); + const char* s = v; + while (s < end) + { + const char* item_end = find_top(s, end, ","); + const char* u = s; + const char* url = NULL; + size_t url_length = 0; + bool truetype = false; + while (u < item_end) + { + u = skip(u, item_end); + if (item_end - u > 4 && ieq(u, 4, "url(")) + { + const char* a = skip(u + 4, item_end); + const char* close = find_top(a, item_end, ")"); + const char* b = close; + while (b > a && is_space(b[-1])) + b--; + if (b > a && (*a == '"' || *a == '\'')) + { + a++; + b--; + } + url = a; + url_length = (size_t)(b - a); + u = (close < item_end) ? close + 1 : item_end; + } + else if (item_end - u > 7 && ieq(u, 7, "format(")) + { + const char* close = find_top(u, item_end, ")"); + for (const char* f = u; f + 8 <= close; f++) + { + if (ieq(f, 8, "truetype") || ieq(f, 8, "opentype")) + truetype = true; + } + u = (close < item_end) ? close + 1 : item_end; + } + else + u++; + } + if (url != NULL && (truetype || font_file(url, url_length))) + return resolve_resource(c, base, url, url_length); + s = (item_end < end) ? item_end + 1 : end; + } + return NULL; +} + +static void font_face(conv_t* c, const char* s, const char* end, const char* base) +{ + decl_t* decls = NULL; + uint16_t n = parse_decls(c, s, end, &decls); + font_face_t* f = arena_alloc(&c->arena, sizeof(*f)); + if (f == NULL) + return; + f->weight_min = 400; + f->weight_max = 400; + for (uint16_t i = 0; i < n; i++) + { + const char* v = decls[i].value; + if (strcmp(decls[i].name, "font-family") == 0) + { + size_t length = strlen(v); + if (length >= 2U && (v[0] == '"' || v[0] == '\'')) + { + v++; + length -= 2U; + } + f->family = arena_strndup(&c->arena, v, length); + for (char* t = f->family; t != NULL && *t != '\0'; t++) + *t = lower(*t); + } + else if (strcmp(decls[i].name, "font-weight") == 0) + { + const char* rest = NULL; + f->weight_min = parse_weight(v, &rest); + f->weight_max = (rest != NULL && *skip(rest, rest + strlen(rest)) != '\0') ? parse_weight(rest, NULL) : f->weight_min; + } + else if (strcmp(decls[i].name, "font-style") == 0) + f->italic = strncmp(v, "italic", 6) == 0 || strncmp(v, "oblique", 7) == 0; + else if (strcmp(decls[i].name, "src") == 0) + f->path = pick_src(c, v, base); + } + if (f->family == NULL || f->path == NULL) + return; /* Nothing it can be drawn with (woff2 only, a remote font) */ + f->next = c->faces; + c->faces = f; +} + +/* ---- @media ---- */ + +static int32_t media_px(const char* s, const char* end) +{ + int32_t v = 0, frac = 0, scale = 1; + s = skip(s, end); + for (; s < end && *s >= '0' && *s <= '9'; s++) + v = v * 10 + (*s - '0'); + if (s < end && *s == '.') + { + for (s++; s < end && *s >= '0' && *s <= '9' && scale < 1000; s++, scale *= 10) + frac = frac * 10 + (*s - '0'); + } + int32_t px = v * U + frac * U / scale; + if (end - s >= 2 && (ieq(s, 2, "em") || (end - s >= 3 && ieq(s, 3, "rem")))) + px *= 16; + return px; +} + +static bool media_feature(conv_t* c, const char* s, const char* end) +{ + const char* colon = find_top(s, end, ":"); + const char* name = skip(s, end); + const char* name_end = colon; + while (name_end > name && is_space(name_end[-1])) + name_end--; + size_t n = (size_t)(name_end - name); + const char* v = (colon < end) ? skip(colon + 1, end) : end; + if (ieq(name, n, "min-width")) + return c->vw >= media_px(v, end); + if (ieq(name, n, "max-width")) + return c->vw <= media_px(v, end); + if (ieq(name, n, "min-height")) + return c->vh >= media_px(v, end); + if (ieq(name, n, "max-height")) + return c->vh <= media_px(v, end); + if (ieq(name, n, "orientation")) + return ieq(v, 8, "portrait") ? c->vh >= c->vw : c->vw > c->vh; + if (ieq(name, n, "prefers-color-scheme")) + return ieq(v, 5, "light"); + if (ieq(name, n, "prefers-reduced-motion")) + return ieq(v, 13, "no-preference"); + if (ieq(name, n, "hover") || ieq(name, n, "any-hover")) + return ieq(v, 4, "none"); + if (ieq(name, n, "pointer") || ieq(name, n, "any-pointer")) + return ieq(v, 6, "coarse"); + return false; +} + +/* A media query list: any of its queries */ +static bool media_matches(conv_t* c, const char* s, const char* end) +{ + s = skip(s, end); + if (s >= end) + return true; + while (s < end) + { + const char* q_end = find_top(s, end, ","); + bool ok = true, negate = false; + const char* t = s; + while (t < q_end) + { + t = skip(t, q_end); + if (t >= q_end) + break; + if (*t == '(') + { + const char* close = find_top(t + 1, q_end, ")"); + ok = ok && media_feature(c, t + 1, close); + t = (close < q_end) ? close + 1 : q_end; + continue; + } + const char* w = t; + while (t < q_end && is_name_char(*t)) + t++; + size_t n = (size_t)(t - w); + if (n == 0) + { + t++; + continue; + } + if (ieq(w, n, "not")) + negate = true; + else if (ieq(w, n, "only") || ieq(w, n, "and") || ieq(w, n, "all") || ieq(w, n, "screen")) + continue; + else + ok = false; /* print, speech, ... */ + } + if (ok != negate) + return true; + s = (q_end < end) ? q_end + 1 : end; + } + return false; +} + +/* ---- Style sheets ---- */ + +static void parse_sheet(conv_t* c, const char* s, const char* end, const char* base, uint32_t layer, uint32_t depth); + +/* @import url(...) [media] */ +static void import(conv_t* c, const char* s, const char* end, const char* base, uint32_t layer, uint32_t depth) +{ + s = skip(s, end); + const char* url = NULL; + size_t n = 0; + if (end - s > 4 && ieq(s, 4, "url(")) + { + const char* a = skip(s + 4, end); + const char* close = find_top(a, end, ")"); + const char* b = close; + while (b > a && is_space(b[-1])) + b--; + if (b > a && (*a == '"' || *a == '\'')) + { + a++; + b--; + } + url = a; + n = (size_t)(b - a); + s = (close < end) ? close + 1 : end; + } + else if (s < end && (*s == '"' || *s == '\'')) + { + const char* q = skip_string(s, end); + url = s + 1; + n = (size_t)(q - s - 2); + s = q; + } + if (url == NULL || depth >= MAX_IMPORT_DEPTH || !media_matches(c, s, end)) + return; + char* absolute = resource_url(c, base, url, n); + char* path = (absolute != NULL) ? resource_path(c, absolute) : NULL; + size_t size = 0; + char* text = (path != NULL) ? read_resource(c, path, &size) : NULL; + if (text == NULL) + { + WARN(c, "cannot load the style sheet %s\n", (absolute != NULL) ? absolute : "?"); + return; + } + parse_sheet(c, text, text + size, path, layer, depth + 1U); +} + +/* A rule: its selectors, each a rule of the same declarations */ +static void style_rule(conv_t* c, const char* prelude, const char* prelude_end, const char* body, const char* body_end, + const char* base, uint32_t layer) +{ + decl_t* decls = NULL; + uint16_t n = parse_decls(c, body, body_end, &decls); + if (n == 0) + return; + uint32_t order = ++c->order; + const char* s = prelude; + while (s < prelude_end) + { + const char* sel_end = find_top(s, prelude_end, ","); + rule_t* r = arena_alloc(&c->arena, sizeof(*r)); + if (r == NULL) + return; + if (parse_selector(c, s, sel_end, r)) + { + r->decls = decls; + r->decl_count = n; + r->order = order; + r->layer = (uint8_t)layer; + r->base = base; + css_add_rule(c, r); + } + s = (sel_end < prelude_end) ? sel_end + 1 : prelude_end; + } +} + +static void parse_sheet(conv_t* c, const char* s, const char* end, const char* base, uint32_t layer, uint32_t depth) +{ + while (s < end && !c->arena.failed) + { + s = skip(s, end); + if (s >= end) + break; + if (*s == '}') + { + s++; /* A stray one */ + continue; + } + if (*s == '@') + { + const char* name = ++s; + while (s < end && is_name_char(*s)) + s++; + size_t n = (size_t)(s - name); + const char* stop = find_top(s, end, ";{"); + if (stop >= end || *stop == ';') + { + if (ieq(name, n, "import")) + import(c, s, stop, base, layer, depth); + s = (stop < end) ? stop + 1 : end; + continue; + } + const char* body = stop + 1; + const char* close = find_top(body, end, "}"); + if (ieq(name, n, "media")) + { + if (media_matches(c, s, stop)) + parse_sheet(c, body, close, base, layer, depth); + } + else if (ieq(name, n, "supports") || ieq(name, n, "layer") || ieq(name, n, "container")) + parse_sheet(c, body, close, base, layer, depth); + else if (ieq(name, n, "font-face")) + font_face(c, body, close, base); + s = (close < end) ? close + 1 : end; + continue; + } + const char* open = find_top(s, end, "{"); + if (open >= end) + break; + const char* close = find_top(open + 1, end, "}"); + style_rule(c, s, open, open + 1, close, base, layer); + s = (close < end) ? close + 1 : end; + } +} + +uint16_t css_parse_decls(conv_t* c, const char* text, size_t length, decl_t** decls) +{ + return parse_decls(c, text, text + length, decls); +} + +void css_parse(conv_t* c, const char* text, size_t length, const char* base, uint32_t layer) +{ + parse_sheet(c, text, text + length, base, layer, 0); +} diff --git a/src/dmvs_html.c b/src/dmvs_html.c new file mode 100644 index 0000000..310e35c --- /dev/null +++ b/src/dmvs_html.c @@ -0,0 +1,524 @@ +#define DMOD_ENABLE_REGISTRATION ON +#include "private.h" +#include +#include + +/* + * dmvs_html - the dmvsi converter of HTML pages: a still page (no scripts) + * of HTML and CSS - its " + "
x
" + "")); + dmvsi_doc_t doc = convert(TEST_FILE("styles.html"), NULL, 200, 150, &status); + DMOD_TEST_EXPECT_EQ(status, 0); + DMOD_TEST_EXPECT_TRUE(doc != NULL); + if (doc == NULL) + return; + const dmvsi_node_t* root = dmvsi_root(doc); + /* calc(50% - 20px) of 200 = 80, + the borders: 84 x 34 (3em of 10px + 4) */ + dmvsi_rect_t a = { DMVSI_PX(10), DMVSI_PX(20), DMVSI_PX(84), DMVSI_PX(34) }; + const dmvsi_node_t* n = find(root->first, DMVSI_NODE_RECT, NULL, &a); + DMOD_TEST_EXPECT_TRUE(n != NULL && n->u.fill.paint.color == 0x80FF0000u && n->u.fill.radius == DMVSI_PX(4)); + DMOD_TEST_EXPECT_TRUE(n != NULL && n->next != NULL && n->next->kind == DMVSI_NODE_FRAME && + n->next->u.frame.paint.color == 0x800000FFu && n->next->u.frame.width == DMVSI_PX(2)); + /* var() and its fallback; the media query does not hold at 200 px */ + dmvsi_rect_t b = { 0, DMVSI_PX(100), DMVSI_PX(30), DMVSI_PX(15) }; + n = find(root->first, DMVSI_NODE_RECT, NULL, &b); + DMOD_TEST_EXPECT_TRUE(n != NULL && n->u.fill.paint.color == 0x8800FF00u); + dmvsi_free(doc); +} + +DMOD_TEST_STEP(dmvs_html_lays_flex_out_and_stacks) +{ + int status = -1; + DMOD_TEST_EXPECT_TRUE(write_file(TEST_FILE("flex.html"), + "" + "
" + "
" + "
" + "")); + dmvsi_doc_t doc = convert(TEST_FILE("flex.html"), NULL, 200, 200, &status); + DMOD_TEST_EXPECT_EQ(status, 0); + if (doc == NULL) + return; + const dmvsi_node_t* root = dmvsi_root(doc); + dmvsi_rect_t r1 = { DMVSI_PX(75), 0, DMVSI_PX(50), DMVSI_PX(40) }; /* the middle one, space-between */ + dmvsi_rect_t r2 = { DMVSI_PX(150), 0, DMVSI_PX(50), DMVSI_PX(40) }; + dmvsi_rect_t grow = { 0, DMVSI_PX(70), DMVSI_PX(200), DMVSI_PX(70) }; /* 100 - 30 */ + DMOD_TEST_EXPECT_TRUE(find(root->first, DMVSI_NODE_RECT, NULL, &r1) != NULL); + DMOD_TEST_EXPECT_TRUE(find(root->first, DMVSI_NODE_RECT, NULL, &r2) != NULL); + DMOD_TEST_EXPECT_TRUE(find(root->first, DMVSI_NODE_RECT, NULL, &grow) != NULL); + /* A flex item with a z-index is painted over a positioned box of z-index auto */ + dmvsi_rect_t z1 = { 0, 0, DMVSI_PX(200), DMVSI_PX(200) }, z2 = { 0, DMVSI_PX(140), DMVSI_PX(10), DMVSI_PX(10) }; + const dmvsi_node_t* a = find(root->first, DMVSI_NODE_RECT, NULL, &z1); + const dmvsi_node_t* b = find(root->first, DMVSI_NODE_RECT, NULL, &z2); + bool b_after_a = false; + for (const dmvsi_node_t* k = a; k != NULL; k = k->next) + b_after_a = b_after_a || k == b; + DMOD_TEST_EXPECT_TRUE(a != NULL && b != NULL && b_after_a); + dmvsi_free(doc); +} + +DMOD_TEST_STEP(dmvs_html_reports_what_it_cannot_convert) { - DMOD_TEST_EXPECT_TRUE(1); + int status = 0; + DMOD_TEST_EXPECT_TRUE(convert(TEST_FILE("missing.html"), NULL, 0, 0, &status) == NULL); + DMOD_TEST_EXPECT_EQ(status, -ENOENT); + DMOD_TEST_EXPECT_TRUE(convert(FIXTURE("dmodos.html"), "no-such-id", 0, 0, &status) == NULL); + DMOD_TEST_EXPECT_EQ(status, -EINVAL); } diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md new file mode 100644 index 0000000..6e45ca7 --- /dev/null +++ b/tests/fixtures/README.md @@ -0,0 +1,16 @@ +# Test fixtures + +## dmodos/ + +A UI design for a 272 x 480 screen, made in HTML with Tailwind CSS (its +Play CDN), Font Awesome and Inter from Google Fonts - the page as it is, +`dmodos.html`. The test maps its CDN resources to the files here, as +`todmvs -m URL=FILE` does: + +| URL | File | +|-----|------| +| `https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css` | `fontawesome.css` - Font Awesome Free 6.4.0's CSS (MIT) | +| `.../font-awesome/6.4.0/webfonts/fa-solid-900.ttf`, `fa-brands-400.ttf` | `fonts/icons-solid.ttf`, `fonts/icons-brands.ttf` - its fonts cut down to the icons of the page, renamed as the SIL OFL asks of a modified font (`fonts/LICENSE-FontAwesome.txt`) | +| `https://fonts.googleapis.com/css2` | `inter.css` - `@font-face` of `fonts/Inter-*.otf`, Inter cut down to the characters of the page (SIL OFL, `fonts/LICENSE-Inter.txt`) | + +The expected boxes in the test are the ones Chrome lays the page out in. diff --git a/tests/fixtures/dmodos/dmodos.html b/tests/fixtures/dmodos/dmodos.html new file mode 100644 index 0000000..44ce6dd --- /dev/null +++ b/tests/fixtures/dmodos/dmodos.html @@ -0,0 +1,470 @@ + + + + + + dmodOS - 272x480 UI + + + + + + + + + + + + + +
+ +
+ + +
+
+ 12:00 + dmodOS +
+
+ + +
+ 84% + +
+
+
+ + +
+ +
+
+
+

12:00

+

Poniedziałek, 17 Lip

+
+
+
+ +
+ 22°C + Banino, PL +
+
+
+
+ + +
+ + + + + + + + + + +
+
+ + + + +
+
+ + Ustawienia +
+
+
+
+ + Wi-Fi +
+
+
+
+
+
+
+ + Bluetooth +
+
+
+
+
+
+
+ + Jasność ekranu +
+ 80% +
+
+
+ + O systemie dmodOS +
+ +
+
+
+ + +
+
+ + Zasoby Systemu +
+
+ +
+
+ CPU + 24% +
+
+
+
+
+ +
+
+ RAM + 112 / 256 MB +
+
+
+
+
+ +
+
+ Flash + 1.2 / 4.0 GB +
+
+
+
+
+ +
+ Uptime: 04:12:33 • Temp: 42°C +
+
+
+ + +
+
+ + Sklep dmodOS +
+
+ +
+ +

Gry Retro

+

Odkryj nowości w dmodOS

+
+ +

Polecane Aplikacje

+ + +
+
+
+ +
+
+ Kalkulator + Narzędzia +
+
+ +
+ +
+
+
+ +
+
+ Chat dmod + Komunikacja +
+
+ +
+
+
+ + +
+
+ +
+ +
+ +
+
+ +
+ +

Retro Synthwave

+

Artysta Nieznany

+ +
+ 1:24 +
+
+
+
+
+ 3:45 +
+ +
+ + + +
+
+
+ + +
+
+ + Smart Dom +
+
+
+ +

Salon

+

100%

+
+
+ +

Kuchnia

+

Wył.

+
+
+ +

Term.

+

21.5°C

+
+
+ +

Drzwi

+

Zamek

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

22°C

+

Banino, PL

+

Częściowe zachmurzenie

+ +
+
+

Wiatr

+

14 km/h

+
+
+

Wilgot.

+

65%

+
+
+

Deszcz

+

0 mm

+
+
+
+
+ +
+ + +
+
+ + + + \ No newline at end of file diff --git a/tests/fixtures/dmodos/fontawesome.css b/tests/fixtures/dmodos/fontawesome.css new file mode 100644 index 0000000..2fa60b1 --- /dev/null +++ b/tests/fixtures/dmodos/fontawesome.css @@ -0,0 +1,7955 @@ +/*! + * Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2023 Fonticons, Inc. + */ +.fa { + font-family: var(--fa-style-family, "Font Awesome 6 Free"); + font-weight: var(--fa-style, 900); } + +.fa, +.fa-classic, +.fa-sharp, +.fas, +.fa-solid, +.far, +.fa-regular, +.fab, +.fa-brands { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: var(--fa-display, inline-block); + font-style: normal; + font-variant: normal; + line-height: 1; + text-rendering: auto; } + +.fas, +.fa-classic, +.fa-solid, +.far, +.fa-regular { + font-family: 'Font Awesome 6 Free'; } + +.fab, +.fa-brands { + font-family: 'Font Awesome 6 Brands'; } + +.fa-1x { + font-size: 1em; } + +.fa-2x { + font-size: 2em; } + +.fa-3x { + font-size: 3em; } + +.fa-4x { + font-size: 4em; } + +.fa-5x { + font-size: 5em; } + +.fa-6x { + font-size: 6em; } + +.fa-7x { + font-size: 7em; } + +.fa-8x { + font-size: 8em; } + +.fa-9x { + font-size: 9em; } + +.fa-10x { + font-size: 10em; } + +.fa-2xs { + font-size: 0.625em; + line-height: 0.1em; + vertical-align: 0.225em; } + +.fa-xs { + font-size: 0.75em; + line-height: 0.08333em; + vertical-align: 0.125em; } + +.fa-sm { + font-size: 0.875em; + line-height: 0.07143em; + vertical-align: 0.05357em; } + +.fa-lg { + font-size: 1.25em; + line-height: 0.05em; + vertical-align: -0.075em; } + +.fa-xl { + font-size: 1.5em; + line-height: 0.04167em; + vertical-align: -0.125em; } + +.fa-2xl { + font-size: 2em; + line-height: 0.03125em; + vertical-align: -0.1875em; } + +.fa-fw { + text-align: center; + width: 1.25em; } + +.fa-ul { + list-style-type: none; + margin-left: var(--fa-li-margin, 2.5em); + padding-left: 0; } + .fa-ul > li { + position: relative; } + +.fa-li { + left: calc(var(--fa-li-width, 2em) * -1); + position: absolute; + text-align: center; + width: var(--fa-li-width, 2em); + line-height: inherit; } + +.fa-border { + border-color: var(--fa-border-color, #eee); + border-radius: var(--fa-border-radius, 0.1em); + border-style: var(--fa-border-style, solid); + border-width: var(--fa-border-width, 0.08em); + padding: var(--fa-border-padding, 0.2em 0.25em 0.15em); } + +.fa-pull-left { + float: left; + margin-right: var(--fa-pull-margin, 0.3em); } + +.fa-pull-right { + float: right; + margin-left: var(--fa-pull-margin, 0.3em); } + +.fa-beat { + -webkit-animation-name: fa-beat; + animation-name: fa-beat; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out); + animation-timing-function: var(--fa-animation-timing, ease-in-out); } + +.fa-bounce { + -webkit-animation-name: fa-bounce; + animation-name: fa-bounce; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); } + +.fa-fade { + -webkit-animation-name: fa-fade; + animation-name: fa-fade; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } + +.fa-beat-fade { + -webkit-animation-name: fa-beat-fade; + animation-name: fa-beat-fade; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } + +.fa-flip { + -webkit-animation-name: fa-flip; + animation-name: fa-flip; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out); + animation-timing-function: var(--fa-animation-timing, ease-in-out); } + +.fa-shake { + -webkit-animation-name: fa-shake; + animation-name: fa-shake; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, linear); + animation-timing-function: var(--fa-animation-timing, linear); } + +.fa-spin { + -webkit-animation-name: fa-spin; + animation-name: fa-spin; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 2s); + animation-duration: var(--fa-animation-duration, 2s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, linear); + animation-timing-function: var(--fa-animation-timing, linear); } + +.fa-spin-reverse { + --fa-animation-direction: reverse; } + +.fa-pulse, +.fa-spin-pulse { + -webkit-animation-name: fa-spin; + animation-name: fa-spin; + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, steps(8)); + animation-timing-function: var(--fa-animation-timing, steps(8)); } + +@media (prefers-reduced-motion: reduce) { + .fa-beat, + .fa-bounce, + .fa-fade, + .fa-beat-fade, + .fa-flip, + .fa-pulse, + .fa-shake, + .fa-spin, + .fa-spin-pulse { + -webkit-animation-delay: -1ms; + animation-delay: -1ms; + -webkit-animation-duration: 1ms; + animation-duration: 1ms; + -webkit-animation-iteration-count: 1; + animation-iteration-count: 1; + -webkit-transition-delay: 0s; + transition-delay: 0s; + -webkit-transition-duration: 0s; + transition-duration: 0s; } } + +@-webkit-keyframes fa-beat { + 0%, 90% { + -webkit-transform: scale(1); + transform: scale(1); } + 45% { + -webkit-transform: scale(var(--fa-beat-scale, 1.25)); + transform: scale(var(--fa-beat-scale, 1.25)); } } + +@keyframes fa-beat { + 0%, 90% { + -webkit-transform: scale(1); + transform: scale(1); } + 45% { + -webkit-transform: scale(var(--fa-beat-scale, 1.25)); + transform: scale(var(--fa-beat-scale, 1.25)); } } + +@-webkit-keyframes fa-bounce { + 0% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } + 10% { + -webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); + transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); } + 30% { + -webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); + transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); } + 50% { + -webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); + transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); } + 57% { + -webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); + transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); } + 64% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } + 100% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } } + +@keyframes fa-bounce { + 0% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } + 10% { + -webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); + transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); } + 30% { + -webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); + transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); } + 50% { + -webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); + transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); } + 57% { + -webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); + transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); } + 64% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } + 100% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } } + +@-webkit-keyframes fa-fade { + 50% { + opacity: var(--fa-fade-opacity, 0.4); } } + +@keyframes fa-fade { + 50% { + opacity: var(--fa-fade-opacity, 0.4); } } + +@-webkit-keyframes fa-beat-fade { + 0%, 100% { + opacity: var(--fa-beat-fade-opacity, 0.4); + -webkit-transform: scale(1); + transform: scale(1); } + 50% { + opacity: 1; + -webkit-transform: scale(var(--fa-beat-fade-scale, 1.125)); + transform: scale(var(--fa-beat-fade-scale, 1.125)); } } + +@keyframes fa-beat-fade { + 0%, 100% { + opacity: var(--fa-beat-fade-opacity, 0.4); + -webkit-transform: scale(1); + transform: scale(1); } + 50% { + opacity: 1; + -webkit-transform: scale(var(--fa-beat-fade-scale, 1.125)); + transform: scale(var(--fa-beat-fade-scale, 1.125)); } } + +@-webkit-keyframes fa-flip { + 50% { + -webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); + transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } } + +@keyframes fa-flip { + 50% { + -webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); + transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } } + +@-webkit-keyframes fa-shake { + 0% { + -webkit-transform: rotate(-15deg); + transform: rotate(-15deg); } + 4% { + -webkit-transform: rotate(15deg); + transform: rotate(15deg); } + 8%, 24% { + -webkit-transform: rotate(-18deg); + transform: rotate(-18deg); } + 12%, 28% { + -webkit-transform: rotate(18deg); + transform: rotate(18deg); } + 16% { + -webkit-transform: rotate(-22deg); + transform: rotate(-22deg); } + 20% { + -webkit-transform: rotate(22deg); + transform: rotate(22deg); } + 32% { + -webkit-transform: rotate(-12deg); + transform: rotate(-12deg); } + 36% { + -webkit-transform: rotate(12deg); + transform: rotate(12deg); } + 40%, 100% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } } + +@keyframes fa-shake { + 0% { + -webkit-transform: rotate(-15deg); + transform: rotate(-15deg); } + 4% { + -webkit-transform: rotate(15deg); + transform: rotate(15deg); } + 8%, 24% { + -webkit-transform: rotate(-18deg); + transform: rotate(-18deg); } + 12%, 28% { + -webkit-transform: rotate(18deg); + transform: rotate(18deg); } + 16% { + -webkit-transform: rotate(-22deg); + transform: rotate(-22deg); } + 20% { + -webkit-transform: rotate(22deg); + transform: rotate(22deg); } + 32% { + -webkit-transform: rotate(-12deg); + transform: rotate(-12deg); } + 36% { + -webkit-transform: rotate(12deg); + transform: rotate(12deg); } + 40%, 100% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } } + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +.fa-rotate-90 { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); } + +.fa-rotate-180 { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); } + +.fa-rotate-270 { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + +.fa-flip-horizontal { + -webkit-transform: scale(-1, 1); + transform: scale(-1, 1); } + +.fa-flip-vertical { + -webkit-transform: scale(1, -1); + transform: scale(1, -1); } + +.fa-flip-both, +.fa-flip-horizontal.fa-flip-vertical { + -webkit-transform: scale(-1, -1); + transform: scale(-1, -1); } + +.fa-rotate-by { + -webkit-transform: rotate(var(--fa-rotate-angle, none)); + transform: rotate(var(--fa-rotate-angle, none)); } + +.fa-stack { + display: inline-block; + height: 2em; + line-height: 2em; + position: relative; + vertical-align: middle; + width: 2.5em; } + +.fa-stack-1x, +.fa-stack-2x { + left: 0; + position: absolute; + text-align: center; + width: 100%; + z-index: var(--fa-stack-z-index, auto); } + +.fa-stack-1x { + line-height: inherit; } + +.fa-stack-2x { + font-size: 2em; } + +.fa-inverse { + color: var(--fa-inverse, #fff); } + +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen +readers do not read off random characters that represent icons */ + +.fa-0::before { + content: "\30"; } + +.fa-1::before { + content: "\31"; } + +.fa-2::before { + content: "\32"; } + +.fa-3::before { + content: "\33"; } + +.fa-4::before { + content: "\34"; } + +.fa-5::before { + content: "\35"; } + +.fa-6::before { + content: "\36"; } + +.fa-7::before { + content: "\37"; } + +.fa-8::before { + content: "\38"; } + +.fa-9::before { + content: "\39"; } + +.fa-fill-drip::before { + content: "\f576"; } + +.fa-arrows-to-circle::before { + content: "\e4bd"; } + +.fa-circle-chevron-right::before { + content: "\f138"; } + +.fa-chevron-circle-right::before { + content: "\f138"; } + +.fa-at::before { + content: "\40"; } + +.fa-trash-can::before { + content: "\f2ed"; } + +.fa-trash-alt::before { + content: "\f2ed"; } + +.fa-text-height::before { + content: "\f034"; } + +.fa-user-xmark::before { + content: "\f235"; } + +.fa-user-times::before { + content: "\f235"; } + +.fa-stethoscope::before { + content: "\f0f1"; } + +.fa-message::before { + content: "\f27a"; } + +.fa-comment-alt::before { + content: "\f27a"; } + +.fa-info::before { + content: "\f129"; } + +.fa-down-left-and-up-right-to-center::before { + content: "\f422"; } + +.fa-compress-alt::before { + content: "\f422"; } + +.fa-explosion::before { + content: "\e4e9"; } + +.fa-file-lines::before { + content: "\f15c"; } + +.fa-file-alt::before { + content: "\f15c"; } + +.fa-file-text::before { + content: "\f15c"; } + +.fa-wave-square::before { + content: "\f83e"; } + +.fa-ring::before { + content: "\f70b"; } + +.fa-building-un::before { + content: "\e4d9"; } + +.fa-dice-three::before { + content: "\f527"; } + +.fa-calendar-days::before { + content: "\f073"; } + +.fa-calendar-alt::before { + content: "\f073"; } + +.fa-anchor-circle-check::before { + content: "\e4aa"; } + +.fa-building-circle-arrow-right::before { + content: "\e4d1"; } + +.fa-volleyball::before { + content: "\f45f"; } + +.fa-volleyball-ball::before { + content: "\f45f"; } + +.fa-arrows-up-to-line::before { + content: "\e4c2"; } + +.fa-sort-down::before { + content: "\f0dd"; } + +.fa-sort-desc::before { + content: "\f0dd"; } + +.fa-circle-minus::before { + content: "\f056"; } + +.fa-minus-circle::before { + content: "\f056"; } + +.fa-door-open::before { + content: "\f52b"; } + +.fa-right-from-bracket::before { + content: "\f2f5"; } + +.fa-sign-out-alt::before { + content: "\f2f5"; } + +.fa-atom::before { + content: "\f5d2"; } + +.fa-soap::before { + content: "\e06e"; } + +.fa-icons::before { + content: "\f86d"; } + +.fa-heart-music-camera-bolt::before { + content: "\f86d"; } + +.fa-microphone-lines-slash::before { + content: "\f539"; } + +.fa-microphone-alt-slash::before { + content: "\f539"; } + +.fa-bridge-circle-check::before { + content: "\e4c9"; } + +.fa-pump-medical::before { + content: "\e06a"; } + +.fa-fingerprint::before { + content: "\f577"; } + +.fa-hand-point-right::before { + content: "\f0a4"; } + +.fa-magnifying-glass-location::before { + content: "\f689"; } + +.fa-search-location::before { + content: "\f689"; } + +.fa-forward-step::before { + content: "\f051"; } + +.fa-step-forward::before { + content: "\f051"; } + +.fa-face-smile-beam::before { + content: "\f5b8"; } + +.fa-smile-beam::before { + content: "\f5b8"; } + +.fa-flag-checkered::before { + content: "\f11e"; } + +.fa-football::before { + content: "\f44e"; } + +.fa-football-ball::before { + content: "\f44e"; } + +.fa-school-circle-exclamation::before { + content: "\e56c"; } + +.fa-crop::before { + content: "\f125"; } + +.fa-angles-down::before { + content: "\f103"; } + +.fa-angle-double-down::before { + content: "\f103"; } + +.fa-users-rectangle::before { + content: "\e594"; } + +.fa-people-roof::before { + content: "\e537"; } + +.fa-people-line::before { + content: "\e534"; } + +.fa-beer-mug-empty::before { + content: "\f0fc"; } + +.fa-beer::before { + content: "\f0fc"; } + +.fa-diagram-predecessor::before { + content: "\e477"; } + +.fa-arrow-up-long::before { + content: "\f176"; } + +.fa-long-arrow-up::before { + content: "\f176"; } + +.fa-fire-flame-simple::before { + content: "\f46a"; } + +.fa-burn::before { + content: "\f46a"; } + +.fa-person::before { + content: "\f183"; } + +.fa-male::before { + content: "\f183"; } + +.fa-laptop::before { + content: "\f109"; } + +.fa-file-csv::before { + content: "\f6dd"; } + +.fa-menorah::before { + content: "\f676"; } + +.fa-truck-plane::before { + content: "\e58f"; } + +.fa-record-vinyl::before { + content: "\f8d9"; } + +.fa-face-grin-stars::before { + content: "\f587"; } + +.fa-grin-stars::before { + content: "\f587"; } + +.fa-bong::before { + content: "\f55c"; } + +.fa-spaghetti-monster-flying::before { + content: "\f67b"; } + +.fa-pastafarianism::before { + content: "\f67b"; } + +.fa-arrow-down-up-across-line::before { + content: "\e4af"; } + +.fa-spoon::before { + content: "\f2e5"; } + +.fa-utensil-spoon::before { + content: "\f2e5"; } + +.fa-jar-wheat::before { + content: "\e517"; } + +.fa-envelopes-bulk::before { + content: "\f674"; } + +.fa-mail-bulk::before { + content: "\f674"; } + +.fa-file-circle-exclamation::before { + content: "\e4eb"; } + +.fa-circle-h::before { + content: "\f47e"; } + +.fa-hospital-symbol::before { + content: "\f47e"; } + +.fa-pager::before { + content: "\f815"; } + +.fa-address-book::before { + content: "\f2b9"; } + +.fa-contact-book::before { + content: "\f2b9"; } + +.fa-strikethrough::before { + content: "\f0cc"; } + +.fa-k::before { + content: "\4b"; } + +.fa-landmark-flag::before { + content: "\e51c"; } + +.fa-pencil::before { + content: "\f303"; } + +.fa-pencil-alt::before { + content: "\f303"; } + +.fa-backward::before { + content: "\f04a"; } + +.fa-caret-right::before { + content: "\f0da"; } + +.fa-comments::before { + content: "\f086"; } + +.fa-paste::before { + content: "\f0ea"; } + +.fa-file-clipboard::before { + content: "\f0ea"; } + +.fa-code-pull-request::before { + content: "\e13c"; } + +.fa-clipboard-list::before { + content: "\f46d"; } + +.fa-truck-ramp-box::before { + content: "\f4de"; } + +.fa-truck-loading::before { + content: "\f4de"; } + +.fa-user-check::before { + content: "\f4fc"; } + +.fa-vial-virus::before { + content: "\e597"; } + +.fa-sheet-plastic::before { + content: "\e571"; } + +.fa-blog::before { + content: "\f781"; } + +.fa-user-ninja::before { + content: "\f504"; } + +.fa-person-arrow-up-from-line::before { + content: "\e539"; } + +.fa-scroll-torah::before { + content: "\f6a0"; } + +.fa-torah::before { + content: "\f6a0"; } + +.fa-broom-ball::before { + content: "\f458"; } + +.fa-quidditch::before { + content: "\f458"; } + +.fa-quidditch-broom-ball::before { + content: "\f458"; } + +.fa-toggle-off::before { + content: "\f204"; } + +.fa-box-archive::before { + content: "\f187"; } + +.fa-archive::before { + content: "\f187"; } + +.fa-person-drowning::before { + content: "\e545"; } + +.fa-arrow-down-9-1::before { + content: "\f886"; } + +.fa-sort-numeric-desc::before { + content: "\f886"; } + +.fa-sort-numeric-down-alt::before { + content: "\f886"; } + +.fa-face-grin-tongue-squint::before { + content: "\f58a"; } + +.fa-grin-tongue-squint::before { + content: "\f58a"; } + +.fa-spray-can::before { + content: "\f5bd"; } + +.fa-truck-monster::before { + content: "\f63b"; } + +.fa-w::before { + content: "\57"; } + +.fa-earth-africa::before { + content: "\f57c"; } + +.fa-globe-africa::before { + content: "\f57c"; } + +.fa-rainbow::before { + content: "\f75b"; } + +.fa-circle-notch::before { + content: "\f1ce"; } + +.fa-tablet-screen-button::before { + content: "\f3fa"; } + +.fa-tablet-alt::before { + content: "\f3fa"; } + +.fa-paw::before { + content: "\f1b0"; } + +.fa-cloud::before { + content: "\f0c2"; } + +.fa-trowel-bricks::before { + content: "\e58a"; } + +.fa-face-flushed::before { + content: "\f579"; } + +.fa-flushed::before { + content: "\f579"; } + +.fa-hospital-user::before { + content: "\f80d"; } + +.fa-tent-arrow-left-right::before { + content: "\e57f"; } + +.fa-gavel::before { + content: "\f0e3"; } + +.fa-legal::before { + content: "\f0e3"; } + +.fa-binoculars::before { + content: "\f1e5"; } + +.fa-microphone-slash::before { + content: "\f131"; } + +.fa-box-tissue::before { + content: "\e05b"; } + +.fa-motorcycle::before { + content: "\f21c"; } + +.fa-bell-concierge::before { + content: "\f562"; } + +.fa-concierge-bell::before { + content: "\f562"; } + +.fa-pen-ruler::before { + content: "\f5ae"; } + +.fa-pencil-ruler::before { + content: "\f5ae"; } + +.fa-people-arrows::before { + content: "\e068"; } + +.fa-people-arrows-left-right::before { + content: "\e068"; } + +.fa-mars-and-venus-burst::before { + content: "\e523"; } + +.fa-square-caret-right::before { + content: "\f152"; } + +.fa-caret-square-right::before { + content: "\f152"; } + +.fa-scissors::before { + content: "\f0c4"; } + +.fa-cut::before { + content: "\f0c4"; } + +.fa-sun-plant-wilt::before { + content: "\e57a"; } + +.fa-toilets-portable::before { + content: "\e584"; } + +.fa-hockey-puck::before { + content: "\f453"; } + +.fa-table::before { + content: "\f0ce"; } + +.fa-magnifying-glass-arrow-right::before { + content: "\e521"; } + +.fa-tachograph-digital::before { + content: "\f566"; } + +.fa-digital-tachograph::before { + content: "\f566"; } + +.fa-users-slash::before { + content: "\e073"; } + +.fa-clover::before { + content: "\e139"; } + +.fa-reply::before { + content: "\f3e5"; } + +.fa-mail-reply::before { + content: "\f3e5"; } + +.fa-star-and-crescent::before { + content: "\f699"; } + +.fa-house-fire::before { + content: "\e50c"; } + +.fa-square-minus::before { + content: "\f146"; } + +.fa-minus-square::before { + content: "\f146"; } + +.fa-helicopter::before { + content: "\f533"; } + +.fa-compass::before { + content: "\f14e"; } + +.fa-square-caret-down::before { + content: "\f150"; } + +.fa-caret-square-down::before { + content: "\f150"; } + +.fa-file-circle-question::before { + content: "\e4ef"; } + +.fa-laptop-code::before { + content: "\f5fc"; } + +.fa-swatchbook::before { + content: "\f5c3"; } + +.fa-prescription-bottle::before { + content: "\f485"; } + +.fa-bars::before { + content: "\f0c9"; } + +.fa-navicon::before { + content: "\f0c9"; } + +.fa-people-group::before { + content: "\e533"; } + +.fa-hourglass-end::before { + content: "\f253"; } + +.fa-hourglass-3::before { + content: "\f253"; } + +.fa-heart-crack::before { + content: "\f7a9"; } + +.fa-heart-broken::before { + content: "\f7a9"; } + +.fa-square-up-right::before { + content: "\f360"; } + +.fa-external-link-square-alt::before { + content: "\f360"; } + +.fa-face-kiss-beam::before { + content: "\f597"; } + +.fa-kiss-beam::before { + content: "\f597"; } + +.fa-film::before { + content: "\f008"; } + +.fa-ruler-horizontal::before { + content: "\f547"; } + +.fa-people-robbery::before { + content: "\e536"; } + +.fa-lightbulb::before { + content: "\f0eb"; } + +.fa-caret-left::before { + content: "\f0d9"; } + +.fa-circle-exclamation::before { + content: "\f06a"; } + +.fa-exclamation-circle::before { + content: "\f06a"; } + +.fa-school-circle-xmark::before { + content: "\e56d"; } + +.fa-arrow-right-from-bracket::before { + content: "\f08b"; } + +.fa-sign-out::before { + content: "\f08b"; } + +.fa-circle-chevron-down::before { + content: "\f13a"; } + +.fa-chevron-circle-down::before { + content: "\f13a"; } + +.fa-unlock-keyhole::before { + content: "\f13e"; } + +.fa-unlock-alt::before { + content: "\f13e"; } + +.fa-cloud-showers-heavy::before { + content: "\f740"; } + +.fa-headphones-simple::before { + content: "\f58f"; } + +.fa-headphones-alt::before { + content: "\f58f"; } + +.fa-sitemap::before { + content: "\f0e8"; } + +.fa-circle-dollar-to-slot::before { + content: "\f4b9"; } + +.fa-donate::before { + content: "\f4b9"; } + +.fa-memory::before { + content: "\f538"; } + +.fa-road-spikes::before { + content: "\e568"; } + +.fa-fire-burner::before { + content: "\e4f1"; } + +.fa-flag::before { + content: "\f024"; } + +.fa-hanukiah::before { + content: "\f6e6"; } + +.fa-feather::before { + content: "\f52d"; } + +.fa-volume-low::before { + content: "\f027"; } + +.fa-volume-down::before { + content: "\f027"; } + +.fa-comment-slash::before { + content: "\f4b3"; } + +.fa-cloud-sun-rain::before { + content: "\f743"; } + +.fa-compress::before { + content: "\f066"; } + +.fa-wheat-awn::before { + content: "\e2cd"; } + +.fa-wheat-alt::before { + content: "\e2cd"; } + +.fa-ankh::before { + content: "\f644"; } + +.fa-hands-holding-child::before { + content: "\e4fa"; } + +.fa-asterisk::before { + content: "\2a"; } + +.fa-square-check::before { + content: "\f14a"; } + +.fa-check-square::before { + content: "\f14a"; } + +.fa-peseta-sign::before { + content: "\e221"; } + +.fa-heading::before { + content: "\f1dc"; } + +.fa-header::before { + content: "\f1dc"; } + +.fa-ghost::before { + content: "\f6e2"; } + +.fa-list::before { + content: "\f03a"; } + +.fa-list-squares::before { + content: "\f03a"; } + +.fa-square-phone-flip::before { + content: "\f87b"; } + +.fa-phone-square-alt::before { + content: "\f87b"; } + +.fa-cart-plus::before { + content: "\f217"; } + +.fa-gamepad::before { + content: "\f11b"; } + +.fa-circle-dot::before { + content: "\f192"; } + +.fa-dot-circle::before { + content: "\f192"; } + +.fa-face-dizzy::before { + content: "\f567"; } + +.fa-dizzy::before { + content: "\f567"; } + +.fa-egg::before { + content: "\f7fb"; } + +.fa-house-medical-circle-xmark::before { + content: "\e513"; } + +.fa-campground::before { + content: "\f6bb"; } + +.fa-folder-plus::before { + content: "\f65e"; } + +.fa-futbol::before { + content: "\f1e3"; } + +.fa-futbol-ball::before { + content: "\f1e3"; } + +.fa-soccer-ball::before { + content: "\f1e3"; } + +.fa-paintbrush::before { + content: "\f1fc"; } + +.fa-paint-brush::before { + content: "\f1fc"; } + +.fa-lock::before { + content: "\f023"; } + +.fa-gas-pump::before { + content: "\f52f"; } + +.fa-hot-tub-person::before { + content: "\f593"; } + +.fa-hot-tub::before { + content: "\f593"; } + +.fa-map-location::before { + content: "\f59f"; } + +.fa-map-marked::before { + content: "\f59f"; } + +.fa-house-flood-water::before { + content: "\e50e"; } + +.fa-tree::before { + content: "\f1bb"; } + +.fa-bridge-lock::before { + content: "\e4cc"; } + +.fa-sack-dollar::before { + content: "\f81d"; } + +.fa-pen-to-square::before { + content: "\f044"; } + +.fa-edit::before { + content: "\f044"; } + +.fa-car-side::before { + content: "\f5e4"; } + +.fa-share-nodes::before { + content: "\f1e0"; } + +.fa-share-alt::before { + content: "\f1e0"; } + +.fa-heart-circle-minus::before { + content: "\e4ff"; } + +.fa-hourglass-half::before { + content: "\f252"; } + +.fa-hourglass-2::before { + content: "\f252"; } + +.fa-microscope::before { + content: "\f610"; } + +.fa-sink::before { + content: "\e06d"; } + +.fa-bag-shopping::before { + content: "\f290"; } + +.fa-shopping-bag::before { + content: "\f290"; } + +.fa-arrow-down-z-a::before { + content: "\f881"; } + +.fa-sort-alpha-desc::before { + content: "\f881"; } + +.fa-sort-alpha-down-alt::before { + content: "\f881"; } + +.fa-mitten::before { + content: "\f7b5"; } + +.fa-person-rays::before { + content: "\e54d"; } + +.fa-users::before { + content: "\f0c0"; } + +.fa-eye-slash::before { + content: "\f070"; } + +.fa-flask-vial::before { + content: "\e4f3"; } + +.fa-hand::before { + content: "\f256"; } + +.fa-hand-paper::before { + content: "\f256"; } + +.fa-om::before { + content: "\f679"; } + +.fa-worm::before { + content: "\e599"; } + +.fa-house-circle-xmark::before { + content: "\e50b"; } + +.fa-plug::before { + content: "\f1e6"; } + +.fa-chevron-up::before { + content: "\f077"; } + +.fa-hand-spock::before { + content: "\f259"; } + +.fa-stopwatch::before { + content: "\f2f2"; } + +.fa-face-kiss::before { + content: "\f596"; } + +.fa-kiss::before { + content: "\f596"; } + +.fa-bridge-circle-xmark::before { + content: "\e4cb"; } + +.fa-face-grin-tongue::before { + content: "\f589"; } + +.fa-grin-tongue::before { + content: "\f589"; } + +.fa-chess-bishop::before { + content: "\f43a"; } + +.fa-face-grin-wink::before { + content: "\f58c"; } + +.fa-grin-wink::before { + content: "\f58c"; } + +.fa-ear-deaf::before { + content: "\f2a4"; } + +.fa-deaf::before { + content: "\f2a4"; } + +.fa-deafness::before { + content: "\f2a4"; } + +.fa-hard-of-hearing::before { + content: "\f2a4"; } + +.fa-road-circle-check::before { + content: "\e564"; } + +.fa-dice-five::before { + content: "\f523"; } + +.fa-square-rss::before { + content: "\f143"; } + +.fa-rss-square::before { + content: "\f143"; } + +.fa-land-mine-on::before { + content: "\e51b"; } + +.fa-i-cursor::before { + content: "\f246"; } + +.fa-stamp::before { + content: "\f5bf"; } + +.fa-stairs::before { + content: "\e289"; } + +.fa-i::before { + content: "\49"; } + +.fa-hryvnia-sign::before { + content: "\f6f2"; } + +.fa-hryvnia::before { + content: "\f6f2"; } + +.fa-pills::before { + content: "\f484"; } + +.fa-face-grin-wide::before { + content: "\f581"; } + +.fa-grin-alt::before { + content: "\f581"; } + +.fa-tooth::before { + content: "\f5c9"; } + +.fa-v::before { + content: "\56"; } + +.fa-bangladeshi-taka-sign::before { + content: "\e2e6"; } + +.fa-bicycle::before { + content: "\f206"; } + +.fa-staff-snake::before { + content: "\e579"; } + +.fa-rod-asclepius::before { + content: "\e579"; } + +.fa-rod-snake::before { + content: "\e579"; } + +.fa-staff-aesculapius::before { + content: "\e579"; } + +.fa-head-side-cough-slash::before { + content: "\e062"; } + +.fa-truck-medical::before { + content: "\f0f9"; } + +.fa-ambulance::before { + content: "\f0f9"; } + +.fa-wheat-awn-circle-exclamation::before { + content: "\e598"; } + +.fa-snowman::before { + content: "\f7d0"; } + +.fa-mortar-pestle::before { + content: "\f5a7"; } + +.fa-road-barrier::before { + content: "\e562"; } + +.fa-school::before { + content: "\f549"; } + +.fa-igloo::before { + content: "\f7ae"; } + +.fa-joint::before { + content: "\f595"; } + +.fa-angle-right::before { + content: "\f105"; } + +.fa-horse::before { + content: "\f6f0"; } + +.fa-q::before { + content: "\51"; } + +.fa-g::before { + content: "\47"; } + +.fa-notes-medical::before { + content: "\f481"; } + +.fa-temperature-half::before { + content: "\f2c9"; } + +.fa-temperature-2::before { + content: "\f2c9"; } + +.fa-thermometer-2::before { + content: "\f2c9"; } + +.fa-thermometer-half::before { + content: "\f2c9"; } + +.fa-dong-sign::before { + content: "\e169"; } + +.fa-capsules::before { + content: "\f46b"; } + +.fa-poo-storm::before { + content: "\f75a"; } + +.fa-poo-bolt::before { + content: "\f75a"; } + +.fa-face-frown-open::before { + content: "\f57a"; } + +.fa-frown-open::before { + content: "\f57a"; } + +.fa-hand-point-up::before { + content: "\f0a6"; } + +.fa-money-bill::before { + content: "\f0d6"; } + +.fa-bookmark::before { + content: "\f02e"; } + +.fa-align-justify::before { + content: "\f039"; } + +.fa-umbrella-beach::before { + content: "\f5ca"; } + +.fa-helmet-un::before { + content: "\e503"; } + +.fa-bullseye::before { + content: "\f140"; } + +.fa-bacon::before { + content: "\f7e5"; } + +.fa-hand-point-down::before { + content: "\f0a7"; } + +.fa-arrow-up-from-bracket::before { + content: "\e09a"; } + +.fa-folder::before { + content: "\f07b"; } + +.fa-folder-blank::before { + content: "\f07b"; } + +.fa-file-waveform::before { + content: "\f478"; } + +.fa-file-medical-alt::before { + content: "\f478"; } + +.fa-radiation::before { + content: "\f7b9"; } + +.fa-chart-simple::before { + content: "\e473"; } + +.fa-mars-stroke::before { + content: "\f229"; } + +.fa-vial::before { + content: "\f492"; } + +.fa-gauge::before { + content: "\f624"; } + +.fa-dashboard::before { + content: "\f624"; } + +.fa-gauge-med::before { + content: "\f624"; } + +.fa-tachometer-alt-average::before { + content: "\f624"; } + +.fa-wand-magic-sparkles::before { + content: "\e2ca"; } + +.fa-magic-wand-sparkles::before { + content: "\e2ca"; } + +.fa-e::before { + content: "\45"; } + +.fa-pen-clip::before { + content: "\f305"; } + +.fa-pen-alt::before { + content: "\f305"; } + +.fa-bridge-circle-exclamation::before { + content: "\e4ca"; } + +.fa-user::before { + content: "\f007"; } + +.fa-school-circle-check::before { + content: "\e56b"; } + +.fa-dumpster::before { + content: "\f793"; } + +.fa-van-shuttle::before { + content: "\f5b6"; } + +.fa-shuttle-van::before { + content: "\f5b6"; } + +.fa-building-user::before { + content: "\e4da"; } + +.fa-square-caret-left::before { + content: "\f191"; } + +.fa-caret-square-left::before { + content: "\f191"; } + +.fa-highlighter::before { + content: "\f591"; } + +.fa-key::before { + content: "\f084"; } + +.fa-bullhorn::before { + content: "\f0a1"; } + +.fa-globe::before { + content: "\f0ac"; } + +.fa-synagogue::before { + content: "\f69b"; } + +.fa-person-half-dress::before { + content: "\e548"; } + +.fa-road-bridge::before { + content: "\e563"; } + +.fa-location-arrow::before { + content: "\f124"; } + +.fa-c::before { + content: "\43"; } + +.fa-tablet-button::before { + content: "\f10a"; } + +.fa-building-lock::before { + content: "\e4d6"; } + +.fa-pizza-slice::before { + content: "\f818"; } + +.fa-money-bill-wave::before { + content: "\f53a"; } + +.fa-chart-area::before { + content: "\f1fe"; } + +.fa-area-chart::before { + content: "\f1fe"; } + +.fa-house-flag::before { + content: "\e50d"; } + +.fa-person-circle-minus::before { + content: "\e540"; } + +.fa-ban::before { + content: "\f05e"; } + +.fa-cancel::before { + content: "\f05e"; } + +.fa-camera-rotate::before { + content: "\e0d8"; } + +.fa-spray-can-sparkles::before { + content: "\f5d0"; } + +.fa-air-freshener::before { + content: "\f5d0"; } + +.fa-star::before { + content: "\f005"; } + +.fa-repeat::before { + content: "\f363"; } + +.fa-cross::before { + content: "\f654"; } + +.fa-box::before { + content: "\f466"; } + +.fa-venus-mars::before { + content: "\f228"; } + +.fa-arrow-pointer::before { + content: "\f245"; } + +.fa-mouse-pointer::before { + content: "\f245"; } + +.fa-maximize::before { + content: "\f31e"; } + +.fa-expand-arrows-alt::before { + content: "\f31e"; } + +.fa-charging-station::before { + content: "\f5e7"; } + +.fa-shapes::before { + content: "\f61f"; } + +.fa-triangle-circle-square::before { + content: "\f61f"; } + +.fa-shuffle::before { + content: "\f074"; } + +.fa-random::before { + content: "\f074"; } + +.fa-person-running::before { + content: "\f70c"; } + +.fa-running::before { + content: "\f70c"; } + +.fa-mobile-retro::before { + content: "\e527"; } + +.fa-grip-lines-vertical::before { + content: "\f7a5"; } + +.fa-spider::before { + content: "\f717"; } + +.fa-hands-bound::before { + content: "\e4f9"; } + +.fa-file-invoice-dollar::before { + content: "\f571"; } + +.fa-plane-circle-exclamation::before { + content: "\e556"; } + +.fa-x-ray::before { + content: "\f497"; } + +.fa-spell-check::before { + content: "\f891"; } + +.fa-slash::before { + content: "\f715"; } + +.fa-computer-mouse::before { + content: "\f8cc"; } + +.fa-mouse::before { + content: "\f8cc"; } + +.fa-arrow-right-to-bracket::before { + content: "\f090"; } + +.fa-sign-in::before { + content: "\f090"; } + +.fa-shop-slash::before { + content: "\e070"; } + +.fa-store-alt-slash::before { + content: "\e070"; } + +.fa-server::before { + content: "\f233"; } + +.fa-virus-covid-slash::before { + content: "\e4a9"; } + +.fa-shop-lock::before { + content: "\e4a5"; } + +.fa-hourglass-start::before { + content: "\f251"; } + +.fa-hourglass-1::before { + content: "\f251"; } + +.fa-blender-phone::before { + content: "\f6b6"; } + +.fa-building-wheat::before { + content: "\e4db"; } + +.fa-person-breastfeeding::before { + content: "\e53a"; } + +.fa-right-to-bracket::before { + content: "\f2f6"; } + +.fa-sign-in-alt::before { + content: "\f2f6"; } + +.fa-venus::before { + content: "\f221"; } + +.fa-passport::before { + content: "\f5ab"; } + +.fa-heart-pulse::before { + content: "\f21e"; } + +.fa-heartbeat::before { + content: "\f21e"; } + +.fa-people-carry-box::before { + content: "\f4ce"; } + +.fa-people-carry::before { + content: "\f4ce"; } + +.fa-temperature-high::before { + content: "\f769"; } + +.fa-microchip::before { + content: "\f2db"; } + +.fa-crown::before { + content: "\f521"; } + +.fa-weight-hanging::before { + content: "\f5cd"; } + +.fa-xmarks-lines::before { + content: "\e59a"; } + +.fa-file-prescription::before { + content: "\f572"; } + +.fa-weight-scale::before { + content: "\f496"; } + +.fa-weight::before { + content: "\f496"; } + +.fa-user-group::before { + content: "\f500"; } + +.fa-user-friends::before { + content: "\f500"; } + +.fa-arrow-up-a-z::before { + content: "\f15e"; } + +.fa-sort-alpha-up::before { + content: "\f15e"; } + +.fa-chess-knight::before { + content: "\f441"; } + +.fa-face-laugh-squint::before { + content: "\f59b"; } + +.fa-laugh-squint::before { + content: "\f59b"; } + +.fa-wheelchair::before { + content: "\f193"; } + +.fa-circle-arrow-up::before { + content: "\f0aa"; } + +.fa-arrow-circle-up::before { + content: "\f0aa"; } + +.fa-toggle-on::before { + content: "\f205"; } + +.fa-person-walking::before { + content: "\f554"; } + +.fa-walking::before { + content: "\f554"; } + +.fa-l::before { + content: "\4c"; } + +.fa-fire::before { + content: "\f06d"; } + +.fa-bed-pulse::before { + content: "\f487"; } + +.fa-procedures::before { + content: "\f487"; } + +.fa-shuttle-space::before { + content: "\f197"; } + +.fa-space-shuttle::before { + content: "\f197"; } + +.fa-face-laugh::before { + content: "\f599"; } + +.fa-laugh::before { + content: "\f599"; } + +.fa-folder-open::before { + content: "\f07c"; } + +.fa-heart-circle-plus::before { + content: "\e500"; } + +.fa-code-fork::before { + content: "\e13b"; } + +.fa-city::before { + content: "\f64f"; } + +.fa-microphone-lines::before { + content: "\f3c9"; } + +.fa-microphone-alt::before { + content: "\f3c9"; } + +.fa-pepper-hot::before { + content: "\f816"; } + +.fa-unlock::before { + content: "\f09c"; } + +.fa-colon-sign::before { + content: "\e140"; } + +.fa-headset::before { + content: "\f590"; } + +.fa-store-slash::before { + content: "\e071"; } + +.fa-road-circle-xmark::before { + content: "\e566"; } + +.fa-user-minus::before { + content: "\f503"; } + +.fa-mars-stroke-up::before { + content: "\f22a"; } + +.fa-mars-stroke-v::before { + content: "\f22a"; } + +.fa-champagne-glasses::before { + content: "\f79f"; } + +.fa-glass-cheers::before { + content: "\f79f"; } + +.fa-clipboard::before { + content: "\f328"; } + +.fa-house-circle-exclamation::before { + content: "\e50a"; } + +.fa-file-arrow-up::before { + content: "\f574"; } + +.fa-file-upload::before { + content: "\f574"; } + +.fa-wifi::before { + content: "\f1eb"; } + +.fa-wifi-3::before { + content: "\f1eb"; } + +.fa-wifi-strong::before { + content: "\f1eb"; } + +.fa-bath::before { + content: "\f2cd"; } + +.fa-bathtub::before { + content: "\f2cd"; } + +.fa-underline::before { + content: "\f0cd"; } + +.fa-user-pen::before { + content: "\f4ff"; } + +.fa-user-edit::before { + content: "\f4ff"; } + +.fa-signature::before { + content: "\f5b7"; } + +.fa-stroopwafel::before { + content: "\f551"; } + +.fa-bold::before { + content: "\f032"; } + +.fa-anchor-lock::before { + content: "\e4ad"; } + +.fa-building-ngo::before { + content: "\e4d7"; } + +.fa-manat-sign::before { + content: "\e1d5"; } + +.fa-not-equal::before { + content: "\f53e"; } + +.fa-border-top-left::before { + content: "\f853"; } + +.fa-border-style::before { + content: "\f853"; } + +.fa-map-location-dot::before { + content: "\f5a0"; } + +.fa-map-marked-alt::before { + content: "\f5a0"; } + +.fa-jedi::before { + content: "\f669"; } + +.fa-square-poll-vertical::before { + content: "\f681"; } + +.fa-poll::before { + content: "\f681"; } + +.fa-mug-hot::before { + content: "\f7b6"; } + +.fa-car-battery::before { + content: "\f5df"; } + +.fa-battery-car::before { + content: "\f5df"; } + +.fa-gift::before { + content: "\f06b"; } + +.fa-dice-two::before { + content: "\f528"; } + +.fa-chess-queen::before { + content: "\f445"; } + +.fa-glasses::before { + content: "\f530"; } + +.fa-chess-board::before { + content: "\f43c"; } + +.fa-building-circle-check::before { + content: "\e4d2"; } + +.fa-person-chalkboard::before { + content: "\e53d"; } + +.fa-mars-stroke-right::before { + content: "\f22b"; } + +.fa-mars-stroke-h::before { + content: "\f22b"; } + +.fa-hand-back-fist::before { + content: "\f255"; } + +.fa-hand-rock::before { + content: "\f255"; } + +.fa-square-caret-up::before { + content: "\f151"; } + +.fa-caret-square-up::before { + content: "\f151"; } + +.fa-cloud-showers-water::before { + content: "\e4e4"; } + +.fa-chart-bar::before { + content: "\f080"; } + +.fa-bar-chart::before { + content: "\f080"; } + +.fa-hands-bubbles::before { + content: "\e05e"; } + +.fa-hands-wash::before { + content: "\e05e"; } + +.fa-less-than-equal::before { + content: "\f537"; } + +.fa-train::before { + content: "\f238"; } + +.fa-eye-low-vision::before { + content: "\f2a8"; } + +.fa-low-vision::before { + content: "\f2a8"; } + +.fa-crow::before { + content: "\f520"; } + +.fa-sailboat::before { + content: "\e445"; } + +.fa-window-restore::before { + content: "\f2d2"; } + +.fa-square-plus::before { + content: "\f0fe"; } + +.fa-plus-square::before { + content: "\f0fe"; } + +.fa-torii-gate::before { + content: "\f6a1"; } + +.fa-frog::before { + content: "\f52e"; } + +.fa-bucket::before { + content: "\e4cf"; } + +.fa-image::before { + content: "\f03e"; } + +.fa-microphone::before { + content: "\f130"; } + +.fa-cow::before { + content: "\f6c8"; } + +.fa-caret-up::before { + content: "\f0d8"; } + +.fa-screwdriver::before { + content: "\f54a"; } + +.fa-folder-closed::before { + content: "\e185"; } + +.fa-house-tsunami::before { + content: "\e515"; } + +.fa-square-nfi::before { + content: "\e576"; } + +.fa-arrow-up-from-ground-water::before { + content: "\e4b5"; } + +.fa-martini-glass::before { + content: "\f57b"; } + +.fa-glass-martini-alt::before { + content: "\f57b"; } + +.fa-rotate-left::before { + content: "\f2ea"; } + +.fa-rotate-back::before { + content: "\f2ea"; } + +.fa-rotate-backward::before { + content: "\f2ea"; } + +.fa-undo-alt::before { + content: "\f2ea"; } + +.fa-table-columns::before { + content: "\f0db"; } + +.fa-columns::before { + content: "\f0db"; } + +.fa-lemon::before { + content: "\f094"; } + +.fa-head-side-mask::before { + content: "\e063"; } + +.fa-handshake::before { + content: "\f2b5"; } + +.fa-gem::before { + content: "\f3a5"; } + +.fa-dolly::before { + content: "\f472"; } + +.fa-dolly-box::before { + content: "\f472"; } + +.fa-smoking::before { + content: "\f48d"; } + +.fa-minimize::before { + content: "\f78c"; } + +.fa-compress-arrows-alt::before { + content: "\f78c"; } + +.fa-monument::before { + content: "\f5a6"; } + +.fa-snowplow::before { + content: "\f7d2"; } + +.fa-angles-right::before { + content: "\f101"; } + +.fa-angle-double-right::before { + content: "\f101"; } + +.fa-cannabis::before { + content: "\f55f"; } + +.fa-circle-play::before { + content: "\f144"; } + +.fa-play-circle::before { + content: "\f144"; } + +.fa-tablets::before { + content: "\f490"; } + +.fa-ethernet::before { + content: "\f796"; } + +.fa-euro-sign::before { + content: "\f153"; } + +.fa-eur::before { + content: "\f153"; } + +.fa-euro::before { + content: "\f153"; } + +.fa-chair::before { + content: "\f6c0"; } + +.fa-circle-check::before { + content: "\f058"; } + +.fa-check-circle::before { + content: "\f058"; } + +.fa-circle-stop::before { + content: "\f28d"; } + +.fa-stop-circle::before { + content: "\f28d"; } + +.fa-compass-drafting::before { + content: "\f568"; } + +.fa-drafting-compass::before { + content: "\f568"; } + +.fa-plate-wheat::before { + content: "\e55a"; } + +.fa-icicles::before { + content: "\f7ad"; } + +.fa-person-shelter::before { + content: "\e54f"; } + +.fa-neuter::before { + content: "\f22c"; } + +.fa-id-badge::before { + content: "\f2c1"; } + +.fa-marker::before { + content: "\f5a1"; } + +.fa-face-laugh-beam::before { + content: "\f59a"; } + +.fa-laugh-beam::before { + content: "\f59a"; } + +.fa-helicopter-symbol::before { + content: "\e502"; } + +.fa-universal-access::before { + content: "\f29a"; } + +.fa-circle-chevron-up::before { + content: "\f139"; } + +.fa-chevron-circle-up::before { + content: "\f139"; } + +.fa-lari-sign::before { + content: "\e1c8"; } + +.fa-volcano::before { + content: "\f770"; } + +.fa-person-walking-dashed-line-arrow-right::before { + content: "\e553"; } + +.fa-sterling-sign::before { + content: "\f154"; } + +.fa-gbp::before { + content: "\f154"; } + +.fa-pound-sign::before { + content: "\f154"; } + +.fa-viruses::before { + content: "\e076"; } + +.fa-square-person-confined::before { + content: "\e577"; } + +.fa-user-tie::before { + content: "\f508"; } + +.fa-arrow-down-long::before { + content: "\f175"; } + +.fa-long-arrow-down::before { + content: "\f175"; } + +.fa-tent-arrow-down-to-line::before { + content: "\e57e"; } + +.fa-certificate::before { + content: "\f0a3"; } + +.fa-reply-all::before { + content: "\f122"; } + +.fa-mail-reply-all::before { + content: "\f122"; } + +.fa-suitcase::before { + content: "\f0f2"; } + +.fa-person-skating::before { + content: "\f7c5"; } + +.fa-skating::before { + content: "\f7c5"; } + +.fa-filter-circle-dollar::before { + content: "\f662"; } + +.fa-funnel-dollar::before { + content: "\f662"; } + +.fa-camera-retro::before { + content: "\f083"; } + +.fa-circle-arrow-down::before { + content: "\f0ab"; } + +.fa-arrow-circle-down::before { + content: "\f0ab"; } + +.fa-file-import::before { + content: "\f56f"; } + +.fa-arrow-right-to-file::before { + content: "\f56f"; } + +.fa-square-arrow-up-right::before { + content: "\f14c"; } + +.fa-external-link-square::before { + content: "\f14c"; } + +.fa-box-open::before { + content: "\f49e"; } + +.fa-scroll::before { + content: "\f70e"; } + +.fa-spa::before { + content: "\f5bb"; } + +.fa-location-pin-lock::before { + content: "\e51f"; } + +.fa-pause::before { + content: "\f04c"; } + +.fa-hill-avalanche::before { + content: "\e507"; } + +.fa-temperature-empty::before { + content: "\f2cb"; } + +.fa-temperature-0::before { + content: "\f2cb"; } + +.fa-thermometer-0::before { + content: "\f2cb"; } + +.fa-thermometer-empty::before { + content: "\f2cb"; } + +.fa-bomb::before { + content: "\f1e2"; } + +.fa-registered::before { + content: "\f25d"; } + +.fa-address-card::before { + content: "\f2bb"; } + +.fa-contact-card::before { + content: "\f2bb"; } + +.fa-vcard::before { + content: "\f2bb"; } + +.fa-scale-unbalanced-flip::before { + content: "\f516"; } + +.fa-balance-scale-right::before { + content: "\f516"; } + +.fa-subscript::before { + content: "\f12c"; } + +.fa-diamond-turn-right::before { + content: "\f5eb"; } + +.fa-directions::before { + content: "\f5eb"; } + +.fa-burst::before { + content: "\e4dc"; } + +.fa-house-laptop::before { + content: "\e066"; } + +.fa-laptop-house::before { + content: "\e066"; } + +.fa-face-tired::before { + content: "\f5c8"; } + +.fa-tired::before { + content: "\f5c8"; } + +.fa-money-bills::before { + content: "\e1f3"; } + +.fa-smog::before { + content: "\f75f"; } + +.fa-crutch::before { + content: "\f7f7"; } + +.fa-cloud-arrow-up::before { + content: "\f0ee"; } + +.fa-cloud-upload::before { + content: "\f0ee"; } + +.fa-cloud-upload-alt::before { + content: "\f0ee"; } + +.fa-palette::before { + content: "\f53f"; } + +.fa-arrows-turn-right::before { + content: "\e4c0"; } + +.fa-vest::before { + content: "\e085"; } + +.fa-ferry::before { + content: "\e4ea"; } + +.fa-arrows-down-to-people::before { + content: "\e4b9"; } + +.fa-seedling::before { + content: "\f4d8"; } + +.fa-sprout::before { + content: "\f4d8"; } + +.fa-left-right::before { + content: "\f337"; } + +.fa-arrows-alt-h::before { + content: "\f337"; } + +.fa-boxes-packing::before { + content: "\e4c7"; } + +.fa-circle-arrow-left::before { + content: "\f0a8"; } + +.fa-arrow-circle-left::before { + content: "\f0a8"; } + +.fa-group-arrows-rotate::before { + content: "\e4f6"; } + +.fa-bowl-food::before { + content: "\e4c6"; } + +.fa-candy-cane::before { + content: "\f786"; } + +.fa-arrow-down-wide-short::before { + content: "\f160"; } + +.fa-sort-amount-asc::before { + content: "\f160"; } + +.fa-sort-amount-down::before { + content: "\f160"; } + +.fa-cloud-bolt::before { + content: "\f76c"; } + +.fa-thunderstorm::before { + content: "\f76c"; } + +.fa-text-slash::before { + content: "\f87d"; } + +.fa-remove-format::before { + content: "\f87d"; } + +.fa-face-smile-wink::before { + content: "\f4da"; } + +.fa-smile-wink::before { + content: "\f4da"; } + +.fa-file-word::before { + content: "\f1c2"; } + +.fa-file-powerpoint::before { + content: "\f1c4"; } + +.fa-arrows-left-right::before { + content: "\f07e"; } + +.fa-arrows-h::before { + content: "\f07e"; } + +.fa-house-lock::before { + content: "\e510"; } + +.fa-cloud-arrow-down::before { + content: "\f0ed"; } + +.fa-cloud-download::before { + content: "\f0ed"; } + +.fa-cloud-download-alt::before { + content: "\f0ed"; } + +.fa-children::before { + content: "\e4e1"; } + +.fa-chalkboard::before { + content: "\f51b"; } + +.fa-blackboard::before { + content: "\f51b"; } + +.fa-user-large-slash::before { + content: "\f4fa"; } + +.fa-user-alt-slash::before { + content: "\f4fa"; } + +.fa-envelope-open::before { + content: "\f2b6"; } + +.fa-handshake-simple-slash::before { + content: "\e05f"; } + +.fa-handshake-alt-slash::before { + content: "\e05f"; } + +.fa-mattress-pillow::before { + content: "\e525"; } + +.fa-guarani-sign::before { + content: "\e19a"; } + +.fa-arrows-rotate::before { + content: "\f021"; } + +.fa-refresh::before { + content: "\f021"; } + +.fa-sync::before { + content: "\f021"; } + +.fa-fire-extinguisher::before { + content: "\f134"; } + +.fa-cruzeiro-sign::before { + content: "\e152"; } + +.fa-greater-than-equal::before { + content: "\f532"; } + +.fa-shield-halved::before { + content: "\f3ed"; } + +.fa-shield-alt::before { + content: "\f3ed"; } + +.fa-book-atlas::before { + content: "\f558"; } + +.fa-atlas::before { + content: "\f558"; } + +.fa-virus::before { + content: "\e074"; } + +.fa-envelope-circle-check::before { + content: "\e4e8"; } + +.fa-layer-group::before { + content: "\f5fd"; } + +.fa-arrows-to-dot::before { + content: "\e4be"; } + +.fa-archway::before { + content: "\f557"; } + +.fa-heart-circle-check::before { + content: "\e4fd"; } + +.fa-house-chimney-crack::before { + content: "\f6f1"; } + +.fa-house-damage::before { + content: "\f6f1"; } + +.fa-file-zipper::before { + content: "\f1c6"; } + +.fa-file-archive::before { + content: "\f1c6"; } + +.fa-square::before { + content: "\f0c8"; } + +.fa-martini-glass-empty::before { + content: "\f000"; } + +.fa-glass-martini::before { + content: "\f000"; } + +.fa-couch::before { + content: "\f4b8"; } + +.fa-cedi-sign::before { + content: "\e0df"; } + +.fa-italic::before { + content: "\f033"; } + +.fa-church::before { + content: "\f51d"; } + +.fa-comments-dollar::before { + content: "\f653"; } + +.fa-democrat::before { + content: "\f747"; } + +.fa-z::before { + content: "\5a"; } + +.fa-person-skiing::before { + content: "\f7c9"; } + +.fa-skiing::before { + content: "\f7c9"; } + +.fa-road-lock::before { + content: "\e567"; } + +.fa-a::before { + content: "\41"; } + +.fa-temperature-arrow-down::before { + content: "\e03f"; } + +.fa-temperature-down::before { + content: "\e03f"; } + +.fa-feather-pointed::before { + content: "\f56b"; } + +.fa-feather-alt::before { + content: "\f56b"; } + +.fa-p::before { + content: "\50"; } + +.fa-snowflake::before { + content: "\f2dc"; } + +.fa-newspaper::before { + content: "\f1ea"; } + +.fa-rectangle-ad::before { + content: "\f641"; } + +.fa-ad::before { + content: "\f641"; } + +.fa-circle-arrow-right::before { + content: "\f0a9"; } + +.fa-arrow-circle-right::before { + content: "\f0a9"; } + +.fa-filter-circle-xmark::before { + content: "\e17b"; } + +.fa-locust::before { + content: "\e520"; } + +.fa-sort::before { + content: "\f0dc"; } + +.fa-unsorted::before { + content: "\f0dc"; } + +.fa-list-ol::before { + content: "\f0cb"; } + +.fa-list-1-2::before { + content: "\f0cb"; } + +.fa-list-numeric::before { + content: "\f0cb"; } + +.fa-person-dress-burst::before { + content: "\e544"; } + +.fa-money-check-dollar::before { + content: "\f53d"; } + +.fa-money-check-alt::before { + content: "\f53d"; } + +.fa-vector-square::before { + content: "\f5cb"; } + +.fa-bread-slice::before { + content: "\f7ec"; } + +.fa-language::before { + content: "\f1ab"; } + +.fa-face-kiss-wink-heart::before { + content: "\f598"; } + +.fa-kiss-wink-heart::before { + content: "\f598"; } + +.fa-filter::before { + content: "\f0b0"; } + +.fa-question::before { + content: "\3f"; } + +.fa-file-signature::before { + content: "\f573"; } + +.fa-up-down-left-right::before { + content: "\f0b2"; } + +.fa-arrows-alt::before { + content: "\f0b2"; } + +.fa-house-chimney-user::before { + content: "\e065"; } + +.fa-hand-holding-heart::before { + content: "\f4be"; } + +.fa-puzzle-piece::before { + content: "\f12e"; } + +.fa-money-check::before { + content: "\f53c"; } + +.fa-star-half-stroke::before { + content: "\f5c0"; } + +.fa-star-half-alt::before { + content: "\f5c0"; } + +.fa-code::before { + content: "\f121"; } + +.fa-whiskey-glass::before { + content: "\f7a0"; } + +.fa-glass-whiskey::before { + content: "\f7a0"; } + +.fa-building-circle-exclamation::before { + content: "\e4d3"; } + +.fa-magnifying-glass-chart::before { + content: "\e522"; } + +.fa-arrow-up-right-from-square::before { + content: "\f08e"; } + +.fa-external-link::before { + content: "\f08e"; } + +.fa-cubes-stacked::before { + content: "\e4e6"; } + +.fa-won-sign::before { + content: "\f159"; } + +.fa-krw::before { + content: "\f159"; } + +.fa-won::before { + content: "\f159"; } + +.fa-virus-covid::before { + content: "\e4a8"; } + +.fa-austral-sign::before { + content: "\e0a9"; } + +.fa-f::before { + content: "\46"; } + +.fa-leaf::before { + content: "\f06c"; } + +.fa-road::before { + content: "\f018"; } + +.fa-taxi::before { + content: "\f1ba"; } + +.fa-cab::before { + content: "\f1ba"; } + +.fa-person-circle-plus::before { + content: "\e541"; } + +.fa-chart-pie::before { + content: "\f200"; } + +.fa-pie-chart::before { + content: "\f200"; } + +.fa-bolt-lightning::before { + content: "\e0b7"; } + +.fa-sack-xmark::before { + content: "\e56a"; } + +.fa-file-excel::before { + content: "\f1c3"; } + +.fa-file-contract::before { + content: "\f56c"; } + +.fa-fish-fins::before { + content: "\e4f2"; } + +.fa-building-flag::before { + content: "\e4d5"; } + +.fa-face-grin-beam::before { + content: "\f582"; } + +.fa-grin-beam::before { + content: "\f582"; } + +.fa-object-ungroup::before { + content: "\f248"; } + +.fa-poop::before { + content: "\f619"; } + +.fa-location-pin::before { + content: "\f041"; } + +.fa-map-marker::before { + content: "\f041"; } + +.fa-kaaba::before { + content: "\f66b"; } + +.fa-toilet-paper::before { + content: "\f71e"; } + +.fa-helmet-safety::before { + content: "\f807"; } + +.fa-hard-hat::before { + content: "\f807"; } + +.fa-hat-hard::before { + content: "\f807"; } + +.fa-eject::before { + content: "\f052"; } + +.fa-circle-right::before { + content: "\f35a"; } + +.fa-arrow-alt-circle-right::before { + content: "\f35a"; } + +.fa-plane-circle-check::before { + content: "\e555"; } + +.fa-face-rolling-eyes::before { + content: "\f5a5"; } + +.fa-meh-rolling-eyes::before { + content: "\f5a5"; } + +.fa-object-group::before { + content: "\f247"; } + +.fa-chart-line::before { + content: "\f201"; } + +.fa-line-chart::before { + content: "\f201"; } + +.fa-mask-ventilator::before { + content: "\e524"; } + +.fa-arrow-right::before { + content: "\f061"; } + +.fa-signs-post::before { + content: "\f277"; } + +.fa-map-signs::before { + content: "\f277"; } + +.fa-cash-register::before { + content: "\f788"; } + +.fa-person-circle-question::before { + content: "\e542"; } + +.fa-h::before { + content: "\48"; } + +.fa-tarp::before { + content: "\e57b"; } + +.fa-screwdriver-wrench::before { + content: "\f7d9"; } + +.fa-tools::before { + content: "\f7d9"; } + +.fa-arrows-to-eye::before { + content: "\e4bf"; } + +.fa-plug-circle-bolt::before { + content: "\e55b"; } + +.fa-heart::before { + content: "\f004"; } + +.fa-mars-and-venus::before { + content: "\f224"; } + +.fa-house-user::before { + content: "\e1b0"; } + +.fa-home-user::before { + content: "\e1b0"; } + +.fa-dumpster-fire::before { + content: "\f794"; } + +.fa-house-crack::before { + content: "\e3b1"; } + +.fa-martini-glass-citrus::before { + content: "\f561"; } + +.fa-cocktail::before { + content: "\f561"; } + +.fa-face-surprise::before { + content: "\f5c2"; } + +.fa-surprise::before { + content: "\f5c2"; } + +.fa-bottle-water::before { + content: "\e4c5"; } + +.fa-circle-pause::before { + content: "\f28b"; } + +.fa-pause-circle::before { + content: "\f28b"; } + +.fa-toilet-paper-slash::before { + content: "\e072"; } + +.fa-apple-whole::before { + content: "\f5d1"; } + +.fa-apple-alt::before { + content: "\f5d1"; } + +.fa-kitchen-set::before { + content: "\e51a"; } + +.fa-r::before { + content: "\52"; } + +.fa-temperature-quarter::before { + content: "\f2ca"; } + +.fa-temperature-1::before { + content: "\f2ca"; } + +.fa-thermometer-1::before { + content: "\f2ca"; } + +.fa-thermometer-quarter::before { + content: "\f2ca"; } + +.fa-cube::before { + content: "\f1b2"; } + +.fa-bitcoin-sign::before { + content: "\e0b4"; } + +.fa-shield-dog::before { + content: "\e573"; } + +.fa-solar-panel::before { + content: "\f5ba"; } + +.fa-lock-open::before { + content: "\f3c1"; } + +.fa-elevator::before { + content: "\e16d"; } + +.fa-money-bill-transfer::before { + content: "\e528"; } + +.fa-money-bill-trend-up::before { + content: "\e529"; } + +.fa-house-flood-water-circle-arrow-right::before { + content: "\e50f"; } + +.fa-square-poll-horizontal::before { + content: "\f682"; } + +.fa-poll-h::before { + content: "\f682"; } + +.fa-circle::before { + content: "\f111"; } + +.fa-backward-fast::before { + content: "\f049"; } + +.fa-fast-backward::before { + content: "\f049"; } + +.fa-recycle::before { + content: "\f1b8"; } + +.fa-user-astronaut::before { + content: "\f4fb"; } + +.fa-plane-slash::before { + content: "\e069"; } + +.fa-trademark::before { + content: "\f25c"; } + +.fa-basketball::before { + content: "\f434"; } + +.fa-basketball-ball::before { + content: "\f434"; } + +.fa-satellite-dish::before { + content: "\f7c0"; } + +.fa-circle-up::before { + content: "\f35b"; } + +.fa-arrow-alt-circle-up::before { + content: "\f35b"; } + +.fa-mobile-screen-button::before { + content: "\f3cd"; } + +.fa-mobile-alt::before { + content: "\f3cd"; } + +.fa-volume-high::before { + content: "\f028"; } + +.fa-volume-up::before { + content: "\f028"; } + +.fa-users-rays::before { + content: "\e593"; } + +.fa-wallet::before { + content: "\f555"; } + +.fa-clipboard-check::before { + content: "\f46c"; } + +.fa-file-audio::before { + content: "\f1c7"; } + +.fa-burger::before { + content: "\f805"; } + +.fa-hamburger::before { + content: "\f805"; } + +.fa-wrench::before { + content: "\f0ad"; } + +.fa-bugs::before { + content: "\e4d0"; } + +.fa-rupee-sign::before { + content: "\f156"; } + +.fa-rupee::before { + content: "\f156"; } + +.fa-file-image::before { + content: "\f1c5"; } + +.fa-circle-question::before { + content: "\f059"; } + +.fa-question-circle::before { + content: "\f059"; } + +.fa-plane-departure::before { + content: "\f5b0"; } + +.fa-handshake-slash::before { + content: "\e060"; } + +.fa-book-bookmark::before { + content: "\e0bb"; } + +.fa-code-branch::before { + content: "\f126"; } + +.fa-hat-cowboy::before { + content: "\f8c0"; } + +.fa-bridge::before { + content: "\e4c8"; } + +.fa-phone-flip::before { + content: "\f879"; } + +.fa-phone-alt::before { + content: "\f879"; } + +.fa-truck-front::before { + content: "\e2b7"; } + +.fa-cat::before { + content: "\f6be"; } + +.fa-anchor-circle-exclamation::before { + content: "\e4ab"; } + +.fa-truck-field::before { + content: "\e58d"; } + +.fa-route::before { + content: "\f4d7"; } + +.fa-clipboard-question::before { + content: "\e4e3"; } + +.fa-panorama::before { + content: "\e209"; } + +.fa-comment-medical::before { + content: "\f7f5"; } + +.fa-teeth-open::before { + content: "\f62f"; } + +.fa-file-circle-minus::before { + content: "\e4ed"; } + +.fa-tags::before { + content: "\f02c"; } + +.fa-wine-glass::before { + content: "\f4e3"; } + +.fa-forward-fast::before { + content: "\f050"; } + +.fa-fast-forward::before { + content: "\f050"; } + +.fa-face-meh-blank::before { + content: "\f5a4"; } + +.fa-meh-blank::before { + content: "\f5a4"; } + +.fa-square-parking::before { + content: "\f540"; } + +.fa-parking::before { + content: "\f540"; } + +.fa-house-signal::before { + content: "\e012"; } + +.fa-bars-progress::before { + content: "\f828"; } + +.fa-tasks-alt::before { + content: "\f828"; } + +.fa-faucet-drip::before { + content: "\e006"; } + +.fa-cart-flatbed::before { + content: "\f474"; } + +.fa-dolly-flatbed::before { + content: "\f474"; } + +.fa-ban-smoking::before { + content: "\f54d"; } + +.fa-smoking-ban::before { + content: "\f54d"; } + +.fa-terminal::before { + content: "\f120"; } + +.fa-mobile-button::before { + content: "\f10b"; } + +.fa-house-medical-flag::before { + content: "\e514"; } + +.fa-basket-shopping::before { + content: "\f291"; } + +.fa-shopping-basket::before { + content: "\f291"; } + +.fa-tape::before { + content: "\f4db"; } + +.fa-bus-simple::before { + content: "\f55e"; } + +.fa-bus-alt::before { + content: "\f55e"; } + +.fa-eye::before { + content: "\f06e"; } + +.fa-face-sad-cry::before { + content: "\f5b3"; } + +.fa-sad-cry::before { + content: "\f5b3"; } + +.fa-audio-description::before { + content: "\f29e"; } + +.fa-person-military-to-person::before { + content: "\e54c"; } + +.fa-file-shield::before { + content: "\e4f0"; } + +.fa-user-slash::before { + content: "\f506"; } + +.fa-pen::before { + content: "\f304"; } + +.fa-tower-observation::before { + content: "\e586"; } + +.fa-file-code::before { + content: "\f1c9"; } + +.fa-signal::before { + content: "\f012"; } + +.fa-signal-5::before { + content: "\f012"; } + +.fa-signal-perfect::before { + content: "\f012"; } + +.fa-bus::before { + content: "\f207"; } + +.fa-heart-circle-xmark::before { + content: "\e501"; } + +.fa-house-chimney::before { + content: "\e3af"; } + +.fa-home-lg::before { + content: "\e3af"; } + +.fa-window-maximize::before { + content: "\f2d0"; } + +.fa-face-frown::before { + content: "\f119"; } + +.fa-frown::before { + content: "\f119"; } + +.fa-prescription::before { + content: "\f5b1"; } + +.fa-shop::before { + content: "\f54f"; } + +.fa-store-alt::before { + content: "\f54f"; } + +.fa-floppy-disk::before { + content: "\f0c7"; } + +.fa-save::before { + content: "\f0c7"; } + +.fa-vihara::before { + content: "\f6a7"; } + +.fa-scale-unbalanced::before { + content: "\f515"; } + +.fa-balance-scale-left::before { + content: "\f515"; } + +.fa-sort-up::before { + content: "\f0de"; } + +.fa-sort-asc::before { + content: "\f0de"; } + +.fa-comment-dots::before { + content: "\f4ad"; } + +.fa-commenting::before { + content: "\f4ad"; } + +.fa-plant-wilt::before { + content: "\e5aa"; } + +.fa-diamond::before { + content: "\f219"; } + +.fa-face-grin-squint::before { + content: "\f585"; } + +.fa-grin-squint::before { + content: "\f585"; } + +.fa-hand-holding-dollar::before { + content: "\f4c0"; } + +.fa-hand-holding-usd::before { + content: "\f4c0"; } + +.fa-bacterium::before { + content: "\e05a"; } + +.fa-hand-pointer::before { + content: "\f25a"; } + +.fa-drum-steelpan::before { + content: "\f56a"; } + +.fa-hand-scissors::before { + content: "\f257"; } + +.fa-hands-praying::before { + content: "\f684"; } + +.fa-praying-hands::before { + content: "\f684"; } + +.fa-arrow-rotate-right::before { + content: "\f01e"; } + +.fa-arrow-right-rotate::before { + content: "\f01e"; } + +.fa-arrow-rotate-forward::before { + content: "\f01e"; } + +.fa-redo::before { + content: "\f01e"; } + +.fa-biohazard::before { + content: "\f780"; } + +.fa-location-crosshairs::before { + content: "\f601"; } + +.fa-location::before { + content: "\f601"; } + +.fa-mars-double::before { + content: "\f227"; } + +.fa-child-dress::before { + content: "\e59c"; } + +.fa-users-between-lines::before { + content: "\e591"; } + +.fa-lungs-virus::before { + content: "\e067"; } + +.fa-face-grin-tears::before { + content: "\f588"; } + +.fa-grin-tears::before { + content: "\f588"; } + +.fa-phone::before { + content: "\f095"; } + +.fa-calendar-xmark::before { + content: "\f273"; } + +.fa-calendar-times::before { + content: "\f273"; } + +.fa-child-reaching::before { + content: "\e59d"; } + +.fa-head-side-virus::before { + content: "\e064"; } + +.fa-user-gear::before { + content: "\f4fe"; } + +.fa-user-cog::before { + content: "\f4fe"; } + +.fa-arrow-up-1-9::before { + content: "\f163"; } + +.fa-sort-numeric-up::before { + content: "\f163"; } + +.fa-door-closed::before { + content: "\f52a"; } + +.fa-shield-virus::before { + content: "\e06c"; } + +.fa-dice-six::before { + content: "\f526"; } + +.fa-mosquito-net::before { + content: "\e52c"; } + +.fa-bridge-water::before { + content: "\e4ce"; } + +.fa-person-booth::before { + content: "\f756"; } + +.fa-text-width::before { + content: "\f035"; } + +.fa-hat-wizard::before { + content: "\f6e8"; } + +.fa-pen-fancy::before { + content: "\f5ac"; } + +.fa-person-digging::before { + content: "\f85e"; } + +.fa-digging::before { + content: "\f85e"; } + +.fa-trash::before { + content: "\f1f8"; } + +.fa-gauge-simple::before { + content: "\f629"; } + +.fa-gauge-simple-med::before { + content: "\f629"; } + +.fa-tachometer-average::before { + content: "\f629"; } + +.fa-book-medical::before { + content: "\f7e6"; } + +.fa-poo::before { + content: "\f2fe"; } + +.fa-quote-right::before { + content: "\f10e"; } + +.fa-quote-right-alt::before { + content: "\f10e"; } + +.fa-shirt::before { + content: "\f553"; } + +.fa-t-shirt::before { + content: "\f553"; } + +.fa-tshirt::before { + content: "\f553"; } + +.fa-cubes::before { + content: "\f1b3"; } + +.fa-divide::before { + content: "\f529"; } + +.fa-tenge-sign::before { + content: "\f7d7"; } + +.fa-tenge::before { + content: "\f7d7"; } + +.fa-headphones::before { + content: "\f025"; } + +.fa-hands-holding::before { + content: "\f4c2"; } + +.fa-hands-clapping::before { + content: "\e1a8"; } + +.fa-republican::before { + content: "\f75e"; } + +.fa-arrow-left::before { + content: "\f060"; } + +.fa-person-circle-xmark::before { + content: "\e543"; } + +.fa-ruler::before { + content: "\f545"; } + +.fa-align-left::before { + content: "\f036"; } + +.fa-dice-d6::before { + content: "\f6d1"; } + +.fa-restroom::before { + content: "\f7bd"; } + +.fa-j::before { + content: "\4a"; } + +.fa-users-viewfinder::before { + content: "\e595"; } + +.fa-file-video::before { + content: "\f1c8"; } + +.fa-up-right-from-square::before { + content: "\f35d"; } + +.fa-external-link-alt::before { + content: "\f35d"; } + +.fa-table-cells::before { + content: "\f00a"; } + +.fa-th::before { + content: "\f00a"; } + +.fa-file-pdf::before { + content: "\f1c1"; } + +.fa-book-bible::before { + content: "\f647"; } + +.fa-bible::before { + content: "\f647"; } + +.fa-o::before { + content: "\4f"; } + +.fa-suitcase-medical::before { + content: "\f0fa"; } + +.fa-medkit::before { + content: "\f0fa"; } + +.fa-user-secret::before { + content: "\f21b"; } + +.fa-otter::before { + content: "\f700"; } + +.fa-person-dress::before { + content: "\f182"; } + +.fa-female::before { + content: "\f182"; } + +.fa-comment-dollar::before { + content: "\f651"; } + +.fa-business-time::before { + content: "\f64a"; } + +.fa-briefcase-clock::before { + content: "\f64a"; } + +.fa-table-cells-large::before { + content: "\f009"; } + +.fa-th-large::before { + content: "\f009"; } + +.fa-book-tanakh::before { + content: "\f827"; } + +.fa-tanakh::before { + content: "\f827"; } + +.fa-phone-volume::before { + content: "\f2a0"; } + +.fa-volume-control-phone::before { + content: "\f2a0"; } + +.fa-hat-cowboy-side::before { + content: "\f8c1"; } + +.fa-clipboard-user::before { + content: "\f7f3"; } + +.fa-child::before { + content: "\f1ae"; } + +.fa-lira-sign::before { + content: "\f195"; } + +.fa-satellite::before { + content: "\f7bf"; } + +.fa-plane-lock::before { + content: "\e558"; } + +.fa-tag::before { + content: "\f02b"; } + +.fa-comment::before { + content: "\f075"; } + +.fa-cake-candles::before { + content: "\f1fd"; } + +.fa-birthday-cake::before { + content: "\f1fd"; } + +.fa-cake::before { + content: "\f1fd"; } + +.fa-envelope::before { + content: "\f0e0"; } + +.fa-angles-up::before { + content: "\f102"; } + +.fa-angle-double-up::before { + content: "\f102"; } + +.fa-paperclip::before { + content: "\f0c6"; } + +.fa-arrow-right-to-city::before { + content: "\e4b3"; } + +.fa-ribbon::before { + content: "\f4d6"; } + +.fa-lungs::before { + content: "\f604"; } + +.fa-arrow-up-9-1::before { + content: "\f887"; } + +.fa-sort-numeric-up-alt::before { + content: "\f887"; } + +.fa-litecoin-sign::before { + content: "\e1d3"; } + +.fa-border-none::before { + content: "\f850"; } + +.fa-circle-nodes::before { + content: "\e4e2"; } + +.fa-parachute-box::before { + content: "\f4cd"; } + +.fa-indent::before { + content: "\f03c"; } + +.fa-truck-field-un::before { + content: "\e58e"; } + +.fa-hourglass::before { + content: "\f254"; } + +.fa-hourglass-empty::before { + content: "\f254"; } + +.fa-mountain::before { + content: "\f6fc"; } + +.fa-user-doctor::before { + content: "\f0f0"; } + +.fa-user-md::before { + content: "\f0f0"; } + +.fa-circle-info::before { + content: "\f05a"; } + +.fa-info-circle::before { + content: "\f05a"; } + +.fa-cloud-meatball::before { + content: "\f73b"; } + +.fa-camera::before { + content: "\f030"; } + +.fa-camera-alt::before { + content: "\f030"; } + +.fa-square-virus::before { + content: "\e578"; } + +.fa-meteor::before { + content: "\f753"; } + +.fa-car-on::before { + content: "\e4dd"; } + +.fa-sleigh::before { + content: "\f7cc"; } + +.fa-arrow-down-1-9::before { + content: "\f162"; } + +.fa-sort-numeric-asc::before { + content: "\f162"; } + +.fa-sort-numeric-down::before { + content: "\f162"; } + +.fa-hand-holding-droplet::before { + content: "\f4c1"; } + +.fa-hand-holding-water::before { + content: "\f4c1"; } + +.fa-water::before { + content: "\f773"; } + +.fa-calendar-check::before { + content: "\f274"; } + +.fa-braille::before { + content: "\f2a1"; } + +.fa-prescription-bottle-medical::before { + content: "\f486"; } + +.fa-prescription-bottle-alt::before { + content: "\f486"; } + +.fa-landmark::before { + content: "\f66f"; } + +.fa-truck::before { + content: "\f0d1"; } + +.fa-crosshairs::before { + content: "\f05b"; } + +.fa-person-cane::before { + content: "\e53c"; } + +.fa-tent::before { + content: "\e57d"; } + +.fa-vest-patches::before { + content: "\e086"; } + +.fa-check-double::before { + content: "\f560"; } + +.fa-arrow-down-a-z::before { + content: "\f15d"; } + +.fa-sort-alpha-asc::before { + content: "\f15d"; } + +.fa-sort-alpha-down::before { + content: "\f15d"; } + +.fa-money-bill-wheat::before { + content: "\e52a"; } + +.fa-cookie::before { + content: "\f563"; } + +.fa-arrow-rotate-left::before { + content: "\f0e2"; } + +.fa-arrow-left-rotate::before { + content: "\f0e2"; } + +.fa-arrow-rotate-back::before { + content: "\f0e2"; } + +.fa-arrow-rotate-backward::before { + content: "\f0e2"; } + +.fa-undo::before { + content: "\f0e2"; } + +.fa-hard-drive::before { + content: "\f0a0"; } + +.fa-hdd::before { + content: "\f0a0"; } + +.fa-face-grin-squint-tears::before { + content: "\f586"; } + +.fa-grin-squint-tears::before { + content: "\f586"; } + +.fa-dumbbell::before { + content: "\f44b"; } + +.fa-rectangle-list::before { + content: "\f022"; } + +.fa-list-alt::before { + content: "\f022"; } + +.fa-tarp-droplet::before { + content: "\e57c"; } + +.fa-house-medical-circle-check::before { + content: "\e511"; } + +.fa-person-skiing-nordic::before { + content: "\f7ca"; } + +.fa-skiing-nordic::before { + content: "\f7ca"; } + +.fa-calendar-plus::before { + content: "\f271"; } + +.fa-plane-arrival::before { + content: "\f5af"; } + +.fa-circle-left::before { + content: "\f359"; } + +.fa-arrow-alt-circle-left::before { + content: "\f359"; } + +.fa-train-subway::before { + content: "\f239"; } + +.fa-subway::before { + content: "\f239"; } + +.fa-chart-gantt::before { + content: "\e0e4"; } + +.fa-indian-rupee-sign::before { + content: "\e1bc"; } + +.fa-indian-rupee::before { + content: "\e1bc"; } + +.fa-inr::before { + content: "\e1bc"; } + +.fa-crop-simple::before { + content: "\f565"; } + +.fa-crop-alt::before { + content: "\f565"; } + +.fa-money-bill-1::before { + content: "\f3d1"; } + +.fa-money-bill-alt::before { + content: "\f3d1"; } + +.fa-left-long::before { + content: "\f30a"; } + +.fa-long-arrow-alt-left::before { + content: "\f30a"; } + +.fa-dna::before { + content: "\f471"; } + +.fa-virus-slash::before { + content: "\e075"; } + +.fa-minus::before { + content: "\f068"; } + +.fa-subtract::before { + content: "\f068"; } + +.fa-chess::before { + content: "\f439"; } + +.fa-arrow-left-long::before { + content: "\f177"; } + +.fa-long-arrow-left::before { + content: "\f177"; } + +.fa-plug-circle-check::before { + content: "\e55c"; } + +.fa-street-view::before { + content: "\f21d"; } + +.fa-franc-sign::before { + content: "\e18f"; } + +.fa-volume-off::before { + content: "\f026"; } + +.fa-hands-asl-interpreting::before { + content: "\f2a3"; } + +.fa-american-sign-language-interpreting::before { + content: "\f2a3"; } + +.fa-asl-interpreting::before { + content: "\f2a3"; } + +.fa-hands-american-sign-language-interpreting::before { + content: "\f2a3"; } + +.fa-gear::before { + content: "\f013"; } + +.fa-cog::before { + content: "\f013"; } + +.fa-droplet-slash::before { + content: "\f5c7"; } + +.fa-tint-slash::before { + content: "\f5c7"; } + +.fa-mosque::before { + content: "\f678"; } + +.fa-mosquito::before { + content: "\e52b"; } + +.fa-star-of-david::before { + content: "\f69a"; } + +.fa-person-military-rifle::before { + content: "\e54b"; } + +.fa-cart-shopping::before { + content: "\f07a"; } + +.fa-shopping-cart::before { + content: "\f07a"; } + +.fa-vials::before { + content: "\f493"; } + +.fa-plug-circle-plus::before { + content: "\e55f"; } + +.fa-place-of-worship::before { + content: "\f67f"; } + +.fa-grip-vertical::before { + content: "\f58e"; } + +.fa-arrow-turn-up::before { + content: "\f148"; } + +.fa-level-up::before { + content: "\f148"; } + +.fa-u::before { + content: "\55"; } + +.fa-square-root-variable::before { + content: "\f698"; } + +.fa-square-root-alt::before { + content: "\f698"; } + +.fa-clock::before { + content: "\f017"; } + +.fa-clock-four::before { + content: "\f017"; } + +.fa-backward-step::before { + content: "\f048"; } + +.fa-step-backward::before { + content: "\f048"; } + +.fa-pallet::before { + content: "\f482"; } + +.fa-faucet::before { + content: "\e005"; } + +.fa-baseball-bat-ball::before { + content: "\f432"; } + +.fa-s::before { + content: "\53"; } + +.fa-timeline::before { + content: "\e29c"; } + +.fa-keyboard::before { + content: "\f11c"; } + +.fa-caret-down::before { + content: "\f0d7"; } + +.fa-house-chimney-medical::before { + content: "\f7f2"; } + +.fa-clinic-medical::before { + content: "\f7f2"; } + +.fa-temperature-three-quarters::before { + content: "\f2c8"; } + +.fa-temperature-3::before { + content: "\f2c8"; } + +.fa-thermometer-3::before { + content: "\f2c8"; } + +.fa-thermometer-three-quarters::before { + content: "\f2c8"; } + +.fa-mobile-screen::before { + content: "\f3cf"; } + +.fa-mobile-android-alt::before { + content: "\f3cf"; } + +.fa-plane-up::before { + content: "\e22d"; } + +.fa-piggy-bank::before { + content: "\f4d3"; } + +.fa-battery-half::before { + content: "\f242"; } + +.fa-battery-3::before { + content: "\f242"; } + +.fa-mountain-city::before { + content: "\e52e"; } + +.fa-coins::before { + content: "\f51e"; } + +.fa-khanda::before { + content: "\f66d"; } + +.fa-sliders::before { + content: "\f1de"; } + +.fa-sliders-h::before { + content: "\f1de"; } + +.fa-folder-tree::before { + content: "\f802"; } + +.fa-network-wired::before { + content: "\f6ff"; } + +.fa-map-pin::before { + content: "\f276"; } + +.fa-hamsa::before { + content: "\f665"; } + +.fa-cent-sign::before { + content: "\e3f5"; } + +.fa-flask::before { + content: "\f0c3"; } + +.fa-person-pregnant::before { + content: "\e31e"; } + +.fa-wand-sparkles::before { + content: "\f72b"; } + +.fa-ellipsis-vertical::before { + content: "\f142"; } + +.fa-ellipsis-v::before { + content: "\f142"; } + +.fa-ticket::before { + content: "\f145"; } + +.fa-power-off::before { + content: "\f011"; } + +.fa-right-long::before { + content: "\f30b"; } + +.fa-long-arrow-alt-right::before { + content: "\f30b"; } + +.fa-flag-usa::before { + content: "\f74d"; } + +.fa-laptop-file::before { + content: "\e51d"; } + +.fa-tty::before { + content: "\f1e4"; } + +.fa-teletype::before { + content: "\f1e4"; } + +.fa-diagram-next::before { + content: "\e476"; } + +.fa-person-rifle::before { + content: "\e54e"; } + +.fa-house-medical-circle-exclamation::before { + content: "\e512"; } + +.fa-closed-captioning::before { + content: "\f20a"; } + +.fa-person-hiking::before { + content: "\f6ec"; } + +.fa-hiking::before { + content: "\f6ec"; } + +.fa-venus-double::before { + content: "\f226"; } + +.fa-images::before { + content: "\f302"; } + +.fa-calculator::before { + content: "\f1ec"; } + +.fa-people-pulling::before { + content: "\e535"; } + +.fa-n::before { + content: "\4e"; } + +.fa-cable-car::before { + content: "\f7da"; } + +.fa-tram::before { + content: "\f7da"; } + +.fa-cloud-rain::before { + content: "\f73d"; } + +.fa-building-circle-xmark::before { + content: "\e4d4"; } + +.fa-ship::before { + content: "\f21a"; } + +.fa-arrows-down-to-line::before { + content: "\e4b8"; } + +.fa-download::before { + content: "\f019"; } + +.fa-face-grin::before { + content: "\f580"; } + +.fa-grin::before { + content: "\f580"; } + +.fa-delete-left::before { + content: "\f55a"; } + +.fa-backspace::before { + content: "\f55a"; } + +.fa-eye-dropper::before { + content: "\f1fb"; } + +.fa-eye-dropper-empty::before { + content: "\f1fb"; } + +.fa-eyedropper::before { + content: "\f1fb"; } + +.fa-file-circle-check::before { + content: "\e5a0"; } + +.fa-forward::before { + content: "\f04e"; } + +.fa-mobile::before { + content: "\f3ce"; } + +.fa-mobile-android::before { + content: "\f3ce"; } + +.fa-mobile-phone::before { + content: "\f3ce"; } + +.fa-face-meh::before { + content: "\f11a"; } + +.fa-meh::before { + content: "\f11a"; } + +.fa-align-center::before { + content: "\f037"; } + +.fa-book-skull::before { + content: "\f6b7"; } + +.fa-book-dead::before { + content: "\f6b7"; } + +.fa-id-card::before { + content: "\f2c2"; } + +.fa-drivers-license::before { + content: "\f2c2"; } + +.fa-outdent::before { + content: "\f03b"; } + +.fa-dedent::before { + content: "\f03b"; } + +.fa-heart-circle-exclamation::before { + content: "\e4fe"; } + +.fa-house::before { + content: "\f015"; } + +.fa-home::before { + content: "\f015"; } + +.fa-home-alt::before { + content: "\f015"; } + +.fa-home-lg-alt::before { + content: "\f015"; } + +.fa-calendar-week::before { + content: "\f784"; } + +.fa-laptop-medical::before { + content: "\f812"; } + +.fa-b::before { + content: "\42"; } + +.fa-file-medical::before { + content: "\f477"; } + +.fa-dice-one::before { + content: "\f525"; } + +.fa-kiwi-bird::before { + content: "\f535"; } + +.fa-arrow-right-arrow-left::before { + content: "\f0ec"; } + +.fa-exchange::before { + content: "\f0ec"; } + +.fa-rotate-right::before { + content: "\f2f9"; } + +.fa-redo-alt::before { + content: "\f2f9"; } + +.fa-rotate-forward::before { + content: "\f2f9"; } + +.fa-utensils::before { + content: "\f2e7"; } + +.fa-cutlery::before { + content: "\f2e7"; } + +.fa-arrow-up-wide-short::before { + content: "\f161"; } + +.fa-sort-amount-up::before { + content: "\f161"; } + +.fa-mill-sign::before { + content: "\e1ed"; } + +.fa-bowl-rice::before { + content: "\e2eb"; } + +.fa-skull::before { + content: "\f54c"; } + +.fa-tower-broadcast::before { + content: "\f519"; } + +.fa-broadcast-tower::before { + content: "\f519"; } + +.fa-truck-pickup::before { + content: "\f63c"; } + +.fa-up-long::before { + content: "\f30c"; } + +.fa-long-arrow-alt-up::before { + content: "\f30c"; } + +.fa-stop::before { + content: "\f04d"; } + +.fa-code-merge::before { + content: "\f387"; } + +.fa-upload::before { + content: "\f093"; } + +.fa-hurricane::before { + content: "\f751"; } + +.fa-mound::before { + content: "\e52d"; } + +.fa-toilet-portable::before { + content: "\e583"; } + +.fa-compact-disc::before { + content: "\f51f"; } + +.fa-file-arrow-down::before { + content: "\f56d"; } + +.fa-file-download::before { + content: "\f56d"; } + +.fa-caravan::before { + content: "\f8ff"; } + +.fa-shield-cat::before { + content: "\e572"; } + +.fa-bolt::before { + content: "\f0e7"; } + +.fa-zap::before { + content: "\f0e7"; } + +.fa-glass-water::before { + content: "\e4f4"; } + +.fa-oil-well::before { + content: "\e532"; } + +.fa-vault::before { + content: "\e2c5"; } + +.fa-mars::before { + content: "\f222"; } + +.fa-toilet::before { + content: "\f7d8"; } + +.fa-plane-circle-xmark::before { + content: "\e557"; } + +.fa-yen-sign::before { + content: "\f157"; } + +.fa-cny::before { + content: "\f157"; } + +.fa-jpy::before { + content: "\f157"; } + +.fa-rmb::before { + content: "\f157"; } + +.fa-yen::before { + content: "\f157"; } + +.fa-ruble-sign::before { + content: "\f158"; } + +.fa-rouble::before { + content: "\f158"; } + +.fa-rub::before { + content: "\f158"; } + +.fa-ruble::before { + content: "\f158"; } + +.fa-sun::before { + content: "\f185"; } + +.fa-guitar::before { + content: "\f7a6"; } + +.fa-face-laugh-wink::before { + content: "\f59c"; } + +.fa-laugh-wink::before { + content: "\f59c"; } + +.fa-horse-head::before { + content: "\f7ab"; } + +.fa-bore-hole::before { + content: "\e4c3"; } + +.fa-industry::before { + content: "\f275"; } + +.fa-circle-down::before { + content: "\f358"; } + +.fa-arrow-alt-circle-down::before { + content: "\f358"; } + +.fa-arrows-turn-to-dots::before { + content: "\e4c1"; } + +.fa-florin-sign::before { + content: "\e184"; } + +.fa-arrow-down-short-wide::before { + content: "\f884"; } + +.fa-sort-amount-desc::before { + content: "\f884"; } + +.fa-sort-amount-down-alt::before { + content: "\f884"; } + +.fa-less-than::before { + content: "\3c"; } + +.fa-angle-down::before { + content: "\f107"; } + +.fa-car-tunnel::before { + content: "\e4de"; } + +.fa-head-side-cough::before { + content: "\e061"; } + +.fa-grip-lines::before { + content: "\f7a4"; } + +.fa-thumbs-down::before { + content: "\f165"; } + +.fa-user-lock::before { + content: "\f502"; } + +.fa-arrow-right-long::before { + content: "\f178"; } + +.fa-long-arrow-right::before { + content: "\f178"; } + +.fa-anchor-circle-xmark::before { + content: "\e4ac"; } + +.fa-ellipsis::before { + content: "\f141"; } + +.fa-ellipsis-h::before { + content: "\f141"; } + +.fa-chess-pawn::before { + content: "\f443"; } + +.fa-kit-medical::before { + content: "\f479"; } + +.fa-first-aid::before { + content: "\f479"; } + +.fa-person-through-window::before { + content: "\e5a9"; } + +.fa-toolbox::before { + content: "\f552"; } + +.fa-hands-holding-circle::before { + content: "\e4fb"; } + +.fa-bug::before { + content: "\f188"; } + +.fa-credit-card::before { + content: "\f09d"; } + +.fa-credit-card-alt::before { + content: "\f09d"; } + +.fa-car::before { + content: "\f1b9"; } + +.fa-automobile::before { + content: "\f1b9"; } + +.fa-hand-holding-hand::before { + content: "\e4f7"; } + +.fa-book-open-reader::before { + content: "\f5da"; } + +.fa-book-reader::before { + content: "\f5da"; } + +.fa-mountain-sun::before { + content: "\e52f"; } + +.fa-arrows-left-right-to-line::before { + content: "\e4ba"; } + +.fa-dice-d20::before { + content: "\f6cf"; } + +.fa-truck-droplet::before { + content: "\e58c"; } + +.fa-file-circle-xmark::before { + content: "\e5a1"; } + +.fa-temperature-arrow-up::before { + content: "\e040"; } + +.fa-temperature-up::before { + content: "\e040"; } + +.fa-medal::before { + content: "\f5a2"; } + +.fa-bed::before { + content: "\f236"; } + +.fa-square-h::before { + content: "\f0fd"; } + +.fa-h-square::before { + content: "\f0fd"; } + +.fa-podcast::before { + content: "\f2ce"; } + +.fa-temperature-full::before { + content: "\f2c7"; } + +.fa-temperature-4::before { + content: "\f2c7"; } + +.fa-thermometer-4::before { + content: "\f2c7"; } + +.fa-thermometer-full::before { + content: "\f2c7"; } + +.fa-bell::before { + content: "\f0f3"; } + +.fa-superscript::before { + content: "\f12b"; } + +.fa-plug-circle-xmark::before { + content: "\e560"; } + +.fa-star-of-life::before { + content: "\f621"; } + +.fa-phone-slash::before { + content: "\f3dd"; } + +.fa-paint-roller::before { + content: "\f5aa"; } + +.fa-handshake-angle::before { + content: "\f4c4"; } + +.fa-hands-helping::before { + content: "\f4c4"; } + +.fa-location-dot::before { + content: "\f3c5"; } + +.fa-map-marker-alt::before { + content: "\f3c5"; } + +.fa-file::before { + content: "\f15b"; } + +.fa-greater-than::before { + content: "\3e"; } + +.fa-person-swimming::before { + content: "\f5c4"; } + +.fa-swimmer::before { + content: "\f5c4"; } + +.fa-arrow-down::before { + content: "\f063"; } + +.fa-droplet::before { + content: "\f043"; } + +.fa-tint::before { + content: "\f043"; } + +.fa-eraser::before { + content: "\f12d"; } + +.fa-earth-americas::before { + content: "\f57d"; } + +.fa-earth::before { + content: "\f57d"; } + +.fa-earth-america::before { + content: "\f57d"; } + +.fa-globe-americas::before { + content: "\f57d"; } + +.fa-person-burst::before { + content: "\e53b"; } + +.fa-dove::before { + content: "\f4ba"; } + +.fa-battery-empty::before { + content: "\f244"; } + +.fa-battery-0::before { + content: "\f244"; } + +.fa-socks::before { + content: "\f696"; } + +.fa-inbox::before { + content: "\f01c"; } + +.fa-section::before { + content: "\e447"; } + +.fa-gauge-high::before { + content: "\f625"; } + +.fa-tachometer-alt::before { + content: "\f625"; } + +.fa-tachometer-alt-fast::before { + content: "\f625"; } + +.fa-envelope-open-text::before { + content: "\f658"; } + +.fa-hospital::before { + content: "\f0f8"; } + +.fa-hospital-alt::before { + content: "\f0f8"; } + +.fa-hospital-wide::before { + content: "\f0f8"; } + +.fa-wine-bottle::before { + content: "\f72f"; } + +.fa-chess-rook::before { + content: "\f447"; } + +.fa-bars-staggered::before { + content: "\f550"; } + +.fa-reorder::before { + content: "\f550"; } + +.fa-stream::before { + content: "\f550"; } + +.fa-dharmachakra::before { + content: "\f655"; } + +.fa-hotdog::before { + content: "\f80f"; } + +.fa-person-walking-with-cane::before { + content: "\f29d"; } + +.fa-blind::before { + content: "\f29d"; } + +.fa-drum::before { + content: "\f569"; } + +.fa-ice-cream::before { + content: "\f810"; } + +.fa-heart-circle-bolt::before { + content: "\e4fc"; } + +.fa-fax::before { + content: "\f1ac"; } + +.fa-paragraph::before { + content: "\f1dd"; } + +.fa-check-to-slot::before { + content: "\f772"; } + +.fa-vote-yea::before { + content: "\f772"; } + +.fa-star-half::before { + content: "\f089"; } + +.fa-boxes-stacked::before { + content: "\f468"; } + +.fa-boxes::before { + content: "\f468"; } + +.fa-boxes-alt::before { + content: "\f468"; } + +.fa-link::before { + content: "\f0c1"; } + +.fa-chain::before { + content: "\f0c1"; } + +.fa-ear-listen::before { + content: "\f2a2"; } + +.fa-assistive-listening-systems::before { + content: "\f2a2"; } + +.fa-tree-city::before { + content: "\e587"; } + +.fa-play::before { + content: "\f04b"; } + +.fa-font::before { + content: "\f031"; } + +.fa-rupiah-sign::before { + content: "\e23d"; } + +.fa-magnifying-glass::before { + content: "\f002"; } + +.fa-search::before { + content: "\f002"; } + +.fa-table-tennis-paddle-ball::before { + content: "\f45d"; } + +.fa-ping-pong-paddle-ball::before { + content: "\f45d"; } + +.fa-table-tennis::before { + content: "\f45d"; } + +.fa-person-dots-from-line::before { + content: "\f470"; } + +.fa-diagnoses::before { + content: "\f470"; } + +.fa-trash-can-arrow-up::before { + content: "\f82a"; } + +.fa-trash-restore-alt::before { + content: "\f82a"; } + +.fa-naira-sign::before { + content: "\e1f6"; } + +.fa-cart-arrow-down::before { + content: "\f218"; } + +.fa-walkie-talkie::before { + content: "\f8ef"; } + +.fa-file-pen::before { + content: "\f31c"; } + +.fa-file-edit::before { + content: "\f31c"; } + +.fa-receipt::before { + content: "\f543"; } + +.fa-square-pen::before { + content: "\f14b"; } + +.fa-pen-square::before { + content: "\f14b"; } + +.fa-pencil-square::before { + content: "\f14b"; } + +.fa-suitcase-rolling::before { + content: "\f5c1"; } + +.fa-person-circle-exclamation::before { + content: "\e53f"; } + +.fa-chevron-down::before { + content: "\f078"; } + +.fa-battery-full::before { + content: "\f240"; } + +.fa-battery::before { + content: "\f240"; } + +.fa-battery-5::before { + content: "\f240"; } + +.fa-skull-crossbones::before { + content: "\f714"; } + +.fa-code-compare::before { + content: "\e13a"; } + +.fa-list-ul::before { + content: "\f0ca"; } + +.fa-list-dots::before { + content: "\f0ca"; } + +.fa-school-lock::before { + content: "\e56f"; } + +.fa-tower-cell::before { + content: "\e585"; } + +.fa-down-long::before { + content: "\f309"; } + +.fa-long-arrow-alt-down::before { + content: "\f309"; } + +.fa-ranking-star::before { + content: "\e561"; } + +.fa-chess-king::before { + content: "\f43f"; } + +.fa-person-harassing::before { + content: "\e549"; } + +.fa-brazilian-real-sign::before { + content: "\e46c"; } + +.fa-landmark-dome::before { + content: "\f752"; } + +.fa-landmark-alt::before { + content: "\f752"; } + +.fa-arrow-up::before { + content: "\f062"; } + +.fa-tv::before { + content: "\f26c"; } + +.fa-television::before { + content: "\f26c"; } + +.fa-tv-alt::before { + content: "\f26c"; } + +.fa-shrimp::before { + content: "\e448"; } + +.fa-list-check::before { + content: "\f0ae"; } + +.fa-tasks::before { + content: "\f0ae"; } + +.fa-jug-detergent::before { + content: "\e519"; } + +.fa-circle-user::before { + content: "\f2bd"; } + +.fa-user-circle::before { + content: "\f2bd"; } + +.fa-user-shield::before { + content: "\f505"; } + +.fa-wind::before { + content: "\f72e"; } + +.fa-car-burst::before { + content: "\f5e1"; } + +.fa-car-crash::before { + content: "\f5e1"; } + +.fa-y::before { + content: "\59"; } + +.fa-person-snowboarding::before { + content: "\f7ce"; } + +.fa-snowboarding::before { + content: "\f7ce"; } + +.fa-truck-fast::before { + content: "\f48b"; } + +.fa-shipping-fast::before { + content: "\f48b"; } + +.fa-fish::before { + content: "\f578"; } + +.fa-user-graduate::before { + content: "\f501"; } + +.fa-circle-half-stroke::before { + content: "\f042"; } + +.fa-adjust::before { + content: "\f042"; } + +.fa-clapperboard::before { + content: "\e131"; } + +.fa-circle-radiation::before { + content: "\f7ba"; } + +.fa-radiation-alt::before { + content: "\f7ba"; } + +.fa-baseball::before { + content: "\f433"; } + +.fa-baseball-ball::before { + content: "\f433"; } + +.fa-jet-fighter-up::before { + content: "\e518"; } + +.fa-diagram-project::before { + content: "\f542"; } + +.fa-project-diagram::before { + content: "\f542"; } + +.fa-copy::before { + content: "\f0c5"; } + +.fa-volume-xmark::before { + content: "\f6a9"; } + +.fa-volume-mute::before { + content: "\f6a9"; } + +.fa-volume-times::before { + content: "\f6a9"; } + +.fa-hand-sparkles::before { + content: "\e05d"; } + +.fa-grip::before { + content: "\f58d"; } + +.fa-grip-horizontal::before { + content: "\f58d"; } + +.fa-share-from-square::before { + content: "\f14d"; } + +.fa-share-square::before { + content: "\f14d"; } + +.fa-child-combatant::before { + content: "\e4e0"; } + +.fa-child-rifle::before { + content: "\e4e0"; } + +.fa-gun::before { + content: "\e19b"; } + +.fa-square-phone::before { + content: "\f098"; } + +.fa-phone-square::before { + content: "\f098"; } + +.fa-plus::before { + content: "\2b"; } + +.fa-add::before { + content: "\2b"; } + +.fa-expand::before { + content: "\f065"; } + +.fa-computer::before { + content: "\e4e5"; } + +.fa-xmark::before { + content: "\f00d"; } + +.fa-close::before { + content: "\f00d"; } + +.fa-multiply::before { + content: "\f00d"; } + +.fa-remove::before { + content: "\f00d"; } + +.fa-times::before { + content: "\f00d"; } + +.fa-arrows-up-down-left-right::before { + content: "\f047"; } + +.fa-arrows::before { + content: "\f047"; } + +.fa-chalkboard-user::before { + content: "\f51c"; } + +.fa-chalkboard-teacher::before { + content: "\f51c"; } + +.fa-peso-sign::before { + content: "\e222"; } + +.fa-building-shield::before { + content: "\e4d8"; } + +.fa-baby::before { + content: "\f77c"; } + +.fa-users-line::before { + content: "\e592"; } + +.fa-quote-left::before { + content: "\f10d"; } + +.fa-quote-left-alt::before { + content: "\f10d"; } + +.fa-tractor::before { + content: "\f722"; } + +.fa-trash-arrow-up::before { + content: "\f829"; } + +.fa-trash-restore::before { + content: "\f829"; } + +.fa-arrow-down-up-lock::before { + content: "\e4b0"; } + +.fa-lines-leaning::before { + content: "\e51e"; } + +.fa-ruler-combined::before { + content: "\f546"; } + +.fa-copyright::before { + content: "\f1f9"; } + +.fa-equals::before { + content: "\3d"; } + +.fa-blender::before { + content: "\f517"; } + +.fa-teeth::before { + content: "\f62e"; } + +.fa-shekel-sign::before { + content: "\f20b"; } + +.fa-ils::before { + content: "\f20b"; } + +.fa-shekel::before { + content: "\f20b"; } + +.fa-sheqel::before { + content: "\f20b"; } + +.fa-sheqel-sign::before { + content: "\f20b"; } + +.fa-map::before { + content: "\f279"; } + +.fa-rocket::before { + content: "\f135"; } + +.fa-photo-film::before { + content: "\f87c"; } + +.fa-photo-video::before { + content: "\f87c"; } + +.fa-folder-minus::before { + content: "\f65d"; } + +.fa-store::before { + content: "\f54e"; } + +.fa-arrow-trend-up::before { + content: "\e098"; } + +.fa-plug-circle-minus::before { + content: "\e55e"; } + +.fa-sign-hanging::before { + content: "\f4d9"; } + +.fa-sign::before { + content: "\f4d9"; } + +.fa-bezier-curve::before { + content: "\f55b"; } + +.fa-bell-slash::before { + content: "\f1f6"; } + +.fa-tablet::before { + content: "\f3fb"; } + +.fa-tablet-android::before { + content: "\f3fb"; } + +.fa-school-flag::before { + content: "\e56e"; } + +.fa-fill::before { + content: "\f575"; } + +.fa-angle-up::before { + content: "\f106"; } + +.fa-drumstick-bite::before { + content: "\f6d7"; } + +.fa-holly-berry::before { + content: "\f7aa"; } + +.fa-chevron-left::before { + content: "\f053"; } + +.fa-bacteria::before { + content: "\e059"; } + +.fa-hand-lizard::before { + content: "\f258"; } + +.fa-notdef::before { + content: "\e1fe"; } + +.fa-disease::before { + content: "\f7fa"; } + +.fa-briefcase-medical::before { + content: "\f469"; } + +.fa-genderless::before { + content: "\f22d"; } + +.fa-chevron-right::before { + content: "\f054"; } + +.fa-retweet::before { + content: "\f079"; } + +.fa-car-rear::before { + content: "\f5de"; } + +.fa-car-alt::before { + content: "\f5de"; } + +.fa-pump-soap::before { + content: "\e06b"; } + +.fa-video-slash::before { + content: "\f4e2"; } + +.fa-battery-quarter::before { + content: "\f243"; } + +.fa-battery-2::before { + content: "\f243"; } + +.fa-radio::before { + content: "\f8d7"; } + +.fa-baby-carriage::before { + content: "\f77d"; } + +.fa-carriage-baby::before { + content: "\f77d"; } + +.fa-traffic-light::before { + content: "\f637"; } + +.fa-thermometer::before { + content: "\f491"; } + +.fa-vr-cardboard::before { + content: "\f729"; } + +.fa-hand-middle-finger::before { + content: "\f806"; } + +.fa-percent::before { + content: "\25"; } + +.fa-percentage::before { + content: "\25"; } + +.fa-truck-moving::before { + content: "\f4df"; } + +.fa-glass-water-droplet::before { + content: "\e4f5"; } + +.fa-display::before { + content: "\e163"; } + +.fa-face-smile::before { + content: "\f118"; } + +.fa-smile::before { + content: "\f118"; } + +.fa-thumbtack::before { + content: "\f08d"; } + +.fa-thumb-tack::before { + content: "\f08d"; } + +.fa-trophy::before { + content: "\f091"; } + +.fa-person-praying::before { + content: "\f683"; } + +.fa-pray::before { + content: "\f683"; } + +.fa-hammer::before { + content: "\f6e3"; } + +.fa-hand-peace::before { + content: "\f25b"; } + +.fa-rotate::before { + content: "\f2f1"; } + +.fa-sync-alt::before { + content: "\f2f1"; } + +.fa-spinner::before { + content: "\f110"; } + +.fa-robot::before { + content: "\f544"; } + +.fa-peace::before { + content: "\f67c"; } + +.fa-gears::before { + content: "\f085"; } + +.fa-cogs::before { + content: "\f085"; } + +.fa-warehouse::before { + content: "\f494"; } + +.fa-arrow-up-right-dots::before { + content: "\e4b7"; } + +.fa-splotch::before { + content: "\f5bc"; } + +.fa-face-grin-hearts::before { + content: "\f584"; } + +.fa-grin-hearts::before { + content: "\f584"; } + +.fa-dice-four::before { + content: "\f524"; } + +.fa-sim-card::before { + content: "\f7c4"; } + +.fa-transgender::before { + content: "\f225"; } + +.fa-transgender-alt::before { + content: "\f225"; } + +.fa-mercury::before { + content: "\f223"; } + +.fa-arrow-turn-down::before { + content: "\f149"; } + +.fa-level-down::before { + content: "\f149"; } + +.fa-person-falling-burst::before { + content: "\e547"; } + +.fa-award::before { + content: "\f559"; } + +.fa-ticket-simple::before { + content: "\f3ff"; } + +.fa-ticket-alt::before { + content: "\f3ff"; } + +.fa-building::before { + content: "\f1ad"; } + +.fa-angles-left::before { + content: "\f100"; } + +.fa-angle-double-left::before { + content: "\f100"; } + +.fa-qrcode::before { + content: "\f029"; } + +.fa-clock-rotate-left::before { + content: "\f1da"; } + +.fa-history::before { + content: "\f1da"; } + +.fa-face-grin-beam-sweat::before { + content: "\f583"; } + +.fa-grin-beam-sweat::before { + content: "\f583"; } + +.fa-file-export::before { + content: "\f56e"; } + +.fa-arrow-right-from-file::before { + content: "\f56e"; } + +.fa-shield::before { + content: "\f132"; } + +.fa-shield-blank::before { + content: "\f132"; } + +.fa-arrow-up-short-wide::before { + content: "\f885"; } + +.fa-sort-amount-up-alt::before { + content: "\f885"; } + +.fa-house-medical::before { + content: "\e3b2"; } + +.fa-golf-ball-tee::before { + content: "\f450"; } + +.fa-golf-ball::before { + content: "\f450"; } + +.fa-circle-chevron-left::before { + content: "\f137"; } + +.fa-chevron-circle-left::before { + content: "\f137"; } + +.fa-house-chimney-window::before { + content: "\e00d"; } + +.fa-pen-nib::before { + content: "\f5ad"; } + +.fa-tent-arrow-turn-left::before { + content: "\e580"; } + +.fa-tents::before { + content: "\e582"; } + +.fa-wand-magic::before { + content: "\f0d0"; } + +.fa-magic::before { + content: "\f0d0"; } + +.fa-dog::before { + content: "\f6d3"; } + +.fa-carrot::before { + content: "\f787"; } + +.fa-moon::before { + content: "\f186"; } + +.fa-wine-glass-empty::before { + content: "\f5ce"; } + +.fa-wine-glass-alt::before { + content: "\f5ce"; } + +.fa-cheese::before { + content: "\f7ef"; } + +.fa-yin-yang::before { + content: "\f6ad"; } + +.fa-music::before { + content: "\f001"; } + +.fa-code-commit::before { + content: "\f386"; } + +.fa-temperature-low::before { + content: "\f76b"; } + +.fa-person-biking::before { + content: "\f84a"; } + +.fa-biking::before { + content: "\f84a"; } + +.fa-broom::before { + content: "\f51a"; } + +.fa-shield-heart::before { + content: "\e574"; } + +.fa-gopuram::before { + content: "\f664"; } + +.fa-earth-oceania::before { + content: "\e47b"; } + +.fa-globe-oceania::before { + content: "\e47b"; } + +.fa-square-xmark::before { + content: "\f2d3"; } + +.fa-times-square::before { + content: "\f2d3"; } + +.fa-xmark-square::before { + content: "\f2d3"; } + +.fa-hashtag::before { + content: "\23"; } + +.fa-up-right-and-down-left-from-center::before { + content: "\f424"; } + +.fa-expand-alt::before { + content: "\f424"; } + +.fa-oil-can::before { + content: "\f613"; } + +.fa-t::before { + content: "\54"; } + +.fa-hippo::before { + content: "\f6ed"; } + +.fa-chart-column::before { + content: "\e0e3"; } + +.fa-infinity::before { + content: "\f534"; } + +.fa-vial-circle-check::before { + content: "\e596"; } + +.fa-person-arrow-down-to-line::before { + content: "\e538"; } + +.fa-voicemail::before { + content: "\f897"; } + +.fa-fan::before { + content: "\f863"; } + +.fa-person-walking-luggage::before { + content: "\e554"; } + +.fa-up-down::before { + content: "\f338"; } + +.fa-arrows-alt-v::before { + content: "\f338"; } + +.fa-cloud-moon-rain::before { + content: "\f73c"; } + +.fa-calendar::before { + content: "\f133"; } + +.fa-trailer::before { + content: "\e041"; } + +.fa-bahai::before { + content: "\f666"; } + +.fa-haykal::before { + content: "\f666"; } + +.fa-sd-card::before { + content: "\f7c2"; } + +.fa-dragon::before { + content: "\f6d5"; } + +.fa-shoe-prints::before { + content: "\f54b"; } + +.fa-circle-plus::before { + content: "\f055"; } + +.fa-plus-circle::before { + content: "\f055"; } + +.fa-face-grin-tongue-wink::before { + content: "\f58b"; } + +.fa-grin-tongue-wink::before { + content: "\f58b"; } + +.fa-hand-holding::before { + content: "\f4bd"; } + +.fa-plug-circle-exclamation::before { + content: "\e55d"; } + +.fa-link-slash::before { + content: "\f127"; } + +.fa-chain-broken::before { + content: "\f127"; } + +.fa-chain-slash::before { + content: "\f127"; } + +.fa-unlink::before { + content: "\f127"; } + +.fa-clone::before { + content: "\f24d"; } + +.fa-person-walking-arrow-loop-left::before { + content: "\e551"; } + +.fa-arrow-up-z-a::before { + content: "\f882"; } + +.fa-sort-alpha-up-alt::before { + content: "\f882"; } + +.fa-fire-flame-curved::before { + content: "\f7e4"; } + +.fa-fire-alt::before { + content: "\f7e4"; } + +.fa-tornado::before { + content: "\f76f"; } + +.fa-file-circle-plus::before { + content: "\e494"; } + +.fa-book-quran::before { + content: "\f687"; } + +.fa-quran::before { + content: "\f687"; } + +.fa-anchor::before { + content: "\f13d"; } + +.fa-border-all::before { + content: "\f84c"; } + +.fa-face-angry::before { + content: "\f556"; } + +.fa-angry::before { + content: "\f556"; } + +.fa-cookie-bite::before { + content: "\f564"; } + +.fa-arrow-trend-down::before { + content: "\e097"; } + +.fa-rss::before { + content: "\f09e"; } + +.fa-feed::before { + content: "\f09e"; } + +.fa-draw-polygon::before { + content: "\f5ee"; } + +.fa-scale-balanced::before { + content: "\f24e"; } + +.fa-balance-scale::before { + content: "\f24e"; } + +.fa-gauge-simple-high::before { + content: "\f62a"; } + +.fa-tachometer::before { + content: "\f62a"; } + +.fa-tachometer-fast::before { + content: "\f62a"; } + +.fa-shower::before { + content: "\f2cc"; } + +.fa-desktop::before { + content: "\f390"; } + +.fa-desktop-alt::before { + content: "\f390"; } + +.fa-m::before { + content: "\4d"; } + +.fa-table-list::before { + content: "\f00b"; } + +.fa-th-list::before { + content: "\f00b"; } + +.fa-comment-sms::before { + content: "\f7cd"; } + +.fa-sms::before { + content: "\f7cd"; } + +.fa-book::before { + content: "\f02d"; } + +.fa-user-plus::before { + content: "\f234"; } + +.fa-check::before { + content: "\f00c"; } + +.fa-battery-three-quarters::before { + content: "\f241"; } + +.fa-battery-4::before { + content: "\f241"; } + +.fa-house-circle-check::before { + content: "\e509"; } + +.fa-angle-left::before { + content: "\f104"; } + +.fa-diagram-successor::before { + content: "\e47a"; } + +.fa-truck-arrow-right::before { + content: "\e58b"; } + +.fa-arrows-split-up-and-left::before { + content: "\e4bc"; } + +.fa-hand-fist::before { + content: "\f6de"; } + +.fa-fist-raised::before { + content: "\f6de"; } + +.fa-cloud-moon::before { + content: "\f6c3"; } + +.fa-briefcase::before { + content: "\f0b1"; } + +.fa-person-falling::before { + content: "\e546"; } + +.fa-image-portrait::before { + content: "\f3e0"; } + +.fa-portrait::before { + content: "\f3e0"; } + +.fa-user-tag::before { + content: "\f507"; } + +.fa-rug::before { + content: "\e569"; } + +.fa-earth-europe::before { + content: "\f7a2"; } + +.fa-globe-europe::before { + content: "\f7a2"; } + +.fa-cart-flatbed-suitcase::before { + content: "\f59d"; } + +.fa-luggage-cart::before { + content: "\f59d"; } + +.fa-rectangle-xmark::before { + content: "\f410"; } + +.fa-rectangle-times::before { + content: "\f410"; } + +.fa-times-rectangle::before { + content: "\f410"; } + +.fa-window-close::before { + content: "\f410"; } + +.fa-baht-sign::before { + content: "\e0ac"; } + +.fa-book-open::before { + content: "\f518"; } + +.fa-book-journal-whills::before { + content: "\f66a"; } + +.fa-journal-whills::before { + content: "\f66a"; } + +.fa-handcuffs::before { + content: "\e4f8"; } + +.fa-triangle-exclamation::before { + content: "\f071"; } + +.fa-exclamation-triangle::before { + content: "\f071"; } + +.fa-warning::before { + content: "\f071"; } + +.fa-database::before { + content: "\f1c0"; } + +.fa-share::before { + content: "\f064"; } + +.fa-arrow-turn-right::before { + content: "\f064"; } + +.fa-mail-forward::before { + content: "\f064"; } + +.fa-bottle-droplet::before { + content: "\e4c4"; } + +.fa-mask-face::before { + content: "\e1d7"; } + +.fa-hill-rockslide::before { + content: "\e508"; } + +.fa-right-left::before { + content: "\f362"; } + +.fa-exchange-alt::before { + content: "\f362"; } + +.fa-paper-plane::before { + content: "\f1d8"; } + +.fa-road-circle-exclamation::before { + content: "\e565"; } + +.fa-dungeon::before { + content: "\f6d9"; } + +.fa-align-right::before { + content: "\f038"; } + +.fa-money-bill-1-wave::before { + content: "\f53b"; } + +.fa-money-bill-wave-alt::before { + content: "\f53b"; } + +.fa-life-ring::before { + content: "\f1cd"; } + +.fa-hands::before { + content: "\f2a7"; } + +.fa-sign-language::before { + content: "\f2a7"; } + +.fa-signing::before { + content: "\f2a7"; } + +.fa-calendar-day::before { + content: "\f783"; } + +.fa-water-ladder::before { + content: "\f5c5"; } + +.fa-ladder-water::before { + content: "\f5c5"; } + +.fa-swimming-pool::before { + content: "\f5c5"; } + +.fa-arrows-up-down::before { + content: "\f07d"; } + +.fa-arrows-v::before { + content: "\f07d"; } + +.fa-face-grimace::before { + content: "\f57f"; } + +.fa-grimace::before { + content: "\f57f"; } + +.fa-wheelchair-move::before { + content: "\e2ce"; } + +.fa-wheelchair-alt::before { + content: "\e2ce"; } + +.fa-turn-down::before { + content: "\f3be"; } + +.fa-level-down-alt::before { + content: "\f3be"; } + +.fa-person-walking-arrow-right::before { + content: "\e552"; } + +.fa-square-envelope::before { + content: "\f199"; } + +.fa-envelope-square::before { + content: "\f199"; } + +.fa-dice::before { + content: "\f522"; } + +.fa-bowling-ball::before { + content: "\f436"; } + +.fa-brain::before { + content: "\f5dc"; } + +.fa-bandage::before { + content: "\f462"; } + +.fa-band-aid::before { + content: "\f462"; } + +.fa-calendar-minus::before { + content: "\f272"; } + +.fa-circle-xmark::before { + content: "\f057"; } + +.fa-times-circle::before { + content: "\f057"; } + +.fa-xmark-circle::before { + content: "\f057"; } + +.fa-gifts::before { + content: "\f79c"; } + +.fa-hotel::before { + content: "\f594"; } + +.fa-earth-asia::before { + content: "\f57e"; } + +.fa-globe-asia::before { + content: "\f57e"; } + +.fa-id-card-clip::before { + content: "\f47f"; } + +.fa-id-card-alt::before { + content: "\f47f"; } + +.fa-magnifying-glass-plus::before { + content: "\f00e"; } + +.fa-search-plus::before { + content: "\f00e"; } + +.fa-thumbs-up::before { + content: "\f164"; } + +.fa-user-clock::before { + content: "\f4fd"; } + +.fa-hand-dots::before { + content: "\f461"; } + +.fa-allergies::before { + content: "\f461"; } + +.fa-file-invoice::before { + content: "\f570"; } + +.fa-window-minimize::before { + content: "\f2d1"; } + +.fa-mug-saucer::before { + content: "\f0f4"; } + +.fa-coffee::before { + content: "\f0f4"; } + +.fa-brush::before { + content: "\f55d"; } + +.fa-mask::before { + content: "\f6fa"; } + +.fa-magnifying-glass-minus::before { + content: "\f010"; } + +.fa-search-minus::before { + content: "\f010"; } + +.fa-ruler-vertical::before { + content: "\f548"; } + +.fa-user-large::before { + content: "\f406"; } + +.fa-user-alt::before { + content: "\f406"; } + +.fa-train-tram::before { + content: "\e5b4"; } + +.fa-user-nurse::before { + content: "\f82f"; } + +.fa-syringe::before { + content: "\f48e"; } + +.fa-cloud-sun::before { + content: "\f6c4"; } + +.fa-stopwatch-20::before { + content: "\e06f"; } + +.fa-square-full::before { + content: "\f45c"; } + +.fa-magnet::before { + content: "\f076"; } + +.fa-jar::before { + content: "\e516"; } + +.fa-note-sticky::before { + content: "\f249"; } + +.fa-sticky-note::before { + content: "\f249"; } + +.fa-bug-slash::before { + content: "\e490"; } + +.fa-arrow-up-from-water-pump::before { + content: "\e4b6"; } + +.fa-bone::before { + content: "\f5d7"; } + +.fa-user-injured::before { + content: "\f728"; } + +.fa-face-sad-tear::before { + content: "\f5b4"; } + +.fa-sad-tear::before { + content: "\f5b4"; } + +.fa-plane::before { + content: "\f072"; } + +.fa-tent-arrows-down::before { + content: "\e581"; } + +.fa-exclamation::before { + content: "\21"; } + +.fa-arrows-spin::before { + content: "\e4bb"; } + +.fa-print::before { + content: "\f02f"; } + +.fa-turkish-lira-sign::before { + content: "\e2bb"; } + +.fa-try::before { + content: "\e2bb"; } + +.fa-turkish-lira::before { + content: "\e2bb"; } + +.fa-dollar-sign::before { + content: "\24"; } + +.fa-dollar::before { + content: "\24"; } + +.fa-usd::before { + content: "\24"; } + +.fa-x::before { + content: "\58"; } + +.fa-magnifying-glass-dollar::before { + content: "\f688"; } + +.fa-search-dollar::before { + content: "\f688"; } + +.fa-users-gear::before { + content: "\f509"; } + +.fa-users-cog::before { + content: "\f509"; } + +.fa-person-military-pointing::before { + content: "\e54a"; } + +.fa-building-columns::before { + content: "\f19c"; } + +.fa-bank::before { + content: "\f19c"; } + +.fa-institution::before { + content: "\f19c"; } + +.fa-museum::before { + content: "\f19c"; } + +.fa-university::before { + content: "\f19c"; } + +.fa-umbrella::before { + content: "\f0e9"; } + +.fa-trowel::before { + content: "\e589"; } + +.fa-d::before { + content: "\44"; } + +.fa-stapler::before { + content: "\e5af"; } + +.fa-masks-theater::before { + content: "\f630"; } + +.fa-theater-masks::before { + content: "\f630"; } + +.fa-kip-sign::before { + content: "\e1c4"; } + +.fa-hand-point-left::before { + content: "\f0a5"; } + +.fa-handshake-simple::before { + content: "\f4c6"; } + +.fa-handshake-alt::before { + content: "\f4c6"; } + +.fa-jet-fighter::before { + content: "\f0fb"; } + +.fa-fighter-jet::before { + content: "\f0fb"; } + +.fa-square-share-nodes::before { + content: "\f1e1"; } + +.fa-share-alt-square::before { + content: "\f1e1"; } + +.fa-barcode::before { + content: "\f02a"; } + +.fa-plus-minus::before { + content: "\e43c"; } + +.fa-video::before { + content: "\f03d"; } + +.fa-video-camera::before { + content: "\f03d"; } + +.fa-graduation-cap::before { + content: "\f19d"; } + +.fa-mortar-board::before { + content: "\f19d"; } + +.fa-hand-holding-medical::before { + content: "\e05c"; } + +.fa-person-circle-check::before { + content: "\e53e"; } + +.fa-turn-up::before { + content: "\f3bf"; } + +.fa-level-up-alt::before { + content: "\f3bf"; } + +.sr-only, +.fa-sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; } + +.sr-only-focusable:not(:focus), +.fa-sr-only-focusable:not(:focus) { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; } +:root, :host { + --fa-style-family-brands: 'Font Awesome 6 Brands'; + --fa-font-brands: normal 400 1em/1 'Font Awesome 6 Brands'; } + +@font-face { + font-family: 'Font Awesome 6 Brands'; + font-style: normal; + font-weight: 400; + font-display: block; + src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); } + +.fab, +.fa-brands { + font-weight: 400; } + +.fa-monero:before { + content: "\f3d0"; } + +.fa-hooli:before { + content: "\f427"; } + +.fa-yelp:before { + content: "\f1e9"; } + +.fa-cc-visa:before { + content: "\f1f0"; } + +.fa-lastfm:before { + content: "\f202"; } + +.fa-shopware:before { + content: "\f5b5"; } + +.fa-creative-commons-nc:before { + content: "\f4e8"; } + +.fa-aws:before { + content: "\f375"; } + +.fa-redhat:before { + content: "\f7bc"; } + +.fa-yoast:before { + content: "\f2b1"; } + +.fa-cloudflare:before { + content: "\e07d"; } + +.fa-ups:before { + content: "\f7e0"; } + +.fa-wpexplorer:before { + content: "\f2de"; } + +.fa-dyalog:before { + content: "\f399"; } + +.fa-bity:before { + content: "\f37a"; } + +.fa-stackpath:before { + content: "\f842"; } + +.fa-buysellads:before { + content: "\f20d"; } + +.fa-first-order:before { + content: "\f2b0"; } + +.fa-modx:before { + content: "\f285"; } + +.fa-guilded:before { + content: "\e07e"; } + +.fa-vnv:before { + content: "\f40b"; } + +.fa-square-js:before { + content: "\f3b9"; } + +.fa-js-square:before { + content: "\f3b9"; } + +.fa-microsoft:before { + content: "\f3ca"; } + +.fa-qq:before { + content: "\f1d6"; } + +.fa-orcid:before { + content: "\f8d2"; } + +.fa-java:before { + content: "\f4e4"; } + +.fa-invision:before { + content: "\f7b0"; } + +.fa-creative-commons-pd-alt:before { + content: "\f4ed"; } + +.fa-centercode:before { + content: "\f380"; } + +.fa-glide-g:before { + content: "\f2a6"; } + +.fa-drupal:before { + content: "\f1a9"; } + +.fa-hire-a-helper:before { + content: "\f3b0"; } + +.fa-creative-commons-by:before { + content: "\f4e7"; } + +.fa-unity:before { + content: "\e049"; } + +.fa-whmcs:before { + content: "\f40d"; } + +.fa-rocketchat:before { + content: "\f3e8"; } + +.fa-vk:before { + content: "\f189"; } + +.fa-untappd:before { + content: "\f405"; } + +.fa-mailchimp:before { + content: "\f59e"; } + +.fa-css3-alt:before { + content: "\f38b"; } + +.fa-square-reddit:before { + content: "\f1a2"; } + +.fa-reddit-square:before { + content: "\f1a2"; } + +.fa-vimeo-v:before { + content: "\f27d"; } + +.fa-contao:before { + content: "\f26d"; } + +.fa-square-font-awesome:before { + content: "\e5ad"; } + +.fa-deskpro:before { + content: "\f38f"; } + +.fa-sistrix:before { + content: "\f3ee"; } + +.fa-square-instagram:before { + content: "\e055"; } + +.fa-instagram-square:before { + content: "\e055"; } + +.fa-battle-net:before { + content: "\f835"; } + +.fa-the-red-yeti:before { + content: "\f69d"; } + +.fa-square-hacker-news:before { + content: "\f3af"; } + +.fa-hacker-news-square:before { + content: "\f3af"; } + +.fa-edge:before { + content: "\f282"; } + +.fa-napster:before { + content: "\f3d2"; } + +.fa-square-snapchat:before { + content: "\f2ad"; } + +.fa-snapchat-square:before { + content: "\f2ad"; } + +.fa-google-plus-g:before { + content: "\f0d5"; } + +.fa-artstation:before { + content: "\f77a"; } + +.fa-markdown:before { + content: "\f60f"; } + +.fa-sourcetree:before { + content: "\f7d3"; } + +.fa-google-plus:before { + content: "\f2b3"; } + +.fa-diaspora:before { + content: "\f791"; } + +.fa-foursquare:before { + content: "\f180"; } + +.fa-stack-overflow:before { + content: "\f16c"; } + +.fa-github-alt:before { + content: "\f113"; } + +.fa-phoenix-squadron:before { + content: "\f511"; } + +.fa-pagelines:before { + content: "\f18c"; } + +.fa-algolia:before { + content: "\f36c"; } + +.fa-red-river:before { + content: "\f3e3"; } + +.fa-creative-commons-sa:before { + content: "\f4ef"; } + +.fa-safari:before { + content: "\f267"; } + +.fa-google:before { + content: "\f1a0"; } + +.fa-square-font-awesome-stroke:before { + content: "\f35c"; } + +.fa-font-awesome-alt:before { + content: "\f35c"; } + +.fa-atlassian:before { + content: "\f77b"; } + +.fa-linkedin-in:before { + content: "\f0e1"; } + +.fa-digital-ocean:before { + content: "\f391"; } + +.fa-nimblr:before { + content: "\f5a8"; } + +.fa-chromecast:before { + content: "\f838"; } + +.fa-evernote:before { + content: "\f839"; } + +.fa-hacker-news:before { + content: "\f1d4"; } + +.fa-creative-commons-sampling:before { + content: "\f4f0"; } + +.fa-adversal:before { + content: "\f36a"; } + +.fa-creative-commons:before { + content: "\f25e"; } + +.fa-watchman-monitoring:before { + content: "\e087"; } + +.fa-fonticons:before { + content: "\f280"; } + +.fa-weixin:before { + content: "\f1d7"; } + +.fa-shirtsinbulk:before { + content: "\f214"; } + +.fa-codepen:before { + content: "\f1cb"; } + +.fa-git-alt:before { + content: "\f841"; } + +.fa-lyft:before { + content: "\f3c3"; } + +.fa-rev:before { + content: "\f5b2"; } + +.fa-windows:before { + content: "\f17a"; } + +.fa-wizards-of-the-coast:before { + content: "\f730"; } + +.fa-square-viadeo:before { + content: "\f2aa"; } + +.fa-viadeo-square:before { + content: "\f2aa"; } + +.fa-meetup:before { + content: "\f2e0"; } + +.fa-centos:before { + content: "\f789"; } + +.fa-adn:before { + content: "\f170"; } + +.fa-cloudsmith:before { + content: "\f384"; } + +.fa-pied-piper-alt:before { + content: "\f1a8"; } + +.fa-square-dribbble:before { + content: "\f397"; } + +.fa-dribbble-square:before { + content: "\f397"; } + +.fa-codiepie:before { + content: "\f284"; } + +.fa-node:before { + content: "\f419"; } + +.fa-mix:before { + content: "\f3cb"; } + +.fa-steam:before { + content: "\f1b6"; } + +.fa-cc-apple-pay:before { + content: "\f416"; } + +.fa-scribd:before { + content: "\f28a"; } + +.fa-openid:before { + content: "\f19b"; } + +.fa-instalod:before { + content: "\e081"; } + +.fa-expeditedssl:before { + content: "\f23e"; } + +.fa-sellcast:before { + content: "\f2da"; } + +.fa-square-twitter:before { + content: "\f081"; } + +.fa-twitter-square:before { + content: "\f081"; } + +.fa-r-project:before { + content: "\f4f7"; } + +.fa-delicious:before { + content: "\f1a5"; } + +.fa-freebsd:before { + content: "\f3a4"; } + +.fa-vuejs:before { + content: "\f41f"; } + +.fa-accusoft:before { + content: "\f369"; } + +.fa-ioxhost:before { + content: "\f208"; } + +.fa-fonticons-fi:before { + content: "\f3a2"; } + +.fa-app-store:before { + content: "\f36f"; } + +.fa-cc-mastercard:before { + content: "\f1f1"; } + +.fa-itunes-note:before { + content: "\f3b5"; } + +.fa-golang:before { + content: "\e40f"; } + +.fa-kickstarter:before { + content: "\f3bb"; } + +.fa-grav:before { + content: "\f2d6"; } + +.fa-weibo:before { + content: "\f18a"; } + +.fa-uncharted:before { + content: "\e084"; } + +.fa-firstdraft:before { + content: "\f3a1"; } + +.fa-square-youtube:before { + content: "\f431"; } + +.fa-youtube-square:before { + content: "\f431"; } + +.fa-wikipedia-w:before { + content: "\f266"; } + +.fa-wpressr:before { + content: "\f3e4"; } + +.fa-rendact:before { + content: "\f3e4"; } + +.fa-angellist:before { + content: "\f209"; } + +.fa-galactic-republic:before { + content: "\f50c"; } + +.fa-nfc-directional:before { + content: "\e530"; } + +.fa-skype:before { + content: "\f17e"; } + +.fa-joget:before { + content: "\f3b7"; } + +.fa-fedora:before { + content: "\f798"; } + +.fa-stripe-s:before { + content: "\f42a"; } + +.fa-meta:before { + content: "\e49b"; } + +.fa-laravel:before { + content: "\f3bd"; } + +.fa-hotjar:before { + content: "\f3b1"; } + +.fa-bluetooth-b:before { + content: "\f294"; } + +.fa-sticker-mule:before { + content: "\f3f7"; } + +.fa-creative-commons-zero:before { + content: "\f4f3"; } + +.fa-hips:before { + content: "\f452"; } + +.fa-behance:before { + content: "\f1b4"; } + +.fa-reddit:before { + content: "\f1a1"; } + +.fa-discord:before { + content: "\f392"; } + +.fa-chrome:before { + content: "\f268"; } + +.fa-app-store-ios:before { + content: "\f370"; } + +.fa-cc-discover:before { + content: "\f1f2"; } + +.fa-wpbeginner:before { + content: "\f297"; } + +.fa-confluence:before { + content: "\f78d"; } + +.fa-mdb:before { + content: "\f8ca"; } + +.fa-dochub:before { + content: "\f394"; } + +.fa-accessible-icon:before { + content: "\f368"; } + +.fa-ebay:before { + content: "\f4f4"; } + +.fa-amazon:before { + content: "\f270"; } + +.fa-unsplash:before { + content: "\e07c"; } + +.fa-yarn:before { + content: "\f7e3"; } + +.fa-square-steam:before { + content: "\f1b7"; } + +.fa-steam-square:before { + content: "\f1b7"; } + +.fa-500px:before { + content: "\f26e"; } + +.fa-square-vimeo:before { + content: "\f194"; } + +.fa-vimeo-square:before { + content: "\f194"; } + +.fa-asymmetrik:before { + content: "\f372"; } + +.fa-font-awesome:before { + content: "\f2b4"; } + +.fa-font-awesome-flag:before { + content: "\f2b4"; } + +.fa-font-awesome-logo-full:before { + content: "\f2b4"; } + +.fa-gratipay:before { + content: "\f184"; } + +.fa-apple:before { + content: "\f179"; } + +.fa-hive:before { + content: "\e07f"; } + +.fa-gitkraken:before { + content: "\f3a6"; } + +.fa-keybase:before { + content: "\f4f5"; } + +.fa-apple-pay:before { + content: "\f415"; } + +.fa-padlet:before { + content: "\e4a0"; } + +.fa-amazon-pay:before { + content: "\f42c"; } + +.fa-square-github:before { + content: "\f092"; } + +.fa-github-square:before { + content: "\f092"; } + +.fa-stumbleupon:before { + content: "\f1a4"; } + +.fa-fedex:before { + content: "\f797"; } + +.fa-phoenix-framework:before { + content: "\f3dc"; } + +.fa-shopify:before { + content: "\e057"; } + +.fa-neos:before { + content: "\f612"; } + +.fa-hackerrank:before { + content: "\f5f7"; } + +.fa-researchgate:before { + content: "\f4f8"; } + +.fa-swift:before { + content: "\f8e1"; } + +.fa-angular:before { + content: "\f420"; } + +.fa-speakap:before { + content: "\f3f3"; } + +.fa-angrycreative:before { + content: "\f36e"; } + +.fa-y-combinator:before { + content: "\f23b"; } + +.fa-empire:before { + content: "\f1d1"; } + +.fa-envira:before { + content: "\f299"; } + +.fa-square-gitlab:before { + content: "\e5ae"; } + +.fa-gitlab-square:before { + content: "\e5ae"; } + +.fa-studiovinari:before { + content: "\f3f8"; } + +.fa-pied-piper:before { + content: "\f2ae"; } + +.fa-wordpress:before { + content: "\f19a"; } + +.fa-product-hunt:before { + content: "\f288"; } + +.fa-firefox:before { + content: "\f269"; } + +.fa-linode:before { + content: "\f2b8"; } + +.fa-goodreads:before { + content: "\f3a8"; } + +.fa-square-odnoklassniki:before { + content: "\f264"; } + +.fa-odnoklassniki-square:before { + content: "\f264"; } + +.fa-jsfiddle:before { + content: "\f1cc"; } + +.fa-sith:before { + content: "\f512"; } + +.fa-themeisle:before { + content: "\f2b2"; } + +.fa-page4:before { + content: "\f3d7"; } + +.fa-hashnode:before { + content: "\e499"; } + +.fa-react:before { + content: "\f41b"; } + +.fa-cc-paypal:before { + content: "\f1f4"; } + +.fa-squarespace:before { + content: "\f5be"; } + +.fa-cc-stripe:before { + content: "\f1f5"; } + +.fa-creative-commons-share:before { + content: "\f4f2"; } + +.fa-bitcoin:before { + content: "\f379"; } + +.fa-keycdn:before { + content: "\f3ba"; } + +.fa-opera:before { + content: "\f26a"; } + +.fa-itch-io:before { + content: "\f83a"; } + +.fa-umbraco:before { + content: "\f8e8"; } + +.fa-galactic-senate:before { + content: "\f50d"; } + +.fa-ubuntu:before { + content: "\f7df"; } + +.fa-draft2digital:before { + content: "\f396"; } + +.fa-stripe:before { + content: "\f429"; } + +.fa-houzz:before { + content: "\f27c"; } + +.fa-gg:before { + content: "\f260"; } + +.fa-dhl:before { + content: "\f790"; } + +.fa-square-pinterest:before { + content: "\f0d3"; } + +.fa-pinterest-square:before { + content: "\f0d3"; } + +.fa-xing:before { + content: "\f168"; } + +.fa-blackberry:before { + content: "\f37b"; } + +.fa-creative-commons-pd:before { + content: "\f4ec"; } + +.fa-playstation:before { + content: "\f3df"; } + +.fa-quinscape:before { + content: "\f459"; } + +.fa-less:before { + content: "\f41d"; } + +.fa-blogger-b:before { + content: "\f37d"; } + +.fa-opencart:before { + content: "\f23d"; } + +.fa-vine:before { + content: "\f1ca"; } + +.fa-paypal:before { + content: "\f1ed"; } + +.fa-gitlab:before { + content: "\f296"; } + +.fa-typo3:before { + content: "\f42b"; } + +.fa-reddit-alien:before { + content: "\f281"; } + +.fa-yahoo:before { + content: "\f19e"; } + +.fa-dailymotion:before { + content: "\e052"; } + +.fa-affiliatetheme:before { + content: "\f36b"; } + +.fa-pied-piper-pp:before { + content: "\f1a7"; } + +.fa-bootstrap:before { + content: "\f836"; } + +.fa-odnoklassniki:before { + content: "\f263"; } + +.fa-nfc-symbol:before { + content: "\e531"; } + +.fa-ethereum:before { + content: "\f42e"; } + +.fa-speaker-deck:before { + content: "\f83c"; } + +.fa-creative-commons-nc-eu:before { + content: "\f4e9"; } + +.fa-patreon:before { + content: "\f3d9"; } + +.fa-avianex:before { + content: "\f374"; } + +.fa-ello:before { + content: "\f5f1"; } + +.fa-gofore:before { + content: "\f3a7"; } + +.fa-bimobject:before { + content: "\f378"; } + +.fa-facebook-f:before { + content: "\f39e"; } + +.fa-square-google-plus:before { + content: "\f0d4"; } + +.fa-google-plus-square:before { + content: "\f0d4"; } + +.fa-mandalorian:before { + content: "\f50f"; } + +.fa-first-order-alt:before { + content: "\f50a"; } + +.fa-osi:before { + content: "\f41a"; } + +.fa-google-wallet:before { + content: "\f1ee"; } + +.fa-d-and-d-beyond:before { + content: "\f6ca"; } + +.fa-periscope:before { + content: "\f3da"; } + +.fa-fulcrum:before { + content: "\f50b"; } + +.fa-cloudscale:before { + content: "\f383"; } + +.fa-forumbee:before { + content: "\f211"; } + +.fa-mizuni:before { + content: "\f3cc"; } + +.fa-schlix:before { + content: "\f3ea"; } + +.fa-square-xing:before { + content: "\f169"; } + +.fa-xing-square:before { + content: "\f169"; } + +.fa-bandcamp:before { + content: "\f2d5"; } + +.fa-wpforms:before { + content: "\f298"; } + +.fa-cloudversify:before { + content: "\f385"; } + +.fa-usps:before { + content: "\f7e1"; } + +.fa-megaport:before { + content: "\f5a3"; } + +.fa-magento:before { + content: "\f3c4"; } + +.fa-spotify:before { + content: "\f1bc"; } + +.fa-optin-monster:before { + content: "\f23c"; } + +.fa-fly:before { + content: "\f417"; } + +.fa-aviato:before { + content: "\f421"; } + +.fa-itunes:before { + content: "\f3b4"; } + +.fa-cuttlefish:before { + content: "\f38c"; } + +.fa-blogger:before { + content: "\f37c"; } + +.fa-flickr:before { + content: "\f16e"; } + +.fa-viber:before { + content: "\f409"; } + +.fa-soundcloud:before { + content: "\f1be"; } + +.fa-digg:before { + content: "\f1a6"; } + +.fa-tencent-weibo:before { + content: "\f1d5"; } + +.fa-symfony:before { + content: "\f83d"; } + +.fa-maxcdn:before { + content: "\f136"; } + +.fa-etsy:before { + content: "\f2d7"; } + +.fa-facebook-messenger:before { + content: "\f39f"; } + +.fa-audible:before { + content: "\f373"; } + +.fa-think-peaks:before { + content: "\f731"; } + +.fa-bilibili:before { + content: "\e3d9"; } + +.fa-erlang:before { + content: "\f39d"; } + +.fa-cotton-bureau:before { + content: "\f89e"; } + +.fa-dashcube:before { + content: "\f210"; } + +.fa-42-group:before { + content: "\e080"; } + +.fa-innosoft:before { + content: "\e080"; } + +.fa-stack-exchange:before { + content: "\f18d"; } + +.fa-elementor:before { + content: "\f430"; } + +.fa-square-pied-piper:before { + content: "\e01e"; } + +.fa-pied-piper-square:before { + content: "\e01e"; } + +.fa-creative-commons-nd:before { + content: "\f4eb"; } + +.fa-palfed:before { + content: "\f3d8"; } + +.fa-superpowers:before { + content: "\f2dd"; } + +.fa-resolving:before { + content: "\f3e7"; } + +.fa-xbox:before { + content: "\f412"; } + +.fa-searchengin:before { + content: "\f3eb"; } + +.fa-tiktok:before { + content: "\e07b"; } + +.fa-square-facebook:before { + content: "\f082"; } + +.fa-facebook-square:before { + content: "\f082"; } + +.fa-renren:before { + content: "\f18b"; } + +.fa-linux:before { + content: "\f17c"; } + +.fa-glide:before { + content: "\f2a5"; } + +.fa-linkedin:before { + content: "\f08c"; } + +.fa-hubspot:before { + content: "\f3b2"; } + +.fa-deploydog:before { + content: "\f38e"; } + +.fa-twitch:before { + content: "\f1e8"; } + +.fa-ravelry:before { + content: "\f2d9"; } + +.fa-mixer:before { + content: "\e056"; } + +.fa-square-lastfm:before { + content: "\f203"; } + +.fa-lastfm-square:before { + content: "\f203"; } + +.fa-vimeo:before { + content: "\f40a"; } + +.fa-mendeley:before { + content: "\f7b3"; } + +.fa-uniregistry:before { + content: "\f404"; } + +.fa-figma:before { + content: "\f799"; } + +.fa-creative-commons-remix:before { + content: "\f4ee"; } + +.fa-cc-amazon-pay:before { + content: "\f42d"; } + +.fa-dropbox:before { + content: "\f16b"; } + +.fa-instagram:before { + content: "\f16d"; } + +.fa-cmplid:before { + content: "\e360"; } + +.fa-facebook:before { + content: "\f09a"; } + +.fa-gripfire:before { + content: "\f3ac"; } + +.fa-jedi-order:before { + content: "\f50e"; } + +.fa-uikit:before { + content: "\f403"; } + +.fa-fort-awesome-alt:before { + content: "\f3a3"; } + +.fa-phabricator:before { + content: "\f3db"; } + +.fa-ussunnah:before { + content: "\f407"; } + +.fa-earlybirds:before { + content: "\f39a"; } + +.fa-trade-federation:before { + content: "\f513"; } + +.fa-autoprefixer:before { + content: "\f41c"; } + +.fa-whatsapp:before { + content: "\f232"; } + +.fa-slideshare:before { + content: "\f1e7"; } + +.fa-google-play:before { + content: "\f3ab"; } + +.fa-viadeo:before { + content: "\f2a9"; } + +.fa-line:before { + content: "\f3c0"; } + +.fa-google-drive:before { + content: "\f3aa"; } + +.fa-servicestack:before { + content: "\f3ec"; } + +.fa-simplybuilt:before { + content: "\f215"; } + +.fa-bitbucket:before { + content: "\f171"; } + +.fa-imdb:before { + content: "\f2d8"; } + +.fa-deezer:before { + content: "\e077"; } + +.fa-raspberry-pi:before { + content: "\f7bb"; } + +.fa-jira:before { + content: "\f7b1"; } + +.fa-docker:before { + content: "\f395"; } + +.fa-screenpal:before { + content: "\e570"; } + +.fa-bluetooth:before { + content: "\f293"; } + +.fa-gitter:before { + content: "\f426"; } + +.fa-d-and-d:before { + content: "\f38d"; } + +.fa-microblog:before { + content: "\e01a"; } + +.fa-cc-diners-club:before { + content: "\f24c"; } + +.fa-gg-circle:before { + content: "\f261"; } + +.fa-pied-piper-hat:before { + content: "\f4e5"; } + +.fa-kickstarter-k:before { + content: "\f3bc"; } + +.fa-yandex:before { + content: "\f413"; } + +.fa-readme:before { + content: "\f4d5"; } + +.fa-html5:before { + content: "\f13b"; } + +.fa-sellsy:before { + content: "\f213"; } + +.fa-sass:before { + content: "\f41e"; } + +.fa-wirsindhandwerk:before { + content: "\e2d0"; } + +.fa-wsh:before { + content: "\e2d0"; } + +.fa-buromobelexperte:before { + content: "\f37f"; } + +.fa-salesforce:before { + content: "\f83b"; } + +.fa-octopus-deploy:before { + content: "\e082"; } + +.fa-medapps:before { + content: "\f3c6"; } + +.fa-ns8:before { + content: "\f3d5"; } + +.fa-pinterest-p:before { + content: "\f231"; } + +.fa-apper:before { + content: "\f371"; } + +.fa-fort-awesome:before { + content: "\f286"; } + +.fa-waze:before { + content: "\f83f"; } + +.fa-cc-jcb:before { + content: "\f24b"; } + +.fa-snapchat:before { + content: "\f2ab"; } + +.fa-snapchat-ghost:before { + content: "\f2ab"; } + +.fa-fantasy-flight-games:before { + content: "\f6dc"; } + +.fa-rust:before { + content: "\e07a"; } + +.fa-wix:before { + content: "\f5cf"; } + +.fa-square-behance:before { + content: "\f1b5"; } + +.fa-behance-square:before { + content: "\f1b5"; } + +.fa-supple:before { + content: "\f3f9"; } + +.fa-rebel:before { + content: "\f1d0"; } + +.fa-css3:before { + content: "\f13c"; } + +.fa-staylinked:before { + content: "\f3f5"; } + +.fa-kaggle:before { + content: "\f5fa"; } + +.fa-space-awesome:before { + content: "\e5ac"; } + +.fa-deviantart:before { + content: "\f1bd"; } + +.fa-cpanel:before { + content: "\f388"; } + +.fa-goodreads-g:before { + content: "\f3a9"; } + +.fa-square-git:before { + content: "\f1d2"; } + +.fa-git-square:before { + content: "\f1d2"; } + +.fa-square-tumblr:before { + content: "\f174"; } + +.fa-tumblr-square:before { + content: "\f174"; } + +.fa-trello:before { + content: "\f181"; } + +.fa-creative-commons-nc-jp:before { + content: "\f4ea"; } + +.fa-get-pocket:before { + content: "\f265"; } + +.fa-perbyte:before { + content: "\e083"; } + +.fa-grunt:before { + content: "\f3ad"; } + +.fa-weebly:before { + content: "\f5cc"; } + +.fa-connectdevelop:before { + content: "\f20e"; } + +.fa-leanpub:before { + content: "\f212"; } + +.fa-black-tie:before { + content: "\f27e"; } + +.fa-themeco:before { + content: "\f5c6"; } + +.fa-python:before { + content: "\f3e2"; } + +.fa-android:before { + content: "\f17b"; } + +.fa-bots:before { + content: "\e340"; } + +.fa-free-code-camp:before { + content: "\f2c5"; } + +.fa-hornbill:before { + content: "\f592"; } + +.fa-js:before { + content: "\f3b8"; } + +.fa-ideal:before { + content: "\e013"; } + +.fa-git:before { + content: "\f1d3"; } + +.fa-dev:before { + content: "\f6cc"; } + +.fa-sketch:before { + content: "\f7c6"; } + +.fa-yandex-international:before { + content: "\f414"; } + +.fa-cc-amex:before { + content: "\f1f3"; } + +.fa-uber:before { + content: "\f402"; } + +.fa-github:before { + content: "\f09b"; } + +.fa-php:before { + content: "\f457"; } + +.fa-alipay:before { + content: "\f642"; } + +.fa-youtube:before { + content: "\f167"; } + +.fa-skyatlas:before { + content: "\f216"; } + +.fa-firefox-browser:before { + content: "\e007"; } + +.fa-replyd:before { + content: "\f3e6"; } + +.fa-suse:before { + content: "\f7d6"; } + +.fa-jenkins:before { + content: "\f3b6"; } + +.fa-twitter:before { + content: "\f099"; } + +.fa-rockrms:before { + content: "\f3e9"; } + +.fa-pinterest:before { + content: "\f0d2"; } + +.fa-buffer:before { + content: "\f837"; } + +.fa-npm:before { + content: "\f3d4"; } + +.fa-yammer:before { + content: "\f840"; } + +.fa-btc:before { + content: "\f15a"; } + +.fa-dribbble:before { + content: "\f17d"; } + +.fa-stumbleupon-circle:before { + content: "\f1a3"; } + +.fa-internet-explorer:before { + content: "\f26b"; } + +.fa-stubber:before { + content: "\e5c7"; } + +.fa-telegram:before { + content: "\f2c6"; } + +.fa-telegram-plane:before { + content: "\f2c6"; } + +.fa-old-republic:before { + content: "\f510"; } + +.fa-odysee:before { + content: "\e5c6"; } + +.fa-square-whatsapp:before { + content: "\f40c"; } + +.fa-whatsapp-square:before { + content: "\f40c"; } + +.fa-node-js:before { + content: "\f3d3"; } + +.fa-edge-legacy:before { + content: "\e078"; } + +.fa-slack:before { + content: "\f198"; } + +.fa-slack-hash:before { + content: "\f198"; } + +.fa-medrt:before { + content: "\f3c8"; } + +.fa-usb:before { + content: "\f287"; } + +.fa-tumblr:before { + content: "\f173"; } + +.fa-vaadin:before { + content: "\f408"; } + +.fa-quora:before { + content: "\f2c4"; } + +.fa-reacteurope:before { + content: "\f75d"; } + +.fa-medium:before { + content: "\f23a"; } + +.fa-medium-m:before { + content: "\f23a"; } + +.fa-amilia:before { + content: "\f36d"; } + +.fa-mixcloud:before { + content: "\f289"; } + +.fa-flipboard:before { + content: "\f44d"; } + +.fa-viacoin:before { + content: "\f237"; } + +.fa-critical-role:before { + content: "\f6c9"; } + +.fa-sitrox:before { + content: "\e44a"; } + +.fa-discourse:before { + content: "\f393"; } + +.fa-joomla:before { + content: "\f1aa"; } + +.fa-mastodon:before { + content: "\f4f6"; } + +.fa-airbnb:before { + content: "\f834"; } + +.fa-wolf-pack-battalion:before { + content: "\f514"; } + +.fa-buy-n-large:before { + content: "\f8a6"; } + +.fa-gulp:before { + content: "\f3ae"; } + +.fa-creative-commons-sampling-plus:before { + content: "\f4f1"; } + +.fa-strava:before { + content: "\f428"; } + +.fa-ember:before { + content: "\f423"; } + +.fa-canadian-maple-leaf:before { + content: "\f785"; } + +.fa-teamspeak:before { + content: "\f4f9"; } + +.fa-pushed:before { + content: "\f3e1"; } + +.fa-wordpress-simple:before { + content: "\f411"; } + +.fa-nutritionix:before { + content: "\f3d6"; } + +.fa-wodu:before { + content: "\e088"; } + +.fa-google-pay:before { + content: "\e079"; } + +.fa-intercom:before { + content: "\f7af"; } + +.fa-zhihu:before { + content: "\f63f"; } + +.fa-korvue:before { + content: "\f42f"; } + +.fa-pix:before { + content: "\e43a"; } + +.fa-steam-symbol:before { + content: "\f3f6"; } +:root, :host { + --fa-style-family-classic: 'Font Awesome 6 Free'; + --fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free'; } + +@font-face { + font-family: 'Font Awesome 6 Free'; + font-style: normal; + font-weight: 400; + font-display: block; + src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); } + +.far, +.fa-regular { + font-weight: 400; } +:root, :host { + --fa-style-family-classic: 'Font Awesome 6 Free'; + --fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free'; } + +@font-face { + font-family: 'Font Awesome 6 Free'; + font-style: normal; + font-weight: 900; + font-display: block; + src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } + +.fas, +.fa-solid { + font-weight: 900; } +@font-face { + font-family: 'Font Awesome 5 Brands'; + font-display: block; + font-weight: 400; + src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); } + +@font-face { + font-family: 'Font Awesome 5 Free'; + font-display: block; + font-weight: 900; + src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } + +@font-face { + font-family: 'Font Awesome 5 Free'; + font-display: block; + font-weight: 400; + src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); } +@font-face { + font-family: 'FontAwesome'; + font-display: block; + src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } + +@font-face { + font-family: 'FontAwesome'; + font-display: block; + src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); } + +@font-face { + font-family: 'FontAwesome'; + font-display: block; + src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); + unicode-range: U+F003,U+F006,U+F014,U+F016-F017,U+F01A-F01B,U+F01D,U+F022,U+F03E,U+F044,U+F046,U+F05C-F05D,U+F06E,U+F070,U+F087-F088,U+F08A,U+F094,U+F096-F097,U+F09D,U+F0A0,U+F0A2,U+F0A4-F0A7,U+F0C5,U+F0C7,U+F0E5-F0E6,U+F0EB,U+F0F6-F0F8,U+F10C,U+F114-F115,U+F118-F11A,U+F11C-F11D,U+F133,U+F147,U+F14E,U+F150-F152,U+F185-F186,U+F18E,U+F190-F192,U+F196,U+F1C1-F1C9,U+F1D9,U+F1DB,U+F1E3,U+F1EA,U+F1F7,U+F1F9,U+F20A,U+F247-F248,U+F24A,U+F24D,U+F255-F25B,U+F25D,U+F271-F274,U+F278,U+F27B,U+F28C,U+F28E,U+F29C,U+F2B5,U+F2B7,U+F2BA,U+F2BC,U+F2BE,U+F2C0-F2C1,U+F2C3,U+F2D0,U+F2D2,U+F2D4,U+F2DC; } + +@font-face { + font-family: 'FontAwesome'; + font-display: block; + src: url("../webfonts/fa-v4compatibility.woff2") format("woff2"), url("../webfonts/fa-v4compatibility.ttf") format("truetype"); + unicode-range: U+F041,U+F047,U+F065-F066,U+F07D-F07E,U+F080,U+F08B,U+F08E,U+F090,U+F09A,U+F0AC,U+F0AE,U+F0B2,U+F0D0,U+F0D6,U+F0E4,U+F0EC,U+F10A-F10B,U+F123,U+F13E,U+F148-F149,U+F14C,U+F156,U+F15E,U+F160-F161,U+F163,U+F175-F178,U+F195,U+F1F8,U+F219,U+F27A; } diff --git a/tests/fixtures/dmodos/fonts/Inter-Bold.otf b/tests/fixtures/dmodos/fonts/Inter-Bold.otf new file mode 100644 index 0000000000000000000000000000000000000000..f624a34e5245b9c14bf30398ec50924733be0b5c GIT binary patch literal 12320 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/dmodos/fonts/Inter-Regular.otf b/tests/fixtures/dmodos/fonts/Inter-Regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..24cac09daf2ce036fe5c9fb2946888431e1fec5d GIT binary patch literal 12468 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 E2Qu+JApigX literal 0 HcmV?d00001 diff --git a/tests/fixtures/dmodos/fonts/Inter-SemiBold.otf b/tests/fixtures/dmodos/fonts/Inter-SemiBold.otf new file mode 100644 index 0000000000000000000000000000000000000000..cb172503ae910377e3087e7514ae8d1f69029ee3 GIT binary patch literal 12392 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/dmodos/fonts/LICENSE-FontAwesome.txt b/tests/fixtures/dmodos/fonts/LICENSE-FontAwesome.txt new file mode 100644 index 0000000..39e18e3 --- /dev/null +++ b/tests/fixtures/dmodos/fonts/LICENSE-FontAwesome.txt @@ -0,0 +1,165 @@ +Fonticons, Inc. (https://fontawesome.com) + +-------------------------------------------------------------------------------- + +Font Awesome Free License + +Font Awesome Free is free, open source, and GPL friendly. You can use it for +commercial projects, open source projects, or really almost whatever you want. +Full Font Awesome Free license: https://fontawesome.com/license/free. + +-------------------------------------------------------------------------------- + +# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) + +The Font Awesome Free download is licensed under a Creative Commons +Attribution 4.0 International License and applies to all icons packaged +as SVG and JS file types. + +-------------------------------------------------------------------------------- + +# Fonts: SIL OFL 1.1 License + +In the Font Awesome Free download, the SIL OFL license applies to all icons +packaged as web and desktop font files. + +Copyright (c) 2023 Fonticons, Inc. (https://fontawesome.com) +with Reserved Font Name: "Font Awesome". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +SIL OPEN FONT LICENSE +Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting — in part or in whole — any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +-------------------------------------------------------------------------------- + +# Code: MIT License (https://opensource.org/licenses/MIT) + +In the Font Awesome Free download, the MIT license applies to all non-font and +non-icon files. + +Copyright 2023 Fonticons, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in the +Software without restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +# Attribution + +Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font +Awesome Free files already contain embedded comments with sufficient +attribution, so you shouldn't need to do anything additional when using these +files normally. + +We've kept attribution comments terse, so we ask that you do not actively work +to remove them from files, especially code. They're a great way for folks to +learn about Font Awesome. + +-------------------------------------------------------------------------------- + +# Brand Icons + +All brand icons are trademarks of their respective owners. The use of these +trademarks does not indicate endorsement of the trademark holder by Font +Awesome, nor vice versa. **Please do not use brand logos for any purpose except +to represent the company, product, or service to which they refer.** diff --git a/tests/fixtures/dmodos/fonts/LICENSE-Inter.txt b/tests/fixtures/dmodos/fonts/LICENSE-Inter.txt new file mode 100644 index 0000000..9b2ca37 --- /dev/null +++ b/tests/fixtures/dmodos/fonts/LICENSE-Inter.txt @@ -0,0 +1,92 @@ +Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/tests/fixtures/dmodos/fonts/icons-brands.ttf b/tests/fixtures/dmodos/fonts/icons-brands.ttf new file mode 100644 index 0000000000000000000000000000000000000000..13ba4e91b195fdbaddcadc51c210802e86f84d70 GIT binary patch literal 972 zcmb7DL2DC17=5$5ZKBp1X*F7eE}~6KTZJVxDi+%cNt{QdnkfXJP96zA|e*N87O#L-)t7z(33;voA=(l@7r%?2n2w=s6&N5 zlRa(R_%s^;Vvy>cyla-x)!ZasCQsYW(tP)kjko0MBNYxDOC5co=6_@r^PwpoflKcJ@6gTKKa z<&jj{h7u}@cDB`D$V8h<6F+zh9jtU!%ro~&v~ID#$E=JFb-Q9>8)EWi_8R0s0(Z#DwHkc|7?Ru%3c>{~@&;EMr% zp$iG-;i5<#O2v|~WG0zOWnyDQv9YyVE4hBh>CdT+yF-Q?AMDqkbGqZ`xsT6n+pvsR zhGB(OyYM3VhCmM$*$*FqXYPrxwnOj4L5xItBGh>TZ4Wcj7?-CZ78t<&2rKBu%LuD5 zuo_`)ho=+A@io#DQny^sn#tb`W_CalOpFQm9Pg0Sc&+yId_sk8uWBx~A!tzAEt)>zKqA*&LlV7T{2+wB>AP U?DUWRcLr}E9KkpFpV&SA0fi){Pyhe` literal 0 HcmV?d00001 diff --git a/tests/fixtures/dmodos/fonts/icons-solid.ttf b/tests/fixtures/dmodos/fonts/icons-solid.ttf new file mode 100644 index 0000000000000000000000000000000000000000..183f9d84232cd0cca1b42290b9a2fa26b2011e3b GIT binary patch literal 6280 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 R Date: Sun, 4 Oct 2026 22:32:22 +0200 Subject: [PATCH 3/3] dmvs_html: documentation --- README.md | 114 ++++++++++++++++++++++++++++++++++++++++++++++++- dmvs_html.dmr | 1 - docs/README.md | 12 ++++++ 3 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 docs/README.md diff --git a/README.md b/README.md index 2691542..e333c55 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,114 @@ # dmvs_html -DMVS html + +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![CI](https://github.com/choco-technologies/dmvs_html/actions/workflows/ci.yml/badge.svg)](https://github.com/choco-technologies/dmvs_html/actions/workflows/ci.yml) + +HTML and CSS for [dmvsi](https://github.com/choco-technologies/dmvsi): a +converter plugin that lays a page out as a browser does and describes it as +a dmvsi document - [todmvs](https://github.com/choco-technologies/todmvs) +makes a dmview view of it. + +It has its own HTML parser, CSS cascade and layout - no browser, no +dependencies - so it runs on a PC at build time and on a device. It is made +for **still UI pages**: the screens a designer (or an AI) draws in HTML with +Tailwind CSS. Scripts are not run: a page is converted as it is when it +loads. + +## What it does + +**HTML** - elements, attributes, text with character references, the end +tags a page may leave out, `