diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 2661e182..227410ea 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,89 +1,89 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or advances of - any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email address, - without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at our discord server. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at our discord server. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index dbf7a530..3d1d8ac8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,188 +1,188 @@ -# Contributing to OpenCut - -⚠️ We are currently NOT accepting feature PRs while we build out the core editor. - -If you want to contribute: - -1. Open an issue first to discuss -2. Wait for maintainer approval -3. Only then start coding - -Critical bug fixes may be accepted on a case-by-case basis. - -Thank you for your interest in contributing to OpenCut! This document provides guidelines and instructions for contributing. - -## Getting Started - -### Prerequisites - -- [Node.js](https://nodejs.org/en/) (v18 or later) -- [Bun](https://bun.sh/docs/installation) - (for `npm` alternative) -- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) - -> **Note:** Docker is optional, but it's essential for running the local database and Redis services. If you're planning to contribute to frontend features, you can skip the Docker setup. If you have followed the steps below in [Setup](#setup), you're all set to go! - -### Setup - -1. Fork the repository -2. Clone your fork locally -3. Navigate to the web app directory: `cd apps/web` -4. Copy `.env.example` to `.env.local`: - - ```bash - # Unix/Linux/Mac - cp .env.example .env.local - - # Windows Command Prompt - copy .env.example .env.local - - # Windows PowerShell - Copy-Item .env.example .env.local - ``` - -5. Install dependencies: `bun install` -6. Start the development server: `bun run dev` - -> **Note:** If you see an error like `Unsupported URL Type "workspace:*"` when running `npm install`, you have two options: -> -> 1. Upgrade to a recent npm version (v9 or later), which has full workspace protocol support. -> 2. Use an alternative package manager such as **bun** or **pnpm**. - -## What to Focus On - -**🎯 Good Areas to Contribute:** - -- Timeline functionality and UI improvements -- Project management features -- Performance optimizations -- Bug fixes in existing functionality -- UI/UX improvements -- Documentation and testing - -**⚠️ Areas to Avoid:** - -- Preview panel enhancements (text fonts, stickers, effects) -- Export functionality improvements -- Preview rendering optimizations - -**Why?** We're currently planning a major refactor of the preview system. The current preview renders DOM elements (HTML), but we're moving to a binary rendering approach similar to CapCut. This new system will ensure consistency between preview and export, and provide much better performance and quality. - -The current HTML-based preview is essentially a prototype - the binary approach will be the "real deal." To avoid wasted effort, please focus on other areas of the application until this refactor is complete. - -If you're unsure whether your idea falls into the preview category, feel free to ask us [directly in discord](https://discord.gg/zmR9N35cjK) or create a GitHub issue! - -## Development Setup - -### Local Development - -1. Start the database and Redis services: - - ```bash - # From project root - docker-compose up -d - ``` - -2. Navigate to the web app directory: - - ```bash - cd apps/web - ``` - -3. Copy `.env.example` to `.env.local`: - - ```bash - # Unix/Linux/Mac - cp .env.example .env.local - - # Windows Command Prompt - copy .env.example .env.local - - # Windows PowerShell - Copy-Item .env.example .env.local - ``` - -4. Configure required environment variables in `.env.local`: - - **Required Variables:** - - ```bash - # Database (matches docker-compose.yaml) - DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut" - - # Generate a secure secret for Better Auth - BETTER_AUTH_SECRET="your-generated-secret-here" - NEXT_PUBLIC_SITE_URL="http://localhost:3000" - - # Redis (matches docker-compose.yaml) - UPSTASH_REDIS_REST_URL="http://localhost:8079" - UPSTASH_REDIS_REST_TOKEN="example_token" - - # Development - NODE_ENV="development" - ``` - - **Generate BETTER_AUTH_SECRET:** - - ```bash - # Unix/Linux/Mac - openssl rand -base64 32 - - # Windows PowerShell (simple method) - [System.Web.Security.Membership]::GeneratePassword(32, 0) - - # Cross-platform (using Node.js) - node -e "console.log(require('crypto').randomBytes(32).toString('base64'))" - - # Or use an online generator: https://generate-secret.vercel.app/32 - ``` - -5. Run database migrations: `bun run db:migrate` -6. Start the development server: `bun run dev` - -## How to Contribute - -### Reporting Bugs - -- Use the bug report template -- Include steps to reproduce -- Provide screenshots if applicable - -### Suggesting Features - -- Use the feature request template -- Explain the use case -- Consider implementation details - -### Code Contributions - -1. Create a new branch: `git checkout -b feature/your-feature-name` -2. Make your changes -3. Navigate to the web app directory: `cd apps/web` -4. Run the linter: `bun run lint` -5. Format your code: `bunx biome format --write .` -6. Commit your changes with a descriptive message -7. Push to your fork and create a pull request - -## Code Style - -- We use Biome for code formatting and linting -- Run `bunx biome format --write .` from the `apps/web` directory to format code -- Run `bun run lint` from the `apps/web` directory to check for linting issues -- Follow the existing code patterns - -## Pull Request Process - -1. Fill out the pull request template completely -2. Link any related issues -3. Ensure CI passes -4. Request review from maintainers -5. Address any feedback - -## Community - -- Be respectful and inclusive -- Follow our Code of Conduct -- Help others in discussions and issues - -Thank you for contributing! +# Contributing to OpenCut + +⚠️ We are currently NOT accepting feature PRs while we build out the core editor. + +If you want to contribute: + +1. Open an issue first to discuss +2. Wait for maintainer approval +3. Only then start coding + +Critical bug fixes may be accepted on a case-by-case basis. + +Thank you for your interest in contributing to OpenCut! This document provides guidelines and instructions for contributing. + +## Getting Started + +### Prerequisites + +- [Node.js](https://nodejs.org/en/) (v18 or later) +- [Bun](https://bun.sh/docs/installation) + (for `npm` alternative) +- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) + +> **Note:** Docker is optional, but it's essential for running the local database and Redis services. If you're planning to contribute to frontend features, you can skip the Docker setup. If you have followed the steps below in [Setup](#setup), you're all set to go! + +### Setup + +1. Fork the repository +2. Clone your fork locally +3. Navigate to the web app directory: `cd apps/web` +4. Copy `.env.example` to `.env.local`: + + ```bash + # Unix/Linux/Mac + cp .env.example .env.local + + # Windows Command Prompt + copy .env.example .env.local + + # Windows PowerShell + Copy-Item .env.example .env.local + ``` + +5. Install dependencies: `bun install` +6. Start the development server: `bun run dev` + +> **Note:** If you see an error like `Unsupported URL Type "workspace:*"` when running `npm install`, you have two options: +> +> 1. Upgrade to a recent npm version (v9 or later), which has full workspace protocol support. +> 2. Use an alternative package manager such as **bun** or **pnpm**. + +## What to Focus On + +**🎯 Good Areas to Contribute:** + +- Timeline functionality and UI improvements +- Project management features +- Performance optimizations +- Bug fixes in existing functionality +- UI/UX improvements +- Documentation and testing + +**⚠️ Areas to Avoid:** + +- Preview panel enhancements (text fonts, stickers, effects) +- Export functionality improvements +- Preview rendering optimizations + +**Why?** We're currently planning a major refactor of the preview system. The current preview renders DOM elements (HTML), but we're moving to a binary rendering approach similar to CapCut. This new system will ensure consistency between preview and export, and provide much better performance and quality. + +The current HTML-based preview is essentially a prototype - the binary approach will be the "real deal." To avoid wasted effort, please focus on other areas of the application until this refactor is complete. + +If you're unsure whether your idea falls into the preview category, feel free to ask us [directly in discord](https://discord.gg/zmR9N35cjK) or create a GitHub issue! + +## Development Setup + +### Local Development + +1. Start the database and Redis services: + + ```bash + # From project root + docker-compose up -d + ``` + +2. Navigate to the web app directory: + + ```bash + cd apps/web + ``` + +3. Copy `.env.example` to `.env.local`: + + ```bash + # Unix/Linux/Mac + cp .env.example .env.local + + # Windows Command Prompt + copy .env.example .env.local + + # Windows PowerShell + Copy-Item .env.example .env.local + ``` + +4. Configure required environment variables in `.env.local`: + + **Required Variables:** + + ```bash + # Database (matches docker-compose.yaml) + DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut" + + # Generate a secure secret for Better Auth + BETTER_AUTH_SECRET="your-generated-secret-here" + NEXT_PUBLIC_SITE_URL="http://localhost:3000" + + # Redis (matches docker-compose.yaml) + UPSTASH_REDIS_REST_URL="http://localhost:8079" + UPSTASH_REDIS_REST_TOKEN="example_token" + + # Development + NODE_ENV="development" + ``` + + **Generate BETTER_AUTH_SECRET:** + + ```bash + # Unix/Linux/Mac + openssl rand -base64 32 + + # Windows PowerShell (simple method) + [System.Web.Security.Membership]::GeneratePassword(32, 0) + + # Cross-platform (using Node.js) + node -e "console.log(require('crypto').randomBytes(32).toString('base64'))" + + # Or use an online generator: https://generate-secret.vercel.app/32 + ``` + +5. Run database migrations: `bun run db:migrate` +6. Start the development server: `bun run dev` + +## How to Contribute + +### Reporting Bugs + +- Use the bug report template +- Include steps to reproduce +- Provide screenshots if applicable + +### Suggesting Features + +- Use the feature request template +- Explain the use case +- Consider implementation details + +### Code Contributions + +1. Create a new branch: `git checkout -b feature/your-feature-name` +2. Make your changes +3. Navigate to the web app directory: `cd apps/web` +4. Run the linter: `bun run lint` +5. Format your code: `bunx biome format --write .` +6. Commit your changes with a descriptive message +7. Push to your fork and create a pull request + +## Code Style + +- We use Biome for code formatting and linting +- Run `bunx biome format --write .` from the `apps/web` directory to format code +- Run `bun run lint` from the `apps/web` directory to check for linting issues +- Follow the existing code patterns + +## Pull Request Process + +1. Fill out the pull request template completely +2. Link any related issues +3. Ensure CI passes +4. Request review from maintainers +5. Address any feedback + +## Community + +- Be respectful and inclusive +- Follow our Code of Conduct +- Help others in discussions and issues + +Thank you for contributing! diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 01965d41..001466ae 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,70 +1,70 @@ -name: Bug report -description: Create a report to help us improve -title: "[BUG] " -labels: bug -body: - - type: input - id: Platform - attributes: - label: Platform - description: Please enter the platform on which you encountered the bug. - placeholder: e.g. Windows 11, Ubuntu 14.04 - validations: - required: true - - type: input - id: Browser - attributes: - label: Browser - description: Please enter the browser on which you encountered the bug. - placeholder: e.g. Chrome 137, Firefox 137, Safari 17 - validations: - required: true - - type: textarea - id: current-behavior - attributes: - label: Current Behavior - description: A concise description of what you're experiencing. - validations: - required: true - - type: textarea - id: expected-behavior - attributes: - label: Expected Behavior - description: A concise description of what you expected to happen. - validations: - required: false - - type: dropdown - id: recurrence-probability - attributes: - label: Recurrence Probability - description: How often does this bug occur? - options: - - Always - - Usually - - Sometimes - - Seldom - default: 0 - validations: - required: true - - type: textarea - id: steps-to-reproduce - attributes: - label: Steps To Reproduce - description: Steps to reproduce the behavior. - placeholder: | - 1. Go to '...' - 2. Click on '....' - 3. Scroll down to '....' - 4. See error - validations: - required: true - - type: textarea - id: additional-context - attributes: - label: Anything else? - description: | - Links? References? Anything that will give us more context about the issue you are encountering! - - Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. - validations: - required: false +name: Bug report +description: Create a report to help us improve +title: "[BUG] " +labels: bug +body: + - type: input + id: Platform + attributes: + label: Platform + description: Please enter the platform on which you encountered the bug. + placeholder: e.g. Windows 11, Ubuntu 14.04 + validations: + required: true + - type: input + id: Browser + attributes: + label: Browser + description: Please enter the browser on which you encountered the bug. + placeholder: e.g. Chrome 137, Firefox 137, Safari 17 + validations: + required: true + - type: textarea + id: current-behavior + attributes: + label: Current Behavior + description: A concise description of what you're experiencing. + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: false + - type: dropdown + id: recurrence-probability + attributes: + label: Recurrence Probability + description: How often does this bug occur? + options: + - Always + - Usually + - Sometimes + - Seldom + default: 0 + validations: + required: true + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + - type: textarea + id: additional-context + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 336e2d6a..cdeea8d9 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,42 +1,42 @@ -name: Feature request -description: Suggest an idea for OpenCut -title: "[FEATURE] " -labels: enhancement -body: - - type: markdown - attributes: - value: Please make sure that no duplicated issues has already been delivered. - - type: textarea - id: problem - attributes: - label: Problem - placeholder: Is your feature request related to a problem? Please describe. - description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - validations: - required: true - - type: textarea - id: solution - attributes: - label: Solution - placeholder: Describe the solution you'd like. - description: A clear and concise description of what you want to happen. - validations: - required: true - - type: textarea - id: alternative - attributes: - label: Alternative - placeholder: Describe alternatives you've considered. - description: A clear and concise description of any alternative solutions or features you've considered. - validations: - required: true - - type: textarea - id: additional-context - attributes: - label: Anything else? - description: | - Links? References? Anything that will give us more context about the issue you are encountering! - - Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. - validations: - required: false +name: Feature request +description: Suggest an idea for OpenCut +title: "[FEATURE] " +labels: enhancement +body: + - type: markdown + attributes: + value: Please make sure that no duplicated issues has already been delivered. + - type: textarea + id: problem + attributes: + label: Problem + placeholder: Is your feature request related to a problem? Please describe. + description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + validations: + required: true + - type: textarea + id: solution + attributes: + label: Solution + placeholder: Describe the solution you'd like. + description: A clear and concise description of what you want to happen. + validations: + required: true + - type: textarea + id: alternative + attributes: + label: Alternative + placeholder: Describe alternatives you've considered. + description: A clear and concise description of any alternative solutions or features you've considered. + validations: + required: true + - type: textarea + id: additional-context + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 1369bbab..3105845f 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -1,28 +1,28 @@ -# Security Policy - -## Supported Versions - -| Version | Supported | -| ------- | ------------------ | -| 1.x.x | :white_check_mark: | - -## Reporting a Vulnerability - -We take security vulnerabilities seriously. If you discover a security vulnerability within OpenCut, please send an email to security@opencut.app. All security vulnerabilities will be promptly addressed. - -Please do not report security vulnerabilities through public GitHub issues. - -### What to include in your report - -- Description of the vulnerability -- Steps to reproduce -- Potential impact -- Any suggested fixes - -### Response timeline - -- We will acknowledge receipt within 48 hours -- We will provide a detailed response within 5 business days -- We will keep you updated on our progress - -Thank you for helping keep OpenCut secure! +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 1.x.x | :white_check_mark: | + +## Reporting a Vulnerability + +We take security vulnerabilities seriously. If you discover a security vulnerability within OpenCut, please send an email to security@opencut.app. All security vulnerabilities will be promptly addressed. + +Please do not report security vulnerabilities through public GitHub issues. + +### What to include in your report + +- Description of the vulnerability +- Steps to reproduce +- Potential impact +- Any suggested fixes + +### Response timeline + +- We will acknowledge receipt within 48 hours +- We will provide a detailed response within 5 business days +- We will keep you updated on our progress + +Thank you for helping keep OpenCut secure! diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md index 1df3d7bd..88b5797a 100644 --- a/.github/SUPPORT.md +++ b/.github/SUPPORT.md @@ -1,27 +1,27 @@ -# Getting Help - -Thanks for using OpenCut! If you need help, here are your options: - -## Documentation - -- Check our [README](../README.md) for basic setup instructions -- Review the [Contributing Guidelines](CONTRIBUTING.md) for development setup - -## Issues - -- **Bug reports**: Use the bug report template -- **Feature requests**: Use the feature request template -- **Questions**: Use GitHub Discussions for general questions - -## Community - -- Join our discussions on GitHub -- Follow the [Code of Conduct](CODE_OF_CONDUCT.md) - -## Response Times - -- Issues are typically triaged within 2-3 business days -- Feature requests may take longer to evaluate -- Security issues are handled with priority - -We appreciate your patience and contributions to making OpenCut better! +# Getting Help + +Thanks for using OpenCut! If you need help, here are your options: + +## Documentation + +- Check our [README](../README.md) for basic setup instructions +- Review the [Contributing Guidelines](CONTRIBUTING.md) for development setup + +## Issues + +- **Bug reports**: Use the bug report template +- **Feature requests**: Use the feature request template +- **Questions**: Use GitHub Discussions for general questions + +## Community + +- Join our discussions on GitHub +- Follow the [Code of Conduct](CODE_OF_CONDUCT.md) + +## Response Times + +- Issues are typically triaged within 2-3 business days +- Feature requests may take longer to evaluate +- Security issues are handled with priority + +We appreciate your patience and contributions to making OpenCut better! diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 77e361d1..c30aecc2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,344 +1,344 @@ ---- -applyTo: "**/*.{ts,tsx,js,jsx}" ---- - -# Project Context - -Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's lightning-fast formatter and linter. - -## Key Principles - -- Zero configuration required -- Subsecond performance -- Maximum type safety -- AI-friendly code generation - -## Before Writing Code - -1. Analyze existing patterns in the codebase -2. Consider edge cases and error scenarios -3. Follow the rules below strictly -4. Validate accessibility requirements - -## Rules - -### Accessibility (a11y) - -- Don't use `accessKey` attribute on any HTML element. -- Don't set `aria-hidden="true"` on focusable elements. -- Don't add ARIA roles, states, and properties to elements that don't support them. -- Don't use distracting elements like `` or ``. -- Only use the `scope` prop on `` elements. -- Don't assign non-interactive ARIA roles to interactive HTML elements. -- Make sure label elements have text content and are associated with an input. -- Don't assign interactive ARIA roles to non-interactive HTML elements. -- Don't assign `tabIndex` to non-interactive HTML elements. -- Don't use positive integers for `tabIndex` property. -- Don't include "image", "picture", or "photo" in img alt prop. -- Don't use explicit role property that's the same as the implicit/default role. -- Make static elements with click handlers use a valid role attribute. -- Always include a `title` element for SVG elements. -- Give all elements requiring alt text meaningful information for screen readers. -- Make sure anchors have content that's accessible to screen readers. -- Assign `tabIndex` to non-interactive HTML elements with `aria-activedescendant`. -- Include all required ARIA attributes for elements with ARIA roles. -- Make sure ARIA properties are valid for the element's supported roles. -- Always include a `type` attribute for button elements. -- Make elements with interactive roles and handlers focusable. -- Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`). -- Always include a `lang` attribute on the html element. -- Always include a `title` attribute for iframe elements. -- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`. -- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`. -- Include caption tracks for audio and video elements. -- Use semantic elements instead of role attributes in JSX. -- Make sure all anchors are valid and navigable. -- Ensure all ARIA properties (`aria-*`) are valid. -- Use valid, non-abstract ARIA roles for elements with ARIA roles. -- Use valid ARIA state and property values. -- Use valid values for the `autocomplete` attribute on input elements. -- Use correct ISO language/country codes for the `lang` attribute. - -### Code Complexity and Quality - -- Don't use consecutive spaces in regular expression literals. -- Don't use the `arguments` object. -- Don't use primitive type aliases or misleading types. -- Don't use the comma operator. -- Don't use empty type parameters in type aliases and interfaces. -- Don't write functions that exceed a given Cognitive Complexity score. -- Don't nest describe() blocks too deeply in test files. -- Don't use unnecessary boolean casts. -- Don't use unnecessary callbacks with flatMap. -- Use for...of statements instead of Array.forEach. -- Don't create classes that only have static members (like a static namespace). -- Don't use this and super in static contexts. -- Don't use unnecessary catch clauses. -- Don't use unnecessary constructors. -- Don't use unnecessary continue statements. -- Don't export empty modules that don't change anything. -- Don't use unnecessary escape sequences in regular expression literals. -- Don't use unnecessary fragments. -- Don't use unnecessary labels. -- Don't use unnecessary nested block statements. -- Don't rename imports, exports, and destructured assignments to the same name. -- Don't use unnecessary string or template literal concatenation. -- Don't use String.raw in template literals when there are no escape sequences. -- Don't use useless case statements in switch statements. -- Don't use ternary operators when simpler alternatives exist. -- Don't use useless `this` aliasing. -- Don't use any or unknown as type constraints. -- Don't initialize variables to undefined. -- Don't use the void operators (they're not familiar). -- Use arrow functions instead of function expressions. -- Use Date.now() to get milliseconds since the Unix Epoch. -- Use .flatMap() instead of map().flat() when possible. -- Use literal property access instead of computed property access. -- Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work. -- Use concise optional chaining instead of chained logical expressions. -- Use regular expression literals instead of the RegExp constructor when possible. -- Don't use number literal object member names that aren't base 10 or use underscore separators. -- Remove redundant terms from logical expressions. -- Use while loops instead of for loops when you don't need initializer and update expressions. -- Don't pass children as props. -- Don't reassign const variables. -- Don't use constant expressions in conditions. -- Don't use `Math.min` and `Math.max` to clamp values when the result is constant. -- Don't return a value from a constructor. -- Don't use empty character classes in regular expression literals. -- Don't use empty destructuring patterns. -- Don't call global object properties as functions. -- Don't declare functions and vars that are accessible outside their block. -- Make sure builtins are correctly instantiated. -- Don't use super() incorrectly inside classes. Also check that super() is called in classes that extend other constructors. -- Don't use variables and function parameters before they're declared. -- Don't use 8 and 9 escape sequences in string literals. -- Don't use literal numbers that lose precision. - -### React and JSX Best Practices - -- Don't use the return value of React.render. -- Make sure all dependencies are correctly specified in React hooks. -- Make sure all React hooks are called from the top level of component functions. -- Don't forget key props in iterators and collection literals. -- Don't destructure props inside JSX components in Solid projects. -- Don't define React components inside other components. -- Don't use event handlers on non-interactive elements. -- Don't assign to React component props. -- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element. -- Don't use dangerous JSX props. -- Don't use Array index in keys. -- Don't insert comments as text nodes. -- Don't assign JSX properties multiple times. -- Don't add extra closing tags for components without children. -- Use `<>...` instead of `...`. -- Watch out for possible "wrong" semicolons inside JSX elements. - -### Correctness and Safety - -- Don't assign a value to itself. -- Don't return a value from a setter. -- Don't compare expressions that modify string case with non-compliant values. -- Don't use lexical declarations in switch clauses. -- Don't use variables that haven't been declared in the document. -- Don't write unreachable code. -- Make sure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass. -- Don't use control flow statements in finally blocks. -- Don't use optional chaining where undefined values aren't allowed. -- Don't have unused function parameters. -- Don't have unused imports. -- Don't have unused labels. -- Don't have unused private class members. -- Don't have unused variables. -- Make sure void (self-closing) elements don't have children. -- Don't return a value from a function with the return type 'void' -- Use isNaN() when checking for NaN. -- Make sure "for" loop update clauses move the counter in the right direction. -- Make sure typeof expressions are compared to valid values. -- Make sure generator functions contain yield. -- Don't use await inside loops. -- Don't use bitwise operators. -- Don't use expressions where the operation doesn't change the value. -- Make sure Promise-like statements are handled appropriately. -- Don't use **dirname and **filename in the global scope. -- Prevent import cycles. -- Don't use configured elements. -- Don't hardcode sensitive data like API keys and tokens. -- Don't let variable declarations shadow variables from outer scopes. -- Don't use the TypeScript directive @ts-ignore. -- Prevent duplicate polyfills from Polyfill.io. -- Don't use useless backreferences in regular expressions that always match empty strings. -- Don't use unnecessary escapes in string literals. -- Don't use useless undefined. -- Make sure getters and setters for the same property are next to each other in class and object definitions. -- Make sure object literals are declared consistently (defaults to explicit definitions). -- Use static Response methods instead of new Response() constructor when possible. -- Make sure switch-case statements are exhaustive. -- Make sure the `preconnect` attribute is used when using Google Fonts. -- Use `Array#{indexOf,lastIndexOf}()` instead of `Array#{findIndex,findLastIndex}()` when looking for the index of an item. -- Make sure iterable callbacks return consistent values. -- Use `with { type: "json" }` for JSON module imports. -- Use numeric separators in numeric literals. -- Use object spread instead of `Object.assign()` when constructing new objects. -- Always use the radix argument when using `parseInt()`. -- Make sure JSDoc comment lines start with a single asterisk, except for the first one. -- Include a description parameter for `Symbol()`. -- Don't use spread (`...`) syntax on accumulators. -- Don't use the `delete` operator. -- Don't access namespace imports dynamically. -- Don't use namespace imports. -- Declare regex literals at the top level. -- Don't use `target="_blank"` without `rel="noopener"`. - -### TypeScript Best Practices - -- Don't use TypeScript enums. -- Don't export imported variables. -- Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions. -- Don't use TypeScript namespaces. -- Don't use non-null assertions with the `!` postfix operator. -- Don't use parameter properties in class constructors. -- Don't use user-defined types. -- Use `as const` instead of literal types and type annotations. -- Use either `T[]` or `Array` consistently. -- Initialize each enum member value explicitly. -- Use `export type` for types. -- Use `import type` for types. -- Make sure all enum members are literal values. -- Don't use TypeScript const enum. -- Don't declare empty interfaces. -- Don't let variables evolve into any type through reassignments. -- Don't use the any type. -- Don't misuse the non-null assertion operator (!) in TypeScript files. -- Don't use implicit any type on variable declarations. -- Don't merge interfaces and classes unsafely. -- Don't use overload signatures that aren't next to each other. -- Use the namespace keyword instead of the module keyword to declare TypeScript namespaces. - -### Style and Consistency - -- Don't use global `eval()`. -- Don't use callbacks in asynchronous tests and hooks. -- Don't use negation in `if` statements that have `else` clauses. -- Don't use nested ternary expressions. -- Don't reassign function parameters. -- This rule lets you specify global variable names you don't want to use in your application. -- Don't use specified modules when loaded by import or require. -- Don't use constants whose value is the upper-case version of their name. -- Use `String.slice()` instead of `String.substr()` and `String.substring()`. -- Don't use template literals if you don't need interpolation or special-character handling. -- Don't use `else` blocks when the `if` block breaks early. -- Don't use yoda expressions. -- Don't use Array constructors. -- Use `at()` instead of integer index access. -- Follow curly brace conventions. -- Use `else if` instead of nested `if` statements in `else` clauses. -- Use single `if` statements instead of nested `if` clauses. -- Use `new` for all builtins except `String`, `Number`, and `Boolean`. -- Use consistent accessibility modifiers on class properties and methods. -- Use `const` declarations for variables that are only assigned once. -- Put default function parameters and optional function parameters last. -- Include a `default` clause in switch statements. -- Use the `**` operator instead of `Math.pow`. -- Use `for-of` loops when you need the index to extract an item from the iterated array. -- Use `node:assert/strict` over `node:assert`. -- Use the `node:` protocol for Node.js builtin modules. -- Use Number properties instead of global ones. -- Use assignment operator shorthand where possible. -- Use function types instead of object types with call signatures. -- Use template literals over string concatenation. -- Use `new` when throwing an error. -- Don't throw non-Error values. -- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`. -- Use standard constants instead of approximated literals. -- Don't assign values in expressions. -- Don't use async functions as Promise executors. -- Don't reassign exceptions in catch clauses. -- Don't reassign class members. -- Don't compare against -0. -- Don't use labeled statements that aren't loops. -- Don't use void type outside of generic or return types. -- Don't use console. -- Don't use control characters and escape sequences that match control characters in regular expression literals. -- Don't use debugger. -- Don't assign directly to document.cookie. -- Use `===` and `!==`. -- Don't use duplicate case labels. -- Don't use duplicate class members. -- Don't use duplicate conditions in if-else-if chains. -- Don't use two keys with the same name inside objects. -- Don't use duplicate function parameter names. -- Don't have duplicate hooks in describe blocks. -- Don't use empty block statements and static blocks. -- Don't let switch clauses fall through. -- Don't reassign function declarations. -- Don't allow assignments to native objects and read-only global variables. -- Use Number.isFinite instead of global isFinite. -- Use Number.isNaN instead of global isNaN. -- Don't assign to imported bindings. -- Don't use irregular whitespace characters. -- Don't use labels that share a name with a variable. -- Don't use characters made with multiple code points in character class syntax. -- Make sure to use new and constructor properly. -- Don't use shorthand assign when the variable appears on both sides. -- Don't use octal escape sequences in string literals. -- Don't use Object.prototype builtins directly. -- Don't redeclare variables, functions, classes, and types in the same scope. -- Don't have redundant "use strict". -- Don't compare things where both sides are exactly the same. -- Don't let identifiers shadow restricted names. -- Don't use sparse arrays (arrays with holes). -- Don't use template literal placeholder syntax in regular strings. -- Don't use the then property. -- Don't use unsafe negation. -- Don't use var. -- Don't use with statements in non-strict contexts. -- Make sure async functions actually use await. -- Make sure default clauses in switch statements come last. -- Make sure to pass a message value when creating a built-in error. -- Make sure get methods always return a value. -- Use a recommended display strategy with Google Fonts. -- Make sure for-in loops include an if statement. -- Use Array.isArray() instead of instanceof Array. -- Make sure to use the digits argument with Number#toFixed(). -- Make sure to use the "use strict" directive in script files. - -### Next.js Specific Rules - -- Don't use `` elements in Next.js projects. -- Don't use `` elements in Next.js projects. -- Don't import next/document outside of pages/\_document.jsx in Next.js projects. -- Don't use the next/head module in pages/\_document.js on Next.js projects. - -### Testing Best Practices - -- Don't use export or module.exports in test files. -- Don't use focused tests. -- Make sure the assertion function, like expect, is placed inside an it() function call. -- Don't use disabled tests. - -## Common Tasks - -- `npx ultracite init` - Initialize Ultracite in your project -- `npx ultracite format` - Format and fix code automatically -- `npx ultracite lint` - Check for issues without fixing - -## Example: Error Handling - -```typescript -// ✅ Good: Comprehensive error handling -try { - const result = await fetchData(); - return { success: true, data: result }; -} catch (error) { - console.error("API call failed:", error); - return { success: false, error: error.message }; -} - -// ❌ Bad: Swallowing errors -try { - return await fetchData(); -} catch (e) { - console.log(e); -} -``` +--- +applyTo: "**/*.{ts,tsx,js,jsx}" +--- + +# Project Context + +Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's lightning-fast formatter and linter. + +## Key Principles + +- Zero configuration required +- Subsecond performance +- Maximum type safety +- AI-friendly code generation + +## Before Writing Code + +1. Analyze existing patterns in the codebase +2. Consider edge cases and error scenarios +3. Follow the rules below strictly +4. Validate accessibility requirements + +## Rules + +### Accessibility (a11y) + +- Don't use `accessKey` attribute on any HTML element. +- Don't set `aria-hidden="true"` on focusable elements. +- Don't add ARIA roles, states, and properties to elements that don't support them. +- Don't use distracting elements like `` or ``. +- Only use the `scope` prop on `` elements. +- Don't assign non-interactive ARIA roles to interactive HTML elements. +- Make sure label elements have text content and are associated with an input. +- Don't assign interactive ARIA roles to non-interactive HTML elements. +- Don't assign `tabIndex` to non-interactive HTML elements. +- Don't use positive integers for `tabIndex` property. +- Don't include "image", "picture", or "photo" in img alt prop. +- Don't use explicit role property that's the same as the implicit/default role. +- Make static elements with click handlers use a valid role attribute. +- Always include a `title` element for SVG elements. +- Give all elements requiring alt text meaningful information for screen readers. +- Make sure anchors have content that's accessible to screen readers. +- Assign `tabIndex` to non-interactive HTML elements with `aria-activedescendant`. +- Include all required ARIA attributes for elements with ARIA roles. +- Make sure ARIA properties are valid for the element's supported roles. +- Always include a `type` attribute for button elements. +- Make elements with interactive roles and handlers focusable. +- Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`). +- Always include a `lang` attribute on the html element. +- Always include a `title` attribute for iframe elements. +- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`. +- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`. +- Include caption tracks for audio and video elements. +- Use semantic elements instead of role attributes in JSX. +- Make sure all anchors are valid and navigable. +- Ensure all ARIA properties (`aria-*`) are valid. +- Use valid, non-abstract ARIA roles for elements with ARIA roles. +- Use valid ARIA state and property values. +- Use valid values for the `autocomplete` attribute on input elements. +- Use correct ISO language/country codes for the `lang` attribute. + +### Code Complexity and Quality + +- Don't use consecutive spaces in regular expression literals. +- Don't use the `arguments` object. +- Don't use primitive type aliases or misleading types. +- Don't use the comma operator. +- Don't use empty type parameters in type aliases and interfaces. +- Don't write functions that exceed a given Cognitive Complexity score. +- Don't nest describe() blocks too deeply in test files. +- Don't use unnecessary boolean casts. +- Don't use unnecessary callbacks with flatMap. +- Use for...of statements instead of Array.forEach. +- Don't create classes that only have static members (like a static namespace). +- Don't use this and super in static contexts. +- Don't use unnecessary catch clauses. +- Don't use unnecessary constructors. +- Don't use unnecessary continue statements. +- Don't export empty modules that don't change anything. +- Don't use unnecessary escape sequences in regular expression literals. +- Don't use unnecessary fragments. +- Don't use unnecessary labels. +- Don't use unnecessary nested block statements. +- Don't rename imports, exports, and destructured assignments to the same name. +- Don't use unnecessary string or template literal concatenation. +- Don't use String.raw in template literals when there are no escape sequences. +- Don't use useless case statements in switch statements. +- Don't use ternary operators when simpler alternatives exist. +- Don't use useless `this` aliasing. +- Don't use any or unknown as type constraints. +- Don't initialize variables to undefined. +- Don't use the void operators (they're not familiar). +- Use arrow functions instead of function expressions. +- Use Date.now() to get milliseconds since the Unix Epoch. +- Use .flatMap() instead of map().flat() when possible. +- Use literal property access instead of computed property access. +- Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work. +- Use concise optional chaining instead of chained logical expressions. +- Use regular expression literals instead of the RegExp constructor when possible. +- Don't use number literal object member names that aren't base 10 or use underscore separators. +- Remove redundant terms from logical expressions. +- Use while loops instead of for loops when you don't need initializer and update expressions. +- Don't pass children as props. +- Don't reassign const variables. +- Don't use constant expressions in conditions. +- Don't use `Math.min` and `Math.max` to clamp values when the result is constant. +- Don't return a value from a constructor. +- Don't use empty character classes in regular expression literals. +- Don't use empty destructuring patterns. +- Don't call global object properties as functions. +- Don't declare functions and vars that are accessible outside their block. +- Make sure builtins are correctly instantiated. +- Don't use super() incorrectly inside classes. Also check that super() is called in classes that extend other constructors. +- Don't use variables and function parameters before they're declared. +- Don't use 8 and 9 escape sequences in string literals. +- Don't use literal numbers that lose precision. + +### React and JSX Best Practices + +- Don't use the return value of React.render. +- Make sure all dependencies are correctly specified in React hooks. +- Make sure all React hooks are called from the top level of component functions. +- Don't forget key props in iterators and collection literals. +- Don't destructure props inside JSX components in Solid projects. +- Don't define React components inside other components. +- Don't use event handlers on non-interactive elements. +- Don't assign to React component props. +- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element. +- Don't use dangerous JSX props. +- Don't use Array index in keys. +- Don't insert comments as text nodes. +- Don't assign JSX properties multiple times. +- Don't add extra closing tags for components without children. +- Use `<>...` instead of `...`. +- Watch out for possible "wrong" semicolons inside JSX elements. + +### Correctness and Safety + +- Don't assign a value to itself. +- Don't return a value from a setter. +- Don't compare expressions that modify string case with non-compliant values. +- Don't use lexical declarations in switch clauses. +- Don't use variables that haven't been declared in the document. +- Don't write unreachable code. +- Make sure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass. +- Don't use control flow statements in finally blocks. +- Don't use optional chaining where undefined values aren't allowed. +- Don't have unused function parameters. +- Don't have unused imports. +- Don't have unused labels. +- Don't have unused private class members. +- Don't have unused variables. +- Make sure void (self-closing) elements don't have children. +- Don't return a value from a function with the return type 'void' +- Use isNaN() when checking for NaN. +- Make sure "for" loop update clauses move the counter in the right direction. +- Make sure typeof expressions are compared to valid values. +- Make sure generator functions contain yield. +- Don't use await inside loops. +- Don't use bitwise operators. +- Don't use expressions where the operation doesn't change the value. +- Make sure Promise-like statements are handled appropriately. +- Don't use **dirname and **filename in the global scope. +- Prevent import cycles. +- Don't use configured elements. +- Don't hardcode sensitive data like API keys and tokens. +- Don't let variable declarations shadow variables from outer scopes. +- Don't use the TypeScript directive @ts-ignore. +- Prevent duplicate polyfills from Polyfill.io. +- Don't use useless backreferences in regular expressions that always match empty strings. +- Don't use unnecessary escapes in string literals. +- Don't use useless undefined. +- Make sure getters and setters for the same property are next to each other in class and object definitions. +- Make sure object literals are declared consistently (defaults to explicit definitions). +- Use static Response methods instead of new Response() constructor when possible. +- Make sure switch-case statements are exhaustive. +- Make sure the `preconnect` attribute is used when using Google Fonts. +- Use `Array#{indexOf,lastIndexOf}()` instead of `Array#{findIndex,findLastIndex}()` when looking for the index of an item. +- Make sure iterable callbacks return consistent values. +- Use `with { type: "json" }` for JSON module imports. +- Use numeric separators in numeric literals. +- Use object spread instead of `Object.assign()` when constructing new objects. +- Always use the radix argument when using `parseInt()`. +- Make sure JSDoc comment lines start with a single asterisk, except for the first one. +- Include a description parameter for `Symbol()`. +- Don't use spread (`...`) syntax on accumulators. +- Don't use the `delete` operator. +- Don't access namespace imports dynamically. +- Don't use namespace imports. +- Declare regex literals at the top level. +- Don't use `target="_blank"` without `rel="noopener"`. + +### TypeScript Best Practices + +- Don't use TypeScript enums. +- Don't export imported variables. +- Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions. +- Don't use TypeScript namespaces. +- Don't use non-null assertions with the `!` postfix operator. +- Don't use parameter properties in class constructors. +- Don't use user-defined types. +- Use `as const` instead of literal types and type annotations. +- Use either `T[]` or `Array` consistently. +- Initialize each enum member value explicitly. +- Use `export type` for types. +- Use `import type` for types. +- Make sure all enum members are literal values. +- Don't use TypeScript const enum. +- Don't declare empty interfaces. +- Don't let variables evolve into any type through reassignments. +- Don't use the any type. +- Don't misuse the non-null assertion operator (!) in TypeScript files. +- Don't use implicit any type on variable declarations. +- Don't merge interfaces and classes unsafely. +- Don't use overload signatures that aren't next to each other. +- Use the namespace keyword instead of the module keyword to declare TypeScript namespaces. + +### Style and Consistency + +- Don't use global `eval()`. +- Don't use callbacks in asynchronous tests and hooks. +- Don't use negation in `if` statements that have `else` clauses. +- Don't use nested ternary expressions. +- Don't reassign function parameters. +- This rule lets you specify global variable names you don't want to use in your application. +- Don't use specified modules when loaded by import or require. +- Don't use constants whose value is the upper-case version of their name. +- Use `String.slice()` instead of `String.substr()` and `String.substring()`. +- Don't use template literals if you don't need interpolation or special-character handling. +- Don't use `else` blocks when the `if` block breaks early. +- Don't use yoda expressions. +- Don't use Array constructors. +- Use `at()` instead of integer index access. +- Follow curly brace conventions. +- Use `else if` instead of nested `if` statements in `else` clauses. +- Use single `if` statements instead of nested `if` clauses. +- Use `new` for all builtins except `String`, `Number`, and `Boolean`. +- Use consistent accessibility modifiers on class properties and methods. +- Use `const` declarations for variables that are only assigned once. +- Put default function parameters and optional function parameters last. +- Include a `default` clause in switch statements. +- Use the `**` operator instead of `Math.pow`. +- Use `for-of` loops when you need the index to extract an item from the iterated array. +- Use `node:assert/strict` over `node:assert`. +- Use the `node:` protocol for Node.js builtin modules. +- Use Number properties instead of global ones. +- Use assignment operator shorthand where possible. +- Use function types instead of object types with call signatures. +- Use template literals over string concatenation. +- Use `new` when throwing an error. +- Don't throw non-Error values. +- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`. +- Use standard constants instead of approximated literals. +- Don't assign values in expressions. +- Don't use async functions as Promise executors. +- Don't reassign exceptions in catch clauses. +- Don't reassign class members. +- Don't compare against -0. +- Don't use labeled statements that aren't loops. +- Don't use void type outside of generic or return types. +- Don't use console. +- Don't use control characters and escape sequences that match control characters in regular expression literals. +- Don't use debugger. +- Don't assign directly to document.cookie. +- Use `===` and `!==`. +- Don't use duplicate case labels. +- Don't use duplicate class members. +- Don't use duplicate conditions in if-else-if chains. +- Don't use two keys with the same name inside objects. +- Don't use duplicate function parameter names. +- Don't have duplicate hooks in describe blocks. +- Don't use empty block statements and static blocks. +- Don't let switch clauses fall through. +- Don't reassign function declarations. +- Don't allow assignments to native objects and read-only global variables. +- Use Number.isFinite instead of global isFinite. +- Use Number.isNaN instead of global isNaN. +- Don't assign to imported bindings. +- Don't use irregular whitespace characters. +- Don't use labels that share a name with a variable. +- Don't use characters made with multiple code points in character class syntax. +- Make sure to use new and constructor properly. +- Don't use shorthand assign when the variable appears on both sides. +- Don't use octal escape sequences in string literals. +- Don't use Object.prototype builtins directly. +- Don't redeclare variables, functions, classes, and types in the same scope. +- Don't have redundant "use strict". +- Don't compare things where both sides are exactly the same. +- Don't let identifiers shadow restricted names. +- Don't use sparse arrays (arrays with holes). +- Don't use template literal placeholder syntax in regular strings. +- Don't use the then property. +- Don't use unsafe negation. +- Don't use var. +- Don't use with statements in non-strict contexts. +- Make sure async functions actually use await. +- Make sure default clauses in switch statements come last. +- Make sure to pass a message value when creating a built-in error. +- Make sure get methods always return a value. +- Use a recommended display strategy with Google Fonts. +- Make sure for-in loops include an if statement. +- Use Array.isArray() instead of instanceof Array. +- Make sure to use the digits argument with Number#toFixed(). +- Make sure to use the "use strict" directive in script files. + +### Next.js Specific Rules + +- Don't use `` elements in Next.js projects. +- Don't use `` elements in Next.js projects. +- Don't import next/document outside of pages/\_document.jsx in Next.js projects. +- Don't use the next/head module in pages/\_document.js on Next.js projects. + +### Testing Best Practices + +- Don't use export or module.exports in test files. +- Don't use focused tests. +- Make sure the assertion function, like expect, is placed inside an it() function call. +- Don't use disabled tests. + +## Common Tasks + +- `npx ultracite init` - Initialize Ultracite in your project +- `npx ultracite format` - Format and fix code automatically +- `npx ultracite lint` - Check for issues without fixing + +## Example: Error Handling + +```typescript +// ✅ Good: Comprehensive error handling +try { + const result = await fetchData(); + return { success: true, data: result }; +} catch (error) { + console.error("API call failed:", error); + return { success: false, error: error.message }; +} + +// ❌ Bad: Swallowing errors +try { + return await fetchData(); +} catch (e) { + console.log(e); +} +``` diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c7bdff47..1616f5c0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,11 @@ -⚠️ READ BEFORE SUBMITTING ⚠️ - -We are not currently accepting PRs except for critical bugs. - -If this is a bug fix: -- [ ] I've opened an issue first -- [ ] This was approved by a maintainer - -If this is a feature: - +⚠️ READ BEFORE SUBMITTING ⚠️ + +We are not currently accepting PRs except for critical bugs. + +If this is a bug fix: +- [ ] I've opened an issue first +- [ ] This was approved by a maintainer + +If this is a feature: + This PR will be closed. Please open an issue to discuss first. \ No newline at end of file diff --git a/.github/workflows/bun-ci.yml b/.github/workflows/bun-ci.yml index 83c3eac9..9c34f977 100644 --- a/.github/workflows/bun-ci.yml +++ b/.github/workflows/bun-ci.yml @@ -1,66 +1,66 @@ -name: Bun CI - -concurrency: - group: bun-ci-${{ github.ref }} - cancel-in-progress: true - -on: - push: - branches: [main] - paths-ignore: - - "*.md" - pull_request: - branches: [main] - paths-ignore: - - "*.md" - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - - env: - DATABASE_URL: "postgresql://opencut:opencut@localhost:5432/opencut" - BETTER_AUTH_SECRET: "supersecret" - NEXT_PUBLIC_SITE_URL: "http://localhost:3000" - UPSTASH_REDIS_REST_URL: "https://your-upstash-redis-url" - UPSTASH_REDIS_REST_TOKEN: "your-upstash-redis-token" - NEXT_PUBLIC_MARBLE_API_URL: "https://placeholder.example.com" - MARBLE_WORKSPACE_KEY: "placeholder" - FREESOUND_CLIENT_ID: "placeholder" - FREESOUND_API_KEY: "placeholder" - CLOUDFLARE_ACCOUNT_ID: "placeholder" - R2_ACCESS_KEY_ID: "placeholder" - R2_SECRET_ACCESS_KEY: "placeholder" - R2_BUCKET_NAME: "placeholder" - MODAL_TRANSCRIPTION_URL: "https://placeholder.example.com" - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install Bun - uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 - with: - bun-version: 1.2.18 - - - name: Cache Bun modules - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-1.2.18-${{ hashFiles('apps/web/bun.lock') }} - - - name: Install dependencies - working-directory: apps/web - run: bun install - - - name: Build - working-directory: apps/web - run: bun run build - - - name: Run tests - working-directory: apps/web - run: echo "No tests implemented yet" - continue-on-error: true +name: Bun CI + +concurrency: + group: bun-ci-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: [main] + paths-ignore: + - "*.md" + pull_request: + branches: [main] + paths-ignore: + - "*.md" + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + env: + DATABASE_URL: "postgresql://opencut:opencut@localhost:5432/opencut" + BETTER_AUTH_SECRET: "supersecret" + NEXT_PUBLIC_SITE_URL: "http://localhost:3000" + UPSTASH_REDIS_REST_URL: "https://your-upstash-redis-url" + UPSTASH_REDIS_REST_TOKEN: "your-upstash-redis-token" + NEXT_PUBLIC_MARBLE_API_URL: "https://placeholder.example.com" + MARBLE_WORKSPACE_KEY: "placeholder" + FREESOUND_CLIENT_ID: "placeholder" + FREESOUND_API_KEY: "placeholder" + CLOUDFLARE_ACCOUNT_ID: "placeholder" + R2_ACCESS_KEY_ID: "placeholder" + R2_SECRET_ACCESS_KEY: "placeholder" + R2_BUCKET_NAME: "placeholder" + MODAL_TRANSCRIPTION_URL: "https://placeholder.example.com" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Bun + uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 + with: + bun-version: 1.2.18 + + - name: Cache Bun modules + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-1.2.18-${{ hashFiles('apps/web/bun.lock') }} + + - name: Install dependencies + working-directory: apps/web + run: bun install + + - name: Build + working-directory: apps/web + run: bun run build + + - name: Run tests + working-directory: apps/web + run: echo "No tests implemented yet" + continue-on-error: true diff --git a/.vscode/settings.json b/.vscode/settings.json index 6caa16da..187722fb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,20 +1,20 @@ -{ - "editor.defaultFormatter": "esbenp.prettier-vscode", - "[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "typescript.tsdk": "node_modules/typescript/lib", - "editor.formatOnSave": true, - "editor.formatOnPaste": true, - "editor.codeActionsOnSave": { - "source.fixAll.biome": "explicit", - "source.organizeImports.biome": "explicit" - }, - "emmet.showExpandedAbbreviation": "never", - "[typescriptreact]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" - } -} +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "typescript.tsdk": "node_modules/typescript/lib", + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.codeActionsOnSave": { + "source.fixAll.biome": "explicit", + "source.organizeImports.biome": "explicit" + }, + "emmet.showExpandedAbbreviation": "never", + "[typescriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + } +} diff --git a/README.md b/README.md index 9e9a8d66..d86b8978 100644 --- a/README.md +++ b/README.md @@ -1,121 +1,121 @@ - - - - - -
- OpenCut Logo - -

OpenCut

-

A free, open-source video editor for web, desktop, and mobile.

-
- -## Sponsors - -Thanks to [Vercel](https://vercel.com?utm_source=github-opencut&utm_campaign=oss) and [fal.ai](https://fal.ai?utm_source=github-opencut&utm_campaign=oss) for their support of open-source software. - - - Vercel OSS Program - - - - Powered by fal.ai - - -## Why? - -- **Privacy**: Your videos stay on your device -- **Free features**: Most basic CapCut features are now paywalled -- **Simple**: People want editors that are easy to use - CapCut proved that - -## Features - -- Timeline-based editing -- Multi-track support -- Real-time preview -- No watermarks or subscriptions -- Analytics provided by [Databuddy](https://www.databuddy.cc?utm_source=opencut), 100% Anonymized & Non-invasive. -- Blog powered by [Marble](https://marblecms.com?utm_source=opencut), Headless CMS. - -## Project Structure - -- `apps/web/` – Main Next.js web application -- `src/components/` – UI and editor components -- `src/hooks/` – Custom React hooks -- `src/lib/` – Utility and API logic -- `src/stores/` – State management (Zustand, etc.) -- `src/types/` – TypeScript types - -## Getting Started - -### Prerequisites - -- [Bun](https://bun.sh/docs/installation) -- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) - -> **Note:** Docker is optional but recommended for running the local database and Redis. If you only want to work on frontend features, you can skip it. - -### Setup - -1. Fork and clone the repository - -2. Copy the environment file: - - ```bash - # Unix/Linux/Mac - cp apps/web/.env.example apps/web/.env.local - - # Windows PowerShell - Copy-Item apps/web/.env.example apps/web/.env.local - ``` - -3. Start the database and Redis: - - ```bash - docker compose up -d db redis serverless-redis-http - ``` - -4. Install dependencies and start the dev server: - - ```bash - bun install - bun dev:web - ``` - -The application will be available at [http://localhost:3000](http://localhost:3000). - -The `.env.example` has sensible defaults that match the Docker Compose config — it should work out of the box. - -### Self-Hosting with Docker - -To run everything (including a production build of the app) in Docker: - -```bash -docker compose up -d -``` - -The app will be available at [http://localhost:3100](http://localhost:3100). - -## Contributing - -We welcome contributions! While we're actively developing and refactoring certain areas, there are plenty of opportunities to contribute effectively. - -**🎯 Focus areas:** Timeline functionality, project management, performance, bug fixes, and UI improvements outside the preview panel. - -**⚠️ Avoid for now:** Preview panel enhancements (fonts, stickers, effects) and export functionality - we're refactoring these with a new binary rendering approach. - -See our [Contributing Guide](.github/CONTRIBUTING.md) for detailed setup instructions, development guidelines, and complete focus area guidance. - -**Quick start for contributors:** - -- Fork the repo and clone locally -- Follow the setup instructions in CONTRIBUTING.md -- Create a feature branch and submit a PR - -## License - -[MIT LICENSE](LICENSE) - ---- - -![Star History Chart](https://api.star-history.com/svg?repos=opencut-app/opencut&type=Date) + + + + + +
+ OpenCut Logo + +

OpenCut

+

A free, open-source video editor for web, desktop, and mobile.

+
+ +## Sponsors + +Thanks to [Vercel](https://vercel.com?utm_source=github-opencut&utm_campaign=oss) and [fal.ai](https://fal.ai?utm_source=github-opencut&utm_campaign=oss) for their support of open-source software. + + + Vercel OSS Program + + + + Powered by fal.ai + + +## Why? + +- **Privacy**: Your videos stay on your device +- **Free features**: Most basic CapCut features are now paywalled +- **Simple**: People want editors that are easy to use - CapCut proved that + +## Features + +- Timeline-based editing +- Multi-track support +- Real-time preview +- No watermarks or subscriptions +- Analytics provided by [Databuddy](https://www.databuddy.cc?utm_source=opencut), 100% Anonymized & Non-invasive. +- Blog powered by [Marble](https://marblecms.com?utm_source=opencut), Headless CMS. + +## Project Structure + +- `apps/web/` – Main Next.js web application +- `src/components/` – UI and editor components +- `src/hooks/` – Custom React hooks +- `src/lib/` – Utility and API logic +- `src/stores/` – State management (Zustand, etc.) +- `src/types/` – TypeScript types + +## Getting Started + +### Prerequisites + +- [Bun](https://bun.sh/docs/installation) +- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) + +> **Note:** Docker is optional but recommended for running the local database and Redis. If you only want to work on frontend features, you can skip it. + +### Setup + +1. Fork and clone the repository + +2. Copy the environment file: + + ```bash + # Unix/Linux/Mac + cp apps/web/.env.example apps/web/.env.local + + # Windows PowerShell + Copy-Item apps/web/.env.example apps/web/.env.local + ``` + +3. Start the database and Redis: + + ```bash + docker compose up -d db redis serverless-redis-http + ``` + +4. Install dependencies and start the dev server: + + ```bash + bun install + bun dev:web + ``` + +The application will be available at [http://localhost:3000](http://localhost:3000). + +The `.env.example` has sensible defaults that match the Docker Compose config — it should work out of the box. + +### Self-Hosting with Docker + +To run everything (including a production build of the app) in Docker: + +```bash +docker compose up -d +``` + +The app will be available at [http://localhost:3100](http://localhost:3100). + +## Contributing + +We welcome contributions! While we're actively developing and refactoring certain areas, there are plenty of opportunities to contribute effectively. + +**🎯 Focus areas:** Timeline functionality, project management, performance, bug fixes, and UI improvements outside the preview panel. + +**⚠️ Avoid for now:** Preview panel enhancements (fonts, stickers, effects) and export functionality - we're refactoring these with a new binary rendering approach. + +See our [Contributing Guide](.github/CONTRIBUTING.md) for detailed setup instructions, development guidelines, and complete focus area guidance. + +**Quick start for contributors:** + +- Fork the repo and clone locally +- Follow the setup instructions in CONTRIBUTING.md +- Create a feature branch and submit a PR + +## License + +[MIT LICENSE](LICENSE) + +--- + +![Star History Chart](https://api.star-history.com/svg?repos=opencut-app/opencut&type=Date) diff --git a/apps/web/.env.example b/apps/web/.env.example index e1943571..48d48b1d 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -1,28 +1,28 @@ -# Environment variables example -# Copy this file to .env.local and update the values as needed - -# Node -NODE_ENV=development - -# Public -NEXT_PUBLIC_SITE_URL=http://localhost:3000 -NEXT_PUBLIC_MARBLE_API_URL=https://api.marblecms.com - -# Server -DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut" -BETTER_AUTH_SECRET=your_better_auth_secret - -UPSTASH_REDIS_REST_URL=http://localhost:8079 -UPSTASH_REDIS_REST_TOKEN=example_token - -MARBLE_WORKSPACE_KEY=your_workspace_key_here - -FREESOUND_CLIENT_ID=your_client_id_here -FREESOUND_API_KEY=your_api_key_here - -CLOUDFLARE_ACCOUNT_ID=your_account_id_here -R2_ACCESS_KEY_ID=your_access_key_here -R2_SECRET_ACCESS_KEY=your_secret_key_here -R2_BUCKET_NAME=opencut-transcription # whatever you named your r2 bucket - +# Environment variables example +# Copy this file to .env.local and update the values as needed + +# Node +NODE_ENV=development + +# Public +NEXT_PUBLIC_SITE_URL=http://localhost:3000 +NEXT_PUBLIC_MARBLE_API_URL=https://api.marblecms.com + +# Server +DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut" +BETTER_AUTH_SECRET=your_better_auth_secret + +UPSTASH_REDIS_REST_URL=http://localhost:8079 +UPSTASH_REDIS_REST_TOKEN=example_token + +MARBLE_WORKSPACE_KEY=your_workspace_key_here + +FREESOUND_CLIENT_ID=your_client_id_here +FREESOUND_API_KEY=your_api_key_here + +CLOUDFLARE_ACCOUNT_ID=your_account_id_here +R2_ACCESS_KEY_ID=your_access_key_here +R2_SECRET_ACCESS_KEY=your_secret_key_here +R2_BUCKET_NAME=opencut-transcription # whatever you named your r2 bucket + MODAL_TRANSCRIPTION_URL=your_modal_url_here \ No newline at end of file diff --git a/apps/web/.gitignore b/apps/web/.gitignore index 4b8a9fe9..57ed3c8e 100644 --- a/apps/web/.gitignore +++ b/apps/web/.gitignore @@ -1,10 +1,10 @@ -# Turborepo -.turbo - -# Env vars -.env* -!.env.example - -.next/ - +# Turborepo +.turbo + +# Env vars +.env* +!.env.example + +.next/ + .font-cache/ \ No newline at end of file diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index f8e13a69..b569d629 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -1,72 +1,72 @@ -FROM oven/bun:alpine AS base - -FROM base AS builder - -WORKDIR /app - -ARG FREESOUND_CLIENT_ID -ARG FREESOUND_API_KEY -ARG NEXT_PUBLIC_MARBLE_API_URL=https://api.marblecms.com -ARG MARBLE_WORKSPACE_KEY=build-placeholder - -COPY package.json package.json -COPY bun.lock bun.lock -COPY turbo.json turbo.json - -COPY apps/web/package.json apps/web/package.json -COPY packages/env/package.json packages/env/package.json -COPY packages/ui/package.json packages/ui/package.json - -RUN bun install - -COPY apps/web/ apps/web/ -COPY packages/env/ packages/env/ -COPY packages/ui/ packages/ui/ - -ENV NODE_ENV=production -ENV NEXT_TELEMETRY_DISABLED=1 - -# Build-time env stubs to pass zod validation -ENV DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut" -ENV BETTER_AUTH_SECRET="build-time-secret" -ENV UPSTASH_REDIS_REST_URL="http://localhost:8079" -ENV UPSTASH_REDIS_REST_TOKEN="example_token" -ENV NEXT_PUBLIC_SITE_URL="http://localhost:3000" -ENV NEXT_PUBLIC_MARBLE_API_URL=$NEXT_PUBLIC_MARBLE_API_URL -ENV MARBLE_WORKSPACE_KEY=$MARBLE_WORKSPACE_KEY -ENV CLOUDFLARE_ACCOUNT_ID="build-placeholder" -ENV R2_ACCESS_KEY_ID="build-placeholder" -ENV R2_SECRET_ACCESS_KEY="build-placeholder" -ENV R2_BUCKET_NAME="build-placeholder" -ENV MODAL_TRANSCRIPTION_URL="http://localhost:0" - -ENV FREESOUND_CLIENT_ID=$FREESOUND_CLIENT_ID -ENV FREESOUND_API_KEY=$FREESOUND_API_KEY - -WORKDIR /app/apps/web -RUN bun run build - -# Production image -FROM base AS runner -WORKDIR /app - -ENV NODE_ENV=production -ENV NEXT_TELEMETRY_DISABLED=1 - -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs - -COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public -COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static - -RUN chown nextjs:nodejs apps - -USER nextjs - -EXPOSE 3000 - -ENV PORT=3000 -ENV HOSTNAME="0.0.0.0" - -CMD ["bun", "apps/web/server.js"] +FROM oven/bun:alpine AS base + +FROM base AS builder + +WORKDIR /app + +ARG FREESOUND_CLIENT_ID +ARG FREESOUND_API_KEY +ARG NEXT_PUBLIC_MARBLE_API_URL=https://api.marblecms.com +ARG MARBLE_WORKSPACE_KEY=build-placeholder + +COPY package.json package.json +COPY bun.lock bun.lock +COPY turbo.json turbo.json + +COPY apps/web/package.json apps/web/package.json +COPY packages/env/package.json packages/env/package.json +COPY packages/ui/package.json packages/ui/package.json + +RUN bun install + +COPY apps/web/ apps/web/ +COPY packages/env/ packages/env/ +COPY packages/ui/ packages/ui/ + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +# Build-time env stubs to pass zod validation +ENV DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut" +ENV BETTER_AUTH_SECRET="build-time-secret" +ENV UPSTASH_REDIS_REST_URL="http://localhost:8079" +ENV UPSTASH_REDIS_REST_TOKEN="example_token" +ENV NEXT_PUBLIC_SITE_URL="http://localhost:3000" +ENV NEXT_PUBLIC_MARBLE_API_URL=$NEXT_PUBLIC_MARBLE_API_URL +ENV MARBLE_WORKSPACE_KEY=$MARBLE_WORKSPACE_KEY +ENV CLOUDFLARE_ACCOUNT_ID="build-placeholder" +ENV R2_ACCESS_KEY_ID="build-placeholder" +ENV R2_SECRET_ACCESS_KEY="build-placeholder" +ENV R2_BUCKET_NAME="build-placeholder" +ENV MODAL_TRANSCRIPTION_URL="http://localhost:0" + +ENV FREESOUND_CLIENT_ID=$FREESOUND_CLIENT_ID +ENV FREESOUND_API_KEY=$FREESOUND_API_KEY + +WORKDIR /app/apps/web +RUN bun run build + +# Production image +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public +COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static + +RUN chown nextjs:nodejs apps + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["bun", "apps/web/server.js"] diff --git a/apps/web/components.json b/apps/web/components.json index 5bcedb31..3c84e31a 100644 --- a/apps/web/components.json +++ b/apps/web/components.json @@ -1,21 +1,21 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "new-york", - "rsc": true, - "tsx": true, - "tailwind": { - "config": "", - "css": "src/app/globals.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - }, - "iconLibrary": "lucide" -} +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/apps/web/content-collections.ts b/apps/web/content-collections.ts index a3455d80..b9904bf6 100644 --- a/apps/web/content-collections.ts +++ b/apps/web/content-collections.ts @@ -1,33 +1,33 @@ -import { defineCollection, defineConfig } from "@content-collections/core"; -import { z } from "zod"; - -const changelog = defineCollection({ - name: "changelog", - directory: "content/changelog", - include: "*.md", - schema: z.object({ - content: z.string(), - version: z.string(), - date: z.string(), - title: z.string(), - description: z.string().optional(), - changes: z.array( - z.object({ - type: z.string(), - text: z.string(), - }), - ), - }), - transform: async (doc, { collection }) => { - const allDocs = await collection.documents(); - const sorted = [...allDocs].sort((a, b) => - b.version.localeCompare(a.version, undefined, { numeric: true }), - ); - const isLatest = sorted[0]?.version === doc.version; - return { ...doc, isLatest }; - }, -}); - -export default defineConfig({ - content: [changelog], -}); +import { defineCollection, defineConfig } from "@content-collections/core"; +import { z } from "zod"; + +const changelog = defineCollection({ + name: "changelog", + directory: "content/changelog", + include: "*.md", + schema: z.object({ + content: z.string(), + version: z.string(), + date: z.string(), + title: z.string(), + description: z.string().optional(), + changes: z.array( + z.object({ + type: z.string(), + text: z.string(), + }), + ), + }), + transform: async (doc, { collection }) => { + const allDocs = await collection.documents(); + const sorted = [...allDocs].sort((a, b) => + b.version.localeCompare(a.version, undefined, { numeric: true }), + ); + const isLatest = sorted[0]?.version === doc.version; + return { ...doc, isLatest }; + }, +}); + +export default defineConfig({ + content: [changelog], +}); diff --git a/apps/web/content/changelog/0.1.0.md b/apps/web/content/changelog/0.1.0.md index 08a0a9ed..9f71ab68 100644 --- a/apps/web/content/changelog/0.1.0.md +++ b/apps/web/content/changelog/0.1.0.md @@ -1,35 +1,35 @@ ---- -version: "0.1.0" -date: "2026-02-23" -title: "Editor foundation" -description: "This first release focuses on making editing faster, clearer, and more reliable for day-to-day use." -changes: - - type: new - text: "Rebuilt the properties panel from scratch. Number inputs now support math expressions and click-drag scrubbing instead of just typing values in." - - type: new - text: "The properties panel went from a flat list of basic text styles to organized sections: transform, blending, typography, spacing, and background controls. Text elements finally have position, scale, and rotation." - - type: new - text: "New color picker with eyedropper, opacity control, and multiple color formats." - - type: new - text: "Bookmarks now support notes, color labels, and optional duration to plan scenes more clearly." - - type: new - text: "Move, scale, and rotate elements directly in the preview panel." - - type: new - text: "Blend modes (Multiply, Screen, Overlay, etc). Only available on text elements for now." - - type: new - text: "Paste images, videos, or audio from your clipboard straight into the editor." - - type: new - text: "Hold Shift while moving or resizing to temporarily turn off snapping." - - type: improved - text: "Expanded font selection from 7 to over 1,000." - - type: improved - text: "Fonts load much faster." - - type: improved - text: "All asset panel tabs now share a consistent layout." - - type: improved - text: "Text rendering properly supports multiple lines, line height, and letter spacing." - - type: improved - text: "Better contrast in the dark theme." - - type: fixed - text: "Fixed undo/redo for drag-and-drop so dropped items are reliably undoable." ---- +--- +version: "0.1.0" +date: "2026-02-23" +title: "Editor foundation" +description: "This first release focuses on making editing faster, clearer, and more reliable for day-to-day use." +changes: + - type: new + text: "Rebuilt the properties panel from scratch. Number inputs now support math expressions and click-drag scrubbing instead of just typing values in." + - type: new + text: "The properties panel went from a flat list of basic text styles to organized sections: transform, blending, typography, spacing, and background controls. Text elements finally have position, scale, and rotation." + - type: new + text: "New color picker with eyedropper, opacity control, and multiple color formats." + - type: new + text: "Bookmarks now support notes, color labels, and optional duration to plan scenes more clearly." + - type: new + text: "Move, scale, and rotate elements directly in the preview panel." + - type: new + text: "Blend modes (Multiply, Screen, Overlay, etc). Only available on text elements for now." + - type: new + text: "Paste images, videos, or audio from your clipboard straight into the editor." + - type: new + text: "Hold Shift while moving or resizing to temporarily turn off snapping." + - type: improved + text: "Expanded font selection from 7 to over 1,000." + - type: improved + text: "Fonts load much faster." + - type: improved + text: "All asset panel tabs now share a consistent layout." + - type: improved + text: "Text rendering properly supports multiple lines, line height, and letter spacing." + - type: improved + text: "Better contrast in the dark theme." + - type: fixed + text: "Fixed undo/redo for drag-and-drop so dropped items are reliably undoable." +--- diff --git a/apps/web/content/changelog/0.3.0.md b/apps/web/content/changelog/0.3.0.md index 345688fa..cbd2037a 100644 --- a/apps/web/content/changelog/0.3.0.md +++ b/apps/web/content/changelog/0.3.0.md @@ -1,82 +1,82 @@ ---- -version: "0.3.0" -date: "2026-03-04" -title: "Brand & polish" -description: "This release adds a brand page with downloadable assets, alongside a few fixes and improvements." -changes: - - type: new - text: "Added a brand page with downloadable brand assets. Assets are also accessible directly from the header logo context menu." - - type: fixed - text: "Sponsor logos now correctly invert in dark mode." - - type: improved - text: "Added a changelog link to the footer." - - type: fixed - text: "Media asset cards in the grid view were larger than their thumbnails, leaving empty space around each item. Cards now size to their content." - - type: improved - text: "Playback performance is dramatically better. The editor used to slow to a crawl during playback, and audio would stutter when interacting with elements mid-play. Both are fixed. Playback now runs at a consistent 60fps." - - type: fixed - text: "Project thumbnails now generate correctly even when the timeline is empty. If you set a background color or gradient with no clips, the thumbnail reflects that instead of staying blank." - - type: fixed - text: "Clicking a selected element no longer keeps the rest of the selection." - - type: improved - text: "More vertical space between timeline tracks. Selection outlines on adjacent tracks no longer overlap each other." - - type: fixed - text: "Track labels on the left side of the timeline were misaligned with the tracks on the right." - - type: improved - text: "Holding Shift or Ctrl while drag-selecting in the timeline now adds to your existing selection instead of replacing it. Matches how Shift/Ctrl+click already worked." - - type: improved - text: "Timeline track rows now highlight when they contain a selected element, making it easier to see which tracks are active at a glance." - - type: fixed - text: "Clicking in the empty space below timeline tracks now seeks the playhead." - - type: fixed - text: "Timeline vertical scroll now works anywhere in the panel, not just over the track labels. The header no longer scrolls with the tracks." - - type: new - text: "Scale now has separate width and height controls. You can stretch or squash elements independently on each axis." - - type: improved - text: "System fonts (Arial, Helvetica, Times New Roman, and others) now appear in the font picker alongside Google Fonts." - - type: fixed - text: "MP4 exports with audio failed on Firefox. The export now completes successfully regardless of browser." - - type: improved - text: "Your projects can disappear if your disk runs low on space. OpenCut now asks the browser to protect them." - - type: fixed - text: "Opening the mask or effects panel for one element, then selecting a different element, would keep the wrong panel open and hide the transform handles for the new selection. Each element now remembers its own panel state independently." - - type: new - text: "Volume control for audio and video elements, both in the properties panel and inline on timeline clips." - - type: new - text: "Speed control for video and audio clips. Includes a maintain pitch option so audio doesn't chipmunk or deepen when you change the rate." - - type: improved - text: "Audio waveforms have gotten an upgrade. The old ones were inaccurate, prone to crashing, and not very useful. The new ones are RMS-based, properly scaled, and only render what's visible so they don't slow things down." - - type: improved - text: "The timeline now opens scrolled to the bottom, so your main video track is visible right away instead of being hidden above the fold." - - type: fixed - text: "Snap guides didn't show when moving a full-canvas element to the center of the frame." - - type: fixed - text: "The rotation handle could float above the editor UI when an element was near the top of the canvas. It now clips to the preview area." - - type: fixed - text: "Images with a blur effect at 0% intensity would disappear completely when a blurred background was also active. Now 0% blur correctly renders the image unchanged." - - type: fixed - text: "Section titles in the properties panel showed the wrong text color when the section wasn't collapsible. Non-collapsible titles now use the correct muted style." - - type: new - text: "Masks. Hide or reveal parts of any video or image layer. Choose from split, rectangle, ellipse, star, heart, diamond, text, and custom masks. Adjust position, size, rotation, feather, and stroke in the properties panel, or drag the handles in the preview." - - type: improved - text: "The settings panel has been redesigned with a cleaner layout and better organization." - - type: new - text: "Layout guides are back and expanded. TikTok was the only option before. Now includes a grid guide plus TikTok, Instagram Reels, YouTube Shorts, and Snapchat Spotlight." - - type: new - text: "Canvas backgrounds are back. Set a blur, solid color, or gradient as the canvas background from the new Background tab in the settings panel." - - type: fixed - text: "Blur looked pixelated at higher intensities instead of producing a real blur. Both the canvas background blur and the blur effect are now fixed." - - type: new - text: "Custom canvas size. Instead of being locked to a handful of presets, you can now enter any width and height you want." - - type: new - text: "Preview zoom and panning. Zoom into the canvas to work on fine details, and pan around to navigate when zoomed in." - - type: new - text: "Stickers panel now has content. Browse and add logos, country flags, and shapes." - - type: improved - text: "You can now select multiple assets in the assets panel. Useful for deleting several at once." - - type: fixed - text: "Auto-generated captions were sometimes inaccurate. The underlying issue has been fixed." - - type: new - text: "You can now import transcript files to generate captions instead of running auto-transcription." - ---- +--- +version: "0.3.0" +date: "2026-03-04" +title: "Brand & polish" +description: "This release adds a brand page with downloadable assets, alongside a few fixes and improvements." +changes: + - type: new + text: "Added a brand page with downloadable brand assets. Assets are also accessible directly from the header logo context menu." + - type: fixed + text: "Sponsor logos now correctly invert in dark mode." + - type: improved + text: "Added a changelog link to the footer." + - type: fixed + text: "Media asset cards in the grid view were larger than their thumbnails, leaving empty space around each item. Cards now size to their content." + - type: improved + text: "Playback performance is dramatically better. The editor used to slow to a crawl during playback, and audio would stutter when interacting with elements mid-play. Both are fixed. Playback now runs at a consistent 60fps." + - type: fixed + text: "Project thumbnails now generate correctly even when the timeline is empty. If you set a background color or gradient with no clips, the thumbnail reflects that instead of staying blank." + - type: fixed + text: "Clicking a selected element no longer keeps the rest of the selection." + - type: improved + text: "More vertical space between timeline tracks. Selection outlines on adjacent tracks no longer overlap each other." + - type: fixed + text: "Track labels on the left side of the timeline were misaligned with the tracks on the right." + - type: improved + text: "Holding Shift or Ctrl while drag-selecting in the timeline now adds to your existing selection instead of replacing it. Matches how Shift/Ctrl+click already worked." + - type: improved + text: "Timeline track rows now highlight when they contain a selected element, making it easier to see which tracks are active at a glance." + - type: fixed + text: "Clicking in the empty space below timeline tracks now seeks the playhead." + - type: fixed + text: "Timeline vertical scroll now works anywhere in the panel, not just over the track labels. The header no longer scrolls with the tracks." + - type: new + text: "Scale now has separate width and height controls. You can stretch or squash elements independently on each axis." + - type: improved + text: "System fonts (Arial, Helvetica, Times New Roman, and others) now appear in the font picker alongside Google Fonts." + - type: fixed + text: "MP4 exports with audio failed on Firefox. The export now completes successfully regardless of browser." + - type: improved + text: "Your projects can disappear if your disk runs low on space. OpenCut now asks the browser to protect them." + - type: fixed + text: "Opening the mask or effects panel for one element, then selecting a different element, would keep the wrong panel open and hide the transform handles for the new selection. Each element now remembers its own panel state independently." + - type: new + text: "Volume control for audio and video elements, both in the properties panel and inline on timeline clips." + - type: new + text: "Speed control for video and audio clips. Includes a maintain pitch option so audio doesn't chipmunk or deepen when you change the rate." + - type: improved + text: "Audio waveforms have gotten an upgrade. The old ones were inaccurate, prone to crashing, and not very useful. The new ones are RMS-based, properly scaled, and only render what's visible so they don't slow things down." + - type: improved + text: "The timeline now opens scrolled to the bottom, so your main video track is visible right away instead of being hidden above the fold." + - type: fixed + text: "Snap guides didn't show when moving a full-canvas element to the center of the frame." + - type: fixed + text: "The rotation handle could float above the editor UI when an element was near the top of the canvas. It now clips to the preview area." + - type: fixed + text: "Images with a blur effect at 0% intensity would disappear completely when a blurred background was also active. Now 0% blur correctly renders the image unchanged." + - type: fixed + text: "Section titles in the properties panel showed the wrong text color when the section wasn't collapsible. Non-collapsible titles now use the correct muted style." + - type: new + text: "Masks. Hide or reveal parts of any video or image layer. Choose from split, rectangle, ellipse, star, heart, diamond, text, and custom masks. Adjust position, size, rotation, feather, and stroke in the properties panel, or drag the handles in the preview." + - type: improved + text: "The settings panel has been redesigned with a cleaner layout and better organization." + - type: new + text: "Layout guides are back and expanded. TikTok was the only option before. Now includes a grid guide plus TikTok, Instagram Reels, YouTube Shorts, and Snapchat Spotlight." + - type: new + text: "Canvas backgrounds are back. Set a blur, solid color, or gradient as the canvas background from the new Background tab in the settings panel." + - type: fixed + text: "Blur looked pixelated at higher intensities instead of producing a real blur. Both the canvas background blur and the blur effect are now fixed." + - type: new + text: "Custom canvas size. Instead of being locked to a handful of presets, you can now enter any width and height you want." + - type: new + text: "Preview zoom and panning. Zoom into the canvas to work on fine details, and pan around to navigate when zoomed in." + - type: new + text: "Stickers panel now has content. Browse and add logos, country flags, and shapes." + - type: improved + text: "You can now select multiple assets in the assets panel. Useful for deleting several at once." + - type: fixed + text: "Auto-generated captions were sometimes inaccurate. The underlying issue has been fixed." + - type: new + text: "You can now import transcript files to generate captions instead of running auto-transcription." + +--- diff --git a/apps/web/drizzle.config.ts b/apps/web/drizzle.config.ts index be2a59fc..e3fd2d19 100644 --- a/apps/web/drizzle.config.ts +++ b/apps/web/drizzle.config.ts @@ -1,23 +1,23 @@ -import type { Config } from "drizzle-kit"; -import * as dotenv from "dotenv"; -import { webEnv } from "@opencut/env/web"; - -// Load the right env file based on environment -if (webEnv.NODE_ENV === "production") { - dotenv.config({ path: ".env.production" }); -} else { - dotenv.config({ path: ".env.local" }); -} - -export default { - schema: "./src/schema.ts", - dialect: "postgresql", - migrations: { - table: "drizzle_migrations", - }, - dbCredentials: { - url: webEnv.DATABASE_URL, - }, - out: "./migrations", - strict: webEnv.NODE_ENV === "production", -} satisfies Config; +import type { Config } from "drizzle-kit"; +import * as dotenv from "dotenv"; +import { webEnv } from "@opencut/env/web"; + +// Load the right env file based on environment +if (webEnv.NODE_ENV === "production") { + dotenv.config({ path: ".env.production" }); +} else { + dotenv.config({ path: ".env.local" }); +} + +export default { + schema: "./src/schema.ts", + dialect: "postgresql", + migrations: { + table: "drizzle_migrations", + }, + dbCredentials: { + url: webEnv.DATABASE_URL, + }, + out: "./migrations", + strict: webEnv.NODE_ENV === "production", +} satisfies Config; diff --git a/apps/web/migrations/0000_brainy_saracen.sql b/apps/web/migrations/0000_brainy_saracen.sql index 2c6ab2b2..3179d6c5 100644 --- a/apps/web/migrations/0000_brainy_saracen.sql +++ b/apps/web/migrations/0000_brainy_saracen.sql @@ -1,62 +1,62 @@ -CREATE TABLE "accounts" ( - "id" text PRIMARY KEY NOT NULL, - "account_id" text NOT NULL, - "provider_id" text NOT NULL, - "user_id" text NOT NULL, - "access_token" text, - "refresh_token" text, - "id_token" text, - "access_token_expires_at" timestamp, - "refresh_token_expires_at" timestamp, - "scope" text, - "password" text, - "created_at" timestamp NOT NULL, - "updated_at" timestamp NOT NULL -); ---> statement-breakpoint -ALTER TABLE "accounts" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "sessions" ( - "id" text PRIMARY KEY NOT NULL, - "expires_at" timestamp NOT NULL, - "token" text NOT NULL, - "created_at" timestamp NOT NULL, - "updated_at" timestamp NOT NULL, - "ip_address" text, - "user_agent" text, - "user_id" text NOT NULL, - CONSTRAINT "sessions_token_unique" UNIQUE("token") -); ---> statement-breakpoint -ALTER TABLE "sessions" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "users" ( - "id" text PRIMARY KEY NOT NULL, - "name" text NOT NULL, - "email" text NOT NULL, - "email_verified" boolean NOT NULL, - "image" text, - "created_at" timestamp NOT NULL, - "updated_at" timestamp NOT NULL, - CONSTRAINT "users_email_unique" UNIQUE("email") -); ---> statement-breakpoint -ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "verifications" ( - "id" text PRIMARY KEY NOT NULL, - "identifier" text NOT NULL, - "value" text NOT NULL, - "expires_at" timestamp NOT NULL, - "created_at" timestamp, - "updated_at" timestamp -); ---> statement-breakpoint -ALTER TABLE "verifications" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "waitlist" ( - "id" text PRIMARY KEY NOT NULL, - "email" text NOT NULL, - "created_at" timestamp NOT NULL, - CONSTRAINT "waitlist_email_unique" UNIQUE("email") -); ---> statement-breakpoint -ALTER TABLE "waitlist" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE TABLE "accounts" ( + "id" text PRIMARY KEY NOT NULL, + "account_id" text NOT NULL, + "provider_id" text NOT NULL, + "user_id" text NOT NULL, + "access_token" text, + "refresh_token" text, + "id_token" text, + "access_token_expires_at" timestamp, + "refresh_token_expires_at" timestamp, + "scope" text, + "password" text, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL +); +--> statement-breakpoint +ALTER TABLE "accounts" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "sessions" ( + "id" text PRIMARY KEY NOT NULL, + "expires_at" timestamp NOT NULL, + "token" text NOT NULL, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL, + "ip_address" text, + "user_agent" text, + "user_id" text NOT NULL, + CONSTRAINT "sessions_token_unique" UNIQUE("token") +); +--> statement-breakpoint +ALTER TABLE "sessions" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "users" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL, + "email_verified" boolean NOT NULL, + "image" text, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL, + CONSTRAINT "users_email_unique" UNIQUE("email") +); +--> statement-breakpoint +ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "verifications" ( + "id" text PRIMARY KEY NOT NULL, + "identifier" text NOT NULL, + "value" text NOT NULL, + "expires_at" timestamp NOT NULL, + "created_at" timestamp, + "updated_at" timestamp +); +--> statement-breakpoint +ALTER TABLE "verifications" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "waitlist" ( + "id" text PRIMARY KEY NOT NULL, + "email" text NOT NULL, + "created_at" timestamp NOT NULL, + CONSTRAINT "waitlist_email_unique" UNIQUE("email") +); +--> statement-breakpoint +ALTER TABLE "waitlist" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/apps/web/migrations/meta/0000_snapshot.json b/apps/web/migrations/meta/0000_snapshot.json index 2f6c9b30..42bae1c6 100644 --- a/apps/web/migrations/meta/0000_snapshot.json +++ b/apps/web/migrations/meta/0000_snapshot.json @@ -1,344 +1,344 @@ -{ - "id": "33a6742f-89da-4ac5-958f-421aa1cf9bd6", - "prevId": "00000000-0000-0000-0000-000000000000", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.accounts": { - "name": "accounts", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "account_id": { - "name": "account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token_expires_at": { - "name": "access_token_expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "refresh_token_expires_at": { - "name": "refresh_token_expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "accounts_user_id_users_id_fk": { - "name": "accounts_user_id_users_id_fk", - "tableFrom": "accounts", - "tableTo": "users", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.sessions": { - "name": "sessions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_agent": { - "name": "user_agent", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "sessions_user_id_users_id_fk": { - "name": "sessions_user_id_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "sessions_token_unique": { - "name": "sessions_token_unique", - "nullsNotDistinct": false, - "columns": ["token"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.users": { - "name": "users", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "nullsNotDistinct": false, - "columns": ["email"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.verifications": { - "name": "verifications", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.waitlist": { - "name": "waitlist", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "waitlist_email_unique": { - "name": "waitlist_email_unique", - "nullsNotDistinct": false, - "columns": ["email"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} +{ + "id": "33a6742f-89da-4ac5-958f-421aa1cf9bd6", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.verifications": { + "name": "verifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/apps/web/migrations/meta/_journal.json b/apps/web/migrations/meta/_journal.json index a3cb0863..a91398ed 100644 --- a/apps/web/migrations/meta/_journal.json +++ b/apps/web/migrations/meta/_journal.json @@ -1,13 +1,13 @@ -{ - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1750753385927, - "tag": "0000_brainy_saracen", - "breakpoints": true - } - ] -} +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1750753385927, + "tag": "0000_brainy_saracen", + "breakpoints": true + } + ] +} diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index c4b7818f..cb4c1e39 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -1,6 +1,6 @@ -/// -/// -import "./.next/dev/types/routes.d.ts"; - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. +/// +/// +import "./.next/dev/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index b794bff4..36ba239b 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,65 +1,65 @@ -import type { NextConfig } from "next"; -import { withBotId } from "botid/next/config"; -import { withContentCollections } from "@content-collections/next"; -import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare"; - -initOpenNextCloudflareForDev(); - -const nextConfig: NextConfig = { - turbopack: { - rules: { - "*.glsl": { - loaders: [require.resolve("raw-loader")], - as: "*.js", - }, - }, - }, - compiler: { - removeConsole: process.env.NODE_ENV === "production", - }, - reactStrictMode: true, - productionBrowserSourceMaps: true, - output: "standalone", - images: { - remotePatterns: [ - { - protocol: "https", - hostname: "plus.unsplash.com", - }, - { - protocol: "https", - hostname: "images.unsplash.com", - }, - { - protocol: "https", - hostname: "images.marblecms.com", - }, - { - protocol: "https", - hostname: "lh3.googleusercontent.com", - }, - { - protocol: "https", - hostname: "avatars.githubusercontent.com", - }, - { - protocol: "https", - hostname: "api.iconify.design", - }, - { - protocol: "https", - hostname: "api.simplesvg.com", - }, - { - protocol: "https", - hostname: "api.unisvg.com", - }, - { - protocol: "https", - hostname: "cdn.brandfetch.io", - }, - ], - }, -}; - -export default withContentCollections(withBotId(nextConfig)); +import type { NextConfig } from "next"; +import { withBotId } from "botid/next/config"; +import { withContentCollections } from "@content-collections/next"; +import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare"; + +initOpenNextCloudflareForDev(); + +const nextConfig: NextConfig = { + turbopack: { + rules: { + "*.glsl": { + loaders: [require.resolve("raw-loader")], + as: "*.js", + }, + }, + }, + compiler: { + removeConsole: process.env.NODE_ENV === "production", + }, + reactStrictMode: true, + productionBrowserSourceMaps: true, + output: "standalone", + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "plus.unsplash.com", + }, + { + protocol: "https", + hostname: "images.unsplash.com", + }, + { + protocol: "https", + hostname: "images.marblecms.com", + }, + { + protocol: "https", + hostname: "lh3.googleusercontent.com", + }, + { + protocol: "https", + hostname: "avatars.githubusercontent.com", + }, + { + protocol: "https", + hostname: "api.iconify.design", + }, + { + protocol: "https", + hostname: "api.simplesvg.com", + }, + { + protocol: "https", + hostname: "api.unisvg.com", + }, + { + protocol: "https", + hostname: "cdn.brandfetch.io", + }, + ], + }, +}; + +export default withContentCollections(withBotId(nextConfig)); diff --git a/apps/web/public/_headers b/apps/web/public/_headers index 3b460e68..0a731a13 100644 --- a/apps/web/public/_headers +++ b/apps/web/public/_headers @@ -1,2 +1,2 @@ -/_next/static/* - Cache-Control: public,max-age=31536000,immutable +/_next/static/* + Cache-Control: public,max-age=31536000,immutable diff --git a/apps/web/public/browserconfig.xml b/apps/web/public/browserconfig.xml index 39060b21..3e94aae9 100644 --- a/apps/web/public/browserconfig.xml +++ b/apps/web/public/browserconfig.xml @@ -1,11 +1,11 @@ - - - - - - - - #ffffff - - + + + + + + + + #ffffff + + \ No newline at end of file diff --git a/apps/web/public/ffmpeg/ffmpeg-core.js b/apps/web/public/ffmpeg/ffmpeg-core.js index 405d1dbc..be51b58a 100644 --- a/apps/web/public/ffmpeg/ffmpeg-core.js +++ b/apps/web/public/ffmpeg/ffmpeg-core.js @@ -1,18 +1,18 @@ -var createFFmpegCore = (() => { - var _scriptDir = - typeof document !== "undefined" && document.currentScript - ? document.currentScript.src - : undefined; - - return (createFFmpegCore = {}) => { - var Module = typeof createFFmpegCore != "undefined" ? createFFmpegCore : {}; // Ensure that createFFmpegCore is defined and available.var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});const NULL=0;const SIZE_I32=Uint32Array.BYTES_PER_ELEMENT;const DEFAULT_ARGS=["./ffmpeg","-nostdin","-y"];const DEFAULT_ARGS_FFPROBE=["./ffprobe"];Module["NULL"]=NULL;Module["SIZE_I32"]=SIZE_I32;Module["DEFAULT_ARGS"]=DEFAULT_ARGS;Module["DEFAULT_ARGS_FFPROBE"]=DEFAULT_ARGS_FFPROBE;Module["ret"]=-1;Module["timeout"]=-1;Module["logger"]=()=>{};Module["progress"]=()=>{};function stringToPtr(str){const len=Module["lengthBytesUTF8"](str)+1;const ptr=Module["_malloc"](len);Module["stringToUTF8"](str,ptr,len);return ptr}function stringsToPtr(strs){const len=strs.length;const ptr=Module["_malloc"](len*SIZE_I32);for(let i=0;i{throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=true;var ENVIRONMENT_IS_NODE=false;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAP64,HEAPU64,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b);Module["HEAP64"]=HEAP64=new BigInt64Array(b);Module["HEAPU64"]=HEAPU64=new BigUint64Array(b)}var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeKeepaliveCounter=0;function keepRuntimeAlive(){return noExitRuntime||runtimeKeepaliveCounter>0}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();SOCKFS.root=FS.mount(SOCKFS,{},null);callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}var wasmBinaryFile;wasmBinaryFile="ffmpeg-core.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}catch(err){abort(err)}}function getBinaryPromise(binaryFile){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{if(!response["ok"]){throw"failed to load wasm binary file at '"+binaryFile+"'"}return response["arrayBuffer"]()}).catch(()=>getBinary(binaryFile))}}return Promise.resolve().then(()=>getBinary(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>{return WebAssembly.instantiate(binary,imports)}).then(instance=>{return instance}).then(receiver,reason=>{err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}else{return instantiateArrayBuffer(binaryFile,imports,callback)}}function createWasm(){var info={"a":wasmImports};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["ra"];updateMemoryViews();wasmTable=Module["asm"]["ua"];addOnInit(Module["asm"]["sa"]);removeRunDependency("wasm-instantiate");return exports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err("Module.instantiateWasm callback failed with error: "+e);readyPromiseReject(e)}}instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult).catch(readyPromiseReject);return{}}var ASM_CONSTS={6077464:$0=>{Module.ret=$0}};function send_progress(progress,time){Module.receiveProgress(progress,time)}function is_timeout(diff){if(Module.timeout===-1)return 0;else{return Module.timeout<=diff}}function ExitStatus(status){this.name="ExitStatus";this.message=`Program terminated with exit(${status})`;this.status=status}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}var wasmTableMirror=[];function getWasmTableEntry(funcPtr){var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func}function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return HEAP8[ptr>>0];case"i8":return HEAP8[ptr>>0];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP64[ptr>>3];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];case"*":return HEAPU32[ptr>>2];default:abort(`invalid type for getValue: ${type}`)}}function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":HEAP8[ptr>>0]=value;break;case"i8":HEAP8[ptr>>0]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":HEAP64[ptr>>3]=BigInt(value);break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;case"*":HEAPU32[ptr>>2]=value;break;default:abort(`invalid type for setValue: ${type}`)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function ___assert_fail(condition,filename,line,func){abort(`Assertion failed: ${UTF8ToString(condition)}, at: `+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"])}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24;this.set_type=function(type){HEAPU32[this.ptr+4>>2]=type};this.get_type=function(){return HEAPU32[this.ptr+4>>2]};this.set_destructor=function(destructor){HEAPU32[this.ptr+8>>2]=destructor};this.get_destructor=function(){return HEAPU32[this.ptr+8>>2]};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+12>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+12>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+13>>0]!=0};this.init=function(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)};this.set_adjusted_ptr=function(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr};this.get_adjusted_ptr=function(){return HEAPU32[this.ptr+16>>2]};this.get_exception_ptr=function(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return HEAPU32[this.excPtr>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast}var dlopenMissingError="To use dlopen, you need enable dynamic linking, see https://emscripten.org/docs/compiling/Dynamic-Linking.html";function ___dlsym(handle,symbol){abort(dlopenMissingError)}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};function initRandomFill(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){return view=>crypto.getRandomValues(view)}else abort("initRandomDevice")}function randomFill(view){return(randomFill=initRandomFill())(view)}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var TTY={ttys:[],init:function(){},shutdown:function(){},register:function(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open:function(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close:function(stream){stream.tty.ops.fsync(stream.tty)},fsync:function(stream){stream.tty.ops.fsync(stream.tty)},read:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function zeroMemory(address,size){HEAPU8.fill(0,address,address+size);return address}function alignMemory(size,alignment){return Math.ceil(size/alignment)*alignment}function mmapAlloc(size){size=alignMemory(size,65536);var ptr=_emscripten_builtin_memalign(65536,size);if(!ptr)return 0;return zeroMemory(ptr,size)}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{assert(arrayBuffer,`Loading data file "${url}" failed (no arrayBuffer).`);onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw`Loading data file "${url}" failed.`}});if(dep)addRunDependency(dep)}var preloadPlugins=Module["preloadPlugins"]||[];function FS_handledByPreloadPlugin(byteArray,fullname,finish,onerror){if(typeof Browser!="undefined")Browser.init();var handled=false;preloadPlugins.forEach(function(plugin){if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,onerror);handled=true}});return handled}function FS_createPreloadedFile(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish){var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(FS_handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}}function FS_modeStringToFlags(str){var flagModes={"r":0,"r+":2,"w":512|64|1,"w+":512|64|2,"a":1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags}function FS_getMode(canRead,canWrite){var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode}var WORKERFS={DIR_MODE:16895,FILE_MODE:33279,reader:null,mount:function(mount){assert(ENVIRONMENT_IS_WORKER);if(!WORKERFS.reader)WORKERFS.reader=new FileReaderSync;var root=WORKERFS.createNode(null,"/",WORKERFS.DIR_MODE,0);var createdParents={};function ensureParent(path){var parts=path.split("/");var parent=root;for(var i=0;i=stream.node.size)return 0;var chunk=stream.node.contents.slice(position,position+length);var ab=WORKERFS.reader.readAsArrayBuffer(chunk);buffer.set(new Uint8Array(ab),offset);return chunk.size},write:function(stream,buffer,offset,length,position){throw new FS.ErrnoError(29)},llseek:function(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.size}}if(position<0){throw new FS.ErrnoError(28)}return position}}};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath:(path,opts={})=>{path=PATH_FS.resolve(path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?`${mount}/${path}`:mount+path}path=path?`${node.name}/${path}`:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:()=>{for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd=-1)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS_modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error(`Invalid encoding type "${opts.encoding}"`)}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomLeft=randomFill(randomBuffer).byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.name="ErrnoError";this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS,"WORKERFS":WORKERFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;for(var i=0;i{var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i{for(var i=0;i{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt:function(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAPU32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP64[buf+40>>3]=BigInt(stat.size);HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+56>>3]=BigInt(Math.floor(atime/1e3));HEAPU32[buf+64>>2]=atime%1e3*1e3;HEAP64[buf+72>>3]=BigInt(Math.floor(mtime/1e3));HEAPU32[buf+80>>2]=mtime%1e3*1e3;HEAP64[buf+88>>3]=BigInt(Math.floor(ctime/1e3));HEAPU32[buf+96>>2]=ctime%1e3*1e3;HEAP64[buf+104>>3]=BigInt(stat.ino);return 0},doMsync:function(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function ___syscall__newselect(nfds,readfds,writefds,exceptfds,timeout){try{var total=0;var srcReadLow=readfds?HEAP32[readfds>>2]:0,srcReadHigh=readfds?HEAP32[readfds+4>>2]:0;var srcWriteLow=writefds?HEAP32[writefds>>2]:0,srcWriteHigh=writefds?HEAP32[writefds+4>>2]:0;var srcExceptLow=exceptfds?HEAP32[exceptfds>>2]:0,srcExceptHigh=exceptfds?HEAP32[exceptfds+4>>2]:0;var dstReadLow=0,dstReadHigh=0;var dstWriteLow=0,dstWriteHigh=0;var dstExceptLow=0,dstExceptHigh=0;var allLow=(readfds?HEAP32[readfds>>2]:0)|(writefds?HEAP32[writefds>>2]:0)|(exceptfds?HEAP32[exceptfds>>2]:0);var allHigh=(readfds?HEAP32[readfds+4>>2]:0)|(writefds?HEAP32[writefds+4>>2]:0)|(exceptfds?HEAP32[exceptfds+4>>2]:0);var check=function(fd,low,high,val){return fd<32?low&val:high&val};for(var fd=0;fd>2]=dstReadLow;HEAP32[readfds+4>>2]=dstReadHigh}if(writefds){HEAP32[writefds>>2]=dstWriteLow;HEAP32[writefds+4>>2]=dstWriteHigh}if(exceptfds){HEAP32[exceptfds>>2]=dstExceptLow;HEAP32[exceptfds+4>>2]=dstExceptHigh}return total}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var SOCKFS={mount:function(mount){Module["websocket"]=Module["websocket"]&&"object"===typeof Module["websocket"]?Module["websocket"]:{};Module["websocket"]._callbacks={};Module["websocket"]["on"]=function(event,callback){if("function"===typeof callback){this._callbacks[event]=callback}return this};Module["websocket"].emit=function(event,param){if("function"===typeof this._callbacks[event]){this._callbacks[event].call(this,param)}};return FS.createNode(null,"/",16384|511,0)},createSocket:function(family,type,protocol){type&=~526336;var streaming=type==1;if(streaming&&protocol&&protocol!=6){throw new FS.ErrnoError(66)}var sock={family:family,type:type,protocol:protocol,server:null,error:null,peers:{},pending:[],recv_queue:[],sock_ops:SOCKFS.websocket_sock_ops};var name=SOCKFS.nextname();var node=FS.createNode(SOCKFS.root,name,49152,0);node.sock=sock;var stream=FS.createStream({path:name,node:node,flags:2,seekable:false,stream_ops:SOCKFS.stream_ops});sock.stream=stream;return sock},getSocket:function(fd){var stream=FS.getStream(fd);if(!stream||!FS.isSocket(stream.node.mode)){return null}return stream.node.sock},stream_ops:{poll:function(stream){var sock=stream.node.sock;return sock.sock_ops.poll(sock)},ioctl:function(stream,request,varargs){var sock=stream.node.sock;return sock.sock_ops.ioctl(sock,request,varargs)},read:function(stream,buffer,offset,length,position){var sock=stream.node.sock;var msg=sock.sock_ops.recvmsg(sock,length);if(!msg){return 0}buffer.set(msg.buffer,offset);return msg.buffer.length},write:function(stream,buffer,offset,length,position){var sock=stream.node.sock;return sock.sock_ops.sendmsg(sock,buffer,offset,length)},close:function(stream){var sock=stream.node.sock;sock.sock_ops.close(sock)}},nextname:function(){if(!SOCKFS.nextname.current){SOCKFS.nextname.current=0}return"socket["+SOCKFS.nextname.current+++"]"},websocket_sock_ops:{createPeer:function(sock,addr,port){var ws;if(typeof addr=="object"){ws=addr;addr=null;port=null}if(ws){if(ws._socket){addr=ws._socket.remoteAddress;port=ws._socket.remotePort}else{var result=/ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url);if(!result){throw new Error("WebSocket URL must be in the format ws(s)://address:port")}addr=result[1];port=parseInt(result[2],10)}}else{try{var runtimeConfig=Module["websocket"]&&"object"===typeof Module["websocket"];var url="ws:#".replace("#","//");if(runtimeConfig){if("string"===typeof Module["websocket"]["url"]){url=Module["websocket"]["url"]}}if(url==="ws://"||url==="wss://"){var parts=addr.split("/");url=url+parts[0]+":"+port+"/"+parts.slice(1).join("/")}var subProtocols="binary";if(runtimeConfig){if("string"===typeof Module["websocket"]["subprotocol"]){subProtocols=Module["websocket"]["subprotocol"]}}var opts=undefined;if(subProtocols!=="null"){subProtocols=subProtocols.replace(/^ +| +$/g,"").split(/ *, */);opts=subProtocols}if(runtimeConfig&&null===Module["websocket"]["subprotocol"]){subProtocols="null";opts=undefined}var WebSocketConstructor;{WebSocketConstructor=WebSocket}ws=new WebSocketConstructor(url,opts);ws.binaryType="arraybuffer"}catch(e){throw new FS.ErrnoError(23)}}var peer={addr:addr,port:port,socket:ws,dgram_send_queue:[]};SOCKFS.websocket_sock_ops.addPeer(sock,peer);SOCKFS.websocket_sock_ops.handlePeerEvents(sock,peer);if(sock.type===2&&typeof sock.sport!="undefined"){peer.dgram_send_queue.push(new Uint8Array([255,255,255,255,"p".charCodeAt(0),"o".charCodeAt(0),"r".charCodeAt(0),"t".charCodeAt(0),(sock.sport&65280)>>8,sock.sport&255]))}return peer},getPeer:function(sock,addr,port){return sock.peers[addr+":"+port]},addPeer:function(sock,peer){sock.peers[peer.addr+":"+peer.port]=peer},removePeer:function(sock,peer){delete sock.peers[peer.addr+":"+peer.port]},handlePeerEvents:function(sock,peer){var first=true;var handleOpen=function(){Module["websocket"].emit("open",sock.stream.fd);try{var queued=peer.dgram_send_queue.shift();while(queued){peer.socket.send(queued);queued=peer.dgram_send_queue.shift()}}catch(e){peer.socket.close()}};function handleMessage(data){if(typeof data=="string"){var encoder=new TextEncoder;data=encoder.encode(data)}else{assert(data.byteLength!==undefined);if(data.byteLength==0){return}data=new Uint8Array(data)}var wasfirst=first;first=false;if(wasfirst&&data.length===10&&data[0]===255&&data[1]===255&&data[2]===255&&data[3]===255&&data[4]==="p".charCodeAt(0)&&data[5]==="o".charCodeAt(0)&&data[6]==="r".charCodeAt(0)&&data[7]==="t".charCodeAt(0)){var newport=data[8]<<8|data[9];SOCKFS.websocket_sock_ops.removePeer(sock,peer);peer.port=newport;SOCKFS.websocket_sock_ops.addPeer(sock,peer);return}sock.recv_queue.push({addr:peer.addr,port:peer.port,data:data});Module["websocket"].emit("message",sock.stream.fd)}if(ENVIRONMENT_IS_NODE){peer.socket.on("open",handleOpen);peer.socket.on("message",function(data,isBinary){if(!isBinary){return}handleMessage(new Uint8Array(data).buffer)});peer.socket.on("close",function(){Module["websocket"].emit("close",sock.stream.fd)});peer.socket.on("error",function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])})}else{peer.socket.onopen=handleOpen;peer.socket.onclose=function(){Module["websocket"].emit("close",sock.stream.fd)};peer.socket.onmessage=function peer_socket_onmessage(event){handleMessage(event.data)};peer.socket.onerror=function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])}}},poll:function(sock){if(sock.type===1&&sock.server){return sock.pending.length?64|1:0}var mask=0;var dest=sock.type===1?SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport):null;if(sock.recv_queue.length||!dest||dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=64|1}if(!dest||dest&&dest.socket.readyState===dest.socket.OPEN){mask|=4}if(dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=16}return mask},ioctl:function(sock,request,arg){switch(request){case 21531:var bytes=0;if(sock.recv_queue.length){bytes=sock.recv_queue[0].data.length}HEAP32[arg>>2]=bytes;return 0;default:return 28}},close:function(sock){if(sock.server){try{sock.server.close()}catch(e){}sock.server=null}var peers=Object.keys(sock.peers);for(var i=0;i>2]=value;return value}function inetPton4(str){var b=str.split(".");for(var i=0;i<4;i++){var tmp=Number(b[i]);if(isNaN(tmp))return null;b[i]=tmp}return(b[0]|b[1]<<8|b[2]<<16|b[3]<<24)>>>0}function jstoi_q(str){return parseInt(str)}function inetPton6(str){var words;var w,offset,z;var valid6regx=/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i;var parts=[];if(!valid6regx.test(str)){return null}if(str==="::"){return[0,0,0,0,0,0,0,0]}if(str.startsWith("::")){str=str.replace("::","Z:")}else{str=str.replace("::",":Z:")}if(str.indexOf(".")>0){str=str.replace(new RegExp("[.]","g"),":");words=str.split(":");words[words.length-4]=jstoi_q(words[words.length-4])+jstoi_q(words[words.length-3])*256;words[words.length-3]=jstoi_q(words[words.length-2])+jstoi_q(words[words.length-1])*256;words=words.slice(0,words.length-2)}else{words=str.split(":")}offset=0;z=0;for(w=0;w>2]=16}HEAP16[sa>>1]=family;HEAP32[sa+4>>2]=addr;HEAP16[sa+2>>1]=_htons(port);break;case 10:addr=inetPton6(addr);zeroMemory(sa,28);if(addrlen){HEAP32[addrlen>>2]=28}HEAP32[sa>>2]=family;HEAP32[sa+8>>2]=addr[0];HEAP32[sa+12>>2]=addr[1];HEAP32[sa+16>>2]=addr[2];HEAP32[sa+20>>2]=addr[3];HEAP16[sa+2>>1]=_htons(port);break;default:return 5}return 0}var DNS={address_map:{id:1,addrs:{},names:{}},lookup_name:function(name){var res=inetPton4(name);if(res!==null){return name}res=inetPton6(name);if(res!==null){return name}var addr;if(DNS.address_map.addrs[name]){addr=DNS.address_map.addrs[name]}else{var id=DNS.address_map.id++;assert(id<65535,"exceeded max address mappings of 65535");addr="172.29."+(id&255)+"."+(id&65280);DNS.address_map.names[addr]=name;DNS.address_map.addrs[name]=addr}return addr},lookup_addr:function(addr){if(DNS.address_map.names[addr]){return DNS.address_map.names[addr]}return null}};function ___syscall_accept4(fd,addr,addrlen,flags,d1,d2){try{var sock=getSocketFromFD(fd);var newsock=sock.sock_ops.accept(sock);if(addr){var errno=writeSockaddr(addr,newsock.family,DNS.lookup_name(newsock.daddr),newsock.dport,addrlen)}return newsock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function inetNtop4(addr){return(addr&255)+"."+(addr>>8&255)+"."+(addr>>16&255)+"."+(addr>>24&255)}function inetNtop6(ints){var str="";var word=0;var longest=0;var lastzero=0;var zstart=0;var len=0;var i=0;var parts=[ints[0]&65535,ints[0]>>16,ints[1]&65535,ints[1]>>16,ints[2]&65535,ints[2]>>16,ints[3]&65535,ints[3]>>16];var hasipv4=true;var v4part="";for(i=0;i<5;i++){if(parts[i]!==0){hasipv4=false;break}}if(hasipv4){v4part=inetNtop4(parts[6]|parts[7]<<16);if(parts[5]===-1){str="::ffff:";str+=v4part;return str}if(parts[5]===0){str="::";if(v4part==="0.0.0.0")v4part="";if(v4part==="0.0.0.1")v4part="1";str+=v4part;return str}}for(word=0;word<8;word++){if(parts[word]===0){if(word-lastzero>1){len=0}lastzero=word;len++}if(len>longest){longest=len;zstart=word-longest+1}}for(word=0;word<8;word++){if(longest>1){if(parts[word]===0&&word>=zstart&&word>1];var port=_ntohs(HEAPU16[sa+2>>1]);var addr;switch(family){case 2:if(salen!==16){return{errno:28}}addr=HEAP32[sa+4>>2];addr=inetNtop4(addr);break;case 10:if(salen!==28){return{errno:28}}addr=[HEAP32[sa+8>>2],HEAP32[sa+12>>2],HEAP32[sa+16>>2],HEAP32[sa+20>>2]];addr=inetNtop6(addr);break;default:return{errno:5}}return{family:family,addr:addr,port:port}}function getSocketAddress(addrp,addrlen,allowNull){if(allowNull&&addrp===0)return null;var info=readSockaddr(addrp,addrlen);if(info.errno)throw new FS.ErrnoError(info.errno);info.addr=DNS.lookup_addr(info.addr)||info.addr;return info}function ___syscall_bind(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.bind(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_connect(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.connect(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_faccessat(dirfd,path,amode,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(amode&~7){return-28}var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.createStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function ___syscall_getdents64(fd,dirp,count){try{var stream=SYSCALLS.getStreamFromFD(fd);if(!stream.getdents){stream.getdents=FS.readdir(stream.path)}var struct_size=280;var pos=0;var off=FS.llseek(stream,0,1);var idx=Math.floor(off/struct_size);while(idx>3]=BigInt(id);HEAP64[dirp+pos+8>>3]=BigInt((idx+1)*struct_size);HEAP16[dirp+pos+16>>1]=280;HEAP8[dirp+pos+18>>0]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size;idx+=1}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_getpeername(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);if(!sock.daddr){return-53}var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(sock.daddr),sock.dport,addrlen);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_getsockname(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(sock.saddr||"0.0.0.0"),sock.sport,addrlen);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_getsockopt(fd,level,optname,optval,optlen,d1){try{var sock=getSocketFromFD(fd);if(level===1){if(optname===4){HEAP32[optval>>2]=sock.error;HEAP32[optlen>>2]=4;sock.error=null;return 0}}return-50}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_listen(fd,backlog){try{var sock=getSocketFromFD(fd);sock.sock_ops.listen(sock,backlog);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.lstat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_mkdirat(dirfd,path,mode){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~6400;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.doStat(nofollow?FS.lstat:FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?SYSCALLS.get():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_poll(fds,nfds,timeout){try{var nonzero=0;for(var i=0;i>2];var events=HEAP16[pollfd+4>>1];var mask=32;var stream=FS.getStream(fd);if(stream){mask=SYSCALLS.DEFAULT_POLLMASK;if(stream.stream_ops.poll){mask=stream.stream_ops.poll(stream)}}mask&=events|8|16;if(mask)nonzero++;HEAP16[pollfd+6>>1]=mask}return nonzero}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_recvfrom(fd,buf,len,flags,addr,addrlen){try{var sock=getSocketFromFD(fd);var msg=sock.sock_ops.recvmsg(sock,len);if(!msg)return 0;if(addr){var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(msg.addr),msg.port,addrlen)}HEAPU8.set(msg.buffer,buf);return msg.buffer.byteLength}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_renameat(olddirfd,oldpath,newdirfd,newpath){try{oldpath=SYSCALLS.getStr(oldpath);newpath=SYSCALLS.getStr(newpath);oldpath=SYSCALLS.calculateAt(olddirfd,oldpath);newpath=SYSCALLS.calculateAt(newdirfd,newpath);FS.rename(oldpath,newpath);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_rmdir(path){try{path=SYSCALLS.getStr(path);FS.rmdir(path);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_sendto(fd,message,length,flags,addr,addr_len){try{var sock=getSocketFromFD(fd);var dest=getSocketAddress(addr,addr_len,true);if(!dest){return FS.write(sock.stream,HEAP8,message,length)}return sock.sock_ops.sendmsg(sock,HEAP8,message,length,dest.addr,dest.port)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_socket(domain,type,protocol){try{var sock=SOCKFS.createSocket(domain,type,protocol);return sock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_unlinkat(dirfd,path,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(flags===0){FS.unlink(path)}else if(flags===512){FS.rmdir(path)}else{abort("Invalid flags passed to unlinkat")}return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var nowIsMonotonic=true;function __emscripten_get_now_is_monotonic(){return nowIsMonotonic}function __emscripten_throw_longjmp(){throw Infinity}function readI53FromI64(ptr){return HEAPU32[ptr>>2]+HEAP32[ptr+4>>2]*4294967296}function __gmtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}function isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}var MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];function ydayFromDate(date){var leap=isLeapYear(date.getFullYear());var monthDaysCumulative=leap?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday}function __localtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}function __mktime_js(tmPtr){var date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var dst=HEAP32[tmPtr+32>>2];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){HEAP32[tmPtr+32>>2]=Number(summerOffset!=winterOffset&&dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4)}HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getYear();return date.getTime()/1e3|0}function __mmap_js(len,prot,flags,fd,off,allocated,addr){try{var stream=SYSCALLS.getStreamFromFD(fd);var res=FS.mmap(stream,len,off,prot,flags);var ptr=res.ptr;HEAP32[allocated>>2]=res.allocated;HEAPU32[addr>>2]=ptr;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function __munmap_js(addr,len,prot,flags,fd,offset){try{var stream=SYSCALLS.getStreamFromFD(fd);if(prot&2){SYSCALLS.doMsync(addr,stream,len,flags,offset)}FS.munmap(stream)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function stringToNewUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8(str,ret,size);return ret}function __tzset_js(timezone,daylight,tzname){var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=stringToNewUTF8(winterName);var summerNamePtr=stringToNewUTF8(summerName);if(summerOffset>2]=winterNamePtr;HEAPU32[tzname+4>>2]=summerNamePtr}else{HEAPU32[tzname>>2]=summerNamePtr;HEAPU32[tzname+4>>2]=winterNamePtr}}function _abort(){abort("")}Module["_abort"]=_abort;function _dlopen(handle){abort(dlopenMissingError)}var readEmAsmArgsArray=[];function readEmAsmArgs(sigPtr,buf){readEmAsmArgsArray.length=0;var ch;buf>>=2;while(ch=HEAPU8[sigPtr++]){buf+=ch!=105&buf;readEmAsmArgsArray.push(ch==105?HEAP32[buf]:(ch==106?HEAP64:HEAPF64)[buf++>>1]);++buf}return readEmAsmArgsArray}function runEmAsmFunction(code,sigPtr,argbuf){var args=readEmAsmArgs(sigPtr,argbuf);return ASM_CONSTS[code].apply(null,args)}function _emscripten_asm_const_int(code,sigPtr,argbuf){return runEmAsmFunction(code,sigPtr,argbuf)}function _emscripten_date_now(){return Date.now()}function getHeapMax(){return 2147483648}function _emscripten_get_heap_max(){return getHeapMax()}var _emscripten_get_now;_emscripten_get_now=()=>performance.now();function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function emscripten_realloc_buffer(size){var b=wasmMemory.buffer;try{wasmMemory.grow(size-b.byteLength+65535>>>16);updateMemoryViews();return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}var alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings}function stringToAscii(str,buffer){for(var i=0;i>0]=str.charCodeAt(i)}HEAP8[buffer>>0]=0}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;stringToAscii(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _proc_exit(code){EXITSTATUS=code;if(!keepRuntimeAlive()){if(Module["onExit"])Module["onExit"](code);ABORT=true}quit_(code,new ExitStatus(code))}function exitJS(status,implicit){EXITSTATUS=status;_proc_exit(status)}var _exit=exitJS;function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var rightsBase=0;var rightsInheriting=0;var flags=0;{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4}HEAP8[pbuf>>0]=type;HEAP16[pbuf+2>>1]=flags;HEAP64[pbuf+8>>3]=BigInt(rightsBase);HEAP64[pbuf+16>>3]=BigInt(rightsInheriting);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var MAX_INT53=9007199254740992;var MIN_INT53=-9007199254740992;function bigintToI53Checked(num){return numMAX_INT53?NaN:Number(num)}function _fd_seek(fd,offset,whence,newOffset){try{offset=bigintToI53Checked(offset);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(typeof offset!=="undefined"){offset+=curr}}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _getaddrinfo(node,service,hint,out){var addr=0;var port=0;var flags=0;var family=0;var type=0;var proto=0;var ai;function allocaddrinfo(family,type,proto,canon,addr,port){var sa,salen,ai;var errno;salen=family===10?28:16;addr=family===10?inetNtop6(addr):inetNtop4(addr);sa=_malloc(salen);errno=writeSockaddr(sa,family,addr,port);assert(!errno);ai=_malloc(32);HEAP32[ai+4>>2]=family;HEAP32[ai+8>>2]=type;HEAP32[ai+12>>2]=proto;HEAPU32[ai+24>>2]=canon;HEAPU32[ai+20>>2]=sa;if(family===10){HEAP32[ai+16>>2]=28}else{HEAP32[ai+16>>2]=16}HEAP32[ai+28>>2]=0;return ai}if(hint){flags=HEAP32[hint>>2];family=HEAP32[hint+4>>2];type=HEAP32[hint+8>>2];proto=HEAP32[hint+12>>2]}if(type&&!proto){proto=type===2?17:6}if(!type&&proto){type=proto===17?2:1}if(proto===0){proto=6}if(type===0){type=1}if(!node&&!service){return-2}if(flags&~(1|2|4|1024|8|16|32)){return-1}if(hint!==0&&HEAP32[hint>>2]&2&&!node){return-1}if(flags&32){return-2}if(type!==0&&type!==1&&type!==2){return-7}if(family!==0&&family!==2&&family!==10){return-6}if(service){service=UTF8ToString(service);port=parseInt(service,10);if(isNaN(port)){if(flags&1024){return-2}return-8}}if(!node){if(family===0){family=2}if((flags&1)===0){if(family===2){addr=_htonl(2130706433)}else{addr=[0,0,0,1]}}ai=allocaddrinfo(family,type,proto,null,addr,port);HEAPU32[out>>2]=ai;return 0}node=UTF8ToString(node);addr=inetPton4(node);if(addr!==null){if(family===0||family===2){family=2}else if(family===10&&flags&8){addr=[0,0,_htonl(65535),addr];family=10}else{return-2}}else{addr=inetPton6(node);if(addr!==null){if(family===0||family===10){family=10}else{return-2}}}if(addr!=null){ai=allocaddrinfo(family,type,proto,node,addr,port);HEAPU32[out>>2]=ai;return 0}if(flags&4){return-2}node=DNS.lookup_name(node);addr=inetPton4(node);if(family===0){family=2}else if(family===10){addr=[0,0,_htonl(65535),addr]}ai=allocaddrinfo(family,type,proto,null,addr,port);HEAPU32[out>>2]=ai;return 0}function _getnameinfo(sa,salen,node,nodelen,serv,servlen,flags){var info=readSockaddr(sa,salen);if(info.errno){return-6}var port=info.port;var addr=info.addr;var overflowed=false;if(node&&nodelen){var lookup;if(flags&1||!(lookup=DNS.lookup_addr(addr))){if(flags&8){return-2}}else{addr=lookup}var numBytesWrittenExclNull=stringToUTF8(addr,node,nodelen);if(numBytesWrittenExclNull+1>=nodelen){overflowed=true}}if(serv&&servlen){port=""+port;var numBytesWrittenExclNull=stringToUTF8(port,serv,servlen);if(numBytesWrittenExclNull+1>=servlen){overflowed=true}}if(overflowed){return-12}return 0}function arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?MONTH_DAYS_LEAP:MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}return thisDate.getFullYear()}return thisDate.getFullYear()-1}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+arraySum(isLeapYear(date.tm_year+1900)?MONTH_DAYS_LEAP:MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}return"PM"},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":function(date){return date.tm_wday},"%W":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.createPreloadedFile=FS_createPreloadedFile;FS.staticInit();var wasmImports={"b":___assert_fail,"f":___cxa_throw,"ka":___dlsym,"R":___syscall__newselect,"L":___syscall_accept4,"K":___syscall_bind,"J":___syscall_connect,"la":___syscall_faccessat,"g":___syscall_fcntl64,"ha":___syscall_fstat64,"U":___syscall_getdents64,"I":___syscall_getpeername,"H":___syscall_getsockname,"G":___syscall_getsockopt,"y":___syscall_ioctl,"F":___syscall_listen,"ea":___syscall_lstat64,"$":___syscall_mkdirat,"fa":___syscall_newfstatat,"w":___syscall_openat,"V":___syscall_poll,"E":___syscall_recvfrom,"T":___syscall_renameat,"S":___syscall_rmdir,"D":___syscall_sendto,"v":___syscall_socket,"ga":___syscall_stat64,"O":___syscall_unlinkat,"ia":__emscripten_get_now_is_monotonic,"M":__emscripten_throw_longjmp,"Y":__gmtime_js,"Z":__localtime_js,"_":__mktime_js,"W":__mmap_js,"X":__munmap_js,"P":__tzset_js,"a":_abort,"t":_dlopen,"oa":_emscripten_asm_const_int,"m":_emscripten_date_now,"Q":_emscripten_get_heap_max,"p":_emscripten_get_now,"ja":_emscripten_memcpy_big,"N":_emscripten_resize_heap,"ca":_environ_get,"da":_environ_sizes_get,"l":_exit,"n":_fd_close,"ba":_fd_fdstat_get,"x":_fd_read,"aa":_fd_seek,"q":_fd_write,"k":_getaddrinfo,"i":_getnameinfo,"pa":invoke_i,"na":invoke_ii,"c":invoke_iii,"o":invoke_iiii,"s":invoke_iiiii,"z":invoke_iiiiii,"r":invoke_iiiiiiiii,"B":invoke_iiiijj,"qa":invoke_iij,"h":invoke_vi,"j":invoke_vii,"d":invoke_viiii,"ma":invoke_viiiiii,"A":invoke_viiiiiiii,"C":is_timeout,"u":send_progress,"e":_strftime};var asm=createWasm();var ___wasm_call_ctors=function(){return(___wasm_call_ctors=Module["asm"]["sa"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["ta"]).apply(null,arguments)};var ___errno_location=function(){return(___errno_location=Module["asm"]["va"]).apply(null,arguments)};var _ntohs=function(){return(_ntohs=Module["asm"]["wa"]).apply(null,arguments)};var _htons=function(){return(_htons=Module["asm"]["xa"]).apply(null,arguments)};var _ffmpeg=Module["_ffmpeg"]=function(){return(_ffmpeg=Module["_ffmpeg"]=Module["asm"]["ya"]).apply(null,arguments)};var _ffprobe=Module["_ffprobe"]=function(){return(_ffprobe=Module["_ffprobe"]=Module["asm"]["za"]).apply(null,arguments)};var _htonl=function(){return(_htonl=Module["asm"]["Aa"]).apply(null,arguments)};var _emscripten_builtin_memalign=function(){return(_emscripten_builtin_memalign=Module["asm"]["Ba"]).apply(null,arguments)};var _setThrew=function(){return(_setThrew=Module["asm"]["Ca"]).apply(null,arguments)};var stackSave=function(){return(stackSave=Module["asm"]["Da"]).apply(null,arguments)};var stackRestore=function(){return(stackRestore=Module["asm"]["Ea"]).apply(null,arguments)};var ___cxa_is_pointer_type=function(){return(___cxa_is_pointer_type=Module["asm"]["Fa"]).apply(null,arguments)};var _ff_h264_cabac_tables=Module["_ff_h264_cabac_tables"]=1546732;var ___start_em_js=Module["___start_em_js"]=6077485;var ___stop_em_js=Module["___stop_em_js"]=6077662;function invoke_iiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vii(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiijj(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vi(index,a1){var sp=stackSave();try{getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiii(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iij(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_i(index){var sp=stackSave();try{return getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_ii(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}Module["setValue"]=setValue;Module["getValue"]=getValue;Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;Module["FS"]=FS;var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); - - return createFFmpegCore.ready; - }; -})(); -if (typeof exports === "object" && typeof module === "object") - module.exports = createFFmpegCore; -else if (typeof define === "function" && define["amd"]) - define([], () => createFFmpegCore); -else if (typeof exports === "object") - exports["createFFmpegCore"] = createFFmpegCore; +var createFFmpegCore = (() => { + var _scriptDir = + typeof document !== "undefined" && document.currentScript + ? document.currentScript.src + : undefined; + + return (createFFmpegCore = {}) => { + var Module = typeof createFFmpegCore != "undefined" ? createFFmpegCore : {}; // Ensure that createFFmpegCore is defined and available.var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});const NULL=0;const SIZE_I32=Uint32Array.BYTES_PER_ELEMENT;const DEFAULT_ARGS=["./ffmpeg","-nostdin","-y"];const DEFAULT_ARGS_FFPROBE=["./ffprobe"];Module["NULL"]=NULL;Module["SIZE_I32"]=SIZE_I32;Module["DEFAULT_ARGS"]=DEFAULT_ARGS;Module["DEFAULT_ARGS_FFPROBE"]=DEFAULT_ARGS_FFPROBE;Module["ret"]=-1;Module["timeout"]=-1;Module["logger"]=()=>{};Module["progress"]=()=>{};function stringToPtr(str){const len=Module["lengthBytesUTF8"](str)+1;const ptr=Module["_malloc"](len);Module["stringToUTF8"](str,ptr,len);return ptr}function stringsToPtr(strs){const len=strs.length;const ptr=Module["_malloc"](len*SIZE_I32);for(let i=0;i{throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=true;var ENVIRONMENT_IS_NODE=false;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAP64,HEAPU64,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b);Module["HEAP64"]=HEAP64=new BigInt64Array(b);Module["HEAPU64"]=HEAPU64=new BigUint64Array(b)}var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeKeepaliveCounter=0;function keepRuntimeAlive(){return noExitRuntime||runtimeKeepaliveCounter>0}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();SOCKFS.root=FS.mount(SOCKFS,{},null);callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}var wasmBinaryFile;wasmBinaryFile="ffmpeg-core.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}catch(err){abort(err)}}function getBinaryPromise(binaryFile){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{if(!response["ok"]){throw"failed to load wasm binary file at '"+binaryFile+"'"}return response["arrayBuffer"]()}).catch(()=>getBinary(binaryFile))}}return Promise.resolve().then(()=>getBinary(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>{return WebAssembly.instantiate(binary,imports)}).then(instance=>{return instance}).then(receiver,reason=>{err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}else{return instantiateArrayBuffer(binaryFile,imports,callback)}}function createWasm(){var info={"a":wasmImports};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["ra"];updateMemoryViews();wasmTable=Module["asm"]["ua"];addOnInit(Module["asm"]["sa"]);removeRunDependency("wasm-instantiate");return exports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err("Module.instantiateWasm callback failed with error: "+e);readyPromiseReject(e)}}instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult).catch(readyPromiseReject);return{}}var ASM_CONSTS={6077464:$0=>{Module.ret=$0}};function send_progress(progress,time){Module.receiveProgress(progress,time)}function is_timeout(diff){if(Module.timeout===-1)return 0;else{return Module.timeout<=diff}}function ExitStatus(status){this.name="ExitStatus";this.message=`Program terminated with exit(${status})`;this.status=status}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}var wasmTableMirror=[];function getWasmTableEntry(funcPtr){var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func}function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return HEAP8[ptr>>0];case"i8":return HEAP8[ptr>>0];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP64[ptr>>3];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];case"*":return HEAPU32[ptr>>2];default:abort(`invalid type for getValue: ${type}`)}}function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":HEAP8[ptr>>0]=value;break;case"i8":HEAP8[ptr>>0]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":HEAP64[ptr>>3]=BigInt(value);break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;case"*":HEAPU32[ptr>>2]=value;break;default:abort(`invalid type for setValue: ${type}`)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function ___assert_fail(condition,filename,line,func){abort(`Assertion failed: ${UTF8ToString(condition)}, at: `+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"])}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24;this.set_type=function(type){HEAPU32[this.ptr+4>>2]=type};this.get_type=function(){return HEAPU32[this.ptr+4>>2]};this.set_destructor=function(destructor){HEAPU32[this.ptr+8>>2]=destructor};this.get_destructor=function(){return HEAPU32[this.ptr+8>>2]};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+12>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+12>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+13>>0]!=0};this.init=function(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)};this.set_adjusted_ptr=function(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr};this.get_adjusted_ptr=function(){return HEAPU32[this.ptr+16>>2]};this.get_exception_ptr=function(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return HEAPU32[this.excPtr>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast}var dlopenMissingError="To use dlopen, you need enable dynamic linking, see https://emscripten.org/docs/compiling/Dynamic-Linking.html";function ___dlsym(handle,symbol){abort(dlopenMissingError)}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};function initRandomFill(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){return view=>crypto.getRandomValues(view)}else abort("initRandomDevice")}function randomFill(view){return(randomFill=initRandomFill())(view)}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var TTY={ttys:[],init:function(){},shutdown:function(){},register:function(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open:function(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close:function(stream){stream.tty.ops.fsync(stream.tty)},fsync:function(stream){stream.tty.ops.fsync(stream.tty)},read:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function zeroMemory(address,size){HEAPU8.fill(0,address,address+size);return address}function alignMemory(size,alignment){return Math.ceil(size/alignment)*alignment}function mmapAlloc(size){size=alignMemory(size,65536);var ptr=_emscripten_builtin_memalign(65536,size);if(!ptr)return 0;return zeroMemory(ptr,size)}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{assert(arrayBuffer,`Loading data file "${url}" failed (no arrayBuffer).`);onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw`Loading data file "${url}" failed.`}});if(dep)addRunDependency(dep)}var preloadPlugins=Module["preloadPlugins"]||[];function FS_handledByPreloadPlugin(byteArray,fullname,finish,onerror){if(typeof Browser!="undefined")Browser.init();var handled=false;preloadPlugins.forEach(function(plugin){if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,onerror);handled=true}});return handled}function FS_createPreloadedFile(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish){var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(FS_handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}}function FS_modeStringToFlags(str){var flagModes={"r":0,"r+":2,"w":512|64|1,"w+":512|64|2,"a":1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags}function FS_getMode(canRead,canWrite){var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode}var WORKERFS={DIR_MODE:16895,FILE_MODE:33279,reader:null,mount:function(mount){assert(ENVIRONMENT_IS_WORKER);if(!WORKERFS.reader)WORKERFS.reader=new FileReaderSync;var root=WORKERFS.createNode(null,"/",WORKERFS.DIR_MODE,0);var createdParents={};function ensureParent(path){var parts=path.split("/");var parent=root;for(var i=0;i=stream.node.size)return 0;var chunk=stream.node.contents.slice(position,position+length);var ab=WORKERFS.reader.readAsArrayBuffer(chunk);buffer.set(new Uint8Array(ab),offset);return chunk.size},write:function(stream,buffer,offset,length,position){throw new FS.ErrnoError(29)},llseek:function(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.size}}if(position<0){throw new FS.ErrnoError(28)}return position}}};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath:(path,opts={})=>{path=PATH_FS.resolve(path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?`${mount}/${path}`:mount+path}path=path?`${node.name}/${path}`:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:()=>{for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd=-1)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS_modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error(`Invalid encoding type "${opts.encoding}"`)}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomLeft=randomFill(randomBuffer).byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.name="ErrnoError";this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS,"WORKERFS":WORKERFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;for(var i=0;i{var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i{for(var i=0;i{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt:function(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAPU32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP64[buf+40>>3]=BigInt(stat.size);HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+56>>3]=BigInt(Math.floor(atime/1e3));HEAPU32[buf+64>>2]=atime%1e3*1e3;HEAP64[buf+72>>3]=BigInt(Math.floor(mtime/1e3));HEAPU32[buf+80>>2]=mtime%1e3*1e3;HEAP64[buf+88>>3]=BigInt(Math.floor(ctime/1e3));HEAPU32[buf+96>>2]=ctime%1e3*1e3;HEAP64[buf+104>>3]=BigInt(stat.ino);return 0},doMsync:function(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function ___syscall__newselect(nfds,readfds,writefds,exceptfds,timeout){try{var total=0;var srcReadLow=readfds?HEAP32[readfds>>2]:0,srcReadHigh=readfds?HEAP32[readfds+4>>2]:0;var srcWriteLow=writefds?HEAP32[writefds>>2]:0,srcWriteHigh=writefds?HEAP32[writefds+4>>2]:0;var srcExceptLow=exceptfds?HEAP32[exceptfds>>2]:0,srcExceptHigh=exceptfds?HEAP32[exceptfds+4>>2]:0;var dstReadLow=0,dstReadHigh=0;var dstWriteLow=0,dstWriteHigh=0;var dstExceptLow=0,dstExceptHigh=0;var allLow=(readfds?HEAP32[readfds>>2]:0)|(writefds?HEAP32[writefds>>2]:0)|(exceptfds?HEAP32[exceptfds>>2]:0);var allHigh=(readfds?HEAP32[readfds+4>>2]:0)|(writefds?HEAP32[writefds+4>>2]:0)|(exceptfds?HEAP32[exceptfds+4>>2]:0);var check=function(fd,low,high,val){return fd<32?low&val:high&val};for(var fd=0;fd>2]=dstReadLow;HEAP32[readfds+4>>2]=dstReadHigh}if(writefds){HEAP32[writefds>>2]=dstWriteLow;HEAP32[writefds+4>>2]=dstWriteHigh}if(exceptfds){HEAP32[exceptfds>>2]=dstExceptLow;HEAP32[exceptfds+4>>2]=dstExceptHigh}return total}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var SOCKFS={mount:function(mount){Module["websocket"]=Module["websocket"]&&"object"===typeof Module["websocket"]?Module["websocket"]:{};Module["websocket"]._callbacks={};Module["websocket"]["on"]=function(event,callback){if("function"===typeof callback){this._callbacks[event]=callback}return this};Module["websocket"].emit=function(event,param){if("function"===typeof this._callbacks[event]){this._callbacks[event].call(this,param)}};return FS.createNode(null,"/",16384|511,0)},createSocket:function(family,type,protocol){type&=~526336;var streaming=type==1;if(streaming&&protocol&&protocol!=6){throw new FS.ErrnoError(66)}var sock={family:family,type:type,protocol:protocol,server:null,error:null,peers:{},pending:[],recv_queue:[],sock_ops:SOCKFS.websocket_sock_ops};var name=SOCKFS.nextname();var node=FS.createNode(SOCKFS.root,name,49152,0);node.sock=sock;var stream=FS.createStream({path:name,node:node,flags:2,seekable:false,stream_ops:SOCKFS.stream_ops});sock.stream=stream;return sock},getSocket:function(fd){var stream=FS.getStream(fd);if(!stream||!FS.isSocket(stream.node.mode)){return null}return stream.node.sock},stream_ops:{poll:function(stream){var sock=stream.node.sock;return sock.sock_ops.poll(sock)},ioctl:function(stream,request,varargs){var sock=stream.node.sock;return sock.sock_ops.ioctl(sock,request,varargs)},read:function(stream,buffer,offset,length,position){var sock=stream.node.sock;var msg=sock.sock_ops.recvmsg(sock,length);if(!msg){return 0}buffer.set(msg.buffer,offset);return msg.buffer.length},write:function(stream,buffer,offset,length,position){var sock=stream.node.sock;return sock.sock_ops.sendmsg(sock,buffer,offset,length)},close:function(stream){var sock=stream.node.sock;sock.sock_ops.close(sock)}},nextname:function(){if(!SOCKFS.nextname.current){SOCKFS.nextname.current=0}return"socket["+SOCKFS.nextname.current+++"]"},websocket_sock_ops:{createPeer:function(sock,addr,port){var ws;if(typeof addr=="object"){ws=addr;addr=null;port=null}if(ws){if(ws._socket){addr=ws._socket.remoteAddress;port=ws._socket.remotePort}else{var result=/ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url);if(!result){throw new Error("WebSocket URL must be in the format ws(s)://address:port")}addr=result[1];port=parseInt(result[2],10)}}else{try{var runtimeConfig=Module["websocket"]&&"object"===typeof Module["websocket"];var url="ws:#".replace("#","//");if(runtimeConfig){if("string"===typeof Module["websocket"]["url"]){url=Module["websocket"]["url"]}}if(url==="ws://"||url==="wss://"){var parts=addr.split("/");url=url+parts[0]+":"+port+"/"+parts.slice(1).join("/")}var subProtocols="binary";if(runtimeConfig){if("string"===typeof Module["websocket"]["subprotocol"]){subProtocols=Module["websocket"]["subprotocol"]}}var opts=undefined;if(subProtocols!=="null"){subProtocols=subProtocols.replace(/^ +| +$/g,"").split(/ *, */);opts=subProtocols}if(runtimeConfig&&null===Module["websocket"]["subprotocol"]){subProtocols="null";opts=undefined}var WebSocketConstructor;{WebSocketConstructor=WebSocket}ws=new WebSocketConstructor(url,opts);ws.binaryType="arraybuffer"}catch(e){throw new FS.ErrnoError(23)}}var peer={addr:addr,port:port,socket:ws,dgram_send_queue:[]};SOCKFS.websocket_sock_ops.addPeer(sock,peer);SOCKFS.websocket_sock_ops.handlePeerEvents(sock,peer);if(sock.type===2&&typeof sock.sport!="undefined"){peer.dgram_send_queue.push(new Uint8Array([255,255,255,255,"p".charCodeAt(0),"o".charCodeAt(0),"r".charCodeAt(0),"t".charCodeAt(0),(sock.sport&65280)>>8,sock.sport&255]))}return peer},getPeer:function(sock,addr,port){return sock.peers[addr+":"+port]},addPeer:function(sock,peer){sock.peers[peer.addr+":"+peer.port]=peer},removePeer:function(sock,peer){delete sock.peers[peer.addr+":"+peer.port]},handlePeerEvents:function(sock,peer){var first=true;var handleOpen=function(){Module["websocket"].emit("open",sock.stream.fd);try{var queued=peer.dgram_send_queue.shift();while(queued){peer.socket.send(queued);queued=peer.dgram_send_queue.shift()}}catch(e){peer.socket.close()}};function handleMessage(data){if(typeof data=="string"){var encoder=new TextEncoder;data=encoder.encode(data)}else{assert(data.byteLength!==undefined);if(data.byteLength==0){return}data=new Uint8Array(data)}var wasfirst=first;first=false;if(wasfirst&&data.length===10&&data[0]===255&&data[1]===255&&data[2]===255&&data[3]===255&&data[4]==="p".charCodeAt(0)&&data[5]==="o".charCodeAt(0)&&data[6]==="r".charCodeAt(0)&&data[7]==="t".charCodeAt(0)){var newport=data[8]<<8|data[9];SOCKFS.websocket_sock_ops.removePeer(sock,peer);peer.port=newport;SOCKFS.websocket_sock_ops.addPeer(sock,peer);return}sock.recv_queue.push({addr:peer.addr,port:peer.port,data:data});Module["websocket"].emit("message",sock.stream.fd)}if(ENVIRONMENT_IS_NODE){peer.socket.on("open",handleOpen);peer.socket.on("message",function(data,isBinary){if(!isBinary){return}handleMessage(new Uint8Array(data).buffer)});peer.socket.on("close",function(){Module["websocket"].emit("close",sock.stream.fd)});peer.socket.on("error",function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])})}else{peer.socket.onopen=handleOpen;peer.socket.onclose=function(){Module["websocket"].emit("close",sock.stream.fd)};peer.socket.onmessage=function peer_socket_onmessage(event){handleMessage(event.data)};peer.socket.onerror=function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])}}},poll:function(sock){if(sock.type===1&&sock.server){return sock.pending.length?64|1:0}var mask=0;var dest=sock.type===1?SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport):null;if(sock.recv_queue.length||!dest||dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=64|1}if(!dest||dest&&dest.socket.readyState===dest.socket.OPEN){mask|=4}if(dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=16}return mask},ioctl:function(sock,request,arg){switch(request){case 21531:var bytes=0;if(sock.recv_queue.length){bytes=sock.recv_queue[0].data.length}HEAP32[arg>>2]=bytes;return 0;default:return 28}},close:function(sock){if(sock.server){try{sock.server.close()}catch(e){}sock.server=null}var peers=Object.keys(sock.peers);for(var i=0;i>2]=value;return value}function inetPton4(str){var b=str.split(".");for(var i=0;i<4;i++){var tmp=Number(b[i]);if(isNaN(tmp))return null;b[i]=tmp}return(b[0]|b[1]<<8|b[2]<<16|b[3]<<24)>>>0}function jstoi_q(str){return parseInt(str)}function inetPton6(str){var words;var w,offset,z;var valid6regx=/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i;var parts=[];if(!valid6regx.test(str)){return null}if(str==="::"){return[0,0,0,0,0,0,0,0]}if(str.startsWith("::")){str=str.replace("::","Z:")}else{str=str.replace("::",":Z:")}if(str.indexOf(".")>0){str=str.replace(new RegExp("[.]","g"),":");words=str.split(":");words[words.length-4]=jstoi_q(words[words.length-4])+jstoi_q(words[words.length-3])*256;words[words.length-3]=jstoi_q(words[words.length-2])+jstoi_q(words[words.length-1])*256;words=words.slice(0,words.length-2)}else{words=str.split(":")}offset=0;z=0;for(w=0;w>2]=16}HEAP16[sa>>1]=family;HEAP32[sa+4>>2]=addr;HEAP16[sa+2>>1]=_htons(port);break;case 10:addr=inetPton6(addr);zeroMemory(sa,28);if(addrlen){HEAP32[addrlen>>2]=28}HEAP32[sa>>2]=family;HEAP32[sa+8>>2]=addr[0];HEAP32[sa+12>>2]=addr[1];HEAP32[sa+16>>2]=addr[2];HEAP32[sa+20>>2]=addr[3];HEAP16[sa+2>>1]=_htons(port);break;default:return 5}return 0}var DNS={address_map:{id:1,addrs:{},names:{}},lookup_name:function(name){var res=inetPton4(name);if(res!==null){return name}res=inetPton6(name);if(res!==null){return name}var addr;if(DNS.address_map.addrs[name]){addr=DNS.address_map.addrs[name]}else{var id=DNS.address_map.id++;assert(id<65535,"exceeded max address mappings of 65535");addr="172.29."+(id&255)+"."+(id&65280);DNS.address_map.names[addr]=name;DNS.address_map.addrs[name]=addr}return addr},lookup_addr:function(addr){if(DNS.address_map.names[addr]){return DNS.address_map.names[addr]}return null}};function ___syscall_accept4(fd,addr,addrlen,flags,d1,d2){try{var sock=getSocketFromFD(fd);var newsock=sock.sock_ops.accept(sock);if(addr){var errno=writeSockaddr(addr,newsock.family,DNS.lookup_name(newsock.daddr),newsock.dport,addrlen)}return newsock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function inetNtop4(addr){return(addr&255)+"."+(addr>>8&255)+"."+(addr>>16&255)+"."+(addr>>24&255)}function inetNtop6(ints){var str="";var word=0;var longest=0;var lastzero=0;var zstart=0;var len=0;var i=0;var parts=[ints[0]&65535,ints[0]>>16,ints[1]&65535,ints[1]>>16,ints[2]&65535,ints[2]>>16,ints[3]&65535,ints[3]>>16];var hasipv4=true;var v4part="";for(i=0;i<5;i++){if(parts[i]!==0){hasipv4=false;break}}if(hasipv4){v4part=inetNtop4(parts[6]|parts[7]<<16);if(parts[5]===-1){str="::ffff:";str+=v4part;return str}if(parts[5]===0){str="::";if(v4part==="0.0.0.0")v4part="";if(v4part==="0.0.0.1")v4part="1";str+=v4part;return str}}for(word=0;word<8;word++){if(parts[word]===0){if(word-lastzero>1){len=0}lastzero=word;len++}if(len>longest){longest=len;zstart=word-longest+1}}for(word=0;word<8;word++){if(longest>1){if(parts[word]===0&&word>=zstart&&word>1];var port=_ntohs(HEAPU16[sa+2>>1]);var addr;switch(family){case 2:if(salen!==16){return{errno:28}}addr=HEAP32[sa+4>>2];addr=inetNtop4(addr);break;case 10:if(salen!==28){return{errno:28}}addr=[HEAP32[sa+8>>2],HEAP32[sa+12>>2],HEAP32[sa+16>>2],HEAP32[sa+20>>2]];addr=inetNtop6(addr);break;default:return{errno:5}}return{family:family,addr:addr,port:port}}function getSocketAddress(addrp,addrlen,allowNull){if(allowNull&&addrp===0)return null;var info=readSockaddr(addrp,addrlen);if(info.errno)throw new FS.ErrnoError(info.errno);info.addr=DNS.lookup_addr(info.addr)||info.addr;return info}function ___syscall_bind(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.bind(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_connect(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.connect(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_faccessat(dirfd,path,amode,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(amode&~7){return-28}var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.createStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function ___syscall_getdents64(fd,dirp,count){try{var stream=SYSCALLS.getStreamFromFD(fd);if(!stream.getdents){stream.getdents=FS.readdir(stream.path)}var struct_size=280;var pos=0;var off=FS.llseek(stream,0,1);var idx=Math.floor(off/struct_size);while(idx>3]=BigInt(id);HEAP64[dirp+pos+8>>3]=BigInt((idx+1)*struct_size);HEAP16[dirp+pos+16>>1]=280;HEAP8[dirp+pos+18>>0]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size;idx+=1}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_getpeername(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);if(!sock.daddr){return-53}var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(sock.daddr),sock.dport,addrlen);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_getsockname(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(sock.saddr||"0.0.0.0"),sock.sport,addrlen);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_getsockopt(fd,level,optname,optval,optlen,d1){try{var sock=getSocketFromFD(fd);if(level===1){if(optname===4){HEAP32[optval>>2]=sock.error;HEAP32[optlen>>2]=4;sock.error=null;return 0}}return-50}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_listen(fd,backlog){try{var sock=getSocketFromFD(fd);sock.sock_ops.listen(sock,backlog);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.lstat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_mkdirat(dirfd,path,mode){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~6400;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.doStat(nofollow?FS.lstat:FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?SYSCALLS.get():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_poll(fds,nfds,timeout){try{var nonzero=0;for(var i=0;i>2];var events=HEAP16[pollfd+4>>1];var mask=32;var stream=FS.getStream(fd);if(stream){mask=SYSCALLS.DEFAULT_POLLMASK;if(stream.stream_ops.poll){mask=stream.stream_ops.poll(stream)}}mask&=events|8|16;if(mask)nonzero++;HEAP16[pollfd+6>>1]=mask}return nonzero}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_recvfrom(fd,buf,len,flags,addr,addrlen){try{var sock=getSocketFromFD(fd);var msg=sock.sock_ops.recvmsg(sock,len);if(!msg)return 0;if(addr){var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(msg.addr),msg.port,addrlen)}HEAPU8.set(msg.buffer,buf);return msg.buffer.byteLength}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_renameat(olddirfd,oldpath,newdirfd,newpath){try{oldpath=SYSCALLS.getStr(oldpath);newpath=SYSCALLS.getStr(newpath);oldpath=SYSCALLS.calculateAt(olddirfd,oldpath);newpath=SYSCALLS.calculateAt(newdirfd,newpath);FS.rename(oldpath,newpath);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_rmdir(path){try{path=SYSCALLS.getStr(path);FS.rmdir(path);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_sendto(fd,message,length,flags,addr,addr_len){try{var sock=getSocketFromFD(fd);var dest=getSocketAddress(addr,addr_len,true);if(!dest){return FS.write(sock.stream,HEAP8,message,length)}return sock.sock_ops.sendmsg(sock,HEAP8,message,length,dest.addr,dest.port)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_socket(domain,type,protocol){try{var sock=SOCKFS.createSocket(domain,type,protocol);return sock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_unlinkat(dirfd,path,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(flags===0){FS.unlink(path)}else if(flags===512){FS.rmdir(path)}else{abort("Invalid flags passed to unlinkat")}return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var nowIsMonotonic=true;function __emscripten_get_now_is_monotonic(){return nowIsMonotonic}function __emscripten_throw_longjmp(){throw Infinity}function readI53FromI64(ptr){return HEAPU32[ptr>>2]+HEAP32[ptr+4>>2]*4294967296}function __gmtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}function isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}var MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];function ydayFromDate(date){var leap=isLeapYear(date.getFullYear());var monthDaysCumulative=leap?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday}function __localtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}function __mktime_js(tmPtr){var date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var dst=HEAP32[tmPtr+32>>2];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){HEAP32[tmPtr+32>>2]=Number(summerOffset!=winterOffset&&dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4)}HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getYear();return date.getTime()/1e3|0}function __mmap_js(len,prot,flags,fd,off,allocated,addr){try{var stream=SYSCALLS.getStreamFromFD(fd);var res=FS.mmap(stream,len,off,prot,flags);var ptr=res.ptr;HEAP32[allocated>>2]=res.allocated;HEAPU32[addr>>2]=ptr;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function __munmap_js(addr,len,prot,flags,fd,offset){try{var stream=SYSCALLS.getStreamFromFD(fd);if(prot&2){SYSCALLS.doMsync(addr,stream,len,flags,offset)}FS.munmap(stream)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function stringToNewUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8(str,ret,size);return ret}function __tzset_js(timezone,daylight,tzname){var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=stringToNewUTF8(winterName);var summerNamePtr=stringToNewUTF8(summerName);if(summerOffset>2]=winterNamePtr;HEAPU32[tzname+4>>2]=summerNamePtr}else{HEAPU32[tzname>>2]=summerNamePtr;HEAPU32[tzname+4>>2]=winterNamePtr}}function _abort(){abort("")}Module["_abort"]=_abort;function _dlopen(handle){abort(dlopenMissingError)}var readEmAsmArgsArray=[];function readEmAsmArgs(sigPtr,buf){readEmAsmArgsArray.length=0;var ch;buf>>=2;while(ch=HEAPU8[sigPtr++]){buf+=ch!=105&buf;readEmAsmArgsArray.push(ch==105?HEAP32[buf]:(ch==106?HEAP64:HEAPF64)[buf++>>1]);++buf}return readEmAsmArgsArray}function runEmAsmFunction(code,sigPtr,argbuf){var args=readEmAsmArgs(sigPtr,argbuf);return ASM_CONSTS[code].apply(null,args)}function _emscripten_asm_const_int(code,sigPtr,argbuf){return runEmAsmFunction(code,sigPtr,argbuf)}function _emscripten_date_now(){return Date.now()}function getHeapMax(){return 2147483648}function _emscripten_get_heap_max(){return getHeapMax()}var _emscripten_get_now;_emscripten_get_now=()=>performance.now();function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function emscripten_realloc_buffer(size){var b=wasmMemory.buffer;try{wasmMemory.grow(size-b.byteLength+65535>>>16);updateMemoryViews();return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}var alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings}function stringToAscii(str,buffer){for(var i=0;i>0]=str.charCodeAt(i)}HEAP8[buffer>>0]=0}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;stringToAscii(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _proc_exit(code){EXITSTATUS=code;if(!keepRuntimeAlive()){if(Module["onExit"])Module["onExit"](code);ABORT=true}quit_(code,new ExitStatus(code))}function exitJS(status,implicit){EXITSTATUS=status;_proc_exit(status)}var _exit=exitJS;function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var rightsBase=0;var rightsInheriting=0;var flags=0;{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4}HEAP8[pbuf>>0]=type;HEAP16[pbuf+2>>1]=flags;HEAP64[pbuf+8>>3]=BigInt(rightsBase);HEAP64[pbuf+16>>3]=BigInt(rightsInheriting);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var MAX_INT53=9007199254740992;var MIN_INT53=-9007199254740992;function bigintToI53Checked(num){return numMAX_INT53?NaN:Number(num)}function _fd_seek(fd,offset,whence,newOffset){try{offset=bigintToI53Checked(offset);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(typeof offset!=="undefined"){offset+=curr}}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}function _getaddrinfo(node,service,hint,out){var addr=0;var port=0;var flags=0;var family=0;var type=0;var proto=0;var ai;function allocaddrinfo(family,type,proto,canon,addr,port){var sa,salen,ai;var errno;salen=family===10?28:16;addr=family===10?inetNtop6(addr):inetNtop4(addr);sa=_malloc(salen);errno=writeSockaddr(sa,family,addr,port);assert(!errno);ai=_malloc(32);HEAP32[ai+4>>2]=family;HEAP32[ai+8>>2]=type;HEAP32[ai+12>>2]=proto;HEAPU32[ai+24>>2]=canon;HEAPU32[ai+20>>2]=sa;if(family===10){HEAP32[ai+16>>2]=28}else{HEAP32[ai+16>>2]=16}HEAP32[ai+28>>2]=0;return ai}if(hint){flags=HEAP32[hint>>2];family=HEAP32[hint+4>>2];type=HEAP32[hint+8>>2];proto=HEAP32[hint+12>>2]}if(type&&!proto){proto=type===2?17:6}if(!type&&proto){type=proto===17?2:1}if(proto===0){proto=6}if(type===0){type=1}if(!node&&!service){return-2}if(flags&~(1|2|4|1024|8|16|32)){return-1}if(hint!==0&&HEAP32[hint>>2]&2&&!node){return-1}if(flags&32){return-2}if(type!==0&&type!==1&&type!==2){return-7}if(family!==0&&family!==2&&family!==10){return-6}if(service){service=UTF8ToString(service);port=parseInt(service,10);if(isNaN(port)){if(flags&1024){return-2}return-8}}if(!node){if(family===0){family=2}if((flags&1)===0){if(family===2){addr=_htonl(2130706433)}else{addr=[0,0,0,1]}}ai=allocaddrinfo(family,type,proto,null,addr,port);HEAPU32[out>>2]=ai;return 0}node=UTF8ToString(node);addr=inetPton4(node);if(addr!==null){if(family===0||family===2){family=2}else if(family===10&&flags&8){addr=[0,0,_htonl(65535),addr];family=10}else{return-2}}else{addr=inetPton6(node);if(addr!==null){if(family===0||family===10){family=10}else{return-2}}}if(addr!=null){ai=allocaddrinfo(family,type,proto,node,addr,port);HEAPU32[out>>2]=ai;return 0}if(flags&4){return-2}node=DNS.lookup_name(node);addr=inetPton4(node);if(family===0){family=2}else if(family===10){addr=[0,0,_htonl(65535),addr]}ai=allocaddrinfo(family,type,proto,null,addr,port);HEAPU32[out>>2]=ai;return 0}function _getnameinfo(sa,salen,node,nodelen,serv,servlen,flags){var info=readSockaddr(sa,salen);if(info.errno){return-6}var port=info.port;var addr=info.addr;var overflowed=false;if(node&&nodelen){var lookup;if(flags&1||!(lookup=DNS.lookup_addr(addr))){if(flags&8){return-2}}else{addr=lookup}var numBytesWrittenExclNull=stringToUTF8(addr,node,nodelen);if(numBytesWrittenExclNull+1>=nodelen){overflowed=true}}if(serv&&servlen){port=""+port;var numBytesWrittenExclNull=stringToUTF8(port,serv,servlen);if(numBytesWrittenExclNull+1>=servlen){overflowed=true}}if(overflowed){return-12}return 0}function arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?MONTH_DAYS_LEAP:MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}return thisDate.getFullYear()}return thisDate.getFullYear()-1}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+arraySum(isLeapYear(date.tm_year+1900)?MONTH_DAYS_LEAP:MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}return"PM"},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":function(date){return date.tm_wday},"%W":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.createPreloadedFile=FS_createPreloadedFile;FS.staticInit();var wasmImports={"b":___assert_fail,"f":___cxa_throw,"ka":___dlsym,"R":___syscall__newselect,"L":___syscall_accept4,"K":___syscall_bind,"J":___syscall_connect,"la":___syscall_faccessat,"g":___syscall_fcntl64,"ha":___syscall_fstat64,"U":___syscall_getdents64,"I":___syscall_getpeername,"H":___syscall_getsockname,"G":___syscall_getsockopt,"y":___syscall_ioctl,"F":___syscall_listen,"ea":___syscall_lstat64,"$":___syscall_mkdirat,"fa":___syscall_newfstatat,"w":___syscall_openat,"V":___syscall_poll,"E":___syscall_recvfrom,"T":___syscall_renameat,"S":___syscall_rmdir,"D":___syscall_sendto,"v":___syscall_socket,"ga":___syscall_stat64,"O":___syscall_unlinkat,"ia":__emscripten_get_now_is_monotonic,"M":__emscripten_throw_longjmp,"Y":__gmtime_js,"Z":__localtime_js,"_":__mktime_js,"W":__mmap_js,"X":__munmap_js,"P":__tzset_js,"a":_abort,"t":_dlopen,"oa":_emscripten_asm_const_int,"m":_emscripten_date_now,"Q":_emscripten_get_heap_max,"p":_emscripten_get_now,"ja":_emscripten_memcpy_big,"N":_emscripten_resize_heap,"ca":_environ_get,"da":_environ_sizes_get,"l":_exit,"n":_fd_close,"ba":_fd_fdstat_get,"x":_fd_read,"aa":_fd_seek,"q":_fd_write,"k":_getaddrinfo,"i":_getnameinfo,"pa":invoke_i,"na":invoke_ii,"c":invoke_iii,"o":invoke_iiii,"s":invoke_iiiii,"z":invoke_iiiiii,"r":invoke_iiiiiiiii,"B":invoke_iiiijj,"qa":invoke_iij,"h":invoke_vi,"j":invoke_vii,"d":invoke_viiii,"ma":invoke_viiiiii,"A":invoke_viiiiiiii,"C":is_timeout,"u":send_progress,"e":_strftime};var asm=createWasm();var ___wasm_call_ctors=function(){return(___wasm_call_ctors=Module["asm"]["sa"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["ta"]).apply(null,arguments)};var ___errno_location=function(){return(___errno_location=Module["asm"]["va"]).apply(null,arguments)};var _ntohs=function(){return(_ntohs=Module["asm"]["wa"]).apply(null,arguments)};var _htons=function(){return(_htons=Module["asm"]["xa"]).apply(null,arguments)};var _ffmpeg=Module["_ffmpeg"]=function(){return(_ffmpeg=Module["_ffmpeg"]=Module["asm"]["ya"]).apply(null,arguments)};var _ffprobe=Module["_ffprobe"]=function(){return(_ffprobe=Module["_ffprobe"]=Module["asm"]["za"]).apply(null,arguments)};var _htonl=function(){return(_htonl=Module["asm"]["Aa"]).apply(null,arguments)};var _emscripten_builtin_memalign=function(){return(_emscripten_builtin_memalign=Module["asm"]["Ba"]).apply(null,arguments)};var _setThrew=function(){return(_setThrew=Module["asm"]["Ca"]).apply(null,arguments)};var stackSave=function(){return(stackSave=Module["asm"]["Da"]).apply(null,arguments)};var stackRestore=function(){return(stackRestore=Module["asm"]["Ea"]).apply(null,arguments)};var ___cxa_is_pointer_type=function(){return(___cxa_is_pointer_type=Module["asm"]["Fa"]).apply(null,arguments)};var _ff_h264_cabac_tables=Module["_ff_h264_cabac_tables"]=1546732;var ___start_em_js=Module["___start_em_js"]=6077485;var ___stop_em_js=Module["___stop_em_js"]=6077662;function invoke_iiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vii(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiijj(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vi(index,a1){var sp=stackSave();try{getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiii(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iij(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_i(index){var sp=stackSave();try{return getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_ii(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}Module["setValue"]=setValue;Module["getValue"]=getValue;Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;Module["FS"]=FS;var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); + + return createFFmpegCore.ready; + }; +})(); +if (typeof exports === "object" && typeof module === "object") + module.exports = createFFmpegCore; +else if (typeof define === "function" && define["amd"]) + define([], () => createFFmpegCore); +else if (typeof exports === "object") + exports["createFFmpegCore"] = createFFmpegCore; diff --git a/apps/web/public/flags/ad.svg b/apps/web/public/flags/ad.svg index 199ff198..dd495953 100644 --- a/apps/web/public/flags/ad.svg +++ b/apps/web/public/flags/ad.svg @@ -1,150 +1,150 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ae.svg b/apps/web/public/flags/ae.svg index 651ac852..810de70d 100644 --- a/apps/web/public/flags/ae.svg +++ b/apps/web/public/flags/ae.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/af.svg b/apps/web/public/flags/af.svg index 4dbe4556..2924328c 100644 --- a/apps/web/public/flags/af.svg +++ b/apps/web/public/flags/af.svg @@ -1,81 +1,81 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ag.svg b/apps/web/public/flags/ag.svg index 243c3d8f..39a4c90b 100644 --- a/apps/web/public/flags/ag.svg +++ b/apps/web/public/flags/ag.svg @@ -1,14 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ai.svg b/apps/web/public/flags/ai.svg index 9c2ea339..0fb6032f 100644 --- a/apps/web/public/flags/ai.svg +++ b/apps/web/public/flags/ai.svg @@ -1,29 +1,29 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/al.svg b/apps/web/public/flags/al.svg index e85d95f0..c1353dd3 100644 --- a/apps/web/public/flags/al.svg +++ b/apps/web/public/flags/al.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/am.svg b/apps/web/public/flags/am.svg index 99fa4dc5..0bcc6cae 100644 --- a/apps/web/public/flags/am.svg +++ b/apps/web/public/flags/am.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/ao.svg b/apps/web/public/flags/ao.svg index b73b1ec5..d37de17b 100644 --- a/apps/web/public/flags/ao.svg +++ b/apps/web/public/flags/ao.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/aq.svg b/apps/web/public/flags/aq.svg index c7e35362..78ce8a5f 100644 --- a/apps/web/public/flags/aq.svg +++ b/apps/web/public/flags/aq.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/ar.svg b/apps/web/public/flags/ar.svg index c753da10..45ac8867 100644 --- a/apps/web/public/flags/ar.svg +++ b/apps/web/public/flags/ar.svg @@ -1,32 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/arab.svg b/apps/web/public/flags/arab.svg index 9ef079f6..f037ddb9 100644 --- a/apps/web/public/flags/arab.svg +++ b/apps/web/public/flags/arab.svg @@ -1,109 +1,109 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/as.svg b/apps/web/public/flags/as.svg index 82459dec..f23d9d9b 100644 --- a/apps/web/public/flags/as.svg +++ b/apps/web/public/flags/as.svg @@ -1,72 +1,72 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/asean.svg b/apps/web/public/flags/asean.svg index 189ae020..63f4dcd6 100644 --- a/apps/web/public/flags/asean.svg +++ b/apps/web/public/flags/asean.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/at.svg b/apps/web/public/flags/at.svg index 9d2775c0..76149868 100644 --- a/apps/web/public/flags/at.svg +++ b/apps/web/public/flags/at.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/au.svg b/apps/web/public/flags/au.svg index 96e80768..fad9844a 100644 --- a/apps/web/public/flags/au.svg +++ b/apps/web/public/flags/au.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/aw.svg b/apps/web/public/flags/aw.svg index 413b7c45..78a54790 100644 --- a/apps/web/public/flags/aw.svg +++ b/apps/web/public/flags/aw.svg @@ -1,186 +1,186 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ax.svg b/apps/web/public/flags/ax.svg index 0584d713..e53cadd8 100644 --- a/apps/web/public/flags/ax.svg +++ b/apps/web/public/flags/ax.svg @@ -1,18 +1,18 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/az.svg b/apps/web/public/flags/az.svg index 35575221..9849619d 100644 --- a/apps/web/public/flags/az.svg +++ b/apps/web/public/flags/az.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/ba.svg b/apps/web/public/flags/ba.svg index 93bd9cf9..db836308 100644 --- a/apps/web/public/flags/ba.svg +++ b/apps/web/public/flags/ba.svg @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/apps/web/public/flags/bb.svg b/apps/web/public/flags/bb.svg index cecd5cc3..f6843199 100644 --- a/apps/web/public/flags/bb.svg +++ b/apps/web/public/flags/bb.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/bd.svg b/apps/web/public/flags/bd.svg index 16b794de..2d3b50ed 100644 --- a/apps/web/public/flags/bd.svg +++ b/apps/web/public/flags/bd.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/be.svg b/apps/web/public/flags/be.svg index ac706a0b..b3754f1b 100644 --- a/apps/web/public/flags/be.svg +++ b/apps/web/public/flags/be.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/bf.svg b/apps/web/public/flags/bf.svg index 47138225..8095ee17 100644 --- a/apps/web/public/flags/bf.svg +++ b/apps/web/public/flags/bf.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/bg.svg b/apps/web/public/flags/bg.svg index af2d0d07..e6668f5a 100644 --- a/apps/web/public/flags/bg.svg +++ b/apps/web/public/flags/bg.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/bh.svg b/apps/web/public/flags/bh.svg index 7a2ea549..0e9b7198 100644 --- a/apps/web/public/flags/bh.svg +++ b/apps/web/public/flags/bh.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/bi.svg b/apps/web/public/flags/bi.svg index a4434a95..4e832637 100644 --- a/apps/web/public/flags/bi.svg +++ b/apps/web/public/flags/bi.svg @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/bj.svg b/apps/web/public/flags/bj.svg index 0846724d..ac0d0741 100644 --- a/apps/web/public/flags/bj.svg +++ b/apps/web/public/flags/bj.svg @@ -1,14 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/bl.svg b/apps/web/public/flags/bl.svg index f84cbbae..ad89c4ca 100644 --- a/apps/web/public/flags/bl.svg +++ b/apps/web/public/flags/bl.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/bm.svg b/apps/web/public/flags/bm.svg index f43a5ebc..98578e41 100644 --- a/apps/web/public/flags/bm.svg +++ b/apps/web/public/flags/bm.svg @@ -1,97 +1,97 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/bn.svg b/apps/web/public/flags/bn.svg index f544c25e..afb72607 100644 --- a/apps/web/public/flags/bn.svg +++ b/apps/web/public/flags/bn.svg @@ -1,36 +1,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/bo.svg b/apps/web/public/flags/bo.svg index 7658e3f6..2ebe4df3 100644 --- a/apps/web/public/flags/bo.svg +++ b/apps/web/public/flags/bo.svg @@ -1,673 +1,673 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/bq.svg b/apps/web/public/flags/bq.svg index 0e6bc76e..10ba3c76 100644 --- a/apps/web/public/flags/bq.svg +++ b/apps/web/public/flags/bq.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/br.svg b/apps/web/public/flags/br.svg index 719a763c..56fdbc68 100644 --- a/apps/web/public/flags/br.svg +++ b/apps/web/public/flags/br.svg @@ -1,45 +1,45 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/bs.svg b/apps/web/public/flags/bs.svg index 5cc918e5..3198dcb0 100644 --- a/apps/web/public/flags/bs.svg +++ b/apps/web/public/flags/bs.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/bt.svg b/apps/web/public/flags/bt.svg index 20aef3a6..c707bb43 100644 --- a/apps/web/public/flags/bt.svg +++ b/apps/web/public/flags/bt.svg @@ -1,89 +1,89 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/bv.svg b/apps/web/public/flags/bv.svg index 40e16d94..1e6aeab0 100644 --- a/apps/web/public/flags/bv.svg +++ b/apps/web/public/flags/bv.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/bw.svg b/apps/web/public/flags/bw.svg index 3435608d..355181cf 100644 --- a/apps/web/public/flags/bw.svg +++ b/apps/web/public/flags/bw.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/by.svg b/apps/web/public/flags/by.svg index 948784ff..66f2daa9 100644 --- a/apps/web/public/flags/by.svg +++ b/apps/web/public/flags/by.svg @@ -1,18 +1,18 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/bz.svg b/apps/web/public/flags/bz.svg index d81b16c2..8609d443 100644 --- a/apps/web/public/flags/bz.svg +++ b/apps/web/public/flags/bz.svg @@ -1,145 +1,145 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ca.svg b/apps/web/public/flags/ca.svg index c9b23b49..8e6f250e 100644 --- a/apps/web/public/flags/ca.svg +++ b/apps/web/public/flags/ca.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/cc.svg b/apps/web/public/flags/cc.svg index a42dec66..647426e1 100644 --- a/apps/web/public/flags/cc.svg +++ b/apps/web/public/flags/cc.svg @@ -1,19 +1,19 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/cd.svg b/apps/web/public/flags/cd.svg index b9cf5289..a5645550 100644 --- a/apps/web/public/flags/cd.svg +++ b/apps/web/public/flags/cd.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/cefta.svg b/apps/web/public/flags/cefta.svg index f748d08a..3cf5884b 100644 --- a/apps/web/public/flags/cefta.svg +++ b/apps/web/public/flags/cefta.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/cf.svg b/apps/web/public/flags/cf.svg index a6cd3670..34af5dae 100644 --- a/apps/web/public/flags/cf.svg +++ b/apps/web/public/flags/cf.svg @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/cg.svg b/apps/web/public/flags/cg.svg index f5a0e42d..cec3b071 100644 --- a/apps/web/public/flags/cg.svg +++ b/apps/web/public/flags/cg.svg @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/apps/web/public/flags/ch.svg b/apps/web/public/flags/ch.svg index b42d6709..8980960b 100644 --- a/apps/web/public/flags/ch.svg +++ b/apps/web/public/flags/ch.svg @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/apps/web/public/flags/ci.svg b/apps/web/public/flags/ci.svg index e400f0c1..6e71beef 100644 --- a/apps/web/public/flags/ci.svg +++ b/apps/web/public/flags/ci.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/ck.svg b/apps/web/public/flags/ck.svg index 18e547b1..9217b6be 100644 --- a/apps/web/public/flags/ck.svg +++ b/apps/web/public/flags/ck.svg @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/apps/web/public/flags/cl.svg b/apps/web/public/flags/cl.svg index 5b3c72fa..477fb220 100644 --- a/apps/web/public/flags/cl.svg +++ b/apps/web/public/flags/cl.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/cm.svg b/apps/web/public/flags/cm.svg index 70adc8b6..fbfd5236 100644 --- a/apps/web/public/flags/cm.svg +++ b/apps/web/public/flags/cm.svg @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/cn.svg b/apps/web/public/flags/cn.svg index 10d3489a..86a6776b 100644 --- a/apps/web/public/flags/cn.svg +++ b/apps/web/public/flags/cn.svg @@ -1,11 +1,11 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/apps/web/public/flags/co.svg b/apps/web/public/flags/co.svg index ebd0a0fb..2fa50c0b 100644 --- a/apps/web/public/flags/co.svg +++ b/apps/web/public/flags/co.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/cp.svg b/apps/web/public/flags/cp.svg index b8aa9cfd..dd2edf4d 100644 --- a/apps/web/public/flags/cp.svg +++ b/apps/web/public/flags/cp.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/cr.svg b/apps/web/public/flags/cr.svg index 5a409eeb..ff3bc1d9 100644 --- a/apps/web/public/flags/cr.svg +++ b/apps/web/public/flags/cr.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/cu.svg b/apps/web/public/flags/cu.svg index 053c9ee3..ab740b00 100644 --- a/apps/web/public/flags/cu.svg +++ b/apps/web/public/flags/cu.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/cv.svg b/apps/web/public/flags/cv.svg index aec89949..a1dd7f6b 100644 --- a/apps/web/public/flags/cv.svg +++ b/apps/web/public/flags/cv.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/cw.svg b/apps/web/public/flags/cw.svg index bb0ece22..c8bd3bfa 100644 --- a/apps/web/public/flags/cw.svg +++ b/apps/web/public/flags/cw.svg @@ -1,14 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/cx.svg b/apps/web/public/flags/cx.svg index 3a83c23f..b1b77010 100644 --- a/apps/web/public/flags/cx.svg +++ b/apps/web/public/flags/cx.svg @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/cy.svg b/apps/web/public/flags/cy.svg index ee4b0c78..e92a6b2c 100644 --- a/apps/web/public/flags/cy.svg +++ b/apps/web/public/flags/cy.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/cz.svg b/apps/web/public/flags/cz.svg index 7913de38..846f0b98 100644 --- a/apps/web/public/flags/cz.svg +++ b/apps/web/public/flags/cz.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/de.svg b/apps/web/public/flags/de.svg index 71aa2d2c..0e357abe 100644 --- a/apps/web/public/flags/de.svg +++ b/apps/web/public/flags/de.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/dg.svg b/apps/web/public/flags/dg.svg index dfee2bb9..0feac1f0 100644 --- a/apps/web/public/flags/dg.svg +++ b/apps/web/public/flags/dg.svg @@ -1,130 +1,130 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/dj.svg b/apps/web/public/flags/dj.svg index 9b00a820..50ed6aa6 100644 --- a/apps/web/public/flags/dj.svg +++ b/apps/web/public/flags/dj.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/dk.svg b/apps/web/public/flags/dk.svg index 563277f8..f9dbb2d3 100644 --- a/apps/web/public/flags/dk.svg +++ b/apps/web/public/flags/dk.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/dm.svg b/apps/web/public/flags/dm.svg index 5aa9cea5..fbe5e29d 100644 --- a/apps/web/public/flags/dm.svg +++ b/apps/web/public/flags/dm.svg @@ -1,152 +1,152 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/do.svg b/apps/web/public/flags/do.svg index 6de2b268..fa1154e1 100644 --- a/apps/web/public/flags/do.svg +++ b/apps/web/public/flags/do.svg @@ -1,121 +1,121 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/dz.svg b/apps/web/public/flags/dz.svg index 5ff29a74..107988f1 100644 --- a/apps/web/public/flags/dz.svg +++ b/apps/web/public/flags/dz.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/eac.svg b/apps/web/public/flags/eac.svg index 59d02d21..714f9ad2 100644 --- a/apps/web/public/flags/eac.svg +++ b/apps/web/public/flags/eac.svg @@ -1,48 +1,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ec.svg b/apps/web/public/flags/ec.svg index 88c50bf6..b133e017 100644 --- a/apps/web/public/flags/ec.svg +++ b/apps/web/public/flags/ec.svg @@ -1,138 +1,138 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ee.svg b/apps/web/public/flags/ee.svg index 8b98c2c4..c0ffad85 100644 --- a/apps/web/public/flags/ee.svg +++ b/apps/web/public/flags/ee.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/eg.svg b/apps/web/public/flags/eg.svg index 88e32b3a..fbce4b7e 100644 --- a/apps/web/public/flags/eg.svg +++ b/apps/web/public/flags/eg.svg @@ -1,38 +1,38 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/eh.svg b/apps/web/public/flags/eh.svg index 6aec7288..a47c534c 100644 --- a/apps/web/public/flags/eh.svg +++ b/apps/web/public/flags/eh.svg @@ -1,16 +1,16 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/er.svg b/apps/web/public/flags/er.svg index 48a13b47..273dca76 100644 --- a/apps/web/public/flags/er.svg +++ b/apps/web/public/flags/er.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/es-ct.svg b/apps/web/public/flags/es-ct.svg index 4d859114..edfabd67 100644 --- a/apps/web/public/flags/es-ct.svg +++ b/apps/web/public/flags/es-ct.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/es-ga.svg b/apps/web/public/flags/es-ga.svg index 573ca45d..0153e302 100644 --- a/apps/web/public/flags/es-ga.svg +++ b/apps/web/public/flags/es-ga.svg @@ -1,187 +1,187 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/es-pv.svg b/apps/web/public/flags/es-pv.svg index 63c19f41..52b393de 100644 --- a/apps/web/public/flags/es-pv.svg +++ b/apps/web/public/flags/es-pv.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/es.svg b/apps/web/public/flags/es.svg index a296ebf7..2e1932fa 100644 --- a/apps/web/public/flags/es.svg +++ b/apps/web/public/flags/es.svg @@ -1,544 +1,544 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/et.svg b/apps/web/public/flags/et.svg index 3f99be48..22253643 100644 --- a/apps/web/public/flags/et.svg +++ b/apps/web/public/flags/et.svg @@ -1,14 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/eu.svg b/apps/web/public/flags/eu.svg index b0874c1e..6081c890 100644 --- a/apps/web/public/flags/eu.svg +++ b/apps/web/public/flags/eu.svg @@ -1,28 +1,28 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/fi.svg b/apps/web/public/flags/fi.svg index 470be2d0..380352b5 100644 --- a/apps/web/public/flags/fi.svg +++ b/apps/web/public/flags/fi.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/fj.svg b/apps/web/public/flags/fj.svg index 332ae61d..e0e562d2 100644 --- a/apps/web/public/flags/fj.svg +++ b/apps/web/public/flags/fj.svg @@ -1,120 +1,120 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/fk.svg b/apps/web/public/flags/fk.svg index a0dace83..f81650eb 100644 --- a/apps/web/public/flags/fk.svg +++ b/apps/web/public/flags/fk.svg @@ -1,90 +1,90 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/fm.svg b/apps/web/public/flags/fm.svg index c1b7c977..74ac77a8 100644 --- a/apps/web/public/flags/fm.svg +++ b/apps/web/public/flags/fm.svg @@ -1,11 +1,11 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/apps/web/public/flags/fo.svg b/apps/web/public/flags/fo.svg index f802d285..8e52069e 100644 --- a/apps/web/public/flags/fo.svg +++ b/apps/web/public/flags/fo.svg @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/apps/web/public/flags/fr.svg b/apps/web/public/flags/fr.svg index 4110e59e..49d4bb30 100644 --- a/apps/web/public/flags/fr.svg +++ b/apps/web/public/flags/fr.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/ga.svg b/apps/web/public/flags/ga.svg index 76edab42..7cc67746 100644 --- a/apps/web/public/flags/ga.svg +++ b/apps/web/public/flags/ga.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/gb-eng.svg b/apps/web/public/flags/gb-eng.svg index 12e3b67d..0f4c5d5d 100644 --- a/apps/web/public/flags/gb-eng.svg +++ b/apps/web/public/flags/gb-eng.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/gb-nir.svg b/apps/web/public/flags/gb-nir.svg index e22190aa..685ed17a 100644 --- a/apps/web/public/flags/gb-nir.svg +++ b/apps/web/public/flags/gb-nir.svg @@ -1,132 +1,132 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/gb-sct.svg b/apps/web/public/flags/gb-sct.svg index f50cd322..7ec53f4f 100644 --- a/apps/web/public/flags/gb-sct.svg +++ b/apps/web/public/flags/gb-sct.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/gb-wls.svg b/apps/web/public/flags/gb-wls.svg index d7f57912..2f352500 100644 --- a/apps/web/public/flags/gb-wls.svg +++ b/apps/web/public/flags/gb-wls.svg @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/apps/web/public/flags/gb.svg b/apps/web/public/flags/gb.svg index 79913831..7e7594d2 100644 --- a/apps/web/public/flags/gb.svg +++ b/apps/web/public/flags/gb.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/gd.svg b/apps/web/public/flags/gd.svg index b3d250db..81941264 100644 --- a/apps/web/public/flags/gd.svg +++ b/apps/web/public/flags/gd.svg @@ -1,27 +1,27 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ge.svg b/apps/web/public/flags/ge.svg index ab08a9ab..d0800a98 100644 --- a/apps/web/public/flags/ge.svg +++ b/apps/web/public/flags/ge.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/gf.svg b/apps/web/public/flags/gf.svg index f8fe94c6..0283fdec 100644 --- a/apps/web/public/flags/gf.svg +++ b/apps/web/public/flags/gf.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/gg.svg b/apps/web/public/flags/gg.svg index f8216c8b..1b80efe4 100644 --- a/apps/web/public/flags/gg.svg +++ b/apps/web/public/flags/gg.svg @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/apps/web/public/flags/gh.svg b/apps/web/public/flags/gh.svg index 5c3e3e69..1a9047d7 100644 --- a/apps/web/public/flags/gh.svg +++ b/apps/web/public/flags/gh.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/gi.svg b/apps/web/public/flags/gi.svg index a5d7570d..3b74d19a 100644 --- a/apps/web/public/flags/gi.svg +++ b/apps/web/public/flags/gi.svg @@ -1,32 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/gl.svg b/apps/web/public/flags/gl.svg index eb5a52e9..938d20fa 100644 --- a/apps/web/public/flags/gl.svg +++ b/apps/web/public/flags/gl.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/gm.svg b/apps/web/public/flags/gm.svg index 8fe9d669..4b9515fd 100644 --- a/apps/web/public/flags/gm.svg +++ b/apps/web/public/flags/gm.svg @@ -1,14 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/gn.svg b/apps/web/public/flags/gn.svg index 40d6ad4f..00eca826 100644 --- a/apps/web/public/flags/gn.svg +++ b/apps/web/public/flags/gn.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/gp.svg b/apps/web/public/flags/gp.svg index ee55c4bc..06b67891 100644 --- a/apps/web/public/flags/gp.svg +++ b/apps/web/public/flags/gp.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/gq.svg b/apps/web/public/flags/gq.svg index 64c8eb22..f12157bf 100644 --- a/apps/web/public/flags/gq.svg +++ b/apps/web/public/flags/gq.svg @@ -1,23 +1,23 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/gr.svg b/apps/web/public/flags/gr.svg index 599741ee..2bfaa7e0 100644 --- a/apps/web/public/flags/gr.svg +++ b/apps/web/public/flags/gr.svg @@ -1,16 +1,16 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/gs.svg b/apps/web/public/flags/gs.svg index 29db9b94..0d6ff142 100644 --- a/apps/web/public/flags/gs.svg +++ b/apps/web/public/flags/gs.svg @@ -1,133 +1,133 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/gt.svg b/apps/web/public/flags/gt.svg index 7df9df57..a90ed5bf 100644 --- a/apps/web/public/flags/gt.svg +++ b/apps/web/public/flags/gt.svg @@ -1,204 +1,204 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/gu.svg b/apps/web/public/flags/gu.svg index 3b95219a..a1b53b87 100644 --- a/apps/web/public/flags/gu.svg +++ b/apps/web/public/flags/gu.svg @@ -1,19 +1,19 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/gw.svg b/apps/web/public/flags/gw.svg index d470bac9..8a846ad0 100644 --- a/apps/web/public/flags/gw.svg +++ b/apps/web/public/flags/gw.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/gy.svg b/apps/web/public/flags/gy.svg index 569fb562..93d8b27a 100644 --- a/apps/web/public/flags/gy.svg +++ b/apps/web/public/flags/gy.svg @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/apps/web/public/flags/hk.svg b/apps/web/public/flags/hk.svg index 4fd55bc1..ffa87a23 100644 --- a/apps/web/public/flags/hk.svg +++ b/apps/web/public/flags/hk.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/hm.svg b/apps/web/public/flags/hm.svg index 815c4820..6402ed8d 100644 --- a/apps/web/public/flags/hm.svg +++ b/apps/web/public/flags/hm.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/hn.svg b/apps/web/public/flags/hn.svg index 11fde67d..dbda0dd3 100644 --- a/apps/web/public/flags/hn.svg +++ b/apps/web/public/flags/hn.svg @@ -1,18 +1,18 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/hr.svg b/apps/web/public/flags/hr.svg index dde825c3..ac129f1f 100644 --- a/apps/web/public/flags/hr.svg +++ b/apps/web/public/flags/hr.svg @@ -1,58 +1,58 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ht.svg b/apps/web/public/flags/ht.svg index 8e8efc46..3f246da8 100644 --- a/apps/web/public/flags/ht.svg +++ b/apps/web/public/flags/ht.svg @@ -1,116 +1,116 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/hu.svg b/apps/web/public/flags/hu.svg index baddf7f5..32ea446b 100644 --- a/apps/web/public/flags/hu.svg +++ b/apps/web/public/flags/hu.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/ic.svg b/apps/web/public/flags/ic.svg index 81e6ee2e..d45040c2 100644 --- a/apps/web/public/flags/ic.svg +++ b/apps/web/public/flags/ic.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/id.svg b/apps/web/public/flags/id.svg index 3b7c8fcf..ce55a92f 100644 --- a/apps/web/public/flags/id.svg +++ b/apps/web/public/flags/id.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/ie.svg b/apps/web/public/flags/ie.svg index 049be14d..2b16cf12 100644 --- a/apps/web/public/flags/ie.svg +++ b/apps/web/public/flags/ie.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/il.svg b/apps/web/public/flags/il.svg index f43be7e8..359d81cd 100644 --- a/apps/web/public/flags/il.svg +++ b/apps/web/public/flags/il.svg @@ -1,14 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/im.svg b/apps/web/public/flags/im.svg index fe6a59a1..23a38f16 100644 --- a/apps/web/public/flags/im.svg +++ b/apps/web/public/flags/im.svg @@ -1,36 +1,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/in.svg b/apps/web/public/flags/in.svg index bc47d749..5fea2313 100644 --- a/apps/web/public/flags/in.svg +++ b/apps/web/public/flags/in.svg @@ -1,25 +1,25 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/io.svg b/apps/web/public/flags/io.svg index 3058f7df..db811a4d 100644 --- a/apps/web/public/flags/io.svg +++ b/apps/web/public/flags/io.svg @@ -1,130 +1,130 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/iq.svg b/apps/web/public/flags/iq.svg index 80445147..6b1346e8 100644 --- a/apps/web/public/flags/iq.svg +++ b/apps/web/public/flags/iq.svg @@ -1,10 +1,10 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/apps/web/public/flags/ir.svg b/apps/web/public/flags/ir.svg index 8c6d5162..04ef60b1 100644 --- a/apps/web/public/flags/ir.svg +++ b/apps/web/public/flags/ir.svg @@ -1,219 +1,219 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/is.svg b/apps/web/public/flags/is.svg index a6588afa..fe08a02f 100644 --- a/apps/web/public/flags/is.svg +++ b/apps/web/public/flags/is.svg @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/apps/web/public/flags/it.svg b/apps/web/public/flags/it.svg index 20a8bfdc..88bd45c2 100644 --- a/apps/web/public/flags/it.svg +++ b/apps/web/public/flags/it.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/je.svg b/apps/web/public/flags/je.svg index 70a8754e..9acb4450 100644 --- a/apps/web/public/flags/je.svg +++ b/apps/web/public/flags/je.svg @@ -1,62 +1,62 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/jm.svg b/apps/web/public/flags/jm.svg index 269df038..a114f8ce 100644 --- a/apps/web/public/flags/jm.svg +++ b/apps/web/public/flags/jm.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/jo.svg b/apps/web/public/flags/jo.svg index d6f927d4..bd34ede2 100644 --- a/apps/web/public/flags/jo.svg +++ b/apps/web/public/flags/jo.svg @@ -1,16 +1,16 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/jp.svg b/apps/web/public/flags/jp.svg index cc1c181c..2ec85aa1 100644 --- a/apps/web/public/flags/jp.svg +++ b/apps/web/public/flags/jp.svg @@ -1,11 +1,11 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/apps/web/public/flags/ke.svg b/apps/web/public/flags/ke.svg index 3a67ca3c..c8004f29 100644 --- a/apps/web/public/flags/ke.svg +++ b/apps/web/public/flags/ke.svg @@ -1,23 +1,23 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/kg.svg b/apps/web/public/flags/kg.svg index e26db953..13ae427b 100644 --- a/apps/web/public/flags/kg.svg +++ b/apps/web/public/flags/kg.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/kh.svg b/apps/web/public/flags/kh.svg index a7d52f2c..7f8f9255 100644 --- a/apps/web/public/flags/kh.svg +++ b/apps/web/public/flags/kh.svg @@ -1,61 +1,61 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ki.svg b/apps/web/public/flags/ki.svg index fda03f37..14466d23 100644 --- a/apps/web/public/flags/ki.svg +++ b/apps/web/public/flags/ki.svg @@ -1,36 +1,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/km.svg b/apps/web/public/flags/km.svg index 414d65e4..11429843 100644 --- a/apps/web/public/flags/km.svg +++ b/apps/web/public/flags/km.svg @@ -1,16 +1,16 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/kn.svg b/apps/web/public/flags/kn.svg index 47fe64d6..aea5e413 100644 --- a/apps/web/public/flags/kn.svg +++ b/apps/web/public/flags/kn.svg @@ -1,14 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/kp.svg b/apps/web/public/flags/kp.svg index ad1b713f..13c2f630 100644 --- a/apps/web/public/flags/kp.svg +++ b/apps/web/public/flags/kp.svg @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/kr.svg b/apps/web/public/flags/kr.svg index 6947eab2..2b215d49 100644 --- a/apps/web/public/flags/kr.svg +++ b/apps/web/public/flags/kr.svg @@ -1,24 +1,24 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/kw.svg b/apps/web/public/flags/kw.svg index 3dd89e99..a77b1579 100644 --- a/apps/web/public/flags/kw.svg +++ b/apps/web/public/flags/kw.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ky.svg b/apps/web/public/flags/ky.svg index aeaa7e08..7817710f 100644 --- a/apps/web/public/flags/ky.svg +++ b/apps/web/public/flags/ky.svg @@ -1,103 +1,103 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/kz.svg b/apps/web/public/flags/kz.svg index 2fac45bd..536fb9ff 100644 --- a/apps/web/public/flags/kz.svg +++ b/apps/web/public/flags/kz.svg @@ -1,36 +1,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/la.svg b/apps/web/public/flags/la.svg index 6aea6b72..788f096f 100644 --- a/apps/web/public/flags/la.svg +++ b/apps/web/public/flags/la.svg @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/apps/web/public/flags/lb.svg b/apps/web/public/flags/lb.svg index bde2581d..95359250 100644 --- a/apps/web/public/flags/lb.svg +++ b/apps/web/public/flags/lb.svg @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/lc.svg b/apps/web/public/flags/lc.svg index bb256541..b4de5425 100644 --- a/apps/web/public/flags/lc.svg +++ b/apps/web/public/flags/lc.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/li.svg b/apps/web/public/flags/li.svg index 7a4d1832..50afac07 100644 --- a/apps/web/public/flags/li.svg +++ b/apps/web/public/flags/li.svg @@ -1,43 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/lk.svg b/apps/web/public/flags/lk.svg index cbd660a5..3f9b597d 100644 --- a/apps/web/public/flags/lk.svg +++ b/apps/web/public/flags/lk.svg @@ -1,22 +1,22 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/lr.svg b/apps/web/public/flags/lr.svg index e482ab9d..a2f6a661 100644 --- a/apps/web/public/flags/lr.svg +++ b/apps/web/public/flags/lr.svg @@ -1,14 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ls.svg b/apps/web/public/flags/ls.svg index a7c01a98..d90a8c49 100644 --- a/apps/web/public/flags/ls.svg +++ b/apps/web/public/flags/ls.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/lt.svg b/apps/web/public/flags/lt.svg index 90ec5d24..e42ba503 100644 --- a/apps/web/public/flags/lt.svg +++ b/apps/web/public/flags/lt.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/lu.svg b/apps/web/public/flags/lu.svg index cc122068..62cbef97 100644 --- a/apps/web/public/flags/lu.svg +++ b/apps/web/public/flags/lu.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/lv.svg b/apps/web/public/flags/lv.svg index 6a9e75ec..fdcc9cac 100644 --- a/apps/web/public/flags/lv.svg +++ b/apps/web/public/flags/lv.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/ly.svg b/apps/web/public/flags/ly.svg index 1eaa51e4..e90bb0c0 100644 --- a/apps/web/public/flags/ly.svg +++ b/apps/web/public/flags/ly.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ma.svg b/apps/web/public/flags/ma.svg index 7ce56eff..d390ee6c 100644 --- a/apps/web/public/flags/ma.svg +++ b/apps/web/public/flags/ma.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/mc.svg b/apps/web/public/flags/mc.svg index 9cb6c9e8..2cfc89a1 100644 --- a/apps/web/public/flags/mc.svg +++ b/apps/web/public/flags/mc.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/md.svg b/apps/web/public/flags/md.svg index e9ba506a..12356e93 100644 --- a/apps/web/public/flags/md.svg +++ b/apps/web/public/flags/md.svg @@ -1,70 +1,70 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/me.svg b/apps/web/public/flags/me.svg index 297888c7..3a8853ed 100644 --- a/apps/web/public/flags/me.svg +++ b/apps/web/public/flags/me.svg @@ -1,116 +1,116 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/mf.svg b/apps/web/public/flags/mf.svg index 6305edc1..a06c440f 100644 --- a/apps/web/public/flags/mf.svg +++ b/apps/web/public/flags/mf.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/mg.svg b/apps/web/public/flags/mg.svg index 5fa2d244..a22961c4 100644 --- a/apps/web/public/flags/mg.svg +++ b/apps/web/public/flags/mg.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/mh.svg b/apps/web/public/flags/mh.svg index 7b9f4907..a451e491 100644 --- a/apps/web/public/flags/mh.svg +++ b/apps/web/public/flags/mh.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/mk.svg b/apps/web/public/flags/mk.svg index 4f5cae77..9e102bd0 100644 --- a/apps/web/public/flags/mk.svg +++ b/apps/web/public/flags/mk.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/ml.svg b/apps/web/public/flags/ml.svg index 6f6b7169..91cf9fa4 100644 --- a/apps/web/public/flags/ml.svg +++ b/apps/web/public/flags/ml.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/mm.svg b/apps/web/public/flags/mm.svg index 42b4dee2..6c307abf 100644 --- a/apps/web/public/flags/mm.svg +++ b/apps/web/public/flags/mm.svg @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/apps/web/public/flags/mn.svg b/apps/web/public/flags/mn.svg index 6a38a71f..3425a98f 100644 --- a/apps/web/public/flags/mn.svg +++ b/apps/web/public/flags/mn.svg @@ -1,14 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/mo.svg b/apps/web/public/flags/mo.svg index f638b6cb..5e39a7f3 100644 --- a/apps/web/public/flags/mo.svg +++ b/apps/web/public/flags/mo.svg @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/apps/web/public/flags/mp.svg b/apps/web/public/flags/mp.svg index 26bfa221..c3063628 100644 --- a/apps/web/public/flags/mp.svg +++ b/apps/web/public/flags/mp.svg @@ -1,86 +1,86 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/mq.svg b/apps/web/public/flags/mq.svg index b221951e..43ce9df6 100644 --- a/apps/web/public/flags/mq.svg +++ b/apps/web/public/flags/mq.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/mr.svg b/apps/web/public/flags/mr.svg index d859972c..0c55e5d2 100644 --- a/apps/web/public/flags/mr.svg +++ b/apps/web/public/flags/mr.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/ms.svg b/apps/web/public/flags/ms.svg index 4367505b..1ea744ea 100644 --- a/apps/web/public/flags/ms.svg +++ b/apps/web/public/flags/ms.svg @@ -1,29 +1,29 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/mt.svg b/apps/web/public/flags/mt.svg index 5d5d7c80..145e0d04 100644 --- a/apps/web/public/flags/mt.svg +++ b/apps/web/public/flags/mt.svg @@ -1,58 +1,58 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/mu.svg b/apps/web/public/flags/mu.svg index 82d7a3be..359f3555 100644 --- a/apps/web/public/flags/mu.svg +++ b/apps/web/public/flags/mu.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/mv.svg b/apps/web/public/flags/mv.svg index 10450f98..46e636ee 100644 --- a/apps/web/public/flags/mv.svg +++ b/apps/web/public/flags/mv.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/mw.svg b/apps/web/public/flags/mw.svg index 137ff878..fa3d3caa 100644 --- a/apps/web/public/flags/mw.svg +++ b/apps/web/public/flags/mw.svg @@ -1,10 +1,10 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/apps/web/public/flags/mx.svg b/apps/web/public/flags/mx.svg index e3ec2bc5..3af0a023 100644 --- a/apps/web/public/flags/mx.svg +++ b/apps/web/public/flags/mx.svg @@ -1,382 +1,382 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/my.svg b/apps/web/public/flags/my.svg index 115f864d..df2ec3bc 100644 --- a/apps/web/public/flags/my.svg +++ b/apps/web/public/flags/my.svg @@ -1,26 +1,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/mz.svg b/apps/web/public/flags/mz.svg index 0f94c3a1..768e348f 100644 --- a/apps/web/public/flags/mz.svg +++ b/apps/web/public/flags/mz.svg @@ -1,21 +1,21 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/na.svg b/apps/web/public/flags/na.svg index 35b9f783..01580344 100644 --- a/apps/web/public/flags/na.svg +++ b/apps/web/public/flags/na.svg @@ -1,16 +1,16 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/nc.svg b/apps/web/public/flags/nc.svg index fa155516..44d41716 100644 --- a/apps/web/public/flags/nc.svg +++ b/apps/web/public/flags/nc.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ne.svg b/apps/web/public/flags/ne.svg index 39a82b82..456ff2d2 100644 --- a/apps/web/public/flags/ne.svg +++ b/apps/web/public/flags/ne.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/nf.svg b/apps/web/public/flags/nf.svg index fd61b25c..95c4ee1d 100644 --- a/apps/web/public/flags/nf.svg +++ b/apps/web/public/flags/nf.svg @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/apps/web/public/flags/ng.svg b/apps/web/public/flags/ng.svg index 81eb35f7..bf166c42 100644 --- a/apps/web/public/flags/ng.svg +++ b/apps/web/public/flags/ng.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/ni.svg b/apps/web/public/flags/ni.svg index e4861f5a..45416d45 100644 --- a/apps/web/public/flags/ni.svg +++ b/apps/web/public/flags/ni.svg @@ -1,129 +1,129 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/nl.svg b/apps/web/public/flags/nl.svg index e90f5b03..70e0c160 100644 --- a/apps/web/public/flags/nl.svg +++ b/apps/web/public/flags/nl.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/no.svg b/apps/web/public/flags/no.svg index a5f2a152..c57515f7 100644 --- a/apps/web/public/flags/no.svg +++ b/apps/web/public/flags/no.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/np.svg b/apps/web/public/flags/np.svg index 62428568..c2b1b113 100644 --- a/apps/web/public/flags/np.svg +++ b/apps/web/public/flags/np.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/nr.svg b/apps/web/public/flags/nr.svg index ff394c41..9a64f7aa 100644 --- a/apps/web/public/flags/nr.svg +++ b/apps/web/public/flags/nr.svg @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/apps/web/public/flags/nu.svg b/apps/web/public/flags/nu.svg index 4067baff..f59d1d95 100644 --- a/apps/web/public/flags/nu.svg +++ b/apps/web/public/flags/nu.svg @@ -1,10 +1,10 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/apps/web/public/flags/nz.svg b/apps/web/public/flags/nz.svg index 935d8a74..8aa7ab8b 100644 --- a/apps/web/public/flags/nz.svg +++ b/apps/web/public/flags/nz.svg @@ -1,36 +1,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/om.svg b/apps/web/public/flags/om.svg index 4f1461a0..eb87ba1a 100644 --- a/apps/web/public/flags/om.svg +++ b/apps/web/public/flags/om.svg @@ -1,115 +1,115 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/pa.svg b/apps/web/public/flags/pa.svg index 8dc03bc6..ffbb9256 100644 --- a/apps/web/public/flags/pa.svg +++ b/apps/web/public/flags/pa.svg @@ -1,14 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/pc.svg b/apps/web/public/flags/pc.svg index 5202d6d6..039e8ccd 100644 --- a/apps/web/public/flags/pc.svg +++ b/apps/web/public/flags/pc.svg @@ -1,33 +1,33 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/pe.svg b/apps/web/public/flags/pe.svg index 33e6cfd4..0b6dc998 100644 --- a/apps/web/public/flags/pe.svg +++ b/apps/web/public/flags/pe.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/pf.svg b/apps/web/public/flags/pf.svg index bea0354d..1a86e062 100644 --- a/apps/web/public/flags/pf.svg +++ b/apps/web/public/flags/pf.svg @@ -1,19 +1,19 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/pg.svg b/apps/web/public/flags/pg.svg index 7b7e77aa..507d32ff 100644 --- a/apps/web/public/flags/pg.svg +++ b/apps/web/public/flags/pg.svg @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/apps/web/public/flags/ph.svg b/apps/web/public/flags/ph.svg index b910e24c..13b8a048 100644 --- a/apps/web/public/flags/ph.svg +++ b/apps/web/public/flags/ph.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/pk.svg b/apps/web/public/flags/pk.svg index 4ddc19f8..48c4f16c 100644 --- a/apps/web/public/flags/pk.svg +++ b/apps/web/public/flags/pk.svg @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/pl.svg b/apps/web/public/flags/pl.svg index 0fa51452..560fe336 100644 --- a/apps/web/public/flags/pl.svg +++ b/apps/web/public/flags/pl.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/pm.svg b/apps/web/public/flags/pm.svg index 19a9330a..d12838d8 100644 --- a/apps/web/public/flags/pm.svg +++ b/apps/web/public/flags/pm.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/pn.svg b/apps/web/public/flags/pn.svg index 209ea71a..abd81550 100644 --- a/apps/web/public/flags/pn.svg +++ b/apps/web/public/flags/pn.svg @@ -1,53 +1,53 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/pr.svg b/apps/web/public/flags/pr.svg index ec51831d..57b554c4 100644 --- a/apps/web/public/flags/pr.svg +++ b/apps/web/public/flags/pr.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ps.svg b/apps/web/public/flags/ps.svg index 362d4359..adc546c4 100644 --- a/apps/web/public/flags/ps.svg +++ b/apps/web/public/flags/ps.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/pt.svg b/apps/web/public/flags/pt.svg index 2767cd4e..1df10418 100644 --- a/apps/web/public/flags/pt.svg +++ b/apps/web/public/flags/pt.svg @@ -1,57 +1,57 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/pw.svg b/apps/web/public/flags/pw.svg index 9f89c5f1..ea1fafc5 100644 --- a/apps/web/public/flags/pw.svg +++ b/apps/web/public/flags/pw.svg @@ -1,11 +1,11 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/apps/web/public/flags/py.svg b/apps/web/public/flags/py.svg index abccd879..17aef52d 100644 --- a/apps/web/public/flags/py.svg +++ b/apps/web/public/flags/py.svg @@ -1,157 +1,157 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/qa.svg b/apps/web/public/flags/qa.svg index 901f3fa7..3efa0dc4 100644 --- a/apps/web/public/flags/qa.svg +++ b/apps/web/public/flags/qa.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/re.svg b/apps/web/public/flags/re.svg index 64e788e0..fd514f22 100644 --- a/apps/web/public/flags/re.svg +++ b/apps/web/public/flags/re.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/ro.svg b/apps/web/public/flags/ro.svg index fda0f7be..a33731a1 100644 --- a/apps/web/public/flags/ro.svg +++ b/apps/web/public/flags/ro.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/rs.svg b/apps/web/public/flags/rs.svg index 6d4f74d7..8f8b830e 100644 --- a/apps/web/public/flags/rs.svg +++ b/apps/web/public/flags/rs.svg @@ -1,292 +1,292 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ru.svg b/apps/web/public/flags/ru.svg index cf243011..46758444 100644 --- a/apps/web/public/flags/ru.svg +++ b/apps/web/public/flags/ru.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/rw.svg b/apps/web/public/flags/rw.svg index 06e26ae4..b54e64a1 100644 --- a/apps/web/public/flags/rw.svg +++ b/apps/web/public/flags/rw.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/sa.svg b/apps/web/public/flags/sa.svg index 596cf48b..c84e29c1 100644 --- a/apps/web/public/flags/sa.svg +++ b/apps/web/public/flags/sa.svg @@ -1,25 +1,25 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/sb.svg b/apps/web/public/flags/sb.svg index 6066f94c..c74e6ad2 100644 --- a/apps/web/public/flags/sb.svg +++ b/apps/web/public/flags/sb.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/sc.svg b/apps/web/public/flags/sc.svg index 9a46b369..245e0478 100644 --- a/apps/web/public/flags/sc.svg +++ b/apps/web/public/flags/sc.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/sd.svg b/apps/web/public/flags/sd.svg index 12818b41..5805b799 100644 --- a/apps/web/public/flags/sd.svg +++ b/apps/web/public/flags/sd.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/se.svg b/apps/web/public/flags/se.svg index 8ba745ac..d527fec4 100644 --- a/apps/web/public/flags/se.svg +++ b/apps/web/public/flags/se.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/sg.svg b/apps/web/public/flags/sg.svg index c4dd4ac9..489a4a04 100644 --- a/apps/web/public/flags/sg.svg +++ b/apps/web/public/flags/sg.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/sh-ac.svg b/apps/web/public/flags/sh-ac.svg index c43b301e..6713c99d 100644 --- a/apps/web/public/flags/sh-ac.svg +++ b/apps/web/public/flags/sh-ac.svg @@ -1,689 +1,689 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/sh-hl.svg b/apps/web/public/flags/sh-hl.svg index 2150bf60..289a2da3 100644 --- a/apps/web/public/flags/sh-hl.svg +++ b/apps/web/public/flags/sh-hl.svg @@ -1,164 +1,164 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/sh-ta.svg b/apps/web/public/flags/sh-ta.svg index ba390631..e4f9f0b3 100644 --- a/apps/web/public/flags/sh-ta.svg +++ b/apps/web/public/flags/sh-ta.svg @@ -1,76 +1,76 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/sh.svg b/apps/web/public/flags/sh.svg index 7aba0aec..9912b237 100644 --- a/apps/web/public/flags/sh.svg +++ b/apps/web/public/flags/sh.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/si.svg b/apps/web/public/flags/si.svg index 1bbdd94f..c9a59f61 100644 --- a/apps/web/public/flags/si.svg +++ b/apps/web/public/flags/si.svg @@ -1,18 +1,18 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/sj.svg b/apps/web/public/flags/sj.svg index bb2799ce..5caa2802 100644 --- a/apps/web/public/flags/sj.svg +++ b/apps/web/public/flags/sj.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/sk.svg b/apps/web/public/flags/sk.svg index 676018e6..8c0b5e3f 100644 --- a/apps/web/public/flags/sk.svg +++ b/apps/web/public/flags/sk.svg @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/apps/web/public/flags/sl.svg b/apps/web/public/flags/sl.svg index a07baf75..2869a585 100644 --- a/apps/web/public/flags/sl.svg +++ b/apps/web/public/flags/sl.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/sm.svg b/apps/web/public/flags/sm.svg index e41d2f77..b055ce72 100644 --- a/apps/web/public/flags/sm.svg +++ b/apps/web/public/flags/sm.svg @@ -1,75 +1,75 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/sn.svg b/apps/web/public/flags/sn.svg index 7c0673d6..f27a8690 100644 --- a/apps/web/public/flags/sn.svg +++ b/apps/web/public/flags/sn.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/so.svg b/apps/web/public/flags/so.svg index a581ac63..86877ffd 100644 --- a/apps/web/public/flags/so.svg +++ b/apps/web/public/flags/so.svg @@ -1,11 +1,11 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/apps/web/public/flags/sr.svg b/apps/web/public/flags/sr.svg index 5e71c400..c1e71b5d 100644 --- a/apps/web/public/flags/sr.svg +++ b/apps/web/public/flags/sr.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/ss.svg b/apps/web/public/flags/ss.svg index b257aa0b..16dcbb9a 100644 --- a/apps/web/public/flags/ss.svg +++ b/apps/web/public/flags/ss.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/st.svg b/apps/web/public/flags/st.svg index 1294bcb7..29a8e375 100644 --- a/apps/web/public/flags/st.svg +++ b/apps/web/public/flags/st.svg @@ -1,16 +1,16 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/sv.svg b/apps/web/public/flags/sv.svg index cbc674a6..c4b63fe2 100644 --- a/apps/web/public/flags/sv.svg +++ b/apps/web/public/flags/sv.svg @@ -1,593 +1,593 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/sx.svg b/apps/web/public/flags/sx.svg index ac785617..d37cd8e6 100644 --- a/apps/web/public/flags/sx.svg +++ b/apps/web/public/flags/sx.svg @@ -1,56 +1,56 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/sy.svg b/apps/web/public/flags/sy.svg index 97c05cfc..1896a9cd 100644 --- a/apps/web/public/flags/sy.svg +++ b/apps/web/public/flags/sy.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/sz.svg b/apps/web/public/flags/sz.svg index eb538e44..05c14040 100644 --- a/apps/web/public/flags/sz.svg +++ b/apps/web/public/flags/sz.svg @@ -1,34 +1,34 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/tc.svg b/apps/web/public/flags/tc.svg index 12589710..cb453fd1 100644 --- a/apps/web/public/flags/tc.svg +++ b/apps/web/public/flags/tc.svg @@ -1,50 +1,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/td.svg b/apps/web/public/flags/td.svg index fa3bd927..605217ee 100644 --- a/apps/web/public/flags/td.svg +++ b/apps/web/public/flags/td.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/tf.svg b/apps/web/public/flags/tf.svg index fba23356..735c059c 100644 --- a/apps/web/public/flags/tf.svg +++ b/apps/web/public/flags/tf.svg @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/tg.svg b/apps/web/public/flags/tg.svg index 9d6ea6c5..dbc01750 100644 --- a/apps/web/public/flags/tg.svg +++ b/apps/web/public/flags/tg.svg @@ -1,14 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/th.svg b/apps/web/public/flags/th.svg index 1e93a61e..c4404d2d 100644 --- a/apps/web/public/flags/th.svg +++ b/apps/web/public/flags/th.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/tj.svg b/apps/web/public/flags/tj.svg index f8c9a037..d8b6ad90 100644 --- a/apps/web/public/flags/tj.svg +++ b/apps/web/public/flags/tj.svg @@ -1,22 +1,22 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/tk.svg b/apps/web/public/flags/tk.svg index 05d3e86c..568d4a8f 100644 --- a/apps/web/public/flags/tk.svg +++ b/apps/web/public/flags/tk.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/tl.svg b/apps/web/public/flags/tl.svg index 3d0701a2..2052311a 100644 --- a/apps/web/public/flags/tl.svg +++ b/apps/web/public/flags/tl.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/tm.svg b/apps/web/public/flags/tm.svg index 4154ed76..2b96db0f 100644 --- a/apps/web/public/flags/tm.svg +++ b/apps/web/public/flags/tm.svg @@ -1,204 +1,204 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/tn.svg b/apps/web/public/flags/tn.svg index 5735c198..31c61a95 100644 --- a/apps/web/public/flags/tn.svg +++ b/apps/web/public/flags/tn.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/to.svg b/apps/web/public/flags/to.svg index d0723370..ad429dc4 100644 --- a/apps/web/public/flags/to.svg +++ b/apps/web/public/flags/to.svg @@ -1,10 +1,10 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/apps/web/public/flags/tr.svg b/apps/web/public/flags/tr.svg index b96da21f..1d9e8526 100644 --- a/apps/web/public/flags/tr.svg +++ b/apps/web/public/flags/tr.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/tt.svg b/apps/web/public/flags/tt.svg index bc24938c..cbb0339f 100644 --- a/apps/web/public/flags/tt.svg +++ b/apps/web/public/flags/tt.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/tv.svg b/apps/web/public/flags/tv.svg index 675210ec..eaa1e527 100644 --- a/apps/web/public/flags/tv.svg +++ b/apps/web/public/flags/tv.svg @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/apps/web/public/flags/tw.svg b/apps/web/public/flags/tw.svg index 57fd98b4..4cf6bd8a 100644 --- a/apps/web/public/flags/tw.svg +++ b/apps/web/public/flags/tw.svg @@ -1,34 +1,34 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/tz.svg b/apps/web/public/flags/tz.svg index a2cfbca4..ea2c233c 100644 --- a/apps/web/public/flags/tz.svg +++ b/apps/web/public/flags/tz.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/apps/web/public/flags/ua.svg b/apps/web/public/flags/ua.svg index a339eb1b..4ce79e78 100644 --- a/apps/web/public/flags/ua.svg +++ b/apps/web/public/flags/ua.svg @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/apps/web/public/flags/ug.svg b/apps/web/public/flags/ug.svg index 520eee5c..5630cc7c 100644 --- a/apps/web/public/flags/ug.svg +++ b/apps/web/public/flags/ug.svg @@ -1,30 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/um.svg b/apps/web/public/flags/um.svg index 9e9eddaa..01236d11 100644 --- a/apps/web/public/flags/um.svg +++ b/apps/web/public/flags/um.svg @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/apps/web/public/flags/un.svg b/apps/web/public/flags/un.svg index 632bbb4a..638cb022 100644 --- a/apps/web/public/flags/un.svg +++ b/apps/web/public/flags/un.svg @@ -1,16 +1,16 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/us.svg b/apps/web/public/flags/us.svg index 9cfd0c92..47f54aa5 100644 --- a/apps/web/public/flags/us.svg +++ b/apps/web/public/flags/us.svg @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/apps/web/public/flags/uy.svg b/apps/web/public/flags/uy.svg index 62c36f8e..4f484bcf 100644 --- a/apps/web/public/flags/uy.svg +++ b/apps/web/public/flags/uy.svg @@ -1,28 +1,28 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/uz.svg b/apps/web/public/flags/uz.svg index 0ccca1b1..a8d53bff 100644 --- a/apps/web/public/flags/uz.svg +++ b/apps/web/public/flags/uz.svg @@ -1,30 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/va.svg b/apps/web/public/flags/va.svg index 3e297d63..7eaf5d3f 100644 --- a/apps/web/public/flags/va.svg +++ b/apps/web/public/flags/va.svg @@ -1,190 +1,190 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/vc.svg b/apps/web/public/flags/vc.svg index f26c2d8d..e6091b9d 100644 --- a/apps/web/public/flags/vc.svg +++ b/apps/web/public/flags/vc.svg @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/apps/web/public/flags/ve.svg b/apps/web/public/flags/ve.svg index 314e7f5f..81d317b8 100644 --- a/apps/web/public/flags/ve.svg +++ b/apps/web/public/flags/ve.svg @@ -1,26 +1,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/vg.svg b/apps/web/public/flags/vg.svg index ac900888..4e89e5cf 100644 --- a/apps/web/public/flags/vg.svg +++ b/apps/web/public/flags/vg.svg @@ -1,59 +1,59 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/vi.svg b/apps/web/public/flags/vi.svg index d88d68f9..a8ed7553 100644 --- a/apps/web/public/flags/vi.svg +++ b/apps/web/public/flags/vi.svg @@ -1,28 +1,28 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/vn.svg b/apps/web/public/flags/vn.svg index 7e4bac8f..0450271b 100644 --- a/apps/web/public/flags/vn.svg +++ b/apps/web/public/flags/vn.svg @@ -1,11 +1,11 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/apps/web/public/flags/vu.svg b/apps/web/public/flags/vu.svg index 326d29e9..da7bbc13 100644 --- a/apps/web/public/flags/vu.svg +++ b/apps/web/public/flags/vu.svg @@ -1,21 +1,21 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/wf.svg b/apps/web/public/flags/wf.svg index 054c57df..ca0ade00 100644 --- a/apps/web/public/flags/wf.svg +++ b/apps/web/public/flags/wf.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/ws.svg b/apps/web/public/flags/ws.svg index 0e758a7a..8655cdc2 100644 --- a/apps/web/public/flags/ws.svg +++ b/apps/web/public/flags/ws.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/xk.svg b/apps/web/public/flags/xk.svg index 0e8958d8..d5b5ba7d 100644 --- a/apps/web/public/flags/xk.svg +++ b/apps/web/public/flags/xk.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/xx.svg b/apps/web/public/flags/xx.svg index 9333be36..0baee780 100644 --- a/apps/web/public/flags/xx.svg +++ b/apps/web/public/flags/xx.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/flags/ye.svg b/apps/web/public/flags/ye.svg index 1c9e6d63..83886232 100644 --- a/apps/web/public/flags/ye.svg +++ b/apps/web/public/flags/ye.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/apps/web/public/flags/yt.svg b/apps/web/public/flags/yt.svg index e7776b30..75b580ea 100644 --- a/apps/web/public/flags/yt.svg +++ b/apps/web/public/flags/yt.svg @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/apps/web/public/flags/za.svg b/apps/web/public/flags/za.svg index d563adb9..6aeb743e 100644 --- a/apps/web/public/flags/za.svg +++ b/apps/web/public/flags/za.svg @@ -1,17 +1,17 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/zm.svg b/apps/web/public/flags/zm.svg index 360f37aa..f7f72c72 100644 --- a/apps/web/public/flags/zm.svg +++ b/apps/web/public/flags/zm.svg @@ -1,27 +1,27 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/flags/zw.svg b/apps/web/public/flags/zw.svg index 93aac4f6..0d481a94 100644 --- a/apps/web/public/flags/zw.svg +++ b/apps/web/public/flags/zw.svg @@ -1,21 +1,21 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/fonts/font-atlas.json b/apps/web/public/fonts/font-atlas.json index deb0eb91..a5c2b7f6 100644 --- a/apps/web/public/fonts/font-atlas.json +++ b/apps/web/public/fonts/font-atlas.json @@ -1,11348 +1,11348 @@ -{ - "fonts": { - "42dot Sans": { - "x": 0, - "y": 0, - "w": 131, - "ch": 0, - "s": ["300", "400", "500", "600", "700", "800"] - }, - "ABeeZee": { "x": 145, "y": 0, "w": 106, "ch": 0, "s": ["400", "400i"] }, - "Abel": { "x": 265, "y": 0, "w": 47, "ch": 0, "s": ["400"] }, - "Abhaya Libre": { - "x": 326, - "y": 0, - "w": 139, - "ch": 0, - "s": ["400", "500", "600", "700", "800"] - }, - "Aboreto": { "x": 479, "y": 0, "w": 124, "ch": 0, "s": ["400"] }, - "Abril Fatface": { "x": 617, "y": 0, "w": 152, "ch": 0, "s": ["400"] }, - "Abyssinica SIL": { "x": 783, "y": 0, "w": 165, "ch": 0, "s": ["400"] }, - "Aclonica": { "x": 962, "y": 0, "w": 122, "ch": 0, "s": ["400"] }, - "Acme": { "x": 1098, "y": 0, "w": 63, "ch": 0, "s": ["400"] }, - "Actor": { "x": 0, "y": 40, "w": 64, "ch": 0, "s": ["400"] }, - "Adamina": { "x": 78, "y": 40, "w": 113, "ch": 0, "s": ["400"] }, - "ADLaM Display": { "x": 205, "y": 40, "w": 183, "ch": 0, "s": ["400"] }, - "Advent Pro": { - "x": 402, - "y": 40, - "w": 107, - "ch": 0, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Afacad": { - "x": 523, - "y": 40, - "w": 77, - "ch": 0, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Afacad Flux": { - "x": 614, - "y": 40, - "w": 118, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Agbalumo": { "x": 746, "y": 40, "w": 119, "ch": 0, "s": ["400"] }, - "Agdasima": { "x": 879, "y": 40, "w": 81, "ch": 0, "s": ["400", "700"] }, - "Agu Display": { "x": 974, "y": 40, "w": 140, "ch": 0, "s": ["400"] }, - "Aguafina Script": { "x": 0, "y": 80, "w": 120, "ch": 0, "s": ["400"] }, - "Akatab": { - "x": 134, - "y": 80, - "w": 81, - "ch": 0, - "s": ["400", "500", "600", "700", "800", "900"] - }, - "Akaya Kanadaka": { "x": 229, "y": 80, "w": 176, "ch": 0, "s": ["400"] }, - "Akaya Telivigala": { "x": 419, "y": 80, "w": 165, "ch": 0, "s": ["400"] }, - "Akronim": { "x": 598, "y": 80, "w": 79, "ch": 0, "s": ["400"] }, - "Akshar": { - "x": 691, - "y": 80, - "w": 71, - "ch": 0, - "s": ["300", "400", "500", "600", "700"] - }, - "Aladin": { "x": 776, "y": 80, "w": 61, "ch": 0, "s": ["400"] }, - "Alan Sans": { - "x": 851, - "y": 80, - "w": 116, - "ch": 0, - "s": ["300", "400", "500", "600", "700", "800", "900"] - }, - "Alata": { "x": 981, "y": 80, "w": 68, "ch": 0, "s": ["400"] }, - "Alatsi": { "x": 1063, "y": 80, "w": 66, "ch": 0, "s": ["400"] }, - "Albert Sans": { - "x": 0, - "y": 120, - "w": 133, - "ch": 0, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Aldrich": { "x": 147, "y": 120, "w": 94, "ch": 0, "s": ["400"] }, - "Alef": { "x": 255, "y": 120, "w": 54, "ch": 0, "s": ["400", "700"] }, - "Alegreya": { - "x": 323, - "y": 120, - "w": 92, - "ch": 0, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Alegreya Sans": { - "x": 429, - "y": 120, - "w": 137, - "ch": 0, - "s": [ - "100", - "100i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Alegreya Sans SC": { - "x": 580, - "y": 120, - "w": 179, - "ch": 0, - "s": [ - "100", - "100i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Alegreya SC": { - "x": 773, - "y": 120, - "w": 137, - "ch": 0, - "s": [ - "400", - "400i", - "500", - "500i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Aleo": { - "x": 924, - "y": 120, - "w": 59, - "ch": 0, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Alex Brush": { "x": 997, "y": 120, "w": 117, "ch": 0, "s": ["400"] }, - "Alexandria": { - "x": 0, - "y": 160, - "w": 136, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Alfa Slab One": { "x": 150, "y": 160, "w": 180, "ch": 0, "s": ["400"] }, - "Alice": { "x": 344, "y": 160, "w": 60, "ch": 0, "s": ["400"] }, - "Alike": { "x": 418, "y": 160, "w": 62, "ch": 0, "s": ["400"] }, - "Alike Angular": { "x": 494, "y": 160, "w": 155, "ch": 0, "s": ["400"] }, - "Alkalami": { "x": 663, "y": 160, "w": 109, "ch": 0, "s": ["400"] }, - "Alkatra": { - "x": 786, - "y": 160, - "w": 84, - "ch": 0, - "s": ["400", "500", "600", "700"] - }, - "Allan": { "x": 884, "y": 160, "w": 48, "ch": 0, "s": ["400", "700"] }, - "Allerta": { "x": 946, "y": 160, "w": 93, "ch": 0, "s": ["400"] }, - "Allerta Stencil": { "x": 0, "y": 200, "w": 182, "ch": 0, "s": ["400"] }, - "Allison": { "x": 196, "y": 200, "w": 56, "ch": 0, "s": ["400"] }, - "Allura": { "x": 266, "y": 200, "w": 69, "ch": 0, "s": ["400"] }, - "Almarai": { - "x": 349, - "y": 200, - "w": 88, - "ch": 0, - "s": ["300", "400", "700", "800"] - }, - "Almendra": { - "x": 451, - "y": 200, - "w": 107, - "ch": 0, - "s": ["400", "400i", "700", "700i"] - }, - "Almendra Display": { "x": 572, "y": 200, "w": 192, "ch": 0, "s": ["400"] }, - "Almendra SC": { "x": 778, "y": 200, "w": 142, "ch": 0, "s": ["400"] }, - "Alumni Sans": { - "x": 934, - "y": 200, - "w": 96, - "ch": 0, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Alumni Sans Collegiate One": { - "x": 0, - "y": 240, - "w": 201, - "ch": 0, - "s": ["400", "400i"] - }, - "Alumni Sans Inline One": { - "x": 215, - "y": 240, - "w": 180, - "ch": 0, - "s": ["400", "400i"] - }, - "Alumni Sans Pinstripe": { - "x": 409, - "y": 240, - "w": 159, - "ch": 0, - "s": ["400", "400i"] - }, - "Alumni Sans SC": { - "x": 582, - "y": 240, - "w": 125, - "ch": 0, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Amarante": { "x": 721, "y": 240, "w": 108, "ch": 0, "s": ["400"] }, - "Amaranth": { - "x": 843, - "y": 240, - "w": 110, - "ch": 0, - "s": ["400", "400i", "700", "700i"] - }, - "Amarna": { - "x": 967, - "y": 240, - "w": 92, - "ch": 0, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Amatic SC": { "x": 1073, "y": 240, "w": 77, "ch": 0, "s": ["400", "700"] }, - "Amethysta": { "x": 0, "y": 280, "w": 133, "ch": 0, "s": ["400"] }, - "Amiko": { - "x": 147, - "y": 280, - "w": 84, - "ch": 0, - "s": ["400", "600", "700"] - }, - "Amiri": { - "x": 245, - "y": 280, - "w": 63, - "ch": 0, - "s": ["400", "400i", "700", "700i"] - }, - "Amiri Quran": { "x": 322, "y": 280, "w": 131, "ch": 0, "s": ["400"] }, - "Amita": { "x": 467, "y": 280, "w": 73, "ch": 0, "s": ["400", "700"] }, - "Anaheim": { - "x": 554, - "y": 280, - "w": 93, - "ch": 0, - "s": ["400", "500", "600", "700", "800"] - }, - "Ancizar Sans": { - "x": 661, - "y": 280, - "w": 126, - "ch": 0, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Ancizar Serif": { - "x": 801, - "y": 280, - "w": 136, - "ch": 0, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Andada Pro": { - "x": 951, - "y": 280, - "w": 137, - "ch": 0, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Andika": { - "x": 1102, - "y": 280, - "w": 85, - "ch": 0, - "s": ["400", "400i", "700", "700i"] - }, - "Anek Bangla": { - "x": 0, - "y": 320, - "w": 131, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Anek Devanagari": { - "x": 145, - "y": 320, - "w": 176, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Anek Gujarati": { - "x": 335, - "y": 320, - "w": 143, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Anek Gurmukhi": { - "x": 492, - "y": 320, - "w": 164, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Anek Kannada": { - "x": 670, - "y": 320, - "w": 152, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Anek Latin": { - "x": 836, - "y": 320, - "w": 115, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Anek Malayalam": { - "x": 965, - "y": 320, - "w": 171, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Anek Odia": { - "x": 0, - "y": 360, - "w": 108, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Anek Tamil": { - "x": 122, - "y": 360, - "w": 119, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Anek Telugu": { - "x": 255, - "y": 360, - "w": 131, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Angkor": { "x": 400, "y": 360, "w": 111, "ch": 0, "s": ["400"] }, - "Annapurna SIL": { - "x": 525, - "y": 360, - "w": 151, - "ch": 0, - "s": ["400", "700"] - }, - "Annie Use Your Telescope": { - "x": 690, - "y": 360, - "w": 215, - "ch": 0, - "s": ["400"] - }, - "Anonymous Pro": { - "x": 919, - "y": 360, - "w": 179, - "ch": 0, - "s": ["400", "400i", "700", "700i"] - }, - "Anta": { "x": 1112, "y": 360, "w": 62, "ch": 0, "s": ["400"] }, - "Antic": { "x": 0, "y": 400, "w": 60, "ch": 0, "s": ["400"] }, - "Antic Didone": { "x": 74, "y": 400, "w": 145, "ch": 0, "s": ["400"] }, - "Antic Slab": { "x": 233, "y": 400, "w": 114, "ch": 0, "s": ["400"] }, - "Anton": { "x": 361, "y": 400, "w": 63, "ch": 0, "s": ["400"] }, - "Anton SC": { "x": 438, "y": 400, "w": 93, "ch": 0, "s": ["400"] }, - "Antonio": { - "x": 545, - "y": 400, - "w": 76, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "Anuphan": { - "x": 635, - "y": 400, - "w": 105, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "Anybody": { - "x": 754, - "y": 400, - "w": 107, - "ch": 0, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Aoboshi One": { "x": 875, "y": 400, "w": 158, "ch": 0, "s": ["400"] }, - "AR One Sans": { - "x": 1047, - "y": 400, - "w": 152, - "ch": 0, - "s": ["400", "500", "600", "700"] - }, - "Arapey": { "x": 0, "y": 440, "w": 75, "ch": 0, "s": ["400", "400i"] }, - "Arbutus": { "x": 89, "y": 440, "w": 128, "ch": 0, "s": ["400"] }, - "Arbutus Slab": { "x": 231, "y": 440, "w": 159, "ch": 0, "s": ["400"] }, - "Architects Daughter": { - "x": 404, - "y": 440, - "w": 237, - "ch": 0, - "s": ["400"] - }, - "Archivo": { - "x": 655, - "y": 440, - "w": 90, - "ch": 0, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Archivo Black": { "x": 759, "y": 440, "w": 190, "ch": 0, "s": ["400"] }, - "Archivo Narrow": { - "x": 963, - "y": 440, - "w": 144, - "ch": 0, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Are You Serious": { "x": 0, "y": 480, "w": 142, "ch": 0, "s": ["400"] }, - "Aref Ruqaa": { - "x": 156, - "y": 480, - "w": 130, - "ch": 0, - "s": ["400", "700"] - }, - "Aref Ruqaa Ink": { - "x": 300, - "y": 480, - "w": 172, - "ch": 0, - "s": ["400", "700"] - }, - "Arima": { - "x": 486, - "y": 480, - "w": 73, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "Arima Madurai": { - "x": 573, - "y": 480, - "w": 170, - "ch": 0, - "s": ["100", "200", "300", "400", "500", "700", "800", "900"] - }, - "Arimo": { - "x": 757, - "y": 480, - "w": 71, - "ch": 0, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Arizonia": { "x": 842, "y": 480, "w": 95, "ch": 0, "s": ["400"] }, - "Armata": { "x": 951, "y": 480, "w": 100, "ch": 0, "s": ["400"] }, - "Arsenal": { - "x": 1065, - "y": 480, - "w": 80, - "ch": 0, - "s": ["400", "400i", "700", "700i"] - }, - "Arsenal SC": { - "x": 0, - "y": 520, - "w": 118, - "ch": 0, - "s": ["400", "400i", "700", "700i"] - }, - "Artifika": { "x": 132, "y": 520, "w": 105, "ch": 0, "s": ["400"] }, - "Arvo": { - "x": 251, - "y": 520, - "w": 65, - "ch": 0, - "s": ["400", "400i", "700", "700i"] - }, - "Arya": { "x": 330, "y": 520, "w": 53, "ch": 0, "s": ["400", "700"] }, - "Asap": { - "x": 397, - "y": 520, - "w": 60, - "ch": 0, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Asap Condensed": { - "x": 471, - "y": 520, - "w": 153, - "ch": 0, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Asar": { "x": 638, "y": 520, "w": 53, "ch": 0, "s": ["400"] }, - "Asimovian": { "x": 705, "y": 520, "w": 117, "ch": 0, "s": ["400"] }, - "Asset": { "x": 836, "y": 520, "w": 154, "ch": 0, "s": ["400"] }, - "Assistant": { - "x": 1004, - "y": 520, - "w": 98, - "ch": 0, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Asta Sans": { - "x": 0, - "y": 560, - "w": 115, - "ch": 0, - "s": ["300", "400", "500", "600", "700", "800"] - }, - "Astloch": { "x": 129, "y": 560, "w": 72, "ch": 0, "s": ["400", "700"] }, - "Asul": { "x": 215, "y": 560, "w": 54, "ch": 0, "s": ["400", "700"] }, - "Athiti": { - "x": 283, - "y": 560, - "w": 62, - "ch": 0, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Atkinson Hyperlegible": { - "x": 359, - "y": 560, - "w": 239, - "ch": 0, - "s": ["400", "400i", "700", "700i"] - }, - "Atkinson Hyperlegible Mono": { - "x": 612, - "y": 560, - "w": 403, - "ch": 0, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Atkinson Hyperlegible Next": { - "x": 0, - "y": 600, - "w": 296, - "ch": 0, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Atma": { - "x": 310, - "y": 600, - "w": 59, - "ch": 0, - "s": ["300", "400", "500", "600", "700"] - }, - "Atomic Age": { "x": 383, "y": 600, "w": 144, "ch": 0, "s": ["400"] }, - "Aubrey": { "x": 541, "y": 600, "w": 67, "ch": 0, "s": ["400"] }, - "Audiowide": { "x": 622, "y": 600, "w": 145, "ch": 0, "s": ["400"] }, - "Autour One": { "x": 781, "y": 600, "w": 168, "ch": 0, "s": ["400"] }, - "Average": { "x": 963, "y": 600, "w": 88, "ch": 0, "s": ["400"] }, - "Average Sans": { "x": 0, "y": 640, "w": 138, "ch": 0, "s": ["400"] }, - "Averia Gruesa Libre": { - "x": 152, - "y": 640, - "w": 217, - "ch": 0, - "s": ["400"] - }, - "Averia Libre": { - "x": 383, - "y": 640, - "w": 138, - "ch": 0, - "s": ["300", "300i", "400", "400i", "700", "700i"] - }, - "Averia Sans Libre": { - "x": 535, - "y": 640, - "w": 190, - "ch": 0, - "s": ["300", "300i", "400", "400i", "700", "700i"] - }, - "Averia Serif Libre": { - "x": 739, - "y": 640, - "w": 202, - "ch": 0, - "s": ["300", "300i", "400", "400i", "700", "700i"] - }, - "Azeret Mono": { - "x": 955, - "y": 640, - "w": 180, - "ch": 0, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "B612": { - "x": 0, - "y": 680, - "w": 71, - "ch": 0, - "s": ["400", "400i", "700", "700i"] - }, - "B612 Mono": { - "x": 85, - "y": 680, - "w": 149, - "ch": 0, - "s": ["400", "400i", "700", "700i"] - }, - "Babylonica": { "x": 248, "y": 680, "w": 90, "ch": 0, "s": ["400"] }, - "Bacasime Antique": { "x": 352, "y": 680, "w": 182, "ch": 0, "s": ["400"] }, - "Bad Script": { "x": 548, "y": 680, "w": 103, "ch": 0, "s": ["400"] }, - "Badeen Display": { "x": 665, "y": 680, "w": 194, "ch": 0, "s": ["400"] }, - "Bagel Fat One": { "x": 873, "y": 680, "w": 158, "ch": 0, "s": ["400"] }, - "Bahiana": { "x": 1045, "y": 680, "w": 58, "ch": 0, "s": ["400"] }, - "Bahianita": { "x": 1117, "y": 680, "w": 63, "ch": 0, "s": ["400"] }, - "Bai Jamjuree": { - "x": 0, - "y": 720, - "w": 152, - "ch": 0, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Bakbak One": { "x": 166, "y": 720, "w": 141, "ch": 0, "s": ["400"] }, - "Ballet": { "x": 321, "y": 720, "w": 65, "ch": 0, "s": ["400"] }, - "Baloo 2": { - "x": 400, - "y": 720, - "w": 84, - "ch": 0, - "s": ["400", "500", "600", "700", "800"] - }, - "Baloo Bhai 2": { - "x": 498, - "y": 720, - "w": 134, - "ch": 0, - "s": ["400", "500", "600", "700", "800"] - }, - "Baloo Bhaijaan 2": { - "x": 646, - "y": 720, - "w": 177, - "ch": 0, - "s": ["400", "500", "600", "700", "800"] - }, - "Baloo Bhaina 2": { - "x": 837, - "y": 720, - "w": 160, - "ch": 0, - "s": ["400", "500", "600", "700", "800"] - }, - "Baloo Chettan 2": { - "x": 1011, - "y": 720, - "w": 172, - "ch": 0, - "s": ["400", "500", "600", "700", "800"] - }, - "Baloo Da 2": { - "x": 0, - "y": 760, - "w": 117, - "ch": 0, - "s": ["400", "500", "600", "700", "800"] - }, - "Baloo Paaji 2": { - "x": 131, - "y": 760, - "w": 138, - "ch": 0, - "s": ["400", "500", "600", "700", "800"] - }, - "Baloo Tamma 2": { - "x": 283, - "y": 760, - "w": 169, - "ch": 0, - "s": ["400", "500", "600", "700", "800"] - }, - "Baloo Tammudu 2": { - "x": 466, - "y": 760, - "w": 191, - "ch": 0, - "s": ["400", "500", "600", "700", "800"] - }, - "Baloo Thambi 2": { - "x": 671, - "y": 760, - "w": 165, - "ch": 0, - "s": ["400", "500", "600", "700", "800"] - }, - "Balsamiq Sans": { - "x": 850, - "y": 760, - "w": 168, - "ch": 0, - "s": ["400", "400i", "700", "700i"] - }, - "Balthazar": { "x": 1032, "y": 760, "w": 96, "ch": 0, "s": ["400"] }, - "Bangers": { "x": 0, "y": 0, "w": 81, "ch": 1, "s": ["400"] }, - "Barlow": { - "x": 95, - "y": 0, - "w": 80, - "ch": 1, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Barlow Condensed": { - "x": 189, - "y": 0, - "w": 158, - "ch": 1, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Barlow Semi Condensed": { - "x": 361, - "y": 0, - "w": 231, - "ch": 1, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Barriecito": { "x": 606, "y": 0, "w": 106, "ch": 1, "s": ["400"] }, - "Barrio": { "x": 726, "y": 0, "w": 80, "ch": 1, "s": ["400"] }, - "Basic": { "x": 820, "y": 0, "w": 61, "ch": 1, "s": ["400"] }, - "Baskervville": { - "x": 895, - "y": 0, - "w": 136, - "ch": 1, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Baskervville SC": { - "x": 0, - "y": 40, - "w": 187, - "ch": 1, - "s": ["400", "500", "600", "700"] - }, - "Battambang": { - "x": 201, - "y": 40, - "w": 147, - "ch": 1, - "s": ["100", "300", "400", "700", "900"] - }, - "Baumans": { "x": 362, "y": 40, "w": 101, "ch": 1, "s": ["400"] }, - "Bayon": { "x": 477, "y": 40, "w": 61, "ch": 1, "s": ["400"] }, - "BBH Bartle": { "x": 552, "y": 40, "w": 298, "ch": 1, "s": ["400"] }, - "BBH Bogle": { "x": 864, "y": 40, "w": 98, "ch": 1, "s": ["400"] }, - "BBH Hegarty": { "x": 976, "y": 40, "w": 182, "ch": 1, "s": ["400"] }, - "BBH Sans Bartle": { "x": 0, "y": 80, "w": 427, "ch": 1, "s": ["400"] }, - "BBH Sans Bogle": { "x": 441, "y": 80, "w": 142, "ch": 1, "s": ["400"] }, - "BBH Sans Hegarty": { "x": 597, "y": 80, "w": 254, "ch": 1, "s": ["400"] }, - "Be Vietnam Pro": { - "x": 865, - "y": 80, - "w": 188, - "ch": 1, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Beau Rivage": { "x": 1067, "y": 80, "w": 108, "ch": 1, "s": ["400"] }, - "Bebas Neue": { "x": 0, "y": 120, "w": 96, "ch": 1, "s": ["400"] }, - "Beiruti": { - "x": 110, - "y": 120, - "w": 66, - "ch": 1, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Belanosima": { - "x": 190, - "y": 120, - "w": 124, - "ch": 1, - "s": ["400", "600", "700"] - }, - "Belgrano": { "x": 328, "y": 120, "w": 115, "ch": 1, "s": ["400"] }, - "Bellefair": { "x": 457, "y": 120, "w": 81, "ch": 1, "s": ["400"] }, - "Belleza": { "x": 552, "y": 120, "w": 79, "ch": 1, "s": ["400"] }, - "Bellota": { - "x": 645, - "y": 120, - "w": 84, - "ch": 1, - "s": ["300", "300i", "400", "400i", "700", "700i"] - }, - "Bellota Text": { - "x": 743, - "y": 120, - "w": 129, - "ch": 1, - "s": ["300", "300i", "400", "400i", "700", "700i"] - }, - "BenchNine": { - "x": 886, - "y": 120, - "w": 82, - "ch": 1, - "s": ["300", "400", "700"] - }, - "Benne": { "x": 982, "y": 120, "w": 66, "ch": 1, "s": ["400"] }, - "Bentham": { "x": 1062, "y": 120, "w": 98, "ch": 1, "s": ["400"] }, - "Berkshire Swash": { "x": 0, "y": 160, "w": 178, "ch": 1, "s": ["400"] }, - "Besley": { - "x": 192, - "y": 160, - "w": 86, - "ch": 1, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Beth Ellen": { "x": 292, "y": 160, "w": 138, "ch": 1, "s": ["400"] }, - "Bevan": { "x": 444, "y": 160, "w": 94, "ch": 1, "s": ["400", "400i"] }, - "BhuTuka Expanded One": { - "x": 552, - "y": 160, - "w": 412, - "ch": 1, - "s": ["400"] - }, - "Big Shoulders": { - "x": 978, - "y": 160, - "w": 117, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Big Shoulders Display": { - "x": 0, - "y": 200, - "w": 158, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Big Shoulders Inline": { - "x": 172, - "y": 200, - "w": 167, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Big Shoulders Inline Display": { - "x": 353, - "y": 200, - "w": 203, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Big Shoulders Inline Text": { - "x": 570, - "y": 200, - "w": 201, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Big Shoulders Stencil": { - "x": 785, - "y": 200, - "w": 177, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Big Shoulders Stencil Display": { - "x": 976, - "y": 200, - "w": 211, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Big Shoulders Stencil Text": { - "x": 0, - "y": 240, - "w": 210, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Big Shoulders Text": { - "x": 224, - "y": 240, - "w": 150, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bigelow Rules": { "x": 388, "y": 240, "w": 86, "ch": 1, "s": ["400"] }, - "Bigshot One": { "x": 488, "y": 240, "w": 138, "ch": 1, "s": ["400"] }, - "Bilbo": { "x": 640, "y": 240, "w": 45, "ch": 1, "s": ["400"] }, - "Bilbo Swash Caps": { "x": 699, "y": 240, "w": 144, "ch": 1, "s": ["400"] }, - "BioRhyme": { - "x": 857, - "y": 240, - "w": 139, - "ch": 1, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "BioRhyme Expanded": { - "x": 0, - "y": 280, - "w": 455, - "ch": 1, - "s": ["200", "300", "400", "700", "800"] - }, - "Birthstone": { "x": 469, "y": 280, "w": 79, "ch": 1, "s": ["400"] }, - "Birthstone Bounce": { - "x": 562, - "y": 280, - "w": 167, - "ch": 1, - "s": ["400", "500"] - }, - "Biryani": { - "x": 743, - "y": 280, - "w": 90, - "ch": 1, - "s": ["200", "300", "400", "600", "700", "800", "900"] - }, - "Bitcount": { - "x": 847, - "y": 280, - "w": 124, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bitcount Grid Double": { - "x": 0, - "y": 320, - "w": 296, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bitcount Grid Double Ink": { - "x": 310, - "y": 320, - "w": 354, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bitcount Grid Single": { - "x": 678, - "y": 320, - "w": 296, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bitcount Grid Single Ink": { - "x": 0, - "y": 360, - "w": 354, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bitcount Ink": { - "x": 368, - "y": 360, - "w": 181, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bitcount Prop Double": { - "x": 563, - "y": 360, - "w": 265, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bitcount Prop Double Ink": { - "x": 842, - "y": 360, - "w": 308, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bitcount Prop Single": { - "x": 0, - "y": 400, - "w": 244, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bitcount Prop Single Ink": { - "x": 258, - "y": 400, - "w": 282, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bitcount Single": { - "x": 554, - "y": 400, - "w": 224, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bitcount Single Ink": { - "x": 792, - "y": 400, - "w": 282, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bitter": { - "x": 1088, - "y": 400, - "w": 71, - "ch": 1, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "BIZ UDGothic": { - "x": 0, - "y": 440, - "w": 152, - "ch": 1, - "s": ["400", "700"] - }, - "BIZ UDMincho": { - "x": 166, - "y": 440, - "w": 152, - "ch": 1, - "s": ["400", "700"] - }, - "BIZ UDPGothic": { - "x": 332, - "y": 440, - "w": 209, - "ch": 1, - "s": ["400", "700"] - }, - "BIZ UDPMincho": { - "x": 555, - "y": 440, - "w": 208, - "ch": 1, - "s": ["400", "700"] - }, - "Black And White Picture": { - "x": 777, - "y": 440, - "w": 218, - "ch": 1, - "s": ["400"] - }, - "Black Han Sans": { "x": 0, "y": 480, "w": 212, "ch": 1, "s": ["400"] }, - "Black Ops One": { "x": 226, "y": 480, "w": 191, "ch": 1, "s": ["400"] }, - "Blaka": { "x": 431, "y": 480, "w": 57, "ch": 1, "s": ["400"] }, - "Blaka Hollow": { "x": 502, "y": 480, "w": 119, "ch": 1, "s": ["400"] }, - "Blaka Ink": { "x": 635, "y": 480, "w": 87, "ch": 1, "s": ["400"] }, - "Blinker": { - "x": 736, - "y": 480, - "w": 77, - "ch": 1, - "s": ["100", "200", "300", "400", "600", "700", "800", "900"] - }, - "Bodoni Moda": { - "x": 827, - "y": 480, - "w": 155, - "ch": 1, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Bodoni Moda SC": { - "x": 996, - "y": 480, - "w": 200, - "ch": 1, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Bokor": { "x": 0, "y": 520, "w": 58, "ch": 1, "s": ["400"] }, - "Boldonse": { "x": 72, "y": 520, "w": 147, "ch": 1, "s": ["400"] }, - "Bona Nova": { - "x": 233, - "y": 520, - "w": 124, - "ch": 1, - "s": ["400", "400i", "700", "700i"] - }, - "Bona Nova SC": { - "x": 371, - "y": 520, - "w": 170, - "ch": 1, - "s": ["400", "400i", "700", "700i"] - }, - "Bonbon": { "x": 555, "y": 520, "w": 104, "ch": 1, "s": ["400"] }, - "Bonheur Royale": { "x": 673, "y": 520, "w": 124, "ch": 1, "s": ["400"] }, - "Boogaloo": { "x": 811, "y": 520, "w": 81, "ch": 1, "s": ["400"] }, - "Borel": { "x": 906, "y": 520, "w": 69, "ch": 1, "s": ["400"] }, - "Bowlby One": { "x": 989, "y": 520, "w": 174, "ch": 1, "s": ["400"] }, - "Bowlby One SC": { "x": 0, "y": 560, "w": 213, "ch": 1, "s": ["400"] }, - "Braah One": { "x": 227, "y": 560, "w": 124, "ch": 1, "s": ["400"] }, - "Brawler": { "x": 365, "y": 560, "w": 95, "ch": 1, "s": ["400", "700"] }, - "Bree Serif": { "x": 474, "y": 560, "w": 113, "ch": 1, "s": ["400"] }, - "Bricolage Grotesque": { - "x": 601, - "y": 560, - "w": 238, - "ch": 1, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Briem Hand": { - "x": 853, - "y": 560, - "w": 146, - "ch": 1, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Bruno Ace": { "x": 1013, "y": 560, "w": 168, "ch": 1, "s": ["400"] }, - "Bruno Ace SC": { "x": 0, "y": 600, "w": 219, "ch": 1, "s": ["400"] }, - "Brygada 1918": { - "x": 233, - "y": 600, - "w": 157, - "ch": 1, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Bubblegum Sans": { "x": 404, "y": 600, "w": 155, "ch": 1, "s": ["400"] }, - "Bubbler One": { "x": 573, "y": 600, "w": 117, "ch": 1, "s": ["400"] }, - "Buenard": { - "x": 704, - "y": 600, - "w": 93, - "ch": 1, - "s": ["400", "500", "600", "700"] - }, - "Bungee": { "x": 811, "y": 600, "w": 110, "ch": 1, "s": ["400"] }, - "Bungee Hairline": { "x": 935, "y": 600, "w": 249, "ch": 1, "s": ["400"] }, - "Bungee Inline": { "x": 0, "y": 640, "w": 213, "ch": 1, "s": ["400"] }, - "Bungee Outline": { "x": 227, "y": 640, "w": 232, "ch": 1, "s": ["400"] }, - "Bungee Shade": { "x": 473, "y": 640, "w": 229, "ch": 1, "s": ["400"] }, - "Bungee Spice": { "x": 716, "y": 640, "w": 193, "ch": 1, "s": ["400"] }, - "Bungee Tint": { "x": 923, "y": 640, "w": 179, "ch": 1, "s": ["400"] }, - "Butcherman": { "x": 0, "y": 680, "w": 161, "ch": 1, "s": ["400"] }, - "Butterfly Kids": { "x": 175, "y": 680, "w": 116, "ch": 1, "s": ["400"] }, - "Bytesized": { "x": 305, "y": 680, "w": 116, "ch": 1, "s": ["400"] }, - "Cabin": { - "x": 435, - "y": 680, - "w": 67, - "ch": 1, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Cabin Condensed": { - "x": 516, - "y": 680, - "w": 163, - "ch": 1, - "s": ["400", "500", "600", "700"] - }, - "Cabin Sketch": { - "x": 693, - "y": 680, - "w": 141, - "ch": 1, - "s": ["400", "700"] - }, - "Cactus Classical Serif": { - "x": 848, - "y": 680, - "w": 235, - "ch": 1, - "s": ["400"] - }, - "Caesar Dressing": { "x": 0, "y": 720, "w": 164, "ch": 1, "s": ["400"] }, - "Cagliostro": { "x": 178, "y": 720, "w": 115, "ch": 1, "s": ["400"] }, - "Cairo": { - "x": 307, - "y": 720, - "w": 60, - "ch": 1, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Cairo Play": { - "x": 381, - "y": 720, - "w": 108, - "ch": 1, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Cal Sans": { "x": 503, "y": 720, "w": 99, "ch": 1, "s": ["400"] }, - "Caladea": { - "x": 616, - "y": 720, - "w": 84, - "ch": 1, - "s": ["400", "400i", "700", "700i"] - }, - "Calistoga": { "x": 714, "y": 720, "w": 109, "ch": 1, "s": ["400"] }, - "Calligraffitti": { "x": 837, "y": 720, "w": 124, "ch": 1, "s": ["400"] }, - "Cambay": { - "x": 975, - "y": 720, - "w": 92, - "ch": 1, - "s": ["400", "400i", "700", "700i"] - }, - "Cambo": { "x": 1081, "y": 720, "w": 82, "ch": 1, "s": ["400"] }, - "Candal": { "x": 0, "y": 760, "w": 102, "ch": 1, "s": ["400"] }, - "Cantarell": { - "x": 116, - "y": 760, - "w": 109, - "ch": 1, - "s": ["400", "400i", "700", "700i"] - }, - "Cantata One": { "x": 239, "y": 760, "w": 163, "ch": 1, "s": ["400"] }, - "Cantora One": { "x": 416, "y": 760, "w": 134, "ch": 1, "s": ["400"] }, - "Caprasimo": { "x": 564, "y": 760, "w": 139, "ch": 1, "s": ["400"] }, - "Capriola": { "x": 717, "y": 760, "w": 110, "ch": 1, "s": ["400"] }, - "Caramel": { "x": 841, "y": 760, "w": 66, "ch": 1, "s": ["400"] }, - "Carattere": { "x": 921, "y": 760, "w": 79, "ch": 1, "s": ["400"] }, - "Cardo": { - "x": 1014, - "y": 760, - "w": 72, - "ch": 1, - "s": ["400", "400i", "700", "700i"] - }, - "Carlito": { - "x": 1100, - "y": 760, - "w": 73, - "ch": 1, - "s": ["400", "400i", "700", "700i"] - }, - "Carme": { "x": 0, "y": 0, "w": 79, "ch": 2, "s": ["400"] }, - "Carrois Gothic": { "x": 93, "y": 0, "w": 155, "ch": 2, "s": ["400"] }, - "Carrois Gothic SC": { "x": 262, "y": 0, "w": 211, "ch": 2, "s": ["400"] }, - "Carter One": { "x": 487, "y": 0, "w": 136, "ch": 2, "s": ["400"] }, - "Cascadia Code": { - "x": 637, - "y": 0, - "w": 191, - "ch": 2, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Cascadia Mono": { - "x": 842, - "y": 0, - "w": 191, - "ch": 2, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Castoro": { "x": 1047, "y": 0, "w": 93, "ch": 2, "s": ["400", "400i"] }, - "Castoro Titling": { "x": 0, "y": 40, "w": 236, "ch": 2, "s": ["400"] }, - "Catamaran": { - "x": 250, - "y": 40, - "w": 116, - "ch": 2, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Caudex": { - "x": 380, - "y": 40, - "w": 92, - "ch": 2, - "s": ["400", "400i", "700", "700i"] - }, - "Cause": { - "x": 486, - "y": 40, - "w": 72, - "ch": 2, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Caveat": { - "x": 572, - "y": 40, - "w": 63, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Caveat Brush": { "x": 649, "y": 40, "w": 120, "ch": 2, "s": ["400"] }, - "Cedarville Cursive": { - "x": 783, - "y": 40, - "w": 203, - "ch": 2, - "s": ["400"] - }, - "Ceviche One": { "x": 1000, "y": 40, "w": 111, "ch": 2, "s": ["400"] }, - "Chakra Petch": { - "x": 0, - "y": 80, - "w": 154, - "ch": 2, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Changa": { - "x": 168, - "y": 80, - "w": 86, - "ch": 2, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Changa One": { - "x": 268, - "y": 80, - "w": 136, - "ch": 2, - "s": ["400", "400i"] - }, - "Chango": { "x": 418, "y": 80, "w": 128, "ch": 2, "s": ["400"] }, - "Charis SIL": { - "x": 560, - "y": 80, - "w": 116, - "ch": 2, - "s": ["400", "400i", "700", "700i"] - }, - "Charm": { "x": 690, "y": 80, "w": 69, "ch": 2, "s": ["400", "700"] }, - "Charmonman": { "x": 773, "y": 80, "w": 136, "ch": 2, "s": ["400", "700"] }, - "Chathura": { - "x": 923, - "y": 80, - "w": 55, - "ch": 2, - "s": ["100", "300", "400", "700", "800"] - }, - "Chau Philomene One": { - "x": 992, - "y": 80, - "w": 200, - "ch": 2, - "s": ["400", "400i"] - }, - "Chela One": { "x": 0, "y": 120, "w": 97, "ch": 2, "s": ["400"] }, - "Chelsea Market": { "x": 111, "y": 120, "w": 200, "ch": 2, "s": ["400"] }, - "Cherish": { "x": 325, "y": 120, "w": 60, "ch": 2, "s": ["400"] }, - "Cherry Bomb One": { "x": 399, "y": 120, "w": 200, "ch": 2, "s": ["400"] }, - "Cherry Cream Soda": { - "x": 613, - "y": 120, - "w": 274, - "ch": 2, - "s": ["400"] - }, - "Cherry Swash": { - "x": 901, - "y": 120, - "w": 159, - "ch": 2, - "s": ["400", "700"] - }, - "Chewy": { "x": 1074, "y": 120, "w": 73, "ch": 2, "s": ["400"] }, - "Chicle": { "x": 0, "y": 160, "w": 56, "ch": 2, "s": ["400"] }, - "Chilanka": { "x": 70, "y": 160, "w": 99, "ch": 2, "s": ["400"] }, - "Chiron GoRound TC": { - "x": 183, - "y": 160, - "w": 225, - "ch": 2, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Chiron Hei HK": { - "x": 422, - "y": 160, - "w": 163, - "ch": 2, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Chivo": { - "x": 599, - "y": 160, - "w": 72, - "ch": 2, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Chivo Mono": { - "x": 685, - "y": 160, - "w": 152, - "ch": 2, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Chocolate Classical Sans": { - "x": 851, - "y": 160, - "w": 283, - "ch": 2, - "s": ["400"] - }, - "Chokokutai": { "x": 0, "y": 200, "w": 148, "ch": 2, "s": ["400"] }, - "Chonburi": { "x": 162, "y": 200, "w": 138, "ch": 2, "s": ["400"] }, - "Cinzel": { - "x": 314, - "y": 200, - "w": 94, - "ch": 2, - "s": ["400", "500", "600", "700", "800", "900"] - }, - "Cinzel Decorative": { - "x": 422, - "y": 200, - "w": 262, - "ch": 2, - "s": ["400", "700", "900"] - }, - "Clicker Script": { "x": 698, "y": 200, "w": 117, "ch": 2, "s": ["400"] }, - "Climate Crisis": { "x": 829, "y": 200, "w": 247, "ch": 2, "s": ["400"] }, - "Coda": { "x": 1090, "y": 200, "w": 65, "ch": 2, "s": ["400", "800"] }, - "Codystar": { "x": 0, "y": 240, "w": 137, "ch": 2, "s": ["300", "400"] }, - "Coiny": { "x": 151, "y": 240, "w": 79, "ch": 2, "s": ["400"] }, - "Combo": { "x": 244, "y": 240, "w": 69, "ch": 2, "s": ["400"] }, - "Comfortaa": { - "x": 327, - "y": 240, - "w": 143, - "ch": 2, - "s": ["300", "400", "500", "600", "700"] - }, - "Comforter": { "x": 484, "y": 240, "w": 99, "ch": 2, "s": ["400"] }, - "Comforter Brush": { "x": 597, "y": 240, "w": 147, "ch": 2, "s": ["400"] }, - "Comic Neue": { - "x": 758, - "y": 240, - "w": 133, - "ch": 2, - "s": ["300", "300i", "400", "400i", "700", "700i"] - }, - "Comic Relief": { - "x": 905, - "y": 240, - "w": 147, - "ch": 2, - "s": ["400", "700"] - }, - "Coming Soon": { "x": 0, "y": 280, "w": 152, "ch": 2, "s": ["400"] }, - "Comme": { - "x": 166, - "y": 280, - "w": 96, - "ch": 2, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Commissioner": { - "x": 276, - "y": 280, - "w": 166, - "ch": 2, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Concert One": { "x": 456, "y": 280, "w": 136, "ch": 2, "s": ["400"] }, - "Condiment": { "x": 606, "y": 280, "w": 96, "ch": 2, "s": ["400"] }, - "Contrail One": { "x": 716, "y": 280, "w": 128, "ch": 2, "s": ["400"] }, - "Convergence": { "x": 858, "y": 280, "w": 155, "ch": 2, "s": ["400"] }, - "Cookie": { "x": 1027, "y": 280, "w": 55, "ch": 2, "s": ["400"] }, - "Copse": { "x": 1096, "y": 280, "w": 73, "ch": 2, "s": ["400"] }, - "Coral Pixels": { "x": 0, "y": 320, "w": 140, "ch": 2, "s": ["400"] }, - "Corben": { "x": 154, "y": 320, "w": 91, "ch": 2, "s": ["400", "700"] }, - "Corinthia": { "x": 259, "y": 320, "w": 69, "ch": 2, "s": ["400", "700"] }, - "Cormorant": { - "x": 342, - "y": 320, - "w": 113, - "ch": 2, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Cormorant Garamond": { - "x": 469, - "y": 320, - "w": 220, - "ch": 2, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Cormorant Infant": { - "x": 703, - "y": 320, - "w": 181, - "ch": 2, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Cormorant SC": { - "x": 898, - "y": 320, - "w": 167, - "ch": 2, - "s": ["300", "400", "500", "600", "700"] - }, - "Cormorant Unicase": { - "x": 0, - "y": 360, - "w": 222, - "ch": 2, - "s": ["300", "400", "500", "600", "700"] - }, - "Cormorant Upright": { - "x": 236, - "y": 360, - "w": 188, - "ch": 2, - "s": ["300", "400", "500", "600", "700"] - }, - "Cossette Texte": { - "x": 438, - "y": 360, - "w": 164, - "ch": 2, - "s": ["400", "700"] - }, - "Cossette Titre": { - "x": 616, - "y": 360, - "w": 116, - "ch": 2, - "s": ["400", "700"] - }, - "Courgette": { "x": 746, "y": 360, "w": 109, "ch": 2, "s": ["400"] }, - "Courier Prime": { - "x": 869, - "y": 360, - "w": 196, - "ch": 2, - "s": ["400", "400i", "700", "700i"] - }, - "Cousine": { - "x": 1079, - "y": 360, - "w": 109, - "ch": 2, - "s": ["400", "400i", "700", "700i"] - }, - "Coustard": { "x": 0, "y": 400, "w": 115, "ch": 2, "s": ["400", "900"] }, - "Covered By Your Grace": { - "x": 129, - "y": 400, - "w": 206, - "ch": 2, - "s": ["400"] - }, - "Crafty Girls": { "x": 349, "y": 400, "w": 153, "ch": 2, "s": ["400"] }, - "Creepster": { "x": 516, "y": 400, "w": 101, "ch": 2, "s": ["400"] }, - "Crete Round": { - "x": 631, - "y": 400, - "w": 142, - "ch": 2, - "s": ["400", "400i"] - }, - "Crimson Pro": { - "x": 787, - "y": 400, - "w": 127, - "ch": 2, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Crimson Text": { - "x": 928, - "y": 400, - "w": 139, - "ch": 2, - "s": ["400", "400i", "600", "600i", "700", "700i"] - }, - "Croissant One": { "x": 0, "y": 440, "w": 186, "ch": 2, "s": ["400"] }, - "Crushed": { "x": 200, "y": 440, "w": 79, "ch": 2, "s": ["400"] }, - "Cuprum": { - "x": 293, - "y": 440, - "w": 77, - "ch": 2, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Cute Font": { "x": 384, "y": 440, "w": 78, "ch": 2, "s": ["400"] }, - "Cutive": { "x": 476, "y": 440, "w": 98, "ch": 2, "s": ["400"] }, - "Cutive Mono": { "x": 588, "y": 440, "w": 168, "ch": 2, "s": ["400"] }, - "Dai Banna SIL": { - "x": 770, - "y": 440, - "w": 137, - "ch": 2, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Damion": { "x": 921, "y": 440, "w": 78, "ch": 2, "s": ["400"] }, - "Dancing Script": { - "x": 1013, - "y": 440, - "w": 137, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Danfo": { "x": 0, "y": 480, "w": 86, "ch": 2, "s": ["400"] }, - "Dangrek": { "x": 100, "y": 480, "w": 91, "ch": 2, "s": ["400"] }, - "Darker Grotesque": { - "x": 205, - "y": 480, - "w": 163, - "ch": 2, - "s": ["300", "400", "500", "600", "700", "800", "900"] - }, - "Darumadrop One": { "x": 382, "y": 480, "w": 184, "ch": 2, "s": ["400"] }, - "David Libre": { - "x": 580, - "y": 480, - "w": 120, - "ch": 2, - "s": ["400", "500", "700"] - }, - "Dawning of a New Day": { - "x": 714, - "y": 480, - "w": 208, - "ch": 2, - "s": ["400"] - }, - "Days One": { "x": 936, "y": 480, "w": 133, "ch": 2, "s": ["400"] }, - "Dekko": { "x": 1083, "y": 480, "w": 62, "ch": 2, "s": ["400"] }, - "Dela Gothic One": { "x": 0, "y": 520, "w": 234, "ch": 2, "s": ["400"] }, - "Delicious Handrawn": { - "x": 248, - "y": 520, - "w": 161, - "ch": 2, - "s": ["400"] - }, - "Delius": { "x": 423, "y": 520, "w": 74, "ch": 2, "s": ["400"] }, - "Delius Swash Caps": { - "x": 511, - "y": 520, - "w": 214, - "ch": 2, - "s": ["400"] - }, - "Delius Unicase": { - "x": 739, - "y": 520, - "w": 229, - "ch": 2, - "s": ["400", "700"] - }, - "Della Respira": { "x": 982, "y": 520, "w": 149, "ch": 2, "s": ["400"] }, - "Denk One": { "x": 0, "y": 560, "w": 105, "ch": 2, "s": ["400"] }, - "Devonshire": { "x": 119, "y": 560, "w": 87, "ch": 2, "s": ["400"] }, - "Dhurjati": { "x": 220, "y": 560, "w": 75, "ch": 2, "s": ["400"] }, - "Didact Gothic": { "x": 309, "y": 560, "w": 145, "ch": 2, "s": ["400"] }, - "Diphylleia": { "x": 468, "y": 560, "w": 112, "ch": 2, "s": ["400"] }, - "Diplomata": { "x": 594, "y": 560, "w": 252, "ch": 2, "s": ["400"] }, - "Diplomata SC": { "x": 860, "y": 560, "w": 334, "ch": 2, "s": ["400"] }, - "DM Mono": { - "x": 0, - "y": 600, - "w": 109, - "ch": 2, - "s": ["300", "300i", "400", "400i", "500", "500i"] - }, - "DM Sans": { - "x": 123, - "y": 600, - "w": 104, - "ch": 2, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "DM Serif Display": { - "x": 241, - "y": 600, - "w": 181, - "ch": 2, - "s": ["400", "400i"] - }, - "DM Serif Text": { - "x": 436, - "y": 600, - "w": 150, - "ch": 2, - "s": ["400", "400i"] - }, - "Do Hyeon": { "x": 600, "y": 600, "w": 101, "ch": 2, "s": ["400"] }, - "Dokdo": { "x": 715, "y": 600, "w": 68, "ch": 2, "s": ["400"] }, - "Domine": { - "x": 797, - "y": 600, - "w": 101, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Donegal One": { "x": 912, "y": 600, "w": 161, "ch": 2, "s": ["400"] }, - "Dongle": { - "x": 1087, - "y": 600, - "w": 56, - "ch": 2, - "s": ["300", "400", "700"] - }, - "Doppio One": { "x": 0, "y": 640, "w": 138, "ch": 2, "s": ["400"] }, - "Dorsa": { "x": 152, "y": 640, "w": 32, "ch": 2, "s": ["400"] }, - "Dosis": { - "x": 198, - "y": 640, - "w": 56, - "ch": 2, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "DotGothic16": { "x": 268, "y": 640, "w": 140, "ch": 2, "s": ["400"] }, - "Doto": { - "x": 422, - "y": 640, - "w": 66, - "ch": 2, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Dr Sugiyama": { "x": 502, "y": 640, "w": 116, "ch": 2, "s": ["400"] }, - "Duru Sans": { "x": 632, "y": 640, "w": 133, "ch": 2, "s": ["400"] }, - "Dynalight": { "x": 779, "y": 640, "w": 84, "ch": 2, "s": ["400"] }, - "DynaPuff": { - "x": 877, - "y": 640, - "w": 121, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Eagle Lake": { "x": 1012, "y": 640, "w": 153, "ch": 2, "s": ["400"] }, - "East Sea Dokdo": { "x": 0, "y": 680, "w": 118, "ch": 2, "s": ["400"] }, - "Eater": { "x": 132, "y": 680, "w": 90, "ch": 2, "s": ["400"] }, - "EB Garamond": { - "x": 236, - "y": 680, - "w": 141, - "ch": 2, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Economica": { - "x": 391, - "y": 680, - "w": 88, - "ch": 2, - "s": ["400", "400i", "700", "700i"] - }, - "Eczar": { - "x": 493, - "y": 680, - "w": 65, - "ch": 2, - "s": ["400", "500", "600", "700", "800"] - }, - "Edu AU VIC WA NT Arrows": { - "x": 572, - "y": 680, - "w": 302, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Edu AU VIC WA NT Dots": { - "x": 888, - "y": 680, - "w": 261, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Edu AU VIC WA NT Guides": { - "x": 0, - "y": 720, - "w": 285, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Edu AU VIC WA NT Hand": { - "x": 299, - "y": 720, - "w": 274, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Edu AU VIC WA NT Pre": { - "x": 587, - "y": 720, - "w": 257, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Edu NSW ACT Cursive": { - "x": 858, - "y": 720, - "w": 276, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Edu NSW ACT Foundation": { - "x": 0, - "y": 760, - "w": 239, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Edu NSW ACT Hand Pre": { - "x": 253, - "y": 760, - "w": 294, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Edu QLD Beginner": { - "x": 561, - "y": 760, - "w": 177, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Edu QLD Hand": { - "x": 752, - "y": 760, - "w": 175, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Edu SA Beginner": { - "x": 941, - "y": 760, - "w": 155, - "ch": 2, - "s": ["400", "500", "600", "700"] - }, - "Edu SA Hand": { - "x": 0, - "y": 0, - "w": 159, - "ch": 3, - "s": ["400", "500", "600", "700"] - }, - "Edu TAS Beginner": { - "x": 173, - "y": 0, - "w": 175, - "ch": 3, - "s": ["400", "500", "600", "700"] - }, - "Edu VIC WA NT Beginner": { - "x": 362, - "y": 0, - "w": 243, - "ch": 3, - "s": ["400", "500", "600", "700"] - }, - "Edu VIC WA NT Hand": { - "x": 619, - "y": 0, - "w": 230, - "ch": 3, - "s": ["400", "500", "600", "700"] - }, - "Edu VIC WA NT Hand Pre": { - "x": 863, - "y": 0, - "w": 282, - "ch": 3, - "s": ["400", "500", "600", "700"] - }, - "El Messiri": { - "x": 0, - "y": 40, - "w": 109, - "ch": 3, - "s": ["400", "500", "600", "700"] - }, - "Electrolize": { "x": 123, "y": 40, "w": 123, "ch": 3, "s": ["400"] }, - "Elms Sans": { - "x": 260, - "y": 40, - "w": 123, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Elsie": { "x": 397, "y": 40, "w": 61, "ch": 3, "s": ["400", "900"] }, - "Elsie Swash Caps": { - "x": 472, - "y": 40, - "w": 190, - "ch": 3, - "s": ["400", "900"] - }, - "Emblema One": { "x": 676, "y": 40, "w": 198, "ch": 3, "s": ["400"] }, - "Emilys Candy": { "x": 888, "y": 40, "w": 148, "ch": 3, "s": ["400"] }, - "Encode Sans": { - "x": 1050, - "y": 40, - "w": 141, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Encode Sans Condensed": { - "x": 0, - "y": 80, - "w": 227, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Encode Sans Expanded": { - "x": 241, - "y": 80, - "w": 287, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Encode Sans SC": { - "x": 542, - "y": 80, - "w": 186, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Encode Sans Semi Condensed": { - "x": 742, - "y": 80, - "w": 299, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Encode Sans Semi Expanded": { - "x": 0, - "y": 120, - "w": 330, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Engagement": { "x": 344, "y": 120, "w": 80, "ch": 3, "s": ["400"] }, - "Englebert": { "x": 438, "y": 120, "w": 97, "ch": 3, "s": ["400"] }, - "Enriqueta": { - "x": 549, - "y": 120, - "w": 115, - "ch": 3, - "s": ["400", "500", "600", "700"] - }, - "Ephesis": { "x": 678, "y": 120, "w": 72, "ch": 3, "s": ["400"] }, - "Epilogue": { - "x": 764, - "y": 120, - "w": 109, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Epunda Sans": { - "x": 887, - "y": 120, - "w": 133, - "ch": 3, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Epunda Slab": { - "x": 1034, - "y": 120, - "w": 132, - "ch": 3, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Erica One": { "x": 0, "y": 160, "w": 131, "ch": 3, "s": ["400"] }, - "Esteban": { "x": 145, "y": 160, "w": 93, "ch": 3, "s": ["400"] }, - "Estonia": { "x": 252, "y": 160, "w": 47, "ch": 3, "s": ["400"] }, - "Euphoria Script": { "x": 313, "y": 160, "w": 127, "ch": 3, "s": ["400"] }, - "Ewert": { "x": 454, "y": 160, "w": 97, "ch": 3, "s": ["400"] }, - "Exile": { "x": 565, "y": 160, "w": 81, "ch": 3, "s": ["400"] }, - "Exo": { - "x": 660, - "y": 160, - "w": 48, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Exo 2": { - "x": 722, - "y": 160, - "w": 67, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Expletus Sans": { - "x": 803, - "y": 160, - "w": 159, - "ch": 3, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Explora": { "x": 976, "y": 160, "w": 87, "ch": 3, "s": ["400"] }, - "Faculty Glyphic": { "x": 0, "y": 200, "w": 182, "ch": 3, "s": ["400"] }, - "Fahkwang": { - "x": 196, - "y": 200, - "w": 133, - "ch": 3, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Familjen Grotesk": { - "x": 343, - "y": 200, - "w": 178, - "ch": 3, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Fanwood Text": { - "x": 535, - "y": 200, - "w": 140, - "ch": 3, - "s": ["400", "400i"] - }, - "Farro": { - "x": 689, - "y": 200, - "w": 72, - "ch": 3, - "s": ["300", "400", "500", "700"] - }, - "Farsan": { "x": 775, "y": 200, "w": 61, "ch": 3, "s": ["400"] }, - "Fascinate": { "x": 850, "y": 200, "w": 135, "ch": 3, "s": ["400"] }, - "Fascinate Inline": { "x": 0, "y": 240, "w": 220, "ch": 3, "s": ["400"] }, - "Faster One": { "x": 234, "y": 240, "w": 165, "ch": 3, "s": ["400"] }, - "Fasthand": { "x": 413, "y": 240, "w": 84, "ch": 3, "s": ["400"] }, - "Fauna One": { "x": 511, "y": 240, "w": 141, "ch": 3, "s": ["400"] }, - "Faustina": { - "x": 666, - "y": 240, - "w": 97, - "ch": 3, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Federant": { "x": 777, "y": 240, "w": 105, "ch": 3, "s": ["400"] }, - "Federo": { "x": 896, "y": 240, "w": 78, "ch": 3, "s": ["400"] }, - "Felipa": { "x": 988, "y": 240, "w": 60, "ch": 3, "s": ["400"] }, - "Fenix": { "x": 1062, "y": 240, "w": 63, "ch": 3, "s": ["400"] }, - "Festive": { "x": 0, "y": 280, "w": 67, "ch": 3, "s": ["400"] }, - "Figtree": { - "x": 81, - "y": 280, - "w": 85, - "ch": 3, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Finger Paint": { "x": 180, "y": 280, "w": 158, "ch": 3, "s": ["400"] }, - "Finlandica": { - "x": 352, - "y": 280, - "w": 114, - "ch": 3, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Fira Code": { - "x": 480, - "y": 280, - "w": 138, - "ch": 3, - "s": ["300", "400", "500", "600", "700"] - }, - "Fira Mono": { - "x": 632, - "y": 280, - "w": 138, - "ch": 3, - "s": ["400", "500", "700"] - }, - "Fira Sans": { - "x": 784, - "y": 280, - "w": 105, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Fira Sans Condensed": { - "x": 903, - "y": 280, - "w": 211, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Fira Sans Extra Condensed": { - "x": 0, - "y": 320, - "w": 244, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Fjalla One": { "x": 258, "y": 320, "w": 98, "ch": 3, "s": ["400"] }, - "Fjord One": { "x": 370, "y": 320, "w": 115, "ch": 3, "s": ["400"] }, - "Flamenco": { "x": 499, "y": 320, "w": 99, "ch": 3, "s": ["300", "400"] }, - "Flavors": { "x": 612, "y": 320, "w": 82, "ch": 3, "s": ["400"] }, - "Fleur De Leah": { "x": 708, "y": 320, "w": 133, "ch": 3, "s": ["400"] }, - "Flow Block": { "x": 855, "y": 320, "w": 126, "ch": 3, "s": ["400"] }, - "Flow Circular": { "x": 995, "y": 320, "w": 145, "ch": 3, "s": ["400"] }, - "Flow Rounded": { "x": 0, "y": 360, "w": 160, "ch": 3, "s": ["400"] }, - "Foldit": { - "x": 174, - "y": 360, - "w": 54, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Fondamento": { - "x": 242, - "y": 360, - "w": 133, - "ch": 3, - "s": ["400", "400i"] - }, - "Fontdiner Swanky": { "x": 389, "y": 360, "w": 239, "ch": 3, "s": ["400"] }, - "Forum": { "x": 642, "y": 360, "w": 70, "ch": 3, "s": ["400"] }, - "Fragment Mono": { - "x": 726, - "y": 360, - "w": 201, - "ch": 3, - "s": ["400", "400i"] - }, - "Francois One": { "x": 941, "y": 360, "w": 136, "ch": 3, "s": ["400"] }, - "Frank Ruhl Libre": { - "x": 0, - "y": 400, - "w": 184, - "ch": 3, - "s": ["300", "400", "500", "600", "700", "800", "900"] - }, - "Fraunces": { - "x": 198, - "y": 400, - "w": 112, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Freckle Face": { "x": 324, "y": 400, "w": 141, "ch": 3, "s": ["400"] }, - "Fredericka the Great": { - "x": 479, - "y": 400, - "w": 243, - "ch": 3, - "s": ["400"] - }, - "Fredoka": { - "x": 736, - "y": 400, - "w": 96, - "ch": 3, - "s": ["300", "400", "500", "600", "700"] - }, - "Fredoka One": { "x": 846, "y": 400, "w": 155, "ch": 3, "s": ["400"] }, - "Freehand": { "x": 1015, "y": 400, "w": 85, "ch": 3, "s": ["400"] }, - "Freeman": { "x": 0, "y": 440, "w": 91, "ch": 3, "s": ["400"] }, - "Fresca": { "x": 105, "y": 440, "w": 67, "ch": 3, "s": ["400"] }, - "Frijole": { "x": 186, "y": 440, "w": 132, "ch": 3, "s": ["400"] }, - "Fruktur": { "x": 332, "y": 440, "w": 94, "ch": 3, "s": ["400", "400i"] }, - "Fugaz One": { "x": 440, "y": 440, "w": 127, "ch": 3, "s": ["400"] }, - "Fuggles": { "x": 581, "y": 440, "w": 58, "ch": 3, "s": ["400"] }, - "Funnel Display": { - "x": 653, - "y": 440, - "w": 174, - "ch": 3, - "s": ["300", "400", "500", "600", "700", "800"] - }, - "Funnel Sans": { - "x": 841, - "y": 440, - "w": 140, - "ch": 3, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Fustat": { - "x": 995, - "y": 440, - "w": 75, - "ch": 3, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Fuzzy Bubbles": { - "x": 0, - "y": 480, - "w": 179, - "ch": 3, - "s": ["400", "700"] - }, - "Ga Maamli": { "x": 193, "y": 480, "w": 111, "ch": 3, "s": ["400"] }, - "Gabarito": { - "x": 318, - "y": 480, - "w": 99, - "ch": 3, - "s": ["400", "500", "600", "700", "800", "900"] - }, - "Gabriela": { "x": 431, "y": 480, "w": 106, "ch": 3, "s": ["400"] }, - "Gaegu": { - "x": 551, - "y": 480, - "w": 70, - "ch": 3, - "s": ["300", "400", "700"] - }, - "Gafata": { "x": 635, "y": 480, "w": 72, "ch": 3, "s": ["400"] }, - "Gajraj One": { "x": 721, "y": 480, "w": 148, "ch": 3, "s": ["400"] }, - "Galada": { "x": 883, "y": 480, "w": 78, "ch": 3, "s": ["400"] }, - "Galdeano": { "x": 975, "y": 480, "w": 98, "ch": 3, "s": ["400"] }, - "Galindo": { "x": 1087, "y": 480, "w": 101, "ch": 3, "s": ["400"] }, - "Gamja Flower": { "x": 0, "y": 520, "w": 130, "ch": 3, "s": ["400"] }, - "Gantari": { - "x": 144, - "y": 520, - "w": 89, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Gasoek One": { "x": 247, "y": 520, "w": 163, "ch": 3, "s": ["400"] }, - "Gayathri": { - "x": 424, - "y": 520, - "w": 99, - "ch": 3, - "s": ["100", "400", "700"] - }, - "Geist": { - "x": 537, - "y": 520, - "w": 66, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Geist Mono": { - "x": 617, - "y": 520, - "w": 152, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Gelasio": { - "x": 783, - "y": 520, - "w": 87, - "ch": 3, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Gemunu Libre": { - "x": 884, - "y": 520, - "w": 126, - "ch": 3, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Genos": { - "x": 1024, - "y": 520, - "w": 67, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Gentium Book Basic": { - "x": 0, - "y": 560, - "w": 209, - "ch": 3, - "s": ["400", "400i", "700", "700i"] - }, - "Gentium Book Plus": { - "x": 223, - "y": 560, - "w": 198, - "ch": 3, - "s": ["400", "400i", "700", "700i"] - }, - "Gentium Plus": { - "x": 435, - "y": 560, - "w": 140, - "ch": 3, - "s": ["400", "400i", "700", "700i"] - }, - "Geo": { "x": 589, "y": 560, "w": 42, "ch": 3, "s": ["400", "400i"] }, - "Geologica": { - "x": 645, - "y": 560, - "w": 122, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Geom": { - "x": 781, - "y": 560, - "w": 76, - "ch": 3, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Georama": { - "x": 871, - "y": 560, - "w": 102, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Geostar": { "x": 987, "y": 560, "w": 133, "ch": 3, "s": ["400"] }, - "Geostar Fill": { "x": 0, "y": 600, "w": 192, "ch": 3, "s": ["400"] }, - "Germania One": { "x": 206, "y": 600, "w": 137, "ch": 3, "s": ["400"] }, - "GFS Didot": { "x": 357, "y": 600, "w": 121, "ch": 3, "s": ["400"] }, - "GFS Neohellenic": { - "x": 492, - "y": 600, - "w": 155, - "ch": 3, - "s": ["400", "400i", "700", "700i"] - }, - "Gideon Roman": { "x": 661, "y": 600, "w": 172, "ch": 3, "s": ["400"] }, - "Gidole": { "x": 847, "y": 600, "w": 73, "ch": 3, "s": ["400"] }, - "Gidugu": { "x": 934, "y": 600, "w": 58, "ch": 3, "s": ["400"] }, - "Gilda Display": { "x": 1006, "y": 600, "w": 156, "ch": 3, "s": ["400"] }, - "Girassol": { "x": 0, "y": 640, "w": 91, "ch": 3, "s": ["400"] }, - "Give You Glory": { "x": 105, "y": 640, "w": 170, "ch": 3, "s": ["400"] }, - "Glass Antiqua": { "x": 289, "y": 640, "w": 130, "ch": 3, "s": ["400"] }, - "Glegoo": { "x": 433, "y": 640, "w": 90, "ch": 3, "s": ["400", "700"] }, - "Gloock": { "x": 537, "y": 640, "w": 89, "ch": 3, "s": ["400"] }, - "Gloria Hallelujah": { - "x": 640, - "y": 640, - "w": 190, - "ch": 3, - "s": ["400"] - }, - "Glory": { - "x": 844, - "y": 640, - "w": 56, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Gluten": { - "x": 914, - "y": 640, - "w": 90, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Goblin One": { "x": 0, "y": 680, "w": 202, "ch": 3, "s": ["400"] }, - "Gochi Hand": { "x": 216, "y": 680, "w": 121, "ch": 3, "s": ["400"] }, - "Goldman": { "x": 351, "y": 680, "w": 118, "ch": 3, "s": ["400", "700"] }, - "Golos Text": { - "x": 483, - "y": 680, - "w": 130, - "ch": 3, - "s": ["400", "500", "600", "700", "800", "900"] - }, - "Google Sans": { - "x": 627, - "y": 680, - "w": 146, - "ch": 3, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Google Sans Code": { - "x": 787, - "y": 680, - "w": 239, - "ch": 3, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Google Sans Flex": { - "x": 0, - "y": 720, - "w": 193, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Gorditas": { "x": 207, "y": 720, "w": 111, "ch": 3, "s": ["400", "700"] }, - "Gothic A1": { - "x": 332, - "y": 720, - "w": 114, - "ch": 3, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Gotu": { "x": 460, "y": 720, "w": 67, "ch": 3, "s": ["400"] }, - "Goudy Bookletter 1911": { - "x": 541, - "y": 720, - "w": 218, - "ch": 3, - "s": ["400"] - }, - "Gowun Batang": { - "x": 773, - "y": 720, - "w": 162, - "ch": 3, - "s": ["400", "700"] - }, - "Gowun Dodum": { "x": 949, "y": 720, "w": 161, "ch": 3, "s": ["400"] }, - "Graduate": { "x": 0, "y": 760, "w": 134, "ch": 3, "s": ["400"] }, - "Grand Hotel": { "x": 148, "y": 760, "w": 101, "ch": 3, "s": ["400"] }, - "Grandiflora One": { "x": 263, "y": 760, "w": 174, "ch": 3, "s": ["400"] }, - "Grandstander": { - "x": 451, - "y": 760, - "w": 173, - "ch": 3, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Grape Nuts": { "x": 638, "y": 760, "w": 111, "ch": 3, "s": ["400"] }, - "Gravitas One": { "x": 763, "y": 760, "w": 230, "ch": 3, "s": ["400"] }, - "Great Vibes": { "x": 1007, "y": 760, "w": 104, "ch": 3, "s": ["400"] }, - "Grechen Fuemen": { "x": 0, "y": 0, "w": 179, "ch": 4, "s": ["400"] }, - "Grenze": { - "x": 193, - "y": 0, - "w": 69, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Grenze Gotisch": { - "x": 276, - "y": 0, - "w": 135, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Grey Qo": { "x": 425, "y": 0, "w": 64, "ch": 4, "s": ["400"] }, - "Griffy": { "x": 503, "y": 0, "w": 71, "ch": 4, "s": ["400"] }, - "Gruppo": { "x": 588, "y": 0, "w": 95, "ch": 4, "s": ["400"] }, - "Gudea": { - "x": 697, - "y": 0, - "w": 74, - "ch": 4, - "s": ["400", "400i", "700", "700i"] - }, - "Gugi": { "x": 785, "y": 0, "w": 60, "ch": 4, "s": ["400"] }, - "Gulzar": { "x": 859, "y": 0, "w": 77, "ch": 4, "s": ["400"] }, - "Gupter": { - "x": 950, - "y": 0, - "w": 72, - "ch": 4, - "s": ["400", "500", "700"] - }, - "Gurajada": { "x": 1036, "y": 0, "w": 71, "ch": 4, "s": ["400"] }, - "Gwendolyn": { "x": 0, "y": 40, "w": 89, "ch": 4, "s": ["400", "700"] }, - "Habibi": { "x": 103, "y": 40, "w": 81, "ch": 4, "s": ["400"] }, - "Hachi Maru Pop": { "x": 198, "y": 40, "w": 246, "ch": 4, "s": ["400"] }, - "Hahmlet": { - "x": 458, - "y": 40, - "w": 112, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Halant": { - "x": 584, - "y": 40, - "w": 75, - "ch": 4, - "s": ["300", "400", "500", "600", "700"] - }, - "Hammersmith One": { "x": 673, "y": 40, "w": 217, "ch": 4, "s": ["400"] }, - "Hanalei": { "x": 904, "y": 40, "w": 91, "ch": 4, "s": ["400"] }, - "Hanalei Fill": { "x": 1009, "y": 40, "w": 136, "ch": 4, "s": ["400"] }, - "Handjet": { - "x": 0, - "y": 80, - "w": 68, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Handlee": { "x": 82, "y": 80, "w": 88, "ch": 4, "s": ["400"] }, - "Hanken Grotesk": { - "x": 184, - "y": 80, - "w": 181, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Hanuman": { - "x": 379, - "y": 80, - "w": 123, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Happy Monkey": { "x": 516, "y": 80, "w": 189, "ch": 4, "s": ["400"] }, - "Harmattan": { - "x": 719, - "y": 80, - "w": 99, - "ch": 4, - "s": ["400", "500", "600", "700"] - }, - "Headland One": { "x": 832, "y": 80, "w": 185, "ch": 4, "s": ["400"] }, - "Hedvig Letters Sans": { - "x": 0, - "y": 120, - "w": 224, - "ch": 4, - "s": ["400"] - }, - "Hedvig Letters Serif": { - "x": 238, - "y": 120, - "w": 226, - "ch": 4, - "s": ["400"] - }, - "Heebo": { - "x": 478, - "y": 120, - "w": 78, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Henny Penny": { "x": 570, "y": 120, "w": 148, "ch": 4, "s": ["400"] }, - "Hepta Slab": { - "x": 732, - "y": 120, - "w": 150, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Herr Von Muellerhoff": { - "x": 896, - "y": 120, - "w": 145, - "ch": 4, - "s": ["400"] - }, - "Hi Melody": { "x": 1055, "y": 120, "w": 84, "ch": 4, "s": ["400"] }, - "Hina Mincho": { "x": 0, "y": 160, "w": 128, "ch": 4, "s": ["400"] }, - "Hind": { - "x": 142, - "y": 160, - "w": 56, - "ch": 4, - "s": ["300", "400", "500", "600", "700"] - }, - "Hind Guntur": { - "x": 212, - "y": 160, - "w": 134, - "ch": 4, - "s": ["300", "400", "500", "600", "700"] - }, - "Hind Madurai": { - "x": 360, - "y": 160, - "w": 146, - "ch": 4, - "s": ["300", "400", "500", "600", "700"] - }, - "Hind Mysuru": { - "x": 520, - "y": 160, - "w": 138, - "ch": 4, - "s": ["300", "400", "500", "600", "700"] - }, - "Hind Siliguri": { - "x": 672, - "y": 160, - "w": 130, - "ch": 4, - "s": ["300", "400", "500", "600", "700"] - }, - "Hind Vadodara": { - "x": 816, - "y": 160, - "w": 161, - "ch": 4, - "s": ["300", "400", "500", "600", "700"] - }, - "Holtwood One SC": { "x": 0, "y": 200, "w": 292, "ch": 4, "s": ["400"] }, - "Homemade Apple": { "x": 306, "y": 200, "w": 239, "ch": 4, "s": ["400"] }, - "Homenaje": { "x": 559, "y": 200, "w": 85, "ch": 4, "s": ["400"] }, - "Honk": { "x": 658, "y": 200, "w": 59, "ch": 4, "s": ["400"] }, - "Host Grotesk": { - "x": 731, - "y": 200, - "w": 150, - "ch": 4, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Hubballi": { "x": 895, "y": 200, "w": 87, "ch": 4, "s": ["400"] }, - "Hubot Sans": { - "x": 996, - "y": 200, - "w": 137, - "ch": 4, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Huninn": { "x": 0, "y": 240, "w": 89, "ch": 4, "s": ["400"] }, - "Hurricane": { "x": 103, "y": 240, "w": 76, "ch": 4, "s": ["400"] }, - "Iansui": { "x": 193, "y": 240, "w": 78, "ch": 4, "s": ["400"] }, - "Ibarra Real Nova": { - "x": 285, - "y": 240, - "w": 182, - "ch": 4, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "IBM Plex Mono": { - "x": 481, - "y": 240, - "w": 196, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "IBM Plex Sans": { - "x": 691, - "y": 240, - "w": 163, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "IBM Plex Sans Arabic": { - "x": 868, - "y": 240, - "w": 237, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "IBM Plex Sans Condensed": { - "x": 0, - "y": 280, - "w": 263, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "IBM Plex Sans Devanagari": { - "x": 277, - "y": 280, - "w": 289, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "IBM Plex Sans Hebrew": { - "x": 580, - "y": 280, - "w": 253, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "IBM Plex Sans JP": { - "x": 847, - "y": 280, - "w": 204, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "IBM Plex Sans KR": { - "x": 0, - "y": 320, - "w": 199, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "IBM Plex Sans Thai": { - "x": 213, - "y": 320, - "w": 215, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "IBM Plex Sans Thai Looped": { - "x": 442, - "y": 320, - "w": 300, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "IBM Plex Serif": { - "x": 756, - "y": 320, - "w": 167, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Iceberg": { "x": 937, "y": 320, "w": 84, "ch": 4, "s": ["400"] }, - "Iceland": { "x": 1035, "y": 320, "w": 75, "ch": 4, "s": ["400"] }, - "IM Fell Double Pica": { - "x": 0, - "y": 360, - "w": 204, - "ch": 4, - "s": ["400", "400i"] - }, - "IM Fell Double Pica SC": { - "x": 218, - "y": 360, - "w": 261, - "ch": 4, - "s": ["400"] - }, - "IM Fell DW Pica": { - "x": 493, - "y": 360, - "w": 175, - "ch": 4, - "s": ["400", "400i"] - }, - "IM Fell DW Pica SC": { - "x": 682, - "y": 360, - "w": 220, - "ch": 4, - "s": ["400"] - }, - "IM Fell English": { - "x": 916, - "y": 360, - "w": 168, - "ch": 4, - "s": ["400", "400i"] - }, - "IM Fell English SC": { "x": 0, "y": 400, "w": 215, "ch": 4, "s": ["400"] }, - "IM Fell French Canon": { - "x": 229, - "y": 400, - "w": 233, - "ch": 4, - "s": ["400", "400i"] - }, - "IM Fell French Canon SC": { - "x": 476, - "y": 400, - "w": 279, - "ch": 4, - "s": ["400"] - }, - "IM Fell Great Primer": { - "x": 769, - "y": 400, - "w": 220, - "ch": 4, - "s": ["400", "400i"] - }, - "IM Fell Great Primer SC": { - "x": 0, - "y": 440, - "w": 289, - "ch": 4, - "s": ["400"] - }, - "Imbue": { - "x": 303, - "y": 440, - "w": 52, - "ch": 4, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Imperial Script": { "x": 369, "y": 440, "w": 119, "ch": 4, "s": ["400"] }, - "Imprima": { "x": 502, "y": 440, "w": 98, "ch": 4, "s": ["400"] }, - "Inclusive Sans": { - "x": 614, - "y": 440, - "w": 172, - "ch": 4, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Inconsolata": { - "x": 800, - "y": 440, - "w": 140, - "ch": 4, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Inder": { "x": 954, "y": 440, "w": 64, "ch": 4, "s": ["400"] }, - "Indie Flower": { "x": 1032, "y": 440, "w": 125, "ch": 4, "s": ["400"] }, - "Ingrid Darling": { "x": 0, "y": 480, "w": 114, "ch": 4, "s": ["400"] }, - "Inika": { "x": 128, "y": 480, "w": 67, "ch": 4, "s": ["400", "700"] }, - "Inknut Antiqua": { - "x": 209, - "y": 480, - "w": 211, - "ch": 4, - "s": ["300", "400", "500", "600", "700", "800", "900"] - }, - "Inria Sans": { - "x": 434, - "y": 480, - "w": 110, - "ch": 4, - "s": ["300", "300i", "400", "400i", "700", "700i"] - }, - "Inria Serif": { - "x": 558, - "y": 480, - "w": 120, - "ch": 4, - "s": ["300", "300i", "400", "400i", "700", "700i"] - }, - "Inspiration": { "x": 692, "y": 480, "w": 78, "ch": 4, "s": ["400"] }, - "Instrument Sans": { - "x": 784, - "y": 480, - "w": 189, - "ch": 4, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Instrument Serif": { - "x": 987, - "y": 480, - "w": 143, - "ch": 4, - "s": ["400", "400i"] - }, - "Intel One Mono": { - "x": 0, - "y": 520, - "w": 215, - "ch": 4, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Inter": { - "x": 229, - "y": 520, - "w": 60, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Inter Tight": { - "x": 303, - "y": 520, - "w": 113, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Irish Grover": { "x": 430, "y": 520, "w": 139, "ch": 4, "s": ["400"] }, - "Island Moments": { "x": 583, "y": 520, "w": 125, "ch": 4, "s": ["400"] }, - "Istok Web": { - "x": 722, - "y": 520, - "w": 116, - "ch": 4, - "s": ["400", "400i", "700", "700i"] - }, - "Italiana": { "x": 852, "y": 520, "w": 83, "ch": 4, "s": ["400"] }, - "Italianno": { "x": 949, "y": 520, "w": 68, "ch": 4, "s": ["400"] }, - "Itim": { "x": 1031, "y": 520, "w": 52, "ch": 4, "s": ["400"] }, - "Jacquard 12": { "x": 0, "y": 560, "w": 110, "ch": 4, "s": ["400"] }, - "Jacquard 12 Charted": { - "x": 124, - "y": 560, - "w": 184, - "ch": 4, - "s": ["400"] - }, - "Jacquard 24": { "x": 322, "y": 560, "w": 108, "ch": 4, "s": ["400"] }, - "Jacquard 24 Charted": { - "x": 444, - "y": 560, - "w": 179, - "ch": 4, - "s": ["400"] - }, - "Jacquarda Bastarda 9": { - "x": 637, - "y": 560, - "w": 261, - "ch": 4, - "s": ["400"] - }, - "Jacquarda Bastarda 9 Charted": { - "x": 0, - "y": 600, - "w": 357, - "ch": 4, - "s": ["400"] - }, - "Jacques Francois": { "x": 371, "y": 600, "w": 197, "ch": 4, "s": ["400"] }, - "Jacques Francois Shadow": { - "x": 582, - "y": 600, - "w": 315, - "ch": 4, - "s": ["400"] - }, - "Jaini": { "x": 911, "y": 600, "w": 50, "ch": 4, "s": ["400"] }, - "Jaini Purva": { "x": 975, "y": 600, "w": 102, "ch": 4, "s": ["400"] }, - "Jaldi": { "x": 1091, "y": 600, "w": 52, "ch": 4, "s": ["400", "700"] }, - "Jaro": { "x": 0, "y": 640, "w": 50, "ch": 4, "s": ["400"] }, - "Jersey 10": { "x": 64, "y": 640, "w": 84, "ch": 4, "s": ["400"] }, - "Jersey 10 Charted": { - "x": 162, - "y": 640, - "w": 158, - "ch": 4, - "s": ["400"] - }, - "Jersey 15": { "x": 334, "y": 640, "w": 91, "ch": 4, "s": ["400"] }, - "Jersey 15 Charted": { - "x": 439, - "y": 640, - "w": 168, - "ch": 4, - "s": ["400"] - }, - "Jersey 20": { "x": 621, "y": 640, "w": 98, "ch": 4, "s": ["400"] }, - "Jersey 20 Charted": { - "x": 733, - "y": 640, - "w": 176, - "ch": 4, - "s": ["400"] - }, - "Jersey 25": { "x": 923, "y": 640, "w": 102, "ch": 4, "s": ["400"] }, - "Jersey 25 Charted": { "x": 0, "y": 680, "w": 180, "ch": 4, "s": ["400"] }, - "JetBrains Mono": { - "x": 194, - "y": 680, - "w": 210, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Jim Nightshade": { "x": 418, "y": 680, "w": 119, "ch": 4, "s": ["400"] }, - "Joan": { "x": 551, "y": 680, "w": 54, "ch": 4, "s": ["400"] }, - "Jockey One": { "x": 619, "y": 680, "w": 108, "ch": 4, "s": ["400"] }, - "Jolly Lodger": { "x": 741, "y": 680, "w": 90, "ch": 4, "s": ["400"] }, - "Jomhuria": { "x": 845, "y": 680, "w": 64, "ch": 4, "s": ["400"] }, - "Jomolhari": { "x": 923, "y": 680, "w": 116, "ch": 4, "s": ["400"] }, - "Josefin Sans": { - "x": 1053, - "y": 680, - "w": 138, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Josefin Slab": { - "x": 0, - "y": 720, - "w": 123, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Jost": { - "x": 137, - "y": 720, - "w": 43, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Joti One": { "x": 194, "y": 720, "w": 100, "ch": 4, "s": ["400"] }, - "Jua": { "x": 308, "y": 720, "w": 48, "ch": 4, "s": ["400"] }, - "Judson": { - "x": 370, - "y": 720, - "w": 80, - "ch": 4, - "s": ["400", "400i", "700", "700i"] - }, - "Julee": { "x": 464, "y": 720, "w": 57, "ch": 4, "s": ["400"] }, - "Julius Sans One": { "x": 535, "y": 720, "w": 210, "ch": 4, "s": ["400"] }, - "Junge": { "x": 759, "y": 720, "w": 73, "ch": 4, "s": ["400"] }, - "Jura": { - "x": 846, - "y": 720, - "w": 59, - "ch": 4, - "s": ["300", "400", "500", "600", "700"] - }, - "Just Another Hand": { - "x": 919, - "y": 720, - "w": 112, - "ch": 4, - "s": ["400"] - }, - "Just Me Again Down Here": { - "x": 0, - "y": 760, - "w": 200, - "ch": 4, - "s": ["400"] - }, - "K2D": { - "x": 214, - "y": 760, - "w": 56, - "ch": 4, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Kablammo": { "x": 284, "y": 760, "w": 128, "ch": 4, "s": ["400"] }, - "Kadwa": { "x": 426, "y": 760, "w": 87, "ch": 4, "s": ["400", "700"] }, - "Kaisei Decol": { - "x": 527, - "y": 760, - "w": 154, - "ch": 4, - "s": ["400", "500", "700"] - }, - "Kaisei HarunoUmi": { - "x": 695, - "y": 760, - "w": 223, - "ch": 4, - "s": ["400", "500", "700"] - }, - "Kaisei Opti": { - "x": 932, - "y": 760, - "w": 137, - "ch": 4, - "s": ["400", "500", "700"] - }, - "Kaisei Tokumin": { - "x": 0, - "y": 0, - "w": 191, - "ch": 5, - "s": ["400", "500", "700", "800"] - }, - "Kalam": { "x": 205, "y": 0, "w": 70, "ch": 5, "s": ["300", "400", "700"] }, - "Kalnia": { - "x": 289, - "y": 0, - "w": 87, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "Kalnia Glaze": { - "x": 390, - "y": 0, - "w": 162, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "Kameron": { - "x": 566, - "y": 0, - "w": 107, - "ch": 5, - "s": ["400", "500", "600", "700"] - }, - "Kanchenjunga": { - "x": 687, - "y": 0, - "w": 165, - "ch": 5, - "s": ["400", "500", "600", "700"] - }, - "Kanit": { - "x": 866, - "y": 0, - "w": 65, - "ch": 5, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Kantumruy Pro": { - "x": 945, - "y": 0, - "w": 175, - "ch": 5, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Kapakana": { "x": 0, "y": 40, "w": 83, "ch": 5, "s": ["300", "400"] }, - "Karantina": { - "x": 97, - "y": 40, - "w": 66, - "ch": 5, - "s": ["300", "400", "700"] - }, - "Karla": { - "x": 177, - "y": 40, - "w": 65, - "ch": 5, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Karma": { - "x": 256, - "y": 40, - "w": 79, - "ch": 5, - "s": ["300", "400", "500", "600", "700"] - }, - "Katibeh": { "x": 349, "y": 40, "w": 67, "ch": 5, "s": ["400"] }, - "Kaushan Script": { "x": 430, "y": 40, "w": 159, "ch": 5, "s": ["400"] }, - "Kavivanar": { "x": 603, "y": 40, "w": 107, "ch": 5, "s": ["400"] }, - "Kavoon": { "x": 724, "y": 40, "w": 95, "ch": 5, "s": ["400"] }, - "Kay Pho Du": { - "x": 833, - "y": 40, - "w": 132, - "ch": 5, - "s": ["400", "500", "600", "700"] - }, - "Kdam Thmor Pro": { "x": 979, "y": 40, "w": 189, "ch": 5, "s": ["400"] }, - "Keania One": { "x": 0, "y": 80, "w": 126, "ch": 5, "s": ["400"] }, - "Kedebideri": { - "x": 140, - "y": 80, - "w": 120, - "ch": 5, - "s": ["400", "500", "600", "700", "800", "900"] - }, - "Kelly Slab": { "x": 274, "y": 80, "w": 115, "ch": 5, "s": ["400"] }, - "Kenia": { "x": 403, "y": 80, "w": 57, "ch": 5, "s": ["400"] }, - "Khand": { - "x": 474, - "y": 80, - "w": 61, - "ch": 5, - "s": ["300", "400", "500", "600", "700"] - }, - "Khula": { - "x": 549, - "y": 80, - "w": 68, - "ch": 5, - "s": ["300", "400", "600", "700", "800"] - }, - "Kings": { "x": 631, "y": 80, "w": 63, "ch": 5, "s": ["400"] }, - "Kirang Haerang": { "x": 708, "y": 80, "w": 145, "ch": 5, "s": ["400"] }, - "Kite One": { "x": 867, "y": 80, "w": 97, "ch": 5, "s": ["400"] }, - "Kiwi Maru": { - "x": 978, - "y": 80, - "w": 135, - "ch": 5, - "s": ["300", "400", "500"] - }, - "Klee One": { "x": 0, "y": 120, "w": 109, "ch": 5, "s": ["400", "600"] }, - "Knewave": { "x": 123, "y": 120, "w": 103, "ch": 5, "s": ["400"] }, - "Kodchasan": { - "x": 240, - "y": 120, - "w": 139, - "ch": 5, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Kode Mono": { - "x": 393, - "y": 120, - "w": 138, - "ch": 5, - "s": ["400", "500", "600", "700"] - }, - "Koh Santepheap": { - "x": 545, - "y": 120, - "w": 195, - "ch": 5, - "s": ["100", "300", "400", "700", "900"] - }, - "KoHo": { - "x": 754, - "y": 120, - "w": 66, - "ch": 5, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Kolker Brush": { "x": 834, "y": 120, "w": 84, "ch": 5, "s": ["400"] }, - "Konkhmer Sleokchher": { - "x": 0, - "y": 160, - "w": 269, - "ch": 5, - "s": ["400"] - }, - "Kosugi": { "x": 283, "y": 160, "w": 80, "ch": 5, "s": ["400"] }, - "Kosugi Maru": { "x": 377, "y": 160, "w": 140, "ch": 5, "s": ["400"] }, - "Kotta One": { "x": 531, "y": 160, "w": 110, "ch": 5, "s": ["400"] }, - "Koulen": { "x": 655, "y": 160, "w": 72, "ch": 5, "s": ["400"] }, - "Kranky": { "x": 741, "y": 160, "w": 87, "ch": 5, "s": ["400"] }, - "Kreon": { - "x": 842, - "y": 160, - "w": 68, - "ch": 5, - "s": ["300", "400", "500", "600", "700"] - }, - "Kristi": { "x": 924, "y": 160, "w": 46, "ch": 5, "s": ["400"] }, - "Krona One": { "x": 984, "y": 160, "w": 173, "ch": 5, "s": ["400"] }, - "Krub": { - "x": 0, - "y": 200, - "w": 62, - "ch": 5, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Kufam": { - "x": 76, - "y": 200, - "w": 84, - "ch": 5, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Kulim Park": { - "x": 174, - "y": 200, - "w": 116, - "ch": 5, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "600", - "600i", - "700", - "700i" - ] - }, - "Kumar One": { "x": 304, "y": 200, "w": 167, "ch": 5, "s": ["400"] }, - "Kumar One Outline": { - "x": 485, - "y": 200, - "w": 277, - "ch": 5, - "s": ["400"] - }, - "Kumbh Sans": { - "x": 776, - "y": 200, - "w": 147, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Kurale": { "x": 937, "y": 200, "w": 77, "ch": 5, "s": ["400"] }, - "La Belle Aurore": { "x": 1028, "y": 200, "w": 170, "ch": 5, "s": ["400"] }, - "Labrada": { - "x": 0, - "y": 240, - "w": 91, - "ch": 5, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Lacquer": { "x": 105, "y": 240, "w": 102, "ch": 5, "s": ["400"] }, - "Laila": { - "x": 221, - "y": 240, - "w": 60, - "ch": 5, - "s": ["300", "400", "500", "600", "700"] - }, - "Lakki Reddy": { "x": 295, "y": 240, "w": 138, "ch": 5, "s": ["400"] }, - "Lalezar": { "x": 447, "y": 240, "w": 82, "ch": 5, "s": ["400"] }, - "Lancelot": { "x": 543, "y": 240, "w": 80, "ch": 5, "s": ["400"] }, - "Langar": { "x": 637, "y": 240, "w": 83, "ch": 5, "s": ["400"] }, - "Lateef": { - "x": 734, - "y": 240, - "w": 57, - "ch": 5, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Lato": { - "x": 805, - "y": 240, - "w": 55, - "ch": 5, - "s": [ - "100", - "100i", - "300", - "300i", - "400", - "400i", - "700", - "700i", - "900", - "900i" - ] - }, - "Lavishly Yours": { "x": 874, "y": 240, "w": 135, "ch": 5, "s": ["400"] }, - "League Gothic": { "x": 1023, "y": 240, "w": 97, "ch": 5, "s": ["400"] }, - "League Script": { "x": 0, "y": 280, "w": 156, "ch": 5, "s": ["400"] }, - "League Spartan": { - "x": 170, - "y": 280, - "w": 166, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Leckerli One": { "x": 350, "y": 280, "w": 144, "ch": 5, "s": ["400"] }, - "Ledger": { "x": 508, "y": 280, "w": 91, "ch": 5, "s": ["400"] }, - "Lekton": { - "x": 613, - "y": 280, - "w": 80, - "ch": 5, - "s": ["400", "400i", "700", "700i"] - }, - "Lemon": { "x": 707, "y": 280, "w": 100, "ch": 5, "s": ["400"] }, - "Lemonada": { - "x": 821, - "y": 280, - "w": 153, - "ch": 5, - "s": ["300", "400", "500", "600", "700"] - }, - "Lexend": { - "x": 988, - "y": 280, - "w": 93, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Lexend Deca": { - "x": 0, - "y": 320, - "w": 159, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Lexend Exa": { - "x": 173, - "y": 320, - "w": 176, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Lexend Giga": { - "x": 363, - "y": 320, - "w": 205, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Lexend Mega": { - "x": 582, - "y": 320, - "w": 223, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Lexend Peta": { - "x": 819, - "y": 320, - "w": 218, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Lexend Tera": { - "x": 0, - "y": 360, - "w": 221, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Lexend Zetta": { - "x": 235, - "y": 360, - "w": 260, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Libertinus Keyboard": { - "x": 509, - "y": 360, - "w": 490, - "ch": 5, - "s": ["400"] - }, - "Libertinus Math": { "x": 1013, "y": 360, "w": 166, "ch": 5, "s": ["400"] }, - "Libertinus Mono": { "x": 0, "y": 400, "w": 239, "ch": 5, "s": ["400"] }, - "Libertinus Sans": { - "x": 253, - "y": 400, - "w": 161, - "ch": 5, - "s": ["400", "400i", "700", "700i"] - }, - "Libertinus Serif": { - "x": 428, - "y": 400, - "w": 160, - "ch": 5, - "s": ["400", "400i", "600", "600i", "700", "700i"] - }, - "Libertinus Serif Display": { - "x": 602, - "y": 400, - "w": 234, - "ch": 5, - "s": ["400"] - }, - "Libre Barcode 128": { - "x": 850, - "y": 400, - "w": 143, - "ch": 5, - "s": ["400"] - }, - "Libre Barcode 128 Text": { - "x": 1007, - "y": 400, - "w": 183, - "ch": 5, - "s": ["400"] - }, - "Libre Barcode 39": { "x": 0, "y": 440, "w": 193, "ch": 5, "s": ["400"] }, - "Libre Barcode 39 Extended": { - "x": 207, - "y": 440, - "w": 492, - "ch": 5, - "s": ["400"] - }, - "Libre Barcode 39 Extended Text": { - "x": 0, - "y": 480, - "w": 584, - "ch": 5, - "s": ["400"] - }, - "Libre Barcode 39 Text": { - "x": 598, - "y": 480, - "w": 250, - "ch": 5, - "s": ["400"] - }, - "Libre Barcode EAN13 Text": { - "x": 862, - "y": 480, - "w": 87, - "ch": 5, - "s": ["400"] - }, - "Libre Baskerville": { - "x": 963, - "y": 480, - "w": 216, - "ch": 5, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Libre Bodoni": { - "x": 0, - "y": 520, - "w": 153, - "ch": 5, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Libre Caslon Display": { - "x": 167, - "y": 520, - "w": 197, - "ch": 5, - "s": ["400"] - }, - "Libre Caslon Text": { - "x": 378, - "y": 520, - "w": 210, - "ch": 5, - "s": ["400", "400i", "700", "700i"] - }, - "Libre Franklin": { - "x": 602, - "y": 520, - "w": 159, - "ch": 5, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Licorice": { "x": 775, "y": 520, "w": 60, "ch": 5, "s": ["400"] }, - "Life Savers": { - "x": 849, - "y": 520, - "w": 119, - "ch": 5, - "s": ["400", "700", "800"] - }, - "Lilex": { - "x": 982, - "y": 520, - "w": 80, - "ch": 5, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Lilita One": { "x": 1076, "y": 520, "w": 106, "ch": 5, "s": ["400"] }, - "Lily Script One": { "x": 0, "y": 560, "w": 156, "ch": 5, "s": ["400"] }, - "Limelight": { "x": 170, "y": 560, "w": 132, "ch": 5, "s": ["400"] }, - "Linden Hill": { - "x": 316, - "y": 560, - "w": 113, - "ch": 5, - "s": ["400", "400i"] - }, - "LINE Seed JP": { - "x": 443, - "y": 560, - "w": 169, - "ch": 5, - "s": ["100", "400", "700", "800"] - }, - "Linefont": { - "x": 626, - "y": 560, - "w": 28, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Lisu Bosa": { - "x": 668, - "y": 560, - "w": 106, - "ch": 5, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Liter": { "x": 788, "y": 560, "w": 56, "ch": 5, "s": ["400"] }, - "Literata": { - "x": 858, - "y": 560, - "w": 99, - "ch": 5, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Liu Jian Mao Cao": { "x": 971, "y": 560, "w": 164, "ch": 5, "s": ["400"] }, - "Livvic": { - "x": 0, - "y": 600, - "w": 66, - "ch": 5, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "900", - "900i" - ] - }, - "Lobster": { "x": 80, "y": 600, "w": 72, "ch": 5, "s": ["400"] }, - "Lobster Two": { - "x": 166, - "y": 600, - "w": 114, - "ch": 5, - "s": ["400", "400i", "700", "700i"] - }, - "Londrina Outline": { "x": 294, "y": 600, "w": 156, "ch": 5, "s": ["400"] }, - "Londrina Shadow": { "x": 464, "y": 600, "w": 165, "ch": 5, "s": ["400"] }, - "Londrina Sketch": { "x": 643, "y": 600, "w": 156, "ch": 5, "s": ["400"] }, - "Londrina Solid": { - "x": 813, - "y": 600, - "w": 136, - "ch": 5, - "s": ["100", "300", "400", "900"] - }, - "Long Cang": { "x": 963, "y": 600, "w": 98, "ch": 5, "s": ["400"] }, - "Lora": { - "x": 1075, - "y": 600, - "w": 59, - "ch": 5, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Love Light": { "x": 0, "y": 640, "w": 88, "ch": 5, "s": ["400"] }, - "Love Ya Like A Sister": { - "x": 102, - "y": 640, - "w": 235, - "ch": 5, - "s": ["400"] - }, - "Loved by the King": { - "x": 351, - "y": 640, - "w": 145, - "ch": 5, - "s": ["400"] - }, - "Lovers Quarrel": { "x": 510, "y": 640, "w": 92, "ch": 5, "s": ["400"] }, - "Luckiest Guy": { "x": 616, "y": 640, "w": 156, "ch": 5, "s": ["400"] }, - "Lugrasimo": { "x": 786, "y": 640, "w": 144, "ch": 5, "s": ["400"] }, - "Lumanosimo": { "x": 944, "y": 640, "w": 185, "ch": 5, "s": ["400"] }, - "Lunasima": { "x": 0, "y": 680, "w": 119, "ch": 5, "s": ["400", "700"] }, - "Lusitana": { "x": 133, "y": 680, "w": 96, "ch": 5, "s": ["400", "700"] }, - "Lustria": { "x": 243, "y": 680, "w": 86, "ch": 5, "s": ["400"] }, - "Luxurious Roman": { "x": 343, "y": 680, "w": 201, "ch": 5, "s": ["400"] }, - "Luxurious Script": { "x": 558, "y": 680, "w": 128, "ch": 5, "s": ["400"] }, - "LXGW Marker Gothic": { - "x": 700, - "y": 680, - "w": 233, - "ch": 5, - "s": ["400"] - }, - "LXGW WenKai Mono TC": { - "x": 947, - "y": 680, - "w": 236, - "ch": 5, - "s": ["300", "400", "700"] - }, - "LXGW WenKai TC": { - "x": 0, - "y": 720, - "w": 206, - "ch": 5, - "s": ["300", "400", "700"] - }, - "M PLUS 1": { - "x": 220, - "y": 720, - "w": 120, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "M PLUS 1 Code": { - "x": 354, - "y": 720, - "w": 164, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "M PLUS 1p": { - "x": 532, - "y": 720, - "w": 129, - "ch": 5, - "s": ["100", "300", "400", "500", "700", "800", "900"] - }, - "M PLUS 2": { - "x": 675, - "y": 720, - "w": 120, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "M PLUS Code Latin": { - "x": 809, - "y": 720, - "w": 212, - "ch": 5, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "M PLUS Rounded 1c": { - "x": 0, - "y": 760, - "w": 225, - "ch": 5, - "s": ["100", "300", "400", "500", "700", "800", "900"] - }, - "Ma Shan Zheng": { "x": 239, "y": 760, "w": 126, "ch": 5, "s": ["400"] }, - "Macondo": { "x": 379, "y": 760, "w": 100, "ch": 5, "s": ["400"] }, - "Macondo Swash Caps": { - "x": 493, - "y": 760, - "w": 228, - "ch": 5, - "s": ["400"] - }, - "Mada": { - "x": 735, - "y": 760, - "w": 63, - "ch": 5, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Madimi One": { "x": 812, "y": 760, "w": 133, "ch": 5, "s": ["400"] }, - "Magra": { "x": 959, "y": 760, "w": 70, "ch": 5, "s": ["400", "700"] }, - "Maiden Orange": { "x": 1043, "y": 760, "w": 127, "ch": 5, "s": ["400"] }, - "Maitree": { - "x": 0, - "y": 0, - "w": 96, - "ch": 6, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Major Mono Display": { "x": 110, "y": 0, "w": 328, "ch": 6, "s": ["400"] }, - "Mako": { "x": 452, "y": 0, "w": 67, "ch": 6, "s": ["400"] }, - "Mali": { - "x": 533, - "y": 0, - "w": 56, - "ch": 6, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Mallanna": { "x": 603, "y": 0, "w": 96, "ch": 6, "s": ["400"] }, - "Maname": { "x": 713, "y": 0, "w": 100, "ch": 6, "s": ["400"] }, - "Mandali": { "x": 827, "y": 0, "w": 94, "ch": 6, "s": ["400"] }, - "Manjari": { - "x": 935, - "y": 0, - "w": 87, - "ch": 6, - "s": ["100", "400", "700"] - }, - "Manrope": { - "x": 1036, - "y": 0, - "w": 107, - "ch": 6, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Mansalva": { "x": 0, "y": 40, "w": 111, "ch": 6, "s": ["400"] }, - "Manuale": { - "x": 125, - "y": 40, - "w": 101, - "ch": 6, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Manufacturing Consent": { - "x": 240, - "y": 40, - "w": 214, - "ch": 6, - "s": ["400"] - }, - "Marcellus": { "x": 468, "y": 40, "w": 114, "ch": 6, "s": ["400"] }, - "Marcellus SC": { "x": 596, "y": 40, "w": 157, "ch": 6, "s": ["400"] }, - "Marck Script": { "x": 767, "y": 40, "w": 137, "ch": 6, "s": ["400"] }, - "Margarine": { "x": 918, "y": 40, "w": 119, "ch": 6, "s": ["400"] }, - "Marhey": { - "x": 1051, - "y": 40, - "w": 107, - "ch": 6, - "s": ["300", "400", "500", "600", "700"] - }, - "Markazi Text": { - "x": 0, - "y": 80, - "w": 113, - "ch": 6, - "s": ["400", "500", "600", "700"] - }, - "Marko One": { "x": 127, "y": 80, "w": 152, "ch": 6, "s": ["400"] }, - "Marmelad": { "x": 293, "y": 80, "w": 118, "ch": 6, "s": ["400"] }, - "Martel": { - "x": 425, - "y": 80, - "w": 88, - "ch": 6, - "s": ["200", "300", "400", "600", "700", "800", "900"] - }, - "Martel Sans": { - "x": 527, - "y": 80, - "w": 143, - "ch": 6, - "s": ["200", "300", "400", "600", "700", "800", "900"] - }, - "Martian Mono": { - "x": 684, - "y": 80, - "w": 210, - "ch": 6, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Marvel": { - "x": 908, - "y": 80, - "w": 64, - "ch": 6, - "s": ["400", "400i", "700", "700i"] - }, - "Matangi": { - "x": 986, - "y": 80, - "w": 99, - "ch": 6, - "s": ["300", "400", "500", "600", "700", "800", "900"] - }, - "Mate": { "x": 1099, "y": 80, "w": 61, "ch": 6, "s": ["400", "400i"] }, - "Mate SC": { "x": 0, "y": 120, "w": 99, "ch": 6, "s": ["400"] }, - "Matemasie": { "x": 113, "y": 120, "w": 145, "ch": 6, "s": ["400"] }, - "Material Icons": { "x": 272, "y": 120, "w": 344, "ch": 6, "s": ["400"] }, - "Material Icons Outlined": { - "x": 630, - "y": 120, - "w": 560, - "ch": 6, - "s": ["400"] - }, - "Material Icons Round": { - "x": 0, - "y": 160, - "w": 488, - "ch": 6, - "s": ["400"] - }, - "Material Icons Sharp": { - "x": 502, - "y": 160, - "w": 488, - "ch": 6, - "s": ["400"] - }, - "Material Icons Two Tone": { - "x": 0, - "y": 200, - "w": 560, - "ch": 6, - "s": ["400"] - }, - "Material Symbols": { - "x": 574, - "y": 200, - "w": 392, - "ch": 6, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "Material Symbols Outlined": { - "x": 0, - "y": 240, - "w": 608, - "ch": 6, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "Material Symbols Rounded": { - "x": 0, - "y": 280, - "w": 584, - "ch": 6, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "Material Symbols Sharp": { - "x": 598, - "y": 280, - "w": 536, - "ch": 6, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "Maven Pro": { - "x": 0, - "y": 320, - "w": 123, - "ch": 6, - "s": ["400", "500", "600", "700", "800", "900"] - }, - "McLaren": { "x": 137, "y": 320, "w": 118, "ch": 6, "s": ["400"] }, - "Mea Culpa": { "x": 269, "y": 320, "w": 102, "ch": 6, "s": ["400"] }, - "Meddon": { "x": 385, "y": 320, "w": 129, "ch": 6, "s": ["400"] }, - "MedievalSharp": { "x": 528, "y": 320, "w": 170, "ch": 6, "s": ["400"] }, - "Medula One": { "x": 712, "y": 320, "w": 78, "ch": 6, "s": ["400"] }, - "Meera Inimai": { "x": 804, "y": 320, "w": 138, "ch": 6, "s": ["400"] }, - "Megrim": { "x": 956, "y": 320, "w": 87, "ch": 6, "s": ["400"] }, - "Meie Script": { "x": 1057, "y": 320, "w": 133, "ch": 6, "s": ["400"] }, - "Menbere": { - "x": 0, - "y": 360, - "w": 112, - "ch": 6, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "Meow Script": { "x": 126, "y": 360, "w": 119, "ch": 6, "s": ["400"] }, - "Merienda": { - "x": 259, - "y": 360, - "w": 121, - "ch": 6, - "s": ["300", "400", "500", "600", "700", "800", "900"] - }, - "Merienda One": { "x": 394, "y": 360, "w": 177, "ch": 6, "s": ["400"] }, - "Merriweather": { - "x": 585, - "y": 360, - "w": 168, - "ch": 6, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Merriweather Sans": { - "x": 767, - "y": 360, - "w": 223, - "ch": 6, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Metal": { "x": 1004, "y": 360, "w": 58, "ch": 6, "s": ["400"] }, - "Metal Mania": { "x": 1076, "y": 360, "w": 121, "ch": 6, "s": ["400"] }, - "Metamorphous": { "x": 0, "y": 400, "w": 210, "ch": 6, "s": ["400"] }, - "Metrophobic": { "x": 224, "y": 400, "w": 149, "ch": 6, "s": ["400"] }, - "Michroma": { "x": 387, "y": 400, "w": 161, "ch": 6, "s": ["400"] }, - "Micro 5": { "x": 562, "y": 400, "w": 65, "ch": 6, "s": ["400"] }, - "Micro 5 Charted": { "x": 641, "y": 400, "w": 126, "ch": 6, "s": ["400"] }, - "Milonga": { "x": 781, "y": 400, "w": 99, "ch": 6, "s": ["400"] }, - "Miltonian": { "x": 894, "y": 400, "w": 130, "ch": 6, "s": ["400"] }, - "Miltonian Tattoo": { "x": 0, "y": 440, "w": 214, "ch": 6, "s": ["400"] }, - "Mina": { "x": 228, "y": 440, "w": 58, "ch": 6, "s": ["400", "700"] }, - "Mingzat": { "x": 300, "y": 440, "w": 99, "ch": 6, "s": ["400"] }, - "Miniver": { "x": 413, "y": 440, "w": 89, "ch": 6, "s": ["400"] }, - "Miriam Libre": { - "x": 516, - "y": 440, - "w": 161, - "ch": 6, - "s": ["400", "500", "600", "700"] - }, - "Mirza": { - "x": 691, - "y": 440, - "w": 60, - "ch": 6, - "s": ["400", "500", "600", "700"] - }, - "Miss Fajardose": { "x": 765, "y": 440, "w": 108, "ch": 6, "s": ["400"] }, - "Mitr": { - "x": 887, - "y": 440, - "w": 55, - "ch": 6, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Mochiy Pop One": { "x": 956, "y": 440, "w": 223, "ch": 6, "s": ["400"] }, - "Mochiy Pop P One": { "x": 0, "y": 480, "w": 248, "ch": 6, "s": ["400"] }, - "Modak": { "x": 262, "y": 480, "w": 79, "ch": 6, "s": ["400"] }, - "Modern Antiqua": { "x": 355, "y": 480, "w": 196, "ch": 6, "s": ["400"] }, - "Moderustic": { - "x": 565, - "y": 480, - "w": 135, - "ch": 6, - "s": ["300", "400", "500", "600", "700", "800"] - }, - "Mogra": { "x": 714, "y": 480, "w": 80, "ch": 6, "s": ["400"] }, - "Mohave": { - "x": 808, - "y": 480, - "w": 77, - "ch": 6, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Moirai One": { "x": 899, "y": 480, "w": 146, "ch": 6, "s": ["400"] }, - "Molengo": { "x": 1059, "y": 480, "w": 96, "ch": 6, "s": ["400"] }, - "Momo Signature": { "x": 0, "y": 520, "w": 227, "ch": 6, "s": ["400"] }, - "Momo Trust Display": { - "x": 241, - "y": 520, - "w": 249, - "ch": 6, - "s": ["400"] - }, - "Momo Trust Sans": { - "x": 504, - "y": 520, - "w": 207, - "ch": 6, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Mona Sans": { - "x": 725, - "y": 520, - "w": 136, - "ch": 6, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Monda": { - "x": 875, - "y": 520, - "w": 85, - "ch": 6, - "s": ["400", "500", "600", "700"] - }, - "Monofett": { "x": 974, "y": 520, "w": 119, "ch": 6, "s": ["400"] }, - "Monomakh": { "x": 0, "y": 560, "w": 138, "ch": 6, "s": ["400"] }, - "Monomaniac One": { "x": 152, "y": 560, "w": 156, "ch": 6, "s": ["400"] }, - "Monoton": { "x": 322, "y": 560, "w": 143, "ch": 6, "s": ["400"] }, - "Monsieur La Doulaise": { - "x": 479, - "y": 560, - "w": 215, - "ch": 6, - "s": ["400"] - }, - "Montaga": { "x": 708, "y": 560, "w": 102, "ch": 6, "s": ["400"] }, - "Montagu Slab": { - "x": 824, - "y": 560, - "w": 173, - "ch": 6, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "MonteCarlo": { "x": 1011, "y": 560, "w": 107, "ch": 6, "s": ["400"] }, - "Montez": { "x": 1132, "y": 560, "w": 66, "ch": 6, "s": ["400"] }, - "Montserrat": { - "x": 0, - "y": 600, - "w": 141, - "ch": 6, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Montserrat Alternates": { - "x": 155, - "y": 600, - "w": 282, - "ch": 6, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Montserrat Subrayada": { - "x": 451, - "y": 600, - "w": 347, - "ch": 6, - "s": ["400", "700"] - }, - "Montserrat Underline": { - "x": 812, - "y": 600, - "w": 266, - "ch": 6, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Moo Lah Lah": { "x": 0, "y": 640, "w": 137, "ch": 6, "s": ["400"] }, - "Mooli": { "x": 151, "y": 640, "w": 77, "ch": 6, "s": ["400"] }, - "Moon Dance": { "x": 242, "y": 640, "w": 106, "ch": 6, "s": ["400"] }, - "Moul": { "x": 362, "y": 640, "w": 82, "ch": 6, "s": ["400"] }, - "Moulpali": { "x": 458, "y": 640, "w": 90, "ch": 6, "s": ["400"] }, - "Mountains of Christmas": { - "x": 562, - "y": 640, - "w": 219, - "ch": 6, - "s": ["400", "700"] - }, - "Mouse Memoirs": { "x": 795, "y": 640, "w": 116, "ch": 6, "s": ["400"] }, - "Mozilla Headline": { - "x": 925, - "y": 640, - "w": 192, - "ch": 6, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Mozilla Text": { - "x": 0, - "y": 680, - "w": 148, - "ch": 6, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Mr Bedfort": { "x": 162, "y": 680, "w": 124, "ch": 6, "s": ["400"] }, - "Mr Dafoe": { "x": 300, "y": 680, "w": 92, "ch": 6, "s": ["400"] }, - "Mr De Haviland": { "x": 406, "y": 680, "w": 115, "ch": 6, "s": ["400"] }, - "Mrs Saint Delafield": { - "x": 535, - "y": 680, - "w": 159, - "ch": 6, - "s": ["400"] - }, - "Mrs Sheppards": { "x": 708, "y": 680, "w": 139, "ch": 6, "s": ["400"] }, - "Ms Madi": { "x": 861, "y": 680, "w": 89, "ch": 6, "s": ["400"] }, - "Mukta": { - "x": 964, - "y": 680, - "w": 73, - "ch": 6, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Mukta Mahee": { - "x": 1051, - "y": 680, - "w": 146, - "ch": 6, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Mukta Malar": { - "x": 0, - "y": 720, - "w": 137, - "ch": 6, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Mukta Vaani": { - "x": 151, - "y": 720, - "w": 133, - "ch": 6, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Mulish": { - "x": 298, - "y": 720, - "w": 80, - "ch": 6, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Murecho": { - "x": 392, - "y": 720, - "w": 102, - "ch": 6, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "MuseoModerno": { - "x": 508, - "y": 720, - "w": 199, - "ch": 6, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "My Soul": { "x": 721, "y": 720, "w": 107, "ch": 6, "s": ["400"] }, - "Mynerve": { "x": 842, "y": 720, "w": 104, "ch": 6, "s": ["400"] }, - "Mystery Quest": { "x": 960, "y": 720, "w": 152, "ch": 6, "s": ["400"] }, - "Nabla": { "x": 1126, "y": 720, "w": 72, "ch": 6, "s": ["400"] }, - "Namdhinggo": { - "x": 0, - "y": 760, - "w": 130, - "ch": 6, - "s": ["400", "500", "600", "700", "800"] - }, - "Nanum Brush Script": { - "x": 144, - "y": 760, - "w": 168, - "ch": 6, - "s": ["400"] - }, - "Nanum Gothic": { - "x": 326, - "y": 760, - "w": 171, - "ch": 6, - "s": ["400", "700", "800"] - }, - "Nanum Gothic Coding": { - "x": 511, - "y": 760, - "w": 236, - "ch": 6, - "s": ["400", "700"] - }, - "Nanum Myeongjo": { - "x": 761, - "y": 760, - "w": 201, - "ch": 6, - "s": ["400", "700", "800"] - }, - "Nanum Pen Script": { "x": 976, "y": 760, "w": 157, "ch": 6, "s": ["400"] }, - "Narnoor": { - "x": 0, - "y": 0, - "w": 90, - "ch": 7, - "s": ["400", "500", "600", "700", "800"] - }, - "Nata Sans": { - "x": 104, - "y": 0, - "w": 123, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "National Park": { - "x": 241, - "y": 0, - "w": 149, - "ch": 7, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Neonderthaw": { "x": 404, "y": 0, "w": 139, "ch": 7, "s": ["400"] }, - "Nerko One": { "x": 557, "y": 0, "w": 107, "ch": 7, "s": ["400"] }, - "Neucha": { "x": 678, "y": 0, "w": 72, "ch": 7, "s": ["400"] }, - "Neuton": { - "x": 764, - "y": 0, - "w": 76, - "ch": 7, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "700", - "700i", - "800", - "800i" - ] - }, - "New Amsterdam": { "x": 854, "y": 0, "w": 139, "ch": 7, "s": ["400"] }, - "New Rocker": { "x": 1007, "y": 0, "w": 133, "ch": 7, "s": ["400"] }, - "New Tegomin": { "x": 0, "y": 40, "w": 159, "ch": 7, "s": ["400"] }, - "News Cycle": { "x": 173, "y": 40, "w": 116, "ch": 7, "s": ["400", "700"] }, - "Newsreader": { - "x": 303, - "y": 40, - "w": 129, - "ch": 7, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Niconne": { "x": 446, "y": 40, "w": 79, "ch": 7, "s": ["400"] }, - "Niramit": { - "x": 539, - "y": 40, - "w": 84, - "ch": 7, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Nixie One": { "x": 637, "y": 40, "w": 123, "ch": 7, "s": ["400"] }, - "Nobile": { - "x": 774, - "y": 40, - "w": 82, - "ch": 7, - "s": ["400", "400i", "500", "500i", "700", "700i"] - }, - "Nokora": { - "x": 870, - "y": 40, - "w": 91, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Norican": { "x": 975, "y": 40, "w": 78, "ch": 7, "s": ["400"] }, - "Nosifer": { "x": 0, "y": 80, "w": 154, "ch": 7, "s": ["400"] }, - "Notable": { "x": 168, "y": 80, "w": 144, "ch": 7, "s": ["400"] }, - "Nothing You Could Do": { - "x": 326, - "y": 80, - "w": 248, - "ch": 7, - "s": ["400"] - }, - "Noticia Text": { - "x": 588, - "y": 80, - "w": 142, - "ch": 7, - "s": ["400", "400i", "700", "700i"] - }, - "Noto Kufi Arabic": { - "x": 744, - "y": 80, - "w": 196, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Music": { "x": 954, "y": 80, "w": 136, "ch": 7, "s": ["400"] }, - "Noto Naskh Arabic": { - "x": 0, - "y": 120, - "w": 221, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Nastaliq Urdu": { - "x": 235, - "y": 120, - "w": 222, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Rashi Hebrew": { - "x": 471, - "y": 120, - "w": 230, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans": { - "x": 715, - "y": 120, - "w": 124, - "ch": 7, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Noto Sans Adlam": { - "x": 853, - "y": 120, - "w": 202, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Adlam Unjoined": { - "x": 0, - "y": 160, - "w": 310, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Anatolian Hieroglyphs": { - "x": 324, - "y": 160, - "w": 380, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Arabic": { - "x": 718, - "y": 160, - "w": 201, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Armenian": { - "x": 933, - "y": 160, - "w": 240, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Avestan": { "x": 0, "y": 200, "w": 219, "ch": 7, "s": ["400"] }, - "Noto Sans Balinese": { - "x": 233, - "y": 200, - "w": 225, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Bamum": { - "x": 472, - "y": 200, - "w": 219, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Bassa Vah": { - "x": 705, - "y": 200, - "w": 250, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Batak": { "x": 969, "y": 200, "w": 194, "ch": 7, "s": ["400"] }, - "Noto Sans Bengali": { - "x": 0, - "y": 240, - "w": 214, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Bhaiksuki": { - "x": 228, - "y": 240, - "w": 236, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Brahmi": { "x": 478, "y": 240, "w": 212, "ch": 7, "s": ["400"] }, - "Noto Sans Buginese": { - "x": 704, - "y": 240, - "w": 235, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Buhid": { "x": 953, "y": 240, "w": 196, "ch": 7, "s": ["400"] }, - "Noto Sans Canadian Aboriginal": { - "x": 0, - "y": 280, - "w": 361, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Carian": { "x": 375, "y": 280, "w": 203, "ch": 7, "s": ["400"] }, - "Noto Sans Caucasian Albanian": { - "x": 592, - "y": 280, - "w": 353, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Chakma": { "x": 959, "y": 280, "w": 222, "ch": 7, "s": ["400"] }, - "Noto Sans Cham": { - "x": 0, - "y": 320, - "w": 196, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Cherokee": { - "x": 210, - "y": 320, - "w": 238, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Chorasmian": { - "x": 462, - "y": 320, - "w": 268, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Coptic": { "x": 744, "y": 320, "w": 201, "ch": 7, "s": ["400"] }, - "Noto Sans Cuneiform": { - "x": 0, - "y": 360, - "w": 250, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Cypriot": { - "x": 264, - "y": 360, - "w": 211, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Cypro Minoan": { - "x": 489, - "y": 360, - "w": 288, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Deseret": { - "x": 791, - "y": 360, - "w": 218, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Devanagari": { - "x": 0, - "y": 400, - "w": 259, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Display": { - "x": 273, - "y": 400, - "w": 200, - "ch": 7, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Noto Sans Duployan": { - "x": 487, - "y": 400, - "w": 238, - "ch": 7, - "s": ["400", "700"] - }, - "Noto Sans Egyptian Hieroglyphs": { - "x": 739, - "y": 400, - "w": 369, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Elbasan": { "x": 0, "y": 440, "w": 217, "ch": 7, "s": ["400"] }, - "Noto Sans Elymaic": { - "x": 231, - "y": 440, - "w": 222, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Ethiopic": { - "x": 467, - "y": 440, - "w": 220, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Georgian": { - "x": 701, - "y": 440, - "w": 234, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Glagolitic": { - "x": 949, - "y": 440, - "w": 235, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Gothic": { "x": 0, "y": 480, "w": 203, "ch": 7, "s": ["400"] }, - "Noto Sans Grantha": { - "x": 217, - "y": 480, - "w": 222, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Gujarati": { - "x": 453, - "y": 480, - "w": 220, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Gunjala Gondi": { - "x": 687, - "y": 480, - "w": 291, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Gurmukhi": { - "x": 0, - "y": 520, - "w": 243, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Hanifi Rohingya": { - "x": 257, - "y": 520, - "w": 309, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Hanunoo": { - "x": 580, - "y": 520, - "w": 235, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Hatran": { "x": 829, "y": 520, "w": 208, "ch": 7, "s": ["400"] }, - "Noto Sans Hebrew": { - "x": 0, - "y": 560, - "w": 217, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans HK": { - "x": 231, - "y": 560, - "w": 160, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Imperial Aramaic": { - "x": 405, - "y": 560, - "w": 323, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Indic Siyaq Numbers": { - "x": 742, - "y": 560, - "w": 351, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Inscriptional Pahlavi": { - "x": 0, - "y": 600, - "w": 358, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Inscriptional Parthian": { - "x": 372, - "y": 600, - "w": 373, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Javanese": { - "x": 759, - "y": 600, - "w": 229, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans JP": { - "x": 1002, - "y": 600, - "w": 155, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Kaithi": { "x": 0, "y": 640, "w": 195, "ch": 7, "s": ["400"] }, - "Noto Sans Kannada": { - "x": 209, - "y": 640, - "w": 231, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Kawi": { - "x": 454, - "y": 640, - "w": 180, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Kayah Li": { - "x": 648, - "y": 640, - "w": 224, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Kharoshthi": { - "x": 886, - "y": 640, - "w": 253, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Khmer": { - "x": 0, - "y": 680, - "w": 205, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Khojki": { "x": 219, "y": 680, "w": 199, "ch": 7, "s": ["400"] }, - "Noto Sans Khudawadi": { - "x": 432, - "y": 680, - "w": 256, - "ch": 7, - "s": ["400"] - }, - "Noto Sans KR": { - "x": 702, - "y": 680, - "w": 158, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Lao": { - "x": 874, - "y": 680, - "w": 170, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Lao Looped": { - "x": 0, - "y": 720, - "w": 261, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Lepcha": { "x": 275, "y": 720, "w": 211, "ch": 7, "s": ["400"] }, - "Noto Sans Limbu": { "x": 500, "y": 720, "w": 201, "ch": 7, "s": ["400"] }, - "Noto Sans Linear A": { - "x": 715, - "y": 720, - "w": 222, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Linear B": { - "x": 951, - "y": 720, - "w": 222, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Lisu": { - "x": 0, - "y": 760, - "w": 175, - "ch": 7, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Lydian": { "x": 189, "y": 760, "w": 204, "ch": 7, "s": ["400"] }, - "Noto Sans Mahajani": { - "x": 407, - "y": 760, - "w": 234, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Malayalam": { - "x": 655, - "y": 760, - "w": 255, - "ch": 7, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Mandaic": { - "x": 924, - "y": 760, - "w": 226, - "ch": 7, - "s": ["400"] - }, - "Noto Sans Manichaean": { "x": 0, "y": 0, "w": 268, "ch": 8, "s": ["400"] }, - "Noto Sans Marchen": { "x": 282, "y": 0, "w": 227, "ch": 8, "s": ["400"] }, - "Noto Sans Masaram Gondi": { - "x": 523, - "y": 0, - "w": 318, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Math": { "x": 855, "y": 0, "w": 189, "ch": 8, "s": ["400"] }, - "Noto Sans Mayan Numerals": { - "x": 0, - "y": 40, - "w": 322, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Medefaidrin": { - "x": 336, - "y": 40, - "w": 267, - "ch": 8, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Meetei Mayek": { - "x": 617, - "y": 40, - "w": 289, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Mende Kikakui": { - "x": 0, - "y": 80, - "w": 296, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Meroitic": { - "x": 310, - "y": 80, - "w": 222, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Miao": { "x": 546, "y": 80, "w": 186, "ch": 8, "s": ["400"] }, - "Noto Sans Modi": { "x": 746, "y": 80, "w": 187, "ch": 8, "s": ["400"] }, - "Noto Sans Mongolian": { - "x": 947, - "y": 80, - "w": 251, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Mono": { - "x": 0, - "y": 120, - "w": 210, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Mro": { "x": 224, "y": 120, "w": 176, "ch": 8, "s": ["400"] }, - "Noto Sans Multani": { - "x": 414, - "y": 120, - "w": 216, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Myanmar": { - "x": 644, - "y": 120, - "w": 238, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Nabataean": { - "x": 896, - "y": 120, - "w": 254, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Nag Mundari": { - "x": 0, - "y": 160, - "w": 274, - "ch": 8, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Nandinagari": { - "x": 288, - "y": 160, - "w": 272, - "ch": 8, - "s": ["400"] - }, - "Noto Sans New Tai Lue": { - "x": 574, - "y": 160, - "w": 265, - "ch": 8, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Newa": { "x": 853, "y": 160, "w": 194, "ch": 8, "s": ["400"] }, - "Noto Sans NKo": { "x": 0, "y": 200, "w": 178, "ch": 8, "s": ["400"] }, - "Noto Sans NKo Unjoined": { - "x": 192, - "y": 200, - "w": 286, - "ch": 8, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Nushu": { "x": 492, "y": 200, "w": 204, "ch": 8, "s": ["400"] }, - "Noto Sans Ogham": { "x": 710, "y": 200, "w": 214, "ch": 8, "s": ["400"] }, - "Noto Sans Ol Chiki": { - "x": 938, - "y": 200, - "w": 216, - "ch": 8, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Old Hungarian": { - "x": 0, - "y": 240, - "w": 296, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Old Italic": { - "x": 310, - "y": 240, - "w": 230, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Old North Arabian": { - "x": 554, - "y": 240, - "w": 336, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Old Permic": { - "x": 904, - "y": 240, - "w": 253, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Old Persian": { - "x": 0, - "y": 280, - "w": 260, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Old Sogdian": { - "x": 274, - "y": 280, - "w": 260, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Old South Arabian": { - "x": 548, - "y": 280, - "w": 336, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Old Turkic": { - "x": 898, - "y": 280, - "w": 243, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Oriya": { - "x": 0, - "y": 320, - "w": 190, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Osage": { "x": 204, "y": 320, "w": 202, "ch": 8, "s": ["400"] }, - "Noto Sans Osmanya": { - "x": 420, - "y": 320, - "w": 238, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Pahawh Hmong": { - "x": 672, - "y": 320, - "w": 310, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Palmyrene": { - "x": 0, - "y": 360, - "w": 251, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Pau Cin Hau": { - "x": 265, - "y": 360, - "w": 269, - "ch": 8, - "s": ["400"] - }, - "Noto Sans PhagsPa": { - "x": 548, - "y": 360, - "w": 227, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Phoenician": { - "x": 789, - "y": 360, - "w": 254, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Psalter Pahlavi": { - "x": 0, - "y": 400, - "w": 295, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Rejang": { "x": 309, "y": 400, "w": 208, "ch": 8, "s": ["400"] }, - "Noto Sans Runic": { "x": 531, "y": 400, "w": 192, "ch": 8, "s": ["400"] }, - "Noto Sans Samaritan": { - "x": 737, - "y": 400, - "w": 245, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Saurashtra": { - "x": 0, - "y": 440, - "w": 254, - "ch": 8, - "s": ["400"] - }, - "Noto Sans SC": { - "x": 268, - "y": 440, - "w": 157, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Sharada": { - "x": 439, - "y": 440, - "w": 222, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Shavian": { - "x": 675, - "y": 440, - "w": 218, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Siddham": { - "x": 907, - "y": 440, - "w": 227, - "ch": 8, - "s": ["400"] - }, - "Noto Sans SignWriting": { - "x": 0, - "y": 480, - "w": 297, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Sinhala": { - "x": 311, - "y": 480, - "w": 224, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Sogdian": { - "x": 549, - "y": 480, - "w": 216, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Sora Sompeng": { - "x": 779, - "y": 480, - "w": 295, - "ch": 8, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Soyombo": { "x": 0, "y": 520, "w": 236, "ch": 8, "s": ["400"] }, - "Noto Sans Sundanese": { - "x": 250, - "y": 520, - "w": 254, - "ch": 8, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Sunuwar": { - "x": 518, - "y": 520, - "w": 237, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Syloti Nagri": { - "x": 769, - "y": 520, - "w": 260, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Symbols": { - "x": 0, - "y": 560, - "w": 225, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Symbols 2": { - "x": 239, - "y": 560, - "w": 257, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Syriac": { - "x": 510, - "y": 560, - "w": 196, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Syriac Eastern": { - "x": 720, - "y": 560, - "w": 288, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Syriac Western": { - "x": 0, - "y": 600, - "w": 296, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Tagalog": { - "x": 310, - "y": 600, - "w": 218, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Tagbanwa": { - "x": 542, - "y": 600, - "w": 247, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Tai Le": { "x": 803, "y": 600, "w": 193, "ch": 8, "s": ["400"] }, - "Noto Sans Tai Tham": { - "x": 0, - "y": 640, - "w": 243, - "ch": 8, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Tai Viet": { - "x": 257, - "y": 640, - "w": 210, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Takri": { "x": 481, "y": 640, "w": 184, "ch": 8, "s": ["400"] }, - "Noto Sans Tamil": { - "x": 679, - "y": 640, - "w": 190, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Tamil Supplement": { - "x": 0, - "y": 680, - "w": 334, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Tangsa": { - "x": 348, - "y": 680, - "w": 209, - "ch": 8, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans TC": { - "x": 571, - "y": 680, - "w": 156, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Telugu": { - "x": 741, - "y": 680, - "w": 207, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Thaana": { - "x": 962, - "y": 680, - "w": 213, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Thai": { - "x": 0, - "y": 720, - "w": 178, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Thai Looped": { - "x": 192, - "y": 720, - "w": 269, - "ch": 8, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Sans Tifinagh": { - "x": 475, - "y": 720, - "w": 222, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Tirhuta": { - "x": 711, - "y": 720, - "w": 211, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Ugaritic": { - "x": 936, - "y": 720, - "w": 218, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Vai": { "x": 0, "y": 760, "w": 164, "ch": 8, "s": ["400"] }, - "Noto Sans Vithkuqi": { - "x": 178, - "y": 760, - "w": 223, - "ch": 8, - "s": ["400", "500", "600", "700"] - }, - "Noto Sans Wancho": { "x": 415, "y": 760, "w": 228, "ch": 8, "s": ["400"] }, - "Noto Sans Warang Citi": { - "x": 657, - "y": 760, - "w": 264, - "ch": 8, - "s": ["400"] - }, - "Noto Sans Yi": { "x": 935, "y": 760, "w": 149, "ch": 8, "s": ["400"] }, - "Noto Sans Zanabazar Square": { - "x": 0, - "y": 0, - "w": 334, - "ch": 9, - "s": ["400"] - }, - "Noto Serif": { - "x": 348, - "y": 0, - "w": 123, - "ch": 9, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Noto Serif Ahom": { "x": 485, "y": 0, "w": 196, "ch": 9, "s": ["400"] }, - "Noto Serif Armenian": { - "x": 695, - "y": 0, - "w": 245, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Balinese": { - "x": 954, - "y": 0, - "w": 225, - "ch": 9, - "s": ["400"] - }, - "Noto Serif Bengali": { - "x": 0, - "y": 40, - "w": 215, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Devanagari": { - "x": 229, - "y": 40, - "w": 261, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Display": { - "x": 504, - "y": 40, - "w": 212, - "ch": 9, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Noto Serif Dives Akuru": { - "x": 730, - "y": 40, - "w": 274, - "ch": 9, - "s": ["400"] - }, - "Noto Serif Dogra": { "x": 0, "y": 80, "w": 197, "ch": 9, "s": ["400"] }, - "Noto Serif Ethiopic": { - "x": 211, - "y": 80, - "w": 224, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Georgian": { - "x": 449, - "y": 80, - "w": 234, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Grantha": { - "x": 697, - "y": 80, - "w": 223, - "ch": 9, - "s": ["400"] - }, - "Noto Serif Gujarati": { - "x": 934, - "y": 80, - "w": 225, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Gurmukhi": { - "x": 0, - "y": 120, - "w": 248, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Hebrew": { - "x": 262, - "y": 120, - "w": 221, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Hentaigana": { - "x": 497, - "y": 120, - "w": 269, - "ch": 9, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif HK": { - "x": 780, - "y": 120, - "w": 170, - "ch": 9, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif JP": { - "x": 964, - "y": 120, - "w": 157, - "ch": 9, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Kannada": { - "x": 0, - "y": 160, - "w": 231, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Khitan Small Script": { - "x": 245, - "y": 160, - "w": 383, - "ch": 9, - "s": ["400"] - }, - "Noto Serif Khmer": { - "x": 642, - "y": 160, - "w": 208, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Khojki": { - "x": 864, - "y": 160, - "w": 204, - "ch": 9, - "s": ["400", "500", "600", "700"] - }, - "Noto Serif KR": { - "x": 0, - "y": 200, - "w": 167, - "ch": 9, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Lao": { - "x": 181, - "y": 200, - "w": 171, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Makasar": { - "x": 366, - "y": 200, - "w": 226, - "ch": 9, - "s": ["400"] - }, - "Noto Serif Malayalam": { - "x": 606, - "y": 200, - "w": 259, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif NP Hmong": { - "x": 879, - "y": 200, - "w": 252, - "ch": 9, - "s": ["400", "500", "600", "700"] - }, - "Noto Serif Old Uyghur": { - "x": 0, - "y": 240, - "w": 257, - "ch": 9, - "s": ["400"] - }, - "Noto Serif Oriya": { - "x": 271, - "y": 240, - "w": 200, - "ch": 9, - "s": ["400", "500", "600", "700"] - }, - "Noto Serif Ottoman Siyaq": { - "x": 485, - "y": 240, - "w": 298, - "ch": 9, - "s": ["400"] - }, - "Noto Serif SC": { - "x": 797, - "y": 240, - "w": 162, - "ch": 9, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Sinhala": { - "x": 973, - "y": 240, - "w": 227, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Tamil": { - "x": 0, - "y": 280, - "w": 194, - "ch": 9, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Noto Serif Tangut": { - "x": 208, - "y": 280, - "w": 220, - "ch": 9, - "s": ["400"] - }, - "Noto Serif TC": { - "x": 442, - "y": 280, - "w": 164, - "ch": 9, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Telugu": { - "x": 620, - "y": 280, - "w": 207, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Thai": { - "x": 841, - "y": 280, - "w": 180, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Tibetan": { - "x": 0, - "y": 320, - "w": 217, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Noto Serif Todhri": { - "x": 231, - "y": 320, - "w": 201, - "ch": 9, - "s": ["400"] - }, - "Noto Serif Toto": { - "x": 446, - "y": 320, - "w": 179, - "ch": 9, - "s": ["400", "500", "600", "700"] - }, - "Noto Serif Vithkuqi": { - "x": 639, - "y": 320, - "w": 228, - "ch": 9, - "s": ["400", "500", "600", "700"] - }, - "Noto Serif Yezidi": { - "x": 881, - "y": 320, - "w": 198, - "ch": 9, - "s": ["400", "500", "600", "700"] - }, - "Noto Traditional Nushu": { - "x": 0, - "y": 360, - "w": 289, - "ch": 9, - "s": ["300", "400", "500", "600", "700"] - }, - "Noto Znamenny Musical Notation": { - "x": 303, - "y": 360, - "w": 388, - "ch": 9, - "s": ["400"] - }, - "Nova Cut": { "x": 705, "y": 360, "w": 108, "ch": 9, "s": ["400"] }, - "Nova Flat": { "x": 827, "y": 360, "w": 112, "ch": 9, "s": ["400"] }, - "Nova Mono": { "x": 953, "y": 360, "w": 130, "ch": 9, "s": ["400"] }, - "Nova Oval": { "x": 0, "y": 400, "w": 123, "ch": 9, "s": ["400"] }, - "Nova Round": { "x": 137, "y": 400, "w": 140, "ch": 9, "s": ["400"] }, - "Nova Script": { "x": 291, "y": 400, "w": 140, "ch": 9, "s": ["400"] }, - "Nova Slim": { "x": 445, "y": 400, "w": 121, "ch": 9, "s": ["400"] }, - "Nova Square": { "x": 580, "y": 400, "w": 148, "ch": 9, "s": ["400"] }, - "NTR": { "x": 742, "y": 400, "w": 49, "ch": 9, "s": ["400"] }, - "Numans": { "x": 805, "y": 400, "w": 113, "ch": 9, "s": ["400"] }, - "Nunito": { - "x": 932, - "y": 400, - "w": 81, - "ch": 9, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Nunito Sans": { - "x": 1027, - "y": 400, - "w": 139, - "ch": 9, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Nuosu SIL": { "x": 0, "y": 440, "w": 117, "ch": 9, "s": ["400"] }, - "Odibee Sans": { "x": 131, "y": 440, "w": 95, "ch": 9, "s": ["400"] }, - "Odor Mean Chey": { "x": 240, "y": 440, "w": 184, "ch": 9, "s": ["400"] }, - "Offside": { "x": 438, "y": 440, "w": 96, "ch": 9, "s": ["400"] }, - "Oi": { "x": 548, "y": 440, "w": 47, "ch": 9, "s": ["400"] }, - "Ojuju": { - "x": 609, - "y": 440, - "w": 62, - "ch": 9, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Old Standard TT": { - "x": 685, - "y": 440, - "w": 187, - "ch": 9, - "s": ["400", "400i", "700", "700i"] - }, - "Oldenburg": { "x": 886, "y": 440, "w": 136, "ch": 9, "s": ["400"] }, - "Ole": { "x": 1036, "y": 440, "w": 33, "ch": 9, "s": ["400"] }, - "Oleo Script": { - "x": 1083, - "y": 440, - "w": 109, - "ch": 9, - "s": ["400", "700"] - }, - "Oleo Script Swash Caps": { - "x": 0, - "y": 480, - "w": 227, - "ch": 9, - "s": ["400", "700"] - }, - "Onest": { - "x": 241, - "y": 480, - "w": 76, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Oooh Baby": { "x": 331, "y": 480, "w": 109, "ch": 9, "s": ["400"] }, - "Open Sans": { - "x": 454, - "y": 480, - "w": 129, - "ch": 9, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Oranienbaum": { "x": 597, "y": 480, "w": 137, "ch": 9, "s": ["400"] }, - "Orbit": { "x": 748, "y": 480, "w": 80, "ch": 9, "s": ["400"] }, - "Orbitron": { - "x": 842, - "y": 480, - "w": 117, - "ch": 9, - "s": ["400", "500", "600", "700", "800", "900"] - }, - "Oregano": { "x": 973, "y": 480, "w": 78, "ch": 9, "s": ["400", "400i"] }, - "Orelega One": { "x": 0, "y": 520, "w": 138, "ch": 9, "s": ["400"] }, - "Orienta": { "x": 152, "y": 520, "w": 94, "ch": 9, "s": ["400"] }, - "Original Surfer": { "x": 260, "y": 520, "w": 175, "ch": 9, "s": ["400"] }, - "Oswald": { - "x": 449, - "y": 520, - "w": 70, - "ch": 9, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Outfit": { - "x": 533, - "y": 520, - "w": 71, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Over the Rainbow": { "x": 618, "y": 520, "w": 189, "ch": 9, "s": ["400"] }, - "Overlock": { - "x": 821, - "y": 520, - "w": 95, - "ch": 9, - "s": ["400", "400i", "700", "700i", "900", "900i"] - }, - "Overlock SC": { "x": 930, "y": 520, "w": 139, "ch": 9, "s": ["400"] }, - "Overpass": { - "x": 1083, - "y": 520, - "w": 109, - "ch": 9, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Overpass Mono": { - "x": 0, - "y": 560, - "w": 201, - "ch": 9, - "s": ["300", "400", "500", "600", "700"] - }, - "Ovo": { "x": 215, "y": 560, "w": 52, "ch": 9, "s": ["400"] }, - "Oxanium": { - "x": 281, - "y": 560, - "w": 106, - "ch": 9, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Oxygen": { - "x": 401, - "y": 560, - "w": 92, - "ch": 9, - "s": ["300", "400", "700"] - }, - "Oxygen Mono": { "x": 507, "y": 560, "w": 167, "ch": 9, "s": ["400"] }, - "Pacifico": { "x": 688, "y": 560, "w": 87, "ch": 9, "s": ["400"] }, - "Padauk": { "x": 789, "y": 560, "w": 79, "ch": 9, "s": ["400", "700"] }, - "Padyakke Expanded One": { - "x": 0, - "y": 600, - "w": 415, - "ch": 9, - "s": ["400"] - }, - "Palanquin": { - "x": 429, - "y": 600, - "w": 111, - "ch": 9, - "s": ["100", "200", "300", "400", "500", "600", "700"] - }, - "Palanquin Dark": { - "x": 554, - "y": 600, - "w": 172, - "ch": 9, - "s": ["400", "500", "600", "700"] - }, - "Palette Mosaic": { "x": 740, "y": 600, "w": 212, "ch": 9, "s": ["400"] }, - "Pangolin": { "x": 966, "y": 600, "w": 93, "ch": 9, "s": ["400"] }, - "Paprika": { "x": 1073, "y": 600, "w": 105, "ch": 9, "s": ["400"] }, - "Parastoo": { - "x": 0, - "y": 640, - "w": 82, - "ch": 9, - "s": ["400", "500", "600", "700"] - }, - "Parisienne": { "x": 96, "y": 640, "w": 103, "ch": 9, "s": ["400"] }, - "Parkinsans": { - "x": 213, - "y": 640, - "w": 139, - "ch": 9, - "s": ["300", "400", "500", "600", "700", "800"] - }, - "Passero One": { "x": 366, "y": 640, "w": 125, "ch": 9, "s": ["400"] }, - "Passion One": { - "x": 505, - "y": 640, - "w": 114, - "ch": 9, - "s": ["400", "700", "900"] - }, - "Passions Conflict": { - "x": 633, - "y": 640, - "w": 109, - "ch": 9, - "s": ["400"] - }, - "Pathway Extreme": { - "x": 756, - "y": 640, - "w": 214, - "ch": 9, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Pathway Gothic One": { - "x": 984, - "y": 640, - "w": 154, - "ch": 9, - "s": ["400"] - }, - "Patrick Hand": { "x": 0, "y": 680, "w": 118, "ch": 9, "s": ["400"] }, - "Patrick Hand SC": { "x": 132, "y": 680, "w": 154, "ch": 9, "s": ["400"] }, - "Pattaya": { "x": 300, "y": 680, "w": 85, "ch": 9, "s": ["400"] }, - "Patua One": { "x": 399, "y": 680, "w": 117, "ch": 9, "s": ["400"] }, - "Pavanam": { "x": 530, "y": 680, "w": 97, "ch": 9, "s": ["400"] }, - "Paytone One": { "x": 641, "y": 680, "w": 164, "ch": 9, "s": ["400"] }, - "Peddana": { "x": 819, "y": 680, "w": 67, "ch": 9, "s": ["400"] }, - "Peralta": { "x": 900, "y": 680, "w": 108, "ch": 9, "s": ["400"] }, - "Permanent Marker": { "x": 0, "y": 720, "w": 234, "ch": 9, "s": ["400"] }, - "Petemoss": { "x": 248, "y": 720, "w": 60, "ch": 9, "s": ["400"] }, - "Petit Formal Script": { - "x": 322, - "y": 720, - "w": 255, - "ch": 9, - "s": ["400"] - }, - "Petrona": { - "x": 591, - "y": 720, - "w": 89, - "ch": 9, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Philosopher": { - "x": 694, - "y": 720, - "w": 129, - "ch": 9, - "s": ["400", "400i", "700", "700i"] - }, - "Phudu": { - "x": 837, - "y": 720, - "w": 76, - "ch": 9, - "s": ["300", "400", "500", "600", "700", "800", "900"] - }, - "Piazzolla": { - "x": 927, - "y": 720, - "w": 107, - "ch": 9, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Piedra": { "x": 1048, "y": 720, "w": 68, "ch": 9, "s": ["400"] }, - "Pinyon Script": { "x": 0, "y": 760, "w": 128, "ch": 9, "s": ["400"] }, - "Pirata One": { "x": 142, "y": 760, "w": 92, "ch": 9, "s": ["400"] }, - "Pixelify Sans": { - "x": 248, - "y": 760, - "w": 153, - "ch": 9, - "s": ["400", "500", "600", "700"] - }, - "Plaster": { "x": 415, "y": 760, "w": 126, "ch": 9, "s": ["400"] }, - "Platypi": { - "x": 555, - "y": 760, - "w": 90, - "ch": 9, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Play": { "x": 659, "y": 760, "w": 52, "ch": 9, "s": ["400", "700"] }, - "Playball": { "x": 725, "y": 760, "w": 83, "ch": 9, "s": ["400"] }, - "Playfair": { - "x": 822, - "y": 760, - "w": 90, - "ch": 9, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Playfair Display": { - "x": 926, - "y": 760, - "w": 176, - "ch": 9, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Playfair Display SC": { - "x": 0, - "y": 0, - "w": 247, - "ch": 10, - "s": ["400", "400i", "700", "700i", "900", "900i"] - }, - "Playpen Sans": { - "x": 261, - "y": 0, - "w": 161, - "ch": 10, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Playpen Sans Arabic": { - "x": 436, - "y": 0, - "w": 247, - "ch": 10, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Playpen Sans Deva": { - "x": 697, - "y": 0, - "w": 227, - "ch": 10, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Playpen Sans Hebrew": { - "x": 938, - "y": 0, - "w": 255, - "ch": 10, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Playpen Sans Thai": { - "x": 0, - "y": 40, - "w": 220, - "ch": 10, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Playwrite AR": { - "x": 234, - "y": 40, - "w": 199, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite AR Guides": { - "x": 447, - "y": 40, - "w": 294, - "ch": 10, - "s": ["400"] - }, - "Playwrite AT": { - "x": 755, - "y": 40, - "w": 180, - "ch": 10, - "s": ["100", "100i", "200", "200i", "300", "300i", "400", "400i"] - }, - "Playwrite AT Guides": { - "x": 0, - "y": 80, - "w": 273, - "ch": 10, - "s": ["400", "400i"] - }, - "Playwrite AU NSW": { - "x": 287, - "y": 80, - "w": 257, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite AU NSW Guides": { - "x": 558, - "y": 80, - "w": 350, - "ch": 10, - "s": ["400"] - }, - "Playwrite AU QLD": { - "x": 922, - "y": 80, - "w": 249, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite AU QLD Guides": { - "x": 0, - "y": 120, - "w": 344, - "ch": 10, - "s": ["400"] - }, - "Playwrite AU SA": { - "x": 358, - "y": 120, - "w": 217, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite AU SA Guides": { - "x": 589, - "y": 120, - "w": 312, - "ch": 10, - "s": ["400"] - }, - "Playwrite AU TAS": { - "x": 915, - "y": 120, - "w": 235, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite AU TAS Guides": { - "x": 0, - "y": 160, - "w": 329, - "ch": 10, - "s": ["400"] - }, - "Playwrite AU VIC": { - "x": 343, - "y": 160, - "w": 234, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite AU VIC Guides": { - "x": 591, - "y": 160, - "w": 327, - "ch": 10, - "s": ["400"] - }, - "Playwrite BE VLG": { - "x": 932, - "y": 160, - "w": 266, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite BE VLG Guides": { - "x": 0, - "y": 200, - "w": 359, - "ch": 10, - "s": ["400"] - }, - "Playwrite BE WAL": { - "x": 373, - "y": 200, - "w": 319, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite BE WAL Guides": { - "x": 706, - "y": 200, - "w": 420, - "ch": 10, - "s": ["400"] - }, - "Playwrite BR": { - "x": 0, - "y": 240, - "w": 201, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite BR Guides": { - "x": 215, - "y": 240, - "w": 298, - "ch": 10, - "s": ["400"] - }, - "Playwrite CA": { - "x": 527, - "y": 240, - "w": 191, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite CA Guides": { - "x": 732, - "y": 240, - "w": 288, - "ch": 10, - "s": ["400"] - }, - "Playwrite CL": { - "x": 0, - "y": 280, - "w": 194, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite CL Guides": { - "x": 208, - "y": 280, - "w": 290, - "ch": 10, - "s": ["400"] - }, - "Playwrite CO": { - "x": 512, - "y": 280, - "w": 189, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite CO Guides": { - "x": 715, - "y": 280, - "w": 287, - "ch": 10, - "s": ["400"] - }, - "Playwrite CU": { - "x": 0, - "y": 320, - "w": 192, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite CU Guides": { - "x": 206, - "y": 320, - "w": 289, - "ch": 10, - "s": ["400"] - }, - "Playwrite CZ": { - "x": 509, - "y": 320, - "w": 192, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite CZ Guides": { - "x": 715, - "y": 320, - "w": 287, - "ch": 10, - "s": ["400"] - }, - "Playwrite DE Grund": { - "x": 0, - "y": 360, - "w": 251, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite DE Grund Guides": { - "x": 265, - "y": 360, - "w": 342, - "ch": 10, - "s": ["400"] - }, - "Playwrite DE LA": { - "x": 621, - "y": 360, - "w": 232, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite DE LA Guides": { - "x": 867, - "y": 360, - "w": 325, - "ch": 10, - "s": ["400"] - }, - "Playwrite DE SAS": { - "x": 0, - "y": 400, - "w": 241, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite DE SAS Guides": { - "x": 255, - "y": 400, - "w": 333, - "ch": 10, - "s": ["400"] - }, - "Playwrite DE VA": { - "x": 602, - "y": 400, - "w": 225, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite DE VA Guides": { - "x": 841, - "y": 400, - "w": 320, - "ch": 10, - "s": ["400"] - }, - "Playwrite DK Loopet": { - "x": 0, - "y": 440, - "w": 254, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite DK Loopet Guides": { - "x": 268, - "y": 440, - "w": 345, - "ch": 10, - "s": ["400"] - }, - "Playwrite DK Uloopet": { - "x": 627, - "y": 440, - "w": 262, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite DK Uloopet Guides": { - "x": 0, - "y": 480, - "w": 353, - "ch": 10, - "s": ["400"] - }, - "Playwrite ES": { - "x": 367, - "y": 480, - "w": 176, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite ES Deco": { - "x": 557, - "y": 480, - "w": 256, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite ES Deco Guides": { - "x": 827, - "y": 480, - "w": 350, - "ch": 10, - "s": ["400"] - }, - "Playwrite ES Guides": { - "x": 0, - "y": 520, - "w": 268, - "ch": 10, - "s": ["400"] - }, - "Playwrite FR Moderne": { - "x": 282, - "y": 520, - "w": 287, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite FR Moderne Guides": { - "x": 583, - "y": 520, - "w": 383, - "ch": 10, - "s": ["400"] - }, - "Playwrite FR Trad": { - "x": 0, - "y": 560, - "w": 281, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite FR Trad Guides": { - "x": 295, - "y": 560, - "w": 384, - "ch": 10, - "s": ["400"] - }, - "Playwrite GB J": { - "x": 693, - "y": 560, - "w": 191, - "ch": 10, - "s": ["100", "100i", "200", "200i", "300", "300i", "400", "400i"] - }, - "Playwrite GB J Guides": { - "x": 898, - "y": 560, - "w": 284, - "ch": 10, - "s": ["400", "400i"] - }, - "Playwrite GB S": { - "x": 0, - "y": 600, - "w": 190, - "ch": 10, - "s": ["100", "100i", "200", "200i", "300", "300i", "400", "400i"] - }, - "Playwrite GB S Guides": { - "x": 204, - "y": 600, - "w": 282, - "ch": 10, - "s": ["400", "400i"] - }, - "Playwrite HR": { - "x": 500, - "y": 600, - "w": 191, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite HR Guides": { - "x": 705, - "y": 600, - "w": 286, - "ch": 10, - "s": ["400"] - }, - "Playwrite HR Lijeva": { - "x": 0, - "y": 640, - "w": 276, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite HR Lijeva Guides": { - "x": 290, - "y": 640, - "w": 370, - "ch": 10, - "s": ["400"] - }, - "Playwrite HU": { - "x": 674, - "y": 640, - "w": 190, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite HU Guides": { - "x": 878, - "y": 640, - "w": 285, - "ch": 10, - "s": ["400"] - }, - "Playwrite ID": { - "x": 0, - "y": 680, - "w": 194, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite ID Guides": { - "x": 208, - "y": 680, - "w": 296, - "ch": 10, - "s": ["400"] - }, - "Playwrite IE": { - "x": 518, - "y": 680, - "w": 175, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite IE Guides": { - "x": 707, - "y": 680, - "w": 271, - "ch": 10, - "s": ["400"] - }, - "Playwrite IN": { - "x": 992, - "y": 680, - "w": 189, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite IN Guides": { - "x": 0, - "y": 720, - "w": 285, - "ch": 10, - "s": ["400"] - }, - "Playwrite IS": { - "x": 299, - "y": 720, - "w": 156, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite IS Guides": { - "x": 469, - "y": 720, - "w": 249, - "ch": 10, - "s": ["400"] - }, - "Playwrite IT Moderna": { - "x": 732, - "y": 720, - "w": 273, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite IT Moderna Guides": { - "x": 0, - "y": 760, - "w": 365, - "ch": 10, - "s": ["400"] - }, - "Playwrite IT Trad": { - "x": 379, - "y": 760, - "w": 243, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite IT Trad Guides": { - "x": 636, - "y": 760, - "w": 336, - "ch": 10, - "s": ["400"] - }, - "Playwrite MX": { - "x": 986, - "y": 760, - "w": 200, - "ch": 10, - "s": ["100", "200", "300", "400"] - }, - "Playwrite MX Guides": { "x": 0, "y": 0, "w": 297, "ch": 11, "s": ["400"] }, - "Playwrite NG Modern": { - "x": 311, - "y": 0, - "w": 279, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite NG Modern Guides": { - "x": 604, - "y": 0, - "w": 373, - "ch": 11, - "s": ["400"] - }, - "Playwrite NL": { - "x": 991, - "y": 0, - "w": 207, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite NL Guides": { - "x": 0, - "y": 40, - "w": 306, - "ch": 11, - "s": ["400"] - }, - "Playwrite NO": { - "x": 320, - "y": 40, - "w": 189, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite NO Guides": { - "x": 523, - "y": 40, - "w": 285, - "ch": 11, - "s": ["400"] - }, - "Playwrite NZ": { - "x": 822, - "y": 40, - "w": 164, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite NZ Basic": { - "x": 0, - "y": 80, - "w": 231, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite NZ Basic Guides": { - "x": 245, - "y": 80, - "w": 319, - "ch": 11, - "s": ["400"] - }, - "Playwrite NZ Guides": { - "x": 578, - "y": 80, - "w": 256, - "ch": 11, - "s": ["400"] - }, - "Playwrite PE": { - "x": 848, - "y": 80, - "w": 185, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite PE Guides": { - "x": 0, - "y": 120, - "w": 282, - "ch": 11, - "s": ["400"] - }, - "Playwrite PL": { - "x": 296, - "y": 120, - "w": 178, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite PL Guides": { - "x": 488, - "y": 120, - "w": 272, - "ch": 11, - "s": ["400"] - }, - "Playwrite PT": { - "x": 774, - "y": 120, - "w": 188, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite PT Guides": { - "x": 0, - "y": 160, - "w": 284, - "ch": 11, - "s": ["400"] - }, - "Playwrite RO": { - "x": 298, - "y": 160, - "w": 197, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite RO Guides": { - "x": 509, - "y": 160, - "w": 286, - "ch": 11, - "s": ["400"] - }, - "Playwrite SK": { - "x": 809, - "y": 160, - "w": 195, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite SK Guides": { - "x": 0, - "y": 200, - "w": 289, - "ch": 11, - "s": ["400"] - }, - "Playwrite TZ": { - "x": 303, - "y": 200, - "w": 182, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite TZ Guides": { - "x": 499, - "y": 200, - "w": 271, - "ch": 11, - "s": ["400"] - }, - "Playwrite US Modern": { - "x": 784, - "y": 200, - "w": 277, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite US Modern Guides": { - "x": 0, - "y": 240, - "w": 369, - "ch": 11, - "s": ["400"] - }, - "Playwrite US Trad": { - "x": 383, - "y": 240, - "w": 258, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite US Trad Guides": { - "x": 655, - "y": 240, - "w": 355, - "ch": 11, - "s": ["400"] - }, - "Playwrite VN": { - "x": 0, - "y": 280, - "w": 212, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite VN Guides": { - "x": 226, - "y": 280, - "w": 311, - "ch": 11, - "s": ["400"] - }, - "Playwrite ZA": { - "x": 551, - "y": 280, - "w": 182, - "ch": 11, - "s": ["100", "200", "300", "400"] - }, - "Playwrite ZA Guides": { - "x": 747, - "y": 280, - "w": 276, - "ch": 11, - "s": ["400"] - }, - "Plus Jakarta Sans": { - "x": 0, - "y": 320, - "w": 199, - "ch": 11, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Pochaevsk": { "x": 213, "y": 320, "w": 111, "ch": 11, "s": ["400"] }, - "Podkova": { - "x": 338, - "y": 320, - "w": 97, - "ch": 11, - "s": ["400", "500", "600", "700", "800"] - }, - "Poetsen One": { "x": 449, "y": 320, "w": 148, "ch": 11, "s": ["400"] }, - "Poiret One": { "x": 611, "y": 320, "w": 114, "ch": 11, "s": ["400"] }, - "Poller One": { "x": 739, "y": 320, "w": 164, "ch": 11, "s": ["400"] }, - "Poltawski Nowy": { - "x": 917, - "y": 320, - "w": 182, - "ch": 11, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Poly": { "x": 1113, "y": 320, "w": 54, "ch": 11, "s": ["400", "400i"] }, - "Pompiere": { "x": 0, "y": 360, "w": 75, "ch": 11, "s": ["400"] }, - "Ponnala": { "x": 89, "y": 360, "w": 70, "ch": 11, "s": ["400"] }, - "Ponomar": { "x": 173, "y": 360, "w": 97, "ch": 11, "s": ["400"] }, - "Pontano Sans": { - "x": 284, - "y": 360, - "w": 149, - "ch": 11, - "s": ["300", "400", "500", "600", "700"] - }, - "Poor Story": { "x": 447, "y": 360, "w": 99, "ch": 11, "s": ["400"] }, - "Poppins": { - "x": 560, - "y": 360, - "w": 104, - "ch": 11, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Port Lligat Sans": { - "x": 678, - "y": 360, - "w": 157, - "ch": 11, - "s": ["400"] - }, - "Port Lligat Slab": { - "x": 849, - "y": 360, - "w": 153, - "ch": 11, - "s": ["400"] - }, - "Potta One": { "x": 1016, "y": 360, "w": 143, "ch": 11, "s": ["400"] }, - "Pragati Narrow": { - "x": 0, - "y": 400, - "w": 129, - "ch": 11, - "s": ["400", "700"] - }, - "Praise": { "x": 143, "y": 400, "w": 59, "ch": 11, "s": ["400"] }, - "Prata": { "x": 216, "y": 400, "w": 71, "ch": 11, "s": ["400"] }, - "Preahvihear": { "x": 301, "y": 400, "w": 161, "ch": 11, "s": ["400"] }, - "Press Start 2P": { "x": 476, "y": 400, "w": 344, "ch": 11, "s": ["400"] }, - "Pridi": { - "x": 834, - "y": 400, - "w": 60, - "ch": 11, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Princess Sofia": { "x": 908, "y": 400, "w": 131, "ch": 11, "s": ["400"] }, - "Prociono": { "x": 1053, "y": 400, "w": 101, "ch": 11, "s": ["400"] }, - "Prompt": { - "x": 0, - "y": 440, - "w": 92, - "ch": 11, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Prosto One": { "x": 106, "y": 440, "w": 159, "ch": 11, "s": ["400"] }, - "Protest Guerrilla": { - "x": 279, - "y": 440, - "w": 177, - "ch": 11, - "s": ["400"] - }, - "Protest Revolution": { - "x": 470, - "y": 440, - "w": 200, - "ch": 11, - "s": ["400"] - }, - "Protest Riot": { "x": 684, "y": 440, "w": 131, "ch": 11, "s": ["400"] }, - "Protest Strike": { "x": 829, "y": 440, "w": 151, "ch": 11, "s": ["400"] }, - "Proza Libre": { - "x": 994, - "y": 440, - "w": 137, - "ch": 11, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "PT Mono": { "x": 0, "y": 480, "w": 109, "ch": 11, "s": ["400"] }, - "PT Sans": { - "x": 123, - "y": 480, - "w": 88, - "ch": 11, - "s": ["400", "400i", "700", "700i"] - }, - "PT Sans Caption": { - "x": 225, - "y": 480, - "w": 197, - "ch": 11, - "s": ["400", "700"] - }, - "PT Sans Narrow": { - "x": 436, - "y": 480, - "w": 138, - "ch": 11, - "s": ["400", "700"] - }, - "PT Serif": { - "x": 588, - "y": 480, - "w": 93, - "ch": 11, - "s": ["400", "400i", "700", "700i"] - }, - "PT Serif Caption": { - "x": 695, - "y": 480, - "w": 205, - "ch": 11, - "s": ["400", "400i"] - }, - "Public Sans": { - "x": 914, - "y": 480, - "w": 139, - "ch": 11, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Puppies Play": { "x": 1067, "y": 480, "w": 85, "ch": 11, "s": ["400"] }, - "Puritan": { - "x": 0, - "y": 520, - "w": 78, - "ch": 11, - "s": ["400", "400i", "700", "700i"] - }, - "Purple Purse": { "x": 92, "y": 520, "w": 150, "ch": 11, "s": ["400"] }, - "Pushster": { "x": 256, "y": 520, "w": 89, "ch": 11, "s": ["400"] }, - "Qahiri": { "x": 359, "y": 520, "w": 51, "ch": 11, "s": ["400"] }, - "Quando": { "x": 424, "y": 520, "w": 107, "ch": 11, "s": ["400"] }, - "Quantico": { - "x": 545, - "y": 520, - "w": 109, - "ch": 11, - "s": ["400", "400i", "700", "700i"] - }, - "Quattrocento": { - "x": 668, - "y": 520, - "w": 153, - "ch": 11, - "s": ["400", "700"] - }, - "Quattrocento Sans": { - "x": 835, - "y": 520, - "w": 201, - "ch": 11, - "s": ["400", "400i", "700", "700i"] - }, - "Questrial": { "x": 1050, "y": 520, "w": 105, "ch": 11, "s": ["400"] }, - "Quicksand": { - "x": 0, - "y": 560, - "w": 125, - "ch": 11, - "s": ["300", "400", "500", "600", "700"] - }, - "Quintessential": { "x": 139, "y": 560, "w": 144, "ch": 11, "s": ["400"] }, - "Qwigley": { "x": 297, "y": 560, "w": 59, "ch": 11, "s": ["400"] }, - "Qwitcher Grypen": { - "x": 370, - "y": 560, - "w": 111, - "ch": 11, - "s": ["400", "700"] - }, - "Racing Sans One": { "x": 495, "y": 560, "w": 190, "ch": 11, "s": ["400"] }, - "Radio Canada": { - "x": 699, - "y": 560, - "w": 161, - "ch": 11, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Radio Canada Big": { - "x": 874, - "y": 560, - "w": 196, - "ch": 11, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Radley": { "x": 1084, "y": 560, "w": 81, "ch": 11, "s": ["400", "400i"] }, - "Rajdhani": { - "x": 0, - "y": 600, - "w": 92, - "ch": 11, - "s": ["300", "400", "500", "600", "700"] - }, - "Rakkas": { "x": 106, "y": 600, "w": 79, "ch": 11, "s": ["400"] }, - "Raleway": { - "x": 199, - "y": 600, - "w": 102, - "ch": 11, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Raleway Dots": { "x": 315, "y": 600, "w": 156, "ch": 11, "s": ["400"] }, - "Ramabhadra": { "x": 485, "y": 600, "w": 154, "ch": 11, "s": ["400"] }, - "Ramaraja": { "x": 653, "y": 600, "w": 96, "ch": 11, "s": ["400"] }, - "Rambla": { - "x": 763, - "y": 600, - "w": 83, - "ch": 11, - "s": ["400", "400i", "700", "700i"] - }, - "Rammetto One": { "x": 860, "y": 600, "w": 234, "ch": 11, "s": ["400"] }, - "Rampart One": { "x": 0, "y": 640, "w": 170, "ch": 11, "s": ["400"] }, - "Ranchers": { "x": 184, "y": 640, "w": 97, "ch": 11, "s": ["400"] }, - "Rancho": { "x": 295, "y": 640, "w": 62, "ch": 11, "s": ["400"] }, - "Ranga": { "x": 371, "y": 640, "w": 52, "ch": 11, "s": ["400", "700"] }, - "Rasa": { - "x": 437, - "y": 640, - "w": 53, - "ch": 11, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Rationale": { "x": 504, "y": 640, "w": 90, "ch": 11, "s": ["400"] }, - "Ravi Prakash": { "x": 608, "y": 640, "w": 132, "ch": 11, "s": ["400"] }, - "Readex Pro": { - "x": 754, - "y": 640, - "w": 140, - "ch": 11, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Recursive": { - "x": 908, - "y": 640, - "w": 123, - "ch": 11, - "s": ["300", "400", "500", "600", "700", "800", "900"] - }, - "Red Hat Display": { - "x": 0, - "y": 680, - "w": 175, - "ch": 11, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Red Hat Mono": { - "x": 189, - "y": 680, - "w": 181, - "ch": 11, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Red Hat Text": { - "x": 384, - "y": 680, - "w": 146, - "ch": 11, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Red Rose": { - "x": 544, - "y": 680, - "w": 117, - "ch": 11, - "s": ["300", "400", "500", "600", "700"] - }, - "Redacted": { "x": 675, "y": 680, "w": 80, "ch": 11, "s": ["400"] }, - "Redacted Script": { - "x": 769, - "y": 680, - "w": 201, - "ch": 11, - "s": ["300", "400", "700"] - }, - "Reddit Mono": { - "x": 984, - "y": 680, - "w": 157, - "ch": 11, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Reddit Sans": { - "x": 0, - "y": 720, - "w": 134, - "ch": 11, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Reddit Sans Condensed": { - "x": 148, - "y": 720, - "w": 232, - "ch": 11, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Redressed": { "x": 394, "y": 720, "w": 96, "ch": 11, "s": ["400"] }, - "Reem Kufi": { - "x": 504, - "y": 720, - "w": 118, - "ch": 11, - "s": ["400", "500", "600", "700"] - }, - "Reem Kufi Fun": { - "x": 636, - "y": 720, - "w": 161, - "ch": 11, - "s": ["400", "500", "600", "700"] - }, - "Reem Kufi Ink": { "x": 811, "y": 720, "w": 154, "ch": 11, "s": ["400"] }, - "Reenie Beanie": { "x": 979, "y": 720, "w": 122, "ch": 11, "s": ["400"] }, - "Reggae One": { "x": 0, "y": 760, "w": 161, "ch": 11, "s": ["400"] }, - "REM": { - "x": 175, - "y": 760, - "w": 63, - "ch": 11, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Rethink Sans": { - "x": 252, - "y": 760, - "w": 150, - "ch": 11, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Revalia": { "x": 416, "y": 760, "w": 121, "ch": 11, "s": ["400"] }, - "Rhodium Libre": { "x": 551, "y": 760, "w": 187, "ch": 11, "s": ["400"] }, - "Ribeye": { "x": 752, "y": 760, "w": 92, "ch": 11, "s": ["400"] }, - "Ribeye Marrow": { "x": 858, "y": 760, "w": 201, "ch": 11, "s": ["400"] }, - "Righteous": { "x": 1073, "y": 760, "w": 121, "ch": 11, "s": ["400"] }, - "Risque": { "x": 0, "y": 0, "w": 77, "ch": 12, "s": ["400"] }, - "Road Rage": { "x": 91, "y": 0, "w": 72, "ch": 12, "s": ["400"] }, - "Roboto": { - "x": 177, - "y": 0, - "w": 85, - "ch": 12, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Roboto Condensed": { - "x": 276, - "y": 0, - "w": 188, - "ch": 12, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Roboto Flex": { "x": 478, "y": 0, "w": 134, "ch": 12, "s": ["400"] }, - "Roboto Mono": { - "x": 626, - "y": 0, - "w": 167, - "ch": 12, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Roboto Serif": { - "x": 807, - "y": 0, - "w": 162, - "ch": 12, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Roboto Slab": { - "x": 983, - "y": 0, - "w": 141, - "ch": 12, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Rochester": { "x": 0, "y": 40, "w": 85, "ch": 12, "s": ["400"] }, - "Rock 3D": { "x": 99, "y": 40, "w": 137, "ch": 12, "s": ["400"] }, - "Rock Salt": { "x": 250, "y": 40, "w": 153, "ch": 12, "s": ["400"] }, - "RocknRoll One": { "x": 417, "y": 40, "w": 190, "ch": 12, "s": ["400"] }, - "Rokkitt": { - "x": 621, - "y": 40, - "w": 79, - "ch": 12, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Romanesco": { "x": 714, "y": 40, "w": 73, "ch": 12, "s": ["400"] }, - "Ropa Sans": { - "x": 801, - "y": 40, - "w": 102, - "ch": 12, - "s": ["400", "400i"] - }, - "Rosario": { - "x": 917, - "y": 40, - "w": 84, - "ch": 12, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Rosarivo": { - "x": 1015, - "y": 40, - "w": 106, - "ch": 12, - "s": ["400", "400i"] - }, - "Rouge Script": { "x": 0, "y": 80, "w": 104, "ch": 12, "s": ["400"] }, - "Rowdies": { - "x": 118, - "y": 80, - "w": 106, - "ch": 12, - "s": ["300", "400", "700"] - }, - "Rozha One": { "x": 238, "y": 80, "w": 126, "ch": 12, "s": ["400"] }, - "Rubik": { - "x": 378, - "y": 80, - "w": 71, - "ch": 12, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Rubik 80s Fade": { "x": 463, "y": 80, "w": 199, "ch": 12, "s": ["400"] }, - "Rubik Beastly": { "x": 676, "y": 80, "w": 183, "ch": 12, "s": ["400"] }, - "Rubik Broken Fax": { "x": 873, "y": 80, "w": 227, "ch": 12, "s": ["400"] }, - "Rubik Bubbles": { "x": 0, "y": 120, "w": 188, "ch": 12, "s": ["400"] }, - "Rubik Burned": { "x": 202, "y": 120, "w": 179, "ch": 12, "s": ["400"] }, - "Rubik Dirt": { "x": 395, "y": 120, "w": 137, "ch": 12, "s": ["400"] }, - "Rubik Distressed": { - "x": 546, - "y": 120, - "w": 225, - "ch": 12, - "s": ["400"] - }, - "Rubik Doodle Shadow": { - "x": 785, - "y": 120, - "w": 277, - "ch": 12, - "s": ["400"] - }, - "Rubik Doodle Triangles": { - "x": 0, - "y": 160, - "w": 296, - "ch": 12, - "s": ["400"] - }, - "Rubik Gemstones": { "x": 310, "y": 160, "w": 229, "ch": 12, "s": ["400"] }, - "Rubik Glitch": { "x": 553, "y": 160, "w": 165, "ch": 12, "s": ["400"] }, - "Rubik Glitch Pop": { - "x": 732, - "y": 160, - "w": 217, - "ch": 12, - "s": ["400"] - }, - "Rubik Iso": { "x": 963, "y": 160, "w": 124, "ch": 12, "s": ["400"] }, - "Rubik Lines": { "x": 0, "y": 200, "w": 155, "ch": 12, "s": ["400"] }, - "Rubik Maps": { "x": 169, "y": 200, "w": 151, "ch": 12, "s": ["400"] }, - "Rubik Marker Hatch": { - "x": 334, - "y": 200, - "w": 257, - "ch": 12, - "s": ["400"] - }, - "Rubik Maze": { "x": 605, "y": 200, "w": 150, "ch": 12, "s": ["400"] }, - "Rubik Microbe": { "x": 769, "y": 200, "w": 188, "ch": 12, "s": ["400"] }, - "Rubik Mono One": { "x": 0, "y": 240, "w": 294, "ch": 12, "s": ["400"] }, - "Rubik Moonrocks": { "x": 308, "y": 240, "w": 225, "ch": 12, "s": ["400"] }, - "Rubik Pixels": { "x": 547, "y": 240, "w": 162, "ch": 12, "s": ["400"] }, - "Rubik Puddles": { "x": 723, "y": 240, "w": 187, "ch": 12, "s": ["400"] }, - "Rubik Scribble": { "x": 924, "y": 240, "w": 192, "ch": 12, "s": ["400"] }, - "Rubik Spray Paint": { "x": 0, "y": 280, "w": 233, "ch": 12, "s": ["400"] }, - "Rubik Storm": { "x": 247, "y": 280, "w": 165, "ch": 12, "s": ["400"] }, - "Rubik Vinyl": { "x": 426, "y": 280, "w": 150, "ch": 12, "s": ["400"] }, - "Rubik Wet Paint": { "x": 590, "y": 280, "w": 207, "ch": 12, "s": ["400"] }, - "Ruda": { - "x": 811, - "y": 280, - "w": 66, - "ch": 12, - "s": ["400", "500", "600", "700", "800", "900"] - }, - "Rufina": { "x": 891, "y": 280, "w": 82, "ch": 12, "s": ["400", "700"] }, - "Ruge Boogie": { "x": 987, "y": 280, "w": 99, "ch": 12, "s": ["400"] }, - "Ruluko": { "x": 1100, "y": 280, "w": 78, "ch": 12, "s": ["400"] }, - "Rum Raisin": { "x": 0, "y": 320, "w": 100, "ch": 12, "s": ["400"] }, - "Ruslan Display": { "x": 114, "y": 320, "w": 220, "ch": 12, "s": ["400"] }, - "Russo One": { "x": 348, "y": 320, "w": 138, "ch": 12, "s": ["400"] }, - "Ruthie": { "x": 500, "y": 320, "w": 64, "ch": 12, "s": ["400"] }, - "Ruwudu": { - "x": 578, - "y": 320, - "w": 94, - "ch": 12, - "s": ["400", "500", "600", "700"] - }, - "Rye": { "x": 686, "y": 320, "w": 55, "ch": 12, "s": ["400"] }, - "Sacramento": { "x": 755, "y": 320, "w": 104, "ch": 12, "s": ["400"] }, - "Sahitya": { "x": 873, "y": 320, "w": 81, "ch": 12, "s": ["400", "700"] }, - "Sail": { "x": 968, "y": 320, "w": 50, "ch": 12, "s": ["400"] }, - "Saira": { - "x": 1032, - "y": 320, - "w": 66, - "ch": 12, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Saira Condensed": { - "x": 0, - "y": 360, - "w": 149, - "ch": 12, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Saira Extra Condensed": { - "x": 163, - "y": 360, - "w": 167, - "ch": 12, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Saira Semi Condensed": { - "x": 344, - "y": 360, - "w": 227, - "ch": 12, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Saira Stencil One": { - "x": 585, - "y": 360, - "w": 201, - "ch": 12, - "s": ["400"] - }, - "Salsa": { "x": 800, "y": 360, "w": 66, "ch": 12, "s": ["400"] }, - "Sanchez": { "x": 880, "y": 360, "w": 105, "ch": 12, "s": ["400", "400i"] }, - "Sancreek": { "x": 999, "y": 360, "w": 111, "ch": 12, "s": ["400"] }, - "Sankofa Display": { "x": 0, "y": 400, "w": 163, "ch": 12, "s": ["400"] }, - "Sansation": { - "x": 177, - "y": 400, - "w": 119, - "ch": 12, - "s": ["300", "300i", "400", "400i", "700", "700i"] - }, - "Sansita": { - "x": 310, - "y": 400, - "w": 80, - "ch": 12, - "s": ["400", "400i", "700", "700i", "800", "800i", "900", "900i"] - }, - "Sansita Swashed": { - "x": 404, - "y": 400, - "w": 177, - "ch": 12, - "s": ["300", "400", "500", "600", "700", "800", "900"] - }, - "Sarabun": { - "x": 595, - "y": 400, - "w": 94, - "ch": 12, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Sarala": { "x": 703, "y": 400, "w": 74, "ch": 12, "s": ["400", "700"] }, - "Sarina": { "x": 791, "y": 400, "w": 112, "ch": 12, "s": ["400"] }, - "Sarpanch": { - "x": 917, - "y": 400, - "w": 122, - "ch": 12, - "s": ["400", "500", "600", "700", "800", "900"] - }, - "Sassy Frass": { "x": 1053, "y": 400, "w": 78, "ch": 12, "s": ["400"] }, - "Satisfy": { "x": 0, "y": 440, "w": 70, "ch": 12, "s": ["400"] }, - "Savate": { - "x": 84, - "y": 440, - "w": 81, - "ch": 12, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Sawarabi Gothic": { "x": 179, "y": 440, "w": 186, "ch": 12, "s": ["400"] }, - "Sawarabi Mincho": { "x": 379, "y": 440, "w": 204, "ch": 12, "s": ["400"] }, - "Scada": { - "x": 597, - "y": 440, - "w": 69, - "ch": 12, - "s": ["400", "400i", "700", "700i"] - }, - "Scheherazade New": { - "x": 680, - "y": 440, - "w": 175, - "ch": 12, - "s": ["400", "500", "600", "700"] - }, - "Schibsted Grotesk": { - "x": 869, - "y": 440, - "w": 212, - "ch": 12, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Schoolbell": { "x": 1095, "y": 440, "w": 102, "ch": 12, "s": ["400"] }, - "Science Gothic": { - "x": 0, - "y": 480, - "w": 212, - "ch": 12, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Scope One": { "x": 226, "y": 480, "w": 120, "ch": 12, "s": ["400"] }, - "Seaweed Script": { "x": 360, "y": 480, "w": 137, "ch": 12, "s": ["400"] }, - "Secular One": { "x": 511, "y": 480, "w": 144, "ch": 12, "s": ["400"] }, - "Sedan": { "x": 669, "y": 480, "w": 72, "ch": 12, "s": ["400", "400i"] }, - "Sedan SC": { "x": 755, "y": 480, "w": 111, "ch": 12, "s": ["400"] }, - "Sedgwick Ave": { "x": 880, "y": 480, "w": 140, "ch": 12, "s": ["400"] }, - "Sedgwick Ave Display": { - "x": 0, - "y": 520, - "w": 224, - "ch": 12, - "s": ["400"] - }, - "Sekuya": { "x": 238, "y": 520, "w": 138, "ch": 12, "s": ["400"] }, - "Sen": { - "x": 390, - "y": 520, - "w": 48, - "ch": 12, - "s": ["400", "500", "600", "700", "800"] - }, - "Send Flowers": { "x": 452, "y": 520, "w": 129, "ch": 12, "s": ["400"] }, - "Sevillana": { "x": 595, "y": 520, "w": 96, "ch": 12, "s": ["400"] }, - "Seymour One": { "x": 705, "y": 520, "w": 241, "ch": 12, "s": ["400"] }, - "Shadows Into Light": { - "x": 960, - "y": 520, - "w": 172, - "ch": 12, - "s": ["400"] - }, - "Shadows Into Light Two": { - "x": 0, - "y": 560, - "w": 234, - "ch": 12, - "s": ["400"] - }, - "Shafarik": { "x": 248, "y": 560, "w": 89, "ch": 12, "s": ["400"] }, - "Shalimar": { "x": 351, "y": 560, "w": 59, "ch": 12, "s": ["400"] }, - "Shantell Sans": { - "x": 424, - "y": 560, - "w": 166, - "ch": 12, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Shanti": { "x": 604, "y": 560, "w": 75, "ch": 12, "s": ["400"] }, - "Share": { - "x": 693, - "y": 560, - "w": 62, - "ch": 12, - "s": ["400", "400i", "700", "700i"] - }, - "Share Tech": { "x": 769, "y": 560, "w": 109, "ch": 12, "s": ["400"] }, - "Share Tech Mono": { "x": 892, "y": 560, "w": 203, "ch": 12, "s": ["400"] }, - "Shippori Antique": { "x": 0, "y": 600, "w": 206, "ch": 12, "s": ["400"] }, - "Shippori Antique B1": { - "x": 220, - "y": 600, - "w": 241, - "ch": 12, - "s": ["400"] - }, - "Shippori Mincho": { - "x": 475, - "y": 600, - "w": 202, - "ch": 12, - "s": ["400", "500", "600", "700", "800"] - }, - "Shippori Mincho B1": { - "x": 691, - "y": 600, - "w": 237, - "ch": 12, - "s": ["400", "500", "600", "700", "800"] - }, - "Shizuru": { "x": 942, "y": 600, "w": 82, "ch": 12, "s": ["400"] }, - "Shojumaru": { "x": 0, "y": 640, "w": 184, "ch": 12, "s": ["400"] }, - "Short Stack": { "x": 198, "y": 640, "w": 168, "ch": 12, "s": ["400"] }, - "Shrikhand": { "x": 380, "y": 640, "w": 147, "ch": 12, "s": ["400"] }, - "Sigmar": { "x": 541, "y": 640, "w": 99, "ch": 12, "s": ["400"] }, - "Sigmar One": { "x": 654, "y": 640, "w": 172, "ch": 12, "s": ["400"] }, - "Signika": { - "x": 840, - "y": 640, - "w": 85, - "ch": 12, - "s": ["300", "400", "500", "600", "700"] - }, - "Signika Negative": { - "x": 939, - "y": 640, - "w": 180, - "ch": 12, - "s": ["300", "400", "500", "600", "700"] - }, - "Silkscreen": { "x": 0, "y": 680, "w": 173, "ch": 12, "s": ["400", "700"] }, - "Simonetta": { - "x": 187, - "y": 680, - "w": 108, - "ch": 12, - "s": ["400", "400i", "900", "900i"] - }, - "Sintony": { "x": 309, "y": 680, "w": 97, "ch": 12, "s": ["400", "700"] }, - "Sirin Stencil": { "x": 420, "y": 680, "w": 122, "ch": 12, "s": ["400"] }, - "Sirivennela": { "x": 556, "y": 680, "w": 98, "ch": 12, "s": ["400"] }, - "Six Caps": { "x": 668, "y": 680, "w": 47, "ch": 12, "s": ["400"] }, - "Sixtyfour": { "x": 729, "y": 680, "w": 224, "ch": 12, "s": ["400"] }, - "Sixtyfour Convergence": { - "x": 0, - "y": 720, - "w": 512, - "ch": 12, - "s": ["400"] - }, - "Skranji": { "x": 526, "y": 720, "w": 78, "ch": 12, "s": ["400", "700"] }, - "Slabo 13px": { "x": 618, "y": 720, "w": 127, "ch": 12, "s": ["400"] }, - "Slabo 27px": { "x": 759, "y": 720, "w": 111, "ch": 12, "s": ["400"] }, - "Slackey": { "x": 884, "y": 720, "w": 124, "ch": 12, "s": ["400"] }, - "Slackside One": { "x": 1022, "y": 720, "w": 137, "ch": 12, "s": ["400"] }, - "Smokum": { "x": 0, "y": 760, "w": 76, "ch": 12, "s": ["400"] }, - "Smooch": { "x": 90, "y": 760, "w": 73, "ch": 12, "s": ["400"] }, - "Smooch Sans": { - "x": 177, - "y": 760, - "w": 110, - "ch": 12, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Smythe": { "x": 301, "y": 760, "w": 64, "ch": 12, "s": ["400"] }, - "SN Pro": { - "x": 379, - "y": 760, - "w": 84, - "ch": 12, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Sniglet": { "x": 477, "y": 760, "w": 82, "ch": 12, "s": ["400", "800"] }, - "Snippet": { "x": 573, "y": 760, "w": 89, "ch": 12, "s": ["400"] }, - "Snowburst One": { "x": 676, "y": 760, "w": 197, "ch": 12, "s": ["400"] }, - "Sofadi One": { "x": 887, "y": 760, "w": 137, "ch": 12, "s": ["400"] }, - "Sofia": { "x": 1038, "y": 760, "w": 61, "ch": 12, "s": ["400"] }, - "Sofia Sans": { - "x": 0, - "y": 0, - "w": 115, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Sofia Sans Condensed": { - "x": 129, - "y": 0, - "w": 184, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Sofia Sans Extra Condensed": { - "x": 327, - "y": 0, - "w": 188, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Sofia Sans Semi Condensed": { - "x": 529, - "y": 0, - "w": 280, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Solitreo": { "x": 823, "y": 0, "w": 83, "ch": 13, "s": ["400"] }, - "Solway": { - "x": 920, - "y": 0, - "w": 90, - "ch": 13, - "s": ["300", "400", "500", "700", "800"] - }, - "Sometype Mono": { - "x": 0, - "y": 40, - "w": 189, - "ch": 13, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Sono": { - "x": 203, - "y": 40, - "w": 68, - "ch": 13, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Sonsie One": { "x": 285, "y": 40, "w": 194, "ch": 13, "s": ["400"] }, - "Sora": { - "x": 493, - "y": 40, - "w": 65, - "ch": 13, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Sorts Mill Goudy": { - "x": 572, - "y": 40, - "w": 187, - "ch": 13, - "s": ["400", "400i"] - }, - "Sour Gummy": { - "x": 773, - "y": 40, - "w": 149, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Source Code Pro": { - "x": 936, - "y": 40, - "w": 224, - "ch": 13, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Source Sans 3": { - "x": 0, - "y": 80, - "w": 147, - "ch": 13, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Source Sans Pro": { - "x": 161, - "y": 80, - "w": 170, - "ch": 13, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "600", - "600i", - "700", - "700i", - "900", - "900i" - ] - }, - "Source Serif 4": { - "x": 345, - "y": 80, - "w": 164, - "ch": 13, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Source Serif Pro": { - "x": 523, - "y": 80, - "w": 180, - "ch": 13, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "600", - "600i", - "700", - "700i", - "900", - "900i" - ] - }, - "Space Grotesk": { - "x": 717, - "y": 80, - "w": 176, - "ch": 13, - "s": ["300", "400", "500", "600", "700"] - }, - "Space Mono": { - "x": 907, - "y": 80, - "w": 155, - "ch": 13, - "s": ["400", "400i", "700", "700i"] - }, - "Special Elite": { "x": 0, "y": 120, "w": 177, "ch": 13, "s": ["400"] }, - "Special Gothic": { - "x": 191, - "y": 120, - "w": 156, - "ch": 13, - "s": ["400", "500", "600", "700"] - }, - "Special Gothic Condensed One": { - "x": 361, - "y": 120, - "w": 247, - "ch": 13, - "s": ["400"] - }, - "Special Gothic Expanded One": { - "x": 622, - "y": 120, - "w": 424, - "ch": 13, - "s": ["400"] - }, - "Spectral": { - "x": 1060, - "y": 120, - "w": 95, - "ch": 13, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Spectral SC": { - "x": 0, - "y": 160, - "w": 159, - "ch": 13, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Spicy Rice": { "x": 173, "y": 160, "w": 111, "ch": 13, "s": ["400"] }, - "Spinnaker": { "x": 298, "y": 160, "w": 128, "ch": 13, "s": ["400"] }, - "Spirax": { "x": 440, "y": 160, "w": 73, "ch": 13, "s": ["400"] }, - "Splash": { "x": 527, "y": 160, "w": 88, "ch": 13, "s": ["400"] }, - "Spline Sans": { - "x": 629, - "y": 160, - "w": 133, - "ch": 13, - "s": ["300", "400", "500", "600", "700"] - }, - "Spline Sans Mono": { - "x": 776, - "y": 160, - "w": 239, - "ch": 13, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Squada One": { "x": 1029, "y": 160, "w": 111, "ch": 13, "s": ["400"] }, - "Square Peg": { "x": 0, "y": 200, "w": 73, "ch": 13, "s": ["400"] }, - "Sree Krushnadevaraya": { - "x": 87, - "y": 200, - "w": 222, - "ch": 13, - "s": ["400"] - }, - "Sriracha": { "x": 323, "y": 200, "w": 99, "ch": 13, "s": ["400"] }, - "Srisakdi": { "x": 436, "y": 200, "w": 90, "ch": 13, "s": ["400", "700"] }, - "Staatliches": { "x": 540, "y": 200, "w": 114, "ch": 13, "s": ["400"] }, - "Stack Sans Headline": { - "x": 668, - "y": 200, - "w": 235, - "ch": 13, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Stack Sans Notch": { - "x": 917, - "y": 200, - "w": 205, - "ch": 13, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Stack Sans Text": { - "x": 0, - "y": 240, - "w": 195, - "ch": 13, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Stalemate": { "x": 209, "y": 240, "w": 62, "ch": 13, "s": ["400"] }, - "Stalinist One": { "x": 285, "y": 240, "w": 280, "ch": 13, "s": ["400"] }, - "Stardos Stencil": { - "x": 579, - "y": 240, - "w": 168, - "ch": 13, - "s": ["400", "700"] - }, - "Stick": { "x": 761, "y": 240, "w": 65, "ch": 13, "s": ["400"] }, - "Stick No Bills": { - "x": 840, - "y": 240, - "w": 123, - "ch": 13, - "s": ["200", "300", "400", "500", "600", "700", "800"] - }, - "Stint Ultra Condensed": { - "x": 977, - "y": 240, - "w": 139, - "ch": 13, - "s": ["400"] - }, - "Stint Ultra Expanded": { - "x": 0, - "y": 280, - "w": 310, - "ch": 13, - "s": ["400"] - }, - "STIX Two Text": { - "x": 324, - "y": 280, - "w": 156, - "ch": 13, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Stoke": { "x": 494, "y": 280, "w": 84, "ch": 13, "s": ["300", "400"] }, - "Story Script": { "x": 592, "y": 280, "w": 113, "ch": 13, "s": ["400"] }, - "Strait": { "x": 719, "y": 280, "w": 62, "ch": 13, "s": ["400"] }, - "Style Script": { "x": 795, "y": 280, "w": 97, "ch": 13, "s": ["400"] }, - "Sue Ellen Francisco": { - "x": 906, - "y": 280, - "w": 137, - "ch": 13, - "s": ["400"] - }, - "Suez One": { "x": 1057, "y": 280, "w": 113, "ch": 13, "s": ["400"] }, - "Sulphur Point": { - "x": 0, - "y": 320, - "w": 144, - "ch": 13, - "s": ["300", "400", "700"] - }, - "Sumana": { "x": 158, "y": 320, "w": 92, "ch": 13, "s": ["400", "700"] }, - "Sunshiney": { "x": 264, "y": 320, "w": 94, "ch": 13, "s": ["400"] }, - "Supermercado One": { - "x": 372, - "y": 320, - "w": 196, - "ch": 13, - "s": ["400"] - }, - "Sura": { "x": 582, "y": 320, "w": 58, "ch": 13, "s": ["400", "700"] }, - "Suranna": { "x": 654, "y": 320, "w": 86, "ch": 13, "s": ["400"] }, - "Suravaram": { "x": 754, "y": 320, "w": 104, "ch": 13, "s": ["400"] }, - "SUSE": { - "x": 872, - "y": 320, - "w": 65, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "SUSE Mono": { - "x": 951, - "y": 320, - "w": 138, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Suwannaphum": { - "x": 0, - "y": 360, - "w": 184, - "ch": 13, - "s": ["100", "300", "400", "700", "900"] - }, - "Swanky and Moo Moo": { - "x": 198, - "y": 360, - "w": 203, - "ch": 13, - "s": ["400"] - }, - "Syncopate": { - "x": 415, - "y": 360, - "w": 195, - "ch": 13, - "s": ["400", "700"] - }, - "Syne": { - "x": 624, - "y": 360, - "w": 60, - "ch": 13, - "s": ["400", "500", "600", "700", "800"] - }, - "Syne Mono": { "x": 698, "y": 360, "w": 127, "ch": 13, "s": ["400"] }, - "Syne Tactile": { "x": 839, "y": 360, "w": 119, "ch": 13, "s": ["400"] }, - "Tac One": { "x": 972, "y": 360, "w": 73, "ch": 13, "s": ["400"] }, - "Tagesschrift": { "x": 0, "y": 400, "w": 142, "ch": 13, "s": ["400"] }, - "Tai Heritage Pro": { - "x": 156, - "y": 400, - "w": 171, - "ch": 13, - "s": ["400", "700"] - }, - "Tajawal": { - "x": 341, - "y": 400, - "w": 82, - "ch": 13, - "s": ["200", "300", "400", "500", "700", "800", "900"] - }, - "Tangerine": { "x": 437, "y": 400, "w": 67, "ch": 13, "s": ["400", "700"] }, - "Tapestry": { "x": 518, "y": 400, "w": 100, "ch": 13, "s": ["400"] }, - "Taprom": { "x": 632, "y": 400, "w": 70, "ch": 13, "s": ["400"] }, - "TASA Explorer": { - "x": 716, - "y": 400, - "w": 161, - "ch": 13, - "s": ["400", "500", "600", "700", "800"] - }, - "TASA Orbiter": { - "x": 891, - "y": 400, - "w": 153, - "ch": 13, - "s": ["400", "500", "600", "700", "800"] - }, - "Tauri": { "x": 1058, "y": 400, "w": 66, "ch": 13, "s": ["400"] }, - "Taviraj": { - "x": 0, - "y": 440, - "w": 84, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Teachers": { - "x": 98, - "y": 440, - "w": 100, - "ch": 13, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Teko": { - "x": 212, - "y": 440, - "w": 42, - "ch": 13, - "s": ["300", "400", "500", "600", "700"] - }, - "Tektur": { - "x": 268, - "y": 440, - "w": 86, - "ch": 13, - "s": ["400", "500", "600", "700", "800", "900"] - }, - "Telex": { "x": 368, "y": 440, "w": 64, "ch": 13, "s": ["400"] }, - "Tenali Ramakrishna": { - "x": 446, - "y": 440, - "w": 170, - "ch": 13, - "s": ["400"] - }, - "Tenor Sans": { "x": 630, "y": 440, "w": 134, "ch": 13, "s": ["400"] }, - "Text Me One": { "x": 778, "y": 440, "w": 140, "ch": 13, "s": ["400"] }, - "Texturina": { - "x": 932, - "y": 440, - "w": 113, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Thasadith": { - "x": 1059, - "y": 440, - "w": 101, - "ch": 13, - "s": ["400", "400i", "700", "700i"] - }, - "The Girl Next Door": { - "x": 0, - "y": 480, - "w": 215, - "ch": 13, - "s": ["400"] - }, - "The Nautigal": { - "x": 229, - "y": 480, - "w": 102, - "ch": 13, - "s": ["400", "700"] - }, - "Tienne": { - "x": 345, - "y": 480, - "w": 92, - "ch": 13, - "s": ["400", "700", "900"] - }, - "TikTok Sans": { - "x": 451, - "y": 480, - "w": 141, - "ch": 13, - "s": ["300", "400", "500", "600", "700", "800", "900"] - }, - "Tillana": { - "x": 606, - "y": 480, - "w": 75, - "ch": 13, - "s": ["400", "500", "600", "700", "800"] - }, - "Tilt Neon": { "x": 695, "y": 480, "w": 101, "ch": 13, "s": ["400"] }, - "Tilt Prism": { "x": 810, "y": 480, "w": 121, "ch": 13, "s": ["400"] }, - "Tilt Warp": { "x": 945, "y": 480, "w": 109, "ch": 13, "s": ["400"] }, - "Timmana": { "x": 1068, "y": 480, "w": 90, "ch": 13, "s": ["400"] }, - "Tinos": { - "x": 0, - "y": 520, - "w": 62, - "ch": 13, - "s": ["400", "400i", "700", "700i"] - }, - "Tiny5": { "x": 76, "y": 520, "w": 62, "ch": 13, "s": ["400"] }, - "Tiro Bangla": { - "x": 152, - "y": 520, - "w": 134, - "ch": 13, - "s": ["400", "400i"] - }, - "Tiro Devanagari Hindi": { - "x": 300, - "y": 520, - "w": 251, - "ch": 13, - "s": ["400", "400i"] - }, - "Tiro Devanagari Marathi": { - "x": 565, - "y": 520, - "w": 274, - "ch": 13, - "s": ["400", "400i"] - }, - "Tiro Devanagari Sanskrit": { - "x": 853, - "y": 520, - "w": 276, - "ch": 13, - "s": ["400", "400i"] - }, - "Tiro Gurmukhi": { - "x": 0, - "y": 560, - "w": 173, - "ch": 13, - "s": ["400", "400i"] - }, - "Tiro Kannada": { - "x": 187, - "y": 560, - "w": 156, - "ch": 13, - "s": ["400", "400i"] - }, - "Tiro Tamil": { - "x": 357, - "y": 560, - "w": 122, - "ch": 13, - "s": ["400", "400i"] - }, - "Tiro Telugu": { - "x": 493, - "y": 560, - "w": 134, - "ch": 13, - "s": ["400", "400i"] - }, - "Tirra": { - "x": 641, - "y": 560, - "w": 57, - "ch": 13, - "s": ["400", "500", "600", "700", "800", "900"] - }, - "Titan One": { "x": 712, "y": 560, "w": 131, "ch": 13, "s": ["400"] }, - "Titillium Web": { - "x": 857, - "y": 560, - "w": 140, - "ch": 13, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "600", - "600i", - "700", - "700i", - "900", - "900i" - ] - }, - "Tomorrow": { - "x": 1011, - "y": 560, - "w": 126, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Tourney": { - "x": 0, - "y": 600, - "w": 107, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Trade Winds": { "x": 121, "y": 600, "w": 160, "ch": 13, "s": ["400"] }, - "Train One": { "x": 295, "y": 600, "w": 132, "ch": 13, "s": ["400"] }, - "Triodion": { "x": 441, "y": 600, "w": 94, "ch": 13, "s": ["400"] }, - "Trirong": { - "x": 549, - "y": 600, - "w": 92, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Trispace": { - "x": 655, - "y": 600, - "w": 128, - "ch": 13, - "s": ["100", "200", "300", "400", "500", "600", "700", "800"] - }, - "Trocchi": { "x": 797, "y": 600, "w": 100, "ch": 13, "s": ["400"] }, - "Trochut": { - "x": 911, - "y": 600, - "w": 75, - "ch": 13, - "s": ["400", "400i", "700", "700i"] - }, - "Truculenta": { - "x": 1000, - "y": 600, - "w": 100, - "ch": 13, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Trykker": { "x": 0, "y": 640, "w": 98, "ch": 13, "s": ["400"] }, - "Tsukimi Rounded": { - "x": 112, - "y": 640, - "w": 209, - "ch": 13, - "s": ["300", "400", "500", "600", "700"] - }, - "Tuffy": { - "x": 335, - "y": 640, - "w": 63, - "ch": 13, - "s": ["400", "400i", "700", "700i"] - }, - "Tulpen One": { "x": 412, "y": 640, "w": 59, "ch": 13, "s": ["400"] }, - "Turret Road": { - "x": 485, - "y": 640, - "w": 142, - "ch": 13, - "s": ["200", "300", "400", "500", "700", "800"] - }, - "Twinkle Star": { "x": 641, "y": 640, "w": 137, "ch": 13, "s": ["400"] }, - "Ubuntu": { - "x": 792, - "y": 640, - "w": 90, - "ch": 13, - "s": ["300", "300i", "400", "400i", "500", "500i", "700", "700i"] - }, - "Ubuntu Condensed": { - "x": 896, - "y": 640, - "w": 177, - "ch": 13, - "s": ["400"] - }, - "Ubuntu Mono": { - "x": 0, - "y": 680, - "w": 156, - "ch": 13, - "s": ["400", "400i", "700", "700i"] - }, - "Ubuntu Sans": { - "x": 170, - "y": 680, - "w": 144, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Ubuntu Sans Mono": { - "x": 328, - "y": 680, - "w": 224, - "ch": 13, - "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] - }, - "Uchen": { "x": 566, "y": 680, "w": 75, "ch": 13, "s": ["400"] }, - "Ultra": { "x": 655, "y": 680, "w": 85, "ch": 13, "s": ["400"] }, - "Unbounded": { - "x": 754, - "y": 680, - "w": 174, - "ch": 13, - "s": ["200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Uncial Antiqua": { "x": 942, "y": 680, "w": 211, "ch": 13, "s": ["400"] }, - "Underdog": { "x": 0, "y": 720, "w": 113, "ch": 13, "s": ["400"] }, - "Unica One": { "x": 127, "y": 720, "w": 106, "ch": 13, "s": ["400"] }, - "UnifrakturMaguntia": { - "x": 247, - "y": 720, - "w": 204, - "ch": 13, - "s": ["400"] - }, - "Unkempt": { "x": 465, "y": 720, "w": 99, "ch": 13, "s": ["400", "700"] }, - "Unlock": { "x": 578, "y": 720, "w": 93, "ch": 13, "s": ["400"] }, - "Unna": { - "x": 685, - "y": 720, - "w": 60, - "ch": 13, - "s": ["400", "400i", "700", "700i"] - }, - "UoqMunThenKhung": { "x": 759, "y": 720, "w": 245, "ch": 13, "s": ["400"] }, - "Updock": { "x": 1018, "y": 720, "w": 58, "ch": 13, "s": ["400"] }, - "Urbanist": { - "x": 1090, - "y": 720, - "w": 97, - "ch": 13, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Vampiro One": { "x": 0, "y": 760, "w": 165, "ch": 13, "s": ["400"] }, - "Varela": { "x": 179, "y": 760, "w": 81, "ch": 13, "s": ["400"] }, - "Varela Round": { "x": 274, "y": 760, "w": 161, "ch": 13, "s": ["400"] }, - "Varta": { - "x": 449, - "y": 760, - "w": 61, - "ch": 13, - "s": ["300", "400", "500", "600", "700"] - }, - "Vast Shadow": { "x": 524, "y": 760, "w": 227, "ch": 13, "s": ["400"] }, - "Vazirmatn": { - "x": 765, - "y": 760, - "w": 117, - "ch": 13, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Vend Sans": { - "x": 896, - "y": 760, - "w": 121, - "ch": 13, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Vesper Libre": { - "x": 1031, - "y": 760, - "w": 136, - "ch": 13, - "s": ["400", "500", "700", "900"] - }, - "Viaoda Libre": { "x": 0, "y": 0, "w": 126, "ch": 14, "s": ["400"] }, - "Vibes": { "x": 140, "y": 0, "w": 53, "ch": 14, "s": ["400"] }, - "Vibur": { "x": 207, "y": 0, "w": 61, "ch": 14, "s": ["400"] }, - "Victor Mono": { - "x": 282, - "y": 0, - "w": 167, - "ch": 14, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Vidaloka": { "x": 463, "y": 0, "w": 99, "ch": 14, "s": ["400"] }, - "Viga": { "x": 576, "y": 0, "w": 55, "ch": 14, "s": ["400"] }, - "Vina Sans": { "x": 645, "y": 0, "w": 86, "ch": 14, "s": ["400"] }, - "Voces": { "x": 745, "y": 0, "w": 72, "ch": 14, "s": ["400"] }, - "Volkhov": { - "x": 831, - "y": 0, - "w": 104, - "ch": 14, - "s": ["400", "400i", "700", "700i"] - }, - "Vollkorn": { - "x": 949, - "y": 0, - "w": 99, - "ch": 14, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Vollkorn SC": { - "x": 0, - "y": 40, - "w": 166, - "ch": 14, - "s": ["400", "600", "700", "900"] - }, - "Voltaire": { "x": 180, "y": 40, "w": 73, "ch": 14, "s": ["400"] }, - "VT323": { "x": 267, "y": 40, "w": 56, "ch": 14, "s": ["400"] }, - "Vujahday Script": { "x": 337, "y": 40, "w": 151, "ch": 14, "s": ["400"] }, - "Waiting for the Sunrise": { - "x": 502, - "y": 40, - "w": 230, - "ch": 14, - "s": ["400"] - }, - "Wallpoet": { "x": 746, "y": 40, "w": 127, "ch": 14, "s": ["400"] }, - "Walter Turncoat": { "x": 887, "y": 40, "w": 206, "ch": 14, "s": ["400"] }, - "Warnes": { "x": 0, "y": 80, "w": 120, "ch": 14, "s": ["400"] }, - "Water Brush": { "x": 134, "y": 80, "w": 116, "ch": 14, "s": ["400"] }, - "Waterfall": { "x": 264, "y": 80, "w": 77, "ch": 14, "s": ["400"] }, - "Wavefont": { - "x": 355, - "y": 80, - "w": 28, - "ch": 14, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "WDXL Lubrifont JP N": { - "x": 397, - "y": 80, - "w": 185, - "ch": 14, - "s": ["400"] - }, - "WDXL Lubrifont SC": { - "x": 596, - "y": 80, - "w": 170, - "ch": 14, - "s": ["400"] - }, - "WDXL Lubrifont TC": { - "x": 780, - "y": 80, - "w": 168, - "ch": 14, - "s": ["400"] - }, - "Wellfleet": { "x": 962, "y": 80, "w": 117, "ch": 14, "s": ["400"] }, - "Wendy One": { "x": 0, "y": 120, "w": 142, "ch": 14, "s": ["400"] }, - "Whisper": { "x": 156, "y": 120, "w": 76, "ch": 14, "s": ["400"] }, - "WindSong": { "x": 246, "y": 120, "w": 133, "ch": 14, "s": ["400", "500"] }, - "Winky Rough": { - "x": 393, - "y": 120, - "w": 139, - "ch": 14, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Winky Sans": { - "x": 546, - "y": 120, - "w": 120, - "ch": 14, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Wire One": { "x": 680, "y": 120, "w": 57, "ch": 14, "s": ["400"] }, - "Wittgenstein": { - "x": 751, - "y": 120, - "w": 151, - "ch": 14, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Wix Madefor Display": { - "x": 916, - "y": 120, - "w": 238, - "ch": 14, - "s": ["400", "500", "600", "700", "800"] - }, - "Wix Madefor Text": { - "x": 0, - "y": 160, - "w": 202, - "ch": 14, - "s": [ - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i" - ] - }, - "Work Sans": { - "x": 216, - "y": 160, - "w": 132, - "ch": 14, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Workbench": { "x": 362, "y": 160, "w": 116, "ch": 14, "s": ["400"] }, - "Xanh Mono": { - "x": 492, - "y": 160, - "w": 116, - "ch": 14, - "s": ["400", "400i"] - }, - "Yaldevi": { - "x": 622, - "y": 160, - "w": 83, - "ch": 14, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Yanone Kaffeesatz": { - "x": 719, - "y": 160, - "w": 142, - "ch": 14, - "s": ["200", "300", "400", "500", "600", "700"] - }, - "Yantramanav": { - "x": 875, - "y": 160, - "w": 138, - "ch": 14, - "s": ["100", "300", "400", "500", "700", "900"] - }, - "Yarndings 12": { "x": 0, "y": 200, "w": 226, "ch": 14, "s": ["400"] }, - "Yarndings 12 Charted": { - "x": 240, - "y": 200, - "w": 365, - "ch": 14, - "s": ["400"] - }, - "Yarndings 20": { "x": 619, "y": 200, "w": 219, "ch": 14, "s": ["400"] }, - "Yarndings 20 Charted": { - "x": 0, - "y": 240, - "w": 357, - "ch": 14, - "s": ["400"] - }, - "Yatra One": { "x": 371, "y": 240, "w": 127, "ch": 14, "s": ["400"] }, - "Yellowtail": { "x": 512, "y": 240, "w": 93, "ch": 14, "s": ["400"] }, - "Yeon Sung": { "x": 619, "y": 240, "w": 107, "ch": 14, "s": ["400"] }, - "Yeseva One": { "x": 740, "y": 240, "w": 144, "ch": 14, "s": ["400"] }, - "Yesteryear": { "x": 898, "y": 240, "w": 94, "ch": 14, "s": ["400"] }, - "Yomogi": { "x": 1006, "y": 240, "w": 80, "ch": 14, "s": ["400"] }, - "Young Serif": { "x": 0, "y": 280, "w": 149, "ch": 14, "s": ["400"] }, - "Yrsa": { - "x": 163, - "y": 280, - "w": 49, - "ch": 14, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Ysabeau": { - "x": 226, - "y": 280, - "w": 88, - "ch": 14, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Ysabeau Infant": { - "x": 328, - "y": 280, - "w": 161, - "ch": 14, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Ysabeau Office": { - "x": 503, - "y": 280, - "w": 156, - "ch": 14, - "s": [ - "100", - "100i", - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Ysabeau SC": { - "x": 673, - "y": 280, - "w": 130, - "ch": 14, - "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] - }, - "Yuji Boku": { "x": 817, "y": 280, "w": 135, "ch": 14, "s": ["400"] }, - "Yuji Hentaigana Akari": { - "x": 0, - "y": 320, - "w": 335, - "ch": 14, - "s": ["400"] - }, - "Yuji Hentaigana Akebono": { - "x": 349, - "y": 320, - "w": 374, - "ch": 14, - "s": ["400"] - }, - "Yuji Mai": { "x": 737, "y": 320, "w": 117, "ch": 14, "s": ["400"] }, - "Yuji Syuku": { "x": 868, "y": 320, "w": 144, "ch": 14, "s": ["400"] }, - "Yusei Magic": { "x": 1026, "y": 320, "w": 138, "ch": 14, "s": ["400"] }, - "Zain": { - "x": 0, - "y": 360, - "w": 54, - "ch": 14, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Zalando Sans": { - "x": 68, - "y": 360, - "w": 161, - "ch": 14, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Zalando Sans Expanded": { - "x": 243, - "y": 360, - "w": 335, - "ch": 14, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "Zalando Sans SemiExpanded": { - "x": 592, - "y": 360, - "w": 361, - "ch": 14, - "s": [ - "200", - "200i", - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i", - "800", - "800i", - "900", - "900i" - ] - }, - "ZCOOL KuaiLe": { "x": 967, "y": 360, "w": 180, "ch": 14, "s": ["400"] }, - "ZCOOL QingKe HuangYou": { - "x": 0, - "y": 400, - "w": 222, - "ch": 14, - "s": ["400"] - }, - "ZCOOL XiaoWei": { "x": 236, "y": 400, "w": 173, "ch": 14, "s": ["400"] }, - "Zen Antique": { "x": 423, "y": 400, "w": 149, "ch": 14, "s": ["400"] }, - "Zen Antique Soft": { - "x": 586, - "y": 400, - "w": 204, - "ch": 14, - "s": ["400"] - }, - "Zen Dots": { "x": 804, "y": 400, "w": 147, "ch": 14, "s": ["400"] }, - "Zen Kaku Gothic Antique": { - "x": 0, - "y": 440, - "w": 267, - "ch": 14, - "s": ["300", "400", "500", "700", "900"] - }, - "Zen Kaku Gothic New": { - "x": 281, - "y": 440, - "w": 231, - "ch": 14, - "s": ["300", "400", "500", "700", "900"] - }, - "Zen Kurenaido": { "x": 526, "y": 440, "w": 145, "ch": 14, "s": ["400"] }, - "Zen Loop": { "x": 685, "y": 440, "w": 65, "ch": 14, "s": ["400", "400i"] }, - "Zen Maru Gothic": { - "x": 764, - "y": 440, - "w": 181, - "ch": 14, - "s": ["300", "400", "500", "700", "900"] - }, - "Zen Old Mincho": { - "x": 959, - "y": 440, - "w": 173, - "ch": 14, - "s": ["400", "500", "600", "700", "900"] - }, - "Zen Tokyo Zoo": { "x": 0, "y": 480, "w": 154, "ch": 14, "s": ["400"] }, - "Zeyada": { "x": 168, "y": 480, "w": 70, "ch": 14, "s": ["400"] }, - "Zhi Mang Xing": { "x": 252, "y": 480, "w": 121, "ch": 14, "s": ["400"] }, - "Zilla Slab": { - "x": 387, - "y": 480, - "w": 102, - "ch": 14, - "s": [ - "300", - "300i", - "400", - "400i", - "500", - "500i", - "600", - "600i", - "700", - "700i" - ] - }, - "Zilla Slab Highlight": { - "x": 503, - "y": 480, - "w": 207, - "ch": 14, - "s": ["400", "700"] - } - } -} +{ + "fonts": { + "42dot Sans": { + "x": 0, + "y": 0, + "w": 131, + "ch": 0, + "s": ["300", "400", "500", "600", "700", "800"] + }, + "ABeeZee": { "x": 145, "y": 0, "w": 106, "ch": 0, "s": ["400", "400i"] }, + "Abel": { "x": 265, "y": 0, "w": 47, "ch": 0, "s": ["400"] }, + "Abhaya Libre": { + "x": 326, + "y": 0, + "w": 139, + "ch": 0, + "s": ["400", "500", "600", "700", "800"] + }, + "Aboreto": { "x": 479, "y": 0, "w": 124, "ch": 0, "s": ["400"] }, + "Abril Fatface": { "x": 617, "y": 0, "w": 152, "ch": 0, "s": ["400"] }, + "Abyssinica SIL": { "x": 783, "y": 0, "w": 165, "ch": 0, "s": ["400"] }, + "Aclonica": { "x": 962, "y": 0, "w": 122, "ch": 0, "s": ["400"] }, + "Acme": { "x": 1098, "y": 0, "w": 63, "ch": 0, "s": ["400"] }, + "Actor": { "x": 0, "y": 40, "w": 64, "ch": 0, "s": ["400"] }, + "Adamina": { "x": 78, "y": 40, "w": 113, "ch": 0, "s": ["400"] }, + "ADLaM Display": { "x": 205, "y": 40, "w": 183, "ch": 0, "s": ["400"] }, + "Advent Pro": { + "x": 402, + "y": 40, + "w": 107, + "ch": 0, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Afacad": { + "x": 523, + "y": 40, + "w": 77, + "ch": 0, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Afacad Flux": { + "x": 614, + "y": 40, + "w": 118, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Agbalumo": { "x": 746, "y": 40, "w": 119, "ch": 0, "s": ["400"] }, + "Agdasima": { "x": 879, "y": 40, "w": 81, "ch": 0, "s": ["400", "700"] }, + "Agu Display": { "x": 974, "y": 40, "w": 140, "ch": 0, "s": ["400"] }, + "Aguafina Script": { "x": 0, "y": 80, "w": 120, "ch": 0, "s": ["400"] }, + "Akatab": { + "x": 134, + "y": 80, + "w": 81, + "ch": 0, + "s": ["400", "500", "600", "700", "800", "900"] + }, + "Akaya Kanadaka": { "x": 229, "y": 80, "w": 176, "ch": 0, "s": ["400"] }, + "Akaya Telivigala": { "x": 419, "y": 80, "w": 165, "ch": 0, "s": ["400"] }, + "Akronim": { "x": 598, "y": 80, "w": 79, "ch": 0, "s": ["400"] }, + "Akshar": { + "x": 691, + "y": 80, + "w": 71, + "ch": 0, + "s": ["300", "400", "500", "600", "700"] + }, + "Aladin": { "x": 776, "y": 80, "w": 61, "ch": 0, "s": ["400"] }, + "Alan Sans": { + "x": 851, + "y": 80, + "w": 116, + "ch": 0, + "s": ["300", "400", "500", "600", "700", "800", "900"] + }, + "Alata": { "x": 981, "y": 80, "w": 68, "ch": 0, "s": ["400"] }, + "Alatsi": { "x": 1063, "y": 80, "w": 66, "ch": 0, "s": ["400"] }, + "Albert Sans": { + "x": 0, + "y": 120, + "w": 133, + "ch": 0, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Aldrich": { "x": 147, "y": 120, "w": 94, "ch": 0, "s": ["400"] }, + "Alef": { "x": 255, "y": 120, "w": 54, "ch": 0, "s": ["400", "700"] }, + "Alegreya": { + "x": 323, + "y": 120, + "w": 92, + "ch": 0, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Alegreya Sans": { + "x": 429, + "y": 120, + "w": 137, + "ch": 0, + "s": [ + "100", + "100i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Alegreya Sans SC": { + "x": 580, + "y": 120, + "w": 179, + "ch": 0, + "s": [ + "100", + "100i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Alegreya SC": { + "x": 773, + "y": 120, + "w": 137, + "ch": 0, + "s": [ + "400", + "400i", + "500", + "500i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Aleo": { + "x": 924, + "y": 120, + "w": 59, + "ch": 0, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Alex Brush": { "x": 997, "y": 120, "w": 117, "ch": 0, "s": ["400"] }, + "Alexandria": { + "x": 0, + "y": 160, + "w": 136, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Alfa Slab One": { "x": 150, "y": 160, "w": 180, "ch": 0, "s": ["400"] }, + "Alice": { "x": 344, "y": 160, "w": 60, "ch": 0, "s": ["400"] }, + "Alike": { "x": 418, "y": 160, "w": 62, "ch": 0, "s": ["400"] }, + "Alike Angular": { "x": 494, "y": 160, "w": 155, "ch": 0, "s": ["400"] }, + "Alkalami": { "x": 663, "y": 160, "w": 109, "ch": 0, "s": ["400"] }, + "Alkatra": { + "x": 786, + "y": 160, + "w": 84, + "ch": 0, + "s": ["400", "500", "600", "700"] + }, + "Allan": { "x": 884, "y": 160, "w": 48, "ch": 0, "s": ["400", "700"] }, + "Allerta": { "x": 946, "y": 160, "w": 93, "ch": 0, "s": ["400"] }, + "Allerta Stencil": { "x": 0, "y": 200, "w": 182, "ch": 0, "s": ["400"] }, + "Allison": { "x": 196, "y": 200, "w": 56, "ch": 0, "s": ["400"] }, + "Allura": { "x": 266, "y": 200, "w": 69, "ch": 0, "s": ["400"] }, + "Almarai": { + "x": 349, + "y": 200, + "w": 88, + "ch": 0, + "s": ["300", "400", "700", "800"] + }, + "Almendra": { + "x": 451, + "y": 200, + "w": 107, + "ch": 0, + "s": ["400", "400i", "700", "700i"] + }, + "Almendra Display": { "x": 572, "y": 200, "w": 192, "ch": 0, "s": ["400"] }, + "Almendra SC": { "x": 778, "y": 200, "w": 142, "ch": 0, "s": ["400"] }, + "Alumni Sans": { + "x": 934, + "y": 200, + "w": 96, + "ch": 0, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Alumni Sans Collegiate One": { + "x": 0, + "y": 240, + "w": 201, + "ch": 0, + "s": ["400", "400i"] + }, + "Alumni Sans Inline One": { + "x": 215, + "y": 240, + "w": 180, + "ch": 0, + "s": ["400", "400i"] + }, + "Alumni Sans Pinstripe": { + "x": 409, + "y": 240, + "w": 159, + "ch": 0, + "s": ["400", "400i"] + }, + "Alumni Sans SC": { + "x": 582, + "y": 240, + "w": 125, + "ch": 0, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Amarante": { "x": 721, "y": 240, "w": 108, "ch": 0, "s": ["400"] }, + "Amaranth": { + "x": 843, + "y": 240, + "w": 110, + "ch": 0, + "s": ["400", "400i", "700", "700i"] + }, + "Amarna": { + "x": 967, + "y": 240, + "w": 92, + "ch": 0, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Amatic SC": { "x": 1073, "y": 240, "w": 77, "ch": 0, "s": ["400", "700"] }, + "Amethysta": { "x": 0, "y": 280, "w": 133, "ch": 0, "s": ["400"] }, + "Amiko": { + "x": 147, + "y": 280, + "w": 84, + "ch": 0, + "s": ["400", "600", "700"] + }, + "Amiri": { + "x": 245, + "y": 280, + "w": 63, + "ch": 0, + "s": ["400", "400i", "700", "700i"] + }, + "Amiri Quran": { "x": 322, "y": 280, "w": 131, "ch": 0, "s": ["400"] }, + "Amita": { "x": 467, "y": 280, "w": 73, "ch": 0, "s": ["400", "700"] }, + "Anaheim": { + "x": 554, + "y": 280, + "w": 93, + "ch": 0, + "s": ["400", "500", "600", "700", "800"] + }, + "Ancizar Sans": { + "x": 661, + "y": 280, + "w": 126, + "ch": 0, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Ancizar Serif": { + "x": 801, + "y": 280, + "w": 136, + "ch": 0, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Andada Pro": { + "x": 951, + "y": 280, + "w": 137, + "ch": 0, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Andika": { + "x": 1102, + "y": 280, + "w": 85, + "ch": 0, + "s": ["400", "400i", "700", "700i"] + }, + "Anek Bangla": { + "x": 0, + "y": 320, + "w": 131, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Anek Devanagari": { + "x": 145, + "y": 320, + "w": 176, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Anek Gujarati": { + "x": 335, + "y": 320, + "w": 143, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Anek Gurmukhi": { + "x": 492, + "y": 320, + "w": 164, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Anek Kannada": { + "x": 670, + "y": 320, + "w": 152, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Anek Latin": { + "x": 836, + "y": 320, + "w": 115, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Anek Malayalam": { + "x": 965, + "y": 320, + "w": 171, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Anek Odia": { + "x": 0, + "y": 360, + "w": 108, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Anek Tamil": { + "x": 122, + "y": 360, + "w": 119, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Anek Telugu": { + "x": 255, + "y": 360, + "w": 131, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Angkor": { "x": 400, "y": 360, "w": 111, "ch": 0, "s": ["400"] }, + "Annapurna SIL": { + "x": 525, + "y": 360, + "w": 151, + "ch": 0, + "s": ["400", "700"] + }, + "Annie Use Your Telescope": { + "x": 690, + "y": 360, + "w": 215, + "ch": 0, + "s": ["400"] + }, + "Anonymous Pro": { + "x": 919, + "y": 360, + "w": 179, + "ch": 0, + "s": ["400", "400i", "700", "700i"] + }, + "Anta": { "x": 1112, "y": 360, "w": 62, "ch": 0, "s": ["400"] }, + "Antic": { "x": 0, "y": 400, "w": 60, "ch": 0, "s": ["400"] }, + "Antic Didone": { "x": 74, "y": 400, "w": 145, "ch": 0, "s": ["400"] }, + "Antic Slab": { "x": 233, "y": 400, "w": 114, "ch": 0, "s": ["400"] }, + "Anton": { "x": 361, "y": 400, "w": 63, "ch": 0, "s": ["400"] }, + "Anton SC": { "x": 438, "y": 400, "w": 93, "ch": 0, "s": ["400"] }, + "Antonio": { + "x": 545, + "y": 400, + "w": 76, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "Anuphan": { + "x": 635, + "y": 400, + "w": 105, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "Anybody": { + "x": 754, + "y": 400, + "w": 107, + "ch": 0, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Aoboshi One": { "x": 875, "y": 400, "w": 158, "ch": 0, "s": ["400"] }, + "AR One Sans": { + "x": 1047, + "y": 400, + "w": 152, + "ch": 0, + "s": ["400", "500", "600", "700"] + }, + "Arapey": { "x": 0, "y": 440, "w": 75, "ch": 0, "s": ["400", "400i"] }, + "Arbutus": { "x": 89, "y": 440, "w": 128, "ch": 0, "s": ["400"] }, + "Arbutus Slab": { "x": 231, "y": 440, "w": 159, "ch": 0, "s": ["400"] }, + "Architects Daughter": { + "x": 404, + "y": 440, + "w": 237, + "ch": 0, + "s": ["400"] + }, + "Archivo": { + "x": 655, + "y": 440, + "w": 90, + "ch": 0, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Archivo Black": { "x": 759, "y": 440, "w": 190, "ch": 0, "s": ["400"] }, + "Archivo Narrow": { + "x": 963, + "y": 440, + "w": 144, + "ch": 0, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Are You Serious": { "x": 0, "y": 480, "w": 142, "ch": 0, "s": ["400"] }, + "Aref Ruqaa": { + "x": 156, + "y": 480, + "w": 130, + "ch": 0, + "s": ["400", "700"] + }, + "Aref Ruqaa Ink": { + "x": 300, + "y": 480, + "w": 172, + "ch": 0, + "s": ["400", "700"] + }, + "Arima": { + "x": 486, + "y": 480, + "w": 73, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "Arima Madurai": { + "x": 573, + "y": 480, + "w": 170, + "ch": 0, + "s": ["100", "200", "300", "400", "500", "700", "800", "900"] + }, + "Arimo": { + "x": 757, + "y": 480, + "w": 71, + "ch": 0, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Arizonia": { "x": 842, "y": 480, "w": 95, "ch": 0, "s": ["400"] }, + "Armata": { "x": 951, "y": 480, "w": 100, "ch": 0, "s": ["400"] }, + "Arsenal": { + "x": 1065, + "y": 480, + "w": 80, + "ch": 0, + "s": ["400", "400i", "700", "700i"] + }, + "Arsenal SC": { + "x": 0, + "y": 520, + "w": 118, + "ch": 0, + "s": ["400", "400i", "700", "700i"] + }, + "Artifika": { "x": 132, "y": 520, "w": 105, "ch": 0, "s": ["400"] }, + "Arvo": { + "x": 251, + "y": 520, + "w": 65, + "ch": 0, + "s": ["400", "400i", "700", "700i"] + }, + "Arya": { "x": 330, "y": 520, "w": 53, "ch": 0, "s": ["400", "700"] }, + "Asap": { + "x": 397, + "y": 520, + "w": 60, + "ch": 0, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Asap Condensed": { + "x": 471, + "y": 520, + "w": 153, + "ch": 0, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Asar": { "x": 638, "y": 520, "w": 53, "ch": 0, "s": ["400"] }, + "Asimovian": { "x": 705, "y": 520, "w": 117, "ch": 0, "s": ["400"] }, + "Asset": { "x": 836, "y": 520, "w": 154, "ch": 0, "s": ["400"] }, + "Assistant": { + "x": 1004, + "y": 520, + "w": 98, + "ch": 0, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Asta Sans": { + "x": 0, + "y": 560, + "w": 115, + "ch": 0, + "s": ["300", "400", "500", "600", "700", "800"] + }, + "Astloch": { "x": 129, "y": 560, "w": 72, "ch": 0, "s": ["400", "700"] }, + "Asul": { "x": 215, "y": 560, "w": 54, "ch": 0, "s": ["400", "700"] }, + "Athiti": { + "x": 283, + "y": 560, + "w": 62, + "ch": 0, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Atkinson Hyperlegible": { + "x": 359, + "y": 560, + "w": 239, + "ch": 0, + "s": ["400", "400i", "700", "700i"] + }, + "Atkinson Hyperlegible Mono": { + "x": 612, + "y": 560, + "w": 403, + "ch": 0, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Atkinson Hyperlegible Next": { + "x": 0, + "y": 600, + "w": 296, + "ch": 0, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Atma": { + "x": 310, + "y": 600, + "w": 59, + "ch": 0, + "s": ["300", "400", "500", "600", "700"] + }, + "Atomic Age": { "x": 383, "y": 600, "w": 144, "ch": 0, "s": ["400"] }, + "Aubrey": { "x": 541, "y": 600, "w": 67, "ch": 0, "s": ["400"] }, + "Audiowide": { "x": 622, "y": 600, "w": 145, "ch": 0, "s": ["400"] }, + "Autour One": { "x": 781, "y": 600, "w": 168, "ch": 0, "s": ["400"] }, + "Average": { "x": 963, "y": 600, "w": 88, "ch": 0, "s": ["400"] }, + "Average Sans": { "x": 0, "y": 640, "w": 138, "ch": 0, "s": ["400"] }, + "Averia Gruesa Libre": { + "x": 152, + "y": 640, + "w": 217, + "ch": 0, + "s": ["400"] + }, + "Averia Libre": { + "x": 383, + "y": 640, + "w": 138, + "ch": 0, + "s": ["300", "300i", "400", "400i", "700", "700i"] + }, + "Averia Sans Libre": { + "x": 535, + "y": 640, + "w": 190, + "ch": 0, + "s": ["300", "300i", "400", "400i", "700", "700i"] + }, + "Averia Serif Libre": { + "x": 739, + "y": 640, + "w": 202, + "ch": 0, + "s": ["300", "300i", "400", "400i", "700", "700i"] + }, + "Azeret Mono": { + "x": 955, + "y": 640, + "w": 180, + "ch": 0, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "B612": { + "x": 0, + "y": 680, + "w": 71, + "ch": 0, + "s": ["400", "400i", "700", "700i"] + }, + "B612 Mono": { + "x": 85, + "y": 680, + "w": 149, + "ch": 0, + "s": ["400", "400i", "700", "700i"] + }, + "Babylonica": { "x": 248, "y": 680, "w": 90, "ch": 0, "s": ["400"] }, + "Bacasime Antique": { "x": 352, "y": 680, "w": 182, "ch": 0, "s": ["400"] }, + "Bad Script": { "x": 548, "y": 680, "w": 103, "ch": 0, "s": ["400"] }, + "Badeen Display": { "x": 665, "y": 680, "w": 194, "ch": 0, "s": ["400"] }, + "Bagel Fat One": { "x": 873, "y": 680, "w": 158, "ch": 0, "s": ["400"] }, + "Bahiana": { "x": 1045, "y": 680, "w": 58, "ch": 0, "s": ["400"] }, + "Bahianita": { "x": 1117, "y": 680, "w": 63, "ch": 0, "s": ["400"] }, + "Bai Jamjuree": { + "x": 0, + "y": 720, + "w": 152, + "ch": 0, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Bakbak One": { "x": 166, "y": 720, "w": 141, "ch": 0, "s": ["400"] }, + "Ballet": { "x": 321, "y": 720, "w": 65, "ch": 0, "s": ["400"] }, + "Baloo 2": { + "x": 400, + "y": 720, + "w": 84, + "ch": 0, + "s": ["400", "500", "600", "700", "800"] + }, + "Baloo Bhai 2": { + "x": 498, + "y": 720, + "w": 134, + "ch": 0, + "s": ["400", "500", "600", "700", "800"] + }, + "Baloo Bhaijaan 2": { + "x": 646, + "y": 720, + "w": 177, + "ch": 0, + "s": ["400", "500", "600", "700", "800"] + }, + "Baloo Bhaina 2": { + "x": 837, + "y": 720, + "w": 160, + "ch": 0, + "s": ["400", "500", "600", "700", "800"] + }, + "Baloo Chettan 2": { + "x": 1011, + "y": 720, + "w": 172, + "ch": 0, + "s": ["400", "500", "600", "700", "800"] + }, + "Baloo Da 2": { + "x": 0, + "y": 760, + "w": 117, + "ch": 0, + "s": ["400", "500", "600", "700", "800"] + }, + "Baloo Paaji 2": { + "x": 131, + "y": 760, + "w": 138, + "ch": 0, + "s": ["400", "500", "600", "700", "800"] + }, + "Baloo Tamma 2": { + "x": 283, + "y": 760, + "w": 169, + "ch": 0, + "s": ["400", "500", "600", "700", "800"] + }, + "Baloo Tammudu 2": { + "x": 466, + "y": 760, + "w": 191, + "ch": 0, + "s": ["400", "500", "600", "700", "800"] + }, + "Baloo Thambi 2": { + "x": 671, + "y": 760, + "w": 165, + "ch": 0, + "s": ["400", "500", "600", "700", "800"] + }, + "Balsamiq Sans": { + "x": 850, + "y": 760, + "w": 168, + "ch": 0, + "s": ["400", "400i", "700", "700i"] + }, + "Balthazar": { "x": 1032, "y": 760, "w": 96, "ch": 0, "s": ["400"] }, + "Bangers": { "x": 0, "y": 0, "w": 81, "ch": 1, "s": ["400"] }, + "Barlow": { + "x": 95, + "y": 0, + "w": 80, + "ch": 1, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Barlow Condensed": { + "x": 189, + "y": 0, + "w": 158, + "ch": 1, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Barlow Semi Condensed": { + "x": 361, + "y": 0, + "w": 231, + "ch": 1, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Barriecito": { "x": 606, "y": 0, "w": 106, "ch": 1, "s": ["400"] }, + "Barrio": { "x": 726, "y": 0, "w": 80, "ch": 1, "s": ["400"] }, + "Basic": { "x": 820, "y": 0, "w": 61, "ch": 1, "s": ["400"] }, + "Baskervville": { + "x": 895, + "y": 0, + "w": 136, + "ch": 1, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Baskervville SC": { + "x": 0, + "y": 40, + "w": 187, + "ch": 1, + "s": ["400", "500", "600", "700"] + }, + "Battambang": { + "x": 201, + "y": 40, + "w": 147, + "ch": 1, + "s": ["100", "300", "400", "700", "900"] + }, + "Baumans": { "x": 362, "y": 40, "w": 101, "ch": 1, "s": ["400"] }, + "Bayon": { "x": 477, "y": 40, "w": 61, "ch": 1, "s": ["400"] }, + "BBH Bartle": { "x": 552, "y": 40, "w": 298, "ch": 1, "s": ["400"] }, + "BBH Bogle": { "x": 864, "y": 40, "w": 98, "ch": 1, "s": ["400"] }, + "BBH Hegarty": { "x": 976, "y": 40, "w": 182, "ch": 1, "s": ["400"] }, + "BBH Sans Bartle": { "x": 0, "y": 80, "w": 427, "ch": 1, "s": ["400"] }, + "BBH Sans Bogle": { "x": 441, "y": 80, "w": 142, "ch": 1, "s": ["400"] }, + "BBH Sans Hegarty": { "x": 597, "y": 80, "w": 254, "ch": 1, "s": ["400"] }, + "Be Vietnam Pro": { + "x": 865, + "y": 80, + "w": 188, + "ch": 1, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Beau Rivage": { "x": 1067, "y": 80, "w": 108, "ch": 1, "s": ["400"] }, + "Bebas Neue": { "x": 0, "y": 120, "w": 96, "ch": 1, "s": ["400"] }, + "Beiruti": { + "x": 110, + "y": 120, + "w": 66, + "ch": 1, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Belanosima": { + "x": 190, + "y": 120, + "w": 124, + "ch": 1, + "s": ["400", "600", "700"] + }, + "Belgrano": { "x": 328, "y": 120, "w": 115, "ch": 1, "s": ["400"] }, + "Bellefair": { "x": 457, "y": 120, "w": 81, "ch": 1, "s": ["400"] }, + "Belleza": { "x": 552, "y": 120, "w": 79, "ch": 1, "s": ["400"] }, + "Bellota": { + "x": 645, + "y": 120, + "w": 84, + "ch": 1, + "s": ["300", "300i", "400", "400i", "700", "700i"] + }, + "Bellota Text": { + "x": 743, + "y": 120, + "w": 129, + "ch": 1, + "s": ["300", "300i", "400", "400i", "700", "700i"] + }, + "BenchNine": { + "x": 886, + "y": 120, + "w": 82, + "ch": 1, + "s": ["300", "400", "700"] + }, + "Benne": { "x": 982, "y": 120, "w": 66, "ch": 1, "s": ["400"] }, + "Bentham": { "x": 1062, "y": 120, "w": 98, "ch": 1, "s": ["400"] }, + "Berkshire Swash": { "x": 0, "y": 160, "w": 178, "ch": 1, "s": ["400"] }, + "Besley": { + "x": 192, + "y": 160, + "w": 86, + "ch": 1, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Beth Ellen": { "x": 292, "y": 160, "w": 138, "ch": 1, "s": ["400"] }, + "Bevan": { "x": 444, "y": 160, "w": 94, "ch": 1, "s": ["400", "400i"] }, + "BhuTuka Expanded One": { + "x": 552, + "y": 160, + "w": 412, + "ch": 1, + "s": ["400"] + }, + "Big Shoulders": { + "x": 978, + "y": 160, + "w": 117, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Big Shoulders Display": { + "x": 0, + "y": 200, + "w": 158, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Big Shoulders Inline": { + "x": 172, + "y": 200, + "w": 167, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Big Shoulders Inline Display": { + "x": 353, + "y": 200, + "w": 203, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Big Shoulders Inline Text": { + "x": 570, + "y": 200, + "w": 201, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Big Shoulders Stencil": { + "x": 785, + "y": 200, + "w": 177, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Big Shoulders Stencil Display": { + "x": 976, + "y": 200, + "w": 211, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Big Shoulders Stencil Text": { + "x": 0, + "y": 240, + "w": 210, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Big Shoulders Text": { + "x": 224, + "y": 240, + "w": 150, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bigelow Rules": { "x": 388, "y": 240, "w": 86, "ch": 1, "s": ["400"] }, + "Bigshot One": { "x": 488, "y": 240, "w": 138, "ch": 1, "s": ["400"] }, + "Bilbo": { "x": 640, "y": 240, "w": 45, "ch": 1, "s": ["400"] }, + "Bilbo Swash Caps": { "x": 699, "y": 240, "w": 144, "ch": 1, "s": ["400"] }, + "BioRhyme": { + "x": 857, + "y": 240, + "w": 139, + "ch": 1, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "BioRhyme Expanded": { + "x": 0, + "y": 280, + "w": 455, + "ch": 1, + "s": ["200", "300", "400", "700", "800"] + }, + "Birthstone": { "x": 469, "y": 280, "w": 79, "ch": 1, "s": ["400"] }, + "Birthstone Bounce": { + "x": 562, + "y": 280, + "w": 167, + "ch": 1, + "s": ["400", "500"] + }, + "Biryani": { + "x": 743, + "y": 280, + "w": 90, + "ch": 1, + "s": ["200", "300", "400", "600", "700", "800", "900"] + }, + "Bitcount": { + "x": 847, + "y": 280, + "w": 124, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bitcount Grid Double": { + "x": 0, + "y": 320, + "w": 296, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bitcount Grid Double Ink": { + "x": 310, + "y": 320, + "w": 354, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bitcount Grid Single": { + "x": 678, + "y": 320, + "w": 296, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bitcount Grid Single Ink": { + "x": 0, + "y": 360, + "w": 354, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bitcount Ink": { + "x": 368, + "y": 360, + "w": 181, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bitcount Prop Double": { + "x": 563, + "y": 360, + "w": 265, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bitcount Prop Double Ink": { + "x": 842, + "y": 360, + "w": 308, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bitcount Prop Single": { + "x": 0, + "y": 400, + "w": 244, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bitcount Prop Single Ink": { + "x": 258, + "y": 400, + "w": 282, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bitcount Single": { + "x": 554, + "y": 400, + "w": 224, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bitcount Single Ink": { + "x": 792, + "y": 400, + "w": 282, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bitter": { + "x": 1088, + "y": 400, + "w": 71, + "ch": 1, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "BIZ UDGothic": { + "x": 0, + "y": 440, + "w": 152, + "ch": 1, + "s": ["400", "700"] + }, + "BIZ UDMincho": { + "x": 166, + "y": 440, + "w": 152, + "ch": 1, + "s": ["400", "700"] + }, + "BIZ UDPGothic": { + "x": 332, + "y": 440, + "w": 209, + "ch": 1, + "s": ["400", "700"] + }, + "BIZ UDPMincho": { + "x": 555, + "y": 440, + "w": 208, + "ch": 1, + "s": ["400", "700"] + }, + "Black And White Picture": { + "x": 777, + "y": 440, + "w": 218, + "ch": 1, + "s": ["400"] + }, + "Black Han Sans": { "x": 0, "y": 480, "w": 212, "ch": 1, "s": ["400"] }, + "Black Ops One": { "x": 226, "y": 480, "w": 191, "ch": 1, "s": ["400"] }, + "Blaka": { "x": 431, "y": 480, "w": 57, "ch": 1, "s": ["400"] }, + "Blaka Hollow": { "x": 502, "y": 480, "w": 119, "ch": 1, "s": ["400"] }, + "Blaka Ink": { "x": 635, "y": 480, "w": 87, "ch": 1, "s": ["400"] }, + "Blinker": { + "x": 736, + "y": 480, + "w": 77, + "ch": 1, + "s": ["100", "200", "300", "400", "600", "700", "800", "900"] + }, + "Bodoni Moda": { + "x": 827, + "y": 480, + "w": 155, + "ch": 1, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Bodoni Moda SC": { + "x": 996, + "y": 480, + "w": 200, + "ch": 1, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Bokor": { "x": 0, "y": 520, "w": 58, "ch": 1, "s": ["400"] }, + "Boldonse": { "x": 72, "y": 520, "w": 147, "ch": 1, "s": ["400"] }, + "Bona Nova": { + "x": 233, + "y": 520, + "w": 124, + "ch": 1, + "s": ["400", "400i", "700", "700i"] + }, + "Bona Nova SC": { + "x": 371, + "y": 520, + "w": 170, + "ch": 1, + "s": ["400", "400i", "700", "700i"] + }, + "Bonbon": { "x": 555, "y": 520, "w": 104, "ch": 1, "s": ["400"] }, + "Bonheur Royale": { "x": 673, "y": 520, "w": 124, "ch": 1, "s": ["400"] }, + "Boogaloo": { "x": 811, "y": 520, "w": 81, "ch": 1, "s": ["400"] }, + "Borel": { "x": 906, "y": 520, "w": 69, "ch": 1, "s": ["400"] }, + "Bowlby One": { "x": 989, "y": 520, "w": 174, "ch": 1, "s": ["400"] }, + "Bowlby One SC": { "x": 0, "y": 560, "w": 213, "ch": 1, "s": ["400"] }, + "Braah One": { "x": 227, "y": 560, "w": 124, "ch": 1, "s": ["400"] }, + "Brawler": { "x": 365, "y": 560, "w": 95, "ch": 1, "s": ["400", "700"] }, + "Bree Serif": { "x": 474, "y": 560, "w": 113, "ch": 1, "s": ["400"] }, + "Bricolage Grotesque": { + "x": 601, + "y": 560, + "w": 238, + "ch": 1, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Briem Hand": { + "x": 853, + "y": 560, + "w": 146, + "ch": 1, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Bruno Ace": { "x": 1013, "y": 560, "w": 168, "ch": 1, "s": ["400"] }, + "Bruno Ace SC": { "x": 0, "y": 600, "w": 219, "ch": 1, "s": ["400"] }, + "Brygada 1918": { + "x": 233, + "y": 600, + "w": 157, + "ch": 1, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Bubblegum Sans": { "x": 404, "y": 600, "w": 155, "ch": 1, "s": ["400"] }, + "Bubbler One": { "x": 573, "y": 600, "w": 117, "ch": 1, "s": ["400"] }, + "Buenard": { + "x": 704, + "y": 600, + "w": 93, + "ch": 1, + "s": ["400", "500", "600", "700"] + }, + "Bungee": { "x": 811, "y": 600, "w": 110, "ch": 1, "s": ["400"] }, + "Bungee Hairline": { "x": 935, "y": 600, "w": 249, "ch": 1, "s": ["400"] }, + "Bungee Inline": { "x": 0, "y": 640, "w": 213, "ch": 1, "s": ["400"] }, + "Bungee Outline": { "x": 227, "y": 640, "w": 232, "ch": 1, "s": ["400"] }, + "Bungee Shade": { "x": 473, "y": 640, "w": 229, "ch": 1, "s": ["400"] }, + "Bungee Spice": { "x": 716, "y": 640, "w": 193, "ch": 1, "s": ["400"] }, + "Bungee Tint": { "x": 923, "y": 640, "w": 179, "ch": 1, "s": ["400"] }, + "Butcherman": { "x": 0, "y": 680, "w": 161, "ch": 1, "s": ["400"] }, + "Butterfly Kids": { "x": 175, "y": 680, "w": 116, "ch": 1, "s": ["400"] }, + "Bytesized": { "x": 305, "y": 680, "w": 116, "ch": 1, "s": ["400"] }, + "Cabin": { + "x": 435, + "y": 680, + "w": 67, + "ch": 1, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Cabin Condensed": { + "x": 516, + "y": 680, + "w": 163, + "ch": 1, + "s": ["400", "500", "600", "700"] + }, + "Cabin Sketch": { + "x": 693, + "y": 680, + "w": 141, + "ch": 1, + "s": ["400", "700"] + }, + "Cactus Classical Serif": { + "x": 848, + "y": 680, + "w": 235, + "ch": 1, + "s": ["400"] + }, + "Caesar Dressing": { "x": 0, "y": 720, "w": 164, "ch": 1, "s": ["400"] }, + "Cagliostro": { "x": 178, "y": 720, "w": 115, "ch": 1, "s": ["400"] }, + "Cairo": { + "x": 307, + "y": 720, + "w": 60, + "ch": 1, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Cairo Play": { + "x": 381, + "y": 720, + "w": 108, + "ch": 1, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Cal Sans": { "x": 503, "y": 720, "w": 99, "ch": 1, "s": ["400"] }, + "Caladea": { + "x": 616, + "y": 720, + "w": 84, + "ch": 1, + "s": ["400", "400i", "700", "700i"] + }, + "Calistoga": { "x": 714, "y": 720, "w": 109, "ch": 1, "s": ["400"] }, + "Calligraffitti": { "x": 837, "y": 720, "w": 124, "ch": 1, "s": ["400"] }, + "Cambay": { + "x": 975, + "y": 720, + "w": 92, + "ch": 1, + "s": ["400", "400i", "700", "700i"] + }, + "Cambo": { "x": 1081, "y": 720, "w": 82, "ch": 1, "s": ["400"] }, + "Candal": { "x": 0, "y": 760, "w": 102, "ch": 1, "s": ["400"] }, + "Cantarell": { + "x": 116, + "y": 760, + "w": 109, + "ch": 1, + "s": ["400", "400i", "700", "700i"] + }, + "Cantata One": { "x": 239, "y": 760, "w": 163, "ch": 1, "s": ["400"] }, + "Cantora One": { "x": 416, "y": 760, "w": 134, "ch": 1, "s": ["400"] }, + "Caprasimo": { "x": 564, "y": 760, "w": 139, "ch": 1, "s": ["400"] }, + "Capriola": { "x": 717, "y": 760, "w": 110, "ch": 1, "s": ["400"] }, + "Caramel": { "x": 841, "y": 760, "w": 66, "ch": 1, "s": ["400"] }, + "Carattere": { "x": 921, "y": 760, "w": 79, "ch": 1, "s": ["400"] }, + "Cardo": { + "x": 1014, + "y": 760, + "w": 72, + "ch": 1, + "s": ["400", "400i", "700", "700i"] + }, + "Carlito": { + "x": 1100, + "y": 760, + "w": 73, + "ch": 1, + "s": ["400", "400i", "700", "700i"] + }, + "Carme": { "x": 0, "y": 0, "w": 79, "ch": 2, "s": ["400"] }, + "Carrois Gothic": { "x": 93, "y": 0, "w": 155, "ch": 2, "s": ["400"] }, + "Carrois Gothic SC": { "x": 262, "y": 0, "w": 211, "ch": 2, "s": ["400"] }, + "Carter One": { "x": 487, "y": 0, "w": 136, "ch": 2, "s": ["400"] }, + "Cascadia Code": { + "x": 637, + "y": 0, + "w": 191, + "ch": 2, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Cascadia Mono": { + "x": 842, + "y": 0, + "w": 191, + "ch": 2, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Castoro": { "x": 1047, "y": 0, "w": 93, "ch": 2, "s": ["400", "400i"] }, + "Castoro Titling": { "x": 0, "y": 40, "w": 236, "ch": 2, "s": ["400"] }, + "Catamaran": { + "x": 250, + "y": 40, + "w": 116, + "ch": 2, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Caudex": { + "x": 380, + "y": 40, + "w": 92, + "ch": 2, + "s": ["400", "400i", "700", "700i"] + }, + "Cause": { + "x": 486, + "y": 40, + "w": 72, + "ch": 2, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Caveat": { + "x": 572, + "y": 40, + "w": 63, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Caveat Brush": { "x": 649, "y": 40, "w": 120, "ch": 2, "s": ["400"] }, + "Cedarville Cursive": { + "x": 783, + "y": 40, + "w": 203, + "ch": 2, + "s": ["400"] + }, + "Ceviche One": { "x": 1000, "y": 40, "w": 111, "ch": 2, "s": ["400"] }, + "Chakra Petch": { + "x": 0, + "y": 80, + "w": 154, + "ch": 2, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Changa": { + "x": 168, + "y": 80, + "w": 86, + "ch": 2, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Changa One": { + "x": 268, + "y": 80, + "w": 136, + "ch": 2, + "s": ["400", "400i"] + }, + "Chango": { "x": 418, "y": 80, "w": 128, "ch": 2, "s": ["400"] }, + "Charis SIL": { + "x": 560, + "y": 80, + "w": 116, + "ch": 2, + "s": ["400", "400i", "700", "700i"] + }, + "Charm": { "x": 690, "y": 80, "w": 69, "ch": 2, "s": ["400", "700"] }, + "Charmonman": { "x": 773, "y": 80, "w": 136, "ch": 2, "s": ["400", "700"] }, + "Chathura": { + "x": 923, + "y": 80, + "w": 55, + "ch": 2, + "s": ["100", "300", "400", "700", "800"] + }, + "Chau Philomene One": { + "x": 992, + "y": 80, + "w": 200, + "ch": 2, + "s": ["400", "400i"] + }, + "Chela One": { "x": 0, "y": 120, "w": 97, "ch": 2, "s": ["400"] }, + "Chelsea Market": { "x": 111, "y": 120, "w": 200, "ch": 2, "s": ["400"] }, + "Cherish": { "x": 325, "y": 120, "w": 60, "ch": 2, "s": ["400"] }, + "Cherry Bomb One": { "x": 399, "y": 120, "w": 200, "ch": 2, "s": ["400"] }, + "Cherry Cream Soda": { + "x": 613, + "y": 120, + "w": 274, + "ch": 2, + "s": ["400"] + }, + "Cherry Swash": { + "x": 901, + "y": 120, + "w": 159, + "ch": 2, + "s": ["400", "700"] + }, + "Chewy": { "x": 1074, "y": 120, "w": 73, "ch": 2, "s": ["400"] }, + "Chicle": { "x": 0, "y": 160, "w": 56, "ch": 2, "s": ["400"] }, + "Chilanka": { "x": 70, "y": 160, "w": 99, "ch": 2, "s": ["400"] }, + "Chiron GoRound TC": { + "x": 183, + "y": 160, + "w": 225, + "ch": 2, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Chiron Hei HK": { + "x": 422, + "y": 160, + "w": 163, + "ch": 2, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Chivo": { + "x": 599, + "y": 160, + "w": 72, + "ch": 2, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Chivo Mono": { + "x": 685, + "y": 160, + "w": 152, + "ch": 2, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Chocolate Classical Sans": { + "x": 851, + "y": 160, + "w": 283, + "ch": 2, + "s": ["400"] + }, + "Chokokutai": { "x": 0, "y": 200, "w": 148, "ch": 2, "s": ["400"] }, + "Chonburi": { "x": 162, "y": 200, "w": 138, "ch": 2, "s": ["400"] }, + "Cinzel": { + "x": 314, + "y": 200, + "w": 94, + "ch": 2, + "s": ["400", "500", "600", "700", "800", "900"] + }, + "Cinzel Decorative": { + "x": 422, + "y": 200, + "w": 262, + "ch": 2, + "s": ["400", "700", "900"] + }, + "Clicker Script": { "x": 698, "y": 200, "w": 117, "ch": 2, "s": ["400"] }, + "Climate Crisis": { "x": 829, "y": 200, "w": 247, "ch": 2, "s": ["400"] }, + "Coda": { "x": 1090, "y": 200, "w": 65, "ch": 2, "s": ["400", "800"] }, + "Codystar": { "x": 0, "y": 240, "w": 137, "ch": 2, "s": ["300", "400"] }, + "Coiny": { "x": 151, "y": 240, "w": 79, "ch": 2, "s": ["400"] }, + "Combo": { "x": 244, "y": 240, "w": 69, "ch": 2, "s": ["400"] }, + "Comfortaa": { + "x": 327, + "y": 240, + "w": 143, + "ch": 2, + "s": ["300", "400", "500", "600", "700"] + }, + "Comforter": { "x": 484, "y": 240, "w": 99, "ch": 2, "s": ["400"] }, + "Comforter Brush": { "x": 597, "y": 240, "w": 147, "ch": 2, "s": ["400"] }, + "Comic Neue": { + "x": 758, + "y": 240, + "w": 133, + "ch": 2, + "s": ["300", "300i", "400", "400i", "700", "700i"] + }, + "Comic Relief": { + "x": 905, + "y": 240, + "w": 147, + "ch": 2, + "s": ["400", "700"] + }, + "Coming Soon": { "x": 0, "y": 280, "w": 152, "ch": 2, "s": ["400"] }, + "Comme": { + "x": 166, + "y": 280, + "w": 96, + "ch": 2, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Commissioner": { + "x": 276, + "y": 280, + "w": 166, + "ch": 2, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Concert One": { "x": 456, "y": 280, "w": 136, "ch": 2, "s": ["400"] }, + "Condiment": { "x": 606, "y": 280, "w": 96, "ch": 2, "s": ["400"] }, + "Contrail One": { "x": 716, "y": 280, "w": 128, "ch": 2, "s": ["400"] }, + "Convergence": { "x": 858, "y": 280, "w": 155, "ch": 2, "s": ["400"] }, + "Cookie": { "x": 1027, "y": 280, "w": 55, "ch": 2, "s": ["400"] }, + "Copse": { "x": 1096, "y": 280, "w": 73, "ch": 2, "s": ["400"] }, + "Coral Pixels": { "x": 0, "y": 320, "w": 140, "ch": 2, "s": ["400"] }, + "Corben": { "x": 154, "y": 320, "w": 91, "ch": 2, "s": ["400", "700"] }, + "Corinthia": { "x": 259, "y": 320, "w": 69, "ch": 2, "s": ["400", "700"] }, + "Cormorant": { + "x": 342, + "y": 320, + "w": 113, + "ch": 2, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Cormorant Garamond": { + "x": 469, + "y": 320, + "w": 220, + "ch": 2, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Cormorant Infant": { + "x": 703, + "y": 320, + "w": 181, + "ch": 2, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Cormorant SC": { + "x": 898, + "y": 320, + "w": 167, + "ch": 2, + "s": ["300", "400", "500", "600", "700"] + }, + "Cormorant Unicase": { + "x": 0, + "y": 360, + "w": 222, + "ch": 2, + "s": ["300", "400", "500", "600", "700"] + }, + "Cormorant Upright": { + "x": 236, + "y": 360, + "w": 188, + "ch": 2, + "s": ["300", "400", "500", "600", "700"] + }, + "Cossette Texte": { + "x": 438, + "y": 360, + "w": 164, + "ch": 2, + "s": ["400", "700"] + }, + "Cossette Titre": { + "x": 616, + "y": 360, + "w": 116, + "ch": 2, + "s": ["400", "700"] + }, + "Courgette": { "x": 746, "y": 360, "w": 109, "ch": 2, "s": ["400"] }, + "Courier Prime": { + "x": 869, + "y": 360, + "w": 196, + "ch": 2, + "s": ["400", "400i", "700", "700i"] + }, + "Cousine": { + "x": 1079, + "y": 360, + "w": 109, + "ch": 2, + "s": ["400", "400i", "700", "700i"] + }, + "Coustard": { "x": 0, "y": 400, "w": 115, "ch": 2, "s": ["400", "900"] }, + "Covered By Your Grace": { + "x": 129, + "y": 400, + "w": 206, + "ch": 2, + "s": ["400"] + }, + "Crafty Girls": { "x": 349, "y": 400, "w": 153, "ch": 2, "s": ["400"] }, + "Creepster": { "x": 516, "y": 400, "w": 101, "ch": 2, "s": ["400"] }, + "Crete Round": { + "x": 631, + "y": 400, + "w": 142, + "ch": 2, + "s": ["400", "400i"] + }, + "Crimson Pro": { + "x": 787, + "y": 400, + "w": 127, + "ch": 2, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Crimson Text": { + "x": 928, + "y": 400, + "w": 139, + "ch": 2, + "s": ["400", "400i", "600", "600i", "700", "700i"] + }, + "Croissant One": { "x": 0, "y": 440, "w": 186, "ch": 2, "s": ["400"] }, + "Crushed": { "x": 200, "y": 440, "w": 79, "ch": 2, "s": ["400"] }, + "Cuprum": { + "x": 293, + "y": 440, + "w": 77, + "ch": 2, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Cute Font": { "x": 384, "y": 440, "w": 78, "ch": 2, "s": ["400"] }, + "Cutive": { "x": 476, "y": 440, "w": 98, "ch": 2, "s": ["400"] }, + "Cutive Mono": { "x": 588, "y": 440, "w": 168, "ch": 2, "s": ["400"] }, + "Dai Banna SIL": { + "x": 770, + "y": 440, + "w": 137, + "ch": 2, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Damion": { "x": 921, "y": 440, "w": 78, "ch": 2, "s": ["400"] }, + "Dancing Script": { + "x": 1013, + "y": 440, + "w": 137, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Danfo": { "x": 0, "y": 480, "w": 86, "ch": 2, "s": ["400"] }, + "Dangrek": { "x": 100, "y": 480, "w": 91, "ch": 2, "s": ["400"] }, + "Darker Grotesque": { + "x": 205, + "y": 480, + "w": 163, + "ch": 2, + "s": ["300", "400", "500", "600", "700", "800", "900"] + }, + "Darumadrop One": { "x": 382, "y": 480, "w": 184, "ch": 2, "s": ["400"] }, + "David Libre": { + "x": 580, + "y": 480, + "w": 120, + "ch": 2, + "s": ["400", "500", "700"] + }, + "Dawning of a New Day": { + "x": 714, + "y": 480, + "w": 208, + "ch": 2, + "s": ["400"] + }, + "Days One": { "x": 936, "y": 480, "w": 133, "ch": 2, "s": ["400"] }, + "Dekko": { "x": 1083, "y": 480, "w": 62, "ch": 2, "s": ["400"] }, + "Dela Gothic One": { "x": 0, "y": 520, "w": 234, "ch": 2, "s": ["400"] }, + "Delicious Handrawn": { + "x": 248, + "y": 520, + "w": 161, + "ch": 2, + "s": ["400"] + }, + "Delius": { "x": 423, "y": 520, "w": 74, "ch": 2, "s": ["400"] }, + "Delius Swash Caps": { + "x": 511, + "y": 520, + "w": 214, + "ch": 2, + "s": ["400"] + }, + "Delius Unicase": { + "x": 739, + "y": 520, + "w": 229, + "ch": 2, + "s": ["400", "700"] + }, + "Della Respira": { "x": 982, "y": 520, "w": 149, "ch": 2, "s": ["400"] }, + "Denk One": { "x": 0, "y": 560, "w": 105, "ch": 2, "s": ["400"] }, + "Devonshire": { "x": 119, "y": 560, "w": 87, "ch": 2, "s": ["400"] }, + "Dhurjati": { "x": 220, "y": 560, "w": 75, "ch": 2, "s": ["400"] }, + "Didact Gothic": { "x": 309, "y": 560, "w": 145, "ch": 2, "s": ["400"] }, + "Diphylleia": { "x": 468, "y": 560, "w": 112, "ch": 2, "s": ["400"] }, + "Diplomata": { "x": 594, "y": 560, "w": 252, "ch": 2, "s": ["400"] }, + "Diplomata SC": { "x": 860, "y": 560, "w": 334, "ch": 2, "s": ["400"] }, + "DM Mono": { + "x": 0, + "y": 600, + "w": 109, + "ch": 2, + "s": ["300", "300i", "400", "400i", "500", "500i"] + }, + "DM Sans": { + "x": 123, + "y": 600, + "w": 104, + "ch": 2, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "DM Serif Display": { + "x": 241, + "y": 600, + "w": 181, + "ch": 2, + "s": ["400", "400i"] + }, + "DM Serif Text": { + "x": 436, + "y": 600, + "w": 150, + "ch": 2, + "s": ["400", "400i"] + }, + "Do Hyeon": { "x": 600, "y": 600, "w": 101, "ch": 2, "s": ["400"] }, + "Dokdo": { "x": 715, "y": 600, "w": 68, "ch": 2, "s": ["400"] }, + "Domine": { + "x": 797, + "y": 600, + "w": 101, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Donegal One": { "x": 912, "y": 600, "w": 161, "ch": 2, "s": ["400"] }, + "Dongle": { + "x": 1087, + "y": 600, + "w": 56, + "ch": 2, + "s": ["300", "400", "700"] + }, + "Doppio One": { "x": 0, "y": 640, "w": 138, "ch": 2, "s": ["400"] }, + "Dorsa": { "x": 152, "y": 640, "w": 32, "ch": 2, "s": ["400"] }, + "Dosis": { + "x": 198, + "y": 640, + "w": 56, + "ch": 2, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "DotGothic16": { "x": 268, "y": 640, "w": 140, "ch": 2, "s": ["400"] }, + "Doto": { + "x": 422, + "y": 640, + "w": 66, + "ch": 2, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Dr Sugiyama": { "x": 502, "y": 640, "w": 116, "ch": 2, "s": ["400"] }, + "Duru Sans": { "x": 632, "y": 640, "w": 133, "ch": 2, "s": ["400"] }, + "Dynalight": { "x": 779, "y": 640, "w": 84, "ch": 2, "s": ["400"] }, + "DynaPuff": { + "x": 877, + "y": 640, + "w": 121, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Eagle Lake": { "x": 1012, "y": 640, "w": 153, "ch": 2, "s": ["400"] }, + "East Sea Dokdo": { "x": 0, "y": 680, "w": 118, "ch": 2, "s": ["400"] }, + "Eater": { "x": 132, "y": 680, "w": 90, "ch": 2, "s": ["400"] }, + "EB Garamond": { + "x": 236, + "y": 680, + "w": 141, + "ch": 2, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Economica": { + "x": 391, + "y": 680, + "w": 88, + "ch": 2, + "s": ["400", "400i", "700", "700i"] + }, + "Eczar": { + "x": 493, + "y": 680, + "w": 65, + "ch": 2, + "s": ["400", "500", "600", "700", "800"] + }, + "Edu AU VIC WA NT Arrows": { + "x": 572, + "y": 680, + "w": 302, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Edu AU VIC WA NT Dots": { + "x": 888, + "y": 680, + "w": 261, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Edu AU VIC WA NT Guides": { + "x": 0, + "y": 720, + "w": 285, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Edu AU VIC WA NT Hand": { + "x": 299, + "y": 720, + "w": 274, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Edu AU VIC WA NT Pre": { + "x": 587, + "y": 720, + "w": 257, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Edu NSW ACT Cursive": { + "x": 858, + "y": 720, + "w": 276, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Edu NSW ACT Foundation": { + "x": 0, + "y": 760, + "w": 239, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Edu NSW ACT Hand Pre": { + "x": 253, + "y": 760, + "w": 294, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Edu QLD Beginner": { + "x": 561, + "y": 760, + "w": 177, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Edu QLD Hand": { + "x": 752, + "y": 760, + "w": 175, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Edu SA Beginner": { + "x": 941, + "y": 760, + "w": 155, + "ch": 2, + "s": ["400", "500", "600", "700"] + }, + "Edu SA Hand": { + "x": 0, + "y": 0, + "w": 159, + "ch": 3, + "s": ["400", "500", "600", "700"] + }, + "Edu TAS Beginner": { + "x": 173, + "y": 0, + "w": 175, + "ch": 3, + "s": ["400", "500", "600", "700"] + }, + "Edu VIC WA NT Beginner": { + "x": 362, + "y": 0, + "w": 243, + "ch": 3, + "s": ["400", "500", "600", "700"] + }, + "Edu VIC WA NT Hand": { + "x": 619, + "y": 0, + "w": 230, + "ch": 3, + "s": ["400", "500", "600", "700"] + }, + "Edu VIC WA NT Hand Pre": { + "x": 863, + "y": 0, + "w": 282, + "ch": 3, + "s": ["400", "500", "600", "700"] + }, + "El Messiri": { + "x": 0, + "y": 40, + "w": 109, + "ch": 3, + "s": ["400", "500", "600", "700"] + }, + "Electrolize": { "x": 123, "y": 40, "w": 123, "ch": 3, "s": ["400"] }, + "Elms Sans": { + "x": 260, + "y": 40, + "w": 123, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Elsie": { "x": 397, "y": 40, "w": 61, "ch": 3, "s": ["400", "900"] }, + "Elsie Swash Caps": { + "x": 472, + "y": 40, + "w": 190, + "ch": 3, + "s": ["400", "900"] + }, + "Emblema One": { "x": 676, "y": 40, "w": 198, "ch": 3, "s": ["400"] }, + "Emilys Candy": { "x": 888, "y": 40, "w": 148, "ch": 3, "s": ["400"] }, + "Encode Sans": { + "x": 1050, + "y": 40, + "w": 141, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Encode Sans Condensed": { + "x": 0, + "y": 80, + "w": 227, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Encode Sans Expanded": { + "x": 241, + "y": 80, + "w": 287, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Encode Sans SC": { + "x": 542, + "y": 80, + "w": 186, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Encode Sans Semi Condensed": { + "x": 742, + "y": 80, + "w": 299, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Encode Sans Semi Expanded": { + "x": 0, + "y": 120, + "w": 330, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Engagement": { "x": 344, "y": 120, "w": 80, "ch": 3, "s": ["400"] }, + "Englebert": { "x": 438, "y": 120, "w": 97, "ch": 3, "s": ["400"] }, + "Enriqueta": { + "x": 549, + "y": 120, + "w": 115, + "ch": 3, + "s": ["400", "500", "600", "700"] + }, + "Ephesis": { "x": 678, "y": 120, "w": 72, "ch": 3, "s": ["400"] }, + "Epilogue": { + "x": 764, + "y": 120, + "w": 109, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Epunda Sans": { + "x": 887, + "y": 120, + "w": 133, + "ch": 3, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Epunda Slab": { + "x": 1034, + "y": 120, + "w": 132, + "ch": 3, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Erica One": { "x": 0, "y": 160, "w": 131, "ch": 3, "s": ["400"] }, + "Esteban": { "x": 145, "y": 160, "w": 93, "ch": 3, "s": ["400"] }, + "Estonia": { "x": 252, "y": 160, "w": 47, "ch": 3, "s": ["400"] }, + "Euphoria Script": { "x": 313, "y": 160, "w": 127, "ch": 3, "s": ["400"] }, + "Ewert": { "x": 454, "y": 160, "w": 97, "ch": 3, "s": ["400"] }, + "Exile": { "x": 565, "y": 160, "w": 81, "ch": 3, "s": ["400"] }, + "Exo": { + "x": 660, + "y": 160, + "w": 48, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Exo 2": { + "x": 722, + "y": 160, + "w": 67, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Expletus Sans": { + "x": 803, + "y": 160, + "w": 159, + "ch": 3, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Explora": { "x": 976, "y": 160, "w": 87, "ch": 3, "s": ["400"] }, + "Faculty Glyphic": { "x": 0, "y": 200, "w": 182, "ch": 3, "s": ["400"] }, + "Fahkwang": { + "x": 196, + "y": 200, + "w": 133, + "ch": 3, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Familjen Grotesk": { + "x": 343, + "y": 200, + "w": 178, + "ch": 3, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Fanwood Text": { + "x": 535, + "y": 200, + "w": 140, + "ch": 3, + "s": ["400", "400i"] + }, + "Farro": { + "x": 689, + "y": 200, + "w": 72, + "ch": 3, + "s": ["300", "400", "500", "700"] + }, + "Farsan": { "x": 775, "y": 200, "w": 61, "ch": 3, "s": ["400"] }, + "Fascinate": { "x": 850, "y": 200, "w": 135, "ch": 3, "s": ["400"] }, + "Fascinate Inline": { "x": 0, "y": 240, "w": 220, "ch": 3, "s": ["400"] }, + "Faster One": { "x": 234, "y": 240, "w": 165, "ch": 3, "s": ["400"] }, + "Fasthand": { "x": 413, "y": 240, "w": 84, "ch": 3, "s": ["400"] }, + "Fauna One": { "x": 511, "y": 240, "w": 141, "ch": 3, "s": ["400"] }, + "Faustina": { + "x": 666, + "y": 240, + "w": 97, + "ch": 3, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Federant": { "x": 777, "y": 240, "w": 105, "ch": 3, "s": ["400"] }, + "Federo": { "x": 896, "y": 240, "w": 78, "ch": 3, "s": ["400"] }, + "Felipa": { "x": 988, "y": 240, "w": 60, "ch": 3, "s": ["400"] }, + "Fenix": { "x": 1062, "y": 240, "w": 63, "ch": 3, "s": ["400"] }, + "Festive": { "x": 0, "y": 280, "w": 67, "ch": 3, "s": ["400"] }, + "Figtree": { + "x": 81, + "y": 280, + "w": 85, + "ch": 3, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Finger Paint": { "x": 180, "y": 280, "w": 158, "ch": 3, "s": ["400"] }, + "Finlandica": { + "x": 352, + "y": 280, + "w": 114, + "ch": 3, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Fira Code": { + "x": 480, + "y": 280, + "w": 138, + "ch": 3, + "s": ["300", "400", "500", "600", "700"] + }, + "Fira Mono": { + "x": 632, + "y": 280, + "w": 138, + "ch": 3, + "s": ["400", "500", "700"] + }, + "Fira Sans": { + "x": 784, + "y": 280, + "w": 105, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Fira Sans Condensed": { + "x": 903, + "y": 280, + "w": 211, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Fira Sans Extra Condensed": { + "x": 0, + "y": 320, + "w": 244, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Fjalla One": { "x": 258, "y": 320, "w": 98, "ch": 3, "s": ["400"] }, + "Fjord One": { "x": 370, "y": 320, "w": 115, "ch": 3, "s": ["400"] }, + "Flamenco": { "x": 499, "y": 320, "w": 99, "ch": 3, "s": ["300", "400"] }, + "Flavors": { "x": 612, "y": 320, "w": 82, "ch": 3, "s": ["400"] }, + "Fleur De Leah": { "x": 708, "y": 320, "w": 133, "ch": 3, "s": ["400"] }, + "Flow Block": { "x": 855, "y": 320, "w": 126, "ch": 3, "s": ["400"] }, + "Flow Circular": { "x": 995, "y": 320, "w": 145, "ch": 3, "s": ["400"] }, + "Flow Rounded": { "x": 0, "y": 360, "w": 160, "ch": 3, "s": ["400"] }, + "Foldit": { + "x": 174, + "y": 360, + "w": 54, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Fondamento": { + "x": 242, + "y": 360, + "w": 133, + "ch": 3, + "s": ["400", "400i"] + }, + "Fontdiner Swanky": { "x": 389, "y": 360, "w": 239, "ch": 3, "s": ["400"] }, + "Forum": { "x": 642, "y": 360, "w": 70, "ch": 3, "s": ["400"] }, + "Fragment Mono": { + "x": 726, + "y": 360, + "w": 201, + "ch": 3, + "s": ["400", "400i"] + }, + "Francois One": { "x": 941, "y": 360, "w": 136, "ch": 3, "s": ["400"] }, + "Frank Ruhl Libre": { + "x": 0, + "y": 400, + "w": 184, + "ch": 3, + "s": ["300", "400", "500", "600", "700", "800", "900"] + }, + "Fraunces": { + "x": 198, + "y": 400, + "w": 112, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Freckle Face": { "x": 324, "y": 400, "w": 141, "ch": 3, "s": ["400"] }, + "Fredericka the Great": { + "x": 479, + "y": 400, + "w": 243, + "ch": 3, + "s": ["400"] + }, + "Fredoka": { + "x": 736, + "y": 400, + "w": 96, + "ch": 3, + "s": ["300", "400", "500", "600", "700"] + }, + "Fredoka One": { "x": 846, "y": 400, "w": 155, "ch": 3, "s": ["400"] }, + "Freehand": { "x": 1015, "y": 400, "w": 85, "ch": 3, "s": ["400"] }, + "Freeman": { "x": 0, "y": 440, "w": 91, "ch": 3, "s": ["400"] }, + "Fresca": { "x": 105, "y": 440, "w": 67, "ch": 3, "s": ["400"] }, + "Frijole": { "x": 186, "y": 440, "w": 132, "ch": 3, "s": ["400"] }, + "Fruktur": { "x": 332, "y": 440, "w": 94, "ch": 3, "s": ["400", "400i"] }, + "Fugaz One": { "x": 440, "y": 440, "w": 127, "ch": 3, "s": ["400"] }, + "Fuggles": { "x": 581, "y": 440, "w": 58, "ch": 3, "s": ["400"] }, + "Funnel Display": { + "x": 653, + "y": 440, + "w": 174, + "ch": 3, + "s": ["300", "400", "500", "600", "700", "800"] + }, + "Funnel Sans": { + "x": 841, + "y": 440, + "w": 140, + "ch": 3, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Fustat": { + "x": 995, + "y": 440, + "w": 75, + "ch": 3, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Fuzzy Bubbles": { + "x": 0, + "y": 480, + "w": 179, + "ch": 3, + "s": ["400", "700"] + }, + "Ga Maamli": { "x": 193, "y": 480, "w": 111, "ch": 3, "s": ["400"] }, + "Gabarito": { + "x": 318, + "y": 480, + "w": 99, + "ch": 3, + "s": ["400", "500", "600", "700", "800", "900"] + }, + "Gabriela": { "x": 431, "y": 480, "w": 106, "ch": 3, "s": ["400"] }, + "Gaegu": { + "x": 551, + "y": 480, + "w": 70, + "ch": 3, + "s": ["300", "400", "700"] + }, + "Gafata": { "x": 635, "y": 480, "w": 72, "ch": 3, "s": ["400"] }, + "Gajraj One": { "x": 721, "y": 480, "w": 148, "ch": 3, "s": ["400"] }, + "Galada": { "x": 883, "y": 480, "w": 78, "ch": 3, "s": ["400"] }, + "Galdeano": { "x": 975, "y": 480, "w": 98, "ch": 3, "s": ["400"] }, + "Galindo": { "x": 1087, "y": 480, "w": 101, "ch": 3, "s": ["400"] }, + "Gamja Flower": { "x": 0, "y": 520, "w": 130, "ch": 3, "s": ["400"] }, + "Gantari": { + "x": 144, + "y": 520, + "w": 89, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Gasoek One": { "x": 247, "y": 520, "w": 163, "ch": 3, "s": ["400"] }, + "Gayathri": { + "x": 424, + "y": 520, + "w": 99, + "ch": 3, + "s": ["100", "400", "700"] + }, + "Geist": { + "x": 537, + "y": 520, + "w": 66, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Geist Mono": { + "x": 617, + "y": 520, + "w": 152, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Gelasio": { + "x": 783, + "y": 520, + "w": 87, + "ch": 3, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Gemunu Libre": { + "x": 884, + "y": 520, + "w": 126, + "ch": 3, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Genos": { + "x": 1024, + "y": 520, + "w": 67, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Gentium Book Basic": { + "x": 0, + "y": 560, + "w": 209, + "ch": 3, + "s": ["400", "400i", "700", "700i"] + }, + "Gentium Book Plus": { + "x": 223, + "y": 560, + "w": 198, + "ch": 3, + "s": ["400", "400i", "700", "700i"] + }, + "Gentium Plus": { + "x": 435, + "y": 560, + "w": 140, + "ch": 3, + "s": ["400", "400i", "700", "700i"] + }, + "Geo": { "x": 589, "y": 560, "w": 42, "ch": 3, "s": ["400", "400i"] }, + "Geologica": { + "x": 645, + "y": 560, + "w": 122, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Geom": { + "x": 781, + "y": 560, + "w": 76, + "ch": 3, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Georama": { + "x": 871, + "y": 560, + "w": 102, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Geostar": { "x": 987, "y": 560, "w": 133, "ch": 3, "s": ["400"] }, + "Geostar Fill": { "x": 0, "y": 600, "w": 192, "ch": 3, "s": ["400"] }, + "Germania One": { "x": 206, "y": 600, "w": 137, "ch": 3, "s": ["400"] }, + "GFS Didot": { "x": 357, "y": 600, "w": 121, "ch": 3, "s": ["400"] }, + "GFS Neohellenic": { + "x": 492, + "y": 600, + "w": 155, + "ch": 3, + "s": ["400", "400i", "700", "700i"] + }, + "Gideon Roman": { "x": 661, "y": 600, "w": 172, "ch": 3, "s": ["400"] }, + "Gidole": { "x": 847, "y": 600, "w": 73, "ch": 3, "s": ["400"] }, + "Gidugu": { "x": 934, "y": 600, "w": 58, "ch": 3, "s": ["400"] }, + "Gilda Display": { "x": 1006, "y": 600, "w": 156, "ch": 3, "s": ["400"] }, + "Girassol": { "x": 0, "y": 640, "w": 91, "ch": 3, "s": ["400"] }, + "Give You Glory": { "x": 105, "y": 640, "w": 170, "ch": 3, "s": ["400"] }, + "Glass Antiqua": { "x": 289, "y": 640, "w": 130, "ch": 3, "s": ["400"] }, + "Glegoo": { "x": 433, "y": 640, "w": 90, "ch": 3, "s": ["400", "700"] }, + "Gloock": { "x": 537, "y": 640, "w": 89, "ch": 3, "s": ["400"] }, + "Gloria Hallelujah": { + "x": 640, + "y": 640, + "w": 190, + "ch": 3, + "s": ["400"] + }, + "Glory": { + "x": 844, + "y": 640, + "w": 56, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Gluten": { + "x": 914, + "y": 640, + "w": 90, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Goblin One": { "x": 0, "y": 680, "w": 202, "ch": 3, "s": ["400"] }, + "Gochi Hand": { "x": 216, "y": 680, "w": 121, "ch": 3, "s": ["400"] }, + "Goldman": { "x": 351, "y": 680, "w": 118, "ch": 3, "s": ["400", "700"] }, + "Golos Text": { + "x": 483, + "y": 680, + "w": 130, + "ch": 3, + "s": ["400", "500", "600", "700", "800", "900"] + }, + "Google Sans": { + "x": 627, + "y": 680, + "w": 146, + "ch": 3, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Google Sans Code": { + "x": 787, + "y": 680, + "w": 239, + "ch": 3, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Google Sans Flex": { + "x": 0, + "y": 720, + "w": 193, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Gorditas": { "x": 207, "y": 720, "w": 111, "ch": 3, "s": ["400", "700"] }, + "Gothic A1": { + "x": 332, + "y": 720, + "w": 114, + "ch": 3, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Gotu": { "x": 460, "y": 720, "w": 67, "ch": 3, "s": ["400"] }, + "Goudy Bookletter 1911": { + "x": 541, + "y": 720, + "w": 218, + "ch": 3, + "s": ["400"] + }, + "Gowun Batang": { + "x": 773, + "y": 720, + "w": 162, + "ch": 3, + "s": ["400", "700"] + }, + "Gowun Dodum": { "x": 949, "y": 720, "w": 161, "ch": 3, "s": ["400"] }, + "Graduate": { "x": 0, "y": 760, "w": 134, "ch": 3, "s": ["400"] }, + "Grand Hotel": { "x": 148, "y": 760, "w": 101, "ch": 3, "s": ["400"] }, + "Grandiflora One": { "x": 263, "y": 760, "w": 174, "ch": 3, "s": ["400"] }, + "Grandstander": { + "x": 451, + "y": 760, + "w": 173, + "ch": 3, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Grape Nuts": { "x": 638, "y": 760, "w": 111, "ch": 3, "s": ["400"] }, + "Gravitas One": { "x": 763, "y": 760, "w": 230, "ch": 3, "s": ["400"] }, + "Great Vibes": { "x": 1007, "y": 760, "w": 104, "ch": 3, "s": ["400"] }, + "Grechen Fuemen": { "x": 0, "y": 0, "w": 179, "ch": 4, "s": ["400"] }, + "Grenze": { + "x": 193, + "y": 0, + "w": 69, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Grenze Gotisch": { + "x": 276, + "y": 0, + "w": 135, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Grey Qo": { "x": 425, "y": 0, "w": 64, "ch": 4, "s": ["400"] }, + "Griffy": { "x": 503, "y": 0, "w": 71, "ch": 4, "s": ["400"] }, + "Gruppo": { "x": 588, "y": 0, "w": 95, "ch": 4, "s": ["400"] }, + "Gudea": { + "x": 697, + "y": 0, + "w": 74, + "ch": 4, + "s": ["400", "400i", "700", "700i"] + }, + "Gugi": { "x": 785, "y": 0, "w": 60, "ch": 4, "s": ["400"] }, + "Gulzar": { "x": 859, "y": 0, "w": 77, "ch": 4, "s": ["400"] }, + "Gupter": { + "x": 950, + "y": 0, + "w": 72, + "ch": 4, + "s": ["400", "500", "700"] + }, + "Gurajada": { "x": 1036, "y": 0, "w": 71, "ch": 4, "s": ["400"] }, + "Gwendolyn": { "x": 0, "y": 40, "w": 89, "ch": 4, "s": ["400", "700"] }, + "Habibi": { "x": 103, "y": 40, "w": 81, "ch": 4, "s": ["400"] }, + "Hachi Maru Pop": { "x": 198, "y": 40, "w": 246, "ch": 4, "s": ["400"] }, + "Hahmlet": { + "x": 458, + "y": 40, + "w": 112, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Halant": { + "x": 584, + "y": 40, + "w": 75, + "ch": 4, + "s": ["300", "400", "500", "600", "700"] + }, + "Hammersmith One": { "x": 673, "y": 40, "w": 217, "ch": 4, "s": ["400"] }, + "Hanalei": { "x": 904, "y": 40, "w": 91, "ch": 4, "s": ["400"] }, + "Hanalei Fill": { "x": 1009, "y": 40, "w": 136, "ch": 4, "s": ["400"] }, + "Handjet": { + "x": 0, + "y": 80, + "w": 68, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Handlee": { "x": 82, "y": 80, "w": 88, "ch": 4, "s": ["400"] }, + "Hanken Grotesk": { + "x": 184, + "y": 80, + "w": 181, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Hanuman": { + "x": 379, + "y": 80, + "w": 123, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Happy Monkey": { "x": 516, "y": 80, "w": 189, "ch": 4, "s": ["400"] }, + "Harmattan": { + "x": 719, + "y": 80, + "w": 99, + "ch": 4, + "s": ["400", "500", "600", "700"] + }, + "Headland One": { "x": 832, "y": 80, "w": 185, "ch": 4, "s": ["400"] }, + "Hedvig Letters Sans": { + "x": 0, + "y": 120, + "w": 224, + "ch": 4, + "s": ["400"] + }, + "Hedvig Letters Serif": { + "x": 238, + "y": 120, + "w": 226, + "ch": 4, + "s": ["400"] + }, + "Heebo": { + "x": 478, + "y": 120, + "w": 78, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Henny Penny": { "x": 570, "y": 120, "w": 148, "ch": 4, "s": ["400"] }, + "Hepta Slab": { + "x": 732, + "y": 120, + "w": 150, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Herr Von Muellerhoff": { + "x": 896, + "y": 120, + "w": 145, + "ch": 4, + "s": ["400"] + }, + "Hi Melody": { "x": 1055, "y": 120, "w": 84, "ch": 4, "s": ["400"] }, + "Hina Mincho": { "x": 0, "y": 160, "w": 128, "ch": 4, "s": ["400"] }, + "Hind": { + "x": 142, + "y": 160, + "w": 56, + "ch": 4, + "s": ["300", "400", "500", "600", "700"] + }, + "Hind Guntur": { + "x": 212, + "y": 160, + "w": 134, + "ch": 4, + "s": ["300", "400", "500", "600", "700"] + }, + "Hind Madurai": { + "x": 360, + "y": 160, + "w": 146, + "ch": 4, + "s": ["300", "400", "500", "600", "700"] + }, + "Hind Mysuru": { + "x": 520, + "y": 160, + "w": 138, + "ch": 4, + "s": ["300", "400", "500", "600", "700"] + }, + "Hind Siliguri": { + "x": 672, + "y": 160, + "w": 130, + "ch": 4, + "s": ["300", "400", "500", "600", "700"] + }, + "Hind Vadodara": { + "x": 816, + "y": 160, + "w": 161, + "ch": 4, + "s": ["300", "400", "500", "600", "700"] + }, + "Holtwood One SC": { "x": 0, "y": 200, "w": 292, "ch": 4, "s": ["400"] }, + "Homemade Apple": { "x": 306, "y": 200, "w": 239, "ch": 4, "s": ["400"] }, + "Homenaje": { "x": 559, "y": 200, "w": 85, "ch": 4, "s": ["400"] }, + "Honk": { "x": 658, "y": 200, "w": 59, "ch": 4, "s": ["400"] }, + "Host Grotesk": { + "x": 731, + "y": 200, + "w": 150, + "ch": 4, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Hubballi": { "x": 895, "y": 200, "w": 87, "ch": 4, "s": ["400"] }, + "Hubot Sans": { + "x": 996, + "y": 200, + "w": 137, + "ch": 4, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Huninn": { "x": 0, "y": 240, "w": 89, "ch": 4, "s": ["400"] }, + "Hurricane": { "x": 103, "y": 240, "w": 76, "ch": 4, "s": ["400"] }, + "Iansui": { "x": 193, "y": 240, "w": 78, "ch": 4, "s": ["400"] }, + "Ibarra Real Nova": { + "x": 285, + "y": 240, + "w": 182, + "ch": 4, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "IBM Plex Mono": { + "x": 481, + "y": 240, + "w": 196, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "IBM Plex Sans": { + "x": 691, + "y": 240, + "w": 163, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "IBM Plex Sans Arabic": { + "x": 868, + "y": 240, + "w": 237, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "IBM Plex Sans Condensed": { + "x": 0, + "y": 280, + "w": 263, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "IBM Plex Sans Devanagari": { + "x": 277, + "y": 280, + "w": 289, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "IBM Plex Sans Hebrew": { + "x": 580, + "y": 280, + "w": 253, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "IBM Plex Sans JP": { + "x": 847, + "y": 280, + "w": 204, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "IBM Plex Sans KR": { + "x": 0, + "y": 320, + "w": 199, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "IBM Plex Sans Thai": { + "x": 213, + "y": 320, + "w": 215, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "IBM Plex Sans Thai Looped": { + "x": 442, + "y": 320, + "w": 300, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "IBM Plex Serif": { + "x": 756, + "y": 320, + "w": 167, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Iceberg": { "x": 937, "y": 320, "w": 84, "ch": 4, "s": ["400"] }, + "Iceland": { "x": 1035, "y": 320, "w": 75, "ch": 4, "s": ["400"] }, + "IM Fell Double Pica": { + "x": 0, + "y": 360, + "w": 204, + "ch": 4, + "s": ["400", "400i"] + }, + "IM Fell Double Pica SC": { + "x": 218, + "y": 360, + "w": 261, + "ch": 4, + "s": ["400"] + }, + "IM Fell DW Pica": { + "x": 493, + "y": 360, + "w": 175, + "ch": 4, + "s": ["400", "400i"] + }, + "IM Fell DW Pica SC": { + "x": 682, + "y": 360, + "w": 220, + "ch": 4, + "s": ["400"] + }, + "IM Fell English": { + "x": 916, + "y": 360, + "w": 168, + "ch": 4, + "s": ["400", "400i"] + }, + "IM Fell English SC": { "x": 0, "y": 400, "w": 215, "ch": 4, "s": ["400"] }, + "IM Fell French Canon": { + "x": 229, + "y": 400, + "w": 233, + "ch": 4, + "s": ["400", "400i"] + }, + "IM Fell French Canon SC": { + "x": 476, + "y": 400, + "w": 279, + "ch": 4, + "s": ["400"] + }, + "IM Fell Great Primer": { + "x": 769, + "y": 400, + "w": 220, + "ch": 4, + "s": ["400", "400i"] + }, + "IM Fell Great Primer SC": { + "x": 0, + "y": 440, + "w": 289, + "ch": 4, + "s": ["400"] + }, + "Imbue": { + "x": 303, + "y": 440, + "w": 52, + "ch": 4, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Imperial Script": { "x": 369, "y": 440, "w": 119, "ch": 4, "s": ["400"] }, + "Imprima": { "x": 502, "y": 440, "w": 98, "ch": 4, "s": ["400"] }, + "Inclusive Sans": { + "x": 614, + "y": 440, + "w": 172, + "ch": 4, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Inconsolata": { + "x": 800, + "y": 440, + "w": 140, + "ch": 4, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Inder": { "x": 954, "y": 440, "w": 64, "ch": 4, "s": ["400"] }, + "Indie Flower": { "x": 1032, "y": 440, "w": 125, "ch": 4, "s": ["400"] }, + "Ingrid Darling": { "x": 0, "y": 480, "w": 114, "ch": 4, "s": ["400"] }, + "Inika": { "x": 128, "y": 480, "w": 67, "ch": 4, "s": ["400", "700"] }, + "Inknut Antiqua": { + "x": 209, + "y": 480, + "w": 211, + "ch": 4, + "s": ["300", "400", "500", "600", "700", "800", "900"] + }, + "Inria Sans": { + "x": 434, + "y": 480, + "w": 110, + "ch": 4, + "s": ["300", "300i", "400", "400i", "700", "700i"] + }, + "Inria Serif": { + "x": 558, + "y": 480, + "w": 120, + "ch": 4, + "s": ["300", "300i", "400", "400i", "700", "700i"] + }, + "Inspiration": { "x": 692, "y": 480, "w": 78, "ch": 4, "s": ["400"] }, + "Instrument Sans": { + "x": 784, + "y": 480, + "w": 189, + "ch": 4, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Instrument Serif": { + "x": 987, + "y": 480, + "w": 143, + "ch": 4, + "s": ["400", "400i"] + }, + "Intel One Mono": { + "x": 0, + "y": 520, + "w": 215, + "ch": 4, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Inter": { + "x": 229, + "y": 520, + "w": 60, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Inter Tight": { + "x": 303, + "y": 520, + "w": 113, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Irish Grover": { "x": 430, "y": 520, "w": 139, "ch": 4, "s": ["400"] }, + "Island Moments": { "x": 583, "y": 520, "w": 125, "ch": 4, "s": ["400"] }, + "Istok Web": { + "x": 722, + "y": 520, + "w": 116, + "ch": 4, + "s": ["400", "400i", "700", "700i"] + }, + "Italiana": { "x": 852, "y": 520, "w": 83, "ch": 4, "s": ["400"] }, + "Italianno": { "x": 949, "y": 520, "w": 68, "ch": 4, "s": ["400"] }, + "Itim": { "x": 1031, "y": 520, "w": 52, "ch": 4, "s": ["400"] }, + "Jacquard 12": { "x": 0, "y": 560, "w": 110, "ch": 4, "s": ["400"] }, + "Jacquard 12 Charted": { + "x": 124, + "y": 560, + "w": 184, + "ch": 4, + "s": ["400"] + }, + "Jacquard 24": { "x": 322, "y": 560, "w": 108, "ch": 4, "s": ["400"] }, + "Jacquard 24 Charted": { + "x": 444, + "y": 560, + "w": 179, + "ch": 4, + "s": ["400"] + }, + "Jacquarda Bastarda 9": { + "x": 637, + "y": 560, + "w": 261, + "ch": 4, + "s": ["400"] + }, + "Jacquarda Bastarda 9 Charted": { + "x": 0, + "y": 600, + "w": 357, + "ch": 4, + "s": ["400"] + }, + "Jacques Francois": { "x": 371, "y": 600, "w": 197, "ch": 4, "s": ["400"] }, + "Jacques Francois Shadow": { + "x": 582, + "y": 600, + "w": 315, + "ch": 4, + "s": ["400"] + }, + "Jaini": { "x": 911, "y": 600, "w": 50, "ch": 4, "s": ["400"] }, + "Jaini Purva": { "x": 975, "y": 600, "w": 102, "ch": 4, "s": ["400"] }, + "Jaldi": { "x": 1091, "y": 600, "w": 52, "ch": 4, "s": ["400", "700"] }, + "Jaro": { "x": 0, "y": 640, "w": 50, "ch": 4, "s": ["400"] }, + "Jersey 10": { "x": 64, "y": 640, "w": 84, "ch": 4, "s": ["400"] }, + "Jersey 10 Charted": { + "x": 162, + "y": 640, + "w": 158, + "ch": 4, + "s": ["400"] + }, + "Jersey 15": { "x": 334, "y": 640, "w": 91, "ch": 4, "s": ["400"] }, + "Jersey 15 Charted": { + "x": 439, + "y": 640, + "w": 168, + "ch": 4, + "s": ["400"] + }, + "Jersey 20": { "x": 621, "y": 640, "w": 98, "ch": 4, "s": ["400"] }, + "Jersey 20 Charted": { + "x": 733, + "y": 640, + "w": 176, + "ch": 4, + "s": ["400"] + }, + "Jersey 25": { "x": 923, "y": 640, "w": 102, "ch": 4, "s": ["400"] }, + "Jersey 25 Charted": { "x": 0, "y": 680, "w": 180, "ch": 4, "s": ["400"] }, + "JetBrains Mono": { + "x": 194, + "y": 680, + "w": 210, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Jim Nightshade": { "x": 418, "y": 680, "w": 119, "ch": 4, "s": ["400"] }, + "Joan": { "x": 551, "y": 680, "w": 54, "ch": 4, "s": ["400"] }, + "Jockey One": { "x": 619, "y": 680, "w": 108, "ch": 4, "s": ["400"] }, + "Jolly Lodger": { "x": 741, "y": 680, "w": 90, "ch": 4, "s": ["400"] }, + "Jomhuria": { "x": 845, "y": 680, "w": 64, "ch": 4, "s": ["400"] }, + "Jomolhari": { "x": 923, "y": 680, "w": 116, "ch": 4, "s": ["400"] }, + "Josefin Sans": { + "x": 1053, + "y": 680, + "w": 138, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Josefin Slab": { + "x": 0, + "y": 720, + "w": 123, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Jost": { + "x": 137, + "y": 720, + "w": 43, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Joti One": { "x": 194, "y": 720, "w": 100, "ch": 4, "s": ["400"] }, + "Jua": { "x": 308, "y": 720, "w": 48, "ch": 4, "s": ["400"] }, + "Judson": { + "x": 370, + "y": 720, + "w": 80, + "ch": 4, + "s": ["400", "400i", "700", "700i"] + }, + "Julee": { "x": 464, "y": 720, "w": 57, "ch": 4, "s": ["400"] }, + "Julius Sans One": { "x": 535, "y": 720, "w": 210, "ch": 4, "s": ["400"] }, + "Junge": { "x": 759, "y": 720, "w": 73, "ch": 4, "s": ["400"] }, + "Jura": { + "x": 846, + "y": 720, + "w": 59, + "ch": 4, + "s": ["300", "400", "500", "600", "700"] + }, + "Just Another Hand": { + "x": 919, + "y": 720, + "w": 112, + "ch": 4, + "s": ["400"] + }, + "Just Me Again Down Here": { + "x": 0, + "y": 760, + "w": 200, + "ch": 4, + "s": ["400"] + }, + "K2D": { + "x": 214, + "y": 760, + "w": 56, + "ch": 4, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Kablammo": { "x": 284, "y": 760, "w": 128, "ch": 4, "s": ["400"] }, + "Kadwa": { "x": 426, "y": 760, "w": 87, "ch": 4, "s": ["400", "700"] }, + "Kaisei Decol": { + "x": 527, + "y": 760, + "w": 154, + "ch": 4, + "s": ["400", "500", "700"] + }, + "Kaisei HarunoUmi": { + "x": 695, + "y": 760, + "w": 223, + "ch": 4, + "s": ["400", "500", "700"] + }, + "Kaisei Opti": { + "x": 932, + "y": 760, + "w": 137, + "ch": 4, + "s": ["400", "500", "700"] + }, + "Kaisei Tokumin": { + "x": 0, + "y": 0, + "w": 191, + "ch": 5, + "s": ["400", "500", "700", "800"] + }, + "Kalam": { "x": 205, "y": 0, "w": 70, "ch": 5, "s": ["300", "400", "700"] }, + "Kalnia": { + "x": 289, + "y": 0, + "w": 87, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "Kalnia Glaze": { + "x": 390, + "y": 0, + "w": 162, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "Kameron": { + "x": 566, + "y": 0, + "w": 107, + "ch": 5, + "s": ["400", "500", "600", "700"] + }, + "Kanchenjunga": { + "x": 687, + "y": 0, + "w": 165, + "ch": 5, + "s": ["400", "500", "600", "700"] + }, + "Kanit": { + "x": 866, + "y": 0, + "w": 65, + "ch": 5, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Kantumruy Pro": { + "x": 945, + "y": 0, + "w": 175, + "ch": 5, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Kapakana": { "x": 0, "y": 40, "w": 83, "ch": 5, "s": ["300", "400"] }, + "Karantina": { + "x": 97, + "y": 40, + "w": 66, + "ch": 5, + "s": ["300", "400", "700"] + }, + "Karla": { + "x": 177, + "y": 40, + "w": 65, + "ch": 5, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Karma": { + "x": 256, + "y": 40, + "w": 79, + "ch": 5, + "s": ["300", "400", "500", "600", "700"] + }, + "Katibeh": { "x": 349, "y": 40, "w": 67, "ch": 5, "s": ["400"] }, + "Kaushan Script": { "x": 430, "y": 40, "w": 159, "ch": 5, "s": ["400"] }, + "Kavivanar": { "x": 603, "y": 40, "w": 107, "ch": 5, "s": ["400"] }, + "Kavoon": { "x": 724, "y": 40, "w": 95, "ch": 5, "s": ["400"] }, + "Kay Pho Du": { + "x": 833, + "y": 40, + "w": 132, + "ch": 5, + "s": ["400", "500", "600", "700"] + }, + "Kdam Thmor Pro": { "x": 979, "y": 40, "w": 189, "ch": 5, "s": ["400"] }, + "Keania One": { "x": 0, "y": 80, "w": 126, "ch": 5, "s": ["400"] }, + "Kedebideri": { + "x": 140, + "y": 80, + "w": 120, + "ch": 5, + "s": ["400", "500", "600", "700", "800", "900"] + }, + "Kelly Slab": { "x": 274, "y": 80, "w": 115, "ch": 5, "s": ["400"] }, + "Kenia": { "x": 403, "y": 80, "w": 57, "ch": 5, "s": ["400"] }, + "Khand": { + "x": 474, + "y": 80, + "w": 61, + "ch": 5, + "s": ["300", "400", "500", "600", "700"] + }, + "Khula": { + "x": 549, + "y": 80, + "w": 68, + "ch": 5, + "s": ["300", "400", "600", "700", "800"] + }, + "Kings": { "x": 631, "y": 80, "w": 63, "ch": 5, "s": ["400"] }, + "Kirang Haerang": { "x": 708, "y": 80, "w": 145, "ch": 5, "s": ["400"] }, + "Kite One": { "x": 867, "y": 80, "w": 97, "ch": 5, "s": ["400"] }, + "Kiwi Maru": { + "x": 978, + "y": 80, + "w": 135, + "ch": 5, + "s": ["300", "400", "500"] + }, + "Klee One": { "x": 0, "y": 120, "w": 109, "ch": 5, "s": ["400", "600"] }, + "Knewave": { "x": 123, "y": 120, "w": 103, "ch": 5, "s": ["400"] }, + "Kodchasan": { + "x": 240, + "y": 120, + "w": 139, + "ch": 5, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Kode Mono": { + "x": 393, + "y": 120, + "w": 138, + "ch": 5, + "s": ["400", "500", "600", "700"] + }, + "Koh Santepheap": { + "x": 545, + "y": 120, + "w": 195, + "ch": 5, + "s": ["100", "300", "400", "700", "900"] + }, + "KoHo": { + "x": 754, + "y": 120, + "w": 66, + "ch": 5, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Kolker Brush": { "x": 834, "y": 120, "w": 84, "ch": 5, "s": ["400"] }, + "Konkhmer Sleokchher": { + "x": 0, + "y": 160, + "w": 269, + "ch": 5, + "s": ["400"] + }, + "Kosugi": { "x": 283, "y": 160, "w": 80, "ch": 5, "s": ["400"] }, + "Kosugi Maru": { "x": 377, "y": 160, "w": 140, "ch": 5, "s": ["400"] }, + "Kotta One": { "x": 531, "y": 160, "w": 110, "ch": 5, "s": ["400"] }, + "Koulen": { "x": 655, "y": 160, "w": 72, "ch": 5, "s": ["400"] }, + "Kranky": { "x": 741, "y": 160, "w": 87, "ch": 5, "s": ["400"] }, + "Kreon": { + "x": 842, + "y": 160, + "w": 68, + "ch": 5, + "s": ["300", "400", "500", "600", "700"] + }, + "Kristi": { "x": 924, "y": 160, "w": 46, "ch": 5, "s": ["400"] }, + "Krona One": { "x": 984, "y": 160, "w": 173, "ch": 5, "s": ["400"] }, + "Krub": { + "x": 0, + "y": 200, + "w": 62, + "ch": 5, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Kufam": { + "x": 76, + "y": 200, + "w": 84, + "ch": 5, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Kulim Park": { + "x": 174, + "y": 200, + "w": 116, + "ch": 5, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "600", + "600i", + "700", + "700i" + ] + }, + "Kumar One": { "x": 304, "y": 200, "w": 167, "ch": 5, "s": ["400"] }, + "Kumar One Outline": { + "x": 485, + "y": 200, + "w": 277, + "ch": 5, + "s": ["400"] + }, + "Kumbh Sans": { + "x": 776, + "y": 200, + "w": 147, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Kurale": { "x": 937, "y": 200, "w": 77, "ch": 5, "s": ["400"] }, + "La Belle Aurore": { "x": 1028, "y": 200, "w": 170, "ch": 5, "s": ["400"] }, + "Labrada": { + "x": 0, + "y": 240, + "w": 91, + "ch": 5, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Lacquer": { "x": 105, "y": 240, "w": 102, "ch": 5, "s": ["400"] }, + "Laila": { + "x": 221, + "y": 240, + "w": 60, + "ch": 5, + "s": ["300", "400", "500", "600", "700"] + }, + "Lakki Reddy": { "x": 295, "y": 240, "w": 138, "ch": 5, "s": ["400"] }, + "Lalezar": { "x": 447, "y": 240, "w": 82, "ch": 5, "s": ["400"] }, + "Lancelot": { "x": 543, "y": 240, "w": 80, "ch": 5, "s": ["400"] }, + "Langar": { "x": 637, "y": 240, "w": 83, "ch": 5, "s": ["400"] }, + "Lateef": { + "x": 734, + "y": 240, + "w": 57, + "ch": 5, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Lato": { + "x": 805, + "y": 240, + "w": 55, + "ch": 5, + "s": [ + "100", + "100i", + "300", + "300i", + "400", + "400i", + "700", + "700i", + "900", + "900i" + ] + }, + "Lavishly Yours": { "x": 874, "y": 240, "w": 135, "ch": 5, "s": ["400"] }, + "League Gothic": { "x": 1023, "y": 240, "w": 97, "ch": 5, "s": ["400"] }, + "League Script": { "x": 0, "y": 280, "w": 156, "ch": 5, "s": ["400"] }, + "League Spartan": { + "x": 170, + "y": 280, + "w": 166, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Leckerli One": { "x": 350, "y": 280, "w": 144, "ch": 5, "s": ["400"] }, + "Ledger": { "x": 508, "y": 280, "w": 91, "ch": 5, "s": ["400"] }, + "Lekton": { + "x": 613, + "y": 280, + "w": 80, + "ch": 5, + "s": ["400", "400i", "700", "700i"] + }, + "Lemon": { "x": 707, "y": 280, "w": 100, "ch": 5, "s": ["400"] }, + "Lemonada": { + "x": 821, + "y": 280, + "w": 153, + "ch": 5, + "s": ["300", "400", "500", "600", "700"] + }, + "Lexend": { + "x": 988, + "y": 280, + "w": 93, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Lexend Deca": { + "x": 0, + "y": 320, + "w": 159, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Lexend Exa": { + "x": 173, + "y": 320, + "w": 176, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Lexend Giga": { + "x": 363, + "y": 320, + "w": 205, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Lexend Mega": { + "x": 582, + "y": 320, + "w": 223, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Lexend Peta": { + "x": 819, + "y": 320, + "w": 218, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Lexend Tera": { + "x": 0, + "y": 360, + "w": 221, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Lexend Zetta": { + "x": 235, + "y": 360, + "w": 260, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Libertinus Keyboard": { + "x": 509, + "y": 360, + "w": 490, + "ch": 5, + "s": ["400"] + }, + "Libertinus Math": { "x": 1013, "y": 360, "w": 166, "ch": 5, "s": ["400"] }, + "Libertinus Mono": { "x": 0, "y": 400, "w": 239, "ch": 5, "s": ["400"] }, + "Libertinus Sans": { + "x": 253, + "y": 400, + "w": 161, + "ch": 5, + "s": ["400", "400i", "700", "700i"] + }, + "Libertinus Serif": { + "x": 428, + "y": 400, + "w": 160, + "ch": 5, + "s": ["400", "400i", "600", "600i", "700", "700i"] + }, + "Libertinus Serif Display": { + "x": 602, + "y": 400, + "w": 234, + "ch": 5, + "s": ["400"] + }, + "Libre Barcode 128": { + "x": 850, + "y": 400, + "w": 143, + "ch": 5, + "s": ["400"] + }, + "Libre Barcode 128 Text": { + "x": 1007, + "y": 400, + "w": 183, + "ch": 5, + "s": ["400"] + }, + "Libre Barcode 39": { "x": 0, "y": 440, "w": 193, "ch": 5, "s": ["400"] }, + "Libre Barcode 39 Extended": { + "x": 207, + "y": 440, + "w": 492, + "ch": 5, + "s": ["400"] + }, + "Libre Barcode 39 Extended Text": { + "x": 0, + "y": 480, + "w": 584, + "ch": 5, + "s": ["400"] + }, + "Libre Barcode 39 Text": { + "x": 598, + "y": 480, + "w": 250, + "ch": 5, + "s": ["400"] + }, + "Libre Barcode EAN13 Text": { + "x": 862, + "y": 480, + "w": 87, + "ch": 5, + "s": ["400"] + }, + "Libre Baskerville": { + "x": 963, + "y": 480, + "w": 216, + "ch": 5, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Libre Bodoni": { + "x": 0, + "y": 520, + "w": 153, + "ch": 5, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Libre Caslon Display": { + "x": 167, + "y": 520, + "w": 197, + "ch": 5, + "s": ["400"] + }, + "Libre Caslon Text": { + "x": 378, + "y": 520, + "w": 210, + "ch": 5, + "s": ["400", "400i", "700", "700i"] + }, + "Libre Franklin": { + "x": 602, + "y": 520, + "w": 159, + "ch": 5, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Licorice": { "x": 775, "y": 520, "w": 60, "ch": 5, "s": ["400"] }, + "Life Savers": { + "x": 849, + "y": 520, + "w": 119, + "ch": 5, + "s": ["400", "700", "800"] + }, + "Lilex": { + "x": 982, + "y": 520, + "w": 80, + "ch": 5, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Lilita One": { "x": 1076, "y": 520, "w": 106, "ch": 5, "s": ["400"] }, + "Lily Script One": { "x": 0, "y": 560, "w": 156, "ch": 5, "s": ["400"] }, + "Limelight": { "x": 170, "y": 560, "w": 132, "ch": 5, "s": ["400"] }, + "Linden Hill": { + "x": 316, + "y": 560, + "w": 113, + "ch": 5, + "s": ["400", "400i"] + }, + "LINE Seed JP": { + "x": 443, + "y": 560, + "w": 169, + "ch": 5, + "s": ["100", "400", "700", "800"] + }, + "Linefont": { + "x": 626, + "y": 560, + "w": 28, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Lisu Bosa": { + "x": 668, + "y": 560, + "w": 106, + "ch": 5, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Liter": { "x": 788, "y": 560, "w": 56, "ch": 5, "s": ["400"] }, + "Literata": { + "x": 858, + "y": 560, + "w": 99, + "ch": 5, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Liu Jian Mao Cao": { "x": 971, "y": 560, "w": 164, "ch": 5, "s": ["400"] }, + "Livvic": { + "x": 0, + "y": 600, + "w": 66, + "ch": 5, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "900", + "900i" + ] + }, + "Lobster": { "x": 80, "y": 600, "w": 72, "ch": 5, "s": ["400"] }, + "Lobster Two": { + "x": 166, + "y": 600, + "w": 114, + "ch": 5, + "s": ["400", "400i", "700", "700i"] + }, + "Londrina Outline": { "x": 294, "y": 600, "w": 156, "ch": 5, "s": ["400"] }, + "Londrina Shadow": { "x": 464, "y": 600, "w": 165, "ch": 5, "s": ["400"] }, + "Londrina Sketch": { "x": 643, "y": 600, "w": 156, "ch": 5, "s": ["400"] }, + "Londrina Solid": { + "x": 813, + "y": 600, + "w": 136, + "ch": 5, + "s": ["100", "300", "400", "900"] + }, + "Long Cang": { "x": 963, "y": 600, "w": 98, "ch": 5, "s": ["400"] }, + "Lora": { + "x": 1075, + "y": 600, + "w": 59, + "ch": 5, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Love Light": { "x": 0, "y": 640, "w": 88, "ch": 5, "s": ["400"] }, + "Love Ya Like A Sister": { + "x": 102, + "y": 640, + "w": 235, + "ch": 5, + "s": ["400"] + }, + "Loved by the King": { + "x": 351, + "y": 640, + "w": 145, + "ch": 5, + "s": ["400"] + }, + "Lovers Quarrel": { "x": 510, "y": 640, "w": 92, "ch": 5, "s": ["400"] }, + "Luckiest Guy": { "x": 616, "y": 640, "w": 156, "ch": 5, "s": ["400"] }, + "Lugrasimo": { "x": 786, "y": 640, "w": 144, "ch": 5, "s": ["400"] }, + "Lumanosimo": { "x": 944, "y": 640, "w": 185, "ch": 5, "s": ["400"] }, + "Lunasima": { "x": 0, "y": 680, "w": 119, "ch": 5, "s": ["400", "700"] }, + "Lusitana": { "x": 133, "y": 680, "w": 96, "ch": 5, "s": ["400", "700"] }, + "Lustria": { "x": 243, "y": 680, "w": 86, "ch": 5, "s": ["400"] }, + "Luxurious Roman": { "x": 343, "y": 680, "w": 201, "ch": 5, "s": ["400"] }, + "Luxurious Script": { "x": 558, "y": 680, "w": 128, "ch": 5, "s": ["400"] }, + "LXGW Marker Gothic": { + "x": 700, + "y": 680, + "w": 233, + "ch": 5, + "s": ["400"] + }, + "LXGW WenKai Mono TC": { + "x": 947, + "y": 680, + "w": 236, + "ch": 5, + "s": ["300", "400", "700"] + }, + "LXGW WenKai TC": { + "x": 0, + "y": 720, + "w": 206, + "ch": 5, + "s": ["300", "400", "700"] + }, + "M PLUS 1": { + "x": 220, + "y": 720, + "w": 120, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "M PLUS 1 Code": { + "x": 354, + "y": 720, + "w": 164, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "M PLUS 1p": { + "x": 532, + "y": 720, + "w": 129, + "ch": 5, + "s": ["100", "300", "400", "500", "700", "800", "900"] + }, + "M PLUS 2": { + "x": 675, + "y": 720, + "w": 120, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "M PLUS Code Latin": { + "x": 809, + "y": 720, + "w": 212, + "ch": 5, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "M PLUS Rounded 1c": { + "x": 0, + "y": 760, + "w": 225, + "ch": 5, + "s": ["100", "300", "400", "500", "700", "800", "900"] + }, + "Ma Shan Zheng": { "x": 239, "y": 760, "w": 126, "ch": 5, "s": ["400"] }, + "Macondo": { "x": 379, "y": 760, "w": 100, "ch": 5, "s": ["400"] }, + "Macondo Swash Caps": { + "x": 493, + "y": 760, + "w": 228, + "ch": 5, + "s": ["400"] + }, + "Mada": { + "x": 735, + "y": 760, + "w": 63, + "ch": 5, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Madimi One": { "x": 812, "y": 760, "w": 133, "ch": 5, "s": ["400"] }, + "Magra": { "x": 959, "y": 760, "w": 70, "ch": 5, "s": ["400", "700"] }, + "Maiden Orange": { "x": 1043, "y": 760, "w": 127, "ch": 5, "s": ["400"] }, + "Maitree": { + "x": 0, + "y": 0, + "w": 96, + "ch": 6, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Major Mono Display": { "x": 110, "y": 0, "w": 328, "ch": 6, "s": ["400"] }, + "Mako": { "x": 452, "y": 0, "w": 67, "ch": 6, "s": ["400"] }, + "Mali": { + "x": 533, + "y": 0, + "w": 56, + "ch": 6, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Mallanna": { "x": 603, "y": 0, "w": 96, "ch": 6, "s": ["400"] }, + "Maname": { "x": 713, "y": 0, "w": 100, "ch": 6, "s": ["400"] }, + "Mandali": { "x": 827, "y": 0, "w": 94, "ch": 6, "s": ["400"] }, + "Manjari": { + "x": 935, + "y": 0, + "w": 87, + "ch": 6, + "s": ["100", "400", "700"] + }, + "Manrope": { + "x": 1036, + "y": 0, + "w": 107, + "ch": 6, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Mansalva": { "x": 0, "y": 40, "w": 111, "ch": 6, "s": ["400"] }, + "Manuale": { + "x": 125, + "y": 40, + "w": 101, + "ch": 6, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Manufacturing Consent": { + "x": 240, + "y": 40, + "w": 214, + "ch": 6, + "s": ["400"] + }, + "Marcellus": { "x": 468, "y": 40, "w": 114, "ch": 6, "s": ["400"] }, + "Marcellus SC": { "x": 596, "y": 40, "w": 157, "ch": 6, "s": ["400"] }, + "Marck Script": { "x": 767, "y": 40, "w": 137, "ch": 6, "s": ["400"] }, + "Margarine": { "x": 918, "y": 40, "w": 119, "ch": 6, "s": ["400"] }, + "Marhey": { + "x": 1051, + "y": 40, + "w": 107, + "ch": 6, + "s": ["300", "400", "500", "600", "700"] + }, + "Markazi Text": { + "x": 0, + "y": 80, + "w": 113, + "ch": 6, + "s": ["400", "500", "600", "700"] + }, + "Marko One": { "x": 127, "y": 80, "w": 152, "ch": 6, "s": ["400"] }, + "Marmelad": { "x": 293, "y": 80, "w": 118, "ch": 6, "s": ["400"] }, + "Martel": { + "x": 425, + "y": 80, + "w": 88, + "ch": 6, + "s": ["200", "300", "400", "600", "700", "800", "900"] + }, + "Martel Sans": { + "x": 527, + "y": 80, + "w": 143, + "ch": 6, + "s": ["200", "300", "400", "600", "700", "800", "900"] + }, + "Martian Mono": { + "x": 684, + "y": 80, + "w": 210, + "ch": 6, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Marvel": { + "x": 908, + "y": 80, + "w": 64, + "ch": 6, + "s": ["400", "400i", "700", "700i"] + }, + "Matangi": { + "x": 986, + "y": 80, + "w": 99, + "ch": 6, + "s": ["300", "400", "500", "600", "700", "800", "900"] + }, + "Mate": { "x": 1099, "y": 80, "w": 61, "ch": 6, "s": ["400", "400i"] }, + "Mate SC": { "x": 0, "y": 120, "w": 99, "ch": 6, "s": ["400"] }, + "Matemasie": { "x": 113, "y": 120, "w": 145, "ch": 6, "s": ["400"] }, + "Material Icons": { "x": 272, "y": 120, "w": 344, "ch": 6, "s": ["400"] }, + "Material Icons Outlined": { + "x": 630, + "y": 120, + "w": 560, + "ch": 6, + "s": ["400"] + }, + "Material Icons Round": { + "x": 0, + "y": 160, + "w": 488, + "ch": 6, + "s": ["400"] + }, + "Material Icons Sharp": { + "x": 502, + "y": 160, + "w": 488, + "ch": 6, + "s": ["400"] + }, + "Material Icons Two Tone": { + "x": 0, + "y": 200, + "w": 560, + "ch": 6, + "s": ["400"] + }, + "Material Symbols": { + "x": 574, + "y": 200, + "w": 392, + "ch": 6, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "Material Symbols Outlined": { + "x": 0, + "y": 240, + "w": 608, + "ch": 6, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "Material Symbols Rounded": { + "x": 0, + "y": 280, + "w": 584, + "ch": 6, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "Material Symbols Sharp": { + "x": 598, + "y": 280, + "w": 536, + "ch": 6, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "Maven Pro": { + "x": 0, + "y": 320, + "w": 123, + "ch": 6, + "s": ["400", "500", "600", "700", "800", "900"] + }, + "McLaren": { "x": 137, "y": 320, "w": 118, "ch": 6, "s": ["400"] }, + "Mea Culpa": { "x": 269, "y": 320, "w": 102, "ch": 6, "s": ["400"] }, + "Meddon": { "x": 385, "y": 320, "w": 129, "ch": 6, "s": ["400"] }, + "MedievalSharp": { "x": 528, "y": 320, "w": 170, "ch": 6, "s": ["400"] }, + "Medula One": { "x": 712, "y": 320, "w": 78, "ch": 6, "s": ["400"] }, + "Meera Inimai": { "x": 804, "y": 320, "w": 138, "ch": 6, "s": ["400"] }, + "Megrim": { "x": 956, "y": 320, "w": 87, "ch": 6, "s": ["400"] }, + "Meie Script": { "x": 1057, "y": 320, "w": 133, "ch": 6, "s": ["400"] }, + "Menbere": { + "x": 0, + "y": 360, + "w": 112, + "ch": 6, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "Meow Script": { "x": 126, "y": 360, "w": 119, "ch": 6, "s": ["400"] }, + "Merienda": { + "x": 259, + "y": 360, + "w": 121, + "ch": 6, + "s": ["300", "400", "500", "600", "700", "800", "900"] + }, + "Merienda One": { "x": 394, "y": 360, "w": 177, "ch": 6, "s": ["400"] }, + "Merriweather": { + "x": 585, + "y": 360, + "w": 168, + "ch": 6, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Merriweather Sans": { + "x": 767, + "y": 360, + "w": 223, + "ch": 6, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Metal": { "x": 1004, "y": 360, "w": 58, "ch": 6, "s": ["400"] }, + "Metal Mania": { "x": 1076, "y": 360, "w": 121, "ch": 6, "s": ["400"] }, + "Metamorphous": { "x": 0, "y": 400, "w": 210, "ch": 6, "s": ["400"] }, + "Metrophobic": { "x": 224, "y": 400, "w": 149, "ch": 6, "s": ["400"] }, + "Michroma": { "x": 387, "y": 400, "w": 161, "ch": 6, "s": ["400"] }, + "Micro 5": { "x": 562, "y": 400, "w": 65, "ch": 6, "s": ["400"] }, + "Micro 5 Charted": { "x": 641, "y": 400, "w": 126, "ch": 6, "s": ["400"] }, + "Milonga": { "x": 781, "y": 400, "w": 99, "ch": 6, "s": ["400"] }, + "Miltonian": { "x": 894, "y": 400, "w": 130, "ch": 6, "s": ["400"] }, + "Miltonian Tattoo": { "x": 0, "y": 440, "w": 214, "ch": 6, "s": ["400"] }, + "Mina": { "x": 228, "y": 440, "w": 58, "ch": 6, "s": ["400", "700"] }, + "Mingzat": { "x": 300, "y": 440, "w": 99, "ch": 6, "s": ["400"] }, + "Miniver": { "x": 413, "y": 440, "w": 89, "ch": 6, "s": ["400"] }, + "Miriam Libre": { + "x": 516, + "y": 440, + "w": 161, + "ch": 6, + "s": ["400", "500", "600", "700"] + }, + "Mirza": { + "x": 691, + "y": 440, + "w": 60, + "ch": 6, + "s": ["400", "500", "600", "700"] + }, + "Miss Fajardose": { "x": 765, "y": 440, "w": 108, "ch": 6, "s": ["400"] }, + "Mitr": { + "x": 887, + "y": 440, + "w": 55, + "ch": 6, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Mochiy Pop One": { "x": 956, "y": 440, "w": 223, "ch": 6, "s": ["400"] }, + "Mochiy Pop P One": { "x": 0, "y": 480, "w": 248, "ch": 6, "s": ["400"] }, + "Modak": { "x": 262, "y": 480, "w": 79, "ch": 6, "s": ["400"] }, + "Modern Antiqua": { "x": 355, "y": 480, "w": 196, "ch": 6, "s": ["400"] }, + "Moderustic": { + "x": 565, + "y": 480, + "w": 135, + "ch": 6, + "s": ["300", "400", "500", "600", "700", "800"] + }, + "Mogra": { "x": 714, "y": 480, "w": 80, "ch": 6, "s": ["400"] }, + "Mohave": { + "x": 808, + "y": 480, + "w": 77, + "ch": 6, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Moirai One": { "x": 899, "y": 480, "w": 146, "ch": 6, "s": ["400"] }, + "Molengo": { "x": 1059, "y": 480, "w": 96, "ch": 6, "s": ["400"] }, + "Momo Signature": { "x": 0, "y": 520, "w": 227, "ch": 6, "s": ["400"] }, + "Momo Trust Display": { + "x": 241, + "y": 520, + "w": 249, + "ch": 6, + "s": ["400"] + }, + "Momo Trust Sans": { + "x": 504, + "y": 520, + "w": 207, + "ch": 6, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Mona Sans": { + "x": 725, + "y": 520, + "w": 136, + "ch": 6, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Monda": { + "x": 875, + "y": 520, + "w": 85, + "ch": 6, + "s": ["400", "500", "600", "700"] + }, + "Monofett": { "x": 974, "y": 520, "w": 119, "ch": 6, "s": ["400"] }, + "Monomakh": { "x": 0, "y": 560, "w": 138, "ch": 6, "s": ["400"] }, + "Monomaniac One": { "x": 152, "y": 560, "w": 156, "ch": 6, "s": ["400"] }, + "Monoton": { "x": 322, "y": 560, "w": 143, "ch": 6, "s": ["400"] }, + "Monsieur La Doulaise": { + "x": 479, + "y": 560, + "w": 215, + "ch": 6, + "s": ["400"] + }, + "Montaga": { "x": 708, "y": 560, "w": 102, "ch": 6, "s": ["400"] }, + "Montagu Slab": { + "x": 824, + "y": 560, + "w": 173, + "ch": 6, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "MonteCarlo": { "x": 1011, "y": 560, "w": 107, "ch": 6, "s": ["400"] }, + "Montez": { "x": 1132, "y": 560, "w": 66, "ch": 6, "s": ["400"] }, + "Montserrat": { + "x": 0, + "y": 600, + "w": 141, + "ch": 6, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Montserrat Alternates": { + "x": 155, + "y": 600, + "w": 282, + "ch": 6, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Montserrat Subrayada": { + "x": 451, + "y": 600, + "w": 347, + "ch": 6, + "s": ["400", "700"] + }, + "Montserrat Underline": { + "x": 812, + "y": 600, + "w": 266, + "ch": 6, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Moo Lah Lah": { "x": 0, "y": 640, "w": 137, "ch": 6, "s": ["400"] }, + "Mooli": { "x": 151, "y": 640, "w": 77, "ch": 6, "s": ["400"] }, + "Moon Dance": { "x": 242, "y": 640, "w": 106, "ch": 6, "s": ["400"] }, + "Moul": { "x": 362, "y": 640, "w": 82, "ch": 6, "s": ["400"] }, + "Moulpali": { "x": 458, "y": 640, "w": 90, "ch": 6, "s": ["400"] }, + "Mountains of Christmas": { + "x": 562, + "y": 640, + "w": 219, + "ch": 6, + "s": ["400", "700"] + }, + "Mouse Memoirs": { "x": 795, "y": 640, "w": 116, "ch": 6, "s": ["400"] }, + "Mozilla Headline": { + "x": 925, + "y": 640, + "w": 192, + "ch": 6, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Mozilla Text": { + "x": 0, + "y": 680, + "w": 148, + "ch": 6, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Mr Bedfort": { "x": 162, "y": 680, "w": 124, "ch": 6, "s": ["400"] }, + "Mr Dafoe": { "x": 300, "y": 680, "w": 92, "ch": 6, "s": ["400"] }, + "Mr De Haviland": { "x": 406, "y": 680, "w": 115, "ch": 6, "s": ["400"] }, + "Mrs Saint Delafield": { + "x": 535, + "y": 680, + "w": 159, + "ch": 6, + "s": ["400"] + }, + "Mrs Sheppards": { "x": 708, "y": 680, "w": 139, "ch": 6, "s": ["400"] }, + "Ms Madi": { "x": 861, "y": 680, "w": 89, "ch": 6, "s": ["400"] }, + "Mukta": { + "x": 964, + "y": 680, + "w": 73, + "ch": 6, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Mukta Mahee": { + "x": 1051, + "y": 680, + "w": 146, + "ch": 6, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Mukta Malar": { + "x": 0, + "y": 720, + "w": 137, + "ch": 6, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Mukta Vaani": { + "x": 151, + "y": 720, + "w": 133, + "ch": 6, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Mulish": { + "x": 298, + "y": 720, + "w": 80, + "ch": 6, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Murecho": { + "x": 392, + "y": 720, + "w": 102, + "ch": 6, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "MuseoModerno": { + "x": 508, + "y": 720, + "w": 199, + "ch": 6, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "My Soul": { "x": 721, "y": 720, "w": 107, "ch": 6, "s": ["400"] }, + "Mynerve": { "x": 842, "y": 720, "w": 104, "ch": 6, "s": ["400"] }, + "Mystery Quest": { "x": 960, "y": 720, "w": 152, "ch": 6, "s": ["400"] }, + "Nabla": { "x": 1126, "y": 720, "w": 72, "ch": 6, "s": ["400"] }, + "Namdhinggo": { + "x": 0, + "y": 760, + "w": 130, + "ch": 6, + "s": ["400", "500", "600", "700", "800"] + }, + "Nanum Brush Script": { + "x": 144, + "y": 760, + "w": 168, + "ch": 6, + "s": ["400"] + }, + "Nanum Gothic": { + "x": 326, + "y": 760, + "w": 171, + "ch": 6, + "s": ["400", "700", "800"] + }, + "Nanum Gothic Coding": { + "x": 511, + "y": 760, + "w": 236, + "ch": 6, + "s": ["400", "700"] + }, + "Nanum Myeongjo": { + "x": 761, + "y": 760, + "w": 201, + "ch": 6, + "s": ["400", "700", "800"] + }, + "Nanum Pen Script": { "x": 976, "y": 760, "w": 157, "ch": 6, "s": ["400"] }, + "Narnoor": { + "x": 0, + "y": 0, + "w": 90, + "ch": 7, + "s": ["400", "500", "600", "700", "800"] + }, + "Nata Sans": { + "x": 104, + "y": 0, + "w": 123, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "National Park": { + "x": 241, + "y": 0, + "w": 149, + "ch": 7, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Neonderthaw": { "x": 404, "y": 0, "w": 139, "ch": 7, "s": ["400"] }, + "Nerko One": { "x": 557, "y": 0, "w": 107, "ch": 7, "s": ["400"] }, + "Neucha": { "x": 678, "y": 0, "w": 72, "ch": 7, "s": ["400"] }, + "Neuton": { + "x": 764, + "y": 0, + "w": 76, + "ch": 7, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "700", + "700i", + "800", + "800i" + ] + }, + "New Amsterdam": { "x": 854, "y": 0, "w": 139, "ch": 7, "s": ["400"] }, + "New Rocker": { "x": 1007, "y": 0, "w": 133, "ch": 7, "s": ["400"] }, + "New Tegomin": { "x": 0, "y": 40, "w": 159, "ch": 7, "s": ["400"] }, + "News Cycle": { "x": 173, "y": 40, "w": 116, "ch": 7, "s": ["400", "700"] }, + "Newsreader": { + "x": 303, + "y": 40, + "w": 129, + "ch": 7, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Niconne": { "x": 446, "y": 40, "w": 79, "ch": 7, "s": ["400"] }, + "Niramit": { + "x": 539, + "y": 40, + "w": 84, + "ch": 7, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Nixie One": { "x": 637, "y": 40, "w": 123, "ch": 7, "s": ["400"] }, + "Nobile": { + "x": 774, + "y": 40, + "w": 82, + "ch": 7, + "s": ["400", "400i", "500", "500i", "700", "700i"] + }, + "Nokora": { + "x": 870, + "y": 40, + "w": 91, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Norican": { "x": 975, "y": 40, "w": 78, "ch": 7, "s": ["400"] }, + "Nosifer": { "x": 0, "y": 80, "w": 154, "ch": 7, "s": ["400"] }, + "Notable": { "x": 168, "y": 80, "w": 144, "ch": 7, "s": ["400"] }, + "Nothing You Could Do": { + "x": 326, + "y": 80, + "w": 248, + "ch": 7, + "s": ["400"] + }, + "Noticia Text": { + "x": 588, + "y": 80, + "w": 142, + "ch": 7, + "s": ["400", "400i", "700", "700i"] + }, + "Noto Kufi Arabic": { + "x": 744, + "y": 80, + "w": 196, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Music": { "x": 954, "y": 80, "w": 136, "ch": 7, "s": ["400"] }, + "Noto Naskh Arabic": { + "x": 0, + "y": 120, + "w": 221, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Nastaliq Urdu": { + "x": 235, + "y": 120, + "w": 222, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Rashi Hebrew": { + "x": 471, + "y": 120, + "w": 230, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans": { + "x": 715, + "y": 120, + "w": 124, + "ch": 7, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Noto Sans Adlam": { + "x": 853, + "y": 120, + "w": 202, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Adlam Unjoined": { + "x": 0, + "y": 160, + "w": 310, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Anatolian Hieroglyphs": { + "x": 324, + "y": 160, + "w": 380, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Arabic": { + "x": 718, + "y": 160, + "w": 201, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Armenian": { + "x": 933, + "y": 160, + "w": 240, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Avestan": { "x": 0, "y": 200, "w": 219, "ch": 7, "s": ["400"] }, + "Noto Sans Balinese": { + "x": 233, + "y": 200, + "w": 225, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Bamum": { + "x": 472, + "y": 200, + "w": 219, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Bassa Vah": { + "x": 705, + "y": 200, + "w": 250, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Batak": { "x": 969, "y": 200, "w": 194, "ch": 7, "s": ["400"] }, + "Noto Sans Bengali": { + "x": 0, + "y": 240, + "w": 214, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Bhaiksuki": { + "x": 228, + "y": 240, + "w": 236, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Brahmi": { "x": 478, "y": 240, "w": 212, "ch": 7, "s": ["400"] }, + "Noto Sans Buginese": { + "x": 704, + "y": 240, + "w": 235, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Buhid": { "x": 953, "y": 240, "w": 196, "ch": 7, "s": ["400"] }, + "Noto Sans Canadian Aboriginal": { + "x": 0, + "y": 280, + "w": 361, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Carian": { "x": 375, "y": 280, "w": 203, "ch": 7, "s": ["400"] }, + "Noto Sans Caucasian Albanian": { + "x": 592, + "y": 280, + "w": 353, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Chakma": { "x": 959, "y": 280, "w": 222, "ch": 7, "s": ["400"] }, + "Noto Sans Cham": { + "x": 0, + "y": 320, + "w": 196, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Cherokee": { + "x": 210, + "y": 320, + "w": 238, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Chorasmian": { + "x": 462, + "y": 320, + "w": 268, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Coptic": { "x": 744, "y": 320, "w": 201, "ch": 7, "s": ["400"] }, + "Noto Sans Cuneiform": { + "x": 0, + "y": 360, + "w": 250, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Cypriot": { + "x": 264, + "y": 360, + "w": 211, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Cypro Minoan": { + "x": 489, + "y": 360, + "w": 288, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Deseret": { + "x": 791, + "y": 360, + "w": 218, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Devanagari": { + "x": 0, + "y": 400, + "w": 259, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Display": { + "x": 273, + "y": 400, + "w": 200, + "ch": 7, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Noto Sans Duployan": { + "x": 487, + "y": 400, + "w": 238, + "ch": 7, + "s": ["400", "700"] + }, + "Noto Sans Egyptian Hieroglyphs": { + "x": 739, + "y": 400, + "w": 369, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Elbasan": { "x": 0, "y": 440, "w": 217, "ch": 7, "s": ["400"] }, + "Noto Sans Elymaic": { + "x": 231, + "y": 440, + "w": 222, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Ethiopic": { + "x": 467, + "y": 440, + "w": 220, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Georgian": { + "x": 701, + "y": 440, + "w": 234, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Glagolitic": { + "x": 949, + "y": 440, + "w": 235, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Gothic": { "x": 0, "y": 480, "w": 203, "ch": 7, "s": ["400"] }, + "Noto Sans Grantha": { + "x": 217, + "y": 480, + "w": 222, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Gujarati": { + "x": 453, + "y": 480, + "w": 220, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Gunjala Gondi": { + "x": 687, + "y": 480, + "w": 291, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Gurmukhi": { + "x": 0, + "y": 520, + "w": 243, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Hanifi Rohingya": { + "x": 257, + "y": 520, + "w": 309, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Hanunoo": { + "x": 580, + "y": 520, + "w": 235, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Hatran": { "x": 829, "y": 520, "w": 208, "ch": 7, "s": ["400"] }, + "Noto Sans Hebrew": { + "x": 0, + "y": 560, + "w": 217, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans HK": { + "x": 231, + "y": 560, + "w": 160, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Imperial Aramaic": { + "x": 405, + "y": 560, + "w": 323, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Indic Siyaq Numbers": { + "x": 742, + "y": 560, + "w": 351, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Inscriptional Pahlavi": { + "x": 0, + "y": 600, + "w": 358, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Inscriptional Parthian": { + "x": 372, + "y": 600, + "w": 373, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Javanese": { + "x": 759, + "y": 600, + "w": 229, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans JP": { + "x": 1002, + "y": 600, + "w": 155, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Kaithi": { "x": 0, "y": 640, "w": 195, "ch": 7, "s": ["400"] }, + "Noto Sans Kannada": { + "x": 209, + "y": 640, + "w": 231, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Kawi": { + "x": 454, + "y": 640, + "w": 180, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Kayah Li": { + "x": 648, + "y": 640, + "w": 224, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Kharoshthi": { + "x": 886, + "y": 640, + "w": 253, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Khmer": { + "x": 0, + "y": 680, + "w": 205, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Khojki": { "x": 219, "y": 680, "w": 199, "ch": 7, "s": ["400"] }, + "Noto Sans Khudawadi": { + "x": 432, + "y": 680, + "w": 256, + "ch": 7, + "s": ["400"] + }, + "Noto Sans KR": { + "x": 702, + "y": 680, + "w": 158, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Lao": { + "x": 874, + "y": 680, + "w": 170, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Lao Looped": { + "x": 0, + "y": 720, + "w": 261, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Lepcha": { "x": 275, "y": 720, "w": 211, "ch": 7, "s": ["400"] }, + "Noto Sans Limbu": { "x": 500, "y": 720, "w": 201, "ch": 7, "s": ["400"] }, + "Noto Sans Linear A": { + "x": 715, + "y": 720, + "w": 222, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Linear B": { + "x": 951, + "y": 720, + "w": 222, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Lisu": { + "x": 0, + "y": 760, + "w": 175, + "ch": 7, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Lydian": { "x": 189, "y": 760, "w": 204, "ch": 7, "s": ["400"] }, + "Noto Sans Mahajani": { + "x": 407, + "y": 760, + "w": 234, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Malayalam": { + "x": 655, + "y": 760, + "w": 255, + "ch": 7, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Mandaic": { + "x": 924, + "y": 760, + "w": 226, + "ch": 7, + "s": ["400"] + }, + "Noto Sans Manichaean": { "x": 0, "y": 0, "w": 268, "ch": 8, "s": ["400"] }, + "Noto Sans Marchen": { "x": 282, "y": 0, "w": 227, "ch": 8, "s": ["400"] }, + "Noto Sans Masaram Gondi": { + "x": 523, + "y": 0, + "w": 318, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Math": { "x": 855, "y": 0, "w": 189, "ch": 8, "s": ["400"] }, + "Noto Sans Mayan Numerals": { + "x": 0, + "y": 40, + "w": 322, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Medefaidrin": { + "x": 336, + "y": 40, + "w": 267, + "ch": 8, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Meetei Mayek": { + "x": 617, + "y": 40, + "w": 289, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Mende Kikakui": { + "x": 0, + "y": 80, + "w": 296, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Meroitic": { + "x": 310, + "y": 80, + "w": 222, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Miao": { "x": 546, "y": 80, "w": 186, "ch": 8, "s": ["400"] }, + "Noto Sans Modi": { "x": 746, "y": 80, "w": 187, "ch": 8, "s": ["400"] }, + "Noto Sans Mongolian": { + "x": 947, + "y": 80, + "w": 251, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Mono": { + "x": 0, + "y": 120, + "w": 210, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Mro": { "x": 224, "y": 120, "w": 176, "ch": 8, "s": ["400"] }, + "Noto Sans Multani": { + "x": 414, + "y": 120, + "w": 216, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Myanmar": { + "x": 644, + "y": 120, + "w": 238, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Nabataean": { + "x": 896, + "y": 120, + "w": 254, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Nag Mundari": { + "x": 0, + "y": 160, + "w": 274, + "ch": 8, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Nandinagari": { + "x": 288, + "y": 160, + "w": 272, + "ch": 8, + "s": ["400"] + }, + "Noto Sans New Tai Lue": { + "x": 574, + "y": 160, + "w": 265, + "ch": 8, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Newa": { "x": 853, "y": 160, "w": 194, "ch": 8, "s": ["400"] }, + "Noto Sans NKo": { "x": 0, "y": 200, "w": 178, "ch": 8, "s": ["400"] }, + "Noto Sans NKo Unjoined": { + "x": 192, + "y": 200, + "w": 286, + "ch": 8, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Nushu": { "x": 492, "y": 200, "w": 204, "ch": 8, "s": ["400"] }, + "Noto Sans Ogham": { "x": 710, "y": 200, "w": 214, "ch": 8, "s": ["400"] }, + "Noto Sans Ol Chiki": { + "x": 938, + "y": 200, + "w": 216, + "ch": 8, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Old Hungarian": { + "x": 0, + "y": 240, + "w": 296, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Old Italic": { + "x": 310, + "y": 240, + "w": 230, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Old North Arabian": { + "x": 554, + "y": 240, + "w": 336, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Old Permic": { + "x": 904, + "y": 240, + "w": 253, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Old Persian": { + "x": 0, + "y": 280, + "w": 260, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Old Sogdian": { + "x": 274, + "y": 280, + "w": 260, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Old South Arabian": { + "x": 548, + "y": 280, + "w": 336, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Old Turkic": { + "x": 898, + "y": 280, + "w": 243, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Oriya": { + "x": 0, + "y": 320, + "w": 190, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Osage": { "x": 204, "y": 320, "w": 202, "ch": 8, "s": ["400"] }, + "Noto Sans Osmanya": { + "x": 420, + "y": 320, + "w": 238, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Pahawh Hmong": { + "x": 672, + "y": 320, + "w": 310, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Palmyrene": { + "x": 0, + "y": 360, + "w": 251, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Pau Cin Hau": { + "x": 265, + "y": 360, + "w": 269, + "ch": 8, + "s": ["400"] + }, + "Noto Sans PhagsPa": { + "x": 548, + "y": 360, + "w": 227, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Phoenician": { + "x": 789, + "y": 360, + "w": 254, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Psalter Pahlavi": { + "x": 0, + "y": 400, + "w": 295, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Rejang": { "x": 309, "y": 400, "w": 208, "ch": 8, "s": ["400"] }, + "Noto Sans Runic": { "x": 531, "y": 400, "w": 192, "ch": 8, "s": ["400"] }, + "Noto Sans Samaritan": { + "x": 737, + "y": 400, + "w": 245, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Saurashtra": { + "x": 0, + "y": 440, + "w": 254, + "ch": 8, + "s": ["400"] + }, + "Noto Sans SC": { + "x": 268, + "y": 440, + "w": 157, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Sharada": { + "x": 439, + "y": 440, + "w": 222, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Shavian": { + "x": 675, + "y": 440, + "w": 218, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Siddham": { + "x": 907, + "y": 440, + "w": 227, + "ch": 8, + "s": ["400"] + }, + "Noto Sans SignWriting": { + "x": 0, + "y": 480, + "w": 297, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Sinhala": { + "x": 311, + "y": 480, + "w": 224, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Sogdian": { + "x": 549, + "y": 480, + "w": 216, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Sora Sompeng": { + "x": 779, + "y": 480, + "w": 295, + "ch": 8, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Soyombo": { "x": 0, "y": 520, "w": 236, "ch": 8, "s": ["400"] }, + "Noto Sans Sundanese": { + "x": 250, + "y": 520, + "w": 254, + "ch": 8, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Sunuwar": { + "x": 518, + "y": 520, + "w": 237, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Syloti Nagri": { + "x": 769, + "y": 520, + "w": 260, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Symbols": { + "x": 0, + "y": 560, + "w": 225, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Symbols 2": { + "x": 239, + "y": 560, + "w": 257, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Syriac": { + "x": 510, + "y": 560, + "w": 196, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Syriac Eastern": { + "x": 720, + "y": 560, + "w": 288, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Syriac Western": { + "x": 0, + "y": 600, + "w": 296, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Tagalog": { + "x": 310, + "y": 600, + "w": 218, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Tagbanwa": { + "x": 542, + "y": 600, + "w": 247, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Tai Le": { "x": 803, "y": 600, "w": 193, "ch": 8, "s": ["400"] }, + "Noto Sans Tai Tham": { + "x": 0, + "y": 640, + "w": 243, + "ch": 8, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Tai Viet": { + "x": 257, + "y": 640, + "w": 210, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Takri": { "x": 481, "y": 640, "w": 184, "ch": 8, "s": ["400"] }, + "Noto Sans Tamil": { + "x": 679, + "y": 640, + "w": 190, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Tamil Supplement": { + "x": 0, + "y": 680, + "w": 334, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Tangsa": { + "x": 348, + "y": 680, + "w": 209, + "ch": 8, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans TC": { + "x": 571, + "y": 680, + "w": 156, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Telugu": { + "x": 741, + "y": 680, + "w": 207, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Thaana": { + "x": 962, + "y": 680, + "w": 213, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Thai": { + "x": 0, + "y": 720, + "w": 178, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Thai Looped": { + "x": 192, + "y": 720, + "w": 269, + "ch": 8, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Sans Tifinagh": { + "x": 475, + "y": 720, + "w": 222, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Tirhuta": { + "x": 711, + "y": 720, + "w": 211, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Ugaritic": { + "x": 936, + "y": 720, + "w": 218, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Vai": { "x": 0, "y": 760, "w": 164, "ch": 8, "s": ["400"] }, + "Noto Sans Vithkuqi": { + "x": 178, + "y": 760, + "w": 223, + "ch": 8, + "s": ["400", "500", "600", "700"] + }, + "Noto Sans Wancho": { "x": 415, "y": 760, "w": 228, "ch": 8, "s": ["400"] }, + "Noto Sans Warang Citi": { + "x": 657, + "y": 760, + "w": 264, + "ch": 8, + "s": ["400"] + }, + "Noto Sans Yi": { "x": 935, "y": 760, "w": 149, "ch": 8, "s": ["400"] }, + "Noto Sans Zanabazar Square": { + "x": 0, + "y": 0, + "w": 334, + "ch": 9, + "s": ["400"] + }, + "Noto Serif": { + "x": 348, + "y": 0, + "w": 123, + "ch": 9, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Noto Serif Ahom": { "x": 485, "y": 0, "w": 196, "ch": 9, "s": ["400"] }, + "Noto Serif Armenian": { + "x": 695, + "y": 0, + "w": 245, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Balinese": { + "x": 954, + "y": 0, + "w": 225, + "ch": 9, + "s": ["400"] + }, + "Noto Serif Bengali": { + "x": 0, + "y": 40, + "w": 215, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Devanagari": { + "x": 229, + "y": 40, + "w": 261, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Display": { + "x": 504, + "y": 40, + "w": 212, + "ch": 9, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Noto Serif Dives Akuru": { + "x": 730, + "y": 40, + "w": 274, + "ch": 9, + "s": ["400"] + }, + "Noto Serif Dogra": { "x": 0, "y": 80, "w": 197, "ch": 9, "s": ["400"] }, + "Noto Serif Ethiopic": { + "x": 211, + "y": 80, + "w": 224, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Georgian": { + "x": 449, + "y": 80, + "w": 234, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Grantha": { + "x": 697, + "y": 80, + "w": 223, + "ch": 9, + "s": ["400"] + }, + "Noto Serif Gujarati": { + "x": 934, + "y": 80, + "w": 225, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Gurmukhi": { + "x": 0, + "y": 120, + "w": 248, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Hebrew": { + "x": 262, + "y": 120, + "w": 221, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Hentaigana": { + "x": 497, + "y": 120, + "w": 269, + "ch": 9, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif HK": { + "x": 780, + "y": 120, + "w": 170, + "ch": 9, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif JP": { + "x": 964, + "y": 120, + "w": 157, + "ch": 9, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Kannada": { + "x": 0, + "y": 160, + "w": 231, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Khitan Small Script": { + "x": 245, + "y": 160, + "w": 383, + "ch": 9, + "s": ["400"] + }, + "Noto Serif Khmer": { + "x": 642, + "y": 160, + "w": 208, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Khojki": { + "x": 864, + "y": 160, + "w": 204, + "ch": 9, + "s": ["400", "500", "600", "700"] + }, + "Noto Serif KR": { + "x": 0, + "y": 200, + "w": 167, + "ch": 9, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Lao": { + "x": 181, + "y": 200, + "w": 171, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Makasar": { + "x": 366, + "y": 200, + "w": 226, + "ch": 9, + "s": ["400"] + }, + "Noto Serif Malayalam": { + "x": 606, + "y": 200, + "w": 259, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif NP Hmong": { + "x": 879, + "y": 200, + "w": 252, + "ch": 9, + "s": ["400", "500", "600", "700"] + }, + "Noto Serif Old Uyghur": { + "x": 0, + "y": 240, + "w": 257, + "ch": 9, + "s": ["400"] + }, + "Noto Serif Oriya": { + "x": 271, + "y": 240, + "w": 200, + "ch": 9, + "s": ["400", "500", "600", "700"] + }, + "Noto Serif Ottoman Siyaq": { + "x": 485, + "y": 240, + "w": 298, + "ch": 9, + "s": ["400"] + }, + "Noto Serif SC": { + "x": 797, + "y": 240, + "w": 162, + "ch": 9, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Sinhala": { + "x": 973, + "y": 240, + "w": 227, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Tamil": { + "x": 0, + "y": 280, + "w": 194, + "ch": 9, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Noto Serif Tangut": { + "x": 208, + "y": 280, + "w": 220, + "ch": 9, + "s": ["400"] + }, + "Noto Serif TC": { + "x": 442, + "y": 280, + "w": 164, + "ch": 9, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Telugu": { + "x": 620, + "y": 280, + "w": 207, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Thai": { + "x": 841, + "y": 280, + "w": 180, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Tibetan": { + "x": 0, + "y": 320, + "w": 217, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Noto Serif Todhri": { + "x": 231, + "y": 320, + "w": 201, + "ch": 9, + "s": ["400"] + }, + "Noto Serif Toto": { + "x": 446, + "y": 320, + "w": 179, + "ch": 9, + "s": ["400", "500", "600", "700"] + }, + "Noto Serif Vithkuqi": { + "x": 639, + "y": 320, + "w": 228, + "ch": 9, + "s": ["400", "500", "600", "700"] + }, + "Noto Serif Yezidi": { + "x": 881, + "y": 320, + "w": 198, + "ch": 9, + "s": ["400", "500", "600", "700"] + }, + "Noto Traditional Nushu": { + "x": 0, + "y": 360, + "w": 289, + "ch": 9, + "s": ["300", "400", "500", "600", "700"] + }, + "Noto Znamenny Musical Notation": { + "x": 303, + "y": 360, + "w": 388, + "ch": 9, + "s": ["400"] + }, + "Nova Cut": { "x": 705, "y": 360, "w": 108, "ch": 9, "s": ["400"] }, + "Nova Flat": { "x": 827, "y": 360, "w": 112, "ch": 9, "s": ["400"] }, + "Nova Mono": { "x": 953, "y": 360, "w": 130, "ch": 9, "s": ["400"] }, + "Nova Oval": { "x": 0, "y": 400, "w": 123, "ch": 9, "s": ["400"] }, + "Nova Round": { "x": 137, "y": 400, "w": 140, "ch": 9, "s": ["400"] }, + "Nova Script": { "x": 291, "y": 400, "w": 140, "ch": 9, "s": ["400"] }, + "Nova Slim": { "x": 445, "y": 400, "w": 121, "ch": 9, "s": ["400"] }, + "Nova Square": { "x": 580, "y": 400, "w": 148, "ch": 9, "s": ["400"] }, + "NTR": { "x": 742, "y": 400, "w": 49, "ch": 9, "s": ["400"] }, + "Numans": { "x": 805, "y": 400, "w": 113, "ch": 9, "s": ["400"] }, + "Nunito": { + "x": 932, + "y": 400, + "w": 81, + "ch": 9, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Nunito Sans": { + "x": 1027, + "y": 400, + "w": 139, + "ch": 9, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Nuosu SIL": { "x": 0, "y": 440, "w": 117, "ch": 9, "s": ["400"] }, + "Odibee Sans": { "x": 131, "y": 440, "w": 95, "ch": 9, "s": ["400"] }, + "Odor Mean Chey": { "x": 240, "y": 440, "w": 184, "ch": 9, "s": ["400"] }, + "Offside": { "x": 438, "y": 440, "w": 96, "ch": 9, "s": ["400"] }, + "Oi": { "x": 548, "y": 440, "w": 47, "ch": 9, "s": ["400"] }, + "Ojuju": { + "x": 609, + "y": 440, + "w": 62, + "ch": 9, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Old Standard TT": { + "x": 685, + "y": 440, + "w": 187, + "ch": 9, + "s": ["400", "400i", "700", "700i"] + }, + "Oldenburg": { "x": 886, "y": 440, "w": 136, "ch": 9, "s": ["400"] }, + "Ole": { "x": 1036, "y": 440, "w": 33, "ch": 9, "s": ["400"] }, + "Oleo Script": { + "x": 1083, + "y": 440, + "w": 109, + "ch": 9, + "s": ["400", "700"] + }, + "Oleo Script Swash Caps": { + "x": 0, + "y": 480, + "w": 227, + "ch": 9, + "s": ["400", "700"] + }, + "Onest": { + "x": 241, + "y": 480, + "w": 76, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Oooh Baby": { "x": 331, "y": 480, "w": 109, "ch": 9, "s": ["400"] }, + "Open Sans": { + "x": 454, + "y": 480, + "w": 129, + "ch": 9, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Oranienbaum": { "x": 597, "y": 480, "w": 137, "ch": 9, "s": ["400"] }, + "Orbit": { "x": 748, "y": 480, "w": 80, "ch": 9, "s": ["400"] }, + "Orbitron": { + "x": 842, + "y": 480, + "w": 117, + "ch": 9, + "s": ["400", "500", "600", "700", "800", "900"] + }, + "Oregano": { "x": 973, "y": 480, "w": 78, "ch": 9, "s": ["400", "400i"] }, + "Orelega One": { "x": 0, "y": 520, "w": 138, "ch": 9, "s": ["400"] }, + "Orienta": { "x": 152, "y": 520, "w": 94, "ch": 9, "s": ["400"] }, + "Original Surfer": { "x": 260, "y": 520, "w": 175, "ch": 9, "s": ["400"] }, + "Oswald": { + "x": 449, + "y": 520, + "w": 70, + "ch": 9, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Outfit": { + "x": 533, + "y": 520, + "w": 71, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Over the Rainbow": { "x": 618, "y": 520, "w": 189, "ch": 9, "s": ["400"] }, + "Overlock": { + "x": 821, + "y": 520, + "w": 95, + "ch": 9, + "s": ["400", "400i", "700", "700i", "900", "900i"] + }, + "Overlock SC": { "x": 930, "y": 520, "w": 139, "ch": 9, "s": ["400"] }, + "Overpass": { + "x": 1083, + "y": 520, + "w": 109, + "ch": 9, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Overpass Mono": { + "x": 0, + "y": 560, + "w": 201, + "ch": 9, + "s": ["300", "400", "500", "600", "700"] + }, + "Ovo": { "x": 215, "y": 560, "w": 52, "ch": 9, "s": ["400"] }, + "Oxanium": { + "x": 281, + "y": 560, + "w": 106, + "ch": 9, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Oxygen": { + "x": 401, + "y": 560, + "w": 92, + "ch": 9, + "s": ["300", "400", "700"] + }, + "Oxygen Mono": { "x": 507, "y": 560, "w": 167, "ch": 9, "s": ["400"] }, + "Pacifico": { "x": 688, "y": 560, "w": 87, "ch": 9, "s": ["400"] }, + "Padauk": { "x": 789, "y": 560, "w": 79, "ch": 9, "s": ["400", "700"] }, + "Padyakke Expanded One": { + "x": 0, + "y": 600, + "w": 415, + "ch": 9, + "s": ["400"] + }, + "Palanquin": { + "x": 429, + "y": 600, + "w": 111, + "ch": 9, + "s": ["100", "200", "300", "400", "500", "600", "700"] + }, + "Palanquin Dark": { + "x": 554, + "y": 600, + "w": 172, + "ch": 9, + "s": ["400", "500", "600", "700"] + }, + "Palette Mosaic": { "x": 740, "y": 600, "w": 212, "ch": 9, "s": ["400"] }, + "Pangolin": { "x": 966, "y": 600, "w": 93, "ch": 9, "s": ["400"] }, + "Paprika": { "x": 1073, "y": 600, "w": 105, "ch": 9, "s": ["400"] }, + "Parastoo": { + "x": 0, + "y": 640, + "w": 82, + "ch": 9, + "s": ["400", "500", "600", "700"] + }, + "Parisienne": { "x": 96, "y": 640, "w": 103, "ch": 9, "s": ["400"] }, + "Parkinsans": { + "x": 213, + "y": 640, + "w": 139, + "ch": 9, + "s": ["300", "400", "500", "600", "700", "800"] + }, + "Passero One": { "x": 366, "y": 640, "w": 125, "ch": 9, "s": ["400"] }, + "Passion One": { + "x": 505, + "y": 640, + "w": 114, + "ch": 9, + "s": ["400", "700", "900"] + }, + "Passions Conflict": { + "x": 633, + "y": 640, + "w": 109, + "ch": 9, + "s": ["400"] + }, + "Pathway Extreme": { + "x": 756, + "y": 640, + "w": 214, + "ch": 9, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Pathway Gothic One": { + "x": 984, + "y": 640, + "w": 154, + "ch": 9, + "s": ["400"] + }, + "Patrick Hand": { "x": 0, "y": 680, "w": 118, "ch": 9, "s": ["400"] }, + "Patrick Hand SC": { "x": 132, "y": 680, "w": 154, "ch": 9, "s": ["400"] }, + "Pattaya": { "x": 300, "y": 680, "w": 85, "ch": 9, "s": ["400"] }, + "Patua One": { "x": 399, "y": 680, "w": 117, "ch": 9, "s": ["400"] }, + "Pavanam": { "x": 530, "y": 680, "w": 97, "ch": 9, "s": ["400"] }, + "Paytone One": { "x": 641, "y": 680, "w": 164, "ch": 9, "s": ["400"] }, + "Peddana": { "x": 819, "y": 680, "w": 67, "ch": 9, "s": ["400"] }, + "Peralta": { "x": 900, "y": 680, "w": 108, "ch": 9, "s": ["400"] }, + "Permanent Marker": { "x": 0, "y": 720, "w": 234, "ch": 9, "s": ["400"] }, + "Petemoss": { "x": 248, "y": 720, "w": 60, "ch": 9, "s": ["400"] }, + "Petit Formal Script": { + "x": 322, + "y": 720, + "w": 255, + "ch": 9, + "s": ["400"] + }, + "Petrona": { + "x": 591, + "y": 720, + "w": 89, + "ch": 9, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Philosopher": { + "x": 694, + "y": 720, + "w": 129, + "ch": 9, + "s": ["400", "400i", "700", "700i"] + }, + "Phudu": { + "x": 837, + "y": 720, + "w": 76, + "ch": 9, + "s": ["300", "400", "500", "600", "700", "800", "900"] + }, + "Piazzolla": { + "x": 927, + "y": 720, + "w": 107, + "ch": 9, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Piedra": { "x": 1048, "y": 720, "w": 68, "ch": 9, "s": ["400"] }, + "Pinyon Script": { "x": 0, "y": 760, "w": 128, "ch": 9, "s": ["400"] }, + "Pirata One": { "x": 142, "y": 760, "w": 92, "ch": 9, "s": ["400"] }, + "Pixelify Sans": { + "x": 248, + "y": 760, + "w": 153, + "ch": 9, + "s": ["400", "500", "600", "700"] + }, + "Plaster": { "x": 415, "y": 760, "w": 126, "ch": 9, "s": ["400"] }, + "Platypi": { + "x": 555, + "y": 760, + "w": 90, + "ch": 9, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Play": { "x": 659, "y": 760, "w": 52, "ch": 9, "s": ["400", "700"] }, + "Playball": { "x": 725, "y": 760, "w": 83, "ch": 9, "s": ["400"] }, + "Playfair": { + "x": 822, + "y": 760, + "w": 90, + "ch": 9, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Playfair Display": { + "x": 926, + "y": 760, + "w": 176, + "ch": 9, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Playfair Display SC": { + "x": 0, + "y": 0, + "w": 247, + "ch": 10, + "s": ["400", "400i", "700", "700i", "900", "900i"] + }, + "Playpen Sans": { + "x": 261, + "y": 0, + "w": 161, + "ch": 10, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Playpen Sans Arabic": { + "x": 436, + "y": 0, + "w": 247, + "ch": 10, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Playpen Sans Deva": { + "x": 697, + "y": 0, + "w": 227, + "ch": 10, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Playpen Sans Hebrew": { + "x": 938, + "y": 0, + "w": 255, + "ch": 10, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Playpen Sans Thai": { + "x": 0, + "y": 40, + "w": 220, + "ch": 10, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Playwrite AR": { + "x": 234, + "y": 40, + "w": 199, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite AR Guides": { + "x": 447, + "y": 40, + "w": 294, + "ch": 10, + "s": ["400"] + }, + "Playwrite AT": { + "x": 755, + "y": 40, + "w": 180, + "ch": 10, + "s": ["100", "100i", "200", "200i", "300", "300i", "400", "400i"] + }, + "Playwrite AT Guides": { + "x": 0, + "y": 80, + "w": 273, + "ch": 10, + "s": ["400", "400i"] + }, + "Playwrite AU NSW": { + "x": 287, + "y": 80, + "w": 257, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite AU NSW Guides": { + "x": 558, + "y": 80, + "w": 350, + "ch": 10, + "s": ["400"] + }, + "Playwrite AU QLD": { + "x": 922, + "y": 80, + "w": 249, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite AU QLD Guides": { + "x": 0, + "y": 120, + "w": 344, + "ch": 10, + "s": ["400"] + }, + "Playwrite AU SA": { + "x": 358, + "y": 120, + "w": 217, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite AU SA Guides": { + "x": 589, + "y": 120, + "w": 312, + "ch": 10, + "s": ["400"] + }, + "Playwrite AU TAS": { + "x": 915, + "y": 120, + "w": 235, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite AU TAS Guides": { + "x": 0, + "y": 160, + "w": 329, + "ch": 10, + "s": ["400"] + }, + "Playwrite AU VIC": { + "x": 343, + "y": 160, + "w": 234, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite AU VIC Guides": { + "x": 591, + "y": 160, + "w": 327, + "ch": 10, + "s": ["400"] + }, + "Playwrite BE VLG": { + "x": 932, + "y": 160, + "w": 266, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite BE VLG Guides": { + "x": 0, + "y": 200, + "w": 359, + "ch": 10, + "s": ["400"] + }, + "Playwrite BE WAL": { + "x": 373, + "y": 200, + "w": 319, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite BE WAL Guides": { + "x": 706, + "y": 200, + "w": 420, + "ch": 10, + "s": ["400"] + }, + "Playwrite BR": { + "x": 0, + "y": 240, + "w": 201, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite BR Guides": { + "x": 215, + "y": 240, + "w": 298, + "ch": 10, + "s": ["400"] + }, + "Playwrite CA": { + "x": 527, + "y": 240, + "w": 191, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite CA Guides": { + "x": 732, + "y": 240, + "w": 288, + "ch": 10, + "s": ["400"] + }, + "Playwrite CL": { + "x": 0, + "y": 280, + "w": 194, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite CL Guides": { + "x": 208, + "y": 280, + "w": 290, + "ch": 10, + "s": ["400"] + }, + "Playwrite CO": { + "x": 512, + "y": 280, + "w": 189, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite CO Guides": { + "x": 715, + "y": 280, + "w": 287, + "ch": 10, + "s": ["400"] + }, + "Playwrite CU": { + "x": 0, + "y": 320, + "w": 192, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite CU Guides": { + "x": 206, + "y": 320, + "w": 289, + "ch": 10, + "s": ["400"] + }, + "Playwrite CZ": { + "x": 509, + "y": 320, + "w": 192, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite CZ Guides": { + "x": 715, + "y": 320, + "w": 287, + "ch": 10, + "s": ["400"] + }, + "Playwrite DE Grund": { + "x": 0, + "y": 360, + "w": 251, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite DE Grund Guides": { + "x": 265, + "y": 360, + "w": 342, + "ch": 10, + "s": ["400"] + }, + "Playwrite DE LA": { + "x": 621, + "y": 360, + "w": 232, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite DE LA Guides": { + "x": 867, + "y": 360, + "w": 325, + "ch": 10, + "s": ["400"] + }, + "Playwrite DE SAS": { + "x": 0, + "y": 400, + "w": 241, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite DE SAS Guides": { + "x": 255, + "y": 400, + "w": 333, + "ch": 10, + "s": ["400"] + }, + "Playwrite DE VA": { + "x": 602, + "y": 400, + "w": 225, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite DE VA Guides": { + "x": 841, + "y": 400, + "w": 320, + "ch": 10, + "s": ["400"] + }, + "Playwrite DK Loopet": { + "x": 0, + "y": 440, + "w": 254, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite DK Loopet Guides": { + "x": 268, + "y": 440, + "w": 345, + "ch": 10, + "s": ["400"] + }, + "Playwrite DK Uloopet": { + "x": 627, + "y": 440, + "w": 262, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite DK Uloopet Guides": { + "x": 0, + "y": 480, + "w": 353, + "ch": 10, + "s": ["400"] + }, + "Playwrite ES": { + "x": 367, + "y": 480, + "w": 176, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite ES Deco": { + "x": 557, + "y": 480, + "w": 256, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite ES Deco Guides": { + "x": 827, + "y": 480, + "w": 350, + "ch": 10, + "s": ["400"] + }, + "Playwrite ES Guides": { + "x": 0, + "y": 520, + "w": 268, + "ch": 10, + "s": ["400"] + }, + "Playwrite FR Moderne": { + "x": 282, + "y": 520, + "w": 287, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite FR Moderne Guides": { + "x": 583, + "y": 520, + "w": 383, + "ch": 10, + "s": ["400"] + }, + "Playwrite FR Trad": { + "x": 0, + "y": 560, + "w": 281, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite FR Trad Guides": { + "x": 295, + "y": 560, + "w": 384, + "ch": 10, + "s": ["400"] + }, + "Playwrite GB J": { + "x": 693, + "y": 560, + "w": 191, + "ch": 10, + "s": ["100", "100i", "200", "200i", "300", "300i", "400", "400i"] + }, + "Playwrite GB J Guides": { + "x": 898, + "y": 560, + "w": 284, + "ch": 10, + "s": ["400", "400i"] + }, + "Playwrite GB S": { + "x": 0, + "y": 600, + "w": 190, + "ch": 10, + "s": ["100", "100i", "200", "200i", "300", "300i", "400", "400i"] + }, + "Playwrite GB S Guides": { + "x": 204, + "y": 600, + "w": 282, + "ch": 10, + "s": ["400", "400i"] + }, + "Playwrite HR": { + "x": 500, + "y": 600, + "w": 191, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite HR Guides": { + "x": 705, + "y": 600, + "w": 286, + "ch": 10, + "s": ["400"] + }, + "Playwrite HR Lijeva": { + "x": 0, + "y": 640, + "w": 276, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite HR Lijeva Guides": { + "x": 290, + "y": 640, + "w": 370, + "ch": 10, + "s": ["400"] + }, + "Playwrite HU": { + "x": 674, + "y": 640, + "w": 190, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite HU Guides": { + "x": 878, + "y": 640, + "w": 285, + "ch": 10, + "s": ["400"] + }, + "Playwrite ID": { + "x": 0, + "y": 680, + "w": 194, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite ID Guides": { + "x": 208, + "y": 680, + "w": 296, + "ch": 10, + "s": ["400"] + }, + "Playwrite IE": { + "x": 518, + "y": 680, + "w": 175, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite IE Guides": { + "x": 707, + "y": 680, + "w": 271, + "ch": 10, + "s": ["400"] + }, + "Playwrite IN": { + "x": 992, + "y": 680, + "w": 189, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite IN Guides": { + "x": 0, + "y": 720, + "w": 285, + "ch": 10, + "s": ["400"] + }, + "Playwrite IS": { + "x": 299, + "y": 720, + "w": 156, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite IS Guides": { + "x": 469, + "y": 720, + "w": 249, + "ch": 10, + "s": ["400"] + }, + "Playwrite IT Moderna": { + "x": 732, + "y": 720, + "w": 273, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite IT Moderna Guides": { + "x": 0, + "y": 760, + "w": 365, + "ch": 10, + "s": ["400"] + }, + "Playwrite IT Trad": { + "x": 379, + "y": 760, + "w": 243, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite IT Trad Guides": { + "x": 636, + "y": 760, + "w": 336, + "ch": 10, + "s": ["400"] + }, + "Playwrite MX": { + "x": 986, + "y": 760, + "w": 200, + "ch": 10, + "s": ["100", "200", "300", "400"] + }, + "Playwrite MX Guides": { "x": 0, "y": 0, "w": 297, "ch": 11, "s": ["400"] }, + "Playwrite NG Modern": { + "x": 311, + "y": 0, + "w": 279, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite NG Modern Guides": { + "x": 604, + "y": 0, + "w": 373, + "ch": 11, + "s": ["400"] + }, + "Playwrite NL": { + "x": 991, + "y": 0, + "w": 207, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite NL Guides": { + "x": 0, + "y": 40, + "w": 306, + "ch": 11, + "s": ["400"] + }, + "Playwrite NO": { + "x": 320, + "y": 40, + "w": 189, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite NO Guides": { + "x": 523, + "y": 40, + "w": 285, + "ch": 11, + "s": ["400"] + }, + "Playwrite NZ": { + "x": 822, + "y": 40, + "w": 164, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite NZ Basic": { + "x": 0, + "y": 80, + "w": 231, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite NZ Basic Guides": { + "x": 245, + "y": 80, + "w": 319, + "ch": 11, + "s": ["400"] + }, + "Playwrite NZ Guides": { + "x": 578, + "y": 80, + "w": 256, + "ch": 11, + "s": ["400"] + }, + "Playwrite PE": { + "x": 848, + "y": 80, + "w": 185, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite PE Guides": { + "x": 0, + "y": 120, + "w": 282, + "ch": 11, + "s": ["400"] + }, + "Playwrite PL": { + "x": 296, + "y": 120, + "w": 178, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite PL Guides": { + "x": 488, + "y": 120, + "w": 272, + "ch": 11, + "s": ["400"] + }, + "Playwrite PT": { + "x": 774, + "y": 120, + "w": 188, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite PT Guides": { + "x": 0, + "y": 160, + "w": 284, + "ch": 11, + "s": ["400"] + }, + "Playwrite RO": { + "x": 298, + "y": 160, + "w": 197, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite RO Guides": { + "x": 509, + "y": 160, + "w": 286, + "ch": 11, + "s": ["400"] + }, + "Playwrite SK": { + "x": 809, + "y": 160, + "w": 195, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite SK Guides": { + "x": 0, + "y": 200, + "w": 289, + "ch": 11, + "s": ["400"] + }, + "Playwrite TZ": { + "x": 303, + "y": 200, + "w": 182, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite TZ Guides": { + "x": 499, + "y": 200, + "w": 271, + "ch": 11, + "s": ["400"] + }, + "Playwrite US Modern": { + "x": 784, + "y": 200, + "w": 277, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite US Modern Guides": { + "x": 0, + "y": 240, + "w": 369, + "ch": 11, + "s": ["400"] + }, + "Playwrite US Trad": { + "x": 383, + "y": 240, + "w": 258, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite US Trad Guides": { + "x": 655, + "y": 240, + "w": 355, + "ch": 11, + "s": ["400"] + }, + "Playwrite VN": { + "x": 0, + "y": 280, + "w": 212, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite VN Guides": { + "x": 226, + "y": 280, + "w": 311, + "ch": 11, + "s": ["400"] + }, + "Playwrite ZA": { + "x": 551, + "y": 280, + "w": 182, + "ch": 11, + "s": ["100", "200", "300", "400"] + }, + "Playwrite ZA Guides": { + "x": 747, + "y": 280, + "w": 276, + "ch": 11, + "s": ["400"] + }, + "Plus Jakarta Sans": { + "x": 0, + "y": 320, + "w": 199, + "ch": 11, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Pochaevsk": { "x": 213, "y": 320, "w": 111, "ch": 11, "s": ["400"] }, + "Podkova": { + "x": 338, + "y": 320, + "w": 97, + "ch": 11, + "s": ["400", "500", "600", "700", "800"] + }, + "Poetsen One": { "x": 449, "y": 320, "w": 148, "ch": 11, "s": ["400"] }, + "Poiret One": { "x": 611, "y": 320, "w": 114, "ch": 11, "s": ["400"] }, + "Poller One": { "x": 739, "y": 320, "w": 164, "ch": 11, "s": ["400"] }, + "Poltawski Nowy": { + "x": 917, + "y": 320, + "w": 182, + "ch": 11, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Poly": { "x": 1113, "y": 320, "w": 54, "ch": 11, "s": ["400", "400i"] }, + "Pompiere": { "x": 0, "y": 360, "w": 75, "ch": 11, "s": ["400"] }, + "Ponnala": { "x": 89, "y": 360, "w": 70, "ch": 11, "s": ["400"] }, + "Ponomar": { "x": 173, "y": 360, "w": 97, "ch": 11, "s": ["400"] }, + "Pontano Sans": { + "x": 284, + "y": 360, + "w": 149, + "ch": 11, + "s": ["300", "400", "500", "600", "700"] + }, + "Poor Story": { "x": 447, "y": 360, "w": 99, "ch": 11, "s": ["400"] }, + "Poppins": { + "x": 560, + "y": 360, + "w": 104, + "ch": 11, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Port Lligat Sans": { + "x": 678, + "y": 360, + "w": 157, + "ch": 11, + "s": ["400"] + }, + "Port Lligat Slab": { + "x": 849, + "y": 360, + "w": 153, + "ch": 11, + "s": ["400"] + }, + "Potta One": { "x": 1016, "y": 360, "w": 143, "ch": 11, "s": ["400"] }, + "Pragati Narrow": { + "x": 0, + "y": 400, + "w": 129, + "ch": 11, + "s": ["400", "700"] + }, + "Praise": { "x": 143, "y": 400, "w": 59, "ch": 11, "s": ["400"] }, + "Prata": { "x": 216, "y": 400, "w": 71, "ch": 11, "s": ["400"] }, + "Preahvihear": { "x": 301, "y": 400, "w": 161, "ch": 11, "s": ["400"] }, + "Press Start 2P": { "x": 476, "y": 400, "w": 344, "ch": 11, "s": ["400"] }, + "Pridi": { + "x": 834, + "y": 400, + "w": 60, + "ch": 11, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Princess Sofia": { "x": 908, "y": 400, "w": 131, "ch": 11, "s": ["400"] }, + "Prociono": { "x": 1053, "y": 400, "w": 101, "ch": 11, "s": ["400"] }, + "Prompt": { + "x": 0, + "y": 440, + "w": 92, + "ch": 11, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Prosto One": { "x": 106, "y": 440, "w": 159, "ch": 11, "s": ["400"] }, + "Protest Guerrilla": { + "x": 279, + "y": 440, + "w": 177, + "ch": 11, + "s": ["400"] + }, + "Protest Revolution": { + "x": 470, + "y": 440, + "w": 200, + "ch": 11, + "s": ["400"] + }, + "Protest Riot": { "x": 684, "y": 440, "w": 131, "ch": 11, "s": ["400"] }, + "Protest Strike": { "x": 829, "y": 440, "w": 151, "ch": 11, "s": ["400"] }, + "Proza Libre": { + "x": 994, + "y": 440, + "w": 137, + "ch": 11, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "PT Mono": { "x": 0, "y": 480, "w": 109, "ch": 11, "s": ["400"] }, + "PT Sans": { + "x": 123, + "y": 480, + "w": 88, + "ch": 11, + "s": ["400", "400i", "700", "700i"] + }, + "PT Sans Caption": { + "x": 225, + "y": 480, + "w": 197, + "ch": 11, + "s": ["400", "700"] + }, + "PT Sans Narrow": { + "x": 436, + "y": 480, + "w": 138, + "ch": 11, + "s": ["400", "700"] + }, + "PT Serif": { + "x": 588, + "y": 480, + "w": 93, + "ch": 11, + "s": ["400", "400i", "700", "700i"] + }, + "PT Serif Caption": { + "x": 695, + "y": 480, + "w": 205, + "ch": 11, + "s": ["400", "400i"] + }, + "Public Sans": { + "x": 914, + "y": 480, + "w": 139, + "ch": 11, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Puppies Play": { "x": 1067, "y": 480, "w": 85, "ch": 11, "s": ["400"] }, + "Puritan": { + "x": 0, + "y": 520, + "w": 78, + "ch": 11, + "s": ["400", "400i", "700", "700i"] + }, + "Purple Purse": { "x": 92, "y": 520, "w": 150, "ch": 11, "s": ["400"] }, + "Pushster": { "x": 256, "y": 520, "w": 89, "ch": 11, "s": ["400"] }, + "Qahiri": { "x": 359, "y": 520, "w": 51, "ch": 11, "s": ["400"] }, + "Quando": { "x": 424, "y": 520, "w": 107, "ch": 11, "s": ["400"] }, + "Quantico": { + "x": 545, + "y": 520, + "w": 109, + "ch": 11, + "s": ["400", "400i", "700", "700i"] + }, + "Quattrocento": { + "x": 668, + "y": 520, + "w": 153, + "ch": 11, + "s": ["400", "700"] + }, + "Quattrocento Sans": { + "x": 835, + "y": 520, + "w": 201, + "ch": 11, + "s": ["400", "400i", "700", "700i"] + }, + "Questrial": { "x": 1050, "y": 520, "w": 105, "ch": 11, "s": ["400"] }, + "Quicksand": { + "x": 0, + "y": 560, + "w": 125, + "ch": 11, + "s": ["300", "400", "500", "600", "700"] + }, + "Quintessential": { "x": 139, "y": 560, "w": 144, "ch": 11, "s": ["400"] }, + "Qwigley": { "x": 297, "y": 560, "w": 59, "ch": 11, "s": ["400"] }, + "Qwitcher Grypen": { + "x": 370, + "y": 560, + "w": 111, + "ch": 11, + "s": ["400", "700"] + }, + "Racing Sans One": { "x": 495, "y": 560, "w": 190, "ch": 11, "s": ["400"] }, + "Radio Canada": { + "x": 699, + "y": 560, + "w": 161, + "ch": 11, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Radio Canada Big": { + "x": 874, + "y": 560, + "w": 196, + "ch": 11, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Radley": { "x": 1084, "y": 560, "w": 81, "ch": 11, "s": ["400", "400i"] }, + "Rajdhani": { + "x": 0, + "y": 600, + "w": 92, + "ch": 11, + "s": ["300", "400", "500", "600", "700"] + }, + "Rakkas": { "x": 106, "y": 600, "w": 79, "ch": 11, "s": ["400"] }, + "Raleway": { + "x": 199, + "y": 600, + "w": 102, + "ch": 11, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Raleway Dots": { "x": 315, "y": 600, "w": 156, "ch": 11, "s": ["400"] }, + "Ramabhadra": { "x": 485, "y": 600, "w": 154, "ch": 11, "s": ["400"] }, + "Ramaraja": { "x": 653, "y": 600, "w": 96, "ch": 11, "s": ["400"] }, + "Rambla": { + "x": 763, + "y": 600, + "w": 83, + "ch": 11, + "s": ["400", "400i", "700", "700i"] + }, + "Rammetto One": { "x": 860, "y": 600, "w": 234, "ch": 11, "s": ["400"] }, + "Rampart One": { "x": 0, "y": 640, "w": 170, "ch": 11, "s": ["400"] }, + "Ranchers": { "x": 184, "y": 640, "w": 97, "ch": 11, "s": ["400"] }, + "Rancho": { "x": 295, "y": 640, "w": 62, "ch": 11, "s": ["400"] }, + "Ranga": { "x": 371, "y": 640, "w": 52, "ch": 11, "s": ["400", "700"] }, + "Rasa": { + "x": 437, + "y": 640, + "w": 53, + "ch": 11, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Rationale": { "x": 504, "y": 640, "w": 90, "ch": 11, "s": ["400"] }, + "Ravi Prakash": { "x": 608, "y": 640, "w": 132, "ch": 11, "s": ["400"] }, + "Readex Pro": { + "x": 754, + "y": 640, + "w": 140, + "ch": 11, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Recursive": { + "x": 908, + "y": 640, + "w": 123, + "ch": 11, + "s": ["300", "400", "500", "600", "700", "800", "900"] + }, + "Red Hat Display": { + "x": 0, + "y": 680, + "w": 175, + "ch": 11, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Red Hat Mono": { + "x": 189, + "y": 680, + "w": 181, + "ch": 11, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Red Hat Text": { + "x": 384, + "y": 680, + "w": 146, + "ch": 11, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Red Rose": { + "x": 544, + "y": 680, + "w": 117, + "ch": 11, + "s": ["300", "400", "500", "600", "700"] + }, + "Redacted": { "x": 675, "y": 680, "w": 80, "ch": 11, "s": ["400"] }, + "Redacted Script": { + "x": 769, + "y": 680, + "w": 201, + "ch": 11, + "s": ["300", "400", "700"] + }, + "Reddit Mono": { + "x": 984, + "y": 680, + "w": 157, + "ch": 11, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Reddit Sans": { + "x": 0, + "y": 720, + "w": 134, + "ch": 11, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Reddit Sans Condensed": { + "x": 148, + "y": 720, + "w": 232, + "ch": 11, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Redressed": { "x": 394, "y": 720, "w": 96, "ch": 11, "s": ["400"] }, + "Reem Kufi": { + "x": 504, + "y": 720, + "w": 118, + "ch": 11, + "s": ["400", "500", "600", "700"] + }, + "Reem Kufi Fun": { + "x": 636, + "y": 720, + "w": 161, + "ch": 11, + "s": ["400", "500", "600", "700"] + }, + "Reem Kufi Ink": { "x": 811, "y": 720, "w": 154, "ch": 11, "s": ["400"] }, + "Reenie Beanie": { "x": 979, "y": 720, "w": 122, "ch": 11, "s": ["400"] }, + "Reggae One": { "x": 0, "y": 760, "w": 161, "ch": 11, "s": ["400"] }, + "REM": { + "x": 175, + "y": 760, + "w": 63, + "ch": 11, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Rethink Sans": { + "x": 252, + "y": 760, + "w": 150, + "ch": 11, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Revalia": { "x": 416, "y": 760, "w": 121, "ch": 11, "s": ["400"] }, + "Rhodium Libre": { "x": 551, "y": 760, "w": 187, "ch": 11, "s": ["400"] }, + "Ribeye": { "x": 752, "y": 760, "w": 92, "ch": 11, "s": ["400"] }, + "Ribeye Marrow": { "x": 858, "y": 760, "w": 201, "ch": 11, "s": ["400"] }, + "Righteous": { "x": 1073, "y": 760, "w": 121, "ch": 11, "s": ["400"] }, + "Risque": { "x": 0, "y": 0, "w": 77, "ch": 12, "s": ["400"] }, + "Road Rage": { "x": 91, "y": 0, "w": 72, "ch": 12, "s": ["400"] }, + "Roboto": { + "x": 177, + "y": 0, + "w": 85, + "ch": 12, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Roboto Condensed": { + "x": 276, + "y": 0, + "w": 188, + "ch": 12, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Roboto Flex": { "x": 478, "y": 0, "w": 134, "ch": 12, "s": ["400"] }, + "Roboto Mono": { + "x": 626, + "y": 0, + "w": 167, + "ch": 12, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Roboto Serif": { + "x": 807, + "y": 0, + "w": 162, + "ch": 12, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Roboto Slab": { + "x": 983, + "y": 0, + "w": 141, + "ch": 12, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Rochester": { "x": 0, "y": 40, "w": 85, "ch": 12, "s": ["400"] }, + "Rock 3D": { "x": 99, "y": 40, "w": 137, "ch": 12, "s": ["400"] }, + "Rock Salt": { "x": 250, "y": 40, "w": 153, "ch": 12, "s": ["400"] }, + "RocknRoll One": { "x": 417, "y": 40, "w": 190, "ch": 12, "s": ["400"] }, + "Rokkitt": { + "x": 621, + "y": 40, + "w": 79, + "ch": 12, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Romanesco": { "x": 714, "y": 40, "w": 73, "ch": 12, "s": ["400"] }, + "Ropa Sans": { + "x": 801, + "y": 40, + "w": 102, + "ch": 12, + "s": ["400", "400i"] + }, + "Rosario": { + "x": 917, + "y": 40, + "w": 84, + "ch": 12, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Rosarivo": { + "x": 1015, + "y": 40, + "w": 106, + "ch": 12, + "s": ["400", "400i"] + }, + "Rouge Script": { "x": 0, "y": 80, "w": 104, "ch": 12, "s": ["400"] }, + "Rowdies": { + "x": 118, + "y": 80, + "w": 106, + "ch": 12, + "s": ["300", "400", "700"] + }, + "Rozha One": { "x": 238, "y": 80, "w": 126, "ch": 12, "s": ["400"] }, + "Rubik": { + "x": 378, + "y": 80, + "w": 71, + "ch": 12, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Rubik 80s Fade": { "x": 463, "y": 80, "w": 199, "ch": 12, "s": ["400"] }, + "Rubik Beastly": { "x": 676, "y": 80, "w": 183, "ch": 12, "s": ["400"] }, + "Rubik Broken Fax": { "x": 873, "y": 80, "w": 227, "ch": 12, "s": ["400"] }, + "Rubik Bubbles": { "x": 0, "y": 120, "w": 188, "ch": 12, "s": ["400"] }, + "Rubik Burned": { "x": 202, "y": 120, "w": 179, "ch": 12, "s": ["400"] }, + "Rubik Dirt": { "x": 395, "y": 120, "w": 137, "ch": 12, "s": ["400"] }, + "Rubik Distressed": { + "x": 546, + "y": 120, + "w": 225, + "ch": 12, + "s": ["400"] + }, + "Rubik Doodle Shadow": { + "x": 785, + "y": 120, + "w": 277, + "ch": 12, + "s": ["400"] + }, + "Rubik Doodle Triangles": { + "x": 0, + "y": 160, + "w": 296, + "ch": 12, + "s": ["400"] + }, + "Rubik Gemstones": { "x": 310, "y": 160, "w": 229, "ch": 12, "s": ["400"] }, + "Rubik Glitch": { "x": 553, "y": 160, "w": 165, "ch": 12, "s": ["400"] }, + "Rubik Glitch Pop": { + "x": 732, + "y": 160, + "w": 217, + "ch": 12, + "s": ["400"] + }, + "Rubik Iso": { "x": 963, "y": 160, "w": 124, "ch": 12, "s": ["400"] }, + "Rubik Lines": { "x": 0, "y": 200, "w": 155, "ch": 12, "s": ["400"] }, + "Rubik Maps": { "x": 169, "y": 200, "w": 151, "ch": 12, "s": ["400"] }, + "Rubik Marker Hatch": { + "x": 334, + "y": 200, + "w": 257, + "ch": 12, + "s": ["400"] + }, + "Rubik Maze": { "x": 605, "y": 200, "w": 150, "ch": 12, "s": ["400"] }, + "Rubik Microbe": { "x": 769, "y": 200, "w": 188, "ch": 12, "s": ["400"] }, + "Rubik Mono One": { "x": 0, "y": 240, "w": 294, "ch": 12, "s": ["400"] }, + "Rubik Moonrocks": { "x": 308, "y": 240, "w": 225, "ch": 12, "s": ["400"] }, + "Rubik Pixels": { "x": 547, "y": 240, "w": 162, "ch": 12, "s": ["400"] }, + "Rubik Puddles": { "x": 723, "y": 240, "w": 187, "ch": 12, "s": ["400"] }, + "Rubik Scribble": { "x": 924, "y": 240, "w": 192, "ch": 12, "s": ["400"] }, + "Rubik Spray Paint": { "x": 0, "y": 280, "w": 233, "ch": 12, "s": ["400"] }, + "Rubik Storm": { "x": 247, "y": 280, "w": 165, "ch": 12, "s": ["400"] }, + "Rubik Vinyl": { "x": 426, "y": 280, "w": 150, "ch": 12, "s": ["400"] }, + "Rubik Wet Paint": { "x": 590, "y": 280, "w": 207, "ch": 12, "s": ["400"] }, + "Ruda": { + "x": 811, + "y": 280, + "w": 66, + "ch": 12, + "s": ["400", "500", "600", "700", "800", "900"] + }, + "Rufina": { "x": 891, "y": 280, "w": 82, "ch": 12, "s": ["400", "700"] }, + "Ruge Boogie": { "x": 987, "y": 280, "w": 99, "ch": 12, "s": ["400"] }, + "Ruluko": { "x": 1100, "y": 280, "w": 78, "ch": 12, "s": ["400"] }, + "Rum Raisin": { "x": 0, "y": 320, "w": 100, "ch": 12, "s": ["400"] }, + "Ruslan Display": { "x": 114, "y": 320, "w": 220, "ch": 12, "s": ["400"] }, + "Russo One": { "x": 348, "y": 320, "w": 138, "ch": 12, "s": ["400"] }, + "Ruthie": { "x": 500, "y": 320, "w": 64, "ch": 12, "s": ["400"] }, + "Ruwudu": { + "x": 578, + "y": 320, + "w": 94, + "ch": 12, + "s": ["400", "500", "600", "700"] + }, + "Rye": { "x": 686, "y": 320, "w": 55, "ch": 12, "s": ["400"] }, + "Sacramento": { "x": 755, "y": 320, "w": 104, "ch": 12, "s": ["400"] }, + "Sahitya": { "x": 873, "y": 320, "w": 81, "ch": 12, "s": ["400", "700"] }, + "Sail": { "x": 968, "y": 320, "w": 50, "ch": 12, "s": ["400"] }, + "Saira": { + "x": 1032, + "y": 320, + "w": 66, + "ch": 12, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Saira Condensed": { + "x": 0, + "y": 360, + "w": 149, + "ch": 12, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Saira Extra Condensed": { + "x": 163, + "y": 360, + "w": 167, + "ch": 12, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Saira Semi Condensed": { + "x": 344, + "y": 360, + "w": 227, + "ch": 12, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Saira Stencil One": { + "x": 585, + "y": 360, + "w": 201, + "ch": 12, + "s": ["400"] + }, + "Salsa": { "x": 800, "y": 360, "w": 66, "ch": 12, "s": ["400"] }, + "Sanchez": { "x": 880, "y": 360, "w": 105, "ch": 12, "s": ["400", "400i"] }, + "Sancreek": { "x": 999, "y": 360, "w": 111, "ch": 12, "s": ["400"] }, + "Sankofa Display": { "x": 0, "y": 400, "w": 163, "ch": 12, "s": ["400"] }, + "Sansation": { + "x": 177, + "y": 400, + "w": 119, + "ch": 12, + "s": ["300", "300i", "400", "400i", "700", "700i"] + }, + "Sansita": { + "x": 310, + "y": 400, + "w": 80, + "ch": 12, + "s": ["400", "400i", "700", "700i", "800", "800i", "900", "900i"] + }, + "Sansita Swashed": { + "x": 404, + "y": 400, + "w": 177, + "ch": 12, + "s": ["300", "400", "500", "600", "700", "800", "900"] + }, + "Sarabun": { + "x": 595, + "y": 400, + "w": 94, + "ch": 12, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Sarala": { "x": 703, "y": 400, "w": 74, "ch": 12, "s": ["400", "700"] }, + "Sarina": { "x": 791, "y": 400, "w": 112, "ch": 12, "s": ["400"] }, + "Sarpanch": { + "x": 917, + "y": 400, + "w": 122, + "ch": 12, + "s": ["400", "500", "600", "700", "800", "900"] + }, + "Sassy Frass": { "x": 1053, "y": 400, "w": 78, "ch": 12, "s": ["400"] }, + "Satisfy": { "x": 0, "y": 440, "w": 70, "ch": 12, "s": ["400"] }, + "Savate": { + "x": 84, + "y": 440, + "w": 81, + "ch": 12, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Sawarabi Gothic": { "x": 179, "y": 440, "w": 186, "ch": 12, "s": ["400"] }, + "Sawarabi Mincho": { "x": 379, "y": 440, "w": 204, "ch": 12, "s": ["400"] }, + "Scada": { + "x": 597, + "y": 440, + "w": 69, + "ch": 12, + "s": ["400", "400i", "700", "700i"] + }, + "Scheherazade New": { + "x": 680, + "y": 440, + "w": 175, + "ch": 12, + "s": ["400", "500", "600", "700"] + }, + "Schibsted Grotesk": { + "x": 869, + "y": 440, + "w": 212, + "ch": 12, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Schoolbell": { "x": 1095, "y": 440, "w": 102, "ch": 12, "s": ["400"] }, + "Science Gothic": { + "x": 0, + "y": 480, + "w": 212, + "ch": 12, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Scope One": { "x": 226, "y": 480, "w": 120, "ch": 12, "s": ["400"] }, + "Seaweed Script": { "x": 360, "y": 480, "w": 137, "ch": 12, "s": ["400"] }, + "Secular One": { "x": 511, "y": 480, "w": 144, "ch": 12, "s": ["400"] }, + "Sedan": { "x": 669, "y": 480, "w": 72, "ch": 12, "s": ["400", "400i"] }, + "Sedan SC": { "x": 755, "y": 480, "w": 111, "ch": 12, "s": ["400"] }, + "Sedgwick Ave": { "x": 880, "y": 480, "w": 140, "ch": 12, "s": ["400"] }, + "Sedgwick Ave Display": { + "x": 0, + "y": 520, + "w": 224, + "ch": 12, + "s": ["400"] + }, + "Sekuya": { "x": 238, "y": 520, "w": 138, "ch": 12, "s": ["400"] }, + "Sen": { + "x": 390, + "y": 520, + "w": 48, + "ch": 12, + "s": ["400", "500", "600", "700", "800"] + }, + "Send Flowers": { "x": 452, "y": 520, "w": 129, "ch": 12, "s": ["400"] }, + "Sevillana": { "x": 595, "y": 520, "w": 96, "ch": 12, "s": ["400"] }, + "Seymour One": { "x": 705, "y": 520, "w": 241, "ch": 12, "s": ["400"] }, + "Shadows Into Light": { + "x": 960, + "y": 520, + "w": 172, + "ch": 12, + "s": ["400"] + }, + "Shadows Into Light Two": { + "x": 0, + "y": 560, + "w": 234, + "ch": 12, + "s": ["400"] + }, + "Shafarik": { "x": 248, "y": 560, "w": 89, "ch": 12, "s": ["400"] }, + "Shalimar": { "x": 351, "y": 560, "w": 59, "ch": 12, "s": ["400"] }, + "Shantell Sans": { + "x": 424, + "y": 560, + "w": 166, + "ch": 12, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Shanti": { "x": 604, "y": 560, "w": 75, "ch": 12, "s": ["400"] }, + "Share": { + "x": 693, + "y": 560, + "w": 62, + "ch": 12, + "s": ["400", "400i", "700", "700i"] + }, + "Share Tech": { "x": 769, "y": 560, "w": 109, "ch": 12, "s": ["400"] }, + "Share Tech Mono": { "x": 892, "y": 560, "w": 203, "ch": 12, "s": ["400"] }, + "Shippori Antique": { "x": 0, "y": 600, "w": 206, "ch": 12, "s": ["400"] }, + "Shippori Antique B1": { + "x": 220, + "y": 600, + "w": 241, + "ch": 12, + "s": ["400"] + }, + "Shippori Mincho": { + "x": 475, + "y": 600, + "w": 202, + "ch": 12, + "s": ["400", "500", "600", "700", "800"] + }, + "Shippori Mincho B1": { + "x": 691, + "y": 600, + "w": 237, + "ch": 12, + "s": ["400", "500", "600", "700", "800"] + }, + "Shizuru": { "x": 942, "y": 600, "w": 82, "ch": 12, "s": ["400"] }, + "Shojumaru": { "x": 0, "y": 640, "w": 184, "ch": 12, "s": ["400"] }, + "Short Stack": { "x": 198, "y": 640, "w": 168, "ch": 12, "s": ["400"] }, + "Shrikhand": { "x": 380, "y": 640, "w": 147, "ch": 12, "s": ["400"] }, + "Sigmar": { "x": 541, "y": 640, "w": 99, "ch": 12, "s": ["400"] }, + "Sigmar One": { "x": 654, "y": 640, "w": 172, "ch": 12, "s": ["400"] }, + "Signika": { + "x": 840, + "y": 640, + "w": 85, + "ch": 12, + "s": ["300", "400", "500", "600", "700"] + }, + "Signika Negative": { + "x": 939, + "y": 640, + "w": 180, + "ch": 12, + "s": ["300", "400", "500", "600", "700"] + }, + "Silkscreen": { "x": 0, "y": 680, "w": 173, "ch": 12, "s": ["400", "700"] }, + "Simonetta": { + "x": 187, + "y": 680, + "w": 108, + "ch": 12, + "s": ["400", "400i", "900", "900i"] + }, + "Sintony": { "x": 309, "y": 680, "w": 97, "ch": 12, "s": ["400", "700"] }, + "Sirin Stencil": { "x": 420, "y": 680, "w": 122, "ch": 12, "s": ["400"] }, + "Sirivennela": { "x": 556, "y": 680, "w": 98, "ch": 12, "s": ["400"] }, + "Six Caps": { "x": 668, "y": 680, "w": 47, "ch": 12, "s": ["400"] }, + "Sixtyfour": { "x": 729, "y": 680, "w": 224, "ch": 12, "s": ["400"] }, + "Sixtyfour Convergence": { + "x": 0, + "y": 720, + "w": 512, + "ch": 12, + "s": ["400"] + }, + "Skranji": { "x": 526, "y": 720, "w": 78, "ch": 12, "s": ["400", "700"] }, + "Slabo 13px": { "x": 618, "y": 720, "w": 127, "ch": 12, "s": ["400"] }, + "Slabo 27px": { "x": 759, "y": 720, "w": 111, "ch": 12, "s": ["400"] }, + "Slackey": { "x": 884, "y": 720, "w": 124, "ch": 12, "s": ["400"] }, + "Slackside One": { "x": 1022, "y": 720, "w": 137, "ch": 12, "s": ["400"] }, + "Smokum": { "x": 0, "y": 760, "w": 76, "ch": 12, "s": ["400"] }, + "Smooch": { "x": 90, "y": 760, "w": 73, "ch": 12, "s": ["400"] }, + "Smooch Sans": { + "x": 177, + "y": 760, + "w": 110, + "ch": 12, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Smythe": { "x": 301, "y": 760, "w": 64, "ch": 12, "s": ["400"] }, + "SN Pro": { + "x": 379, + "y": 760, + "w": 84, + "ch": 12, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Sniglet": { "x": 477, "y": 760, "w": 82, "ch": 12, "s": ["400", "800"] }, + "Snippet": { "x": 573, "y": 760, "w": 89, "ch": 12, "s": ["400"] }, + "Snowburst One": { "x": 676, "y": 760, "w": 197, "ch": 12, "s": ["400"] }, + "Sofadi One": { "x": 887, "y": 760, "w": 137, "ch": 12, "s": ["400"] }, + "Sofia": { "x": 1038, "y": 760, "w": 61, "ch": 12, "s": ["400"] }, + "Sofia Sans": { + "x": 0, + "y": 0, + "w": 115, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Sofia Sans Condensed": { + "x": 129, + "y": 0, + "w": 184, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Sofia Sans Extra Condensed": { + "x": 327, + "y": 0, + "w": 188, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Sofia Sans Semi Condensed": { + "x": 529, + "y": 0, + "w": 280, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Solitreo": { "x": 823, "y": 0, "w": 83, "ch": 13, "s": ["400"] }, + "Solway": { + "x": 920, + "y": 0, + "w": 90, + "ch": 13, + "s": ["300", "400", "500", "700", "800"] + }, + "Sometype Mono": { + "x": 0, + "y": 40, + "w": 189, + "ch": 13, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Sono": { + "x": 203, + "y": 40, + "w": 68, + "ch": 13, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Sonsie One": { "x": 285, "y": 40, "w": 194, "ch": 13, "s": ["400"] }, + "Sora": { + "x": 493, + "y": 40, + "w": 65, + "ch": 13, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Sorts Mill Goudy": { + "x": 572, + "y": 40, + "w": 187, + "ch": 13, + "s": ["400", "400i"] + }, + "Sour Gummy": { + "x": 773, + "y": 40, + "w": 149, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Source Code Pro": { + "x": 936, + "y": 40, + "w": 224, + "ch": 13, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Source Sans 3": { + "x": 0, + "y": 80, + "w": 147, + "ch": 13, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Source Sans Pro": { + "x": 161, + "y": 80, + "w": 170, + "ch": 13, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "600", + "600i", + "700", + "700i", + "900", + "900i" + ] + }, + "Source Serif 4": { + "x": 345, + "y": 80, + "w": 164, + "ch": 13, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Source Serif Pro": { + "x": 523, + "y": 80, + "w": 180, + "ch": 13, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "600", + "600i", + "700", + "700i", + "900", + "900i" + ] + }, + "Space Grotesk": { + "x": 717, + "y": 80, + "w": 176, + "ch": 13, + "s": ["300", "400", "500", "600", "700"] + }, + "Space Mono": { + "x": 907, + "y": 80, + "w": 155, + "ch": 13, + "s": ["400", "400i", "700", "700i"] + }, + "Special Elite": { "x": 0, "y": 120, "w": 177, "ch": 13, "s": ["400"] }, + "Special Gothic": { + "x": 191, + "y": 120, + "w": 156, + "ch": 13, + "s": ["400", "500", "600", "700"] + }, + "Special Gothic Condensed One": { + "x": 361, + "y": 120, + "w": 247, + "ch": 13, + "s": ["400"] + }, + "Special Gothic Expanded One": { + "x": 622, + "y": 120, + "w": 424, + "ch": 13, + "s": ["400"] + }, + "Spectral": { + "x": 1060, + "y": 120, + "w": 95, + "ch": 13, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Spectral SC": { + "x": 0, + "y": 160, + "w": 159, + "ch": 13, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Spicy Rice": { "x": 173, "y": 160, "w": 111, "ch": 13, "s": ["400"] }, + "Spinnaker": { "x": 298, "y": 160, "w": 128, "ch": 13, "s": ["400"] }, + "Spirax": { "x": 440, "y": 160, "w": 73, "ch": 13, "s": ["400"] }, + "Splash": { "x": 527, "y": 160, "w": 88, "ch": 13, "s": ["400"] }, + "Spline Sans": { + "x": 629, + "y": 160, + "w": 133, + "ch": 13, + "s": ["300", "400", "500", "600", "700"] + }, + "Spline Sans Mono": { + "x": 776, + "y": 160, + "w": 239, + "ch": 13, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Squada One": { "x": 1029, "y": 160, "w": 111, "ch": 13, "s": ["400"] }, + "Square Peg": { "x": 0, "y": 200, "w": 73, "ch": 13, "s": ["400"] }, + "Sree Krushnadevaraya": { + "x": 87, + "y": 200, + "w": 222, + "ch": 13, + "s": ["400"] + }, + "Sriracha": { "x": 323, "y": 200, "w": 99, "ch": 13, "s": ["400"] }, + "Srisakdi": { "x": 436, "y": 200, "w": 90, "ch": 13, "s": ["400", "700"] }, + "Staatliches": { "x": 540, "y": 200, "w": 114, "ch": 13, "s": ["400"] }, + "Stack Sans Headline": { + "x": 668, + "y": 200, + "w": 235, + "ch": 13, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Stack Sans Notch": { + "x": 917, + "y": 200, + "w": 205, + "ch": 13, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Stack Sans Text": { + "x": 0, + "y": 240, + "w": 195, + "ch": 13, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Stalemate": { "x": 209, "y": 240, "w": 62, "ch": 13, "s": ["400"] }, + "Stalinist One": { "x": 285, "y": 240, "w": 280, "ch": 13, "s": ["400"] }, + "Stardos Stencil": { + "x": 579, + "y": 240, + "w": 168, + "ch": 13, + "s": ["400", "700"] + }, + "Stick": { "x": 761, "y": 240, "w": 65, "ch": 13, "s": ["400"] }, + "Stick No Bills": { + "x": 840, + "y": 240, + "w": 123, + "ch": 13, + "s": ["200", "300", "400", "500", "600", "700", "800"] + }, + "Stint Ultra Condensed": { + "x": 977, + "y": 240, + "w": 139, + "ch": 13, + "s": ["400"] + }, + "Stint Ultra Expanded": { + "x": 0, + "y": 280, + "w": 310, + "ch": 13, + "s": ["400"] + }, + "STIX Two Text": { + "x": 324, + "y": 280, + "w": 156, + "ch": 13, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Stoke": { "x": 494, "y": 280, "w": 84, "ch": 13, "s": ["300", "400"] }, + "Story Script": { "x": 592, "y": 280, "w": 113, "ch": 13, "s": ["400"] }, + "Strait": { "x": 719, "y": 280, "w": 62, "ch": 13, "s": ["400"] }, + "Style Script": { "x": 795, "y": 280, "w": 97, "ch": 13, "s": ["400"] }, + "Sue Ellen Francisco": { + "x": 906, + "y": 280, + "w": 137, + "ch": 13, + "s": ["400"] + }, + "Suez One": { "x": 1057, "y": 280, "w": 113, "ch": 13, "s": ["400"] }, + "Sulphur Point": { + "x": 0, + "y": 320, + "w": 144, + "ch": 13, + "s": ["300", "400", "700"] + }, + "Sumana": { "x": 158, "y": 320, "w": 92, "ch": 13, "s": ["400", "700"] }, + "Sunshiney": { "x": 264, "y": 320, "w": 94, "ch": 13, "s": ["400"] }, + "Supermercado One": { + "x": 372, + "y": 320, + "w": 196, + "ch": 13, + "s": ["400"] + }, + "Sura": { "x": 582, "y": 320, "w": 58, "ch": 13, "s": ["400", "700"] }, + "Suranna": { "x": 654, "y": 320, "w": 86, "ch": 13, "s": ["400"] }, + "Suravaram": { "x": 754, "y": 320, "w": 104, "ch": 13, "s": ["400"] }, + "SUSE": { + "x": 872, + "y": 320, + "w": 65, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "SUSE Mono": { + "x": 951, + "y": 320, + "w": 138, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Suwannaphum": { + "x": 0, + "y": 360, + "w": 184, + "ch": 13, + "s": ["100", "300", "400", "700", "900"] + }, + "Swanky and Moo Moo": { + "x": 198, + "y": 360, + "w": 203, + "ch": 13, + "s": ["400"] + }, + "Syncopate": { + "x": 415, + "y": 360, + "w": 195, + "ch": 13, + "s": ["400", "700"] + }, + "Syne": { + "x": 624, + "y": 360, + "w": 60, + "ch": 13, + "s": ["400", "500", "600", "700", "800"] + }, + "Syne Mono": { "x": 698, "y": 360, "w": 127, "ch": 13, "s": ["400"] }, + "Syne Tactile": { "x": 839, "y": 360, "w": 119, "ch": 13, "s": ["400"] }, + "Tac One": { "x": 972, "y": 360, "w": 73, "ch": 13, "s": ["400"] }, + "Tagesschrift": { "x": 0, "y": 400, "w": 142, "ch": 13, "s": ["400"] }, + "Tai Heritage Pro": { + "x": 156, + "y": 400, + "w": 171, + "ch": 13, + "s": ["400", "700"] + }, + "Tajawal": { + "x": 341, + "y": 400, + "w": 82, + "ch": 13, + "s": ["200", "300", "400", "500", "700", "800", "900"] + }, + "Tangerine": { "x": 437, "y": 400, "w": 67, "ch": 13, "s": ["400", "700"] }, + "Tapestry": { "x": 518, "y": 400, "w": 100, "ch": 13, "s": ["400"] }, + "Taprom": { "x": 632, "y": 400, "w": 70, "ch": 13, "s": ["400"] }, + "TASA Explorer": { + "x": 716, + "y": 400, + "w": 161, + "ch": 13, + "s": ["400", "500", "600", "700", "800"] + }, + "TASA Orbiter": { + "x": 891, + "y": 400, + "w": 153, + "ch": 13, + "s": ["400", "500", "600", "700", "800"] + }, + "Tauri": { "x": 1058, "y": 400, "w": 66, "ch": 13, "s": ["400"] }, + "Taviraj": { + "x": 0, + "y": 440, + "w": 84, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Teachers": { + "x": 98, + "y": 440, + "w": 100, + "ch": 13, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Teko": { + "x": 212, + "y": 440, + "w": 42, + "ch": 13, + "s": ["300", "400", "500", "600", "700"] + }, + "Tektur": { + "x": 268, + "y": 440, + "w": 86, + "ch": 13, + "s": ["400", "500", "600", "700", "800", "900"] + }, + "Telex": { "x": 368, "y": 440, "w": 64, "ch": 13, "s": ["400"] }, + "Tenali Ramakrishna": { + "x": 446, + "y": 440, + "w": 170, + "ch": 13, + "s": ["400"] + }, + "Tenor Sans": { "x": 630, "y": 440, "w": 134, "ch": 13, "s": ["400"] }, + "Text Me One": { "x": 778, "y": 440, "w": 140, "ch": 13, "s": ["400"] }, + "Texturina": { + "x": 932, + "y": 440, + "w": 113, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Thasadith": { + "x": 1059, + "y": 440, + "w": 101, + "ch": 13, + "s": ["400", "400i", "700", "700i"] + }, + "The Girl Next Door": { + "x": 0, + "y": 480, + "w": 215, + "ch": 13, + "s": ["400"] + }, + "The Nautigal": { + "x": 229, + "y": 480, + "w": 102, + "ch": 13, + "s": ["400", "700"] + }, + "Tienne": { + "x": 345, + "y": 480, + "w": 92, + "ch": 13, + "s": ["400", "700", "900"] + }, + "TikTok Sans": { + "x": 451, + "y": 480, + "w": 141, + "ch": 13, + "s": ["300", "400", "500", "600", "700", "800", "900"] + }, + "Tillana": { + "x": 606, + "y": 480, + "w": 75, + "ch": 13, + "s": ["400", "500", "600", "700", "800"] + }, + "Tilt Neon": { "x": 695, "y": 480, "w": 101, "ch": 13, "s": ["400"] }, + "Tilt Prism": { "x": 810, "y": 480, "w": 121, "ch": 13, "s": ["400"] }, + "Tilt Warp": { "x": 945, "y": 480, "w": 109, "ch": 13, "s": ["400"] }, + "Timmana": { "x": 1068, "y": 480, "w": 90, "ch": 13, "s": ["400"] }, + "Tinos": { + "x": 0, + "y": 520, + "w": 62, + "ch": 13, + "s": ["400", "400i", "700", "700i"] + }, + "Tiny5": { "x": 76, "y": 520, "w": 62, "ch": 13, "s": ["400"] }, + "Tiro Bangla": { + "x": 152, + "y": 520, + "w": 134, + "ch": 13, + "s": ["400", "400i"] + }, + "Tiro Devanagari Hindi": { + "x": 300, + "y": 520, + "w": 251, + "ch": 13, + "s": ["400", "400i"] + }, + "Tiro Devanagari Marathi": { + "x": 565, + "y": 520, + "w": 274, + "ch": 13, + "s": ["400", "400i"] + }, + "Tiro Devanagari Sanskrit": { + "x": 853, + "y": 520, + "w": 276, + "ch": 13, + "s": ["400", "400i"] + }, + "Tiro Gurmukhi": { + "x": 0, + "y": 560, + "w": 173, + "ch": 13, + "s": ["400", "400i"] + }, + "Tiro Kannada": { + "x": 187, + "y": 560, + "w": 156, + "ch": 13, + "s": ["400", "400i"] + }, + "Tiro Tamil": { + "x": 357, + "y": 560, + "w": 122, + "ch": 13, + "s": ["400", "400i"] + }, + "Tiro Telugu": { + "x": 493, + "y": 560, + "w": 134, + "ch": 13, + "s": ["400", "400i"] + }, + "Tirra": { + "x": 641, + "y": 560, + "w": 57, + "ch": 13, + "s": ["400", "500", "600", "700", "800", "900"] + }, + "Titan One": { "x": 712, "y": 560, "w": 131, "ch": 13, "s": ["400"] }, + "Titillium Web": { + "x": 857, + "y": 560, + "w": 140, + "ch": 13, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "600", + "600i", + "700", + "700i", + "900", + "900i" + ] + }, + "Tomorrow": { + "x": 1011, + "y": 560, + "w": 126, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Tourney": { + "x": 0, + "y": 600, + "w": 107, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Trade Winds": { "x": 121, "y": 600, "w": 160, "ch": 13, "s": ["400"] }, + "Train One": { "x": 295, "y": 600, "w": 132, "ch": 13, "s": ["400"] }, + "Triodion": { "x": 441, "y": 600, "w": 94, "ch": 13, "s": ["400"] }, + "Trirong": { + "x": 549, + "y": 600, + "w": 92, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Trispace": { + "x": 655, + "y": 600, + "w": 128, + "ch": 13, + "s": ["100", "200", "300", "400", "500", "600", "700", "800"] + }, + "Trocchi": { "x": 797, "y": 600, "w": 100, "ch": 13, "s": ["400"] }, + "Trochut": { + "x": 911, + "y": 600, + "w": 75, + "ch": 13, + "s": ["400", "400i", "700", "700i"] + }, + "Truculenta": { + "x": 1000, + "y": 600, + "w": 100, + "ch": 13, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Trykker": { "x": 0, "y": 640, "w": 98, "ch": 13, "s": ["400"] }, + "Tsukimi Rounded": { + "x": 112, + "y": 640, + "w": 209, + "ch": 13, + "s": ["300", "400", "500", "600", "700"] + }, + "Tuffy": { + "x": 335, + "y": 640, + "w": 63, + "ch": 13, + "s": ["400", "400i", "700", "700i"] + }, + "Tulpen One": { "x": 412, "y": 640, "w": 59, "ch": 13, "s": ["400"] }, + "Turret Road": { + "x": 485, + "y": 640, + "w": 142, + "ch": 13, + "s": ["200", "300", "400", "500", "700", "800"] + }, + "Twinkle Star": { "x": 641, "y": 640, "w": 137, "ch": 13, "s": ["400"] }, + "Ubuntu": { + "x": 792, + "y": 640, + "w": 90, + "ch": 13, + "s": ["300", "300i", "400", "400i", "500", "500i", "700", "700i"] + }, + "Ubuntu Condensed": { + "x": 896, + "y": 640, + "w": 177, + "ch": 13, + "s": ["400"] + }, + "Ubuntu Mono": { + "x": 0, + "y": 680, + "w": 156, + "ch": 13, + "s": ["400", "400i", "700", "700i"] + }, + "Ubuntu Sans": { + "x": 170, + "y": 680, + "w": 144, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Ubuntu Sans Mono": { + "x": 328, + "y": 680, + "w": 224, + "ch": 13, + "s": ["400", "400i", "500", "500i", "600", "600i", "700", "700i"] + }, + "Uchen": { "x": 566, "y": 680, "w": 75, "ch": 13, "s": ["400"] }, + "Ultra": { "x": 655, "y": 680, "w": 85, "ch": 13, "s": ["400"] }, + "Unbounded": { + "x": 754, + "y": 680, + "w": 174, + "ch": 13, + "s": ["200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Uncial Antiqua": { "x": 942, "y": 680, "w": 211, "ch": 13, "s": ["400"] }, + "Underdog": { "x": 0, "y": 720, "w": 113, "ch": 13, "s": ["400"] }, + "Unica One": { "x": 127, "y": 720, "w": 106, "ch": 13, "s": ["400"] }, + "UnifrakturMaguntia": { + "x": 247, + "y": 720, + "w": 204, + "ch": 13, + "s": ["400"] + }, + "Unkempt": { "x": 465, "y": 720, "w": 99, "ch": 13, "s": ["400", "700"] }, + "Unlock": { "x": 578, "y": 720, "w": 93, "ch": 13, "s": ["400"] }, + "Unna": { + "x": 685, + "y": 720, + "w": 60, + "ch": 13, + "s": ["400", "400i", "700", "700i"] + }, + "UoqMunThenKhung": { "x": 759, "y": 720, "w": 245, "ch": 13, "s": ["400"] }, + "Updock": { "x": 1018, "y": 720, "w": 58, "ch": 13, "s": ["400"] }, + "Urbanist": { + "x": 1090, + "y": 720, + "w": 97, + "ch": 13, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Vampiro One": { "x": 0, "y": 760, "w": 165, "ch": 13, "s": ["400"] }, + "Varela": { "x": 179, "y": 760, "w": 81, "ch": 13, "s": ["400"] }, + "Varela Round": { "x": 274, "y": 760, "w": 161, "ch": 13, "s": ["400"] }, + "Varta": { + "x": 449, + "y": 760, + "w": 61, + "ch": 13, + "s": ["300", "400", "500", "600", "700"] + }, + "Vast Shadow": { "x": 524, "y": 760, "w": 227, "ch": 13, "s": ["400"] }, + "Vazirmatn": { + "x": 765, + "y": 760, + "w": 117, + "ch": 13, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Vend Sans": { + "x": 896, + "y": 760, + "w": 121, + "ch": 13, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Vesper Libre": { + "x": 1031, + "y": 760, + "w": 136, + "ch": 13, + "s": ["400", "500", "700", "900"] + }, + "Viaoda Libre": { "x": 0, "y": 0, "w": 126, "ch": 14, "s": ["400"] }, + "Vibes": { "x": 140, "y": 0, "w": 53, "ch": 14, "s": ["400"] }, + "Vibur": { "x": 207, "y": 0, "w": 61, "ch": 14, "s": ["400"] }, + "Victor Mono": { + "x": 282, + "y": 0, + "w": 167, + "ch": 14, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Vidaloka": { "x": 463, "y": 0, "w": 99, "ch": 14, "s": ["400"] }, + "Viga": { "x": 576, "y": 0, "w": 55, "ch": 14, "s": ["400"] }, + "Vina Sans": { "x": 645, "y": 0, "w": 86, "ch": 14, "s": ["400"] }, + "Voces": { "x": 745, "y": 0, "w": 72, "ch": 14, "s": ["400"] }, + "Volkhov": { + "x": 831, + "y": 0, + "w": 104, + "ch": 14, + "s": ["400", "400i", "700", "700i"] + }, + "Vollkorn": { + "x": 949, + "y": 0, + "w": 99, + "ch": 14, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Vollkorn SC": { + "x": 0, + "y": 40, + "w": 166, + "ch": 14, + "s": ["400", "600", "700", "900"] + }, + "Voltaire": { "x": 180, "y": 40, "w": 73, "ch": 14, "s": ["400"] }, + "VT323": { "x": 267, "y": 40, "w": 56, "ch": 14, "s": ["400"] }, + "Vujahday Script": { "x": 337, "y": 40, "w": 151, "ch": 14, "s": ["400"] }, + "Waiting for the Sunrise": { + "x": 502, + "y": 40, + "w": 230, + "ch": 14, + "s": ["400"] + }, + "Wallpoet": { "x": 746, "y": 40, "w": 127, "ch": 14, "s": ["400"] }, + "Walter Turncoat": { "x": 887, "y": 40, "w": 206, "ch": 14, "s": ["400"] }, + "Warnes": { "x": 0, "y": 80, "w": 120, "ch": 14, "s": ["400"] }, + "Water Brush": { "x": 134, "y": 80, "w": 116, "ch": 14, "s": ["400"] }, + "Waterfall": { "x": 264, "y": 80, "w": 77, "ch": 14, "s": ["400"] }, + "Wavefont": { + "x": 355, + "y": 80, + "w": 28, + "ch": 14, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "WDXL Lubrifont JP N": { + "x": 397, + "y": 80, + "w": 185, + "ch": 14, + "s": ["400"] + }, + "WDXL Lubrifont SC": { + "x": 596, + "y": 80, + "w": 170, + "ch": 14, + "s": ["400"] + }, + "WDXL Lubrifont TC": { + "x": 780, + "y": 80, + "w": 168, + "ch": 14, + "s": ["400"] + }, + "Wellfleet": { "x": 962, "y": 80, "w": 117, "ch": 14, "s": ["400"] }, + "Wendy One": { "x": 0, "y": 120, "w": 142, "ch": 14, "s": ["400"] }, + "Whisper": { "x": 156, "y": 120, "w": 76, "ch": 14, "s": ["400"] }, + "WindSong": { "x": 246, "y": 120, "w": 133, "ch": 14, "s": ["400", "500"] }, + "Winky Rough": { + "x": 393, + "y": 120, + "w": 139, + "ch": 14, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Winky Sans": { + "x": 546, + "y": 120, + "w": 120, + "ch": 14, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Wire One": { "x": 680, "y": 120, "w": 57, "ch": 14, "s": ["400"] }, + "Wittgenstein": { + "x": 751, + "y": 120, + "w": 151, + "ch": 14, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Wix Madefor Display": { + "x": 916, + "y": 120, + "w": 238, + "ch": 14, + "s": ["400", "500", "600", "700", "800"] + }, + "Wix Madefor Text": { + "x": 0, + "y": 160, + "w": 202, + "ch": 14, + "s": [ + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i" + ] + }, + "Work Sans": { + "x": 216, + "y": 160, + "w": 132, + "ch": 14, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Workbench": { "x": 362, "y": 160, "w": 116, "ch": 14, "s": ["400"] }, + "Xanh Mono": { + "x": 492, + "y": 160, + "w": 116, + "ch": 14, + "s": ["400", "400i"] + }, + "Yaldevi": { + "x": 622, + "y": 160, + "w": 83, + "ch": 14, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Yanone Kaffeesatz": { + "x": 719, + "y": 160, + "w": 142, + "ch": 14, + "s": ["200", "300", "400", "500", "600", "700"] + }, + "Yantramanav": { + "x": 875, + "y": 160, + "w": 138, + "ch": 14, + "s": ["100", "300", "400", "500", "700", "900"] + }, + "Yarndings 12": { "x": 0, "y": 200, "w": 226, "ch": 14, "s": ["400"] }, + "Yarndings 12 Charted": { + "x": 240, + "y": 200, + "w": 365, + "ch": 14, + "s": ["400"] + }, + "Yarndings 20": { "x": 619, "y": 200, "w": 219, "ch": 14, "s": ["400"] }, + "Yarndings 20 Charted": { + "x": 0, + "y": 240, + "w": 357, + "ch": 14, + "s": ["400"] + }, + "Yatra One": { "x": 371, "y": 240, "w": 127, "ch": 14, "s": ["400"] }, + "Yellowtail": { "x": 512, "y": 240, "w": 93, "ch": 14, "s": ["400"] }, + "Yeon Sung": { "x": 619, "y": 240, "w": 107, "ch": 14, "s": ["400"] }, + "Yeseva One": { "x": 740, "y": 240, "w": 144, "ch": 14, "s": ["400"] }, + "Yesteryear": { "x": 898, "y": 240, "w": 94, "ch": 14, "s": ["400"] }, + "Yomogi": { "x": 1006, "y": 240, "w": 80, "ch": 14, "s": ["400"] }, + "Young Serif": { "x": 0, "y": 280, "w": 149, "ch": 14, "s": ["400"] }, + "Yrsa": { + "x": 163, + "y": 280, + "w": 49, + "ch": 14, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Ysabeau": { + "x": 226, + "y": 280, + "w": 88, + "ch": 14, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Ysabeau Infant": { + "x": 328, + "y": 280, + "w": 161, + "ch": 14, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Ysabeau Office": { + "x": 503, + "y": 280, + "w": 156, + "ch": 14, + "s": [ + "100", + "100i", + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Ysabeau SC": { + "x": 673, + "y": 280, + "w": 130, + "ch": 14, + "s": ["100", "200", "300", "400", "500", "600", "700", "800", "900"] + }, + "Yuji Boku": { "x": 817, "y": 280, "w": 135, "ch": 14, "s": ["400"] }, + "Yuji Hentaigana Akari": { + "x": 0, + "y": 320, + "w": 335, + "ch": 14, + "s": ["400"] + }, + "Yuji Hentaigana Akebono": { + "x": 349, + "y": 320, + "w": 374, + "ch": 14, + "s": ["400"] + }, + "Yuji Mai": { "x": 737, "y": 320, "w": 117, "ch": 14, "s": ["400"] }, + "Yuji Syuku": { "x": 868, "y": 320, "w": 144, "ch": 14, "s": ["400"] }, + "Yusei Magic": { "x": 1026, "y": 320, "w": 138, "ch": 14, "s": ["400"] }, + "Zain": { + "x": 0, + "y": 360, + "w": 54, + "ch": 14, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Zalando Sans": { + "x": 68, + "y": 360, + "w": 161, + "ch": 14, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Zalando Sans Expanded": { + "x": 243, + "y": 360, + "w": 335, + "ch": 14, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "Zalando Sans SemiExpanded": { + "x": 592, + "y": 360, + "w": 361, + "ch": 14, + "s": [ + "200", + "200i", + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i", + "800", + "800i", + "900", + "900i" + ] + }, + "ZCOOL KuaiLe": { "x": 967, "y": 360, "w": 180, "ch": 14, "s": ["400"] }, + "ZCOOL QingKe HuangYou": { + "x": 0, + "y": 400, + "w": 222, + "ch": 14, + "s": ["400"] + }, + "ZCOOL XiaoWei": { "x": 236, "y": 400, "w": 173, "ch": 14, "s": ["400"] }, + "Zen Antique": { "x": 423, "y": 400, "w": 149, "ch": 14, "s": ["400"] }, + "Zen Antique Soft": { + "x": 586, + "y": 400, + "w": 204, + "ch": 14, + "s": ["400"] + }, + "Zen Dots": { "x": 804, "y": 400, "w": 147, "ch": 14, "s": ["400"] }, + "Zen Kaku Gothic Antique": { + "x": 0, + "y": 440, + "w": 267, + "ch": 14, + "s": ["300", "400", "500", "700", "900"] + }, + "Zen Kaku Gothic New": { + "x": 281, + "y": 440, + "w": 231, + "ch": 14, + "s": ["300", "400", "500", "700", "900"] + }, + "Zen Kurenaido": { "x": 526, "y": 440, "w": 145, "ch": 14, "s": ["400"] }, + "Zen Loop": { "x": 685, "y": 440, "w": 65, "ch": 14, "s": ["400", "400i"] }, + "Zen Maru Gothic": { + "x": 764, + "y": 440, + "w": 181, + "ch": 14, + "s": ["300", "400", "500", "700", "900"] + }, + "Zen Old Mincho": { + "x": 959, + "y": 440, + "w": 173, + "ch": 14, + "s": ["400", "500", "600", "700", "900"] + }, + "Zen Tokyo Zoo": { "x": 0, "y": 480, "w": 154, "ch": 14, "s": ["400"] }, + "Zeyada": { "x": 168, "y": 480, "w": 70, "ch": 14, "s": ["400"] }, + "Zhi Mang Xing": { "x": 252, "y": 480, "w": 121, "ch": 14, "s": ["400"] }, + "Zilla Slab": { + "x": 387, + "y": 480, + "w": 102, + "ch": 14, + "s": [ + "300", + "300i", + "400", + "400i", + "500", + "500i", + "600", + "600i", + "700", + "700i" + ] + }, + "Zilla Slab Highlight": { + "x": 503, + "y": 480, + "w": 207, + "ch": 14, + "s": ["400", "700"] + } + } +} diff --git a/apps/web/public/landing-page-dark.png:Zone.Identifier b/apps/web/public/landing-page-dark.png:Zone.Identifier new file mode 100644 index 00000000..d6c1ec68 Binary files /dev/null and b/apps/web/public/landing-page-dark.png:Zone.Identifier differ diff --git a/apps/web/public/logos/opencut/icon.svg b/apps/web/public/logos/opencut/icon.svg index b6da2979..753ebb1d 100644 --- a/apps/web/public/logos/opencut/icon.svg +++ b/apps/web/public/logos/opencut/icon.svg @@ -1,11 +1,11 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/apps/web/public/logos/opencut/logo-light.svg b/apps/web/public/logos/opencut/logo-light.svg index 84c2ceb5..3dea5278 100644 --- a/apps/web/public/logos/opencut/logo-light.svg +++ b/apps/web/public/logos/opencut/logo-light.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/logos/opencut/logo.svg b/apps/web/public/logos/opencut/logo.svg index a691b261..179db068 100644 --- a/apps/web/public/logos/opencut/logo.svg +++ b/apps/web/public/logos/opencut/logo.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/apps/web/public/logos/opencut/svg/logo.svg b/apps/web/public/logos/opencut/svg/logo.svg index f4079a5e..47c19566 100644 --- a/apps/web/public/logos/opencut/svg/logo.svg +++ b/apps/web/public/logos/opencut/svg/logo.svg @@ -1,10 +1,10 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/apps/web/public/logos/opencut/symbol-light.svg b/apps/web/public/logos/opencut/symbol-light.svg index b590c442..9d43da68 100644 --- a/apps/web/public/logos/opencut/symbol-light.svg +++ b/apps/web/public/logos/opencut/symbol-light.svg @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/apps/web/public/logos/opencut/symbol.svg b/apps/web/public/logos/opencut/symbol.svg index e821060b..4b9f2b84 100644 --- a/apps/web/public/logos/opencut/symbol.svg +++ b/apps/web/public/logos/opencut/symbol.svg @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/apps/web/public/logos/opencut/text-light.svg b/apps/web/public/logos/opencut/text-light.svg index daec6eec..d30e5128 100644 --- a/apps/web/public/logos/opencut/text-light.svg +++ b/apps/web/public/logos/opencut/text-light.svg @@ -1,10 +1,10 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/apps/web/public/logos/opencut/text.svg b/apps/web/public/logos/opencut/text.svg index 1b419cf5..d5013ea8 100644 --- a/apps/web/public/logos/opencut/text.svg +++ b/apps/web/public/logos/opencut/text.svg @@ -1,3 +1,3 @@ - - - + + + diff --git a/apps/web/public/logos/others/fal.svg b/apps/web/public/logos/others/fal.svg index abc56513..6562afd5 100644 --- a/apps/web/public/logos/others/fal.svg +++ b/apps/web/public/logos/others/fal.svg @@ -1,3 +1,3 @@ - - - + + + diff --git a/apps/web/public/logos/others/vercel.svg b/apps/web/public/logos/others/vercel.svg index 824018a3..483c970d 100644 --- a/apps/web/public/logos/others/vercel.svg +++ b/apps/web/public/logos/others/vercel.svg @@ -1,3 +1,3 @@ - - - + + + diff --git a/apps/web/public/manifest.json b/apps/web/public/manifest.json index 2f00e1e9..3b05a125 100644 --- a/apps/web/public/manifest.json +++ b/apps/web/public/manifest.json @@ -1,44 +1,44 @@ -{ - "name": "OpenCut", - "description": "A simple but powerful video editor that gets the job done. In your browser.", - "display": "standalone", - "start_url": "/", - "icons": [ - { - "src": "/icons/android-icon-36x36.png", - "sizes": "36x36", - "type": "image\/png", - "density": "0.75" - }, - { - "src": "/icons/android-icon-48x48.png", - "sizes": "48x48", - "type": "image\/png", - "density": "1.0" - }, - { - "src": "/icons/android-icon-72x72.png", - "sizes": "72x72", - "type": "image\/png", - "density": "1.5" - }, - { - "src": "/icons/android-icon-96x96.png", - "sizes": "96x96", - "type": "image\/png", - "density": "2.0" - }, - { - "src": "/icons/android-icon-144x144.png", - "sizes": "144x144", - "type": "image\/png", - "density": "3.0" - }, - { - "src": "/icons/android-icon-192x192.png", - "sizes": "192x192", - "type": "image\/png", - "density": "4.0" - } - ] -} +{ + "name": "OpenCut", + "description": "A simple but powerful video editor that gets the job done. In your browser.", + "display": "standalone", + "start_url": "/", + "icons": [ + { + "src": "/icons/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "/icons/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "/icons/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "/icons/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "/icons/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "/icons/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] +} diff --git a/apps/web/scripts/generate-font-sprites.ts b/apps/web/scripts/generate-font-sprites.ts index 144d68ec..f0d98bc8 100644 --- a/apps/web/scripts/generate-font-sprites.ts +++ b/apps/web/scripts/generate-font-sprites.ts @@ -1,281 +1,281 @@ -/** - * Generates font sprite atlas for the font picker. - * - * Downloads Google Fonts from Fontsource, renders each font name as a sprite, - * packs them into chunk images, and outputs a JSON atlas + AVIF images. - * - * Run: npx tsx scripts/generate-font-sprites.ts - * Deps: @napi-rs/canvas sharp (install as devDependencies) - */ - -import { createCanvas, GlobalFonts } from "@napi-rs/canvas"; -import sharp from "sharp"; -import { mkdir, writeFile, readFile } from "node:fs/promises"; -import { existsSync } from "node:fs"; -import { join, dirname } from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -const FONT_SIZE = 24; -const ROW_HEIGHT = 40; -const CANVAS_WIDTH = 1200; -const MAX_CHUNK_HEIGHT = 800; -const PADDING_X = 14; -const WIDTH_BUFFER = 8; -const CONCURRENT_DOWNLOADS = 30; - -const OUTPUT_DIR = join(__dirname, "..", "public", "fonts"); -const CACHE_DIR = join(__dirname, "..", ".font-cache"); -const FONTSOURCE_API = "https://api.fontsource.org/v1/fonts"; - -interface FontsourceFont { - id: string; - family: string; - subsets: string[]; - weights: number[]; - styles: string[]; - category: string; - type: string; -} - -interface MeasuredFont { - family: string; - width: number; - styles: string[]; -} - -interface PackedFont { - family: string; - x: number; - y: number; - w: number; -} - -interface AtlasEntry { - x: number; - y: number; - w: number; - ch: number; - s: string[]; -} - -async function fetchFontList(): Promise { - console.log("Fetching font list from Fontsource..."); - const response = await fetch(FONTSOURCE_API); - if (!response.ok) throw new Error(`Fontsource API error: ${response.status}`); - - const data: FontsourceFont[] = await response.json(); - const filtered = data.filter( - (font) => - font.type === "google" && - font.subsets.includes("latin") && - font.weights.includes(400), - ); - - console.log( - ` ${filtered.length} Google fonts with Latin subset + 400 weight`, - ); - return filtered; -} - -async function downloadFont({ id }: { id: string }): Promise { - const cachePath = join(CACHE_DIR, `${id}.woff2`); - - if (existsSync(cachePath)) { - return readFile(cachePath); - } - - // Fontsource CDN serves woff2 for all Google fonts - const url = `https://cdn.jsdelivr.net/fontsource/fonts/${id}@latest/latin-400-normal.woff2`; - try { - const response = await fetch(url); - if (!response.ok) throw new Error(`HTTP ${response.status}`); - const buffer = Buffer.from(await response.arrayBuffer()); - await writeFile(cachePath, buffer); - return buffer; - } catch { - return null; - } -} - -async function downloadAllFonts({ - fonts, - concurrency, -}: { - fonts: FontsourceFont[]; - concurrency: number; -}): Promise> { - console.log( - `Downloading ${fonts.length} font files (${concurrency} concurrent)...`, - ); - const results = new Map(); - let nextIndex = 0; - let completed = 0; - - async function worker() { - while (nextIndex < fonts.length) { - const index = nextIndex++; - const font = fonts[index]; - const buffer = await downloadFont({ id: font.id }); - if (buffer) results.set(font.id, buffer); - completed++; - if (completed % 100 === 0 || completed === fonts.length) { - process.stdout.write(`\r ${completed}/${fonts.length}`); - } - } - } - - await Promise.all(Array.from({ length: concurrency }, () => worker())); - console.log(`\n Downloaded ${results.size}/${fonts.length} fonts`); - return results; -} - -function measureFonts({ - fonts, - fontBuffers, -}: { - fonts: FontsourceFont[]; - fontBuffers: Map; -}): MeasuredFont[] { - console.log("Registering fonts and measuring text..."); - const measured: MeasuredFont[] = []; - const canvas = createCanvas(CANVAS_WIDTH, ROW_HEIGHT); - const ctx = canvas.getContext("2d"); - - for (const font of fonts) { - const buffer = fontBuffers.get(font.id); - if (!buffer) continue; - - try { - const ok = GlobalFonts.register(buffer, font.family); - if (!ok) continue; - - ctx.font = `${FONT_SIZE}px "${font.family}"`; - const metrics = ctx.measureText(font.family); - const width = Math.ceil(metrics.width) + WIDTH_BUFFER; - - const styles: string[] = []; - for (const weight of font.weights) { - if (font.styles.includes("normal")) styles.push(String(weight)); - if (font.styles.includes("italic")) styles.push(`${weight}i`); - } - - measured.push({ family: font.family, width, styles }); - } catch { - // skip fonts that fail to register - } - } - - measured.sort((a, b) => a.family.localeCompare(b.family)); - console.log(` ${measured.length} fonts measured`); - return measured; -} - -function packIntoChunks({ measured }: { measured: MeasuredFont[] }): { - atlas: Record; - chunks: PackedFont[][]; -} { - console.log("Packing into sprite chunks..."); - const atlas: Record = {}; - const chunks: PackedFont[][] = [[]]; - let chunkIndex = 0; - let cursorX = 0; - let cursorY = 0; - - for (const font of measured) { - // New row if doesn't fit horizontally - if (cursorX + font.width > CANVAS_WIDTH) { - cursorX = 0; - cursorY += ROW_HEIGHT; - } - - // New chunk if doesn't fit vertically - if (cursorY + ROW_HEIGHT > MAX_CHUNK_HEIGHT) { - chunkIndex++; - chunks.push([]); - cursorX = 0; - cursorY = 0; - } - - // Same data goes to BOTH the atlas and the chunk render list - const x = cursorX; - const y = cursorY; - - atlas[font.family] = { - x, - y, - w: font.width, - ch: chunkIndex, - s: font.styles, - }; - chunks[chunkIndex].push({ family: font.family, x, y, w: font.width }); - - cursorX += font.width + PADDING_X; - } - - console.log(` ${chunks.length} chunks`); - return { atlas, chunks }; -} - -async function renderChunks({ - chunks, -}: { - chunks: PackedFont[][]; -}): Promise { - console.log("Rendering sprite chunks..."); - - for (let i = 0; i < chunks.length; i++) { - const chunk = chunks[i]; - const chunkHeight = Math.max(...chunk.map((f) => f.y)) + ROW_HEIGHT; - - const canvas = createCanvas(CANVAS_WIDTH, chunkHeight); - const ctx = canvas.getContext("2d"); - - for (const font of chunk) { - ctx.font = `${FONT_SIZE}px "${font.family}"`; - ctx.fillStyle = "#000000"; - ctx.textBaseline = "middle"; - ctx.fillText(font.family, font.x, font.y + ROW_HEIGHT / 2); - } - - const pngBuffer = canvas.toBuffer("image/png"); - await sharp(pngBuffer) - .avif({ quality: 80 }) - .toFile(join(OUTPUT_DIR, `font-chunk-${i}.avif`)); - - console.log( - ` Chunk ${i}: ${chunk.length} fonts, ${CANVAS_WIDTH}×${chunkHeight}`, - ); - } -} - -async function main() { - await mkdir(OUTPUT_DIR, { recursive: true }); - await mkdir(CACHE_DIR, { recursive: true }); - - const fonts = await fetchFontList(); - const fontBuffers = await downloadAllFonts({ - fonts, - concurrency: CONCURRENT_DOWNLOADS, - }); - const measured = measureFonts({ fonts, fontBuffers }); - const { atlas, chunks } = packIntoChunks({ measured }); - await renderChunks({ chunks }); - - // Write atlas JSON (compact for smaller file size) - await writeFile( - join(OUTPUT_DIR, "font-atlas.json"), - JSON.stringify({ fonts: atlas }), - ); - - const totalFonts = Object.keys(atlas).length; - console.log( - `\nDone! ${totalFonts} fonts in ${chunks.length} chunks → ${OUTPUT_DIR}`, - ); -} - -main().catch((error) => { - console.error("Failed:", error); - process.exit(1); -}); +/** + * Generates font sprite atlas for the font picker. + * + * Downloads Google Fonts from Fontsource, renders each font name as a sprite, + * packs them into chunk images, and outputs a JSON atlas + AVIF images. + * + * Run: npx tsx scripts/generate-font-sprites.ts + * Deps: @napi-rs/canvas sharp (install as devDependencies) + */ + +import { createCanvas, GlobalFonts } from "@napi-rs/canvas"; +import sharp from "sharp"; +import { mkdir, writeFile, readFile } from "node:fs/promises"; +import { existsSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const FONT_SIZE = 24; +const ROW_HEIGHT = 40; +const CANVAS_WIDTH = 1200; +const MAX_CHUNK_HEIGHT = 800; +const PADDING_X = 14; +const WIDTH_BUFFER = 8; +const CONCURRENT_DOWNLOADS = 30; + +const OUTPUT_DIR = join(__dirname, "..", "public", "fonts"); +const CACHE_DIR = join(__dirname, "..", ".font-cache"); +const FONTSOURCE_API = "https://api.fontsource.org/v1/fonts"; + +interface FontsourceFont { + id: string; + family: string; + subsets: string[]; + weights: number[]; + styles: string[]; + category: string; + type: string; +} + +interface MeasuredFont { + family: string; + width: number; + styles: string[]; +} + +interface PackedFont { + family: string; + x: number; + y: number; + w: number; +} + +interface AtlasEntry { + x: number; + y: number; + w: number; + ch: number; + s: string[]; +} + +async function fetchFontList(): Promise { + console.log("Fetching font list from Fontsource..."); + const response = await fetch(FONTSOURCE_API); + if (!response.ok) throw new Error(`Fontsource API error: ${response.status}`); + + const data: FontsourceFont[] = await response.json(); + const filtered = data.filter( + (font) => + font.type === "google" && + font.subsets.includes("latin") && + font.weights.includes(400), + ); + + console.log( + ` ${filtered.length} Google fonts with Latin subset + 400 weight`, + ); + return filtered; +} + +async function downloadFont({ id }: { id: string }): Promise { + const cachePath = join(CACHE_DIR, `${id}.woff2`); + + if (existsSync(cachePath)) { + return readFile(cachePath); + } + + // Fontsource CDN serves woff2 for all Google fonts + const url = `https://cdn.jsdelivr.net/fontsource/fonts/${id}@latest/latin-400-normal.woff2`; + try { + const response = await fetch(url); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const buffer = Buffer.from(await response.arrayBuffer()); + await writeFile(cachePath, buffer); + return buffer; + } catch { + return null; + } +} + +async function downloadAllFonts({ + fonts, + concurrency, +}: { + fonts: FontsourceFont[]; + concurrency: number; +}): Promise> { + console.log( + `Downloading ${fonts.length} font files (${concurrency} concurrent)...`, + ); + const results = new Map(); + let nextIndex = 0; + let completed = 0; + + async function worker() { + while (nextIndex < fonts.length) { + const index = nextIndex++; + const font = fonts[index]; + const buffer = await downloadFont({ id: font.id }); + if (buffer) results.set(font.id, buffer); + completed++; + if (completed % 100 === 0 || completed === fonts.length) { + process.stdout.write(`\r ${completed}/${fonts.length}`); + } + } + } + + await Promise.all(Array.from({ length: concurrency }, () => worker())); + console.log(`\n Downloaded ${results.size}/${fonts.length} fonts`); + return results; +} + +function measureFonts({ + fonts, + fontBuffers, +}: { + fonts: FontsourceFont[]; + fontBuffers: Map; +}): MeasuredFont[] { + console.log("Registering fonts and measuring text..."); + const measured: MeasuredFont[] = []; + const canvas = createCanvas(CANVAS_WIDTH, ROW_HEIGHT); + const ctx = canvas.getContext("2d"); + + for (const font of fonts) { + const buffer = fontBuffers.get(font.id); + if (!buffer) continue; + + try { + const ok = GlobalFonts.register(buffer, font.family); + if (!ok) continue; + + ctx.font = `${FONT_SIZE}px "${font.family}"`; + const metrics = ctx.measureText(font.family); + const width = Math.ceil(metrics.width) + WIDTH_BUFFER; + + const styles: string[] = []; + for (const weight of font.weights) { + if (font.styles.includes("normal")) styles.push(String(weight)); + if (font.styles.includes("italic")) styles.push(`${weight}i`); + } + + measured.push({ family: font.family, width, styles }); + } catch { + // skip fonts that fail to register + } + } + + measured.sort((a, b) => a.family.localeCompare(b.family)); + console.log(` ${measured.length} fonts measured`); + return measured; +} + +function packIntoChunks({ measured }: { measured: MeasuredFont[] }): { + atlas: Record; + chunks: PackedFont[][]; +} { + console.log("Packing into sprite chunks..."); + const atlas: Record = {}; + const chunks: PackedFont[][] = [[]]; + let chunkIndex = 0; + let cursorX = 0; + let cursorY = 0; + + for (const font of measured) { + // New row if doesn't fit horizontally + if (cursorX + font.width > CANVAS_WIDTH) { + cursorX = 0; + cursorY += ROW_HEIGHT; + } + + // New chunk if doesn't fit vertically + if (cursorY + ROW_HEIGHT > MAX_CHUNK_HEIGHT) { + chunkIndex++; + chunks.push([]); + cursorX = 0; + cursorY = 0; + } + + // Same data goes to BOTH the atlas and the chunk render list + const x = cursorX; + const y = cursorY; + + atlas[font.family] = { + x, + y, + w: font.width, + ch: chunkIndex, + s: font.styles, + }; + chunks[chunkIndex].push({ family: font.family, x, y, w: font.width }); + + cursorX += font.width + PADDING_X; + } + + console.log(` ${chunks.length} chunks`); + return { atlas, chunks }; +} + +async function renderChunks({ + chunks, +}: { + chunks: PackedFont[][]; +}): Promise { + console.log("Rendering sprite chunks..."); + + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + const chunkHeight = Math.max(...chunk.map((f) => f.y)) + ROW_HEIGHT; + + const canvas = createCanvas(CANVAS_WIDTH, chunkHeight); + const ctx = canvas.getContext("2d"); + + for (const font of chunk) { + ctx.font = `${FONT_SIZE}px "${font.family}"`; + ctx.fillStyle = "#000000"; + ctx.textBaseline = "middle"; + ctx.fillText(font.family, font.x, font.y + ROW_HEIGHT / 2); + } + + const pngBuffer = canvas.toBuffer("image/png"); + await sharp(pngBuffer) + .avif({ quality: 80 }) + .toFile(join(OUTPUT_DIR, `font-chunk-${i}.avif`)); + + console.log( + ` Chunk ${i}: ${chunk.length} fonts, ${CANVAS_WIDTH}×${chunkHeight}`, + ); + } +} + +async function main() { + await mkdir(OUTPUT_DIR, { recursive: true }); + await mkdir(CACHE_DIR, { recursive: true }); + + const fonts = await fetchFontList(); + const fontBuffers = await downloadAllFonts({ + fonts, + concurrency: CONCURRENT_DOWNLOADS, + }); + const measured = measureFonts({ fonts, fontBuffers }); + const { atlas, chunks } = packIntoChunks({ measured }); + await renderChunks({ chunks }); + + // Write atlas JSON (compact for smaller file size) + await writeFile( + join(OUTPUT_DIR, "font-atlas.json"), + JSON.stringify({ fonts: atlas }), + ); + + const totalFonts = Object.keys(atlas).length; + console.log( + `\nDone! ${totalFonts} fonts in ${chunks.length} chunks → ${OUTPUT_DIR}`, + ); +} + +main().catch((error) => { + console.error("Failed:", error); + process.exit(1); +}); diff --git a/apps/web/src/app/api/auth/[...all]/route.ts b/apps/web/src/app/api/auth/[...all]/route.ts index e17d0cce..544457e2 100644 --- a/apps/web/src/app/api/auth/[...all]/route.ts +++ b/apps/web/src/app/api/auth/[...all]/route.ts @@ -1,4 +1,4 @@ -import { auth } from "@/lib/auth/server"; -import { toNextJsHandler } from "better-auth/next-js"; - -export const { POST, GET } = toNextJsHandler(auth); +import { auth } from "@/lib/auth/server"; +import { toNextJsHandler } from "better-auth/next-js"; + +export const { POST, GET } = toNextJsHandler(auth); diff --git a/apps/web/src/app/api/health/route.ts b/apps/web/src/app/api/health/route.ts index edb40a0d..dbe6fdb1 100644 --- a/apps/web/src/app/api/health/route.ts +++ b/apps/web/src/app/api/health/route.ts @@ -1,3 +1,3 @@ -export async function GET() { - return new Response("OK", { status: 200 }); -} +export async function GET() { + return new Response("OK", { status: 200 }); +} diff --git a/apps/web/src/app/api/sounds/search/route.ts b/apps/web/src/app/api/sounds/search/route.ts index 339a54da..3070d152 100644 --- a/apps/web/src/app/api/sounds/search/route.ts +++ b/apps/web/src/app/api/sounds/search/route.ts @@ -1,280 +1,280 @@ -import { webEnv } from "@opencut/env/web"; -import { type NextRequest, NextResponse } from "next/server"; -import { z } from "zod"; -import { checkRateLimit } from "@/lib/rate-limit"; - -const searchParamsSchema = z.object({ - q: z.string().max(500, "Query too long").optional(), - type: z.enum(["songs", "effects"]).optional(), - page: z.coerce.number().int().min(1).max(1000).default(1), - page_size: z.coerce.number().int().min(1).max(150).default(20), - sort: z - .enum(["downloads", "rating", "created", "score"]) - .default("downloads"), - min_rating: z.coerce.number().min(0).max(5).default(3), - commercial_only: z.coerce.boolean().default(true), -}); - -const freesoundResultSchema = z.object({ - id: z.number(), - name: z.string(), - description: z.string(), - url: z.string().url(), - previews: z - .object({ - "preview-hq-mp3": z.string().url(), - "preview-lq-mp3": z.string().url(), - "preview-hq-ogg": z.string().url(), - "preview-lq-ogg": z.string().url(), - }) - .optional(), - download: z.string().url().optional(), - duration: z.number(), - filesize: z.number(), - type: z.string(), - channels: z.number(), - bitrate: z.number(), - bitdepth: z.number(), - samplerate: z.number(), - username: z.string(), - tags: z.array(z.string()), - license: z.string(), - created: z.string(), - num_downloads: z.number().optional(), - avg_rating: z.number().optional(), - num_ratings: z.number().optional(), -}); - -const freesoundResponseSchema = z.object({ - count: z.number(), - next: z.string().url().nullable(), - previous: z.string().url().nullable(), - results: z.array(freesoundResultSchema), -}); - -const transformedResultSchema = z.object({ - id: z.number(), - name: z.string(), - description: z.string(), - url: z.string(), - previewUrl: z.string().optional(), - downloadUrl: z.string().optional(), - duration: z.number(), - filesize: z.number(), - type: z.string(), - channels: z.number(), - bitrate: z.number(), - bitdepth: z.number(), - samplerate: z.number(), - username: z.string(), - tags: z.array(z.string()), - license: z.string(), - created: z.string(), - downloads: z.number().optional(), - rating: z.number().optional(), - ratingCount: z.number().optional(), -}); - -const apiResponseSchema = z.object({ - count: z.number(), - next: z.string().nullable(), - previous: z.string().nullable(), - results: z.array(transformedResultSchema), - query: z.string().optional(), - type: z.string(), - page: z.number(), - pageSize: z.number(), - sort: z.string(), - minRating: z.number().optional(), -}); - -function buildSortParameter({ query, sort }: { query?: string; sort: string }) { - if (!query) return `${sort}_desc`; - return sort === "score" ? "score" : `${sort}_desc`; -} - -function applyEffectsFilters({ - params, - min_rating, - commercial_only, -}: { - params: URLSearchParams; - min_rating: number; - commercial_only: boolean; -}) { - params.append("filter", "duration:[* TO 30.0]"); - params.append("filter", `avg_rating:[${min_rating} TO *]`); - - if (commercial_only) { - params.append( - "filter", - 'license:("Attribution" OR "Creative Commons 0" OR "Attribution Noncommercial" OR "Attribution Commercial")', - ); - } - - params.append( - "filter", - "tag:sound-effect OR tag:sfx OR tag:foley OR tag:ambient OR tag:nature OR tag:mechanical OR tag:electronic OR tag:impact OR tag:whoosh OR tag:explosion", - ); -} - -function transformFreesoundResult( - result: z.infer, -) { - return { - id: result.id, - name: result.name, - description: result.description, - url: result.url, - previewUrl: - result.previews?.["preview-hq-mp3"] || - result.previews?.["preview-lq-mp3"], - downloadUrl: result.download, - duration: result.duration, - filesize: result.filesize, - type: result.type, - channels: result.channels, - bitrate: result.bitrate, - bitdepth: result.bitdepth, - samplerate: result.samplerate, - username: result.username, - tags: result.tags, - license: result.license, - created: result.created, - downloads: result.num_downloads || 0, - rating: result.avg_rating || 0, - ratingCount: result.num_ratings || 0, - }; -} - -export async function GET(request: NextRequest) { - try { - const { limited } = await checkRateLimit({ request }); - if (limited) { - return NextResponse.json({ error: "Too many requests" }, { status: 429 }); - } - - const { searchParams } = new URL(request.url); - - const validationResult = searchParamsSchema.safeParse({ - q: searchParams.get("q") || undefined, - type: searchParams.get("type") || undefined, - page: searchParams.get("page") || undefined, - page_size: searchParams.get("page_size") || undefined, - sort: searchParams.get("sort") || undefined, - min_rating: searchParams.get("min_rating") || undefined, - }); - - if (!validationResult.success) { - return NextResponse.json( - { - error: "Invalid parameters", - details: validationResult.error.flatten().fieldErrors, - }, - { status: 400 }, - ); - } - - const { - q: query, - type, - page, - page_size: pageSize, - sort, - min_rating, - commercial_only, - } = validationResult.data; - - if (type === "songs") { - return NextResponse.json( - { - error: "Songs are not available yet", - message: - "Song search functionality is coming soon. Try searching for sound effects instead.", - }, - { status: 501 }, - ); - } - - const baseUrl = "https://freesound.org/apiv2/search/text/"; - - const sortParam = buildSortParameter({ query, sort }); - - const params = new URLSearchParams({ - query: query || "", - token: webEnv.FREESOUND_API_KEY, - page: page.toString(), - page_size: pageSize.toString(), - sort: sortParam, - fields: - "id,name,description,url,previews,download,duration,filesize,type,channels,bitrate,bitdepth,samplerate,username,tags,license,created,num_downloads,avg_rating,num_ratings", - }); - - const isEffectsSearch = type === "effects" || !type; - if (isEffectsSearch) { - applyEffectsFilters({ params, min_rating, commercial_only }); - } - - const response = await fetch(`${baseUrl}?${params.toString()}`); - - if (!response.ok) { - const errorText = await response.text(); - console.error("Freesound API error:", response.status, errorText); - return NextResponse.json( - { error: "Failed to search sounds" }, - { status: response.status }, - ); - } - - const rawData = await response.json(); - - const freesoundValidation = freesoundResponseSchema.safeParse(rawData); - if (!freesoundValidation.success) { - console.error( - "Invalid Freesound API response:", - freesoundValidation.error, - ); - return NextResponse.json( - { error: "Invalid response from Freesound API" }, - { status: 502 }, - ); - } - - const data = freesoundValidation.data; - - const transformedResults = data.results.map(transformFreesoundResult); - - const responseData = { - count: data.count, - next: data.next, - previous: data.previous, - results: transformedResults, - query: query || "", - type: type || "effects", - page, - pageSize, - sort, - minRating: min_rating, - }; - - const responseValidation = apiResponseSchema.safeParse(responseData); - if (!responseValidation.success) { - console.error( - "Invalid API response structure:", - responseValidation.error, - ); - return NextResponse.json( - { error: "Internal response formatting error" }, - { status: 500 }, - ); - } - - return NextResponse.json(responseValidation.data); - } catch (error) { - console.error("Error searching sounds:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 }, - ); - } -} +import { webEnv } from "@opencut/env/web"; +import { type NextRequest, NextResponse } from "next/server"; +import { z } from "zod"; +import { checkRateLimit } from "@/lib/rate-limit"; + +const searchParamsSchema = z.object({ + q: z.string().max(500, "Query too long").optional(), + type: z.enum(["songs", "effects"]).optional(), + page: z.coerce.number().int().min(1).max(1000).default(1), + page_size: z.coerce.number().int().min(1).max(150).default(20), + sort: z + .enum(["downloads", "rating", "created", "score"]) + .default("downloads"), + min_rating: z.coerce.number().min(0).max(5).default(3), + commercial_only: z.coerce.boolean().default(true), +}); + +const freesoundResultSchema = z.object({ + id: z.number(), + name: z.string(), + description: z.string(), + url: z.string().url(), + previews: z + .object({ + "preview-hq-mp3": z.string().url(), + "preview-lq-mp3": z.string().url(), + "preview-hq-ogg": z.string().url(), + "preview-lq-ogg": z.string().url(), + }) + .optional(), + download: z.string().url().optional(), + duration: z.number(), + filesize: z.number(), + type: z.string(), + channels: z.number(), + bitrate: z.number(), + bitdepth: z.number(), + samplerate: z.number(), + username: z.string(), + tags: z.array(z.string()), + license: z.string(), + created: z.string(), + num_downloads: z.number().optional(), + avg_rating: z.number().optional(), + num_ratings: z.number().optional(), +}); + +const freesoundResponseSchema = z.object({ + count: z.number(), + next: z.string().url().nullable(), + previous: z.string().url().nullable(), + results: z.array(freesoundResultSchema), +}); + +const transformedResultSchema = z.object({ + id: z.number(), + name: z.string(), + description: z.string(), + url: z.string(), + previewUrl: z.string().optional(), + downloadUrl: z.string().optional(), + duration: z.number(), + filesize: z.number(), + type: z.string(), + channels: z.number(), + bitrate: z.number(), + bitdepth: z.number(), + samplerate: z.number(), + username: z.string(), + tags: z.array(z.string()), + license: z.string(), + created: z.string(), + downloads: z.number().optional(), + rating: z.number().optional(), + ratingCount: z.number().optional(), +}); + +const apiResponseSchema = z.object({ + count: z.number(), + next: z.string().nullable(), + previous: z.string().nullable(), + results: z.array(transformedResultSchema), + query: z.string().optional(), + type: z.string(), + page: z.number(), + pageSize: z.number(), + sort: z.string(), + minRating: z.number().optional(), +}); + +function buildSortParameter({ query, sort }: { query?: string; sort: string }) { + if (!query) return `${sort}_desc`; + return sort === "score" ? "score" : `${sort}_desc`; +} + +function applyEffectsFilters({ + params, + min_rating, + commercial_only, +}: { + params: URLSearchParams; + min_rating: number; + commercial_only: boolean; +}) { + params.append("filter", "duration:[* TO 30.0]"); + params.append("filter", `avg_rating:[${min_rating} TO *]`); + + if (commercial_only) { + params.append( + "filter", + 'license:("Attribution" OR "Creative Commons 0" OR "Attribution Noncommercial" OR "Attribution Commercial")', + ); + } + + params.append( + "filter", + "tag:sound-effect OR tag:sfx OR tag:foley OR tag:ambient OR tag:nature OR tag:mechanical OR tag:electronic OR tag:impact OR tag:whoosh OR tag:explosion", + ); +} + +function transformFreesoundResult( + result: z.infer, +) { + return { + id: result.id, + name: result.name, + description: result.description, + url: result.url, + previewUrl: + result.previews?.["preview-hq-mp3"] || + result.previews?.["preview-lq-mp3"], + downloadUrl: result.download, + duration: result.duration, + filesize: result.filesize, + type: result.type, + channels: result.channels, + bitrate: result.bitrate, + bitdepth: result.bitdepth, + samplerate: result.samplerate, + username: result.username, + tags: result.tags, + license: result.license, + created: result.created, + downloads: result.num_downloads || 0, + rating: result.avg_rating || 0, + ratingCount: result.num_ratings || 0, + }; +} + +export async function GET(request: NextRequest) { + try { + const { limited } = await checkRateLimit({ request }); + if (limited) { + return NextResponse.json({ error: "Too many requests" }, { status: 429 }); + } + + const { searchParams } = new URL(request.url); + + const validationResult = searchParamsSchema.safeParse({ + q: searchParams.get("q") || undefined, + type: searchParams.get("type") || undefined, + page: searchParams.get("page") || undefined, + page_size: searchParams.get("page_size") || undefined, + sort: searchParams.get("sort") || undefined, + min_rating: searchParams.get("min_rating") || undefined, + }); + + if (!validationResult.success) { + return NextResponse.json( + { + error: "Invalid parameters", + details: validationResult.error.flatten().fieldErrors, + }, + { status: 400 }, + ); + } + + const { + q: query, + type, + page, + page_size: pageSize, + sort, + min_rating, + commercial_only, + } = validationResult.data; + + if (type === "songs") { + return NextResponse.json( + { + error: "Songs are not available yet", + message: + "Song search functionality is coming soon. Try searching for sound effects instead.", + }, + { status: 501 }, + ); + } + + const baseUrl = "https://freesound.org/apiv2/search/text/"; + + const sortParam = buildSortParameter({ query, sort }); + + const params = new URLSearchParams({ + query: query || "", + token: webEnv.FREESOUND_API_KEY, + page: page.toString(), + page_size: pageSize.toString(), + sort: sortParam, + fields: + "id,name,description,url,previews,download,duration,filesize,type,channels,bitrate,bitdepth,samplerate,username,tags,license,created,num_downloads,avg_rating,num_ratings", + }); + + const isEffectsSearch = type === "effects" || !type; + if (isEffectsSearch) { + applyEffectsFilters({ params, min_rating, commercial_only }); + } + + const response = await fetch(`${baseUrl}?${params.toString()}`); + + if (!response.ok) { + const errorText = await response.text(); + console.error("Freesound API error:", response.status, errorText); + return NextResponse.json( + { error: "Failed to search sounds" }, + { status: response.status }, + ); + } + + const rawData = await response.json(); + + const freesoundValidation = freesoundResponseSchema.safeParse(rawData); + if (!freesoundValidation.success) { + console.error( + "Invalid Freesound API response:", + freesoundValidation.error, + ); + return NextResponse.json( + { error: "Invalid response from Freesound API" }, + { status: 502 }, + ); + } + + const data = freesoundValidation.data; + + const transformedResults = data.results.map(transformFreesoundResult); + + const responseData = { + count: data.count, + next: data.next, + previous: data.previous, + results: transformedResults, + query: query || "", + type: type || "effects", + page, + pageSize, + sort, + minRating: min_rating, + }; + + const responseValidation = apiResponseSchema.safeParse(responseData); + if (!responseValidation.success) { + console.error( + "Invalid API response structure:", + responseValidation.error, + ); + return NextResponse.json( + { error: "Internal response formatting error" }, + { status: 500 }, + ); + } + + return NextResponse.json(responseValidation.data); + } catch (error) { + console.error("Error searching sounds:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); + } +} diff --git a/apps/web/src/app/base-page.tsx b/apps/web/src/app/base-page.tsx index 1841a1ee..78768581 100644 --- a/apps/web/src/app/base-page.tsx +++ b/apps/web/src/app/base-page.tsx @@ -1,56 +1,56 @@ -import { Header } from "@/components/header"; -import { Footer } from "@/components/footer"; -import { cn } from "@/utils/ui"; - -interface BasePageProps { - children: React.ReactNode; - className?: string; - mainClassName?: string; - maxWidth?: "3xl" | "6xl" | "full"; - title?: string; - description?: React.ReactNode; - action?: React.ReactNode; -} - -export function BasePage({ - children, - className = "", - mainClassName = "", - maxWidth = "3xl", - title, - description, - action, -}: BasePageProps) { - const maxWidthClass = { - "3xl": "max-w-3xl", - "6xl": "max-w-6xl", - full: "max-w-full", - }[maxWidth]; - - return ( -
-
-
- {title && description && ( -
-

- {title} -

-

- {description} -

- {action} -
- )} - {children} -
-
-
- ); -} +import { Header } from "@/components/header"; +import { Footer } from "@/components/footer"; +import { cn } from "@/utils/ui"; + +interface BasePageProps { + children: React.ReactNode; + className?: string; + mainClassName?: string; + maxWidth?: "3xl" | "6xl" | "full"; + title?: string; + description?: React.ReactNode; + action?: React.ReactNode; +} + +export function BasePage({ + children, + className = "", + mainClassName = "", + maxWidth = "3xl", + title, + description, + action, +}: BasePageProps) { + const maxWidthClass = { + "3xl": "max-w-3xl", + "6xl": "max-w-6xl", + full: "max-w-full", + }[maxWidth]; + + return ( +
+
+
+ {title && description && ( +
+

+ {title} +

+

+ {description} +

+ {action} +
+ )} + {children} +
+
+
+ ); +} diff --git a/apps/web/src/app/blog/[slug]/page.tsx b/apps/web/src/app/blog/[slug]/page.tsx index 90afd919..b581de95 100644 --- a/apps/web/src/app/blog/[slug]/page.tsx +++ b/apps/web/src/app/blog/[slug]/page.tsx @@ -1,135 +1,135 @@ -import type { Metadata } from "next"; -import Image from "next/image"; -import { notFound } from "next/navigation"; -import { BasePage } from "@/app/base-page"; -import Prose from "@/components/ui/prose"; -import { Separator } from "@/components/ui/separator"; -import { getPosts, getSinglePost, processHtmlContent } from "@/lib/blog/query"; -import type { Author, Post } from "@/lib/blog/types"; - -type PageProps = { - params: Promise<{ slug: string }>; - searchParams: Promise<{ [key: string]: string | string[] | undefined }>; -}; - -export async function generateMetadata({ - params, -}: PageProps): Promise { - const slug = (await params).slug; - - const data = await getSinglePost({ slug }); - - if (!data || !data.post) return {}; - - return { - title: data.post.title, - description: data.post.description, - twitter: { - title: `${data.post.title}`, - description: `${data.post.description}`, - card: "summary_large_image", - images: [ - { - url: data.post.coverImage, - width: "1200", - height: "630", - alt: data.post.title, - }, - ], - }, - openGraph: { - type: "article", - images: [ - { - url: data.post.coverImage, - width: "1200", - height: "630", - alt: data.post.title, - }, - ], - title: data.post.title, - description: data.post.description, - publishedTime: new Date(data.post.publishedAt).toISOString(), - authors: data.post.authors.map((author: Author) => author.name), - }, - }; -} - -export async function generateStaticParams() { - const data = await getPosts(); - if (!data || !data.posts.length) return []; - - return data.posts.map((post) => ({ - slug: post.slug, - })); -} - -export default async function BlogPostPage({ params }: PageProps) { - const slug = (await params).slug; - const data = await getSinglePost({ slug }); - if (!data || !data.post) return notFound(); - - const html = await processHtmlContent({ html: data.post.content }); - - return ( - - - - - - ); -} - -function PostHeader({ post }: { post: Post }) { - const formattedDate = new Date(post.publishedAt).toLocaleDateString("en-US", { - day: "numeric", - month: "long", - year: "numeric", - }); - - return ( -
- - - {post.coverImage && } -
- ); -} - -function PostCoverImage({ post }: { post: Post }) { - return ( -
- {post.title} -
- ); -} - -function PostMeta({ date, publishedAt }: { date: string; publishedAt: Date }) { - return ( -
- -
- ); -} - -function PostTitle({ title }: { title: string }) { - return ( -

- {title} -

- ); -} - -function PostContent({ html }: { html: string }) { - return ( -
- -
- ); -} +import type { Metadata } from "next"; +import Image from "next/image"; +import { notFound } from "next/navigation"; +import { BasePage } from "@/app/base-page"; +import Prose from "@/components/ui/prose"; +import { Separator } from "@/components/ui/separator"; +import { getPosts, getSinglePost, processHtmlContent } from "@/lib/blog/query"; +import type { Author, Post } from "@/lib/blog/types"; + +type PageProps = { + params: Promise<{ slug: string }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; +}; + +export async function generateMetadata({ + params, +}: PageProps): Promise { + const slug = (await params).slug; + + const data = await getSinglePost({ slug }); + + if (!data || !data.post) return {}; + + return { + title: data.post.title, + description: data.post.description, + twitter: { + title: `${data.post.title}`, + description: `${data.post.description}`, + card: "summary_large_image", + images: [ + { + url: data.post.coverImage, + width: "1200", + height: "630", + alt: data.post.title, + }, + ], + }, + openGraph: { + type: "article", + images: [ + { + url: data.post.coverImage, + width: "1200", + height: "630", + alt: data.post.title, + }, + ], + title: data.post.title, + description: data.post.description, + publishedTime: new Date(data.post.publishedAt).toISOString(), + authors: data.post.authors.map((author: Author) => author.name), + }, + }; +} + +export async function generateStaticParams() { + const data = await getPosts(); + if (!data || !data.posts.length) return []; + + return data.posts.map((post) => ({ + slug: post.slug, + })); +} + +export default async function BlogPostPage({ params }: PageProps) { + const slug = (await params).slug; + const data = await getSinglePost({ slug }); + if (!data || !data.post) return notFound(); + + const html = await processHtmlContent({ html: data.post.content }); + + return ( + + + + + + ); +} + +function PostHeader({ post }: { post: Post }) { + const formattedDate = new Date(post.publishedAt).toLocaleDateString("en-US", { + day: "numeric", + month: "long", + year: "numeric", + }); + + return ( +
+ + + {post.coverImage && } +
+ ); +} + +function PostCoverImage({ post }: { post: Post }) { + return ( +
+ {post.title} +
+ ); +} + +function PostMeta({ date, publishedAt }: { date: string; publishedAt: Date }) { + return ( +
+ +
+ ); +} + +function PostTitle({ title }: { title: string }) { + return ( +

+ {title} +

+ ); +} + +function PostContent({ html }: { html: string }) { + return ( +
+ +
+ ); +} diff --git a/apps/web/src/app/blog/page.tsx b/apps/web/src/app/blog/page.tsx index df0b195d..9f5a8972 100644 --- a/apps/web/src/app/blog/page.tsx +++ b/apps/web/src/app/blog/page.tsx @@ -1,52 +1,52 @@ -import type { Metadata } from "next"; -import Link from "next/link"; -import { BasePage } from "@/app/base-page"; -import { Separator } from "@/components/ui/separator"; -import { getPosts } from "@/lib/blog/query"; -import type { Post } from "@/lib/blog/types"; - -export const metadata: Metadata = { - title: "Blog - OpenCut", - description: - "Read the latest news and updates about OpenCut, the free and open-source video editor.", - openGraph: { - title: "Blog - OpenCut", - description: - "Read the latest news and updates about OpenCut, the free and open-source video editor.", - type: "website", - }, -}; - -export default async function BlogPage() { - const data = await getPosts().catch(() => null); - if (!data || !data.posts) return
No posts yet
; - - return ( - -
- {data.posts.map((post) => ( -
- - -
- ))} -
-
- ); -} - -function BlogPostItem({ post }: { post: Post }) { - return ( - -
-
-

{post.title}

-

{post.description}

-
-
- - ); -} +import type { Metadata } from "next"; +import Link from "next/link"; +import { BasePage } from "@/app/base-page"; +import { Separator } from "@/components/ui/separator"; +import { getPosts } from "@/lib/blog/query"; +import type { Post } from "@/lib/blog/types"; + +export const metadata: Metadata = { + title: "Blog - OpenCut", + description: + "Read the latest news and updates about OpenCut, the free and open-source video editor.", + openGraph: { + title: "Blog - OpenCut", + description: + "Read the latest news and updates about OpenCut, the free and open-source video editor.", + type: "website", + }, +}; + +export default async function BlogPage() { + const data = await getPosts().catch(() => null); + if (!data || !data.posts) return
No posts yet
; + + return ( + +
+ {data.posts.map((post) => ( +
+ + +
+ ))} +
+
+ ); +} + +function BlogPostItem({ post }: { post: Post }) { + return ( + +
+
+

{post.title}

+

{post.description}

+
+
+ + ); +} diff --git a/apps/web/src/app/brand/page.tsx b/apps/web/src/app/brand/page.tsx index 94e3f3e1..a7085154 100644 --- a/apps/web/src/app/brand/page.tsx +++ b/apps/web/src/app/brand/page.tsx @@ -1,268 +1,268 @@ -"use client"; - -import type { CSSProperties } from "react"; -import Image from "next/image"; -import Link from "next/link"; -import { Check, Copy, Download } from "lucide-react"; -import { useState } from "react"; -import { BasePage } from "@/app/base-page"; -import { Button } from "@/components/ui/button"; -import { Card } from "@/components/ui/card"; -import { Separator } from "@/components/ui/separator"; -import { cn } from "@/utils/ui"; - -function downloadAsset(src: string) { - const filename = src.split("/").pop() ?? "asset.svg"; - const a = document.createElement("a"); - a.href = src; - a.download = filename; - a.click(); -} - -async function copyAsset(src: string) { - const res = await fetch(src); - const text = await res.text(); - await navigator.clipboard.writeText(text); -} - -const ALL_ASSETS = () => ASSET_SECTIONS.flatMap((s) => s.assets); - -type AssetTheme = "dark" | "light" | "icon"; - -interface AssetVariant { - src: string; - theme: AssetTheme; - label: string; - width: number; - height: number; -} - -interface AssetSection { - title: string; - description: string; - cols: "1" | "2"; - assets: AssetVariant[]; -} - -const ASSET_SECTIONS: AssetSection[] = [ - { - title: "Symbol", - description: - "Use the symbol on its own when the OpenCut name is already present nearby or space is limited.", - cols: "2", - assets: [ - { - src: "/logos/opencut/symbol.svg", - theme: "dark", - label: "Symbol", - width: 400, - height: 400, - }, - { - src: "/logos/opencut/symbol-light.svg", - theme: "light", - label: "Symbol", - width: 400, - height: 400, - }, - ], - }, - { - title: "Lockup", - description: - "The full lockup combines the symbol and wordmark. Prefer this in most contexts where you have enough horizontal space.", - cols: "2", - assets: [ - { - src: "/logos/opencut/logo.svg", - theme: "dark", - label: "Logo", - width: 1809, - height: 400, - }, - { - src: "/logos/opencut/logo-light.svg", - theme: "light", - label: "Logo", - width: 1809, - height: 400, - }, - { - src: "/logos/opencut/text.svg", - theme: "dark", - label: "Text", - width: 1760, - height: 400, - }, - { - src: "/logos/opencut/text-light.svg", - theme: "light", - label: "Text", - width: 1760, - height: 400, - }, - ], - }, -]; - -export default function BrandPage() { - return ( - - Download OpenCut brand assets for use in your projects.{" "} - - document - .getElementById("guidelines") - ?.scrollIntoView({ behavior: "smooth" }) - } - > - Read the brand guidelines. - - - } - action={ - - } - > -
- {ASSET_SECTIONS.map((section) => ( -
-
-

{section.title}

-

- {section.description} -

-
-
- {section.assets.map((variant) => ( - - ))} -
-
- ))} -
- - - -
-
-

Usage

-

- OpenCut is open source — the code is free to use under its license. - That license does not cover the name or logo. You can say you use - OpenCut, that your project integrates with OpenCut, or that it was - built on top of OpenCut. You cannot name your product OpenCut, imply - we made or endorse your product, or use the marks commercially - without asking first. For anything unclear, reach out at{" "} - - brand@opencut.app - - . -

-
- -
-

What's not allowed

-
    - {[ - "Using OpenCut in the name of your product, service, or domain.", - "Implying that OpenCut made, sponsors, or endorses your work.", - "Using the logo or name on merchandise or commercial marketing.", - "Modifying the marks.", - ].map((item) => ( -
  • - - - {item} -
  • - ))} -
-
-
-
- ); -} - -const CHECKER_STYLES: Record<"dark" | "light", CSSProperties> = { - light: { - backgroundImage: - "linear-gradient(45deg, #292929 25%, transparent 25%), linear-gradient(-45deg, #292929 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #292929 75%), linear-gradient(-45deg, transparent 75%, #292929 75%)", - backgroundSize: "18px 18px", - backgroundPosition: "0 0, 0 9px, 9px -9px, -9px 0px", - backgroundColor: "#000", - }, - dark: { - backgroundImage: - "linear-gradient(45deg, #e0e0e0 25%, transparent 25%), linear-gradient(-45deg, #e0e0e0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #e0e0e0 75%), linear-gradient(-45deg, transparent 75%, #e0e0e0 75%)", - backgroundSize: "18px 18px", - backgroundPosition: "0 0, 0 9px, 9px -9px, -9px 0px", - backgroundColor: "#f5f5f5", - }, -}; - -function AssetCard({ variant }: { variant: AssetVariant }) { - const [copied, setCopied] = useState(false); - - async function handleCopy() { - await copyAsset(variant.src); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } - - return ( - -
- {variant.label} -
- - -
- ); -} +"use client"; + +import type { CSSProperties } from "react"; +import Image from "next/image"; +import Link from "next/link"; +import { Check, Copy, Download } from "lucide-react"; +import { useState } from "react"; +import { BasePage } from "@/app/base-page"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; +import { cn } from "@/utils/ui"; + +function downloadAsset(src: string) { + const filename = src.split("/").pop() ?? "asset.svg"; + const a = document.createElement("a"); + a.href = src; + a.download = filename; + a.click(); +} + +async function copyAsset(src: string) { + const res = await fetch(src); + const text = await res.text(); + await navigator.clipboard.writeText(text); +} + +const ALL_ASSETS = () => ASSET_SECTIONS.flatMap((s) => s.assets); + +type AssetTheme = "dark" | "light" | "icon"; + +interface AssetVariant { + src: string; + theme: AssetTheme; + label: string; + width: number; + height: number; +} + +interface AssetSection { + title: string; + description: string; + cols: "1" | "2"; + assets: AssetVariant[]; +} + +const ASSET_SECTIONS: AssetSection[] = [ + { + title: "Symbol", + description: + "Use the symbol on its own when the OpenCut name is already present nearby or space is limited.", + cols: "2", + assets: [ + { + src: "/logos/opencut/symbol.svg", + theme: "dark", + label: "Symbol", + width: 400, + height: 400, + }, + { + src: "/logos/opencut/symbol-light.svg", + theme: "light", + label: "Symbol", + width: 400, + height: 400, + }, + ], + }, + { + title: "Lockup", + description: + "The full lockup combines the symbol and wordmark. Prefer this in most contexts where you have enough horizontal space.", + cols: "2", + assets: [ + { + src: "/logos/opencut/logo.svg", + theme: "dark", + label: "Logo", + width: 1809, + height: 400, + }, + { + src: "/logos/opencut/logo-light.svg", + theme: "light", + label: "Logo", + width: 1809, + height: 400, + }, + { + src: "/logos/opencut/text.svg", + theme: "dark", + label: "Text", + width: 1760, + height: 400, + }, + { + src: "/logos/opencut/text-light.svg", + theme: "light", + label: "Text", + width: 1760, + height: 400, + }, + ], + }, +]; + +export default function BrandPage() { + return ( + + Download OpenCut brand assets for use in your projects.{" "} + + document + .getElementById("guidelines") + ?.scrollIntoView({ behavior: "smooth" }) + } + > + Read the brand guidelines. + + + } + action={ + + } + > +
+ {ASSET_SECTIONS.map((section) => ( +
+
+

{section.title}

+

+ {section.description} +

+
+
+ {section.assets.map((variant) => ( + + ))} +
+
+ ))} +
+ + + +
+
+

Usage

+

+ OpenCut is open source — the code is free to use under its license. + That license does not cover the name or logo. You can say you use + OpenCut, that your project integrates with OpenCut, or that it was + built on top of OpenCut. You cannot name your product OpenCut, imply + we made or endorse your product, or use the marks commercially + without asking first. For anything unclear, reach out at{" "} + + brand@opencut.app + + . +

+
+ +
+

What's not allowed

+
    + {[ + "Using OpenCut in the name of your product, service, or domain.", + "Implying that OpenCut made, sponsors, or endorses your work.", + "Using the logo or name on merchandise or commercial marketing.", + "Modifying the marks.", + ].map((item) => ( +
  • + - + {item} +
  • + ))} +
+
+
+
+ ); +} + +const CHECKER_STYLES: Record<"dark" | "light", CSSProperties> = { + light: { + backgroundImage: + "linear-gradient(45deg, #292929 25%, transparent 25%), linear-gradient(-45deg, #292929 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #292929 75%), linear-gradient(-45deg, transparent 75%, #292929 75%)", + backgroundSize: "18px 18px", + backgroundPosition: "0 0, 0 9px, 9px -9px, -9px 0px", + backgroundColor: "#000", + }, + dark: { + backgroundImage: + "linear-gradient(45deg, #e0e0e0 25%, transparent 25%), linear-gradient(-45deg, #e0e0e0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #e0e0e0 75%), linear-gradient(-45deg, transparent 75%, #e0e0e0 75%)", + backgroundSize: "18px 18px", + backgroundPosition: "0 0, 0 9px, 9px -9px, -9px 0px", + backgroundColor: "#f5f5f5", + }, +}; + +function AssetCard({ variant }: { variant: AssetVariant }) { + const [copied, setCopied] = useState(false); + + async function handleCopy() { + await copyAsset(variant.src); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + + return ( + +
+ {variant.label} +
+ + +
+ ); +} diff --git a/apps/web/src/app/changelog/[version]/page.tsx b/apps/web/src/app/changelog/[version]/page.tsx index af382822..a3647fbd 100644 --- a/apps/web/src/app/changelog/[version]/page.tsx +++ b/apps/web/src/app/changelog/[version]/page.tsx @@ -1,105 +1,105 @@ -import type { Metadata } from "next"; -import Link from "next/link"; -import { notFound } from "next/navigation"; -import { BasePage } from "@/app/base-page"; -import { allChangelogs } from "content-collections"; -import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; -import { getSortedReleases } from "../utils"; -import { - ReleaseArticle, - ReleaseMeta, - ReleaseTitle, - ReleaseDescription, - ReleaseChanges, -} from "../components/release"; -import { CopyMarkdownButton } from "../components/copy-markdown-button"; - -type Props = { params: Promise<{ version: string }> }; - -export async function generateStaticParams() { - return allChangelogs.map((r) => ({ version: r.version })); -} - -export async function generateMetadata({ params }: Props): Promise { - const { version } = await params; - const release = allChangelogs.find((r) => r.version === version); - if (!release) return {}; - return { - title: `${release.title} (${release.version}) - OpenCut Changelog`, - description: release.description, - }; -} - -export default async function ReleaseDetailPage({ params }: Props) { - const { version } = await params; - const releases = getSortedReleases(); - const index = releases.findIndex((r) => r.version === version); - - if (index === -1) notFound(); - - const release = releases[index]; - const newer = index > 0 ? releases[index - 1] : null; - const older = index < releases.length - 1 ? releases[index + 1] : null; - - return ( - -
- - - All releases - - - -
-
- - -
- {release.title} - {release.description && ( - {release.description} - )} -
- -
- - -
-
- ); -} +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { BasePage } from "@/app/base-page"; +import { allChangelogs } from "content-collections"; +import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"; +import { getSortedReleases } from "../utils"; +import { + ReleaseArticle, + ReleaseMeta, + ReleaseTitle, + ReleaseDescription, + ReleaseChanges, +} from "../components/release"; +import { CopyMarkdownButton } from "../components/copy-markdown-button"; + +type Props = { params: Promise<{ version: string }> }; + +export async function generateStaticParams() { + return allChangelogs.map((r) => ({ version: r.version })); +} + +export async function generateMetadata({ params }: Props): Promise { + const { version } = await params; + const release = allChangelogs.find((r) => r.version === version); + if (!release) return {}; + return { + title: `${release.title} (${release.version}) - OpenCut Changelog`, + description: release.description, + }; +} + +export default async function ReleaseDetailPage({ params }: Props) { + const { version } = await params; + const releases = getSortedReleases(); + const index = releases.findIndex((r) => r.version === version); + + if (index === -1) notFound(); + + const release = releases[index]; + const newer = index > 0 ? releases[index - 1] : null; + const older = index < releases.length - 1 ? releases[index + 1] : null; + + return ( + +
+ + + All releases + + + +
+
+ + +
+ {release.title} + {release.description && ( + {release.description} + )} +
+ +
+ + +
+
+ ); +} diff --git a/apps/web/src/app/changelog/components/copy-markdown-button.tsx b/apps/web/src/app/changelog/components/copy-markdown-button.tsx index 2366550a..edb920f5 100644 --- a/apps/web/src/app/changelog/components/copy-markdown-button.tsx +++ b/apps/web/src/app/changelog/components/copy-markdown-button.tsx @@ -1,68 +1,68 @@ -"use client"; - -import { useState } from "react"; -import { CheckIcon, ClipboardIcon } from "lucide-react"; -import { getSectionTitle, groupAndOrderChanges } from "../utils"; -import type { Change } from "../utils"; -import { cn } from "@/utils/ui"; -import { Button } from "@/components/ui/button"; - -function buildMarkdown({ - description, - changes, -}: { - description?: string; - changes: Change[]; -}): string { - const lines: string[] = []; - - if (description) { - lines.push(description, ""); - } - - const { grouped, orderedTypes } = groupAndOrderChanges({ changes }); - - for (const type of orderedTypes) { - lines.push(`## ${getSectionTitle(type)}`); - for (const change of grouped[type]) { - lines.push(`- ${change.text}`); - } - lines.push(""); - } - - return lines.join("\n").trimEnd(); -} - -export function CopyMarkdownButton({ - description, - changes, -}: { - description?: string; - changes: Change[]; -}) { - const [copied, setCopied] = useState(false); - - const handleCopy = async () => { - const markdown = buildMarkdown({ description, changes }); - await navigator.clipboard.writeText(markdown); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; - - return ( - - ); -} +"use client"; + +import { useState } from "react"; +import { CheckIcon, ClipboardIcon } from "lucide-react"; +import { getSectionTitle, groupAndOrderChanges } from "../utils"; +import type { Change } from "../utils"; +import { cn } from "@/utils/ui"; +import { Button } from "@/components/ui/button"; + +function buildMarkdown({ + description, + changes, +}: { + description?: string; + changes: Change[]; +}): string { + const lines: string[] = []; + + if (description) { + lines.push(description, ""); + } + + const { grouped, orderedTypes } = groupAndOrderChanges({ changes }); + + for (const type of orderedTypes) { + lines.push(`## ${getSectionTitle(type)}`); + for (const change of grouped[type]) { + lines.push(`- ${change.text}`); + } + lines.push(""); + } + + return lines.join("\n").trimEnd(); +} + +export function CopyMarkdownButton({ + description, + changes, +}: { + description?: string; + changes: Change[]; +}) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + const markdown = buildMarkdown({ description, changes }); + await navigator.clipboard.writeText(markdown); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + ); +} diff --git a/apps/web/src/app/changelog/components/release.tsx b/apps/web/src/app/changelog/components/release.tsx index eb04c1f0..2226c321 100644 --- a/apps/web/src/app/changelog/components/release.tsx +++ b/apps/web/src/app/changelog/components/release.tsx @@ -1,106 +1,106 @@ -import type { ReactNode } from "react"; -import Link from "next/link"; -import { cn } from "@/utils/ui"; -import { getSectionTitle, groupAndOrderChanges } from "../utils"; -import type { Release } from "../utils"; - -export function ReleaseArticle({ - variant, - isLatest, - children, -}: { - variant: "list" | "detail"; - isLatest?: boolean; - children: ReactNode; -}) { - if (variant === "list") { - return ( -
-
-
-
-
{children}
-
- ); - } - - return
{children}
; -} - -export function ReleaseMeta({ release }: { release: Release }) { - return ( - - {release.version} — {release.date} - - ); -} - -const titleSizes: Record<"h1" | "h2", string> = { - h1: "text-4xl", - h2: "text-2xl", -}; - -export function ReleaseTitle({ - as: As, - href, - children, -}: { - as: "h1" | "h2"; - href?: string; - children: ReactNode; -}) { - return ( - - {href ? ( - - {children} - - ) : ( - children - )} - - ); -} - -export function ReleaseDescription({ children }: { children: ReactNode }) { - return ( -

- {children} -

- ); -} - -export function ReleaseChanges({ release }: { release: Release }) { - const { grouped, orderedTypes } = groupAndOrderChanges({ - changes: release.changes, - }); - - return ( -
- {orderedTypes.map((type) => ( -
-

- {getSectionTitle(type)}: -

-
    - {grouped[type].map((change) => ( -
  • - {change.text} -
  • - ))} -
-
- ))} -
- ); -} +import type { ReactNode } from "react"; +import Link from "next/link"; +import { cn } from "@/utils/ui"; +import { getSectionTitle, groupAndOrderChanges } from "../utils"; +import type { Release } from "../utils"; + +export function ReleaseArticle({ + variant, + isLatest, + children, +}: { + variant: "list" | "detail"; + isLatest?: boolean; + children: ReactNode; +}) { + if (variant === "list") { + return ( +
+
+
+
+
{children}
+
+ ); + } + + return
{children}
; +} + +export function ReleaseMeta({ release }: { release: Release }) { + return ( + + {release.version} — {release.date} + + ); +} + +const titleSizes: Record<"h1" | "h2", string> = { + h1: "text-4xl", + h2: "text-2xl", +}; + +export function ReleaseTitle({ + as: As, + href, + children, +}: { + as: "h1" | "h2"; + href?: string; + children: ReactNode; +}) { + return ( + + {href ? ( + + {children} + + ) : ( + children + )} + + ); +} + +export function ReleaseDescription({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +export function ReleaseChanges({ release }: { release: Release }) { + const { grouped, orderedTypes } = groupAndOrderChanges({ + changes: release.changes, + }); + + return ( +
+ {orderedTypes.map((type) => ( +
+

+ {getSectionTitle(type)}: +

+
    + {grouped[type].map((change) => ( +
  • + {change.text} +
  • + ))} +
+
+ ))} +
+ ); +} diff --git a/apps/web/src/app/changelog/utils.ts b/apps/web/src/app/changelog/utils.ts index 163207c0..d3405631 100644 --- a/apps/web/src/app/changelog/utils.ts +++ b/apps/web/src/app/changelog/utils.ts @@ -1,43 +1,43 @@ -import { allChangelogs } from "content-collections"; - -export type Change = { type: string; text: string }; -export type Release = (typeof allChangelogs)[number]; - -const knownSectionOrder = ["new", "improved", "fixed", "breaking"]; - -const knownSectionTitles: Record = { - new: "Features", - improved: "Improvements", - fixed: "Fixes", - breaking: "Breaking Changes", -}; - -export function getSectionTitle(type: string): string { - return ( - knownSectionTitles[type] ?? type.charAt(0).toUpperCase() + type.slice(1) - ); -} - -export function groupAndOrderChanges({ changes }: { changes: Change[] }) { - const grouped = changes.reduce>((acc, change) => { - if (!acc[change.type]) acc[change.type] = []; - acc[change.type].push(change); - return acc; - }, {}); - - const customTypes = Object.keys(grouped).filter( - (type) => !knownSectionOrder.includes(type), - ); - const orderedTypes = [ - ...knownSectionOrder.filter((type) => grouped[type]?.length > 0), - ...customTypes, - ]; - - return { grouped, orderedTypes }; -} - -export function getSortedReleases() { - return [...allChangelogs].sort((a, b) => - b.version.localeCompare(a.version, undefined, { numeric: true }), - ); -} +import { allChangelogs } from "content-collections"; + +export type Change = { type: string; text: string }; +export type Release = (typeof allChangelogs)[number]; + +const knownSectionOrder = ["new", "improved", "fixed", "breaking"]; + +const knownSectionTitles: Record = { + new: "Features", + improved: "Improvements", + fixed: "Fixes", + breaking: "Breaking Changes", +}; + +export function getSectionTitle(type: string): string { + return ( + knownSectionTitles[type] ?? type.charAt(0).toUpperCase() + type.slice(1) + ); +} + +export function groupAndOrderChanges({ changes }: { changes: Change[] }) { + const grouped = changes.reduce>((acc, change) => { + if (!acc[change.type]) acc[change.type] = []; + acc[change.type].push(change); + return acc; + }, {}); + + const customTypes = Object.keys(grouped).filter( + (type) => !knownSectionOrder.includes(type), + ); + const orderedTypes = [ + ...knownSectionOrder.filter((type) => grouped[type]?.length > 0), + ...customTypes, + ]; + + return { grouped, orderedTypes }; +} + +export function getSortedReleases() { + return [...allChangelogs].sort((a, b) => + b.version.localeCompare(a.version, undefined, { numeric: true }), + ); +} diff --git a/apps/web/src/app/contributors/page.tsx b/apps/web/src/app/contributors/page.tsx index 21703852..64792632 100644 --- a/apps/web/src/app/contributors/page.tsx +++ b/apps/web/src/app/contributors/page.tsx @@ -1,242 +1,242 @@ -import type { Metadata } from "next"; -import Link from "next/link"; -import { GitHubContributeSection } from "@/components/gitHub-contribute-section"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Card, CardContent } from "@/components/ui/card"; -import { EXTERNAL_TOOLS } from "@/constants/site-constants"; -import { BasePage } from "../base-page"; - -export const metadata: Metadata = { - title: "Contributors - OpenCut", - description: - "Meet the amazing people who contribute to OpenCut, the free and open-source video editor.", - openGraph: { - title: "Contributors - OpenCut", - description: - "Meet the amazing people who contribute to OpenCut, the free and open-source video editor.", - type: "website", - }, -}; - -interface Contributor { - id: number; - login: string; - avatar_url: string; - html_url: string; - contributions: number; - type: string; -} - -async function getContributors(): Promise { - try { - const response = await fetch( - "https://api.github.com/repos/OpenCut-app/OpenCut/contributors?per_page=100", - { - headers: { - Accept: "application/vnd.github.v3+json", - "User-Agent": "OpenCut-Web-App", - }, - next: { revalidate: 600 }, // 10 minutes - }, - ); - - if (!response.ok) { - console.error("Failed to fetch contributors"); - return []; - } - - const contributors = (await response.json()) as Contributor[]; - - const filteredContributors = contributors.filter( - (contributor) => contributor.type === "User", - ); - - return filteredContributors; - } catch (error) { - console.error("Error fetching contributors:", error); - return []; - } -} - -export default async function ContributorsPage() { - const contributors = await getContributors(); - const topContributors = contributors.slice(0, 2); - const otherContributors = contributors.slice(2); - const totalContributions = contributors.reduce( - (sum, c) => sum + c.contributions, - 0, - ); - - return ( - -
- - -
- -
- {topContributors.length > 0 && ( - - )} - {otherContributors.length > 0 && ( - - )} - - -
-
- ); -} - -function StatItem({ value, label }: { value: number; label: string }) { - return ( -
-
- {value} - {label} -
- ); -} - -function TopContributorsSection({ - contributors, -}: { - contributors: Contributor[]; -}) { - return ( -
-
-

Top contributors

-

- Leading the way in contributions -

-
- -
- {contributors.map((contributor) => ( - - ))} -
-
- ); -} - -function TopContributorCard({ contributor }: { contributor: Contributor }) { - return ( - - - - - - - {contributor.login.charAt(0).toUpperCase()} - - -
-

{contributor.login}

-
- {contributor.contributions} - contributions -
-
-
-
- - ); -} - -function AllContributorsSection({ - contributors, -}: { - contributors: Contributor[]; -}) { - return ( -
-
-

All contributors

-

- Everyone who makes OpenCut better -

-
- -
- {contributors.map((contributor) => ( - -
- - - - {contributor.login.charAt(0).toUpperCase()} - - -
-

{contributor.login}

-

- {contributor.contributions} -

-
-
- - ))} -
-
- ); -} - -function ExternalToolsSection() { - return ( -
-
-

External tools

-

Tools we use to build OpenCut

-
- -
- {EXTERNAL_TOOLS.map((tool, index) => ( - - - - -
-

{tool.name}

-

- {tool.description} -

-
-
-
- - ))} -
-
- ); -} +import type { Metadata } from "next"; +import Link from "next/link"; +import { GitHubContributeSection } from "@/components/gitHub-contribute-section"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Card, CardContent } from "@/components/ui/card"; +import { EXTERNAL_TOOLS } from "@/constants/site-constants"; +import { BasePage } from "../base-page"; + +export const metadata: Metadata = { + title: "Contributors - OpenCut", + description: + "Meet the amazing people who contribute to OpenCut, the free and open-source video editor.", + openGraph: { + title: "Contributors - OpenCut", + description: + "Meet the amazing people who contribute to OpenCut, the free and open-source video editor.", + type: "website", + }, +}; + +interface Contributor { + id: number; + login: string; + avatar_url: string; + html_url: string; + contributions: number; + type: string; +} + +async function getContributors(): Promise { + try { + const response = await fetch( + "https://api.github.com/repos/OpenCut-app/OpenCut/contributors?per_page=100", + { + headers: { + Accept: "application/vnd.github.v3+json", + "User-Agent": "OpenCut-Web-App", + }, + next: { revalidate: 600 }, // 10 minutes + }, + ); + + if (!response.ok) { + console.error("Failed to fetch contributors"); + return []; + } + + const contributors = (await response.json()) as Contributor[]; + + const filteredContributors = contributors.filter( + (contributor) => contributor.type === "User", + ); + + return filteredContributors; + } catch (error) { + console.error("Error fetching contributors:", error); + return []; + } +} + +export default async function ContributorsPage() { + const contributors = await getContributors(); + const topContributors = contributors.slice(0, 2); + const otherContributors = contributors.slice(2); + const totalContributions = contributors.reduce( + (sum, c) => sum + c.contributions, + 0, + ); + + return ( + +
+ + +
+ +
+ {topContributors.length > 0 && ( + + )} + {otherContributors.length > 0 && ( + + )} + + +
+
+ ); +} + +function StatItem({ value, label }: { value: number; label: string }) { + return ( +
+
+ {value} + {label} +
+ ); +} + +function TopContributorsSection({ + contributors, +}: { + contributors: Contributor[]; +}) { + return ( +
+
+

Top contributors

+

+ Leading the way in contributions +

+
+ +
+ {contributors.map((contributor) => ( + + ))} +
+
+ ); +} + +function TopContributorCard({ contributor }: { contributor: Contributor }) { + return ( + + + + + + + {contributor.login.charAt(0).toUpperCase()} + + +
+

{contributor.login}

+
+ {contributor.contributions} + contributions +
+
+
+
+ + ); +} + +function AllContributorsSection({ + contributors, +}: { + contributors: Contributor[]; +}) { + return ( +
+
+

All contributors

+

+ Everyone who makes OpenCut better +

+
+ +
+ {contributors.map((contributor) => ( + +
+ + + + {contributor.login.charAt(0).toUpperCase()} + + +
+

{contributor.login}

+

+ {contributor.contributions} +

+
+
+ + ))} +
+
+ ); +} + +function ExternalToolsSection() { + return ( +
+
+

External tools

+

Tools we use to build OpenCut

+
+ +
+ {EXTERNAL_TOOLS.map((tool, index) => ( + + + + +
+

{tool.name}

+

+ {tool.description} +

+
+
+
+ + ))} +
+
+ ); +} diff --git a/apps/web/src/app/editor/[project_id]/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx index 3ec6ddb8..38a25d4c 100644 --- a/apps/web/src/app/editor/[project_id]/page.tsx +++ b/apps/web/src/app/editor/[project_id]/page.tsx @@ -1,113 +1,113 @@ -"use client"; - -import { useParams } from "next/navigation"; -import { - ResizablePanelGroup, - ResizablePanel, - ResizableHandle, -} from "@/components/ui/resizable"; -import { AssetsPanel } from "@/components/editor/panels/assets"; -import { PropertiesPanel } from "@/components/editor/panels/properties"; -import { Timeline } from "@/components/editor/panels/timeline"; -import { PreviewPanel } from "@/components/editor/panels/preview"; -import { EditorHeader } from "@/components/editor/editor-header"; -import { EditorProvider } from "@/components/providers/editor-provider"; -import { Onboarding } from "@/components/editor/onboarding"; -import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog"; -import { usePanelStore } from "@/stores/panel-store"; -import { usePasteMedia } from "@/hooks/use-paste-media"; -import { MobileGate } from "@/components/editor/mobile-gate"; - -export default function Editor() { - const params = useParams(); - const projectId = params.project_id as string; - - return ( - - -
- -
- -
- - -
-
-
- ); -} - -function EditorLayout() { - usePasteMedia(); - const { panels, setPanel } = usePanelStore(); - - return ( - { - setPanel("mainContent", sizes[0] ?? panels.mainContent); - setPanel("timeline", sizes[1] ?? panels.timeline); - }} - > - - { - setPanel("tools", sizes[0] ?? panels.tools); - setPanel("preview", sizes[1] ?? panels.preview); - setPanel("properties", sizes[2] ?? panels.properties); - }} - > - - - - - - - - - - - - - - - - - - - - - - - - - ); -} +"use client"; + +import { useParams } from "next/navigation"; +import { + ResizablePanelGroup, + ResizablePanel, + ResizableHandle, +} from "@/components/ui/resizable"; +import { AssetsPanel } from "@/components/editor/panels/assets"; +import { PropertiesPanel } from "@/components/editor/panels/properties"; +import { Timeline } from "@/components/editor/panels/timeline"; +import { PreviewPanel } from "@/components/editor/panels/preview"; +import { EditorHeader } from "@/components/editor/editor-header"; +import { EditorProvider } from "@/components/providers/editor-provider"; +import { Onboarding } from "@/components/editor/onboarding"; +import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog"; +import { usePanelStore } from "@/stores/panel-store"; +import { usePasteMedia } from "@/hooks/use-paste-media"; +import { MobileGate } from "@/components/editor/mobile-gate"; + +export default function Editor() { + const params = useParams(); + const projectId = params.project_id as string; + + return ( + + +
+ +
+ +
+ + +
+
+
+ ); +} + +function EditorLayout() { + usePasteMedia(); + const { panels, setPanel } = usePanelStore(); + + return ( + { + setPanel("mainContent", sizes[0] ?? panels.mainContent); + setPanel("timeline", sizes[1] ?? panels.timeline); + }} + > + + { + setPanel("tools", sizes[0] ?? panels.tools); + setPanel("preview", sizes[1] ?? panels.preview); + setPanel("properties", sizes[2] ?? panels.properties); + }} + > + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 21219427..a2df844a 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -1,284 +1,284 @@ -@import "tailwindcss"; - -/* Custom variant for dark mode */ -@custom-variant dark (&:where(.dark, .dark *)); - -/* Plugins */ -@plugin "@tailwindcss/typography"; -@plugin "tailwindcss-animate"; - -:root { - --background: hsl(0, 0%, 100%); - --foreground: hsl(0 0% 11%); - --card: hsl(0, 0%, 100%); - --card-foreground: hsl(0 0% 11%); - --popover: hsl(0, 0%, 100%); - --popover-hover: hsl(0, 0%, 96%); - --popover-foreground: hsl(0 0% 2%); - --primary: hsl(200, 90%, 52%); - --primary-foreground: hsl(0, 0%, 100%); - --secondary: hsl(204, 100%, 97%); - --secondary-border: hsl(204, 100%, 94%); - --secondary-foreground: hsl(200, 98%, 39%); - --muted: hsl(0 0% 85.1%); - --muted-foreground: hsl(0 0% 50%); - --accent: hsl(0, 0%, 96%); - --accent-foreground: hsl(0 0% 2%); - --destructive: hsl(0, 83%, 50%); - --destructive-foreground: hsl(0, 0%, 100%); - --constructive: hsl(141, 71%, 48%); - --constructive-foreground: hsl(0, 0%, 100%); - --border: hsl(0 0% 91%); - --input: hsl(0, 0%, 100%); - --ring: hsl(0, 0%, 55%); - --chart-1: hsl(220 70% 50%); - --chart-2: hsl(160 60% 45%); - --chart-3: hsl(30 80% 55%); - --chart-4: hsl(280 65% 60%); - --chart-5: hsl(340 75% 55%); - --sidebar-background: hsl(0 0% 96.1%); - --sidebar-foreground: hsl(0 0% 2%); - --sidebar-primary: hsl(0 0% 2%); - --sidebar-primary-foreground: hsl(0 0% 91%); - --sidebar-accent: hsl(0 0% 85.1%); - --sidebar-accent-foreground: hsl(0 0% 2%); - --sidebar-border: hsl(0 0% 85.1%); - --sidebar-ring: hsl(0 0% 16.9%); - --sidebar: hsl(0 0% 98%); -} - -.panel { - --background: hsl(210, 20%, 98%); - --foreground: hsl(0 0% 13%); - --card: hsl(0, 0%, 98%); - --card-foreground: hsl(0 0% 13%); - --primary-foreground: hsl(0, 0%, 98%); - --secondary: hsl(204, 100%, 95%); - --secondary-border: hsl(204, 100%, 92%); - --secondary-foreground: hsl(200, 98%, 37%); - --muted: hsl(0 0% 83.1%); - --muted-foreground: hsl(0 0% 48%); - --accent: hsl(0, 0%, 93%); - --accent-foreground: hsl(0 0% 5%); - --destructive-foreground: hsl(0, 0%, 98%); - --constructive-foreground: hsl(0, 0%, 98%); - --border: hsl(0 0% 87%); - --input: hsl(0 0% 93%); - --ring: hsl(0, 0%, 53%); -} - -.dark { - --background: hsl(0, 0%, 5%); - --foreground: hsl(0 0% 87%); - --card: hsl(0, 0%, 5%); - --card-foreground: hsl(0 0% 87%); - --popover: hsl(0, 0%, 5%); - --popover-hover: hsl(0, 0%, 13%); - --popover-foreground: hsl(0 0% 95%); - --secondary: hsl(204, 100%, 12%); - --secondary-border: hsl(204, 100%, 15%); - --secondary-foreground: hsl(200, 98%, 61%); - --muted: hsl(0 0% 20%); - --accent: hsl(0, 0%, 14%); - --accent-foreground: hsl(0 0% 95%); - --border: hsl(0 0% 16%); - --input: hsl(0 0% 5%); - --ring: hsl(0, 0%, 50%); - --sidebar-background: hsl(0 0% 8%); - --sidebar-foreground: hsl(0 0% 95%); - --sidebar-primary: hsl(0 0% 95%); - --sidebar-primary-foreground: hsl(0 0% 15%); - --sidebar-accent: hsl(0 0% 20%); - --sidebar-accent-foreground: hsl(0 0% 95%); - --sidebar-border: hsl(0 0% 20%); - --sidebar-ring: hsl(0 0% 83.1%); - --sidebar: hsl(0 0% 6%); -} - -.dark .panel { - --background: hsl(0 0% 10%); - --foreground: hsl(0 0% 85%); - --card: hsl(0, 0%, 10%); - --card-foreground: hsl(0 0% 85%); - --secondary: hsl(204, 67%, 9%); - --secondary-border: hsl(204, 100%, 14%); - --secondary-foreground: hsl(200, 98%, 63%); - --muted: hsl(0 0% 22%); - --accent: hsl(0, 0%, 15%); - --accent-foreground: hsl(0 0% 93%); - --border: hsl(0 0% 18%); - --input: hsl(0 0% 22%); - --ring: hsl(0, 0%, 52%); -} - -@layer base { - /* - The default border color has changed to `currentcolor` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - /* Other default base styles */ - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - /* Prevent back/forward swipe */ - overscroll-behavior-x: contain; - } - ::selection { - @apply bg-primary/35 selection:text-primary-foreground; - } -} - -@theme inline { - /* Responsive breakpoints */ - --breakpoint-xs: 30rem; - - /* Typography */ - --font-sans: var(--font-inter), sans-serif; - - /* Font sizes */ - --text-xs: 0.72rem; - --text-sm: 0.79rem; - --text-base: 0.92rem; - --text-base--line-height: calc(1.5 / 0.95); - --text-xs--line-height: calc(1 / 0.8); - - /* Border radius */ - --radius-lg: 0.82rem; - --radius-md: 0.65rem; - --radius-sm: 0.35rem; - - /* Palette mapped to root design tokens */ - --color-background: var(--background); - --color-foreground: var(--foreground); - - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - - --color-popover: var(--popover); - --color-popover-hover: var(--popover-hover); - --color-popover-foreground: var(--popover-foreground); - - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-border: var(--secondary-border); - --color-secondary-foreground: var(--secondary-foreground); - - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - - --color-destructive: var(--destructive); - --color-destructive-foreground: var(--destructive-foreground); - - --color-constructive: var(--constructive); - --color-constructive-foreground: var(--constructive-foreground); - - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - - /* Chart colors */ - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); - - /* Sidebar */ - --color-sidebar: var(--sidebar-background); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); - - /* Animations */ - --animate-accordion-down: accordion-down 0.2s ease-out; - --animate-accordion-up: accordion-up 0.2s ease-out; - - @keyframes accordion-down { - from { - height: 0; - } - to { - height: var(--radix-accordion-content-height); - } - } - - @keyframes accordion-up { - from { - height: var(--radix-accordion-content-height); - } - to { - height: 0; - } - } -} - -@utility scrollbar-hidden { - -ms-overflow-style: none; - scrollbar-width: none; - &::-webkit-scrollbar { - display: none; - } -} - -@utility scrollbar-x-hidden { - -ms-overflow-style: none; - scrollbar-width: none; - &::-webkit-scrollbar:horizontal { - display: none; - } -} - -@utility scrollbar-y-hidden { - -ms-overflow-style: none; - scrollbar-width: none; - &::-webkit-scrollbar:vertical { - display: none; - } -} - -@utility scrollbar-thin { - &::-webkit-scrollbar { - width: 7px; - height: 7px; - } - &::-webkit-scrollbar-track { - background: transparent; - } - &::-webkit-scrollbar-thumb { - background: var(--border); - border-radius: 4px; - } - &::-webkit-scrollbar-thumb:hover { - background: var(--muted-foreground); - } -} - -@layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } -} +@import "tailwindcss"; + +/* Custom variant for dark mode */ +@custom-variant dark (&:where(.dark, .dark *)); + +/* Plugins */ +@plugin "@tailwindcss/typography"; +@plugin "tailwindcss-animate"; + +:root { + --background: hsl(0, 0%, 100%); + --foreground: hsl(0 0% 11%); + --card: hsl(0, 0%, 100%); + --card-foreground: hsl(0 0% 11%); + --popover: hsl(0, 0%, 100%); + --popover-hover: hsl(0, 0%, 96%); + --popover-foreground: hsl(0 0% 2%); + --primary: hsl(200, 90%, 52%); + --primary-foreground: hsl(0, 0%, 100%); + --secondary: hsl(204, 100%, 97%); + --secondary-border: hsl(204, 100%, 94%); + --secondary-foreground: hsl(200, 98%, 39%); + --muted: hsl(0 0% 85.1%); + --muted-foreground: hsl(0 0% 50%); + --accent: hsl(0, 0%, 96%); + --accent-foreground: hsl(0 0% 2%); + --destructive: hsl(0, 83%, 50%); + --destructive-foreground: hsl(0, 0%, 100%); + --constructive: hsl(141, 71%, 48%); + --constructive-foreground: hsl(0, 0%, 100%); + --border: hsl(0 0% 91%); + --input: hsl(0, 0%, 100%); + --ring: hsl(0, 0%, 55%); + --chart-1: hsl(220 70% 50%); + --chart-2: hsl(160 60% 45%); + --chart-3: hsl(30 80% 55%); + --chart-4: hsl(280 65% 60%); + --chart-5: hsl(340 75% 55%); + --sidebar-background: hsl(0 0% 96.1%); + --sidebar-foreground: hsl(0 0% 2%); + --sidebar-primary: hsl(0 0% 2%); + --sidebar-primary-foreground: hsl(0 0% 91%); + --sidebar-accent: hsl(0 0% 85.1%); + --sidebar-accent-foreground: hsl(0 0% 2%); + --sidebar-border: hsl(0 0% 85.1%); + --sidebar-ring: hsl(0 0% 16.9%); + --sidebar: hsl(0 0% 98%); +} + +.panel { + --background: hsl(210, 20%, 98%); + --foreground: hsl(0 0% 13%); + --card: hsl(0, 0%, 98%); + --card-foreground: hsl(0 0% 13%); + --primary-foreground: hsl(0, 0%, 98%); + --secondary: hsl(204, 100%, 95%); + --secondary-border: hsl(204, 100%, 92%); + --secondary-foreground: hsl(200, 98%, 37%); + --muted: hsl(0 0% 83.1%); + --muted-foreground: hsl(0 0% 48%); + --accent: hsl(0, 0%, 93%); + --accent-foreground: hsl(0 0% 5%); + --destructive-foreground: hsl(0, 0%, 98%); + --constructive-foreground: hsl(0, 0%, 98%); + --border: hsl(0 0% 87%); + --input: hsl(0 0% 93%); + --ring: hsl(0, 0%, 53%); +} + +.dark { + --background: hsl(0, 0%, 5%); + --foreground: hsl(0 0% 87%); + --card: hsl(0, 0%, 5%); + --card-foreground: hsl(0 0% 87%); + --popover: hsl(0, 0%, 5%); + --popover-hover: hsl(0, 0%, 13%); + --popover-foreground: hsl(0 0% 95%); + --secondary: hsl(204, 100%, 12%); + --secondary-border: hsl(204, 100%, 15%); + --secondary-foreground: hsl(200, 98%, 61%); + --muted: hsl(0 0% 20%); + --accent: hsl(0, 0%, 14%); + --accent-foreground: hsl(0 0% 95%); + --border: hsl(0 0% 16%); + --input: hsl(0 0% 5%); + --ring: hsl(0, 0%, 50%); + --sidebar-background: hsl(0 0% 8%); + --sidebar-foreground: hsl(0 0% 95%); + --sidebar-primary: hsl(0 0% 95%); + --sidebar-primary-foreground: hsl(0 0% 15%); + --sidebar-accent: hsl(0 0% 20%); + --sidebar-accent-foreground: hsl(0 0% 95%); + --sidebar-border: hsl(0 0% 20%); + --sidebar-ring: hsl(0 0% 83.1%); + --sidebar: hsl(0 0% 6%); +} + +.dark .panel { + --background: hsl(0 0% 10%); + --foreground: hsl(0 0% 85%); + --card: hsl(0, 0%, 10%); + --card-foreground: hsl(0 0% 85%); + --secondary: hsl(204, 67%, 9%); + --secondary-border: hsl(204, 100%, 14%); + --secondary-foreground: hsl(200, 98%, 63%); + --muted: hsl(0 0% 22%); + --accent: hsl(0, 0%, 15%); + --accent-foreground: hsl(0 0% 93%); + --border: hsl(0 0% 18%); + --input: hsl(0 0% 22%); + --ring: hsl(0, 0%, 52%); +} + +@layer base { + /* + The default border color has changed to `currentcolor` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. + */ + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentcolor); + } + /* Other default base styles */ + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + /* Prevent back/forward swipe */ + overscroll-behavior-x: contain; + } + ::selection { + @apply bg-primary/35 selection:text-primary-foreground; + } +} + +@theme inline { + /* Responsive breakpoints */ + --breakpoint-xs: 30rem; + + /* Typography */ + --font-sans: var(--font-inter), sans-serif; + + /* Font sizes */ + --text-xs: 0.72rem; + --text-sm: 0.79rem; + --text-base: 0.92rem; + --text-base--line-height: calc(1.5 / 0.95); + --text-xs--line-height: calc(1 / 0.8); + + /* Border radius */ + --radius-lg: 0.82rem; + --radius-md: 0.65rem; + --radius-sm: 0.35rem; + + /* Palette mapped to root design tokens */ + --color-background: var(--background); + --color-foreground: var(--foreground); + + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + + --color-popover: var(--popover); + --color-popover-hover: var(--popover-hover); + --color-popover-foreground: var(--popover-foreground); + + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-border: var(--secondary-border); + --color-secondary-foreground: var(--secondary-foreground); + + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + + --color-constructive: var(--constructive); + --color-constructive-foreground: var(--constructive-foreground); + + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + + /* Chart colors */ + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + + /* Sidebar */ + --color-sidebar: var(--sidebar-background); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + + /* Animations */ + --animate-accordion-down: accordion-down 0.2s ease-out; + --animate-accordion-up: accordion-up 0.2s ease-out; + + @keyframes accordion-down { + from { + height: 0; + } + to { + height: var(--radix-accordion-content-height); + } + } + + @keyframes accordion-up { + from { + height: var(--radix-accordion-content-height); + } + to { + height: 0; + } + } +} + +@utility scrollbar-hidden { + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } +} + +@utility scrollbar-x-hidden { + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar:horizontal { + display: none; + } +} + +@utility scrollbar-y-hidden { + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar:vertical { + display: none; + } +} + +@utility scrollbar-thin { + &::-webkit-scrollbar { + width: 7px; + height: 7px; + } + &::-webkit-scrollbar-track { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 4px; + } + &::-webkit-scrollbar-thumb:hover { + background: var(--muted-foreground); + } +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/apps/web/src/app/metadata.ts b/apps/web/src/app/metadata.ts index 7c192d2b..72f0c2aa 100644 --- a/apps/web/src/app/metadata.ts +++ b/apps/web/src/app/metadata.ts @@ -1,86 +1,86 @@ -import type { Metadata } from "next"; -import { SITE_INFO, SITE_URL } from "@/constants/site-constants"; - -export const baseMetaData: Metadata = { - metadataBase: new URL(SITE_URL), - title: SITE_INFO.title, - description: SITE_INFO.description, - openGraph: { - title: SITE_INFO.title, - description: SITE_INFO.description, - url: SITE_URL, - siteName: SITE_INFO.title, - locale: "en_US", - type: "website", - images: [ - { - url: SITE_INFO.openGraphImage, - width: 1200, - height: 630, - alt: "OpenCut Wordmark", - }, - ], - }, - twitter: { - card: "summary_large_image", - title: SITE_INFO.title, - description: SITE_INFO.description, - creator: "@opencutapp", - images: [SITE_INFO.twitterImage], - }, - pinterest: { - richPin: false, - }, - robots: { - index: true, - follow: true, - }, - icons: { - icon: [ - { url: "/favicon.ico" }, - { url: "/icons/favicon-16x16.png", sizes: "16x16", type: "image/png" }, - { url: "/icons/favicon-32x32.png", sizes: "32x32", type: "image/png" }, - { url: "/icons/favicon-96x96.png", sizes: "96x96", type: "image/png" }, - ], - apple: [ - { url: "/icons/apple-icon-57x57.png", sizes: "57x57", type: "image/png" }, - { url: "/icons/apple-icon-60x60.png", sizes: "60x60", type: "image/png" }, - { url: "/icons/apple-icon-72x72.png", sizes: "72x72", type: "image/png" }, - { url: "/icons/apple-icon-76x76.png", sizes: "76x76", type: "image/png" }, - { - url: "/icons/apple-icon-114x114.png", - sizes: "114x114", - type: "image/png", - }, - { - url: "/icons/apple-icon-120x120.png", - sizes: "120x120", - type: "image/png", - }, - { - url: "/icons/apple-icon-144x144.png", - sizes: "144x144", - type: "image/png", - }, - { - url: "/icons/apple-icon-152x152.png", - sizes: "152x152", - type: "image/png", - }, - { - url: "/icons/apple-icon-180x180.png", - sizes: "180x180", - type: "image/png", - }, - ], - shortcut: ["/favicon.ico"], - }, - appleWebApp: { - capable: true, - title: SITE_INFO.title, - }, - manifest: "/manifest.json", - other: { - "msapplication-config": "/browserconfig.xml", - }, -}; +import type { Metadata } from "next"; +import { SITE_INFO, SITE_URL } from "@/constants/site-constants"; + +export const baseMetaData: Metadata = { + metadataBase: new URL(SITE_URL), + title: SITE_INFO.title, + description: SITE_INFO.description, + openGraph: { + title: SITE_INFO.title, + description: SITE_INFO.description, + url: SITE_URL, + siteName: SITE_INFO.title, + locale: "en_US", + type: "website", + images: [ + { + url: SITE_INFO.openGraphImage, + width: 1200, + height: 630, + alt: "OpenCut Wordmark", + }, + ], + }, + twitter: { + card: "summary_large_image", + title: SITE_INFO.title, + description: SITE_INFO.description, + creator: "@opencutapp", + images: [SITE_INFO.twitterImage], + }, + pinterest: { + richPin: false, + }, + robots: { + index: true, + follow: true, + }, + icons: { + icon: [ + { url: "/favicon.ico" }, + { url: "/icons/favicon-16x16.png", sizes: "16x16", type: "image/png" }, + { url: "/icons/favicon-32x32.png", sizes: "32x32", type: "image/png" }, + { url: "/icons/favicon-96x96.png", sizes: "96x96", type: "image/png" }, + ], + apple: [ + { url: "/icons/apple-icon-57x57.png", sizes: "57x57", type: "image/png" }, + { url: "/icons/apple-icon-60x60.png", sizes: "60x60", type: "image/png" }, + { url: "/icons/apple-icon-72x72.png", sizes: "72x72", type: "image/png" }, + { url: "/icons/apple-icon-76x76.png", sizes: "76x76", type: "image/png" }, + { + url: "/icons/apple-icon-114x114.png", + sizes: "114x114", + type: "image/png", + }, + { + url: "/icons/apple-icon-120x120.png", + sizes: "120x120", + type: "image/png", + }, + { + url: "/icons/apple-icon-144x144.png", + sizes: "144x144", + type: "image/png", + }, + { + url: "/icons/apple-icon-152x152.png", + sizes: "152x152", + type: "image/png", + }, + { + url: "/icons/apple-icon-180x180.png", + sizes: "180x180", + type: "image/png", + }, + ], + shortcut: ["/favicon.ico"], + }, + appleWebApp: { + capable: true, + title: SITE_INFO.title, + }, + manifest: "/manifest.json", + other: { + "msapplication-config": "/browserconfig.xml", + }, +}; diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index d97c9a54..43dbc104 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -1,21 +1,21 @@ -import { Hero } from "@/components/landing/hero"; -import { Header } from "@/components/header"; -import { Footer } from "@/components/footer"; -import type { Metadata } from "next"; -import { SITE_URL } from "@/constants/site-constants"; - -export const metadata: Metadata = { - alternates: { - canonical: SITE_URL, - }, -}; - -export default async function Home() { - return ( -
-
- -
-
- ); -} +import { Hero } from "@/components/landing/hero"; +import { Header } from "@/components/header"; +import { Footer } from "@/components/footer"; +import type { Metadata } from "next"; +import { SITE_URL } from "@/constants/site-constants"; + +export const metadata: Metadata = { + alternates: { + canonical: SITE_URL, + }, +}; + +export default async function Home() { + return ( +
+
+ +
+
+ ); +} diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx index f32ce2b9..8c5d9d04 100644 --- a/apps/web/src/app/projects/page.tsx +++ b/apps/web/src/app/projects/page.tsx @@ -1,1015 +1,1015 @@ -"use client"; - -import Image from "next/image"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import type { KeyboardEvent, MouseEvent } from "react"; -import { useEffect, useState } from "react"; -import { toast } from "sonner"; -import type { EditorCore } from "@/core"; -import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog"; -import { StoragePersistenceDialog } from "@/components/editor/dialogs/storage-persistence-dialog"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { Checkbox } from "@/components/ui/checkbox"; -import { Input } from "@/components/ui/input"; -import { Skeleton } from "@/components/ui/skeleton"; -import { useEditor } from "@/hooks/use-editor"; -import { useProjectsStore } from "./store"; -import type { - TProjectMetadata, - TProjectSortKey, - TProjectSortOption, -} from "@/lib/project/types"; -import { formatTimeCode } from "@/lib/time"; -import { formatDate } from "@/utils/date"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; -import { - Calendar04Icon, - GridViewIcon, - LeftToRightListDashIcon, - PlusSignIcon, - Search01Icon, - Video01Icon, - MoreHorizontalIcon, - Delete02Icon, - Copy01Icon, - Edit03Icon, - ArrowDown02Icon, - InformationCircleIcon, -} from "@hugeicons/core-free-icons"; -import { OcVideoIcon } from "@opencut/ui/icons"; -import { Label } from "@/components/ui/label"; -import { - ContextMenu, - ContextMenuContent, - ContextMenuItem, - ContextMenuSeparator, - ContextMenuTrigger, -} from "@/components/ui/context-menu"; -import { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { DeleteProjectDialog } from "@/components/editor/dialogs/delete-project-dialog"; -import { ProjectInfoDialog } from "@/components/editor/dialogs/project-info-dialog"; -import { RenameProjectDialog } from "@/components/editor/dialogs/rename-project-dialog"; -import { cn } from "@/utils/ui"; - -const formatProjectDuration = ({ - duration, -}: { - duration: number | undefined; -}): string | null => { - if (duration === undefined) { - return null; - } - - const format = duration >= 3600 ? "HH:MM:SS" : "MM:SS"; - return formatTimeCode({ timeInSeconds: duration, format }); -}; - -const VIEW_MODE_OPTIONS = [ - { mode: "grid" as const, icon: GridViewIcon, label: "Grid view" }, - { mode: "list" as const, icon: LeftToRightListDashIcon, label: "List view" }, -]; - -export default function ProjectsPage() { - const { searchQuery, sortKey, sortOrder, viewMode } = useProjectsStore(); - const editor = useEditor(); - const sortOption: TProjectSortOption = `${sortKey}-${sortOrder}`; - - const isLoading = useEditor((e) => e.project.getIsLoading()); - const isInitialized = useEditor((e) => e.project.getIsInitialized()); - const projectsToDisplay = useEditor((e) => - e.project.getFilteredAndSortedProjects({ searchQuery, sortOption }), - ); - - useEffect(() => { - if (!editor.project.getIsInitialized()) { - editor.project.loadAllProjects(); - } - }, [editor.project]); - - return ( -
- - - - p.id)} /> -
- {isLoading || !isInitialized ? ( - - ) : projectsToDisplay.length === 0 ? ( - - ) : ( -
- {projectsToDisplay.map((project) => ( - p.id)} - /> - ))} -
- )} -
-
- ); -} - -function ProjectsHeader() { - const { viewMode, isHydrated, setViewMode } = useProjectsStore(); - - return ( -
-
-
- - - - - - Home - - - - - - - All projects - - - - - -
- {VIEW_MODE_OPTIONS.map(({ mode, icon, label }) => ( - - ))} -
-
- -
- - -
-
- -
- ); -} - -const SORT_LABELS: Record = { - createdAt: "Created", - updatedAt: "Modified", - name: "Name", - duration: "Duration", -}; - -function ProjectsToolbar({ projectIds }: { projectIds: string[] }) { - const { - selectedProjectIds, - sortKey, - sortOrder, - setSortOrder, - setSelectedProjects, - clearSelectedProjects, - viewMode, - setViewMode, - } = useProjectsStore(); - - const selectedProjectCount = selectedProjectIds.length; - const isAllSelected = - projectIds.length > 0 && selectedProjectCount === projectIds.length; - const hasSomeSelected = - selectedProjectCount > 0 && selectedProjectCount < projectIds.length; - - const handleSelectAll = ({ checked }: { checked: boolean }) => { - if (checked) { - setSelectedProjects({ projectIds }); - return; - } - clearSelectedProjects(); - }; - - return ( -
-
- - -
- - - - - - -
- -
- {VIEW_MODE_OPTIONS.map(({ mode, icon, label }) => ( - - ))} -
-
- {selectedProjectCount > 0 ? : null} -
- ); -} - -function SearchBar({ - className, - collapsed, -}: { - className?: string; - collapsed?: boolean; -}) { - const { searchQuery, setSearchQuery } = useProjectsStore(); - - return ( - <> - {collapsed ? ( -
- -
- ) : ( -
-
- )} - - ); -} - -const PROJECT_ACTIONS = [ - { - id: "duplicate", - label: "Duplicate", - icon: Copy01Icon, - variant: "outline" as const, - }, - { - id: "delete", - label: "Delete", - icon: Delete02Icon, - variant: "destructive-foreground" as const, - }, -] as const; - -async function deleteProjects({ - editor, - ids, -}: { - editor: EditorCore; - ids: string[]; -}) { - await editor.project.deleteProjects({ ids }); -} - -async function duplicateProjects({ - editor, - ids, -}: { - editor: EditorCore; - ids: string[]; -}) { - await editor.project.duplicateProjects({ ids }); -} - -async function renameProject({ - editor, - id, - name, -}: { - editor: EditorCore; - id: string; - name: string; -}) { - await editor.project.renameProject({ id, name }); -} - -function ProjectActions() { - const editor = useEditor(); - const { selectedProjectIds, clearSelectedProjects } = useProjectsStore(); - const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); - - const savedProjects = editor.project.getSavedProjects(); - const selectedProjectNames = savedProjects - .filter((project) => selectedProjectIds.includes(project.id)) - .map((project) => project.name); - - const handleDuplicate = async () => { - await duplicateProjects({ editor, ids: selectedProjectIds }); - clearSelectedProjects(); - }; - - const handleDeleteClick = () => { - setIsDeleteDialogOpen(true); - }; - - const handleDeleteConfirm = async () => { - await deleteProjects({ editor, ids: selectedProjectIds }); - clearSelectedProjects(); - setIsDeleteDialogOpen(false); - }; - - const actionHandlers: Record void> = { - duplicate: handleDuplicate, - delete: handleDeleteClick, - }; - - return ( - <> -
-
- {PROJECT_ACTIONS.map((action) => ( - - ))} -
- - - - - - - {PROJECT_ACTIONS.map((action) => ( - - - {action.label} - - ))} - - -
- - - - ); -} - -function SortDropdown({ children }: { children: React.ReactNode }) { - const { sortKey, setSortKey } = useProjectsStore(); - - return ( - - {children} - - setSortKey({ sortKey: "createdAt" })} - > - Created - - setSortKey({ sortKey: "updatedAt" })} - > - Modified - - setSortKey({ sortKey: "name" })} - > - Name - - setSortKey({ sortKey: "duration" })} - > - Duration - - - - ); -} - -function NewProjectButton() { - const editor = useEditor(); - const router = useRouter(); - - const handleCreateProject = async () => { - const projectId = await editor.project.createNewProject({ - name: "New project", - }); - router.push(`/editor/${projectId}`); - }; - - return ( - - ); -} - -function ProjectItem({ - project, - allProjectIds, -}: { - project: TProjectMetadata; - allProjectIds: string[]; -}) { - const { - selectedProjectIds, - viewMode, - setProjectSelected, - selectProjectRange, - } = useProjectsStore(); - const selectedProjectIdSet = new Set(selectedProjectIds); - const isSelected = selectedProjectIdSet.has(project.id); - const selectedProjectCount = selectedProjectIds.length; - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false); - const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); - const [isInfoDialogOpen, setIsInfoDialogOpen] = useState(false); - const editor = useEditor(); - const durationLabel = formatProjectDuration({ duration: project.duration }); - const isMultiSelect = selectedProjectCount > 1; - const isGridView = viewMode === "grid"; - - const handleRename = () => setIsRenameDialogOpen(true); - const handleDuplicate = async () => { - await duplicateProjects({ editor, ids: [project.id] }); - }; - const handleDeleteClick = () => setIsDeleteDialogOpen(true); - const handleInfoClick = () => setIsInfoDialogOpen(true); - const handleDeleteConfirm = async () => { - await deleteProjects({ editor, ids: [project.id] }); - setIsDeleteDialogOpen(false); - }; - - const handleCheckboxChange = ({ - checked, - shiftKey, - }: { - checked: boolean; - shiftKey: boolean; - }) => { - if (shiftKey && checked) { - selectProjectRange({ projectId: project.id, allProjectIds }); - return; - } - setProjectSelected({ projectId: project.id, isSelected: checked }); - }; - - const gridContent = ( - -
-
- {project.thumbnail ? ( - Project thumbnail - ) : ( -
- -
- )} -
- - {durationLabel && ( -
- {durationLabel} -
- )} -
- - -

- {project.name} -

-
- - Created {formatDate({ date: project.createdAt })} -
-
-
- ); - - const listRowContent = ( -
-
- {project.thumbnail ? ( - Project thumbnail - ) : ( -
- -
- )} -
- -

- {project.name} -

- - - {durationLabel ?? "—"} - - - - {formatDate({ date: project.createdAt })} - -
- ); - - const listContent = ( -
- event.preventDefault()} - onClick={(event) => { - handleCheckboxChange({ - checked: !isSelected, - shiftKey: event.shiftKey, - }); - }} - onCheckedChange={() => {}} - className="size-5 shrink-0" - /> - - - {listRowContent} - - - {!isMultiSelect && ( - - )} -
- ); - - return ( - <> - - -
- {isGridView ? ( - <> - - {gridContent} - - - event.preventDefault()} - onClick={(event) => { - handleCheckboxChange({ - checked: !isSelected, - shiftKey: event.shiftKey, - }); - }} - onCheckedChange={() => {}} - className={`absolute z-10 size-5 top-3 left-3 ${ - isSelected || isDropdownOpen - ? "opacity-100" - : "opacity-0 group-hover:opacity-100" - }`} - /> - - {!isMultiSelect && ( - - )} - - ) : ( - listContent - )} -
-
- -
- - { - await renameProject({ editor, id: project.id, name: newName }); - setIsRenameDialogOpen(false); - }} - /> - - - - - - ); -} - -function ProjectContextMenuContent({ - onRenameClick, - onDuplicateClick, - onDeleteClick, - onInfoClick, -}: { - onRenameClick: () => void; - onDuplicateClick: () => void; - onDeleteClick: () => void; - onInfoClick: () => void; -}) { - return ( - - } - onClick={onRenameClick} - > - Rename - - } - onClick={onDuplicateClick} - > - Duplicate - - } - onClick={onInfoClick} - > - Info - - - } - onClick={onDeleteClick} - > - Delete - - - ); -} - -function ProjectMenu({ - isOpen, - onOpenChange, - variant = "grid", - onRenameClick, - onDuplicateClick, - onDeleteClick, - onInfoClick, -}: { - isOpen: boolean; - onOpenChange: (open: boolean) => void; - variant?: "grid" | "list"; - onRenameClick: () => void; - onDuplicateClick: () => void; - onDeleteClick: () => void; - onInfoClick: () => void; -}) { - const handleMenuClick = ({ - event, - }: { - event: MouseEvent; - }) => { - event.preventDefault(); - event.stopPropagation(); - }; - - const handleMenuKeyDown = ({ - event, - }: { - event: KeyboardEvent; - }) => { - if (event.key !== "Enter" && event.key !== " ") { - return; - } - event.preventDefault(); - event.stopPropagation(); - }; - - const handleRename = () => { - onRenameClick(); - onOpenChange(false); - }; - - const handleDuplicate = () => { - onDuplicateClick(); - onOpenChange(false); - }; - - const handleDeleteClick = () => { - onDeleteClick(); - onOpenChange(false); - }; - - const handleInfoClick = () => { - onInfoClick(); - onOpenChange(false); - }; - - const isGrid = variant === "grid"; - - return ( - - - - - - - - Rename - - - - Duplicate - - - - Info - - - - Delete - - - - ); -} - -function ProjectsSkeleton() { - const skeletonIds = Array.from( - { length: 24 }, - (_, index) => `skeleton-${index}`, - ); - - return ( -
- {skeletonIds.map((skeletonId) => ( - -
-
- -
-
- - -
- - -
-
-
- ))} -
- ); -} - -function EmptyState() { - const { searchQuery, setSearchQuery } = useProjectsStore(); - const router = useRouter(); - const editor = useEditor(); - const savedProjects = editor.project.getSavedProjects(); - - const handleCreateProject = async () => { - try { - const projectId = await editor.project.createNewProject({ - name: "New project", - }); - router.push(`/editor/${projectId}`); - } catch (error) { - toast.error("Failed to create project", { - description: - error instanceof Error ? error.message : "Please try again", - }); - } - }; - - if (savedProjects.length > 0) { - return ( -
-
- -
-

No results found

-

- Your search for "{searchQuery}" did not return any results. -

-
-
- -
- ); - } - - return ( -
-
-
- -
-

No projects yet

-

- Start creating your first project. Import media, edit, and export your - videos. All privately. -

-
- -
- ); -} +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import type { KeyboardEvent, MouseEvent } from "react"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import type { EditorCore } from "@/core"; +import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog"; +import { StoragePersistenceDialog } from "@/components/editor/dialogs/storage-persistence-dialog"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { Skeleton } from "@/components/ui/skeleton"; +import { useEditor } from "@/hooks/use-editor"; +import { useProjectsStore } from "./store"; +import type { + TProjectMetadata, + TProjectSortKey, + TProjectSortOption, +} from "@/lib/project/types"; +import { formatTimeCode } from "@/lib/time"; +import { formatDate } from "@/utils/date"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; +import { + Calendar04Icon, + GridViewIcon, + LeftToRightListDashIcon, + PlusSignIcon, + Search01Icon, + Video01Icon, + MoreHorizontalIcon, + Delete02Icon, + Copy01Icon, + Edit03Icon, + ArrowDown02Icon, + InformationCircleIcon, +} from "@hugeicons/core-free-icons"; +import { OcVideoIcon } from "@opencut/ui/icons"; +import { Label } from "@/components/ui/label"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, + ContextMenuTrigger, +} from "@/components/ui/context-menu"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { DeleteProjectDialog } from "@/components/editor/dialogs/delete-project-dialog"; +import { ProjectInfoDialog } from "@/components/editor/dialogs/project-info-dialog"; +import { RenameProjectDialog } from "@/components/editor/dialogs/rename-project-dialog"; +import { cn } from "@/utils/ui"; + +const formatProjectDuration = ({ + duration, +}: { + duration: number | undefined; +}): string | null => { + if (duration === undefined) { + return null; + } + + const format = duration >= 3600 ? "HH:MM:SS" : "MM:SS"; + return formatTimeCode({ timeInSeconds: duration, format }); +}; + +const VIEW_MODE_OPTIONS = [ + { mode: "grid" as const, icon: GridViewIcon, label: "Grid view" }, + { mode: "list" as const, icon: LeftToRightListDashIcon, label: "List view" }, +]; + +export default function ProjectsPage() { + const { searchQuery, sortKey, sortOrder, viewMode } = useProjectsStore(); + const editor = useEditor(); + const sortOption: TProjectSortOption = `${sortKey}-${sortOrder}`; + + const isLoading = useEditor((e) => e.project.getIsLoading()); + const isInitialized = useEditor((e) => e.project.getIsInitialized()); + const projectsToDisplay = useEditor((e) => + e.project.getFilteredAndSortedProjects({ searchQuery, sortOption }), + ); + + useEffect(() => { + if (!editor.project.getIsInitialized()) { + editor.project.loadAllProjects(); + } + }, [editor.project]); + + return ( +
+ + + + p.id)} /> +
+ {isLoading || !isInitialized ? ( + + ) : projectsToDisplay.length === 0 ? ( + + ) : ( +
+ {projectsToDisplay.map((project) => ( + p.id)} + /> + ))} +
+ )} +
+
+ ); +} + +function ProjectsHeader() { + const { viewMode, isHydrated, setViewMode } = useProjectsStore(); + + return ( +
+
+
+ + + + + + Home + + + + + + + All projects + + + + + +
+ {VIEW_MODE_OPTIONS.map(({ mode, icon, label }) => ( + + ))} +
+
+ +
+ + +
+
+ +
+ ); +} + +const SORT_LABELS: Record = { + createdAt: "Created", + updatedAt: "Modified", + name: "Name", + duration: "Duration", +}; + +function ProjectsToolbar({ projectIds }: { projectIds: string[] }) { + const { + selectedProjectIds, + sortKey, + sortOrder, + setSortOrder, + setSelectedProjects, + clearSelectedProjects, + viewMode, + setViewMode, + } = useProjectsStore(); + + const selectedProjectCount = selectedProjectIds.length; + const isAllSelected = + projectIds.length > 0 && selectedProjectCount === projectIds.length; + const hasSomeSelected = + selectedProjectCount > 0 && selectedProjectCount < projectIds.length; + + const handleSelectAll = ({ checked }: { checked: boolean }) => { + if (checked) { + setSelectedProjects({ projectIds }); + return; + } + clearSelectedProjects(); + }; + + return ( +
+
+ + +
+ + + + + + +
+ +
+ {VIEW_MODE_OPTIONS.map(({ mode, icon, label }) => ( + + ))} +
+
+ {selectedProjectCount > 0 ? : null} +
+ ); +} + +function SearchBar({ + className, + collapsed, +}: { + className?: string; + collapsed?: boolean; +}) { + const { searchQuery, setSearchQuery } = useProjectsStore(); + + return ( + <> + {collapsed ? ( +
+ +
+ ) : ( +
+
+ )} + + ); +} + +const PROJECT_ACTIONS = [ + { + id: "duplicate", + label: "Duplicate", + icon: Copy01Icon, + variant: "outline" as const, + }, + { + id: "delete", + label: "Delete", + icon: Delete02Icon, + variant: "destructive-foreground" as const, + }, +] as const; + +async function deleteProjects({ + editor, + ids, +}: { + editor: EditorCore; + ids: string[]; +}) { + await editor.project.deleteProjects({ ids }); +} + +async function duplicateProjects({ + editor, + ids, +}: { + editor: EditorCore; + ids: string[]; +}) { + await editor.project.duplicateProjects({ ids }); +} + +async function renameProject({ + editor, + id, + name, +}: { + editor: EditorCore; + id: string; + name: string; +}) { + await editor.project.renameProject({ id, name }); +} + +function ProjectActions() { + const editor = useEditor(); + const { selectedProjectIds, clearSelectedProjects } = useProjectsStore(); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + + const savedProjects = editor.project.getSavedProjects(); + const selectedProjectNames = savedProjects + .filter((project) => selectedProjectIds.includes(project.id)) + .map((project) => project.name); + + const handleDuplicate = async () => { + await duplicateProjects({ editor, ids: selectedProjectIds }); + clearSelectedProjects(); + }; + + const handleDeleteClick = () => { + setIsDeleteDialogOpen(true); + }; + + const handleDeleteConfirm = async () => { + await deleteProjects({ editor, ids: selectedProjectIds }); + clearSelectedProjects(); + setIsDeleteDialogOpen(false); + }; + + const actionHandlers: Record void> = { + duplicate: handleDuplicate, + delete: handleDeleteClick, + }; + + return ( + <> +
+
+ {PROJECT_ACTIONS.map((action) => ( + + ))} +
+ + + + + + + {PROJECT_ACTIONS.map((action) => ( + + + {action.label} + + ))} + + +
+ + + + ); +} + +function SortDropdown({ children }: { children: React.ReactNode }) { + const { sortKey, setSortKey } = useProjectsStore(); + + return ( + + {children} + + setSortKey({ sortKey: "createdAt" })} + > + Created + + setSortKey({ sortKey: "updatedAt" })} + > + Modified + + setSortKey({ sortKey: "name" })} + > + Name + + setSortKey({ sortKey: "duration" })} + > + Duration + + + + ); +} + +function NewProjectButton() { + const editor = useEditor(); + const router = useRouter(); + + const handleCreateProject = async () => { + const projectId = await editor.project.createNewProject({ + name: "New project", + }); + router.push(`/editor/${projectId}`); + }; + + return ( + + ); +} + +function ProjectItem({ + project, + allProjectIds, +}: { + project: TProjectMetadata; + allProjectIds: string[]; +}) { + const { + selectedProjectIds, + viewMode, + setProjectSelected, + selectProjectRange, + } = useProjectsStore(); + const selectedProjectIdSet = new Set(selectedProjectIds); + const isSelected = selectedProjectIdSet.has(project.id); + const selectedProjectCount = selectedProjectIds.length; + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [isInfoDialogOpen, setIsInfoDialogOpen] = useState(false); + const editor = useEditor(); + const durationLabel = formatProjectDuration({ duration: project.duration }); + const isMultiSelect = selectedProjectCount > 1; + const isGridView = viewMode === "grid"; + + const handleRename = () => setIsRenameDialogOpen(true); + const handleDuplicate = async () => { + await duplicateProjects({ editor, ids: [project.id] }); + }; + const handleDeleteClick = () => setIsDeleteDialogOpen(true); + const handleInfoClick = () => setIsInfoDialogOpen(true); + const handleDeleteConfirm = async () => { + await deleteProjects({ editor, ids: [project.id] }); + setIsDeleteDialogOpen(false); + }; + + const handleCheckboxChange = ({ + checked, + shiftKey, + }: { + checked: boolean; + shiftKey: boolean; + }) => { + if (shiftKey && checked) { + selectProjectRange({ projectId: project.id, allProjectIds }); + return; + } + setProjectSelected({ projectId: project.id, isSelected: checked }); + }; + + const gridContent = ( + +
+
+ {project.thumbnail ? ( + Project thumbnail + ) : ( +
+ +
+ )} +
+ + {durationLabel && ( +
+ {durationLabel} +
+ )} +
+ + +

+ {project.name} +

+
+ + Created {formatDate({ date: project.createdAt })} +
+
+
+ ); + + const listRowContent = ( +
+
+ {project.thumbnail ? ( + Project thumbnail + ) : ( +
+ +
+ )} +
+ +

+ {project.name} +

+ + + {durationLabel ?? "—"} + + + + {formatDate({ date: project.createdAt })} + +
+ ); + + const listContent = ( +
+ event.preventDefault()} + onClick={(event) => { + handleCheckboxChange({ + checked: !isSelected, + shiftKey: event.shiftKey, + }); + }} + onCheckedChange={() => {}} + className="size-5 shrink-0" + /> + + + {listRowContent} + + + {!isMultiSelect && ( + + )} +
+ ); + + return ( + <> + + +
+ {isGridView ? ( + <> + + {gridContent} + + + event.preventDefault()} + onClick={(event) => { + handleCheckboxChange({ + checked: !isSelected, + shiftKey: event.shiftKey, + }); + }} + onCheckedChange={() => {}} + className={`absolute z-10 size-5 top-3 left-3 ${ + isSelected || isDropdownOpen + ? "opacity-100" + : "opacity-0 group-hover:opacity-100" + }`} + /> + + {!isMultiSelect && ( + + )} + + ) : ( + listContent + )} +
+
+ +
+ + { + await renameProject({ editor, id: project.id, name: newName }); + setIsRenameDialogOpen(false); + }} + /> + + + + + + ); +} + +function ProjectContextMenuContent({ + onRenameClick, + onDuplicateClick, + onDeleteClick, + onInfoClick, +}: { + onRenameClick: () => void; + onDuplicateClick: () => void; + onDeleteClick: () => void; + onInfoClick: () => void; +}) { + return ( + + } + onClick={onRenameClick} + > + Rename + + } + onClick={onDuplicateClick} + > + Duplicate + + } + onClick={onInfoClick} + > + Info + + + } + onClick={onDeleteClick} + > + Delete + + + ); +} + +function ProjectMenu({ + isOpen, + onOpenChange, + variant = "grid", + onRenameClick, + onDuplicateClick, + onDeleteClick, + onInfoClick, +}: { + isOpen: boolean; + onOpenChange: (open: boolean) => void; + variant?: "grid" | "list"; + onRenameClick: () => void; + onDuplicateClick: () => void; + onDeleteClick: () => void; + onInfoClick: () => void; +}) { + const handleMenuClick = ({ + event, + }: { + event: MouseEvent; + }) => { + event.preventDefault(); + event.stopPropagation(); + }; + + const handleMenuKeyDown = ({ + event, + }: { + event: KeyboardEvent; + }) => { + if (event.key !== "Enter" && event.key !== " ") { + return; + } + event.preventDefault(); + event.stopPropagation(); + }; + + const handleRename = () => { + onRenameClick(); + onOpenChange(false); + }; + + const handleDuplicate = () => { + onDuplicateClick(); + onOpenChange(false); + }; + + const handleDeleteClick = () => { + onDeleteClick(); + onOpenChange(false); + }; + + const handleInfoClick = () => { + onInfoClick(); + onOpenChange(false); + }; + + const isGrid = variant === "grid"; + + return ( + + + + + + + + Rename + + + + Duplicate + + + + Info + + + + Delete + + + + ); +} + +function ProjectsSkeleton() { + const skeletonIds = Array.from( + { length: 24 }, + (_, index) => `skeleton-${index}`, + ); + + return ( +
+ {skeletonIds.map((skeletonId) => ( + +
+
+ +
+
+ + +
+ + +
+
+
+ ))} +
+ ); +} + +function EmptyState() { + const { searchQuery, setSearchQuery } = useProjectsStore(); + const router = useRouter(); + const editor = useEditor(); + const savedProjects = editor.project.getSavedProjects(); + + const handleCreateProject = async () => { + try { + const projectId = await editor.project.createNewProject({ + name: "New project", + }); + router.push(`/editor/${projectId}`); + } catch (error) { + toast.error("Failed to create project", { + description: + error instanceof Error ? error.message : "Please try again", + }); + } + }; + + if (savedProjects.length > 0) { + return ( +
+
+ +
+

No results found

+

+ Your search for "{searchQuery}" did not return any results. +

+
+
+ +
+ ); + } + + return ( +
+
+
+ +
+

No projects yet

+

+ Start creating your first project. Import media, edit, and export your + videos. All privately. +

+
+ +
+ ); +} diff --git a/apps/web/src/app/projects/store.ts b/apps/web/src/app/projects/store.ts index 2c79aa0a..71f89b4a 100644 --- a/apps/web/src/app/projects/store.ts +++ b/apps/web/src/app/projects/store.ts @@ -1,135 +1,135 @@ -import { create } from "zustand"; -import { persist } from "zustand/middleware"; -import type { TProjectSortKey } from "@/lib/project/types"; - -export type ProjectsViewMode = "grid" | "list"; - -interface ProjectsState { - searchQuery: string; - sortKey: TProjectSortKey; - sortOrder: "asc" | "desc"; - viewMode: ProjectsViewMode; - selectedProjectIds: string[]; - lastSelectedProjectId: string | null; - isHydrated: boolean; - setIsHydrated: ({ isHydrated }: { isHydrated: boolean }) => void; - setSearchQuery: ({ query }: { query: string }) => void; - setSortKey: ({ sortKey }: { sortKey: TProjectSortKey }) => void; - setSortOrder: ({ sortOrder }: { sortOrder: "asc" | "desc" }) => void; - toggleSortOrder: () => void; - setViewMode: ({ viewMode }: { viewMode: ProjectsViewMode }) => void; - setSelectedProjects: ({ projectIds }: { projectIds: string[] }) => void; - clearSelectedProjects: () => void; - setProjectSelected: ({ - projectId, - isSelected, - }: { - projectId: string; - isSelected: boolean; - }) => void; - selectProjectRange: ({ - projectId, - allProjectIds, - }: { - projectId: string; - allProjectIds: string[]; - }) => void; -} - -const getNextSelectedProjectIds = ({ - selectedProjectIds, - projectId, - isSelected, -}: { - selectedProjectIds: string[]; - projectId: string; - isSelected: boolean; -}): string[] => { - const selectedProjectIdSet = new Set(selectedProjectIds); - - if (isSelected) { - selectedProjectIdSet.add(projectId); - return Array.from(selectedProjectIdSet); - } - - selectedProjectIdSet.delete(projectId); - return Array.from(selectedProjectIdSet); -}; - -export const useProjectsStore = create()( - persist( - (set) => ({ - searchQuery: "", - sortKey: "updatedAt", - sortOrder: "desc", - viewMode: "grid", - selectedProjectIds: [], - lastSelectedProjectId: null, - isHydrated: false, - setIsHydrated: ({ isHydrated }) => set({ isHydrated }), - setSearchQuery: ({ query }) => set({ searchQuery: query }), - setSortKey: ({ sortKey }) => set({ sortKey }), - setSortOrder: ({ sortOrder }) => set({ sortOrder }), - toggleSortOrder: () => - set((state) => ({ - sortOrder: state.sortOrder === "asc" ? "desc" : "asc", - })), - setViewMode: ({ viewMode }) => set({ viewMode }), - setSelectedProjects: ({ projectIds }) => - set({ selectedProjectIds: projectIds }), - clearSelectedProjects: () => - set({ selectedProjectIds: [], lastSelectedProjectId: null }), - setProjectSelected: ({ projectId, isSelected }) => - set((state) => ({ - selectedProjectIds: getNextSelectedProjectIds({ - selectedProjectIds: state.selectedProjectIds, - projectId, - isSelected, - }), - lastSelectedProjectId: isSelected - ? projectId - : state.lastSelectedProjectId, - })), - selectProjectRange: ({ projectId, allProjectIds }) => - set((state) => { - const anchorId = state.lastSelectedProjectId; - if (!anchorId) { - return { - selectedProjectIds: [projectId], - lastSelectedProjectId: projectId, - }; - } - - const anchorIndex = allProjectIds.indexOf(anchorId); - const targetIndex = allProjectIds.indexOf(projectId); - - if (anchorIndex === -1 || targetIndex === -1) { - return { - selectedProjectIds: [projectId], - lastSelectedProjectId: projectId, - }; - } - - const startIndex = Math.min(anchorIndex, targetIndex); - const endIndex = Math.max(anchorIndex, targetIndex); - const rangeIds = allProjectIds.slice(startIndex, endIndex + 1); - - const merged = new Set([...state.selectedProjectIds, ...rangeIds]); - return { - selectedProjectIds: Array.from(merged), - }; - }), - }), - { - name: "projects-view-mode", - partialize: (state) => ({ - viewMode: state.viewMode, - sortKey: state.sortKey, - sortOrder: state.sortOrder, - }), - onRehydrateStorage: () => (state) => { - state?.setIsHydrated({ isHydrated: true }); - }, - }, - ), -); +import { create } from "zustand"; +import { persist } from "zustand/middleware"; +import type { TProjectSortKey } from "@/lib/project/types"; + +export type ProjectsViewMode = "grid" | "list"; + +interface ProjectsState { + searchQuery: string; + sortKey: TProjectSortKey; + sortOrder: "asc" | "desc"; + viewMode: ProjectsViewMode; + selectedProjectIds: string[]; + lastSelectedProjectId: string | null; + isHydrated: boolean; + setIsHydrated: ({ isHydrated }: { isHydrated: boolean }) => void; + setSearchQuery: ({ query }: { query: string }) => void; + setSortKey: ({ sortKey }: { sortKey: TProjectSortKey }) => void; + setSortOrder: ({ sortOrder }: { sortOrder: "asc" | "desc" }) => void; + toggleSortOrder: () => void; + setViewMode: ({ viewMode }: { viewMode: ProjectsViewMode }) => void; + setSelectedProjects: ({ projectIds }: { projectIds: string[] }) => void; + clearSelectedProjects: () => void; + setProjectSelected: ({ + projectId, + isSelected, + }: { + projectId: string; + isSelected: boolean; + }) => void; + selectProjectRange: ({ + projectId, + allProjectIds, + }: { + projectId: string; + allProjectIds: string[]; + }) => void; +} + +const getNextSelectedProjectIds = ({ + selectedProjectIds, + projectId, + isSelected, +}: { + selectedProjectIds: string[]; + projectId: string; + isSelected: boolean; +}): string[] => { + const selectedProjectIdSet = new Set(selectedProjectIds); + + if (isSelected) { + selectedProjectIdSet.add(projectId); + return Array.from(selectedProjectIdSet); + } + + selectedProjectIdSet.delete(projectId); + return Array.from(selectedProjectIdSet); +}; + +export const useProjectsStore = create()( + persist( + (set) => ({ + searchQuery: "", + sortKey: "updatedAt", + sortOrder: "desc", + viewMode: "grid", + selectedProjectIds: [], + lastSelectedProjectId: null, + isHydrated: false, + setIsHydrated: ({ isHydrated }) => set({ isHydrated }), + setSearchQuery: ({ query }) => set({ searchQuery: query }), + setSortKey: ({ sortKey }) => set({ sortKey }), + setSortOrder: ({ sortOrder }) => set({ sortOrder }), + toggleSortOrder: () => + set((state) => ({ + sortOrder: state.sortOrder === "asc" ? "desc" : "asc", + })), + setViewMode: ({ viewMode }) => set({ viewMode }), + setSelectedProjects: ({ projectIds }) => + set({ selectedProjectIds: projectIds }), + clearSelectedProjects: () => + set({ selectedProjectIds: [], lastSelectedProjectId: null }), + setProjectSelected: ({ projectId, isSelected }) => + set((state) => ({ + selectedProjectIds: getNextSelectedProjectIds({ + selectedProjectIds: state.selectedProjectIds, + projectId, + isSelected, + }), + lastSelectedProjectId: isSelected + ? projectId + : state.lastSelectedProjectId, + })), + selectProjectRange: ({ projectId, allProjectIds }) => + set((state) => { + const anchorId = state.lastSelectedProjectId; + if (!anchorId) { + return { + selectedProjectIds: [projectId], + lastSelectedProjectId: projectId, + }; + } + + const anchorIndex = allProjectIds.indexOf(anchorId); + const targetIndex = allProjectIds.indexOf(projectId); + + if (anchorIndex === -1 || targetIndex === -1) { + return { + selectedProjectIds: [projectId], + lastSelectedProjectId: projectId, + }; + } + + const startIndex = Math.min(anchorIndex, targetIndex); + const endIndex = Math.max(anchorIndex, targetIndex); + const rangeIds = allProjectIds.slice(startIndex, endIndex + 1); + + const merged = new Set([...state.selectedProjectIds, ...rangeIds]); + return { + selectedProjectIds: Array.from(merged), + }; + }), + }), + { + name: "projects-view-mode", + partialize: (state) => ({ + viewMode: state.viewMode, + sortKey: state.sortKey, + sortOrder: state.sortOrder, + }), + onRehydrateStorage: () => (state) => { + state?.setIsHydrated({ isHydrated: true }); + }, + }, + ), +); diff --git a/apps/web/src/app/roadmap/page.tsx b/apps/web/src/app/roadmap/page.tsx index 59c36d78..a0c3d9be 100644 --- a/apps/web/src/app/roadmap/page.tsx +++ b/apps/web/src/app/roadmap/page.tsx @@ -1,146 +1,146 @@ -import type { Metadata } from "next"; -import { BasePage } from "@/app/base-page"; -import { GitHubContributeSection } from "@/components/gitHub-contribute-section"; -import { Badge } from "@/components/ui/badge"; -import { ReactMarkdownWrapper } from "@/components/ui/react-markdown-wrapper"; -import { cn } from "@/utils/ui"; - -const LAST_UPDATED = "February 25, 2026"; - -type StatusType = "complete" | "pending" | "default" | "info"; - -interface Status { - text: string; - type: StatusType; -} - -interface RoadmapItem { - title: string; - description: string; - status: Status; -} - -const roadmapItems: RoadmapItem[] = [ - { - title: "Start", - description: - "This is where it all started. Repository created, initial project structure, and the vision for a free, open-source video editor. [Check out the first tweet](https://x.com/mazeincoding/status/1936706642512388188) to see where it started.", - status: { - text: "Completed", - type: "complete", - }, - }, - { - title: "Core UI", - description: - "Build the foundation - main layout, header, sidebar, timeline container, and basic component structure. Not all functionality yet, but the UI framework that everything else builds on.", - status: { - text: "Completed", - type: "complete", - }, - }, - { - title: "Essential functionality", - description: - "Everything that makes a video editor **useful**. Timeline interactivity, storage, effects, transitions, etc.", - status: { - text: "In progress", - type: "pending", - }, - }, - { - title: "Native app (mobile/desktop)", - description: - "Native OpenCut apps for Mac, Windows, Linux, and iOS/Android.", - status: { - text: "Not started", - type: "default", - }, - }, -]; - -export const metadata: Metadata = { - title: "Roadmap - OpenCut", - description: - "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.", - openGraph: { - title: "OpenCut Roadmap - What's Coming Next", - description: - "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.", - type: "website", - images: [ - { - url: "/open-graph/roadmap.jpg", - width: 1200, - height: 630, - alt: "OpenCut Roadmap", - }, - ], - }, - twitter: { - card: "summary_large_image", - title: "OpenCut Roadmap - What's Coming Next", - description: - "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.", - images: ["/open-graph/roadmap.jpg"], - }, -}; - -export default function RoadmapPage() { - return ( - -
-
- {roadmapItems.map((item, index) => ( - - ))} -
- -
-
- ); -} - -function RoadmapItem({ item, index }: { item: RoadmapItem; index: number }) { - return ( -
-
- {index + 1} -

{item.title}

- -
-
- {item.description} -
-
- ); -} - -function StatusBadge({ - status, - className, -}: { - status: Status; - className?: string; -}) { - return ( - - {status.text} - - ); -} +import type { Metadata } from "next"; +import { BasePage } from "@/app/base-page"; +import { GitHubContributeSection } from "@/components/gitHub-contribute-section"; +import { Badge } from "@/components/ui/badge"; +import { ReactMarkdownWrapper } from "@/components/ui/react-markdown-wrapper"; +import { cn } from "@/utils/ui"; + +const LAST_UPDATED = "February 25, 2026"; + +type StatusType = "complete" | "pending" | "default" | "info"; + +interface Status { + text: string; + type: StatusType; +} + +interface RoadmapItem { + title: string; + description: string; + status: Status; +} + +const roadmapItems: RoadmapItem[] = [ + { + title: "Start", + description: + "This is where it all started. Repository created, initial project structure, and the vision for a free, open-source video editor. [Check out the first tweet](https://x.com/mazeincoding/status/1936706642512388188) to see where it started.", + status: { + text: "Completed", + type: "complete", + }, + }, + { + title: "Core UI", + description: + "Build the foundation - main layout, header, sidebar, timeline container, and basic component structure. Not all functionality yet, but the UI framework that everything else builds on.", + status: { + text: "Completed", + type: "complete", + }, + }, + { + title: "Essential functionality", + description: + "Everything that makes a video editor **useful**. Timeline interactivity, storage, effects, transitions, etc.", + status: { + text: "In progress", + type: "pending", + }, + }, + { + title: "Native app (mobile/desktop)", + description: + "Native OpenCut apps for Mac, Windows, Linux, and iOS/Android.", + status: { + text: "Not started", + type: "default", + }, + }, +]; + +export const metadata: Metadata = { + title: "Roadmap - OpenCut", + description: + "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.", + openGraph: { + title: "OpenCut Roadmap - What's Coming Next", + description: + "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.", + type: "website", + images: [ + { + url: "/open-graph/roadmap.jpg", + width: 1200, + height: 630, + alt: "OpenCut Roadmap", + }, + ], + }, + twitter: { + card: "summary_large_image", + title: "OpenCut Roadmap - What's Coming Next", + description: + "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.", + images: ["/open-graph/roadmap.jpg"], + }, +}; + +export default function RoadmapPage() { + return ( + +
+
+ {roadmapItems.map((item, index) => ( + + ))} +
+ +
+
+ ); +} + +function RoadmapItem({ item, index }: { item: RoadmapItem; index: number }) { + return ( +
+
+ {index + 1} +

{item.title}

+ +
+
+ {item.description} +
+
+ ); +} + +function StatusBadge({ + status, + className, +}: { + status: Status; + className?: string; +}) { + return ( + + {status.text} + + ); +} diff --git a/apps/web/src/app/robots.ts b/apps/web/src/app/robots.ts index 60c13829..c3e80a6a 100644 --- a/apps/web/src/app/robots.ts +++ b/apps/web/src/app/robots.ts @@ -1,13 +1,13 @@ -import type { MetadataRoute } from "next"; -import { SITE_URL } from "@/constants/site-constants"; - -export default function robots(): MetadataRoute.Robots { - return { - rules: { - userAgent: "*", - allow: "/", - disallow: ["/_next/", "/projects/", "/editor/"], - }, - sitemap: `${SITE_URL}/sitemap.xml`, - }; -} +import type { MetadataRoute } from "next"; +import { SITE_URL } from "@/constants/site-constants"; + +export default function robots(): MetadataRoute.Robots { + return { + rules: { + userAgent: "*", + allow: "/", + disallow: ["/_next/", "/projects/", "/editor/"], + }, + sitemap: `${SITE_URL}/sitemap.xml`, + }; +} diff --git a/apps/web/src/app/rss.xml/route.ts b/apps/web/src/app/rss.xml/route.ts index 909d5a1a..9cfad635 100644 --- a/apps/web/src/app/rss.xml/route.ts +++ b/apps/web/src/app/rss.xml/route.ts @@ -1,46 +1,46 @@ -import { Feed } from "feed"; -import { getPosts } from "@/lib/blog/query"; -import { SITE_INFO, SITE_URL } from "@/constants/site-constants"; - -export async function GET() { - try { - const { posts } = await getPosts(); - - const feed = new Feed({ - title: `${SITE_INFO.title} Blog`, - description: SITE_INFO.description, - id: `${SITE_URL}`, - link: `${SITE_URL}/blog/`, - language: "en", - image: `${SITE_INFO.openGraphImage}`, - favicon: `${SITE_INFO.favicon}`, - copyright: `All rights reserved ${new Date().getFullYear()}, ${ - SITE_INFO.title - }`, - }); - - for (const post of posts) { - feed.addItem({ - title: post.title, - id: `${SITE_URL}/blog/${post.slug}`, - link: `${SITE_URL}/blog/${post.slug}`, - description: post.description, - author: post.authors.map((author) => ({ - name: author.name, - })), - date: new Date(post.publishedAt), - image: post.coverImage || SITE_INFO.openGraphImage, - }); - } - - return new Response(feed.rss2(), { - headers: { - "Content-Type": "text/xml", - "Cache-Control": "public, max-age=86400, stale-while-revalidate", - }, - }); - } catch (error) { - console.error("Error generating RSS feed", error); - return new Response("Internal Server Error", { status: 500 }); - } -} +import { Feed } from "feed"; +import { getPosts } from "@/lib/blog/query"; +import { SITE_INFO, SITE_URL } from "@/constants/site-constants"; + +export async function GET() { + try { + const { posts } = await getPosts(); + + const feed = new Feed({ + title: `${SITE_INFO.title} Blog`, + description: SITE_INFO.description, + id: `${SITE_URL}`, + link: `${SITE_URL}/blog/`, + language: "en", + image: `${SITE_INFO.openGraphImage}`, + favicon: `${SITE_INFO.favicon}`, + copyright: `All rights reserved ${new Date().getFullYear()}, ${ + SITE_INFO.title + }`, + }); + + for (const post of posts) { + feed.addItem({ + title: post.title, + id: `${SITE_URL}/blog/${post.slug}`, + link: `${SITE_URL}/blog/${post.slug}`, + description: post.description, + author: post.authors.map((author) => ({ + name: author.name, + })), + date: new Date(post.publishedAt), + image: post.coverImage || SITE_INFO.openGraphImage, + }); + } + + return new Response(feed.rss2(), { + headers: { + "Content-Type": "text/xml", + "Cache-Control": "public, max-age=86400, stale-while-revalidate", + }, + }); + } catch (error) { + console.error("Error generating RSS feed", error); + return new Response("Internal Server Error", { status: 500 }); + } +} diff --git a/apps/web/src/app/sitemap.ts b/apps/web/src/app/sitemap.ts index fed1aed6..2dea655d 100644 --- a/apps/web/src/app/sitemap.ts +++ b/apps/web/src/app/sitemap.ts @@ -1,61 +1,61 @@ -import { SITE_URL } from "@/constants/site-constants"; -import { getPosts } from "@/lib/blog/query"; -import type { MetadataRoute } from "next"; - -export default async function sitemap(): Promise { - const data = await getPosts(); - - const postPages: MetadataRoute.Sitemap = - data?.posts?.map((post) => ({ - url: `${SITE_URL}/blog/${post.slug}`, - lastModified: new Date(post.publishedAt), - changeFrequency: "weekly", - priority: 0.8, - })) ?? []; - - return [ - { - url: SITE_URL, - lastModified: new Date(), - changeFrequency: "weekly", - priority: 1, - }, - { - url: `${SITE_URL}/contributors`, - lastModified: new Date(), - changeFrequency: "daily", - priority: 0.5, - }, - { - url: `${SITE_URL}/roadmap`, - lastModified: new Date(), - changeFrequency: "weekly", - priority: 1, - }, - { - url: `${SITE_URL}/privacy`, - lastModified: new Date(), - changeFrequency: "monthly", - priority: 0.5, - }, - { - url: `${SITE_URL}/terms`, - lastModified: new Date(), - changeFrequency: "monthly", - priority: 0.5, - }, - { - url: `${SITE_URL}/why-not-capcut`, - lastModified: new Date(), - changeFrequency: "yearly", - priority: 1, - }, - { - url: `${SITE_URL}/blog`, - lastModified: new Date(), - changeFrequency: "weekly", - priority: 1, - }, - ...postPages, - ]; -} +import { SITE_URL } from "@/constants/site-constants"; +import { getPosts } from "@/lib/blog/query"; +import type { MetadataRoute } from "next"; + +export default async function sitemap(): Promise { + const data = await getPosts(); + + const postPages: MetadataRoute.Sitemap = + data?.posts?.map((post) => ({ + url: `${SITE_URL}/blog/${post.slug}`, + lastModified: new Date(post.publishedAt), + changeFrequency: "weekly", + priority: 0.8, + })) ?? []; + + return [ + { + url: SITE_URL, + lastModified: new Date(), + changeFrequency: "weekly", + priority: 1, + }, + { + url: `${SITE_URL}/contributors`, + lastModified: new Date(), + changeFrequency: "daily", + priority: 0.5, + }, + { + url: `${SITE_URL}/roadmap`, + lastModified: new Date(), + changeFrequency: "weekly", + priority: 1, + }, + { + url: `${SITE_URL}/privacy`, + lastModified: new Date(), + changeFrequency: "monthly", + priority: 0.5, + }, + { + url: `${SITE_URL}/terms`, + lastModified: new Date(), + changeFrequency: "monthly", + priority: 0.5, + }, + { + url: `${SITE_URL}/why-not-capcut`, + lastModified: new Date(), + changeFrequency: "yearly", + priority: 1, + }, + { + url: `${SITE_URL}/blog`, + lastModified: new Date(), + changeFrequency: "weekly", + priority: 1, + }, + ...postPages, + ]; +} diff --git a/apps/web/src/app/sponsors/page.tsx b/apps/web/src/app/sponsors/page.tsx index a1a57fce..64f18776 100644 --- a/apps/web/src/app/sponsors/page.tsx +++ b/apps/web/src/app/sponsors/page.tsx @@ -1,86 +1,86 @@ -import type { Metadata } from "next"; -import Image from "next/image"; -import Link from "next/link"; -import { BasePage } from "@/app/base-page"; -import { Card, CardContent } from "@/components/ui/card"; -import { SPONSORS, type Sponsor } from "@/constants/site-constants"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { LinkSquare02Icon } from "@hugeicons/core-free-icons"; -import { cn } from "@/utils/ui"; - -export const metadata: Metadata = { - title: "Sponsors - OpenCut", - description: - "Support OpenCut and help us build the future of free and open-source video editing.", - openGraph: { - title: "Sponsors - OpenCut", - description: - "Support OpenCut and help us build the future of free and open-source video editing.", - type: "website", - }, -}; - -export default function SponsorsPage() { - return ( - -
-

- Sponsors -

-

- Support OpenCut and help us build the future of privacy-first video - editing. -

-
- -
- ); -} - -function SponsorsGrid() { - return ( -
- {SPONSORS.map((sponsor) => ( - - ))} -
- ); -} - -function SponsorCard({ sponsor }: { sponsor: Sponsor }) { - return ( - - - - {`${sponsor.name} -
-
-

- {sponsor.name} -

- -
-

{sponsor.description}

-
-
-
- - ); -} +import type { Metadata } from "next"; +import Image from "next/image"; +import Link from "next/link"; +import { BasePage } from "@/app/base-page"; +import { Card, CardContent } from "@/components/ui/card"; +import { SPONSORS, type Sponsor } from "@/constants/site-constants"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { LinkSquare02Icon } from "@hugeicons/core-free-icons"; +import { cn } from "@/utils/ui"; + +export const metadata: Metadata = { + title: "Sponsors - OpenCut", + description: + "Support OpenCut and help us build the future of free and open-source video editing.", + openGraph: { + title: "Sponsors - OpenCut", + description: + "Support OpenCut and help us build the future of free and open-source video editing.", + type: "website", + }, +}; + +export default function SponsorsPage() { + return ( + +
+

+ Sponsors +

+

+ Support OpenCut and help us build the future of privacy-first video + editing. +

+
+ +
+ ); +} + +function SponsorsGrid() { + return ( +
+ {SPONSORS.map((sponsor) => ( + + ))} +
+ ); +} + +function SponsorCard({ sponsor }: { sponsor: Sponsor }) { + return ( + + + + {`${sponsor.name} +
+
+

+ {sponsor.name} +

+ +
+

{sponsor.description}

+
+
+
+ + ); +} diff --git a/apps/web/src/components/editable-timecode.tsx b/apps/web/src/components/editable-timecode.tsx index 40390f07..c346ad53 100644 --- a/apps/web/src/components/editable-timecode.tsx +++ b/apps/web/src/components/editable-timecode.tsx @@ -1,150 +1,150 @@ -"use client"; - -import { useEffect, useRef, useState } from "react"; -import { formatTimeCode, parseTimeCode } from "@/lib/time"; -import type { TTimeCode } from "@/lib/time"; -import { cn } from "@/utils/ui"; - -interface EditableTimecodeProps { - time: number; - duration: number; - format?: TTimeCode; - fps: number; - onTimeChange?: ({ time }: { time: number }) => void; - className?: string; - disabled?: boolean; -} - -export function EditableTimecode({ - time, - duration, - format = "HH:MM:SS:FF", - fps, - onTimeChange, - className, - disabled = false, -}: EditableTimecodeProps) { - const [isEditing, setIsEditing] = useState(false); - const [inputValue, setInputValue] = useState(""); - const [hasError, setHasError] = useState(false); - const inputRef = useRef(null); - const enterPressedRef = useRef(false); - const formattedTime = formatTimeCode({ timeInSeconds: time, format, fps }); - - const startEditing = () => { - if (disabled) return; - setIsEditing(true); - setInputValue(formattedTime); - setHasError(false); - enterPressedRef.current = false; - }; - - const cancelEditing = () => { - setIsEditing(false); - setInputValue(""); - setHasError(false); - enterPressedRef.current = false; - }; - - const applyEdit = () => { - const parsedTime = parseTimeCode({ timeCode: inputValue, format, fps }); - - if (parsedTime === null) { - setHasError(true); - return; - } - - const clampedTime = Math.max( - 0, - duration ? Math.min(duration, parsedTime) : parsedTime, - ); - - onTimeChange?.({ time: clampedTime }); - setIsEditing(false); - setInputValue(""); - setHasError(false); - enterPressedRef.current = false; - }; - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - enterPressedRef.current = true; - applyEdit(); - } else if (event.key === "Escape") { - event.preventDefault(); - cancelEditing(); - } - }; - - const handleInputChange = ({ - target, - }: React.ChangeEvent) => { - setInputValue(target.value); - setHasError(false); - }; - - const handleBlur = () => { - if (!enterPressedRef.current && isEditing) { - applyEdit(); - } - }; - - const handleDisplayKeyDown = ( - event: React.KeyboardEvent, - ) => { - if (disabled) return; - - if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - startEditing(); - } - }; - - useEffect(() => { - if (isEditing && inputRef.current) { - inputRef.current.focus(); - inputRef.current.select(); - } - }, [isEditing]); - - if (isEditing) { - return ( - - ); - } - - return ( - - ); -} +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { formatTimeCode, parseTimeCode } from "@/lib/time"; +import type { TTimeCode } from "@/lib/time"; +import { cn } from "@/utils/ui"; + +interface EditableTimecodeProps { + time: number; + duration: number; + format?: TTimeCode; + fps: number; + onTimeChange?: ({ time }: { time: number }) => void; + className?: string; + disabled?: boolean; +} + +export function EditableTimecode({ + time, + duration, + format = "HH:MM:SS:FF", + fps, + onTimeChange, + className, + disabled = false, +}: EditableTimecodeProps) { + const [isEditing, setIsEditing] = useState(false); + const [inputValue, setInputValue] = useState(""); + const [hasError, setHasError] = useState(false); + const inputRef = useRef(null); + const enterPressedRef = useRef(false); + const formattedTime = formatTimeCode({ timeInSeconds: time, format, fps }); + + const startEditing = () => { + if (disabled) return; + setIsEditing(true); + setInputValue(formattedTime); + setHasError(false); + enterPressedRef.current = false; + }; + + const cancelEditing = () => { + setIsEditing(false); + setInputValue(""); + setHasError(false); + enterPressedRef.current = false; + }; + + const applyEdit = () => { + const parsedTime = parseTimeCode({ timeCode: inputValue, format, fps }); + + if (parsedTime === null) { + setHasError(true); + return; + } + + const clampedTime = Math.max( + 0, + duration ? Math.min(duration, parsedTime) : parsedTime, + ); + + onTimeChange?.({ time: clampedTime }); + setIsEditing(false); + setInputValue(""); + setHasError(false); + enterPressedRef.current = false; + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + event.preventDefault(); + enterPressedRef.current = true; + applyEdit(); + } else if (event.key === "Escape") { + event.preventDefault(); + cancelEditing(); + } + }; + + const handleInputChange = ({ + target, + }: React.ChangeEvent) => { + setInputValue(target.value); + setHasError(false); + }; + + const handleBlur = () => { + if (!enterPressedRef.current && isEditing) { + applyEdit(); + } + }; + + const handleDisplayKeyDown = ( + event: React.KeyboardEvent, + ) => { + if (disabled) return; + + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + startEditing(); + } + }; + + useEffect(() => { + if (isEditing && inputRef.current) { + inputRef.current.focus(); + inputRef.current.select(); + } + }, [isEditing]); + + if (isEditing) { + return ( + + ); + } + + return ( + + ); +} diff --git a/apps/web/src/components/editor/dialogs/project-info-dialog.tsx b/apps/web/src/components/editor/dialogs/project-info-dialog.tsx index 42f99c79..fad711e3 100644 --- a/apps/web/src/components/editor/dialogs/project-info-dialog.tsx +++ b/apps/web/src/components/editor/dialogs/project-info-dialog.tsx @@ -1,83 +1,83 @@ -import { - Dialog, - DialogBody, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import type { TProjectMetadata } from "@/lib/project/types"; -import { formatDate } from "@/utils/date"; -import { formatTimeCode } from "@/lib/time"; -import { Button } from "@/components/ui/button"; - -function InfoRow({ - label, - value, -}: { - label: string; - value: string | React.ReactNode; -}) { - return ( -
- {label} - {value} -
- ); -} - -export function ProjectInfoDialog({ - isOpen, - onOpenChange, - project, -}: { - isOpen: boolean; - onOpenChange: (open: boolean) => void; - project: TProjectMetadata; -}) { - const durationFormatted = - project.duration > 0 - ? formatTimeCode({ - timeInSeconds: project.duration, - format: project.duration >= 3600 ? "HH:MM:SS" : "MM:SS", - }) - : "0:00"; - - return ( - - event.preventDefault()}> - - - {project.name} - - - - - - - - - {project.id.slice(0, 8)} - - } - /> - - - - - - - - ); -} +import { + Dialog, + DialogBody, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import type { TProjectMetadata } from "@/lib/project/types"; +import { formatDate } from "@/utils/date"; +import { formatTimeCode } from "@/lib/time"; +import { Button } from "@/components/ui/button"; + +function InfoRow({ + label, + value, +}: { + label: string; + value: string | React.ReactNode; +}) { + return ( +
+ {label} + {value} +
+ ); +} + +export function ProjectInfoDialog({ + isOpen, + onOpenChange, + project, +}: { + isOpen: boolean; + onOpenChange: (open: boolean) => void; + project: TProjectMetadata; +}) { + const durationFormatted = + project.duration > 0 + ? formatTimeCode({ + timeInSeconds: project.duration, + format: project.duration >= 3600 ? "HH:MM:SS" : "MM:SS", + }) + : "0:00"; + + return ( + + event.preventDefault()}> + + + {project.name} + + + + + + + + + {project.id.slice(0, 8)} + + } + /> + + + + + + + + ); +} diff --git a/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx b/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx index 9a353a1c..70802b5b 100644 --- a/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx +++ b/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx @@ -1,229 +1,229 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { toast } from "sonner"; -import { - type KeyboardShortcut, - useKeyboardShortcutsHelp, -} from "@/hooks/use-keyboard-shortcuts-help"; -import { useKeybindingsStore } from "@/stores/keybindings-store"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogBody, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; - -export function ShortcutsDialog({ - isOpen, - onOpenChange, -}: { - isOpen: boolean; - onOpenChange: (open: boolean) => void; -}) { - const [recordingShortcut, setRecordingShortcut] = - useState(null); - - const { - updateKeybinding, - removeKeybinding, - getKeybindingString, - validateKeybinding, - getKeybindingsForAction, - setIsRecording, - resetToDefaults, - isRecording, - } = useKeybindingsStore(); - - const { shortcuts } = useKeyboardShortcutsHelp(); - - const categories = Array.from(new Set(shortcuts.map((s) => s.category))); - - useEffect(() => { - if (!isRecording || !recordingShortcut) return; - - const handleKeyDown = (e: KeyboardEvent) => { - e.preventDefault(); - e.stopPropagation(); - - const keyString = getKeybindingString(e); - if (keyString) { - const conflict = validateKeybinding( - keyString, - recordingShortcut.action, - ); - if (conflict) { - toast.error( - `Key "${keyString}" is already bound to "${conflict.existingAction}"`, - ); - setRecordingShortcut(null); - return; - } - - const oldKeys = getKeybindingsForAction(recordingShortcut.action); - for (const key of oldKeys) { - removeKeybinding(key); - } - - updateKeybinding(keyString, recordingShortcut.action); - - setIsRecording(false); - setRecordingShortcut(null); - } - }; - - const handleClickOutside = () => { - setRecordingShortcut(null); - setIsRecording(false); - }; - - document.addEventListener("keydown", handleKeyDown); - document.addEventListener("click", handleClickOutside); - - return () => { - document.removeEventListener("keydown", handleKeyDown); - document.removeEventListener("click", handleClickOutside); - }; - }, [ - recordingShortcut, - getKeybindingString, - updateKeybinding, - removeKeybinding, - validateKeybinding, - getKeybindingsForAction, - setIsRecording, - isRecording, - ]); - - const handleStartRecording = (shortcut: KeyboardShortcut) => { - setRecordingShortcut(shortcut); - setIsRecording(true); - }; - - return ( - - - - Keyboard shortcuts - - - -
- {categories.map((category) => ( -
-

- {category} -

-
- {shortcuts - .filter((shortcut) => shortcut.category === category) - .map((shortcut) => ( - handleStartRecording(shortcut)} - /> - ))} -
-
- ))} -
-
- - - -
-
- ); -} - -function ShortcutItem({ - shortcut, - isRecording, - onStartRecording, -}: { - shortcut: KeyboardShortcut; - isRecording: boolean; - onStartRecording: (params: { shortcut: KeyboardShortcut }) => void; -}) { - const displayKeys = shortcut.keys.filter((key: string) => { - if ( - key.includes("Cmd") && - shortcut.keys.includes(key.replace("Cmd", "Ctrl")) - ) - return false; - - return true; - }); - - return ( -
-
- {shortcut.icon && ( -
{shortcut.icon}
- )} - {shortcut.description} -
-
- {displayKeys.map((key: string, index: number) => ( -
-
- {key.split("+").map((keyPart: string, partIndex: number) => { - const keyId = `${shortcut.id}-${index}-${partIndex}`; - return ( - onStartRecording({ shortcut })} - > - {keyPart} - - ); - })} -
- {index < displayKeys.length - 1 && ( - or - )} -
- ))} -
-
- ); -} - -function EditableShortcutKey({ - children, - isRecording, - onStartRecording, -}: { - children: React.ReactNode; - isRecording: boolean; - onStartRecording: () => void; -}) { - const handleClick = (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - onStartRecording(); - }; - - return ( - - ); -} +"use client"; + +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import { + type KeyboardShortcut, + useKeyboardShortcutsHelp, +} from "@/hooks/use-keyboard-shortcuts-help"; +import { useKeybindingsStore } from "@/stores/keybindings-store"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogBody, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; + +export function ShortcutsDialog({ + isOpen, + onOpenChange, +}: { + isOpen: boolean; + onOpenChange: (open: boolean) => void; +}) { + const [recordingShortcut, setRecordingShortcut] = + useState(null); + + const { + updateKeybinding, + removeKeybinding, + getKeybindingString, + validateKeybinding, + getKeybindingsForAction, + setIsRecording, + resetToDefaults, + isRecording, + } = useKeybindingsStore(); + + const { shortcuts } = useKeyboardShortcutsHelp(); + + const categories = Array.from(new Set(shortcuts.map((s) => s.category))); + + useEffect(() => { + if (!isRecording || !recordingShortcut) return; + + const handleKeyDown = (e: KeyboardEvent) => { + e.preventDefault(); + e.stopPropagation(); + + const keyString = getKeybindingString(e); + if (keyString) { + const conflict = validateKeybinding( + keyString, + recordingShortcut.action, + ); + if (conflict) { + toast.error( + `Key "${keyString}" is already bound to "${conflict.existingAction}"`, + ); + setRecordingShortcut(null); + return; + } + + const oldKeys = getKeybindingsForAction(recordingShortcut.action); + for (const key of oldKeys) { + removeKeybinding(key); + } + + updateKeybinding(keyString, recordingShortcut.action); + + setIsRecording(false); + setRecordingShortcut(null); + } + }; + + const handleClickOutside = () => { + setRecordingShortcut(null); + setIsRecording(false); + }; + + document.addEventListener("keydown", handleKeyDown); + document.addEventListener("click", handleClickOutside); + + return () => { + document.removeEventListener("keydown", handleKeyDown); + document.removeEventListener("click", handleClickOutside); + }; + }, [ + recordingShortcut, + getKeybindingString, + updateKeybinding, + removeKeybinding, + validateKeybinding, + getKeybindingsForAction, + setIsRecording, + isRecording, + ]); + + const handleStartRecording = (shortcut: KeyboardShortcut) => { + setRecordingShortcut(shortcut); + setIsRecording(true); + }; + + return ( + + + + Keyboard shortcuts + + + +
+ {categories.map((category) => ( +
+

+ {category} +

+
+ {shortcuts + .filter((shortcut) => shortcut.category === category) + .map((shortcut) => ( + handleStartRecording(shortcut)} + /> + ))} +
+
+ ))} +
+
+ + + +
+
+ ); +} + +function ShortcutItem({ + shortcut, + isRecording, + onStartRecording, +}: { + shortcut: KeyboardShortcut; + isRecording: boolean; + onStartRecording: (params: { shortcut: KeyboardShortcut }) => void; +}) { + const displayKeys = shortcut.keys.filter((key: string) => { + if ( + key.includes("Cmd") && + shortcut.keys.includes(key.replace("Cmd", "Ctrl")) + ) + return false; + + return true; + }); + + return ( +
+
+ {shortcut.icon && ( +
{shortcut.icon}
+ )} + {shortcut.description} +
+
+ {displayKeys.map((key: string, index: number) => ( +
+
+ {key.split("+").map((keyPart: string, partIndex: number) => { + const keyId = `${shortcut.id}-${index}-${partIndex}`; + return ( + onStartRecording({ shortcut })} + > + {keyPart} + + ); + })} +
+ {index < displayKeys.length - 1 && ( + or + )} +
+ ))} +
+
+ ); +} + +function EditableShortcutKey({ + children, + isRecording, + onStartRecording, +}: { + children: React.ReactNode; + isRecording: boolean; + onStartRecording: () => void; +}) { + const handleClick = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + onStartRecording(); + }; + + return ( + + ); +} diff --git a/apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx b/apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx index d391928c..0e854a93 100644 --- a/apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx +++ b/apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx @@ -1,41 +1,41 @@ -"use client"; - -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogBody, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { useStoragePersistence } from "@/hooks/use-storage-persistence"; - -export function StoragePersistenceDialog() { - const { showDialog, onConfirm, onDismiss } = useStoragePersistence(); - - return ( - !open && onDismiss()}> - - - Don't lose your projects - - -

- Your browser can automatically delete your projects when storage - runs low. -

-

- Allow OpenCut to protect them? -

-
- - - - -
-
- ); -} +"use client"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogBody, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { useStoragePersistence } from "@/hooks/use-storage-persistence"; + +export function StoragePersistenceDialog() { + const { showDialog, onConfirm, onDismiss } = useStoragePersistence(); + + return ( + !open && onDismiss()}> + + + Don't lose your projects + + +

+ Your browser can automatically delete your projects when storage + runs low. +

+

+ Allow OpenCut to protect them? +

+
+ + + + +
+
+ ); +} diff --git a/apps/web/src/components/editor/editor-header.tsx b/apps/web/src/components/editor/editor-header.tsx index 8dd730b5..460e8ed6 100644 --- a/apps/web/src/components/editor/editor-header.tsx +++ b/apps/web/src/components/editor/editor-header.tsx @@ -1,245 +1,245 @@ -"use client"; - -import { Button } from "../ui/button"; -import { useRef, useState } from "react"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "../ui/dropdown-menu"; -import Link from "next/link"; -import { RenameProjectDialog } from "./dialogs/rename-project-dialog"; -import { DeleteProjectDialog } from "./dialogs/delete-project-dialog"; -import { useRouter } from "next/navigation"; -import { FaDiscord } from "react-icons/fa6"; -import { ExportButton } from "./export-button"; -import { ThemeToggle } from "../theme-toggle"; -import { DEFAULT_LOGO_URL, SOCIAL_LINKS } from "@/constants/site-constants"; -import { toast } from "sonner"; -import { useEditor } from "@/hooks/use-editor"; -import { CommandIcon, Logout05Icon } from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { ShortcutsDialog } from "./dialogs/shortcuts-dialog"; -import Image from "next/image"; -import { cn } from "@/utils/ui"; - -export function EditorHeader() { - return ( -
-
- - -
- -
- ); -} - -function ProjectDropdown() { - const [openDialog, setOpenDialog] = useState< - "delete" | "rename" | "shortcuts" | null - >(null); - const [isExiting, setIsExiting] = useState(false); - const router = useRouter(); - const editor = useEditor(); - const activeProject = useEditor((e) => e.project.getActive()); - - const handleExit = async () => { - if (isExiting) return; - setIsExiting(true); - - try { - await editor.project.prepareExit(); - editor.project.closeProject(); - } catch (error) { - console.error("Failed to prepare project exit:", error); - } finally { - editor.project.closeProject(); - router.push("/projects"); - } - }; - - const handleSaveProjectName = async (newName: string) => { - if ( - activeProject && - newName.trim() && - newName !== activeProject.metadata.name - ) { - try { - await editor.project.renameProject({ - id: activeProject.metadata.id, - name: newName.trim(), - }); - } catch (error) { - toast.error("Failed to rename project", { - description: - error instanceof Error ? error.message : "Please try again", - }); - } finally { - setOpenDialog(null); - } - } - }; - - const handleDeleteProject = async () => { - if (activeProject) { - try { - await editor.project.deleteProjects({ - ids: [activeProject.metadata.id], - }); - router.push("/projects"); - } catch (error) { - toast.error("Failed to delete project", { - description: - error instanceof Error ? error.message : "Please try again", - }); - } finally { - setOpenDialog(null); - } - } - }; - - return ( - <> - - - - - - } - > - Exit project - - - setOpenDialog("shortcuts")} - icon={} - > - Shortcuts - - - - - }> - - Discord - - - - - setOpenDialog(isOpen ? "rename" : null)} - onConfirm={(newName) => handleSaveProjectName(newName)} - projectName={activeProject?.metadata.name || ""} - /> - setOpenDialog(isOpen ? "delete" : null)} - onConfirm={handleDeleteProject} - projectNames={[activeProject?.metadata.name || ""]} - /> - setOpenDialog(isOpen ? "shortcuts" : null)} - /> - - ); -} - -function EditableProjectName() { - const editor = useEditor(); - const activeProject = useEditor((e) => e.project.getActive()); - const [isEditing, setIsEditing] = useState(false); - const inputRef = useRef(null); - const originalNameRef = useRef(""); - - const projectName = activeProject?.metadata.name || ""; - - const startEditing = () => { - if (isEditing) return; - originalNameRef.current = projectName; - setIsEditing(true); - - requestAnimationFrame(() => { - inputRef.current?.select(); - }); - }; - - const saveEdit = async () => { - if (!inputRef.current || !activeProject) return; - const newName = inputRef.current.value.trim(); - setIsEditing(false); - - if (!newName) { - inputRef.current.value = originalNameRef.current; - return; - } - - if (newName !== originalNameRef.current) { - try { - await editor.project.renameProject({ - id: activeProject.metadata.id, - name: newName, - }); - } catch (error) { - toast.error("Failed to rename project", { - description: - error instanceof Error ? error.message : "Please try again", - }); - } - } - }; - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - inputRef.current?.blur(); - } else if (event.key === "Escape") { - event.preventDefault(); - if (inputRef.current) { - inputRef.current.value = originalNameRef.current; - inputRef.current.setSelectionRange(0, 0); - } - setIsEditing(false); - inputRef.current?.blur(); - } - }; - - return ( - - ); -} +"use client"; + +import { Button } from "../ui/button"; +import { useRef, useState } from "react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; +import Link from "next/link"; +import { RenameProjectDialog } from "./dialogs/rename-project-dialog"; +import { DeleteProjectDialog } from "./dialogs/delete-project-dialog"; +import { useRouter } from "next/navigation"; +import { FaDiscord } from "react-icons/fa6"; +import { ExportButton } from "./export-button"; +import { ThemeToggle } from "../theme-toggle"; +import { DEFAULT_LOGO_URL, SOCIAL_LINKS } from "@/constants/site-constants"; +import { toast } from "sonner"; +import { useEditor } from "@/hooks/use-editor"; +import { CommandIcon, Logout05Icon } from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { ShortcutsDialog } from "./dialogs/shortcuts-dialog"; +import Image from "next/image"; +import { cn } from "@/utils/ui"; + +export function EditorHeader() { + return ( +
+
+ + +
+ +
+ ); +} + +function ProjectDropdown() { + const [openDialog, setOpenDialog] = useState< + "delete" | "rename" | "shortcuts" | null + >(null); + const [isExiting, setIsExiting] = useState(false); + const router = useRouter(); + const editor = useEditor(); + const activeProject = useEditor((e) => e.project.getActive()); + + const handleExit = async () => { + if (isExiting) return; + setIsExiting(true); + + try { + await editor.project.prepareExit(); + editor.project.closeProject(); + } catch (error) { + console.error("Failed to prepare project exit:", error); + } finally { + editor.project.closeProject(); + router.push("/projects"); + } + }; + + const handleSaveProjectName = async (newName: string) => { + if ( + activeProject && + newName.trim() && + newName !== activeProject.metadata.name + ) { + try { + await editor.project.renameProject({ + id: activeProject.metadata.id, + name: newName.trim(), + }); + } catch (error) { + toast.error("Failed to rename project", { + description: + error instanceof Error ? error.message : "Please try again", + }); + } finally { + setOpenDialog(null); + } + } + }; + + const handleDeleteProject = async () => { + if (activeProject) { + try { + await editor.project.deleteProjects({ + ids: [activeProject.metadata.id], + }); + router.push("/projects"); + } catch (error) { + toast.error("Failed to delete project", { + description: + error instanceof Error ? error.message : "Please try again", + }); + } finally { + setOpenDialog(null); + } + } + }; + + return ( + <> + + + + + + } + > + Exit project + + + setOpenDialog("shortcuts")} + icon={} + > + Shortcuts + + + + + }> + + Discord + + + + + setOpenDialog(isOpen ? "rename" : null)} + onConfirm={(newName) => handleSaveProjectName(newName)} + projectName={activeProject?.metadata.name || ""} + /> + setOpenDialog(isOpen ? "delete" : null)} + onConfirm={handleDeleteProject} + projectNames={[activeProject?.metadata.name || ""]} + /> + setOpenDialog(isOpen ? "shortcuts" : null)} + /> + + ); +} + +function EditableProjectName() { + const editor = useEditor(); + const activeProject = useEditor((e) => e.project.getActive()); + const [isEditing, setIsEditing] = useState(false); + const inputRef = useRef(null); + const originalNameRef = useRef(""); + + const projectName = activeProject?.metadata.name || ""; + + const startEditing = () => { + if (isEditing) return; + originalNameRef.current = projectName; + setIsEditing(true); + + requestAnimationFrame(() => { + inputRef.current?.select(); + }); + }; + + const saveEdit = async () => { + if (!inputRef.current || !activeProject) return; + const newName = inputRef.current.value.trim(); + setIsEditing(false); + + if (!newName) { + inputRef.current.value = originalNameRef.current; + return; + } + + if (newName !== originalNameRef.current) { + try { + await editor.project.renameProject({ + id: activeProject.metadata.id, + name: newName, + }); + } catch (error) { + toast.error("Failed to rename project", { + description: + error instanceof Error ? error.message : "Please try again", + }); + } + } + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + event.preventDefault(); + inputRef.current?.blur(); + } else if (event.key === "Escape") { + event.preventDefault(); + if (inputRef.current) { + inputRef.current.value = originalNameRef.current; + inputRef.current.setSelectionRange(0, 0); + } + setIsEditing(false); + inputRef.current?.blur(); + } + }; + + return ( + + ); +} diff --git a/apps/web/src/components/editor/export-button.tsx b/apps/web/src/components/editor/export-button.tsx index ec39e7e6..3fa4af7a 100644 --- a/apps/web/src/components/editor/export-button.tsx +++ b/apps/web/src/components/editor/export-button.tsx @@ -1,336 +1,336 @@ -"use client"; - -import { useState } from "react"; -import { TransitionTopIcon } from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { Button } from "@/components/ui/button"; -import { Label } from "@/components/ui/label"; -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; -import { Progress } from "@/components/ui/progress"; -import { Checkbox } from "@/components/ui/checkbox"; -import { cn } from "@/utils/ui"; -import { - getExportMimeType, - getExportFileExtension, - downloadBuffer, -} from "@/lib/export"; -import { Check, Copy, Download, RotateCcw } from "lucide-react"; -import { - EXPORT_FORMAT_VALUES, - EXPORT_QUALITY_VALUES, - type ExportFormat, - type ExportQuality, -} from "@/lib/export"; -import { - Section, - SectionContent, - SectionHeader, - SectionTitle, -} from "@/components/section"; -import { useEditor } from "@/hooks/use-editor"; -import { DEFAULT_EXPORT_OPTIONS } from "@/constants/export-constants"; - -function isExportFormat(value: string): value is ExportFormat { - return EXPORT_FORMAT_VALUES.some((formatValue) => formatValue === value); -} - -function isExportQuality(value: string): value is ExportQuality { - return EXPORT_QUALITY_VALUES.some((qualityValue) => qualityValue === value); -} - -export function ExportButton() { - const [isExportPopoverOpen, setIsExportPopoverOpen] = useState(false); - const editor = useEditor(); - const activeProject = useEditor((e) => e.project.getActiveOrNull()); - const hasProject = !!activeProject; - - const handlePopoverOpenChange = ({ open }: { open: boolean }) => { - if (!open) { - editor.project.cancelExport(); - editor.project.clearExportState(); - } - setIsExportPopoverOpen(open); - }; - - return ( - handlePopoverOpenChange({ open })} - > - - - - {hasProject && } - - ); -} - -function ExportPopover({ - onOpenChange, -}: { - onOpenChange: (open: boolean) => void; -}) { - const editor = useEditor(); - const activeProject = useEditor((e) => e.project.getActive()); - const exportState = useEditor((e) => e.project.getExportState()); - const { isExporting, progress, result: exportResult } = exportState; - const [format, setFormat] = useState( - DEFAULT_EXPORT_OPTIONS.format, - ); - const [quality, setQuality] = useState( - DEFAULT_EXPORT_OPTIONS.quality, - ); - const [shouldIncludeAudio, setShouldIncludeAudio] = useState( - DEFAULT_EXPORT_OPTIONS.includeAudio ?? true, - ); - - const handleExport = async () => { - if (!activeProject) return; - - const result = await editor.project.export({ - options: { - format, - quality, - fps: activeProject.settings.fps, - includeAudio: shouldIncludeAudio, - }, - }); - - if (result.cancelled) { - editor.project.clearExportState(); - return; - } - - if (result.success && result.buffer) { - downloadBuffer({ - buffer: result.buffer, - filename: `${activeProject.metadata.name}${getExportFileExtension({ format })}`, - mimeType: getExportMimeType({ format }), - }); - - editor.project.clearExportState(); - onOpenChange(false); - } - }; - - const handleCancel = () => { - editor.project.cancelExport(); - }; - - return ( - - {exportResult && !exportResult.success ? ( - - ) : ( - <> -
-

- {isExporting ? "Exporting project" : "Export project"} -

-
- -
- {!isExporting && ( - <> -
-
- - Format - - - { - if (isExportFormat(value)) { - setFormat(value); - } - }} - > -
- - -
-
- - -
-
-
-
- -
- - Quality - - - { - if (isExportQuality(value)) { - setQuality(value); - } - }} - > -
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- -
- - Audio - - -
- - setShouldIncludeAudio(!!checked) - } - /> - -
-
-
-
- -
- -
- - )} - - {isExporting && ( -
-
-
-

- {Math.round(progress * 100)}% -

-

100%

-
- -
- - -
- )} -
- - )} -
- ); -} - -function ExportError({ - error, - onRetry, -}: { - error: string; - onRetry: () => void; -}) { - const [copied, setCopied] = useState(false); - - const handleCopy = async () => { - await navigator.clipboard.writeText(error); - setCopied(true); - setTimeout(() => setCopied(false), 1000); - }; - - return ( -
-
-

Export failed

-

{error}

-
- -
- - -
-
- ); -} +"use client"; + +import { useState } from "react"; +import { TransitionTopIcon } from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { Progress } from "@/components/ui/progress"; +import { Checkbox } from "@/components/ui/checkbox"; +import { cn } from "@/utils/ui"; +import { + getExportMimeType, + getExportFileExtension, + downloadBuffer, +} from "@/lib/export"; +import { Check, Copy, Download, RotateCcw } from "lucide-react"; +import { + EXPORT_FORMAT_VALUES, + EXPORT_QUALITY_VALUES, + type ExportFormat, + type ExportQuality, +} from "@/lib/export"; +import { + Section, + SectionContent, + SectionHeader, + SectionTitle, +} from "@/components/section"; +import { useEditor } from "@/hooks/use-editor"; +import { DEFAULT_EXPORT_OPTIONS } from "@/constants/export-constants"; + +function isExportFormat(value: string): value is ExportFormat { + return EXPORT_FORMAT_VALUES.some((formatValue) => formatValue === value); +} + +function isExportQuality(value: string): value is ExportQuality { + return EXPORT_QUALITY_VALUES.some((qualityValue) => qualityValue === value); +} + +export function ExportButton() { + const [isExportPopoverOpen, setIsExportPopoverOpen] = useState(false); + const editor = useEditor(); + const activeProject = useEditor((e) => e.project.getActiveOrNull()); + const hasProject = !!activeProject; + + const handlePopoverOpenChange = ({ open }: { open: boolean }) => { + if (!open) { + editor.project.cancelExport(); + editor.project.clearExportState(); + } + setIsExportPopoverOpen(open); + }; + + return ( + handlePopoverOpenChange({ open })} + > + + + + {hasProject && } + + ); +} + +function ExportPopover({ + onOpenChange, +}: { + onOpenChange: (open: boolean) => void; +}) { + const editor = useEditor(); + const activeProject = useEditor((e) => e.project.getActive()); + const exportState = useEditor((e) => e.project.getExportState()); + const { isExporting, progress, result: exportResult } = exportState; + const [format, setFormat] = useState( + DEFAULT_EXPORT_OPTIONS.format, + ); + const [quality, setQuality] = useState( + DEFAULT_EXPORT_OPTIONS.quality, + ); + const [shouldIncludeAudio, setShouldIncludeAudio] = useState( + DEFAULT_EXPORT_OPTIONS.includeAudio ?? true, + ); + + const handleExport = async () => { + if (!activeProject) return; + + const result = await editor.project.export({ + options: { + format, + quality, + fps: activeProject.settings.fps, + includeAudio: shouldIncludeAudio, + }, + }); + + if (result.cancelled) { + editor.project.clearExportState(); + return; + } + + if (result.success && result.buffer) { + downloadBuffer({ + buffer: result.buffer, + filename: `${activeProject.metadata.name}${getExportFileExtension({ format })}`, + mimeType: getExportMimeType({ format }), + }); + + editor.project.clearExportState(); + onOpenChange(false); + } + }; + + const handleCancel = () => { + editor.project.cancelExport(); + }; + + return ( + + {exportResult && !exportResult.success ? ( + + ) : ( + <> +
+

+ {isExporting ? "Exporting project" : "Export project"} +

+
+ +
+ {!isExporting && ( + <> +
+
+ + Format + + + { + if (isExportFormat(value)) { + setFormat(value); + } + }} + > +
+ + +
+
+ + +
+
+
+
+ +
+ + Quality + + + { + if (isExportQuality(value)) { + setQuality(value); + } + }} + > +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+ + Audio + + +
+ + setShouldIncludeAudio(!!checked) + } + /> + +
+
+
+
+ +
+ +
+ + )} + + {isExporting && ( +
+
+
+

+ {Math.round(progress * 100)}% +

+

100%

+
+ +
+ + +
+ )} +
+ + )} +
+ ); +} + +function ExportError({ + error, + onRetry, +}: { + error: string; + onRetry: () => void; +}) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + await navigator.clipboard.writeText(error); + setCopied(true); + setTimeout(() => setCopied(false), 1000); + }; + + return ( +
+
+

Export failed

+

{error}

+
+ +
+ + +
+
+ ); +} diff --git a/apps/web/src/components/editor/mobile-gate.tsx b/apps/web/src/components/editor/mobile-gate.tsx index 6483e438..3dde2fa7 100644 --- a/apps/web/src/components/editor/mobile-gate.tsx +++ b/apps/web/src/components/editor/mobile-gate.tsx @@ -1,72 +1,72 @@ -"use client"; - -import { useEffect, useState } from "react"; -import Link from "next/link"; -import { Button } from "../ui/button"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { ArrowLeft01Icon, ArrowRight01Icon } from "@hugeicons/core-free-icons"; -import { useRouter } from "next/navigation"; - -const STORAGE_KEY = "mobile-acknowledged"; - -interface MobileGateProps { - children: React.ReactNode; -} - -export function MobileGate({ children }: MobileGateProps) { - const router = useRouter(); - const [show, setShow] = useState(null); - - useEffect(() => { - const isMobile = window.innerWidth < 1024; - const acknowledged = localStorage.getItem(STORAGE_KEY) === "true"; - setShow(isMobile && !acknowledged); - }, []); - - if (show === null) return null; - if (!show) return <>{children}; - - const handleContinue = () => { - localStorage.setItem(STORAGE_KEY, "true"); - setShow(false); - }; - - const handleGoBack = () => { - router.back(); - }; - - return ( -
- - -
-
-

- Desktop only (for now) -

-

- OpenCut isn't optimized for mobile or iPad yet. Things will break - and the layout will be a mess. Come back on a desktop for the real - experience. -

-
-
- - -
-
-
- ); -} +"use client"; + +import { useEffect, useState } from "react"; +import Link from "next/link"; +import { Button } from "../ui/button"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { ArrowLeft01Icon, ArrowRight01Icon } from "@hugeicons/core-free-icons"; +import { useRouter } from "next/navigation"; + +const STORAGE_KEY = "mobile-acknowledged"; + +interface MobileGateProps { + children: React.ReactNode; +} + +export function MobileGate({ children }: MobileGateProps) { + const router = useRouter(); + const [show, setShow] = useState(null); + + useEffect(() => { + const isMobile = window.innerWidth < 1024; + const acknowledged = localStorage.getItem(STORAGE_KEY) === "true"; + setShow(isMobile && !acknowledged); + }, []); + + if (show === null) return null; + if (!show) return <>{children}; + + const handleContinue = () => { + localStorage.setItem(STORAGE_KEY, "true"); + setShow(false); + }; + + const handleGoBack = () => { + router.back(); + }; + + return ( +
+ + +
+
+

+ Desktop only (for now) +

+

+ OpenCut isn't optimized for mobile or iPad yet. Things will break + and the layout will be a mess. Come back on a desktop for the real + experience. +

+
+
+ + +
+
+
+ ); +} diff --git a/apps/web/src/components/editor/panels/assets/drag-overlay.tsx b/apps/web/src/components/editor/panels/assets/drag-overlay.tsx index 9d9ae903..00ccc6e4 100644 --- a/apps/web/src/components/editor/panels/assets/drag-overlay.tsx +++ b/apps/web/src/components/editor/panels/assets/drag-overlay.tsx @@ -1,61 +1,61 @@ -import { HugeiconsIcon } from "@hugeicons/react"; -import { UploadIcon } from "@hugeicons/core-free-icons"; - -interface MediaDragOverlayProps { - isVisible: boolean; - isProcessing?: boolean; - progress?: number; - onClick?: () => void; -} - -export function MediaDragOverlay({ - isVisible, - isProcessing = false, - progress = 0, - onClick, -}: MediaDragOverlayProps) { - if (!isVisible) return null; - - const handleClick = ({ - event, - }: { - event: React.MouseEvent; - }) => { - if (isProcessing || !onClick) return; - event.preventDefault(); - event.stopPropagation(); - onClick(); - }; - - return ( - - ); -} +import { HugeiconsIcon } from "@hugeicons/react"; +import { UploadIcon } from "@hugeicons/core-free-icons"; + +interface MediaDragOverlayProps { + isVisible: boolean; + isProcessing?: boolean; + progress?: number; + onClick?: () => void; +} + +export function MediaDragOverlay({ + isVisible, + isProcessing = false, + progress = 0, + onClick, +}: MediaDragOverlayProps) { + if (!isVisible) return null; + + const handleClick = ({ + event, + }: { + event: React.MouseEvent; + }) => { + if (isProcessing || !onClick) return; + event.preventDefault(); + event.stopPropagation(); + onClick(); + }; + + return ( + + ); +} diff --git a/apps/web/src/components/editor/panels/assets/draggable-item.tsx b/apps/web/src/components/editor/panels/assets/draggable-item.tsx index 8c9acce8..166c81db 100644 --- a/apps/web/src/components/editor/panels/assets/draggable-item.tsx +++ b/apps/web/src/components/editor/panels/assets/draggable-item.tsx @@ -1,239 +1,239 @@ -"use client"; - -import { Plus } from "lucide-react"; -import { type ReactNode, useEffect, useRef, useState } from "react"; -import { createPortal } from "react-dom"; -import { AspectRatio } from "@/components/ui/aspect-ratio"; -import { Button } from "@/components/ui/button"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { useEditor } from "@/hooks/use-editor"; -import { clearDragData, setDragData } from "@/lib/drag-data"; -import type { TimelineDragData } from "@/lib/timeline/drag"; -import { cn } from "@/utils/ui"; - -export interface DraggableItemProps { - name: string; - preview: ReactNode; - dragData: TimelineDragData; - onDragStart?: ({ e }: { e: React.DragEvent }) => void; - onAddToTimeline?: ({ currentTime }: { currentTime: number }) => void; - aspectRatio?: number; - className?: string; - containerClassName?: string; - shouldShowPlusOnDrag?: boolean; - shouldShowLabel?: boolean; - isRounded?: boolean; - variant?: "card" | "compact"; - isDraggable?: boolean; -} - -export function DraggableItem({ - name, - preview, - dragData, - onDragStart, - onAddToTimeline, - aspectRatio = 16 / 9, - className = "", - containerClassName, - shouldShowPlusOnDrag = true, - shouldShowLabel = true, - isRounded = true, - variant = "card", - isDraggable = true, -}: DraggableItemProps) { - const [isDragging, setIsDragging] = useState(false); - const [dragPosition, setDragPosition] = useState({ x: 0, y: 0 }); - const dragRef = useRef(null); - const editor = useEditor(); - - const handleAddToTimeline = () => { - onAddToTimeline?.({ currentTime: editor.playback.getCurrentTime() }); - }; - - const emptyImg = new window.Image(); - emptyImg.src = - "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="; - - useEffect(() => { - if (!isDragging) return; - - const handleDragOver = (e: DragEvent) => { - setDragPosition({ x: e.clientX, y: e.clientY }); - }; - - document.addEventListener("dragover", handleDragOver); - - return () => { - document.removeEventListener("dragover", handleDragOver); - }; - }, [isDragging]); - - const handleDragStart = (e: React.DragEvent) => { - e.dataTransfer.setDragImage(emptyImg, 0, 0); - - setDragData({ dataTransfer: e.dataTransfer, dragData }); - e.dataTransfer.effectAllowed = "copy"; - - setDragPosition({ x: e.clientX, y: e.clientY }); - setIsDragging(true); - - onDragStart?.({ e }); - }; - - const handleDragEnd = () => { - setIsDragging(false); - clearDragData(); - }; - - return ( - <> - {variant === "card" ? ( -
-
- - {preview} - {!isDragging && ( - - )} - - {shouldShowLabel && ( - - {name} - - - )} -
-
- ) : ( -
- -
- )} - - {isDraggable && - isDragging && - typeof document !== "undefined" && - createPortal( -
-
- -
- {preview} -
- {shouldShowPlusOnDrag && ( - - )} -
-
-
, - document.body, - )} - - ); -} - -function PlusButton({ - className, - onClick, - tooltipText, -}: { - className?: string; - onClick?: () => void; - tooltipText?: string; -}) { - const button = ( - - ); - - if (tooltipText) { - return ( - - {button} - -

{tooltipText}

-
-
- ); - } - - return button; -} +"use client"; + +import { Plus } from "lucide-react"; +import { type ReactNode, useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import { AspectRatio } from "@/components/ui/aspect-ratio"; +import { Button } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { useEditor } from "@/hooks/use-editor"; +import { clearDragData, setDragData } from "@/lib/drag-data"; +import type { TimelineDragData } from "@/lib/timeline/drag"; +import { cn } from "@/utils/ui"; + +export interface DraggableItemProps { + name: string; + preview: ReactNode; + dragData: TimelineDragData; + onDragStart?: ({ e }: { e: React.DragEvent }) => void; + onAddToTimeline?: ({ currentTime }: { currentTime: number }) => void; + aspectRatio?: number; + className?: string; + containerClassName?: string; + shouldShowPlusOnDrag?: boolean; + shouldShowLabel?: boolean; + isRounded?: boolean; + variant?: "card" | "compact"; + isDraggable?: boolean; +} + +export function DraggableItem({ + name, + preview, + dragData, + onDragStart, + onAddToTimeline, + aspectRatio = 16 / 9, + className = "", + containerClassName, + shouldShowPlusOnDrag = true, + shouldShowLabel = true, + isRounded = true, + variant = "card", + isDraggable = true, +}: DraggableItemProps) { + const [isDragging, setIsDragging] = useState(false); + const [dragPosition, setDragPosition] = useState({ x: 0, y: 0 }); + const dragRef = useRef(null); + const editor = useEditor(); + + const handleAddToTimeline = () => { + onAddToTimeline?.({ currentTime: editor.playback.getCurrentTime() }); + }; + + const emptyImg = new window.Image(); + emptyImg.src = + "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="; + + useEffect(() => { + if (!isDragging) return; + + const handleDragOver = (e: DragEvent) => { + setDragPosition({ x: e.clientX, y: e.clientY }); + }; + + document.addEventListener("dragover", handleDragOver); + + return () => { + document.removeEventListener("dragover", handleDragOver); + }; + }, [isDragging]); + + const handleDragStart = (e: React.DragEvent) => { + e.dataTransfer.setDragImage(emptyImg, 0, 0); + + setDragData({ dataTransfer: e.dataTransfer, dragData }); + e.dataTransfer.effectAllowed = "copy"; + + setDragPosition({ x: e.clientX, y: e.clientY }); + setIsDragging(true); + + onDragStart?.({ e }); + }; + + const handleDragEnd = () => { + setIsDragging(false); + clearDragData(); + }; + + return ( + <> + {variant === "card" ? ( +
+
+ + {preview} + {!isDragging && ( + + )} + + {shouldShowLabel && ( + + {name} + + + )} +
+
+ ) : ( +
+ +
+ )} + + {isDraggable && + isDragging && + typeof document !== "undefined" && + createPortal( +
+
+ +
+ {preview} +
+ {shouldShowPlusOnDrag && ( + + )} +
+
+
, + document.body, + )} + + ); +} + +function PlusButton({ + className, + onClick, + tooltipText, +}: { + className?: string; + onClick?: () => void; + tooltipText?: string; +}) { + const button = ( + + ); + + if (tooltipText) { + return ( + + {button} + +

{tooltipText}

+
+
+ ); + } + + return button; +} diff --git a/apps/web/src/components/editor/panels/assets/tabbar.tsx b/apps/web/src/components/editor/panels/assets/tabbar.tsx index a4e072f1..da53bed0 100644 --- a/apps/web/src/components/editor/panels/assets/tabbar.tsx +++ b/apps/web/src/components/editor/panels/assets/tabbar.tsx @@ -1,111 +1,111 @@ -"use client"; - -import { useCallback, useEffect, useRef, useState } from "react"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/utils/ui"; -import { - TAB_KEYS, - tabs, - useAssetsPanelStore, -} from "@/stores/assets-panel-store"; - -export function TabBar() { - const { activeTab, setActiveTab } = useAssetsPanelStore(); - const [showTopFade, setShowTopFade] = useState(false); - const [showBottomFade, setShowBottomFade] = useState(false); - const scrollRef = useRef(null); - - const checkScrollPosition = useCallback(() => { - const element = scrollRef.current; - if (!element) return; - - const { scrollTop, scrollHeight, clientHeight } = element; - setShowTopFade(scrollTop > 0); - setShowBottomFade(scrollTop < scrollHeight - clientHeight - 1); - }, []); - - useEffect(() => { - const element = scrollRef.current; - if (!element) return; - - checkScrollPosition(); - element.addEventListener("scroll", checkScrollPosition); - - const resizeObserver = new ResizeObserver(checkScrollPosition); - resizeObserver.observe(element); - - return () => { - element.removeEventListener("scroll", checkScrollPosition); - resizeObserver.disconnect(); - }; - }, [checkScrollPosition]); - - return ( -
-
- {TAB_KEYS.map((tabKey) => { - const tab = tabs[tabKey]; - return ( - - - - - -
- {tab.label} -
-
-
- ); - })} -
- - - -
- ); -} - -function FadeOverlay({ - direction, - show, -}: { - direction: "top" | "bottom"; - show: boolean; -}) { - return ( -
- ); -} +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/utils/ui"; +import { + TAB_KEYS, + tabs, + useAssetsPanelStore, +} from "@/stores/assets-panel-store"; + +export function TabBar() { + const { activeTab, setActiveTab } = useAssetsPanelStore(); + const [showTopFade, setShowTopFade] = useState(false); + const [showBottomFade, setShowBottomFade] = useState(false); + const scrollRef = useRef(null); + + const checkScrollPosition = useCallback(() => { + const element = scrollRef.current; + if (!element) return; + + const { scrollTop, scrollHeight, clientHeight } = element; + setShowTopFade(scrollTop > 0); + setShowBottomFade(scrollTop < scrollHeight - clientHeight - 1); + }, []); + + useEffect(() => { + const element = scrollRef.current; + if (!element) return; + + checkScrollPosition(); + element.addEventListener("scroll", checkScrollPosition); + + const resizeObserver = new ResizeObserver(checkScrollPosition); + resizeObserver.observe(element); + + return () => { + element.removeEventListener("scroll", checkScrollPosition); + resizeObserver.disconnect(); + }; + }, [checkScrollPosition]); + + return ( +
+
+ {TAB_KEYS.map((tabKey) => { + const tab = tabs[tabKey]; + return ( + + + + + +
+ {tab.label} +
+
+
+ ); + })} +
+ + + +
+ ); +} + +function FadeOverlay({ + direction, + show, +}: { + direction: "top" | "bottom"; + show: boolean; +}) { + return ( +
+ ); +} diff --git a/apps/web/src/components/editor/panels/assets/views/base-panel.tsx b/apps/web/src/components/editor/panels/assets/views/base-panel.tsx index 0ece9b19..869304cd 100644 --- a/apps/web/src/components/editor/panels/assets/views/base-panel.tsx +++ b/apps/web/src/components/editor/panels/assets/views/base-panel.tsx @@ -1,57 +1,57 @@ -import { cn } from "@/utils/ui"; - -interface PanelViewProps extends React.HTMLAttributes { - title?: string; - actions?: React.ReactNode; - children: React.ReactNode; - contentClassName?: string; - scrollClassName?: string; - hideHeader?: boolean; - ref?: React.Ref; - onScroll?: React.UIEventHandler; - scrollRef?: React.Ref; -} - -export function PanelView({ - title, - actions, - children, - className, - contentClassName, - scrollClassName, - hideHeader = false, - ref, - onScroll, - scrollRef, - ...rest -}: PanelViewProps) { - return ( -
- {!hideHeader && ( -
- {title && ( - {title} - )} - {actions} -
- )} -
-
- {children} -
-
-
- ); -} +import { cn } from "@/utils/ui"; + +interface PanelViewProps extends React.HTMLAttributes { + title?: string; + actions?: React.ReactNode; + children: React.ReactNode; + contentClassName?: string; + scrollClassName?: string; + hideHeader?: boolean; + ref?: React.Ref; + onScroll?: React.UIEventHandler; + scrollRef?: React.Ref; +} + +export function PanelView({ + title, + actions, + children, + className, + contentClassName, + scrollClassName, + hideHeader = false, + ref, + onScroll, + scrollRef, + ...rest +}: PanelViewProps) { + return ( +
+ {!hideHeader && ( +
+ {title && ( + {title} + )} + {actions} +
+ )} +
+
+ {children} +
+
+
+ ); +} diff --git a/apps/web/src/components/editor/panels/assets/views/captions.tsx b/apps/web/src/components/editor/panels/assets/views/captions.tsx index 023e1578..f22c0211 100644 --- a/apps/web/src/components/editor/panels/assets/views/captions.tsx +++ b/apps/web/src/components/editor/panels/assets/views/captions.tsx @@ -1,179 +1,179 @@ -import { Button } from "@/components/ui/button"; -import { PanelView } from "@/components/editor/panels/assets/views/base-panel"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { useState, useRef } from "react"; -import { extractTimelineAudio } from "@/lib/media/mediabunny"; -import { useEditor } from "@/hooks/use-editor"; -import { - DEFAULT_TRANSCRIPTION_SAMPLE_RATE, - TRANSCRIPTION_LANGUAGES, -} from "@/constants/transcription-constants"; -import type { - TranscriptionLanguage, - TranscriptionProgress, -} from "@/lib/transcription/types"; -import { transcriptionService } from "@/services/transcription/service"; -import { decodeAudioToFloat32 } from "@/lib/media/audio"; -import { buildCaptionChunks } from "@/lib/transcription/caption"; -import { Spinner } from "@/components/ui/spinner"; -import { - Section, - SectionContent, - SectionField, - SectionFields, -} from "@/components/section"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import { - AddTrackCommand, - BatchCommand, - InsertElementCommand, -} from "@/lib/commands"; - -export function Captions() { - const [selectedLanguage, setSelectedLanguage] = - useState("auto"); - const [isProcessing, setIsProcessing] = useState(false); - const [processingStep, setProcessingStep] = useState(""); - const [error, setError] = useState(null); - const containerRef = useRef(null); - const editor = useEditor(); - - const handleProgress = (progress: TranscriptionProgress) => { - if (progress.status === "loading-model") { - setProcessingStep(`Loading model ${Math.round(progress.progress)}%`); - } else if (progress.status === "transcribing") { - setProcessingStep("Transcribing..."); - } - }; - - const handleGenerateTranscript = async () => { - try { - setIsProcessing(true); - setError(null); - setProcessingStep("Extracting audio..."); - - const audioBlob = await extractTimelineAudio({ - tracks: editor.timeline.getTracks(), - mediaAssets: editor.media.getAssets(), - totalDuration: editor.timeline.getTotalDuration(), - }); - - setProcessingStep("Preparing audio..."); - const { samples } = await decodeAudioToFloat32({ - audioBlob, - sampleRate: DEFAULT_TRANSCRIPTION_SAMPLE_RATE, - }); - - const result = await transcriptionService.transcribe({ - audioData: samples, - language: selectedLanguage === "auto" ? undefined : selectedLanguage, - onProgress: handleProgress, - }); - - setProcessingStep("Generating captions..."); - const captionChunks = buildCaptionChunks({ segments: result.segments }); - - const addTrackCommand = new AddTrackCommand("text", 0); - const insertCommands = captionChunks.map( - (caption, i) => - new InsertElementCommand({ - placement: { - mode: "explicit", - trackId: addTrackCommand.getTrackId(), - }, - element: { - ...DEFAULTS.text.element, - name: `Caption ${i + 1}`, - content: caption.text, - duration: caption.duration, - startTime: caption.startTime, - fontSize: 65, - fontWeight: "bold", - }, - }), - ); - - editor.command.execute({ - command: new BatchCommand([addTrackCommand, ...insertCommands]), - }); - } catch (error) { - console.error("Transcription failed:", error); - setError( - error instanceof Error ? error.message : "An unexpected error occurred", - ); - } finally { - setIsProcessing(false); - setProcessingStep(""); - } - }; - - const handleLanguageChange = ({ value }: { value: string }) => { - if (value === "auto") { - setSelectedLanguage("auto"); - return; - } - - const matchedLanguage = TRANSCRIPTION_LANGUAGES.find( - (language) => language.code === value, - ); - if (!matchedLanguage) return; - setSelectedLanguage(matchedLanguage.code); - }; - - return ( - -
- - - - - - - - {error && ( -
-

{error}

-
- )} -
-
-
- - - -
-
- ); -} +import { Button } from "@/components/ui/button"; +import { PanelView } from "@/components/editor/panels/assets/views/base-panel"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { useState, useRef } from "react"; +import { extractTimelineAudio } from "@/lib/media/mediabunny"; +import { useEditor } from "@/hooks/use-editor"; +import { + DEFAULT_TRANSCRIPTION_SAMPLE_RATE, + TRANSCRIPTION_LANGUAGES, +} from "@/constants/transcription-constants"; +import type { + TranscriptionLanguage, + TranscriptionProgress, +} from "@/lib/transcription/types"; +import { transcriptionService } from "@/services/transcription/service"; +import { decodeAudioToFloat32 } from "@/lib/media/audio"; +import { buildCaptionChunks } from "@/lib/transcription/caption"; +import { Spinner } from "@/components/ui/spinner"; +import { + Section, + SectionContent, + SectionField, + SectionFields, +} from "@/components/section"; +import { DEFAULTS } from "@/lib/timeline/defaults"; +import { + AddTrackCommand, + BatchCommand, + InsertElementCommand, +} from "@/lib/commands"; + +export function Captions() { + const [selectedLanguage, setSelectedLanguage] = + useState("auto"); + const [isProcessing, setIsProcessing] = useState(false); + const [processingStep, setProcessingStep] = useState(""); + const [error, setError] = useState(null); + const containerRef = useRef(null); + const editor = useEditor(); + + const handleProgress = (progress: TranscriptionProgress) => { + if (progress.status === "loading-model") { + setProcessingStep(`Loading model ${Math.round(progress.progress)}%`); + } else if (progress.status === "transcribing") { + setProcessingStep("Transcribing..."); + } + }; + + const handleGenerateTranscript = async () => { + try { + setIsProcessing(true); + setError(null); + setProcessingStep("Extracting audio..."); + + const audioBlob = await extractTimelineAudio({ + tracks: editor.timeline.getTracks(), + mediaAssets: editor.media.getAssets(), + totalDuration: editor.timeline.getTotalDuration(), + }); + + setProcessingStep("Preparing audio..."); + const { samples } = await decodeAudioToFloat32({ + audioBlob, + sampleRate: DEFAULT_TRANSCRIPTION_SAMPLE_RATE, + }); + + const result = await transcriptionService.transcribe({ + audioData: samples, + language: selectedLanguage === "auto" ? undefined : selectedLanguage, + onProgress: handleProgress, + }); + + setProcessingStep("Generating captions..."); + const captionChunks = buildCaptionChunks({ segments: result.segments }); + + const addTrackCommand = new AddTrackCommand("text", 0); + const insertCommands = captionChunks.map( + (caption, i) => + new InsertElementCommand({ + placement: { + mode: "explicit", + trackId: addTrackCommand.getTrackId(), + }, + element: { + ...DEFAULTS.text.element, + name: `Caption ${i + 1}`, + content: caption.text, + duration: caption.duration, + startTime: caption.startTime, + fontSize: 65, + fontWeight: "bold", + }, + }), + ); + + editor.command.execute({ + command: new BatchCommand([addTrackCommand, ...insertCommands]), + }); + } catch (error) { + console.error("Transcription failed:", error); + setError( + error instanceof Error ? error.message : "An unexpected error occurred", + ); + } finally { + setIsProcessing(false); + setProcessingStep(""); + } + }; + + const handleLanguageChange = ({ value }: { value: string }) => { + if (value === "auto") { + setSelectedLanguage("auto"); + return; + } + + const matchedLanguage = TRANSCRIPTION_LANGUAGES.find( + (language) => language.code === value, + ); + if (!matchedLanguage) return; + setSelectedLanguage(matchedLanguage.code); + }; + + return ( + +
+ + + + + + + + {error && ( +
+

{error}

+
+ )} +
+
+
+ + + +
+
+ ); +} diff --git a/apps/web/src/components/editor/panels/assets/views/settings/background.tsx b/apps/web/src/components/editor/panels/assets/views/settings/background.tsx index b3ef1eb1..7a81188f 100644 --- a/apps/web/src/components/editor/panels/assets/views/settings/background.tsx +++ b/apps/web/src/components/editor/panels/assets/views/settings/background.tsx @@ -1,227 +1,227 @@ -"use client"; - -import { memo, useCallback, useEffect, useMemo, useRef } from "react"; -import { - Section, - SectionContent, - SectionHeader, - SectionTitle, -} from "@/components/section"; -import { - BLUR_INTENSITY_PRESETS, - DEFAULT_BLUR_INTENSITY, - DEFAULT_COLOR, -} from "@/constants/project-constants"; -import { patternCraftGradients } from "@/data/colors/pattern-craft"; -import { colors } from "@/data/colors/solid"; -import { syntaxUIGradients } from "@/data/colors/syntax-ui"; -import { useEditor } from "@/hooks/use-editor"; -import { effectPreviewService } from "@/services/renderer/effect-preview"; -import type { TCanvasSize } from "@/lib/project/types"; -import { cn } from "@/utils/ui"; - -const BlurPreview = memo( - ({ - blur, - canvasSize, - isSelected, - onSelect, - }: { - blur: { label: string; value: number }; - canvasSize: TCanvasSize; - isSelected: boolean; - onSelect: () => void; - }) => { - const canvasRef = useRef(null); - const { width, height } = canvasSize; - - useEffect(() => { - const renderPreview = () => { - if (!canvasRef.current) return; - - effectPreviewService.renderPreview({ - effectType: "blur", - params: { intensity: blur.value }, - targetCanvas: canvasRef.current, - uniformDimensions: { width, height }, - }); - }; - - renderPreview(); - return effectPreviewService.onPreviewImageReady({ callback: renderPreview }); - }, [blur.value, width, height]); - - return ( - - ); - }, -); - -BlurPreview.displayName = "BlurPreview"; - -const BackgroundPreviews = memo( - ({ - backgrounds, - currentBackgroundColor, - isColorBackground, - onSelect, - useBackgroundColor = false, - }: { - backgrounds: string[]; - currentBackgroundColor: string; - isColorBackground: boolean; - onSelect: (bg: string) => void; - useBackgroundColor?: boolean; - }) => { - return useMemo( - () => - backgrounds.map((bg) => ( - + ); + }, +); + +BlurPreview.displayName = "BlurPreview"; + +const BackgroundPreviews = memo( + ({ + backgrounds, + currentBackgroundColor, + isColorBackground, + onSelect, + useBackgroundColor = false, + }: { + backgrounds: string[]; + currentBackgroundColor: string; + isColorBackground: boolean; + onSelect: (bg: string) => void; + useBackgroundColor?: boolean; + }) => { + return useMemo( + () => + backgrounds.map((bg) => ( + - ); -} - -function AspectRatioPreview({ ratio }: { ratio?: string }) { - if (!ratio) return null; - - const [w, h] = ratio.split(":").map(Number); - const maxSize = 16; - const width = w >= h ? maxSize : (w / h) * maxSize; - const height = h >= w ? maxSize : (h / w) * maxSize; - - return ( -
- ); -} +"use client"; + +import { useRef, useState } from "react"; +import { PanelView } from "@/components/editor/panels/assets/views/base-panel"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { FPS_PRESETS } from "@/constants/project-constants"; +import { useEditor } from "@/hooks/use-editor"; +import { + Section, + SectionContent, + SectionHeader, + SectionTitle, +} from "@/components/section"; +import { BackgroundContent } from "./background"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Button } from "@/components/ui/button"; +import { NumberField } from "@/components/ui/number-field"; +import { useEditorStore } from "@/stores/editor-store"; +import { usePropertyDraft } from "@/components/editor/panels/properties/hooks/use-property-draft"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { Tick02Icon } from "@hugeicons/core-free-icons"; +import { cn } from "@/utils/ui"; +import { dimensionToAspectRatio } from "@/utils/geometry"; +import { formatNumberForDisplay } from "@/utils/math"; +import { OcSquarePlusIcon } from "@opencut/ui/icons"; +import type { TCanvasSize } from "@/lib/project/types"; + +type SettingsView = "project-info" | "background"; + +const PRESET_LABELS: Record = { + "1:1": "1:1", + "16:9": "16:9", + "9:16": "9:16", + "4:3": "4:3", +}; + +function areCanvasSizesEqual({ + left, + right, +}: { + left: TCanvasSize; + right: TCanvasSize; +}) { + return left.width === right.width && left.height === right.height; +} + +function formatCanvasDimension({ value }: { value: number }) { + return formatNumberForDisplay({ value, maxFractionDigits: 0 }); +} + +function parseCanvasDimension({ input }: { input: string }): number | null { + const trimmed = input.trim(); + if (!trimmed) return null; + + const parsed = Number(trimmed); + if (!Number.isFinite(parsed)) return null; + + const rounded = Math.round(parsed); + return rounded > 0 ? rounded : null; +} + +function useCanvasDimensionDraft({ + value, + onCommit, +}: { + value: number; + onCommit: (value: number) => void; +}) { + const pendingValueRef = useRef(value); + const syncedValueRef = useRef(value); + + if (syncedValueRef.current !== value) { + syncedValueRef.current = value; + pendingValueRef.current = value; + } + + return usePropertyDraft({ + displayValue: formatCanvasDimension({ value }), + parse: (input) => parseCanvasDimension({ input }), + onPreview: (nextValue) => { + pendingValueRef.current = nextValue; + }, + onCommit: () => { + if (pendingValueRef.current !== value) { + onCommit(pendingValueRef.current); + } + }, + }); +} + +export function SettingsView() { + const [view, setView] = useState("project-info"); + const editor = useEditor(); + const activeProject = useEditor((e) => e.project.getActive()); + const { canvasPresets } = useEditorStore(); + const currentCanvasSize = activeProject.settings.canvasSize; + const canvasSizeMode = activeProject.settings.canvasSizeMode ?? "preset"; + const lastCustomCanvasSize = + activeProject.settings.lastCustomCanvasSize ?? null; + + const presetItems = canvasPresets.map((preset, index) => { + const ratio = dimensionToAspectRatio(preset); + return { + id: index.toString(), + label: PRESET_LABELS[ratio] ?? ratio, + ratio, + canvasSize: preset, + }; + }); + + const selectedPresetId = canvasSizeMode === "preset" + ? (presetItems.find((preset) => + areCanvasSizesEqual({ + left: preset.canvasSize, + right: currentCanvasSize, + }), + )?.id ?? null) + : null; + + const updateCustomCanvasSize = ({ + canvasSize, + }: { + canvasSize: TCanvasSize; + }) => { + const shouldUpdateCanvasSize = !areCanvasSizesEqual({ + left: canvasSize, + right: currentCanvasSize, + }); + const shouldUpdateLastCustomCanvasSize = + lastCustomCanvasSize === null || + !areCanvasSizesEqual({ + left: canvasSize, + right: lastCustomCanvasSize, + }); + const shouldUpdateCanvasSizeMode = canvasSizeMode !== "custom"; + + if ( + !shouldUpdateCanvasSize && + !shouldUpdateLastCustomCanvasSize && + !shouldUpdateCanvasSizeMode + ) { + return; + } + + editor.project.updateSettings({ + settings: { + ...(shouldUpdateCanvasSize ? { canvasSize } : {}), + ...(shouldUpdateCanvasSizeMode + ? { canvasSizeMode: "custom" as const } + : {}), + lastCustomCanvasSize: canvasSize, + }, + }); + }; + + const selectPresetCanvasSize = ({ + canvasSize, + }: { + canvasSize: TCanvasSize; + }) => { + const shouldUpdateCanvasSize = !areCanvasSizesEqual({ + left: canvasSize, + right: currentCanvasSize, + }); + const shouldUpdateCanvasSizeMode = canvasSizeMode !== "preset"; + + if (!shouldUpdateCanvasSize && !shouldUpdateCanvasSizeMode) return; + + editor.project.updateSettings({ + settings: { + ...(shouldUpdateCanvasSize ? { canvasSize } : {}), + ...(shouldUpdateCanvasSizeMode + ? { canvasSizeMode: "preset" as const } + : {}), + }, + }); + }; + + const selectCustomCanvasSize = () => { + updateCustomCanvasSize({ + canvasSize: lastCustomCanvasSize ?? currentCanvasSize, + }); + }; + + const widthDraft = useCanvasDimensionDraft({ + value: currentCanvasSize.width, + onCommit: (width) => + updateCustomCanvasSize({ + canvasSize: { width, height: currentCanvasSize.height }, + }), + }); + + const heightDraft = useCanvasDimensionDraft({ + value: currentCanvasSize.height, + onCommit: (height) => + updateCustomCanvasSize({ + canvasSize: { width: currentCanvasSize.width, height }, + }), + }); + + const isCustomSelected = canvasSizeMode === "custom"; + + return ( + setView(v as SettingsView)}> + + Project info + Background + + + } + > + {view === "project-info" && ( +
+
+ + Name + + {activeProject.metadata.name} + + +
+
+ + Frame rate + + +
+
+ + Aspect ratio + + + {presetItems.map((preset) => ( + } + isSelected={selectedPresetId === preset.id} + onClick={() => { + selectPresetCanvasSize({ + canvasSize: preset.canvasSize, + }); + }} + /> + ))} +
+ } + isSelected={isCustomSelected} + onClick={selectCustomCanvasSize} + uiOptions={ +
+ + +
+ } + /> +
+
+
+
+ )} + {view === "background" && } +
+ ); +} + +function AspectRatioItem({ + label, + previewIcon, + isSelected, + onClick, + uiOptions, +}: { + label: string; + previewIcon: React.ReactNode; + isSelected: boolean; + onClick: () => void; + uiOptions?: React.ReactNode; +}) { + return ( + + ); +} + +function AspectRatioPreview({ ratio }: { ratio?: string }) { + if (!ratio) return null; + + const [w, h] = ratio.split(":").map(Number); + const maxSize = 16; + const width = w >= h ? maxSize : (w / h) * maxSize; + const height = h >= w ? maxSize : (h / w) * maxSize; + + return ( +
+ ); +} diff --git a/apps/web/src/components/editor/panels/assets/views/sounds.tsx b/apps/web/src/components/editor/panels/assets/views/sounds.tsx index 4fb43ac2..dc92ad9c 100644 --- a/apps/web/src/components/editor/panels/assets/views/sounds.tsx +++ b/apps/web/src/components/editor/panels/assets/views/sounds.tsx @@ -1,574 +1,574 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { Separator } from "@/components/ui/separator"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { useInfiniteScroll } from "@/hooks/use-infinite-scroll"; -import { useSoundSearch } from "@/hooks/use-sound-search"; -import { useSoundsStore } from "@/stores/sounds-store"; -import type { SavedSound, SoundEffect } from "@/lib/sounds/types"; -import { cn } from "@/utils/ui"; -import { - FavouriteIcon, - FilterMailIcon, - PauseIcon, - PlayIcon, - PlusSignIcon, -} from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/react"; - -export function SoundsView() { - return ( -
- -
- - Sound effects - Songs - Saved - -
- - - - - - - - - - -
-
- ); -} - -function SoundEffectsView() { - const { - topSoundEffects, - isLoading, - searchQuery, - setSearchQuery, - scrollPosition, - setScrollPosition, - loadSavedSounds, - showCommercialOnly, - toggleCommercialFilter, - hasLoaded, - setTopSoundEffects, - setLoading, - setError, - setHasLoaded, - setCurrentPage, - setHasNextPage, - setTotalCount, - } = useSoundsStore(); - const { - results: searchResults, - isLoading: isSearching, - loadMore, - hasNextPage, - isLoadingMore, - } = useSoundSearch({ - query: searchQuery, - commercialOnly: showCommercialOnly, - }); - - const [playingId, setPlayingId] = useState(null); - const [audioElement, setAudioElement] = useState( - null, - ); - - const { scrollAreaRef, handleScroll } = useInfiniteScroll({ - onLoadMore: loadMore, - hasMore: hasNextPage, - isLoading: isLoadingMore || isSearching, - }); - - useEffect(() => { - loadSavedSounds(); - }, [loadSavedSounds]); - - useEffect(() => { - if (hasLoaded) { - return; - } - - let shouldIgnore = false; - - const fetchTopSounds = async () => { - try { - if (!shouldIgnore) { - setLoading({ loading: true }); - setError({ error: null }); - } - - const response = await fetch( - "/api/sounds/search?page_size=50&sort=downloads", - ); - - if (!shouldIgnore) { - if (!response.ok) { - throw new Error(`Failed to fetch: ${response.status}`); - } - - const data = await response.json(); - setTopSoundEffects({ sounds: data.results }); - setHasLoaded({ loaded: true }); - - setCurrentPage({ page: 1 }); - setHasNextPage({ hasNext: !!data.next }); - setTotalCount({ count: data.count }); - } - } catch (error) { - if (!shouldIgnore) { - console.error("Failed to fetch top sounds:", error); - setError({ - error: - error instanceof Error ? error.message : "Failed to load sounds", - }); - } - } finally { - if (!shouldIgnore) { - setLoading({ loading: false }); - } - } - }; - - const timeoutId = setTimeout(fetchTopSounds, 100, {}); - - return () => { - shouldIgnore = true; - clearTimeout(timeoutId); - }; - }, [ - hasLoaded, - setTopSoundEffects, - setLoading, - setError, - setHasLoaded, - setCurrentPage, - setHasNextPage, - setTotalCount, - ]); - - useEffect(() => { - if (!scrollAreaRef.current || scrollPosition <= 0) { - return; - } - - const restoreScrollPosition = () => { - scrollAreaRef.current?.scrollTo({ top: scrollPosition }); - }; - - const timeoutId = setTimeout(restoreScrollPosition, 100, {}); - - return () => clearTimeout(timeoutId); - }, [scrollPosition, scrollAreaRef]); - - const handleScrollWithPosition = ({ - currentTarget, - }: React.UIEvent) => { - const { scrollTop } = currentTarget; - setScrollPosition({ position: scrollTop }); - handleScroll({ currentTarget } as React.UIEvent); - }; - - const displayedSounds = searchQuery ? searchResults : topSoundEffects; - - const playSound = ({ sound }: { sound: SoundEffect }) => { - if (playingId === sound.id) { - audioElement?.pause(); - setPlayingId(null); - return; - } - - audioElement?.pause(); - - if (sound.previewUrl) { - const audio = new Audio(sound.previewUrl); - audio.addEventListener("ended", () => { - setPlayingId(null); - }); - audio.addEventListener("error", () => { - setPlayingId(null); - }); - audio.play().catch((error) => { - console.error("Failed to play sound preview:", error); - setPlayingId(null); - }); - - setAudioElement(audio); - setPlayingId(sound.id); - } - }; - - return ( -
-
- - setSearchQuery({ query: currentTarget.value }) - } - showClearIcon - onClear={() => setSearchQuery({ query: "" })} - /> - - - - - - toggleCommercialFilter()} - > - Show only commercially licensed - -
- {showCommercialOnly - ? "Only showing sounds licensed for commercial use" - : "Showing all sounds regardless of license"} -
-
-
-
- -
- -
- {isLoading && !searchQuery && ( -
- Loading sounds... -
- )} - {isSearching && searchQuery && ( -
Searching...
- )} - {displayedSounds.map((sound) => ( - - ))} - {!isLoading && !isSearching && displayedSounds.length === 0 && ( -
- {searchQuery ? "No sounds found" : "No sounds available"} -
- )} - {isLoadingMore && ( -
- Loading more sounds... -
- )} -
-
-
-
- ); -} - -function SavedSoundsView() { - const { - savedSounds, - isLoadingSavedSounds, - savedSoundsError, - loadSavedSounds, - clearSavedSounds, - } = useSoundsStore(); - - const [playingId, setPlayingId] = useState(null); - const [audioElement, setAudioElement] = useState( - null, - ); - - const [showClearDialog, setShowClearDialog] = useState(false); - - useEffect(() => { - loadSavedSounds(); - }, [loadSavedSounds]); - - const playSound = ({ sound }: { sound: SoundEffect }) => { - if (playingId === sound.id) { - audioElement?.pause(); - setPlayingId(null); - return; - } - - audioElement?.pause(); - - if (sound.previewUrl) { - const audio = new Audio(sound.previewUrl); - audio.addEventListener("ended", () => { - setPlayingId(null); - }); - audio.addEventListener("error", () => { - setPlayingId(null); - }); - audio.play().catch((error) => { - console.error("Failed to play sound preview:", error); - setPlayingId(null); - }); - - setAudioElement(audio); - setPlayingId(sound.id); - } - }; - - const convertToSoundEffect = ({ - savedSound, - }: { - savedSound: SavedSound; - }): SoundEffect => ({ - id: savedSound.id, - name: savedSound.name, - description: "", - url: "", - previewUrl: savedSound.previewUrl, - downloadUrl: savedSound.downloadUrl, - duration: savedSound.duration, - filesize: 0, - type: "audio", - channels: 0, - bitrate: 0, - bitdepth: 0, - samplerate: 0, - username: savedSound.username, - tags: savedSound.tags, - license: savedSound.license, - created: savedSound.savedAt, - downloads: 0, - rating: 0, - ratingCount: 0, - }); - - if (isLoadingSavedSounds) { - return ( -
-
- Loading saved sounds... -
-
- ); - } - - if (savedSoundsError) { - return ( -
-
- Error: {savedSoundsError} -
-
- ); - } - - if (savedSounds.length === 0) { - return ( -
- -
-

No saved sounds

-

- Click the heart icon on any sound to save it here -

-
-
- ); - } - - return ( -
-
-

- {savedSounds.length} saved{" "} - {savedSounds.length === 1 ? "sound" : "sounds"} -

- - - - - - - Clear all saved sounds? - - This will permanently remove all {savedSounds.length} saved - sounds from your collection. This action cannot be undone. - - - - - - - - -
- -
- -
- {savedSounds.map((sound) => ( - - ))} -
-
-
-
- ); -} - -function SongsView() { - return
Songs
; -} - -interface AudioItemProps { - sound: SoundEffect; - isPlaying: boolean; - onPlay: ({ sound }: { sound: SoundEffect }) => void; -} - -function AudioItem({ sound, isPlaying, onPlay }: AudioItemProps) { - const { addSoundToTimeline, isSoundSaved, toggleSavedSound } = - useSoundsStore(); - const isSaved = isSoundSaved({ soundId: sound.id }); - - const handleClick = () => { - onPlay({ sound }); - }; - - const handleSaveClick = ({ - stopPropagation, - }: React.MouseEvent) => { - stopPropagation(); - toggleSavedSound({ soundEffect: sound }); - }; - - const handleAddToTimeline = async ({ - stopPropagation, - }: React.MouseEvent) => { - stopPropagation(); - await addSoundToTimeline({ sound }); - }; - - return ( -
- - -
- - -
-
- ); -} +"use client"; + +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Separator } from "@/components/ui/separator"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { useInfiniteScroll } from "@/hooks/use-infinite-scroll"; +import { useSoundSearch } from "@/hooks/use-sound-search"; +import { useSoundsStore } from "@/stores/sounds-store"; +import type { SavedSound, SoundEffect } from "@/lib/sounds/types"; +import { cn } from "@/utils/ui"; +import { + FavouriteIcon, + FilterMailIcon, + PauseIcon, + PlayIcon, + PlusSignIcon, +} from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; + +export function SoundsView() { + return ( +
+ +
+ + Sound effects + Songs + Saved + +
+ + + + + + + + + + +
+
+ ); +} + +function SoundEffectsView() { + const { + topSoundEffects, + isLoading, + searchQuery, + setSearchQuery, + scrollPosition, + setScrollPosition, + loadSavedSounds, + showCommercialOnly, + toggleCommercialFilter, + hasLoaded, + setTopSoundEffects, + setLoading, + setError, + setHasLoaded, + setCurrentPage, + setHasNextPage, + setTotalCount, + } = useSoundsStore(); + const { + results: searchResults, + isLoading: isSearching, + loadMore, + hasNextPage, + isLoadingMore, + } = useSoundSearch({ + query: searchQuery, + commercialOnly: showCommercialOnly, + }); + + const [playingId, setPlayingId] = useState(null); + const [audioElement, setAudioElement] = useState( + null, + ); + + const { scrollAreaRef, handleScroll } = useInfiniteScroll({ + onLoadMore: loadMore, + hasMore: hasNextPage, + isLoading: isLoadingMore || isSearching, + }); + + useEffect(() => { + loadSavedSounds(); + }, [loadSavedSounds]); + + useEffect(() => { + if (hasLoaded) { + return; + } + + let shouldIgnore = false; + + const fetchTopSounds = async () => { + try { + if (!shouldIgnore) { + setLoading({ loading: true }); + setError({ error: null }); + } + + const response = await fetch( + "/api/sounds/search?page_size=50&sort=downloads", + ); + + if (!shouldIgnore) { + if (!response.ok) { + throw new Error(`Failed to fetch: ${response.status}`); + } + + const data = await response.json(); + setTopSoundEffects({ sounds: data.results }); + setHasLoaded({ loaded: true }); + + setCurrentPage({ page: 1 }); + setHasNextPage({ hasNext: !!data.next }); + setTotalCount({ count: data.count }); + } + } catch (error) { + if (!shouldIgnore) { + console.error("Failed to fetch top sounds:", error); + setError({ + error: + error instanceof Error ? error.message : "Failed to load sounds", + }); + } + } finally { + if (!shouldIgnore) { + setLoading({ loading: false }); + } + } + }; + + const timeoutId = setTimeout(fetchTopSounds, 100, {}); + + return () => { + shouldIgnore = true; + clearTimeout(timeoutId); + }; + }, [ + hasLoaded, + setTopSoundEffects, + setLoading, + setError, + setHasLoaded, + setCurrentPage, + setHasNextPage, + setTotalCount, + ]); + + useEffect(() => { + if (!scrollAreaRef.current || scrollPosition <= 0) { + return; + } + + const restoreScrollPosition = () => { + scrollAreaRef.current?.scrollTo({ top: scrollPosition }); + }; + + const timeoutId = setTimeout(restoreScrollPosition, 100, {}); + + return () => clearTimeout(timeoutId); + }, [scrollPosition, scrollAreaRef]); + + const handleScrollWithPosition = ({ + currentTarget, + }: React.UIEvent) => { + const { scrollTop } = currentTarget; + setScrollPosition({ position: scrollTop }); + handleScroll({ currentTarget } as React.UIEvent); + }; + + const displayedSounds = searchQuery ? searchResults : topSoundEffects; + + const playSound = ({ sound }: { sound: SoundEffect }) => { + if (playingId === sound.id) { + audioElement?.pause(); + setPlayingId(null); + return; + } + + audioElement?.pause(); + + if (sound.previewUrl) { + const audio = new Audio(sound.previewUrl); + audio.addEventListener("ended", () => { + setPlayingId(null); + }); + audio.addEventListener("error", () => { + setPlayingId(null); + }); + audio.play().catch((error) => { + console.error("Failed to play sound preview:", error); + setPlayingId(null); + }); + + setAudioElement(audio); + setPlayingId(sound.id); + } + }; + + return ( +
+
+ + setSearchQuery({ query: currentTarget.value }) + } + showClearIcon + onClear={() => setSearchQuery({ query: "" })} + /> + + + + + + toggleCommercialFilter()} + > + Show only commercially licensed + +
+ {showCommercialOnly + ? "Only showing sounds licensed for commercial use" + : "Showing all sounds regardless of license"} +
+
+
+
+ +
+ +
+ {isLoading && !searchQuery && ( +
+ Loading sounds... +
+ )} + {isSearching && searchQuery && ( +
Searching...
+ )} + {displayedSounds.map((sound) => ( + + ))} + {!isLoading && !isSearching && displayedSounds.length === 0 && ( +
+ {searchQuery ? "No sounds found" : "No sounds available"} +
+ )} + {isLoadingMore && ( +
+ Loading more sounds... +
+ )} +
+
+
+
+ ); +} + +function SavedSoundsView() { + const { + savedSounds, + isLoadingSavedSounds, + savedSoundsError, + loadSavedSounds, + clearSavedSounds, + } = useSoundsStore(); + + const [playingId, setPlayingId] = useState(null); + const [audioElement, setAudioElement] = useState( + null, + ); + + const [showClearDialog, setShowClearDialog] = useState(false); + + useEffect(() => { + loadSavedSounds(); + }, [loadSavedSounds]); + + const playSound = ({ sound }: { sound: SoundEffect }) => { + if (playingId === sound.id) { + audioElement?.pause(); + setPlayingId(null); + return; + } + + audioElement?.pause(); + + if (sound.previewUrl) { + const audio = new Audio(sound.previewUrl); + audio.addEventListener("ended", () => { + setPlayingId(null); + }); + audio.addEventListener("error", () => { + setPlayingId(null); + }); + audio.play().catch((error) => { + console.error("Failed to play sound preview:", error); + setPlayingId(null); + }); + + setAudioElement(audio); + setPlayingId(sound.id); + } + }; + + const convertToSoundEffect = ({ + savedSound, + }: { + savedSound: SavedSound; + }): SoundEffect => ({ + id: savedSound.id, + name: savedSound.name, + description: "", + url: "", + previewUrl: savedSound.previewUrl, + downloadUrl: savedSound.downloadUrl, + duration: savedSound.duration, + filesize: 0, + type: "audio", + channels: 0, + bitrate: 0, + bitdepth: 0, + samplerate: 0, + username: savedSound.username, + tags: savedSound.tags, + license: savedSound.license, + created: savedSound.savedAt, + downloads: 0, + rating: 0, + ratingCount: 0, + }); + + if (isLoadingSavedSounds) { + return ( +
+
+ Loading saved sounds... +
+
+ ); + } + + if (savedSoundsError) { + return ( +
+
+ Error: {savedSoundsError} +
+
+ ); + } + + if (savedSounds.length === 0) { + return ( +
+ +
+

No saved sounds

+

+ Click the heart icon on any sound to save it here +

+
+
+ ); + } + + return ( +
+
+

+ {savedSounds.length} saved{" "} + {savedSounds.length === 1 ? "sound" : "sounds"} +

+ + + + + + + Clear all saved sounds? + + This will permanently remove all {savedSounds.length} saved + sounds from your collection. This action cannot be undone. + + + + + + + + +
+ +
+ +
+ {savedSounds.map((sound) => ( + + ))} +
+
+
+
+ ); +} + +function SongsView() { + return
Songs
; +} + +interface AudioItemProps { + sound: SoundEffect; + isPlaying: boolean; + onPlay: ({ sound }: { sound: SoundEffect }) => void; +} + +function AudioItem({ sound, isPlaying, onPlay }: AudioItemProps) { + const { addSoundToTimeline, isSoundSaved, toggleSavedSound } = + useSoundsStore(); + const isSaved = isSoundSaved({ soundId: sound.id }); + + const handleClick = () => { + onPlay({ sound }); + }; + + const handleSaveClick = ({ + stopPropagation, + }: React.MouseEvent) => { + stopPropagation(); + toggleSavedSound({ soundEffect: sound }); + }; + + const handleAddToTimeline = async ({ + stopPropagation, + }: React.MouseEvent) => { + stopPropagation(); + await addSoundToTimeline({ sound }); + }; + + return ( +
+ + +
+ + +
+
+ ); +} diff --git a/apps/web/src/components/editor/panels/assets/views/stickers.tsx b/apps/web/src/components/editor/panels/assets/views/stickers.tsx index af489630..285b37d3 100644 --- a/apps/web/src/components/editor/panels/assets/views/stickers.tsx +++ b/apps/web/src/components/editor/panels/assets/views/stickers.tsx @@ -1,470 +1,470 @@ -"use client"; - -import Image from "next/image"; -import type { CSSProperties } from "react"; -import { useEffect, useState } from "react"; -import { toast } from "sonner"; -import { DraggableItem } from "@/components/editor/panels/assets/draggable-item"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Spinner } from "@/components/ui/spinner"; -import { useEditor } from "@/hooks/use-editor"; -import { resolveStickerIntrinsicSize } from "@/lib/stickers"; -import { - buildGraphicElement, - buildStickerElement, -} from "@/lib/timeline/element-utils"; -import { STICKER_CATEGORIES } from "@/constants/sticker-constants"; -import { getRegionLabel, resolveQueryToRegions } from "@/lib/stickers"; -import { parseShapeStickerId } from "@/lib/stickers/providers/shapes"; -import type { TimelineDragData } from "@/lib/timeline/drag"; -import type { - StickerBrowseSection, - StickerCategory, - StickerItem as StickerData, -} from "@/lib/stickers"; -import { useStickersStore } from "@/stores/stickers-store"; -import { cn } from "@/utils/ui"; -import { - HappyIcon, -} from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/react"; - -export function StickersView() { - const { - browseContent, - browseStickers, - searchQuery, - searchStickers, - selectedCategory, - setSearchQuery, - setSelectedCategory, - viewMode, - } = useStickersStore(); - - useEffect(() => { - if (viewMode === "browse" && !browseContent) { - void browseStickers(); - } - }, [browseContent, browseStickers, viewMode]); - - return ( -
-
- { - setSearchQuery({ query: e.target.value }); - void searchStickers({ query: e.target.value }); - }} - showClearIcon - onClear={() => { - setSearchQuery({ query: "" }); - void searchStickers({ query: "" }); - }} - className="w-full" - containerClassName="w-full" - /> -
- -
-
-
- {Object.entries(STICKER_CATEGORIES).map(([key, label]) => { - const isActive = key === selectedCategory; - return ( - - ); - })} -
-
-
- -
-
-
- ); -} - -function StickerGrid({ - items, - shouldCapSize = false, -}: { - items: StickerData[]; - shouldCapSize?: boolean; -}) { - const gridStyle: CSSProperties & { - "--sticker-min": string; - "--sticker-max"?: string; - } = { - gridTemplateColumns: shouldCapSize - ? "repeat(auto-fill, minmax(var(--sticker-min, 80px), var(--sticker-max, 140px)))" - : "repeat(auto-fill, minmax(var(--sticker-min, 80px), 1fr))", - "--sticker-min": "80px", - ...(shouldCapSize ? { "--sticker-max": "140px" } : {}), - }; - - return ( -
- {items.map((item) => ( - - ))} -
- ); -} - -function StickerRow({ items }: { items: StickerData[] }) { - return ( -
- {items.map((item) => ( -
- -
- ))} -
- ); -} - -function EmptyView({ message }: { message: string }) { - return ( -
- -
-

No stickers found

-

{message}

-
-
- ); -} - -function RegionBanner({ region }: { region: string }) { - return ( -
- - {region} -
- ); -} - -function StickersContentView() { - const { - browseContent, - clearRecentStickers, - isBrowsing, - isSearching, - searchQuery, - searchResults, - selectedCategory, - setSelectedCategory, - viewMode, - } = useStickersStore(); - - if (viewMode === "search") { - if (isSearching) { - return ( -
- -
- ); - } - - if (searchResults?.items.length) { - const normalizedQuery = searchQuery.trim().toLowerCase(); - const isRegionSearch = - selectedCategory === "flags" && - resolveQueryToRegions({ query: normalizedQuery }) !== null; - const regionLabel = getRegionLabel({ query: normalizedQuery }); - - return ( -
- {isRegionSearch && } -
- - {searchResults.total} results - -
- -
- ); - } - - // "all" tab search — sections are in browseContent, fall through to section rendering below - if (selectedCategory !== "all" && searchQuery) { - return ; - } - } - - if (isBrowsing && !browseContent) { - return ( -
- -
- ); - } - - if (!browseContent?.sections.length) { - const categoryLabel = STICKER_CATEGORIES[selectedCategory]; - return ( - - ); - } - - return ( -
- {browseContent.sections.map((section) => ( - { - setSelectedCategory({ category }); - }} - /> - ))} -
- ); -} - -function StickerSection({ - section, - onClearRecent, - onSeeAll, -}: { - section: StickerBrowseSection; - onClearRecent: () => void; - onSeeAll: (category: StickerCategory) => void; -}) { - const hasHeader = - Boolean(section.title) || section.id === "recent" || section.action; - - return ( -
- {hasHeader && ( -
- {section.title ? ( -

{section.title}

- ) : ( -
- )} - -
- {section.id === "recent" && ( - - )} - - {section.action?.type === "see-all" && section.action.category && ( - - )} -
-
- )} - - {section.layout === "row" ? ( - - ) : ( - - )} -
- ); -} - -interface StickerItemProps { - item: StickerData; - shouldCapSize?: boolean; - containerClassName?: string; -} - -function StickerItem({ - item, - shouldCapSize = false, - containerClassName, -}: StickerItemProps) { - const editor = useEditor(); - const { addToRecentStickers } = useStickersStore(); - const [isAdding, setIsAdding] = useState(false); - const [hasImageError, setHasImageError] = useState(false); - - useEffect(() => { - if (!item.id) { - return; - } - - setHasImageError(false); - }, [item.id]); - - const displayName = item.name; - const shapePreset = - item.provider === "shapes" ? parseShapeStickerId({ stickerId: item.id }) : null; - - const handleAdd = async () => { - setIsAdding(true); - try { - const currentTime = editor.playback.getCurrentTime(); - - let element: - | ReturnType - | ReturnType; - if (shapePreset) { - element = buildGraphicElement({ - definitionId: shapePreset.definitionId, - name: shapePreset.name, - startTime: currentTime, - params: shapePreset.params, - }); - } else { - const { width: intrinsicWidth, height: intrinsicHeight } = - await resolveStickerIntrinsicSize({ stickerId: item.id }); - element = buildStickerElement({ - stickerId: item.id, - name: item.name, - startTime: currentTime, - intrinsicWidth, - intrinsicHeight, - }); - } - - editor.timeline.insertElement({ - placement: { mode: "auto" }, - element, - }); - - addToRecentStickers({ stickerId: item.id }); - } catch (error) { - console.error("Failed to add sticker:", error); - toast.error("Failed to add sticker to timeline"); - } finally { - setIsAdding(false); - } - }; - - const preview = ( -
- {hasImageError ? ( - - {displayName} - - ) : ( - {displayName} setHasImageError(true)} - loading="lazy" - unoptimized - /> - )} -
- ); - - const dragData: TimelineDragData = shapePreset - ? { - id: item.id, - type: "graphic", - name: displayName, - definitionId: shapePreset.definitionId, - params: shapePreset.params ?? {}, - } - : { - id: item.id, - type: "sticker", - name: displayName, - stickerId: item.id, - }; - - return ( -
- - {isAdding && ( -
- -
- )} -
- ); -} +"use client"; + +import Image from "next/image"; +import type { CSSProperties } from "react"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import { DraggableItem } from "@/components/editor/panels/assets/draggable-item"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Spinner } from "@/components/ui/spinner"; +import { useEditor } from "@/hooks/use-editor"; +import { resolveStickerIntrinsicSize } from "@/lib/stickers"; +import { + buildGraphicElement, + buildStickerElement, +} from "@/lib/timeline/element-utils"; +import { STICKER_CATEGORIES } from "@/constants/sticker-constants"; +import { getRegionLabel, resolveQueryToRegions } from "@/lib/stickers"; +import { parseShapeStickerId } from "@/lib/stickers/providers/shapes"; +import type { TimelineDragData } from "@/lib/timeline/drag"; +import type { + StickerBrowseSection, + StickerCategory, + StickerItem as StickerData, +} from "@/lib/stickers"; +import { useStickersStore } from "@/stores/stickers-store"; +import { cn } from "@/utils/ui"; +import { + HappyIcon, +} from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; + +export function StickersView() { + const { + browseContent, + browseStickers, + searchQuery, + searchStickers, + selectedCategory, + setSearchQuery, + setSelectedCategory, + viewMode, + } = useStickersStore(); + + useEffect(() => { + if (viewMode === "browse" && !browseContent) { + void browseStickers(); + } + }, [browseContent, browseStickers, viewMode]); + + return ( +
+
+ { + setSearchQuery({ query: e.target.value }); + void searchStickers({ query: e.target.value }); + }} + showClearIcon + onClear={() => { + setSearchQuery({ query: "" }); + void searchStickers({ query: "" }); + }} + className="w-full" + containerClassName="w-full" + /> +
+ +
+
+
+ {Object.entries(STICKER_CATEGORIES).map(([key, label]) => { + const isActive = key === selectedCategory; + return ( + + ); + })} +
+
+
+ +
+
+
+ ); +} + +function StickerGrid({ + items, + shouldCapSize = false, +}: { + items: StickerData[]; + shouldCapSize?: boolean; +}) { + const gridStyle: CSSProperties & { + "--sticker-min": string; + "--sticker-max"?: string; + } = { + gridTemplateColumns: shouldCapSize + ? "repeat(auto-fill, minmax(var(--sticker-min, 80px), var(--sticker-max, 140px)))" + : "repeat(auto-fill, minmax(var(--sticker-min, 80px), 1fr))", + "--sticker-min": "80px", + ...(shouldCapSize ? { "--sticker-max": "140px" } : {}), + }; + + return ( +
+ {items.map((item) => ( + + ))} +
+ ); +} + +function StickerRow({ items }: { items: StickerData[] }) { + return ( +
+ {items.map((item) => ( +
+ +
+ ))} +
+ ); +} + +function EmptyView({ message }: { message: string }) { + return ( +
+ +
+

No stickers found

+

{message}

+
+
+ ); +} + +function RegionBanner({ region }: { region: string }) { + return ( +
+ + {region} +
+ ); +} + +function StickersContentView() { + const { + browseContent, + clearRecentStickers, + isBrowsing, + isSearching, + searchQuery, + searchResults, + selectedCategory, + setSelectedCategory, + viewMode, + } = useStickersStore(); + + if (viewMode === "search") { + if (isSearching) { + return ( +
+ +
+ ); + } + + if (searchResults?.items.length) { + const normalizedQuery = searchQuery.trim().toLowerCase(); + const isRegionSearch = + selectedCategory === "flags" && + resolveQueryToRegions({ query: normalizedQuery }) !== null; + const regionLabel = getRegionLabel({ query: normalizedQuery }); + + return ( +
+ {isRegionSearch && } +
+ + {searchResults.total} results + +
+ +
+ ); + } + + // "all" tab search — sections are in browseContent, fall through to section rendering below + if (selectedCategory !== "all" && searchQuery) { + return ; + } + } + + if (isBrowsing && !browseContent) { + return ( +
+ +
+ ); + } + + if (!browseContent?.sections.length) { + const categoryLabel = STICKER_CATEGORIES[selectedCategory]; + return ( + + ); + } + + return ( +
+ {browseContent.sections.map((section) => ( + { + setSelectedCategory({ category }); + }} + /> + ))} +
+ ); +} + +function StickerSection({ + section, + onClearRecent, + onSeeAll, +}: { + section: StickerBrowseSection; + onClearRecent: () => void; + onSeeAll: (category: StickerCategory) => void; +}) { + const hasHeader = + Boolean(section.title) || section.id === "recent" || section.action; + + return ( +
+ {hasHeader && ( +
+ {section.title ? ( +

{section.title}

+ ) : ( +
+ )} + +
+ {section.id === "recent" && ( + + )} + + {section.action?.type === "see-all" && section.action.category && ( + + )} +
+
+ )} + + {section.layout === "row" ? ( + + ) : ( + + )} +
+ ); +} + +interface StickerItemProps { + item: StickerData; + shouldCapSize?: boolean; + containerClassName?: string; +} + +function StickerItem({ + item, + shouldCapSize = false, + containerClassName, +}: StickerItemProps) { + const editor = useEditor(); + const { addToRecentStickers } = useStickersStore(); + const [isAdding, setIsAdding] = useState(false); + const [hasImageError, setHasImageError] = useState(false); + + useEffect(() => { + if (!item.id) { + return; + } + + setHasImageError(false); + }, [item.id]); + + const displayName = item.name; + const shapePreset = + item.provider === "shapes" ? parseShapeStickerId({ stickerId: item.id }) : null; + + const handleAdd = async () => { + setIsAdding(true); + try { + const currentTime = editor.playback.getCurrentTime(); + + let element: + | ReturnType + | ReturnType; + if (shapePreset) { + element = buildGraphicElement({ + definitionId: shapePreset.definitionId, + name: shapePreset.name, + startTime: currentTime, + params: shapePreset.params, + }); + } else { + const { width: intrinsicWidth, height: intrinsicHeight } = + await resolveStickerIntrinsicSize({ stickerId: item.id }); + element = buildStickerElement({ + stickerId: item.id, + name: item.name, + startTime: currentTime, + intrinsicWidth, + intrinsicHeight, + }); + } + + editor.timeline.insertElement({ + placement: { mode: "auto" }, + element, + }); + + addToRecentStickers({ stickerId: item.id }); + } catch (error) { + console.error("Failed to add sticker:", error); + toast.error("Failed to add sticker to timeline"); + } finally { + setIsAdding(false); + } + }; + + const preview = ( +
+ {hasImageError ? ( + + {displayName} + + ) : ( + {displayName} setHasImageError(true)} + loading="lazy" + unoptimized + /> + )} +
+ ); + + const dragData: TimelineDragData = shapePreset + ? { + id: item.id, + type: "graphic", + name: displayName, + definitionId: shapePreset.definitionId, + params: shapePreset.params ?? {}, + } + : { + id: item.id, + type: "sticker", + name: displayName, + stickerId: item.id, + }; + + return ( +
+ + {isAdding && ( +
+ +
+ )} +
+ ); +} diff --git a/apps/web/src/components/editor/panels/assets/views/text.tsx b/apps/web/src/components/editor/panels/assets/views/text.tsx index 075cc25b..da295da3 100644 --- a/apps/web/src/components/editor/panels/assets/views/text.tsx +++ b/apps/web/src/components/editor/panels/assets/views/text.tsx @@ -1,46 +1,46 @@ -import { DraggableItem } from "@/components/editor/panels/assets/draggable-item"; -import { PanelView } from "@/components/editor/panels/assets/views/base-panel"; -import { useEditor } from "@/hooks/use-editor"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import { buildTextElement } from "@/lib/timeline/element-utils"; - -export function TextView() { - const editor = useEditor(); - - const handleAddToTimeline = ({ currentTime }: { currentTime: number }) => { - const activeScene = editor.scenes.getActiveScene(); - if (!activeScene) return; - - const element = buildTextElement({ - raw: DEFAULTS.text.element, - startTime: currentTime, - }); - - editor.timeline.insertElement({ - element, - placement: { mode: "auto" }, - }); - }; - - return ( - - - Default text -
- } - dragData={{ - id: "temp-text-id", - type: DEFAULTS.text.element.type, - name: DEFAULTS.text.element.name, - content: DEFAULTS.text.element.content, - }} - aspectRatio={1} - onAddToTimeline={handleAddToTimeline} - shouldShowLabel={false} - /> - - ); -} +import { DraggableItem } from "@/components/editor/panels/assets/draggable-item"; +import { PanelView } from "@/components/editor/panels/assets/views/base-panel"; +import { useEditor } from "@/hooks/use-editor"; +import { DEFAULTS } from "@/lib/timeline/defaults"; +import { buildTextElement } from "@/lib/timeline/element-utils"; + +export function TextView() { + const editor = useEditor(); + + const handleAddToTimeline = ({ currentTime }: { currentTime: number }) => { + const activeScene = editor.scenes.getActiveScene(); + if (!activeScene) return; + + const element = buildTextElement({ + raw: DEFAULTS.text.element, + startTime: currentTime, + }); + + editor.timeline.insertElement({ + element, + placement: { mode: "auto" }, + }); + }; + + return ( + + + Default text +
+ } + dragData={{ + id: "temp-text-id", + type: DEFAULTS.text.element.type, + name: DEFAULTS.text.element.name, + content: DEFAULTS.text.element.content, + }} + aspectRatio={1} + onAddToTimeline={handleAddToTimeline} + shouldShowLabel={false} + /> + + ); +} diff --git a/apps/web/src/components/editor/panels/preview/bookmark-note-overlay.tsx b/apps/web/src/components/editor/panels/preview/bookmark-note-overlay.tsx index 4ace25ac..3d96f762 100644 --- a/apps/web/src/components/editor/panels/preview/bookmark-note-overlay.tsx +++ b/apps/web/src/components/editor/panels/preview/bookmark-note-overlay.tsx @@ -1,59 +1,59 @@ -"use client"; - -import { useEditor } from "@/hooks/use-editor"; -import { findCurrentScene } from "@/lib/scenes"; -import { getBookmarksActiveAtTime } from "@/lib/timeline/bookmarks"; - -export function BookmarkNoteOverlay() { - const editor = useEditor(); - const currentTime = editor.playback.getCurrentTime(); - const activeProject = editor.project.getActive(); - - if (!activeProject) { - return null; - } - - const activeScene = findCurrentScene({ - scenes: activeProject.scenes, - currentSceneId: activeProject.currentSceneId, - }); - - if (!activeScene) { - return null; - } - - const bookmarks = activeScene.bookmarks; - const activeBookmarks = getBookmarksActiveAtTime({ - bookmarks, - time: currentTime, - }); - const bookmarksWithNotes = activeBookmarks.filter( - (bookmark) => bookmark.note != null && bookmark.note.trim() !== "", - ); - - if (bookmarksWithNotes.length === 0) { - return null; - } - - return ( -
- {bookmarksWithNotes.map((bookmark) => ( -
- {bookmark.note} -
- ))} -
- ); -} +"use client"; + +import { useEditor } from "@/hooks/use-editor"; +import { findCurrentScene } from "@/lib/scenes"; +import { getBookmarksActiveAtTime } from "@/lib/timeline/bookmarks"; + +export function BookmarkNoteOverlay() { + const editor = useEditor(); + const currentTime = editor.playback.getCurrentTime(); + const activeProject = editor.project.getActive(); + + if (!activeProject) { + return null; + } + + const activeScene = findCurrentScene({ + scenes: activeProject.scenes, + currentSceneId: activeProject.currentSceneId, + }); + + if (!activeScene) { + return null; + } + + const bookmarks = activeScene.bookmarks; + const activeBookmarks = getBookmarksActiveAtTime({ + bookmarks, + time: currentTime, + }); + const bookmarksWithNotes = activeBookmarks.filter( + (bookmark) => bookmark.note != null && bookmark.note.trim() !== "", + ); + + if (bookmarksWithNotes.length === 0) { + return null; + } + + return ( +
+ {bookmarksWithNotes.map((bookmark) => ( +
+ {bookmark.note} +
+ ))} +
+ ); +} diff --git a/apps/web/src/components/editor/panels/preview/context-menu.tsx b/apps/web/src/components/editor/panels/preview/context-menu.tsx index 13226378..677f35db 100644 --- a/apps/web/src/components/editor/panels/preview/context-menu.tsx +++ b/apps/web/src/components/editor/panels/preview/context-menu.tsx @@ -1,73 +1,73 @@ -"use client"; - -import { - ContextMenuCheckboxItem, - ContextMenuContent, - ContextMenuItem, - ContextMenuSeparator, -} from "@/components/ui/context-menu"; -import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; -import { useEditor } from "@/hooks/use-editor"; -import { usePreviewStore } from "@/stores/preview-store"; -import { toast } from "sonner"; - -export function PreviewContextMenu({ - onToggleFullscreen, - containerRef, -}: { - onToggleFullscreen: () => void; - containerRef: React.RefObject; -}) { - const editor = useEditor(); - const viewport = usePreviewViewport(); - const { overlays, setOverlayVisibility } = usePreviewStore(); - - const handleCopySnapshot = async () => { - const result = await editor.renderer.copySnapshot(); - - if (!result.success) { - toast.error("Failed to copy snapshot", { - description: result.error ?? "Please try again", - }); - return; - } - }; - - const handleSaveSnapshot = async () => { - const result = await editor.renderer.saveSnapshot(); - - if (!result.success) { - toast.error("Failed to save snapshot", { - description: result.error ?? "Please try again", - }); - return; - } - }; - - return ( - - - Fit to screen - - - - Full screen - - - Save snapshot - - - Copy snapshot - - - - setOverlayVisibility({ overlay: "bookmarks", isVisible: !!checked }) - } - > - Show bookmark notes - - - ); -} +"use client"; + +import { + ContextMenuCheckboxItem, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, +} from "@/components/ui/context-menu"; +import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; +import { useEditor } from "@/hooks/use-editor"; +import { usePreviewStore } from "@/stores/preview-store"; +import { toast } from "sonner"; + +export function PreviewContextMenu({ + onToggleFullscreen, + containerRef, +}: { + onToggleFullscreen: () => void; + containerRef: React.RefObject; +}) { + const editor = useEditor(); + const viewport = usePreviewViewport(); + const { overlays, setOverlayVisibility } = usePreviewStore(); + + const handleCopySnapshot = async () => { + const result = await editor.renderer.copySnapshot(); + + if (!result.success) { + toast.error("Failed to copy snapshot", { + description: result.error ?? "Please try again", + }); + return; + } + }; + + const handleSaveSnapshot = async () => { + const result = await editor.renderer.saveSnapshot(); + + if (!result.success) { + toast.error("Failed to save snapshot", { + description: result.error ?? "Please try again", + }); + return; + } + }; + + return ( + + + Fit to screen + + + + Full screen + + + Save snapshot + + + Copy snapshot + + + + setOverlayVisibility({ overlay: "bookmarks", isVisible: !!checked }) + } + > + Show bookmark notes + + + ); +} diff --git a/apps/web/src/components/editor/panels/preview/guide-overlay.tsx b/apps/web/src/components/editor/panels/preview/guide-overlay.tsx index ad7a621f..4270fcb3 100644 --- a/apps/web/src/components/editor/panels/preview/guide-overlay.tsx +++ b/apps/web/src/components/editor/panels/preview/guide-overlay.tsx @@ -1,32 +1,32 @@ -"use client"; - -import { getGuideById } from "@/lib/guides"; -import { usePreviewStore } from "@/stores/preview-store"; -import { usePreviewViewport } from "./preview-viewport"; - -export function GuideOverlay() { - const activeGuide = usePreviewStore((state) => state.activeGuide); - const viewport = usePreviewViewport(); - const guide = getGuideById(activeGuide); - - if (!guide) { - return null; - } - - return ( -
- {guide.renderOverlay({ - width: viewport.sceneWidth, - height: viewport.sceneHeight, - })} -
- ); -} +"use client"; + +import { getGuideById } from "@/lib/guides"; +import { usePreviewStore } from "@/stores/preview-store"; +import { usePreviewViewport } from "./preview-viewport"; + +export function GuideOverlay() { + const activeGuide = usePreviewStore((state) => state.activeGuide); + const viewport = usePreviewViewport(); + const guide = getGuideById(activeGuide); + + if (!guide) { + return null; + } + + return ( +
+ {guide.renderOverlay({ + width: viewport.sceneWidth, + height: viewport.sceneHeight, + })} +
+ ); +} diff --git a/apps/web/src/components/editor/panels/preview/guide-popover.tsx b/apps/web/src/components/editor/panels/preview/guide-popover.tsx index e6d3df51..bcac8075 100644 --- a/apps/web/src/components/editor/panels/preview/guide-popover.tsx +++ b/apps/web/src/components/editor/panels/preview/guide-popover.tsx @@ -1,100 +1,100 @@ -"use client"; - -import { AnimatePresence, motion } from "motion/react"; -import { GUIDE_REGISTRY, getGuideById } from "@/lib/guides"; -import { usePreviewStore } from "@/stores/preview-store"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { Label } from "@/components/ui/label"; -import { Separator } from "@/components/ui/separator"; -import { cn } from "@/utils/ui"; - -export function GridPopover({ children }: { children: React.ReactNode }) { - const activeGuide = usePreviewStore((state) => state.activeGuide); - const toggleGuide = usePreviewStore((state) => state.toggleGuide); - const activeGuideDef = getGuideById(activeGuide); - const options = activeGuideDef?.renderOptions?.(); - - return ( - - {children} - -
- -
- {GUIDE_REGISTRY.map((guide) => ( - toggleGuide(guide.id)} - /> - ))} -
-
- - {options && ( - - -
{options}
-
- )} -
-
-
- ); -} - -function GridItem({ - label, - preview, - isSelected, - onClick, -}: { - label: string; - preview: React.ReactNode; - isSelected?: boolean; - onClick: () => void; -}) { - return ( - - ); -} +"use client"; + +import { AnimatePresence, motion } from "motion/react"; +import { GUIDE_REGISTRY, getGuideById } from "@/lib/guides"; +import { usePreviewStore } from "@/stores/preview-store"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Label } from "@/components/ui/label"; +import { Separator } from "@/components/ui/separator"; +import { cn } from "@/utils/ui"; + +export function GridPopover({ children }: { children: React.ReactNode }) { + const activeGuide = usePreviewStore((state) => state.activeGuide); + const toggleGuide = usePreviewStore((state) => state.toggleGuide); + const activeGuideDef = getGuideById(activeGuide); + const options = activeGuideDef?.renderOptions?.(); + + return ( + + {children} + +
+ +
+ {GUIDE_REGISTRY.map((guide) => ( + toggleGuide(guide.id)} + /> + ))} +
+
+ + {options && ( + + +
{options}
+
+ )} +
+
+
+ ); +} + +function GridItem({ + label, + preview, + isSelected, + onClick, +}: { + label: string; + preview: React.ReactNode; + isSelected?: boolean; + onClick: () => void; +}) { + return ( + + ); +} diff --git a/apps/web/src/components/editor/panels/preview/handle-primitives.tsx b/apps/web/src/components/editor/panels/preview/handle-primitives.tsx index 44b8a7f1..d4de3332 100644 --- a/apps/web/src/components/editor/panels/preview/handle-primitives.tsx +++ b/apps/web/src/components/editor/panels/preview/handle-primitives.tsx @@ -1,332 +1,332 @@ -"use client"; - -import { cn } from "@/utils/ui"; -import { HugeiconsIcon } from "@hugeicons/react"; -import type { IconSvgElement } from "@hugeicons/react"; - -export const HANDLE_SIZE = 10; -export const HANDLE_HIT_AREA_SIZE = 18; -export const ICON_HANDLE_RADIUS = 10; -export const EDGE_HANDLE_THIN_SIZE = 6; -export const EDGE_HANDLE_THICK_SIZE = 14; -export const LINE_HIT_AREA_SIZE = 48; - -export function getResizeCursor({ angleDeg }: { angleDeg: number }): string { - const normalized = ((angleDeg % 180) + 180) % 180; - if (normalized < 22.5 || normalized >= 157.5) return "cursor-ew-resize"; - if (normalized < 67.5) return "cursor-nwse-resize"; - if (normalized < 112.5) return "cursor-ns-resize"; - return "cursor-nesw-resize"; -} - -export function HandleButton({ - screen, - cursor, - hitAreaSize, - className, - onPointerDown, - onPointerMove, - onPointerUp, - children, -}: { - screen: { x: number; y: number }; - cursor?: string; - hitAreaSize: number; - className?: string; - onPointerDown: (event: React.PointerEvent) => void; - onPointerMove: (event: React.PointerEvent) => void; - onPointerUp: (event: React.PointerEvent) => void; - children: React.ReactNode; -}) { - return ( - - ); -} - -export function CornerHandle({ - cursor, - screen, - onPointerDown, - onPointerMove, - onPointerUp, -}: { - cursor?: string; - screen: { x: number; y: number }; - onPointerDown: (event: React.PointerEvent) => void; - onPointerMove: (event: React.PointerEvent) => void; - onPointerUp: (event: React.PointerEvent) => void; -}) { - return ( - -
- - ); -} - -export function EdgeHandle({ - edge, - screen, - rotation, - onPointerDown, - onPointerMove, - onPointerUp, -}: { - edge: "right" | "left" | "bottom"; - screen: { x: number; y: number }; - rotation: number; - onPointerDown: (event: React.PointerEvent) => void; - onPointerMove: (event: React.PointerEvent) => void; - onPointerUp: (event: React.PointerEvent) => void; -}) { - const isHorizontalEdge = edge === "right" || edge === "left"; - const width = isHorizontalEdge - ? EDGE_HANDLE_THIN_SIZE - : EDGE_HANDLE_THICK_SIZE; - const height = isHorizontalEdge - ? EDGE_HANDLE_THICK_SIZE - : EDGE_HANDLE_THIN_SIZE; - const cursor = getResizeCursor({ - angleDeg: isHorizontalEdge ? rotation : rotation + 90, - }); - - return ( - -
- - ); -} - -export function IconHandle({ - icon, - screen, - onPointerDown, - onPointerMove, - onPointerUp, -}: { - icon: IconSvgElement; - screen: { x: number; y: number }; - onPointerDown: (event: React.PointerEvent) => void; - onPointerMove: (event: React.PointerEvent) => void; - onPointerUp: (event: React.PointerEvent) => void; -}) { - return ( - - - - ); -} - -export function BoundingBoxOutline({ - center, - outlineWidth, - outlineHeight, - rotation, - cursor, - dashed = false, - onPointerDown, - onPointerMove, - onPointerUp, -}: { - center: { x: number; y: number }; - outlineWidth: number; - outlineHeight: number; - rotation: number; - cursor?: string; - dashed?: boolean; - onPointerDown?: (event: React.PointerEvent) => void; - onPointerMove?: (event: React.PointerEvent) => void; - onPointerUp?: (event: React.PointerEvent) => void; -}) { - return ( - - ); -} - -export function ShapeOutline({ - center, - outlineWidth, - outlineHeight, - rotation, - pathData, - cursor, - onPointerDown, - onPointerMove, - onPointerUp, -}: { - center: { x: number; y: number }; - outlineWidth: number; - outlineHeight: number; - rotation: number; - pathData: string; - cursor?: string; - onPointerDown?: (event: React.PointerEvent) => void; - onPointerMove?: (event: React.PointerEvent) => void; - onPointerUp?: (event: React.PointerEvent) => void; -}) { - return ( - - ); -} - -export function LineOverlay({ - start, - end, - onPointerDown, - onPointerMove, - onPointerUp, -}: { - start: { x: number; y: number }; - end: { x: number; y: number }; - onPointerDown?: (event: React.PointerEvent) => void; - onPointerMove?: (event: React.PointerEvent) => void; - onPointerUp?: (event: React.PointerEvent) => void; -}) { - const dx = end.x - start.x; - const dy = end.y - start.y; - const length = Math.sqrt(dx * dx + dy * dy); - const angleDeg = (Math.atan2(dy, dx) * 180) / Math.PI; - const cx = (start.x + end.x) / 2; - const cy = (start.y + end.y) / 2; - - const sharedStyle = { - left: cx - length / 2, - width: length, - transform: `rotate(${angleDeg}deg)`, - transformOrigin: "center center", - }; - - return ( - <> - {onPointerDown && ( -
- )} -
- - ); -} +"use client"; + +import { cn } from "@/utils/ui"; +import { HugeiconsIcon } from "@hugeicons/react"; +import type { IconSvgElement } from "@hugeicons/react"; + +export const HANDLE_SIZE = 10; +export const HANDLE_HIT_AREA_SIZE = 18; +export const ICON_HANDLE_RADIUS = 10; +export const EDGE_HANDLE_THIN_SIZE = 6; +export const EDGE_HANDLE_THICK_SIZE = 14; +export const LINE_HIT_AREA_SIZE = 48; + +export function getResizeCursor({ angleDeg }: { angleDeg: number }): string { + const normalized = ((angleDeg % 180) + 180) % 180; + if (normalized < 22.5 || normalized >= 157.5) return "cursor-ew-resize"; + if (normalized < 67.5) return "cursor-nwse-resize"; + if (normalized < 112.5) return "cursor-ns-resize"; + return "cursor-nesw-resize"; +} + +export function HandleButton({ + screen, + cursor, + hitAreaSize, + className, + onPointerDown, + onPointerMove, + onPointerUp, + children, +}: { + screen: { x: number; y: number }; + cursor?: string; + hitAreaSize: number; + className?: string; + onPointerDown: (event: React.PointerEvent) => void; + onPointerMove: (event: React.PointerEvent) => void; + onPointerUp: (event: React.PointerEvent) => void; + children: React.ReactNode; +}) { + return ( + + ); +} + +export function CornerHandle({ + cursor, + screen, + onPointerDown, + onPointerMove, + onPointerUp, +}: { + cursor?: string; + screen: { x: number; y: number }; + onPointerDown: (event: React.PointerEvent) => void; + onPointerMove: (event: React.PointerEvent) => void; + onPointerUp: (event: React.PointerEvent) => void; +}) { + return ( + +
+ + ); +} + +export function EdgeHandle({ + edge, + screen, + rotation, + onPointerDown, + onPointerMove, + onPointerUp, +}: { + edge: "right" | "left" | "bottom"; + screen: { x: number; y: number }; + rotation: number; + onPointerDown: (event: React.PointerEvent) => void; + onPointerMove: (event: React.PointerEvent) => void; + onPointerUp: (event: React.PointerEvent) => void; +}) { + const isHorizontalEdge = edge === "right" || edge === "left"; + const width = isHorizontalEdge + ? EDGE_HANDLE_THIN_SIZE + : EDGE_HANDLE_THICK_SIZE; + const height = isHorizontalEdge + ? EDGE_HANDLE_THICK_SIZE + : EDGE_HANDLE_THIN_SIZE; + const cursor = getResizeCursor({ + angleDeg: isHorizontalEdge ? rotation : rotation + 90, + }); + + return ( + +
+ + ); +} + +export function IconHandle({ + icon, + screen, + onPointerDown, + onPointerMove, + onPointerUp, +}: { + icon: IconSvgElement; + screen: { x: number; y: number }; + onPointerDown: (event: React.PointerEvent) => void; + onPointerMove: (event: React.PointerEvent) => void; + onPointerUp: (event: React.PointerEvent) => void; +}) { + return ( + + + + ); +} + +export function BoundingBoxOutline({ + center, + outlineWidth, + outlineHeight, + rotation, + cursor, + dashed = false, + onPointerDown, + onPointerMove, + onPointerUp, +}: { + center: { x: number; y: number }; + outlineWidth: number; + outlineHeight: number; + rotation: number; + cursor?: string; + dashed?: boolean; + onPointerDown?: (event: React.PointerEvent) => void; + onPointerMove?: (event: React.PointerEvent) => void; + onPointerUp?: (event: React.PointerEvent) => void; +}) { + return ( + + ); +} + +export function ShapeOutline({ + center, + outlineWidth, + outlineHeight, + rotation, + pathData, + cursor, + onPointerDown, + onPointerMove, + onPointerUp, +}: { + center: { x: number; y: number }; + outlineWidth: number; + outlineHeight: number; + rotation: number; + pathData: string; + cursor?: string; + onPointerDown?: (event: React.PointerEvent) => void; + onPointerMove?: (event: React.PointerEvent) => void; + onPointerUp?: (event: React.PointerEvent) => void; +}) { + return ( + + ); +} + +export function LineOverlay({ + start, + end, + onPointerDown, + onPointerMove, + onPointerUp, +}: { + start: { x: number; y: number }; + end: { x: number; y: number }; + onPointerDown?: (event: React.PointerEvent) => void; + onPointerMove?: (event: React.PointerEvent) => void; + onPointerUp?: (event: React.PointerEvent) => void; +}) { + const dx = end.x - start.x; + const dy = end.y - start.y; + const length = Math.sqrt(dx * dx + dy * dy); + const angleDeg = (Math.atan2(dy, dx) * 180) / Math.PI; + const cx = (start.x + end.x) / 2; + const cy = (start.y + end.y) / 2; + + const sharedStyle = { + left: cx - length / 2, + width: length, + transform: `rotate(${angleDeg}deg)`, + transformOrigin: "center center", + }; + + return ( + <> + {onPointerDown && ( +
+ )} +
+ + ); +} diff --git a/apps/web/src/components/editor/panels/preview/index.tsx b/apps/web/src/components/editor/panels/preview/index.tsx index c625b9bf..8cfe7f49 100644 --- a/apps/web/src/components/editor/panels/preview/index.tsx +++ b/apps/web/src/components/editor/panels/preview/index.tsx @@ -9,7 +9,6 @@ import { useFullscreen } from "@/hooks/use-fullscreen"; import { CanvasRenderer } from "@/services/renderer/canvas-renderer"; import type { RootNode } from "@/services/renderer/nodes/root-node"; import { buildScene } from "@/services/renderer/scene-builder"; -import { getLastFrameTime } from "@/lib/time"; import { PreviewInteractionOverlay } from "./preview-interaction-overlay"; import { BookmarkNoteOverlay } from "./bookmark-note-overlay"; import { GuideOverlay } from "./guide-overlay"; @@ -134,12 +133,7 @@ function PreviewCanvas({ const render = useCallback(() => { if (canvasRef.current && renderTree && !renderingRef.current) { - const time = editor.playback.getCurrentTime(); - const lastFrameTime = getLastFrameTime({ - duration: renderTree.duration, - fps: renderer.fps, - }); - const renderTime = Math.min(time, lastFrameTime); + const renderTime = editor.playback.getCurrentTime(); const frame = Math.floor(renderTime * renderer.fps); if ( diff --git a/apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx b/apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx index 65c11726..515cb91a 100644 --- a/apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx +++ b/apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx @@ -1,117 +1,117 @@ -import { useState } from "react"; -import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; -import { usePreviewInteraction } from "@/hooks/use-preview-interaction"; -import type { SnapLine } from "@/lib/preview/preview-snap"; -import { TransformHandles } from "./transform-handles"; -import { MaskHandles } from "./mask-handles"; -import { SnapGuides } from "./snap-guides"; -import { TextEditOverlay } from "./text-edit-overlay"; -import { usePropertiesStore } from "../properties/stores/properties-store"; -import { useEditor } from "@/hooks/use-editor"; - -export function PreviewInteractionOverlay() { - const [snapLines, setSnapLines] = useState([]); - const editor = useEditor(); - const viewport = usePreviewViewport(); - const selectedElements = useEditor((e) => e.selection.getSelectedElements()); - const activeTabPerType = usePropertiesStore((s) => s.activeTabPerType); - - const selectedRef = - selectedElements.length === 1 ? selectedElements[0] : null; - const activeTrack = selectedRef - ? editor.timeline.getTrackById({ trackId: selectedRef.trackId }) - : null; - const activeElement = - activeTrack?.elements.find( - (element) => element.id === selectedRef?.elementId, - ) ?? null; - const isMaskMode = activeElement - ? activeTabPerType[activeElement.type] === "masks" - : false; - - const { - onPointerDown, - onPointerMove, - onPointerUp, - onDoubleClick, - editingText, - commitTextEdit, - } = usePreviewInteraction({ - onSnapLinesChange: setSnapLines, - isMaskMode, - }); - - const handlePointerDown = (event: React.PointerEvent) => { - if (viewport.handlePanPointerDown({ event })) { - return; - } - - onPointerDown(event); - }; - - const handlePointerMove = (event: React.PointerEvent) => { - if (viewport.handlePanPointerMove({ event })) { - return; - } - - onPointerMove(event); - }; - - const handlePointerUp = (event: React.PointerEvent) => { - if (viewport.handlePanPointerUp({ event })) { - return; - } - - onPointerUp(event); - }; - - const handleContextMenu = (e: React.MouseEvent) => { - const x = e.nativeEvent.offsetX; - const y = e.nativeEvent.offsetY; - const outsideCanvas = - x < viewport.sceneLeft || - x > viewport.sceneLeft + viewport.sceneWidth || - y < viewport.sceneTop || - y > viewport.sceneTop + viewport.sceneHeight; - if (outsideCanvas) { - e.stopPropagation(); - } - }; - - return ( -
-
e.preventDefault()} - onContextMenu={handleContextMenu} - /> - {editingText ? ( - - ) : isMaskMode ? ( - - ) : ( - - )} - -
- ); -} +import { useState } from "react"; +import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; +import { usePreviewInteraction } from "@/hooks/use-preview-interaction"; +import type { SnapLine } from "@/lib/preview/preview-snap"; +import { TransformHandles } from "./transform-handles"; +import { MaskHandles } from "./mask-handles"; +import { SnapGuides } from "./snap-guides"; +import { TextEditOverlay } from "./text-edit-overlay"; +import { usePropertiesStore } from "../properties/stores/properties-store"; +import { useEditor } from "@/hooks/use-editor"; + +export function PreviewInteractionOverlay() { + const [snapLines, setSnapLines] = useState([]); + const editor = useEditor(); + const viewport = usePreviewViewport(); + const selectedElements = useEditor((e) => e.selection.getSelectedElements()); + const activeTabPerType = usePropertiesStore((s) => s.activeTabPerType); + + const selectedRef = + selectedElements.length === 1 ? selectedElements[0] : null; + const activeTrack = selectedRef + ? editor.timeline.getTrackById({ trackId: selectedRef.trackId }) + : null; + const activeElement = + activeTrack?.elements.find( + (element) => element.id === selectedRef?.elementId, + ) ?? null; + const isMaskMode = activeElement + ? activeTabPerType[activeElement.type] === "masks" + : false; + + const { + onPointerDown, + onPointerMove, + onPointerUp, + onDoubleClick, + editingText, + commitTextEdit, + } = usePreviewInteraction({ + onSnapLinesChange: setSnapLines, + isMaskMode, + }); + + const handlePointerDown = (event: React.PointerEvent) => { + if (viewport.handlePanPointerDown({ event })) { + return; + } + + onPointerDown(event); + }; + + const handlePointerMove = (event: React.PointerEvent) => { + if (viewport.handlePanPointerMove({ event })) { + return; + } + + onPointerMove(event); + }; + + const handlePointerUp = (event: React.PointerEvent) => { + if (viewport.handlePanPointerUp({ event })) { + return; + } + + onPointerUp(event); + }; + + const handleContextMenu = (e: React.MouseEvent) => { + const x = e.nativeEvent.offsetX; + const y = e.nativeEvent.offsetY; + const outsideCanvas = + x < viewport.sceneLeft || + x > viewport.sceneLeft + viewport.sceneWidth || + y < viewport.sceneTop || + y > viewport.sceneTop + viewport.sceneHeight; + if (outsideCanvas) { + e.stopPropagation(); + } + }; + + return ( +
+
e.preventDefault()} + onContextMenu={handleContextMenu} + /> + {editingText ? ( + + ) : isMaskMode ? ( + + ) : ( + + )} + +
+ ); +} diff --git a/apps/web/src/components/editor/panels/preview/preview-viewport.tsx b/apps/web/src/components/editor/panels/preview/preview-viewport.tsx index e38183af..8fd38768 100644 --- a/apps/web/src/components/editor/panels/preview/preview-viewport.tsx +++ b/apps/web/src/components/editor/panels/preview/preview-viewport.tsx @@ -1,576 +1,576 @@ -"use client"; - -import { - createContext, - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from "react"; -import { - canvasToOverlay, - getDisplayScale, - positionToOverlay, - screenPixelsToLogicalThreshold, - screenToCanvas, -} from "@/lib/preview/preview-coords"; -import { clamp, isNearlyEqual } from "@/utils/math"; -import { PREVIEW_ZOOM } from "@/constants/editor-constants"; - -const MIDDLE_MOUSE_BUTTON = 1; -const IS_AT_FIT_EPSILON = 0.001; -const IS_AT_ACTUAL_SIZE_EPSILON = 0.01; - -interface PreviewViewportContextValue { - canPan: boolean; - isAtFit: boolean; - isAtActualSize: boolean; - isPanning: boolean; - sceneHeight: number; - sceneLeft: number; - sceneTop: number; - sceneWidth: number; - zoomPercent: number; - canvasToOverlay: ({ - canvasX, - canvasY, - }: { - canvasX: number; - canvasY: number; - }) => { x: number; y: number }; - fitToScreen: () => void; - getDisplayScale: () => { x: number; y: number }; - handlePanPointerDown: ({ event }: { event: React.PointerEvent }) => boolean; - handlePanPointerMove: ({ event }: { event: React.PointerEvent }) => boolean; - handlePanPointerUp: ({ event }: { event: React.PointerEvent }) => boolean; - positionToOverlay: ({ - positionX, - positionY, - }: { - positionX: number; - positionY: number; - }) => { x: number; y: number }; - panByScreenDelta: ({ - deltaX, - deltaY, - }: { - deltaX: number; - deltaY: number; - }) => void; - resetPan: () => void; - scaleZoom: ({ factor }: { factor: number }) => void; - screenPixelsToLogicalThreshold: ({ - screenPixels, - }: { - screenPixels: number; - }) => { x: number; y: number }; - screenToCanvas: ({ - clientX, - clientY, - }: { - clientX: number; - clientY: number; - }) => { x: number; y: number } | null; - setActualSize: () => void; - setViewportPercent: ({ percent }: { percent: number }) => void; - zoomIn: () => void; - zoomOut: () => void; -} - -const PreviewViewportContext = - createContext(null); - -interface PreviewViewportProviderProps { - children: React.ReactNode; - value: PreviewViewportContextValue; -} - -interface PreviewViewportStateOptions { - canvasHeight: number; - canvasWidth: number; - viewportHeight: number; - viewportRef: React.RefObject; - viewportWidth: number; -} - -interface PanSession { - centerX: number; - centerY: number; - clientX: number; - clientY: number; - pointerId: number; -} - -function getClampedCenterAxis({ - axisSize, - center, - scale, - viewportSize, -}: { - axisSize: number; - center: number; - scale: number; - viewportSize: number; -}): number { - if (axisSize <= 0 || scale <= 0 || viewportSize <= 0) { - return axisSize / 2; - } - - const visibleHalfSpan = viewportSize / (2 * scale); - if (visibleHalfSpan >= axisSize / 2) { - return axisSize / 2; - } - - return clamp({ - value: center, - min: visibleHalfSpan, - max: axisSize - visibleHalfSpan, - }); -} - -function clampViewportCenter({ - canvasHeight, - canvasWidth, - centerX, - centerY, - scale, - viewportHeight, - viewportWidth, -}: { - canvasHeight: number; - canvasWidth: number; - centerX: number; - centerY: number; - scale: number; - viewportHeight: number; - viewportWidth: number; -}): { x: number; y: number } { - return { - x: getClampedCenterAxis({ - center: centerX, - axisSize: canvasWidth, - scale, - viewportSize: viewportWidth, - }), - y: getClampedCenterAxis({ - center: centerY, - axisSize: canvasHeight, - scale, - viewportSize: viewportHeight, - }), - }; -} - -function getFitScale({ - canvasHeight, - canvasWidth, - viewportHeight, - viewportWidth, -}: { - canvasHeight: number; - canvasWidth: number; - viewportHeight: number; - viewportWidth: number; -}): number { - if ( - canvasHeight <= 0 || - canvasWidth <= 0 || - viewportHeight <= 0 || - viewportWidth <= 0 - ) { - return 1; - } - - return Math.min(viewportWidth / canvasWidth, viewportHeight / canvasHeight); -} - -function getClampedZoom({ zoom }: { zoom: number }): number { - return clamp({ - value: zoom, - min: PREVIEW_ZOOM.min, - max: PREVIEW_ZOOM.max, - }); -} - -export function PreviewViewportProvider({ - children, - value, -}: PreviewViewportProviderProps) { - return ( - - {children} - - ); -} - -export function usePreviewViewportState({ - canvasHeight, - canvasWidth, - viewportHeight, - viewportRef, - viewportWidth, -}: PreviewViewportStateOptions): PreviewViewportContextValue { - const [zoom, setZoomState] = useState(1); - const [center, setCenter] = useState(() => ({ - x: canvasWidth / 2, - y: canvasHeight / 2, - })); - const [isPanning, setIsPanning] = useState(false); - const panSessionRef = useRef(null); - const centerRef = useRef(center); - centerRef.current = center; - - const fitScale = useMemo( - () => - getFitScale({ - canvasHeight, - canvasWidth, - viewportHeight, - viewportWidth, - }), - [canvasHeight, canvasWidth, viewportHeight, viewportWidth], - ); - const viewportScale = fitScale * zoom; - const geometry = useMemo( - () => ({ - canvasHeight, - canvasWidth, - centerX: center.x, - centerY: center.y, - scale: viewportScale, - viewportHeight, - viewportWidth, - }), - [ - canvasHeight, - canvasWidth, - center.x, - center.y, - viewportHeight, - viewportScale, - viewportWidth, - ], - ); - - const scaleZoom = useCallback(({ factor }: { factor: number }) => { - setZoomState((previousZoom) => - getClampedZoom({ - zoom: previousZoom * factor, - }), - ); - }, []); - - const panByScreenDelta = useCallback( - ({ deltaX, deltaY }: { deltaX: number; deltaY: number }) => { - if (zoom <= 1 || (deltaX === 0 && deltaY === 0)) { - return; - } - - setCenter((previousCenter) => - clampViewportCenter({ - canvasHeight, - canvasWidth, - centerX: previousCenter.x + deltaX / viewportScale, - centerY: previousCenter.y + deltaY / viewportScale, - scale: viewportScale, - viewportHeight, - viewportWidth, - }), - ); - }, - [ - canvasHeight, - canvasWidth, - viewportHeight, - viewportScale, - viewportWidth, - zoom, - ], - ); - - const resetPan = useCallback(() => { - setCenter({ - x: canvasWidth / 2, - y: canvasHeight / 2, - }); - }, [canvasHeight, canvasWidth]); - - const fitToScreen = useCallback(() => { - setZoomState(1); - setCenter({ - x: canvasWidth / 2, - y: canvasHeight / 2, - }); - }, [canvasHeight, canvasWidth]); - - const zoomIn = useCallback(() => { - setZoomState((previousZoom) => - getClampedZoom({ - zoom: previousZoom * PREVIEW_ZOOM.step, - }), - ); - }, []); - - const zoomOut = useCallback(() => { - setZoomState((previousZoom) => - getClampedZoom({ - zoom: previousZoom / PREVIEW_ZOOM.step, - }), - ); - }, []); - - const setActualSize = useCallback(() => { - const actualSizeZoom = fitScale > 0 ? 1 / fitScale : 1; - setZoomState( - getClampedZoom({ - zoom: actualSizeZoom, - }), - ); - }, [fitScale]); - - const setViewportPercent = useCallback( - ({ percent }: { percent: number }) => { - const targetZoom = fitScale > 0 ? percent / 100 / fitScale : 1; - setZoomState(getClampedZoom({ zoom: targetZoom })); - }, - [fitScale], - ); - - const getViewportRect = useCallback( - () => viewportRef.current?.getBoundingClientRect() ?? null, - [viewportRef], - ); - - const screenToCanvasWithViewport = useCallback( - ({ clientX, clientY }: { clientX: number; clientY: number }) => { - const viewportRect = getViewportRect(); - if (!viewportRect) return null; - - return screenToCanvas({ - clientX, - clientY, - geometry, - viewportRect, - }); - }, - [getViewportRect, geometry], - ); - - const canvasToOverlayWithViewport = useCallback( - ({ canvasX, canvasY }: { canvasX: number; canvasY: number }) => - canvasToOverlay({ - canvasX, - canvasY, - geometry, - }), - [geometry], - ); - - const positionToOverlayWithViewport = useCallback( - ({ positionX, positionY }: { positionX: number; positionY: number }) => - positionToOverlay({ - geometry, - positionX, - positionY, - }), - [geometry], - ); - - const getDisplayScaleWithViewport = useCallback( - () => getDisplayScale({ geometry }), - [geometry], - ); - - const getLogicalThresholdForScreenPixels = useCallback( - ({ screenPixels }: { screenPixels: number }) => - screenPixelsToLogicalThreshold({ - geometry, - screenPixels, - }), - [geometry], - ); - - const handlePanPointerDown = useCallback( - ({ event }: { event: React.PointerEvent }) => { - if (event.button !== MIDDLE_MOUSE_BUTTON || zoom <= 1) { - return false; - } - - event.preventDefault(); - event.stopPropagation(); - - panSessionRef.current = { - centerX: centerRef.current.x, - centerY: centerRef.current.y, - clientX: event.clientX, - clientY: event.clientY, - pointerId: event.pointerId, - }; - setIsPanning(true); - (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId); - return true; - }, - [zoom], - ); - - const handlePanPointerMove = useCallback( - ({ event }: { event: React.PointerEvent }) => { - const panSession = panSessionRef.current; - if (!panSession) { - return false; - } - - event.preventDefault(); - event.stopPropagation(); - - const deltaX = event.clientX - panSession.clientX; - const deltaY = event.clientY - panSession.clientY; - const nextCenter = clampViewportCenter({ - canvasHeight, - canvasWidth, - centerX: panSession.centerX - deltaX / viewportScale, - centerY: panSession.centerY - deltaY / viewportScale, - scale: viewportScale, - viewportHeight, - viewportWidth, - }); - - setCenter(nextCenter); - return true; - }, - [canvasHeight, canvasWidth, viewportHeight, viewportScale, viewportWidth], - ); - - const handlePanPointerUp = useCallback( - ({ event }: { event: React.PointerEvent }) => { - const panSession = panSessionRef.current; - if (!panSession) { - return false; - } - - if ( - (event.currentTarget as HTMLElement).hasPointerCapture( - panSession.pointerId, - ) - ) { - (event.currentTarget as HTMLElement).releasePointerCapture( - panSession.pointerId, - ); - } - - panSessionRef.current = null; - setIsPanning(false); - return true; - }, - [], - ); - - useEffect(() => { - setZoomState(1); - setCenter({ - x: canvasWidth / 2, - y: canvasHeight / 2, - }); - panSessionRef.current = null; - setIsPanning(false); - }, [canvasHeight, canvasWidth]); - - useEffect(() => { - setCenter((previousCenter) => - clampViewportCenter({ - canvasHeight, - canvasWidth, - centerX: previousCenter.x, - centerY: previousCenter.y, - scale: viewportScale, - viewportHeight, - viewportWidth, - }), - ); - }, [canvasHeight, canvasWidth, viewportHeight, viewportScale, viewportWidth]); - - const sceneWidth = canvasWidth * viewportScale; - const sceneHeight = canvasHeight * viewportScale; - const sceneLeft = viewportWidth / 2 - center.x * viewportScale; - const sceneTop = viewportHeight / 2 - center.y * viewportScale; - const canPan = zoom > 1; - const zoomPercent = Math.round(viewportScale * 100); - - return useMemo( - () => ({ - canPan, - isAtActualSize: isNearlyEqual({ - leftValue: viewportScale, - rightValue: 1, - epsilon: IS_AT_ACTUAL_SIZE_EPSILON, - }), - isAtFit: isNearlyEqual({ - leftValue: zoom, - rightValue: 1, - epsilon: IS_AT_FIT_EPSILON, - }), - isPanning, - sceneHeight, - sceneLeft, - sceneTop, - sceneWidth, - zoomPercent, - canvasToOverlay: canvasToOverlayWithViewport, - fitToScreen, - getDisplayScale: getDisplayScaleWithViewport, - handlePanPointerDown, - handlePanPointerMove, - handlePanPointerUp, - panByScreenDelta, - positionToOverlay: positionToOverlayWithViewport, - resetPan, - scaleZoom, - screenPixelsToLogicalThreshold: getLogicalThresholdForScreenPixels, - screenToCanvas: screenToCanvasWithViewport, - setActualSize, - setViewportPercent, - zoomIn, - zoomOut, - }), - [ - canPan, - viewportScale, - zoom, - isPanning, - sceneHeight, - sceneLeft, - sceneTop, - sceneWidth, - zoomPercent, - canvasToOverlayWithViewport, - fitToScreen, - getDisplayScaleWithViewport, - handlePanPointerDown, - handlePanPointerMove, - handlePanPointerUp, - panByScreenDelta, - positionToOverlayWithViewport, - resetPan, - scaleZoom, - getLogicalThresholdForScreenPixels, - screenToCanvasWithViewport, - setActualSize, - setViewportPercent, - zoomIn, - zoomOut, - ], - ); -} - -export function usePreviewViewport() { - const context = useContext(PreviewViewportContext); - if (!context) { - throw new Error( - "usePreviewViewport must be used within PreviewViewportProvider", - ); - } - - return context; -} +"use client"; + +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { + canvasToOverlay, + getDisplayScale, + positionToOverlay, + screenPixelsToLogicalThreshold, + screenToCanvas, +} from "@/lib/preview/preview-coords"; +import { clamp, isNearlyEqual } from "@/utils/math"; +import { PREVIEW_ZOOM } from "@/constants/editor-constants"; + +const MIDDLE_MOUSE_BUTTON = 1; +const IS_AT_FIT_EPSILON = 0.001; +const IS_AT_ACTUAL_SIZE_EPSILON = 0.01; + +interface PreviewViewportContextValue { + canPan: boolean; + isAtFit: boolean; + isAtActualSize: boolean; + isPanning: boolean; + sceneHeight: number; + sceneLeft: number; + sceneTop: number; + sceneWidth: number; + zoomPercent: number; + canvasToOverlay: ({ + canvasX, + canvasY, + }: { + canvasX: number; + canvasY: number; + }) => { x: number; y: number }; + fitToScreen: () => void; + getDisplayScale: () => { x: number; y: number }; + handlePanPointerDown: ({ event }: { event: React.PointerEvent }) => boolean; + handlePanPointerMove: ({ event }: { event: React.PointerEvent }) => boolean; + handlePanPointerUp: ({ event }: { event: React.PointerEvent }) => boolean; + positionToOverlay: ({ + positionX, + positionY, + }: { + positionX: number; + positionY: number; + }) => { x: number; y: number }; + panByScreenDelta: ({ + deltaX, + deltaY, + }: { + deltaX: number; + deltaY: number; + }) => void; + resetPan: () => void; + scaleZoom: ({ factor }: { factor: number }) => void; + screenPixelsToLogicalThreshold: ({ + screenPixels, + }: { + screenPixels: number; + }) => { x: number; y: number }; + screenToCanvas: ({ + clientX, + clientY, + }: { + clientX: number; + clientY: number; + }) => { x: number; y: number } | null; + setActualSize: () => void; + setViewportPercent: ({ percent }: { percent: number }) => void; + zoomIn: () => void; + zoomOut: () => void; +} + +const PreviewViewportContext = + createContext(null); + +interface PreviewViewportProviderProps { + children: React.ReactNode; + value: PreviewViewportContextValue; +} + +interface PreviewViewportStateOptions { + canvasHeight: number; + canvasWidth: number; + viewportHeight: number; + viewportRef: React.RefObject; + viewportWidth: number; +} + +interface PanSession { + centerX: number; + centerY: number; + clientX: number; + clientY: number; + pointerId: number; +} + +function getClampedCenterAxis({ + axisSize, + center, + scale, + viewportSize, +}: { + axisSize: number; + center: number; + scale: number; + viewportSize: number; +}): number { + if (axisSize <= 0 || scale <= 0 || viewportSize <= 0) { + return axisSize / 2; + } + + const visibleHalfSpan = viewportSize / (2 * scale); + if (visibleHalfSpan >= axisSize / 2) { + return axisSize / 2; + } + + return clamp({ + value: center, + min: visibleHalfSpan, + max: axisSize - visibleHalfSpan, + }); +} + +function clampViewportCenter({ + canvasHeight, + canvasWidth, + centerX, + centerY, + scale, + viewportHeight, + viewportWidth, +}: { + canvasHeight: number; + canvasWidth: number; + centerX: number; + centerY: number; + scale: number; + viewportHeight: number; + viewportWidth: number; +}): { x: number; y: number } { + return { + x: getClampedCenterAxis({ + center: centerX, + axisSize: canvasWidth, + scale, + viewportSize: viewportWidth, + }), + y: getClampedCenterAxis({ + center: centerY, + axisSize: canvasHeight, + scale, + viewportSize: viewportHeight, + }), + }; +} + +function getFitScale({ + canvasHeight, + canvasWidth, + viewportHeight, + viewportWidth, +}: { + canvasHeight: number; + canvasWidth: number; + viewportHeight: number; + viewportWidth: number; +}): number { + if ( + canvasHeight <= 0 || + canvasWidth <= 0 || + viewportHeight <= 0 || + viewportWidth <= 0 + ) { + return 1; + } + + return Math.min(viewportWidth / canvasWidth, viewportHeight / canvasHeight); +} + +function getClampedZoom({ zoom }: { zoom: number }): number { + return clamp({ + value: zoom, + min: PREVIEW_ZOOM.min, + max: PREVIEW_ZOOM.max, + }); +} + +export function PreviewViewportProvider({ + children, + value, +}: PreviewViewportProviderProps) { + return ( + + {children} + + ); +} + +export function usePreviewViewportState({ + canvasHeight, + canvasWidth, + viewportHeight, + viewportRef, + viewportWidth, +}: PreviewViewportStateOptions): PreviewViewportContextValue { + const [zoom, setZoomState] = useState(1); + const [center, setCenter] = useState(() => ({ + x: canvasWidth / 2, + y: canvasHeight / 2, + })); + const [isPanning, setIsPanning] = useState(false); + const panSessionRef = useRef(null); + const centerRef = useRef(center); + centerRef.current = center; + + const fitScale = useMemo( + () => + getFitScale({ + canvasHeight, + canvasWidth, + viewportHeight, + viewportWidth, + }), + [canvasHeight, canvasWidth, viewportHeight, viewportWidth], + ); + const viewportScale = fitScale * zoom; + const geometry = useMemo( + () => ({ + canvasHeight, + canvasWidth, + centerX: center.x, + centerY: center.y, + scale: viewportScale, + viewportHeight, + viewportWidth, + }), + [ + canvasHeight, + canvasWidth, + center.x, + center.y, + viewportHeight, + viewportScale, + viewportWidth, + ], + ); + + const scaleZoom = useCallback(({ factor }: { factor: number }) => { + setZoomState((previousZoom) => + getClampedZoom({ + zoom: previousZoom * factor, + }), + ); + }, []); + + const panByScreenDelta = useCallback( + ({ deltaX, deltaY }: { deltaX: number; deltaY: number }) => { + if (zoom <= 1 || (deltaX === 0 && deltaY === 0)) { + return; + } + + setCenter((previousCenter) => + clampViewportCenter({ + canvasHeight, + canvasWidth, + centerX: previousCenter.x + deltaX / viewportScale, + centerY: previousCenter.y + deltaY / viewportScale, + scale: viewportScale, + viewportHeight, + viewportWidth, + }), + ); + }, + [ + canvasHeight, + canvasWidth, + viewportHeight, + viewportScale, + viewportWidth, + zoom, + ], + ); + + const resetPan = useCallback(() => { + setCenter({ + x: canvasWidth / 2, + y: canvasHeight / 2, + }); + }, [canvasHeight, canvasWidth]); + + const fitToScreen = useCallback(() => { + setZoomState(1); + setCenter({ + x: canvasWidth / 2, + y: canvasHeight / 2, + }); + }, [canvasHeight, canvasWidth]); + + const zoomIn = useCallback(() => { + setZoomState((previousZoom) => + getClampedZoom({ + zoom: previousZoom * PREVIEW_ZOOM.step, + }), + ); + }, []); + + const zoomOut = useCallback(() => { + setZoomState((previousZoom) => + getClampedZoom({ + zoom: previousZoom / PREVIEW_ZOOM.step, + }), + ); + }, []); + + const setActualSize = useCallback(() => { + const actualSizeZoom = fitScale > 0 ? 1 / fitScale : 1; + setZoomState( + getClampedZoom({ + zoom: actualSizeZoom, + }), + ); + }, [fitScale]); + + const setViewportPercent = useCallback( + ({ percent }: { percent: number }) => { + const targetZoom = fitScale > 0 ? percent / 100 / fitScale : 1; + setZoomState(getClampedZoom({ zoom: targetZoom })); + }, + [fitScale], + ); + + const getViewportRect = useCallback( + () => viewportRef.current?.getBoundingClientRect() ?? null, + [viewportRef], + ); + + const screenToCanvasWithViewport = useCallback( + ({ clientX, clientY }: { clientX: number; clientY: number }) => { + const viewportRect = getViewportRect(); + if (!viewportRect) return null; + + return screenToCanvas({ + clientX, + clientY, + geometry, + viewportRect, + }); + }, + [getViewportRect, geometry], + ); + + const canvasToOverlayWithViewport = useCallback( + ({ canvasX, canvasY }: { canvasX: number; canvasY: number }) => + canvasToOverlay({ + canvasX, + canvasY, + geometry, + }), + [geometry], + ); + + const positionToOverlayWithViewport = useCallback( + ({ positionX, positionY }: { positionX: number; positionY: number }) => + positionToOverlay({ + geometry, + positionX, + positionY, + }), + [geometry], + ); + + const getDisplayScaleWithViewport = useCallback( + () => getDisplayScale({ geometry }), + [geometry], + ); + + const getLogicalThresholdForScreenPixels = useCallback( + ({ screenPixels }: { screenPixels: number }) => + screenPixelsToLogicalThreshold({ + geometry, + screenPixels, + }), + [geometry], + ); + + const handlePanPointerDown = useCallback( + ({ event }: { event: React.PointerEvent }) => { + if (event.button !== MIDDLE_MOUSE_BUTTON || zoom <= 1) { + return false; + } + + event.preventDefault(); + event.stopPropagation(); + + panSessionRef.current = { + centerX: centerRef.current.x, + centerY: centerRef.current.y, + clientX: event.clientX, + clientY: event.clientY, + pointerId: event.pointerId, + }; + setIsPanning(true); + (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId); + return true; + }, + [zoom], + ); + + const handlePanPointerMove = useCallback( + ({ event }: { event: React.PointerEvent }) => { + const panSession = panSessionRef.current; + if (!panSession) { + return false; + } + + event.preventDefault(); + event.stopPropagation(); + + const deltaX = event.clientX - panSession.clientX; + const deltaY = event.clientY - panSession.clientY; + const nextCenter = clampViewportCenter({ + canvasHeight, + canvasWidth, + centerX: panSession.centerX - deltaX / viewportScale, + centerY: panSession.centerY - deltaY / viewportScale, + scale: viewportScale, + viewportHeight, + viewportWidth, + }); + + setCenter(nextCenter); + return true; + }, + [canvasHeight, canvasWidth, viewportHeight, viewportScale, viewportWidth], + ); + + const handlePanPointerUp = useCallback( + ({ event }: { event: React.PointerEvent }) => { + const panSession = panSessionRef.current; + if (!panSession) { + return false; + } + + if ( + (event.currentTarget as HTMLElement).hasPointerCapture( + panSession.pointerId, + ) + ) { + (event.currentTarget as HTMLElement).releasePointerCapture( + panSession.pointerId, + ); + } + + panSessionRef.current = null; + setIsPanning(false); + return true; + }, + [], + ); + + useEffect(() => { + setZoomState(1); + setCenter({ + x: canvasWidth / 2, + y: canvasHeight / 2, + }); + panSessionRef.current = null; + setIsPanning(false); + }, [canvasHeight, canvasWidth]); + + useEffect(() => { + setCenter((previousCenter) => + clampViewportCenter({ + canvasHeight, + canvasWidth, + centerX: previousCenter.x, + centerY: previousCenter.y, + scale: viewportScale, + viewportHeight, + viewportWidth, + }), + ); + }, [canvasHeight, canvasWidth, viewportHeight, viewportScale, viewportWidth]); + + const sceneWidth = canvasWidth * viewportScale; + const sceneHeight = canvasHeight * viewportScale; + const sceneLeft = viewportWidth / 2 - center.x * viewportScale; + const sceneTop = viewportHeight / 2 - center.y * viewportScale; + const canPan = zoom > 1; + const zoomPercent = Math.round(viewportScale * 100); + + return useMemo( + () => ({ + canPan, + isAtActualSize: isNearlyEqual({ + leftValue: viewportScale, + rightValue: 1, + epsilon: IS_AT_ACTUAL_SIZE_EPSILON, + }), + isAtFit: isNearlyEqual({ + leftValue: zoom, + rightValue: 1, + epsilon: IS_AT_FIT_EPSILON, + }), + isPanning, + sceneHeight, + sceneLeft, + sceneTop, + sceneWidth, + zoomPercent, + canvasToOverlay: canvasToOverlayWithViewport, + fitToScreen, + getDisplayScale: getDisplayScaleWithViewport, + handlePanPointerDown, + handlePanPointerMove, + handlePanPointerUp, + panByScreenDelta, + positionToOverlay: positionToOverlayWithViewport, + resetPan, + scaleZoom, + screenPixelsToLogicalThreshold: getLogicalThresholdForScreenPixels, + screenToCanvas: screenToCanvasWithViewport, + setActualSize, + setViewportPercent, + zoomIn, + zoomOut, + }), + [ + canPan, + viewportScale, + zoom, + isPanning, + sceneHeight, + sceneLeft, + sceneTop, + sceneWidth, + zoomPercent, + canvasToOverlayWithViewport, + fitToScreen, + getDisplayScaleWithViewport, + handlePanPointerDown, + handlePanPointerMove, + handlePanPointerUp, + panByScreenDelta, + positionToOverlayWithViewport, + resetPan, + scaleZoom, + getLogicalThresholdForScreenPixels, + screenToCanvasWithViewport, + setActualSize, + setViewportPercent, + zoomIn, + zoomOut, + ], + ); +} + +export function usePreviewViewport() { + const context = useContext(PreviewViewportContext); + if (!context) { + throw new Error( + "usePreviewViewport must be used within PreviewViewportProvider", + ); + } + + return context; +} diff --git a/apps/web/src/components/editor/panels/preview/snap-guides.tsx b/apps/web/src/components/editor/panels/preview/snap-guides.tsx index 744304ea..ced5e921 100644 --- a/apps/web/src/components/editor/panels/preview/snap-guides.tsx +++ b/apps/web/src/components/editor/panels/preview/snap-guides.tsx @@ -1,47 +1,47 @@ -"use client"; - -import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; -import type { SnapLine } from "@/lib/preview/preview-snap"; - -export function SnapGuides({ lines }: { lines: SnapLine[] }) { - const viewport = usePreviewViewport(); - - if (lines.length === 0) { - return null; - } - - const toOverlayX = (logicalX: number) => - viewport.positionToOverlay({ - positionX: logicalX, - positionY: 0, - }).x; - - const toOverlayY = (logicalY: number) => - viewport.positionToOverlay({ - positionX: 0, - positionY: logicalY, - }).y; - - return ( -
- {lines.map((line) => { - if (line.type === "vertical") { - return ( -
- ); - } - return ( -
- ); - })} -
- ); -} +"use client"; + +import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; +import type { SnapLine } from "@/lib/preview/preview-snap"; + +export function SnapGuides({ lines }: { lines: SnapLine[] }) { + const viewport = usePreviewViewport(); + + if (lines.length === 0) { + return null; + } + + const toOverlayX = (logicalX: number) => + viewport.positionToOverlay({ + positionX: logicalX, + positionY: 0, + }).x; + + const toOverlayY = (logicalY: number) => + viewport.positionToOverlay({ + positionX: 0, + positionY: logicalY, + }).y; + + return ( +
+ {lines.map((line) => { + if (line.type === "vertical") { + return ( +
+ ); + } + return ( +
+ ); + })} +
+ ); +} diff --git a/apps/web/src/components/editor/panels/preview/text-edit-overlay.tsx b/apps/web/src/components/editor/panels/preview/text-edit-overlay.tsx index d3c61a24..aff77f0f 100644 --- a/apps/web/src/components/editor/panels/preview/text-edit-overlay.tsx +++ b/apps/web/src/components/editor/panels/preview/text-edit-overlay.tsx @@ -1,212 +1,212 @@ -"use client"; - -import { useCallback, useEffect, useRef } from "react"; -import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; -import { useEditor } from "@/hooks/use-editor"; -import type { TextElement } from "@/lib/timeline"; -import { - FONT_SIZE_SCALE_REFERENCE, -} from "@/constants/text-constants"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import { - getMetricAscent, - getMetricDescent, - setCanvasLetterSpacing, -} from "@/lib/text/layout"; - -let cachedCanvas: HTMLCanvasElement | null = null; - -function getMeasurementContext(): CanvasRenderingContext2D | null { - if (!cachedCanvas) cachedCanvas = document.createElement("canvas"); - return cachedCanvas.getContext("2d"); -} - -/** - * Uses fontBoundingBox metrics (which CSS uses for line box layout) rather than - * actualBoundingBox to model where the browser places the baseline within the - * line box, then measures actual glyph bounds to find the visual center. - */ -function measureCSSVisualCenterOffset({ - lines, - fontString, - letterSpacingPx, - lineHeightPx, - displayFontSize, -}: { - lines: string[]; - fontString: string; - letterSpacingPx: number; - lineHeightPx: number; - displayFontSize: number; -}): number { - const ctx = getMeasurementContext(); - if (!ctx) return 0; - - ctx.font = fontString; - ctx.textBaseline = "alphabetic"; - setCanvasLetterSpacing({ ctx, letterSpacingPx }); - - const probe = ctx.measureText("M"); - const fontAscent = probe.fontBoundingBoxAscent ?? displayFontSize * 0.8; - const fontDescent = probe.fontBoundingBoxDescent ?? displayFontSize * 0.2; - const halfLeading = (lineHeightPx - fontAscent - fontDescent) / 2; - - let visualTop = Number.POSITIVE_INFINITY; - let visualBottom = Number.NEGATIVE_INFINITY; - - for (let i = 0; i < lines.length; i++) { - const metrics = ctx.measureText(lines[i] || " "); - const baseline = i * lineHeightPx + halfLeading + fontAscent; - visualTop = Math.min( - visualTop, - baseline - getMetricAscent({ metrics, fallbackFontSize: displayFontSize }), - ); - visualBottom = Math.max( - visualBottom, - baseline + getMetricDescent({ metrics, fallbackFontSize: displayFontSize }), - ); - } - - const cssBlockHeight = lines.length * lineHeightPx; - return (visualTop + visualBottom) / 2 - cssBlockHeight / 2; -} - -export function TextEditOverlay({ - trackId, - elementId, - element, - onCommit, -}: { - trackId: string; - elementId: string; - element: TextElement; - onCommit: () => void; -}) { - const editor = useEditor(); - const viewport = usePreviewViewport(); - const divRef = useRef(null); - - useEffect(() => { - const div = divRef.current; - if (!div) return; - div.focus(); - const range = document.createRange(); - range.selectNodeContents(div); - const selection = window.getSelection(); - selection?.removeAllRanges(); - selection?.addRange(range); - }, []); - - const handleInput = useCallback(() => { - const div = divRef.current; - if (!div) return; - const text = div.innerText; - editor.timeline.previewElements({ - updates: [{ trackId, elementId, updates: { content: text } }], - }); - }, [editor.timeline, trackId, elementId]); - - const handleKeyDown = useCallback( - ({ event }: { event: React.KeyboardEvent }) => { - const { key } = event; - if (key === "Escape") { - event.preventDefault(); - onCommit(); - return; - } - }, - [onCommit], - ); - - const canvasSize = editor.project.getActive().settings.canvasSize; - - if (!canvasSize) return null; - - const { x: posX, y: posY } = viewport.positionToOverlay({ - positionX: element.transform.position.x, - positionY: element.transform.position.y, - }); - - const { x: displayScaleX } = viewport.getDisplayScale(); - - const displayFontSize = - element.fontSize * - (canvasSize.height / FONT_SIZE_SCALE_REFERENCE) * - displayScaleX; - - const lineHeight = element.lineHeight ?? DEFAULTS.text.lineHeight; - const fontWeight = element.fontWeight === "bold" ? "bold" : "normal"; - const fontStyle = element.fontStyle === "italic" ? "italic" : "normal"; - const displayLetterSpacing = (element.letterSpacing ?? 0) * displayScaleX; - const lineHeightPx = displayFontSize * lineHeight; - const lines = (element.content || "").split("\n"); - const fontString = `${fontStyle} ${fontWeight} ${displayFontSize}px "${element.fontFamily}", sans-serif`; - - const cssVisualCenterOffset = measureCSSVisualCenterOffset({ - lines, - fontString, - letterSpacingPx: displayLetterSpacing, - lineHeightPx, - displayFontSize, - }); - - const bg = element.background; - const shouldShowBackground = - bg.enabled && bg.color && bg.color !== "transparent"; - const fontSizeRatio = element.fontSize / DEFAULTS.text.element.fontSize; - const displayPaddingX = shouldShowBackground - ? (bg.paddingX ?? DEFAULTS.text.background.paddingX) * - fontSizeRatio * - displayScaleX - : 0; - const displayPaddingY = shouldShowBackground - ? (bg.paddingY ?? DEFAULTS.text.background.paddingY) * - fontSizeRatio * - displayScaleX - : 0; - - return ( -
- {/* biome-ignore lint/a11y/useSemanticElements: contenteditable required for multiline, IME, paste */} -
handleKeyDown({ event })} - > - {element.content || ""} -
-
- ); -} +"use client"; + +import { useCallback, useEffect, useRef } from "react"; +import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; +import { useEditor } from "@/hooks/use-editor"; +import type { TextElement } from "@/lib/timeline"; +import { + FONT_SIZE_SCALE_REFERENCE, +} from "@/constants/text-constants"; +import { DEFAULTS } from "@/lib/timeline/defaults"; +import { + getMetricAscent, + getMetricDescent, + setCanvasLetterSpacing, +} from "@/lib/text/layout"; + +let cachedCanvas: HTMLCanvasElement | null = null; + +function getMeasurementContext(): CanvasRenderingContext2D | null { + if (!cachedCanvas) cachedCanvas = document.createElement("canvas"); + return cachedCanvas.getContext("2d"); +} + +/** + * Uses fontBoundingBox metrics (which CSS uses for line box layout) rather than + * actualBoundingBox to model where the browser places the baseline within the + * line box, then measures actual glyph bounds to find the visual center. + */ +function measureCSSVisualCenterOffset({ + lines, + fontString, + letterSpacingPx, + lineHeightPx, + displayFontSize, +}: { + lines: string[]; + fontString: string; + letterSpacingPx: number; + lineHeightPx: number; + displayFontSize: number; +}): number { + const ctx = getMeasurementContext(); + if (!ctx) return 0; + + ctx.font = fontString; + ctx.textBaseline = "alphabetic"; + setCanvasLetterSpacing({ ctx, letterSpacingPx }); + + const probe = ctx.measureText("M"); + const fontAscent = probe.fontBoundingBoxAscent ?? displayFontSize * 0.8; + const fontDescent = probe.fontBoundingBoxDescent ?? displayFontSize * 0.2; + const halfLeading = (lineHeightPx - fontAscent - fontDescent) / 2; + + let visualTop = Number.POSITIVE_INFINITY; + let visualBottom = Number.NEGATIVE_INFINITY; + + for (let i = 0; i < lines.length; i++) { + const metrics = ctx.measureText(lines[i] || " "); + const baseline = i * lineHeightPx + halfLeading + fontAscent; + visualTop = Math.min( + visualTop, + baseline - getMetricAscent({ metrics, fallbackFontSize: displayFontSize }), + ); + visualBottom = Math.max( + visualBottom, + baseline + getMetricDescent({ metrics, fallbackFontSize: displayFontSize }), + ); + } + + const cssBlockHeight = lines.length * lineHeightPx; + return (visualTop + visualBottom) / 2 - cssBlockHeight / 2; +} + +export function TextEditOverlay({ + trackId, + elementId, + element, + onCommit, +}: { + trackId: string; + elementId: string; + element: TextElement; + onCommit: () => void; +}) { + const editor = useEditor(); + const viewport = usePreviewViewport(); + const divRef = useRef(null); + + useEffect(() => { + const div = divRef.current; + if (!div) return; + div.focus(); + const range = document.createRange(); + range.selectNodeContents(div); + const selection = window.getSelection(); + selection?.removeAllRanges(); + selection?.addRange(range); + }, []); + + const handleInput = useCallback(() => { + const div = divRef.current; + if (!div) return; + const text = div.innerText; + editor.timeline.previewElements({ + updates: [{ trackId, elementId, updates: { content: text } }], + }); + }, [editor.timeline, trackId, elementId]); + + const handleKeyDown = useCallback( + ({ event }: { event: React.KeyboardEvent }) => { + const { key } = event; + if (key === "Escape") { + event.preventDefault(); + onCommit(); + return; + } + }, + [onCommit], + ); + + const canvasSize = editor.project.getActive().settings.canvasSize; + + if (!canvasSize) return null; + + const { x: posX, y: posY } = viewport.positionToOverlay({ + positionX: element.transform.position.x, + positionY: element.transform.position.y, + }); + + const { x: displayScaleX } = viewport.getDisplayScale(); + + const displayFontSize = + element.fontSize * + (canvasSize.height / FONT_SIZE_SCALE_REFERENCE) * + displayScaleX; + + const lineHeight = element.lineHeight ?? DEFAULTS.text.lineHeight; + const fontWeight = element.fontWeight === "bold" ? "bold" : "normal"; + const fontStyle = element.fontStyle === "italic" ? "italic" : "normal"; + const displayLetterSpacing = (element.letterSpacing ?? 0) * displayScaleX; + const lineHeightPx = displayFontSize * lineHeight; + const lines = (element.content || "").split("\n"); + const fontString = `${fontStyle} ${fontWeight} ${displayFontSize}px "${element.fontFamily}", sans-serif`; + + const cssVisualCenterOffset = measureCSSVisualCenterOffset({ + lines, + fontString, + letterSpacingPx: displayLetterSpacing, + lineHeightPx, + displayFontSize, + }); + + const bg = element.background; + const shouldShowBackground = + bg.enabled && bg.color && bg.color !== "transparent"; + const fontSizeRatio = element.fontSize / DEFAULTS.text.element.fontSize; + const displayPaddingX = shouldShowBackground + ? (bg.paddingX ?? DEFAULTS.text.background.paddingX) * + fontSizeRatio * + displayScaleX + : 0; + const displayPaddingY = shouldShowBackground + ? (bg.paddingY ?? DEFAULTS.text.background.paddingY) * + fontSizeRatio * + displayScaleX + : 0; + + return ( +
+ {/* biome-ignore lint/a11y/useSemanticElements: contenteditable required for multiline, IME, paste */} +
handleKeyDown({ event })} + > + {element.content || ""} +
+
+ ); +} diff --git a/apps/web/src/components/editor/panels/preview/toolbar.tsx b/apps/web/src/components/editor/panels/preview/toolbar.tsx index 56cb784a..5c183bbb 100644 --- a/apps/web/src/components/editor/panels/preview/toolbar.tsx +++ b/apps/web/src/components/editor/panels/preview/toolbar.tsx @@ -1,151 +1,151 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { useEditor } from "@/hooks/use-editor"; -import { formatTimeCode } from "@/lib/time"; -import { invokeAction } from "@/lib/actions"; -import { EditableTimecode } from "@/components/editable-timecode"; -import { Button } from "@/components/ui/button"; -import { - FullScreenIcon, - GridTableIcon, - PauseIcon, - PlayIcon, -} from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { getGuideById } from "@/lib/guides"; -import { Separator } from "@/components/ui/separator"; -import { - Select, - SelectTrigger, - SelectContent, - SelectItem, - SelectSeparator, -} from "@/components/ui/select"; -import { PREVIEW_ZOOM_PRESETS } from "@/constants/editor-constants"; -import { usePreviewViewport } from "./preview-viewport"; -import { GridPopover } from "./guide-popover"; -import { usePreviewStore } from "@/stores/preview-store"; - -export function PreviewToolbar({ - onToggleFullscreen, -}: { - onToggleFullscreen: () => void; -}) { - const activeGuide = usePreviewStore((state) => state.activeGuide); - const activeGuideDefinition = getGuideById(activeGuide); - - return ( -
- - -
- - - - - - -
-
- ); -} - -function TimecodeDisplay() { - const editor = useEditor(); - const totalDuration = useEditor((e) => e.timeline.getTotalDuration()); - const fps = useEditor((e) => e.project.getActive().settings.fps); - const [currentTime, setCurrentTime] = useState(() => - editor.playback.getCurrentTime(), - ); - - useEffect(() => { - const handler = (e: Event) => - setCurrentTime((e as CustomEvent<{ time: number }>).detail.time); - window.addEventListener("playback-update", handler); - window.addEventListener("playback-seek", handler); - return () => { - window.removeEventListener("playback-update", handler); - window.removeEventListener("playback-seek", handler); - }; - }, []); - - return ( -
- editor.playback.seek({ time })} - className="text-center" - /> - / - - {formatTimeCode({ - timeInSeconds: totalDuration, - format: "HH:MM:SS:FF", - fps, - })} - -
- ); -} - -function ZoomSelect() { - const { isAtFit, zoomPercent, fitToScreen, setViewportPercent } = - usePreviewViewport(); - - const displayLabel = isAtFit ? "Fit" : `${zoomPercent}%`; - - const onValueChange = (value: string) => { - if (value === "fit") { - fitToScreen(); - } else { - setViewportPercent({ percent: Number(value) }); - } - }; - - return ( - - ); -} - -function PlayPauseButton() { - const isPlaying = useEditor((e) => e.playback.getIsPlaying()); - - return ( - - ); -} +"use client"; + +import { useState, useEffect } from "react"; +import { useEditor } from "@/hooks/use-editor"; +import { formatTimeCode } from "@/lib/time"; +import { invokeAction } from "@/lib/actions"; +import { EditableTimecode } from "@/components/editable-timecode"; +import { Button } from "@/components/ui/button"; +import { + FullScreenIcon, + GridTableIcon, + PauseIcon, + PlayIcon, +} from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { getGuideById } from "@/lib/guides"; +import { Separator } from "@/components/ui/separator"; +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectSeparator, +} from "@/components/ui/select"; +import { PREVIEW_ZOOM_PRESETS } from "@/constants/editor-constants"; +import { usePreviewViewport } from "./preview-viewport"; +import { GridPopover } from "./guide-popover"; +import { usePreviewStore } from "@/stores/preview-store"; + +export function PreviewToolbar({ + onToggleFullscreen, +}: { + onToggleFullscreen: () => void; +}) { + const activeGuide = usePreviewStore((state) => state.activeGuide); + const activeGuideDefinition = getGuideById(activeGuide); + + return ( +
+ + +
+ + + + + + +
+
+ ); +} + +function TimecodeDisplay() { + const editor = useEditor(); + const totalDuration = useEditor((e) => e.timeline.getTotalDuration()); + const fps = useEditor((e) => e.project.getActive().settings.fps); + const [currentTime, setCurrentTime] = useState(() => + editor.playback.getCurrentTime(), + ); + + useEffect(() => { + const handler = (e: Event) => + setCurrentTime((e as CustomEvent<{ time: number }>).detail.time); + window.addEventListener("playback-update", handler); + window.addEventListener("playback-seek", handler); + return () => { + window.removeEventListener("playback-update", handler); + window.removeEventListener("playback-seek", handler); + }; + }, []); + + return ( +
+ editor.playback.seek({ time })} + className="text-center" + /> + / + + {formatTimeCode({ + timeInSeconds: totalDuration, + format: "HH:MM:SS:FF", + fps, + })} + +
+ ); +} + +function ZoomSelect() { + const { isAtFit, zoomPercent, fitToScreen, setViewportPercent } = + usePreviewViewport(); + + const displayLabel = isAtFit ? "Fit" : `${zoomPercent}%`; + + const onValueChange = (value: string) => { + if (value === "fit") { + fitToScreen(); + } else { + setViewportPercent({ percent: Number(value) }); + } + }; + + return ( + + ); +} + +function PlayPauseButton() { + const isPlaying = useEditor((e) => e.playback.getIsPlaying()); + + return ( + + ); +} diff --git a/apps/web/src/components/editor/panels/preview/transform-handles.tsx b/apps/web/src/components/editor/panels/preview/transform-handles.tsx index 88332625..141359c8 100644 --- a/apps/web/src/components/editor/panels/preview/transform-handles.tsx +++ b/apps/web/src/components/editor/panels/preview/transform-handles.tsx @@ -1,140 +1,140 @@ -"use client"; - -import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; -import { useTransformHandles } from "@/hooks/use-transform-handles"; -import { isVisualElement } from "@/lib/timeline/element-utils"; -import { - getCornerPosition, - getEdgeHandlePosition, - getRotationHandlePosition, - type Corner, - type Edge, -} from "@/lib/preview/element-bounds"; -import type { OnSnapLinesChange } from "@/hooks/use-preview-interaction"; -import { - BoundingBoxOutline, - CornerHandle, - EdgeHandle, - IconHandle, - getResizeCursor, -} from "./handle-primitives"; -import { Rotate01Icon } from "@hugeicons/core-free-icons"; - -const CORNERS: Corner[] = [ - "top-left", - "top-right", - "bottom-left", - "bottom-right", -]; -const EDGES: Edge[] = ["right", "left", "bottom"]; - -export function TransformHandles({ - onSnapLinesChange, -}: { - onSnapLinesChange?: OnSnapLinesChange; -}) { - const viewport = usePreviewViewport(); - const { - selectedWithBounds, - hasVisualSelection, - handleCornerPointerDown, - handleEdgePointerDown, - handleRotationPointerDown, - handlePointerMove, - handlePointerUp, - } = useTransformHandles({ onSnapLinesChange }); - - if (!hasVisualSelection || !selectedWithBounds) return null; - - const { bounds, element } = selectedWithBounds; - if (!isVisualElement(element)) return null; - - const displayScale = viewport.getDisplayScale(); - - const toOverlay = ({ - canvasX, - canvasY, - }: { - canvasX: number; - canvasY: number; - }) => - viewport.canvasToOverlay({ - canvasX, - canvasY, - }); - - const center = toOverlay({ canvasX: bounds.cx, canvasY: bounds.cy }); - const outlineWidth = Math.abs(bounds.width) * displayScale.x; - const outlineHeight = Math.abs(bounds.height) * displayScale.y; - - const rotationHandleCanvas = getRotationHandlePosition({ bounds }); - const rotationHandleScreen = toOverlay({ - canvasX: rotationHandleCanvas.x, - canvasY: rotationHandleCanvas.y, - }); - - const onPointerMove = (event: React.PointerEvent) => - handlePointerMove({ event }); - const onPointerUp = () => handlePointerUp(); - - return ( -
- - {CORNERS.map((corner) => { - const cornerPosition = getCornerPosition({ bounds, corner }); - const screen = toOverlay({ - canvasX: cornerPosition.x, - canvasY: cornerPosition.y, - }); - const angleDeg = - Math.atan2(screen.y - center.y, screen.x - center.x) * - (180 / Math.PI); - return ( - - handleCornerPointerDown({ event, corner }) - } - onPointerMove={onPointerMove} - onPointerUp={onPointerUp} - /> - ); - })} - {EDGES.map((edge) => { - const edgePosition = getEdgeHandlePosition({ bounds, edge }); - const screen = toOverlay({ - canvasX: edgePosition.x, - canvasY: edgePosition.y, - }); - return ( - handleEdgePointerDown({ event, edge })} - onPointerMove={onPointerMove} - onPointerUp={onPointerUp} - /> - ); - })} - handleRotationPointerDown({ event })} - onPointerMove={onPointerMove} - onPointerUp={onPointerUp} - /> -
- ); -} +"use client"; + +import { usePreviewViewport } from "@/components/editor/panels/preview/preview-viewport"; +import { useTransformHandles } from "@/hooks/use-transform-handles"; +import { isVisualElement } from "@/lib/timeline/element-utils"; +import { + getCornerPosition, + getEdgeHandlePosition, + getRotationHandlePosition, + type Corner, + type Edge, +} from "@/lib/preview/element-bounds"; +import type { OnSnapLinesChange } from "@/hooks/use-preview-interaction"; +import { + BoundingBoxOutline, + CornerHandle, + EdgeHandle, + IconHandle, + getResizeCursor, +} from "./handle-primitives"; +import { Rotate01Icon } from "@hugeicons/core-free-icons"; + +const CORNERS: Corner[] = [ + "top-left", + "top-right", + "bottom-left", + "bottom-right", +]; +const EDGES: Edge[] = ["right", "left", "bottom"]; + +export function TransformHandles({ + onSnapLinesChange, +}: { + onSnapLinesChange?: OnSnapLinesChange; +}) { + const viewport = usePreviewViewport(); + const { + selectedWithBounds, + hasVisualSelection, + handleCornerPointerDown, + handleEdgePointerDown, + handleRotationPointerDown, + handlePointerMove, + handlePointerUp, + } = useTransformHandles({ onSnapLinesChange }); + + if (!hasVisualSelection || !selectedWithBounds) return null; + + const { bounds, element } = selectedWithBounds; + if (!isVisualElement(element)) return null; + + const displayScale = viewport.getDisplayScale(); + + const toOverlay = ({ + canvasX, + canvasY, + }: { + canvasX: number; + canvasY: number; + }) => + viewport.canvasToOverlay({ + canvasX, + canvasY, + }); + + const center = toOverlay({ canvasX: bounds.cx, canvasY: bounds.cy }); + const outlineWidth = Math.abs(bounds.width) * displayScale.x; + const outlineHeight = Math.abs(bounds.height) * displayScale.y; + + const rotationHandleCanvas = getRotationHandlePosition({ bounds }); + const rotationHandleScreen = toOverlay({ + canvasX: rotationHandleCanvas.x, + canvasY: rotationHandleCanvas.y, + }); + + const onPointerMove = (event: React.PointerEvent) => + handlePointerMove({ event }); + const onPointerUp = () => handlePointerUp(); + + return ( +
+ + {CORNERS.map((corner) => { + const cornerPosition = getCornerPosition({ bounds, corner }); + const screen = toOverlay({ + canvasX: cornerPosition.x, + canvasY: cornerPosition.y, + }); + const angleDeg = + Math.atan2(screen.y - center.y, screen.x - center.x) * + (180 / Math.PI); + return ( + + handleCornerPointerDown({ event, corner }) + } + onPointerMove={onPointerMove} + onPointerUp={onPointerUp} + /> + ); + })} + {EDGES.map((edge) => { + const edgePosition = getEdgeHandlePosition({ bounds, edge }); + const screen = toOverlay({ + canvasX: edgePosition.x, + canvasY: edgePosition.y, + }); + return ( + handleEdgePointerDown({ event, edge })} + onPointerMove={onPointerMove} + onPointerUp={onPointerUp} + /> + ); + })} + handleRotationPointerDown({ event })} + onPointerMove={onPointerMove} + onPointerUp={onPointerUp} + /> +
+ ); +} diff --git a/apps/web/src/components/editor/panels/properties/components/keyframe-toggle.tsx b/apps/web/src/components/editor/panels/properties/components/keyframe-toggle.tsx index b5b8685f..00cea82b 100644 --- a/apps/web/src/components/editor/panels/properties/components/keyframe-toggle.tsx +++ b/apps/web/src/components/editor/panels/properties/components/keyframe-toggle.tsx @@ -1,32 +1,32 @@ -import { Button } from "@/components/ui/button"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { KeyframeIcon } from "@hugeicons/core-free-icons"; -import { cn } from "@/utils/ui"; - -export function KeyframeToggle({ - isActive, - isDisabled = false, - title, - onToggle, -}: { - isActive: boolean; - isDisabled?: boolean; - title: string; - onToggle: () => void; -}) { - return ( - - ); -} +import { Button } from "@/components/ui/button"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { KeyframeIcon } from "@hugeicons/core-free-icons"; +import { cn } from "@/utils/ui"; + +export function KeyframeToggle({ + isActive, + isDisabled = false, + title, + onToggle, +}: { + isActive: boolean; + isDisabled?: boolean; + title: string; + onToggle: () => void; +}) { + return ( + + ); +} diff --git a/apps/web/src/components/editor/panels/properties/empty-view.tsx b/apps/web/src/components/editor/panels/properties/empty-view.tsx index c5576dda..547c0883 100644 --- a/apps/web/src/components/editor/panels/properties/empty-view.tsx +++ b/apps/web/src/components/editor/panels/properties/empty-view.tsx @@ -1,20 +1,20 @@ -import { HugeiconsIcon } from "@hugeicons/react"; -import { Settings05Icon } from "@hugeicons/core-free-icons"; - -export function EmptyView() { - return ( -
- -
-

It's empty here

-

- Click an element on the timeline to edit its properties -

-
-
- ); -} +import { HugeiconsIcon } from "@hugeicons/react"; +import { Settings05Icon } from "@hugeicons/core-free-icons"; + +export function EmptyView() { + return ( +
+ +
+

It's empty here

+

+ Click an element on the timeline to edit its properties +

+
+
+ ); +} diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-element-playhead.ts b/apps/web/src/components/editor/panels/properties/hooks/use-element-playhead.ts index 46e6cb6d..95f338e1 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-element-playhead.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-element-playhead.ts @@ -1,24 +1,24 @@ -import { useEditor } from "@/hooks/use-editor"; -import { getElementLocalTime } from "@/lib/animation"; -import { TIME_EPSILON_SECONDS } from "@/constants/animation-constants"; - -export function useElementPlayhead({ - startTime, - duration, -}: { - startTime: number; - duration: number; -}) { - const editor = useEditor(); - const playheadTime = editor.playback.getCurrentTime(); - const localTime = getElementLocalTime({ - timelineTime: playheadTime, - elementStartTime: startTime, - elementDuration: duration, - }); - const isPlayheadWithinElementRange = - playheadTime >= startTime - TIME_EPSILON_SECONDS && - playheadTime <= startTime + duration + TIME_EPSILON_SECONDS; - - return { localTime, isPlayheadWithinElementRange }; -} +import { useEditor } from "@/hooks/use-editor"; +import { getElementLocalTime } from "@/lib/animation"; +import { TIME_EPSILON_SECONDS } from "@/constants/animation-constants"; + +export function useElementPlayhead({ + startTime, + duration, +}: { + startTime: number; + duration: number; +}) { + const editor = useEditor(); + const playheadTime = editor.playback.getCurrentTime(); + const localTime = getElementLocalTime({ + timelineTime: playheadTime, + elementStartTime: startTime, + elementDuration: duration, + }); + const isPlayheadWithinElementRange = + playheadTime >= startTime - TIME_EPSILON_SECONDS && + playheadTime <= startTime + duration + TIME_EPSILON_SECONDS; + + return { localTime, isPlayheadWithinElementRange }; +} diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts index 957ee1c9..a42e422f 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts @@ -1,95 +1,95 @@ -import { useEditor } from "@/hooks/use-editor"; -import { - getKeyframeAtTime, - hasKeyframesForPath, - upsertElementKeyframe, -} from "@/lib/animation"; -import type { AnimationPropertyPath, ElementAnimations } from "@/lib/animation/types"; -import type { TimelineElement } from "@/lib/timeline"; - -export function useKeyframedColorProperty({ - trackId, - elementId, - animations, - propertyPath, - localTime, - isPlayheadWithinElementRange, - resolvedColor, - buildBaseUpdates, -}: { - trackId: string; - elementId: string; - animations: ElementAnimations | undefined; - propertyPath: AnimationPropertyPath; - localTime: number; - isPlayheadWithinElementRange: boolean; - resolvedColor: string; - buildBaseUpdates: ({ value }: { value: string }) => Partial; -}) { - const editor = useEditor(); - - const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath }); - const keyframeAtTime = isPlayheadWithinElementRange - ? getKeyframeAtTime({ animations, propertyPath, time: localTime }) - : null; - const keyframeIdAtTime = keyframeAtTime?.id ?? null; - const isKeyframedAtTime = keyframeAtTime !== null; - const shouldUseAnimatedChannel = - hasAnimatedKeyframes && isPlayheadWithinElementRange; - - const onChange = ({ color }: { color: string }) => { - if (shouldUseAnimatedChannel) { - editor.timeline.previewElements({ - updates: [ - { - trackId, - elementId, - updates: { - animations: upsertElementKeyframe({ - animations, - propertyPath, - time: localTime, - value: color, - }), - }, - }, - ], - }); - return; - } - - editor.timeline.previewElements({ - updates: [{ trackId, elementId, updates: buildBaseUpdates({ value: color }) }], - }); - }; - - const onChangeEnd = () => editor.timeline.commitPreview(); - - const toggleKeyframe = () => { - if (!isPlayheadWithinElementRange) { - return; - } - - if (keyframeIdAtTime) { - editor.timeline.removeKeyframes({ - keyframes: [{ trackId, elementId, propertyPath, keyframeId: keyframeIdAtTime }], - }); - return; - } - - editor.timeline.upsertKeyframes({ - keyframes: [ - { trackId, elementId, propertyPath, time: localTime, value: resolvedColor }, - ], - }); - }; - - return { - isKeyframedAtTime, - hasAnimatedKeyframes, - keyframeIdAtTime, - onChange, - onChangeEnd, - toggleKeyframe, - }; -} +import { useEditor } from "@/hooks/use-editor"; +import { + getKeyframeAtTime, + hasKeyframesForPath, + upsertElementKeyframe, +} from "@/lib/animation"; +import type { AnimationPropertyPath, ElementAnimations } from "@/lib/animation/types"; +import type { TimelineElement } from "@/lib/timeline"; + +export function useKeyframedColorProperty({ + trackId, + elementId, + animations, + propertyPath, + localTime, + isPlayheadWithinElementRange, + resolvedColor, + buildBaseUpdates, +}: { + trackId: string; + elementId: string; + animations: ElementAnimations | undefined; + propertyPath: AnimationPropertyPath; + localTime: number; + isPlayheadWithinElementRange: boolean; + resolvedColor: string; + buildBaseUpdates: ({ value }: { value: string }) => Partial; +}) { + const editor = useEditor(); + + const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath }); + const keyframeAtTime = isPlayheadWithinElementRange + ? getKeyframeAtTime({ animations, propertyPath, time: localTime }) + : null; + const keyframeIdAtTime = keyframeAtTime?.id ?? null; + const isKeyframedAtTime = keyframeAtTime !== null; + const shouldUseAnimatedChannel = + hasAnimatedKeyframes && isPlayheadWithinElementRange; + + const onChange = ({ color }: { color: string }) => { + if (shouldUseAnimatedChannel) { + editor.timeline.previewElements({ + updates: [ + { + trackId, + elementId, + updates: { + animations: upsertElementKeyframe({ + animations, + propertyPath, + time: localTime, + value: color, + }), + }, + }, + ], + }); + return; + } + + editor.timeline.previewElements({ + updates: [{ trackId, elementId, updates: buildBaseUpdates({ value: color }) }], + }); + }; + + const onChangeEnd = () => editor.timeline.commitPreview(); + + const toggleKeyframe = () => { + if (!isPlayheadWithinElementRange) { + return; + } + + if (keyframeIdAtTime) { + editor.timeline.removeKeyframes({ + keyframes: [{ trackId, elementId, propertyPath, keyframeId: keyframeIdAtTime }], + }); + return; + } + + editor.timeline.upsertKeyframes({ + keyframes: [ + { trackId, elementId, propertyPath, time: localTime, value: resolvedColor }, + ], + }); + }; + + return { + isKeyframedAtTime, + hasAnimatedKeyframes, + keyframeIdAtTime, + onChange, + onChangeEnd, + toggleKeyframe, + }; +} diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts index 0f0168f8..ddd3d2e4 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts @@ -1,161 +1,161 @@ -import { useEditor } from "@/hooks/use-editor"; -import { - getKeyframeAtTime, - hasKeyframesForPath, - upsertElementKeyframe, -} from "@/lib/animation"; -import type { AnimationPropertyPath, ElementAnimations } from "@/lib/animation/types"; -import type { TimelineElement } from "@/lib/timeline"; -import { snapToStep } from "@/utils/math"; -import { usePropertyDraft } from "./use-property-draft"; - -export function useKeyframedNumberProperty({ - trackId, - elementId, - animations, - propertyPath, - localTime, - isPlayheadWithinElementRange, - displayValue, - parse, - valueAtPlayhead, - step, - buildBaseUpdates, -}: { - trackId: string; - elementId: string; - animations: ElementAnimations | undefined; - propertyPath: AnimationPropertyPath; - localTime: number; - isPlayheadWithinElementRange: boolean; - displayValue: string; - parse: (input: string) => number | null; - valueAtPlayhead: number; - step?: number; - buildBaseUpdates: ({ value }: { value: number }) => Partial; -}) { - const editor = useEditor(); - const snapValue = (value: number) => - step != null ? snapToStep({ value, step }) : value; - - const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath }); - const keyframeAtTime = isPlayheadWithinElementRange - ? getKeyframeAtTime({ animations, propertyPath, time: localTime }) - : null; - const keyframeIdAtTime = keyframeAtTime?.id ?? null; - const isKeyframedAtTime = keyframeAtTime !== null; - const shouldUseAnimatedChannel = - hasAnimatedKeyframes && isPlayheadWithinElementRange; - - const previewValue = ({ value }: { value: number }) => { - const nextValue = snapValue(value); - if (shouldUseAnimatedChannel) { - editor.timeline.previewElements({ - updates: [ - { - trackId, - elementId, - updates: { - animations: upsertElementKeyframe({ - animations, - propertyPath, - time: localTime, - value: nextValue, - }), - }, - }, - ], - }); - return; - } - - editor.timeline.previewElements({ - updates: [ - { - trackId, - elementId, - updates: buildBaseUpdates({ value: nextValue }), - }, - ], - }); - }; - - const propertyDraft = usePropertyDraft({ - displayValue, - parse: (input) => { - const parsedValue = parse(input); - return parsedValue === null ? null : snapValue(parsedValue); - }, - onPreview: (value) => previewValue({ value }), - onCommit: () => editor.timeline.commitPreview(), - }); - - const toggleKeyframe = () => { - if (!isPlayheadWithinElementRange) { - return; - } - - if (keyframeIdAtTime) { - editor.timeline.removeKeyframes({ - keyframes: [ - { - trackId, - elementId, - propertyPath, - keyframeId: keyframeIdAtTime, - }, - ], - }); - return; - } - - editor.timeline.upsertKeyframes({ - keyframes: [ - { - trackId, - elementId, - propertyPath, - time: localTime, - value: snapValue(valueAtPlayhead), - }, - ], - }); - }; - - const commitValue = ({ value }: { value: number }) => { - const nextValue = snapValue(value); - if (shouldUseAnimatedChannel) { - editor.timeline.upsertKeyframes({ - keyframes: [ - { - trackId, - elementId, - propertyPath, - time: localTime, - value: nextValue, - }, - ], - }); - return; - } - - editor.timeline.updateElements({ - updates: [ - { - trackId, - elementId, - updates: buildBaseUpdates({ value: nextValue }), - }, - ], - }); - }; - - return { - ...propertyDraft, - hasAnimatedKeyframes, - isKeyframedAtTime, - keyframeIdAtTime, - toggleKeyframe, - commitValue, - }; -} +import { useEditor } from "@/hooks/use-editor"; +import { + getKeyframeAtTime, + hasKeyframesForPath, + upsertElementKeyframe, +} from "@/lib/animation"; +import type { AnimationPropertyPath, ElementAnimations } from "@/lib/animation/types"; +import type { TimelineElement } from "@/lib/timeline"; +import { snapToStep } from "@/utils/math"; +import { usePropertyDraft } from "./use-property-draft"; + +export function useKeyframedNumberProperty({ + trackId, + elementId, + animations, + propertyPath, + localTime, + isPlayheadWithinElementRange, + displayValue, + parse, + valueAtPlayhead, + step, + buildBaseUpdates, +}: { + trackId: string; + elementId: string; + animations: ElementAnimations | undefined; + propertyPath: AnimationPropertyPath; + localTime: number; + isPlayheadWithinElementRange: boolean; + displayValue: string; + parse: (input: string) => number | null; + valueAtPlayhead: number; + step?: number; + buildBaseUpdates: ({ value }: { value: number }) => Partial; +}) { + const editor = useEditor(); + const snapValue = (value: number) => + step != null ? snapToStep({ value, step }) : value; + + const hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath }); + const keyframeAtTime = isPlayheadWithinElementRange + ? getKeyframeAtTime({ animations, propertyPath, time: localTime }) + : null; + const keyframeIdAtTime = keyframeAtTime?.id ?? null; + const isKeyframedAtTime = keyframeAtTime !== null; + const shouldUseAnimatedChannel = + hasAnimatedKeyframes && isPlayheadWithinElementRange; + + const previewValue = ({ value }: { value: number }) => { + const nextValue = snapValue(value); + if (shouldUseAnimatedChannel) { + editor.timeline.previewElements({ + updates: [ + { + trackId, + elementId, + updates: { + animations: upsertElementKeyframe({ + animations, + propertyPath, + time: localTime, + value: nextValue, + }), + }, + }, + ], + }); + return; + } + + editor.timeline.previewElements({ + updates: [ + { + trackId, + elementId, + updates: buildBaseUpdates({ value: nextValue }), + }, + ], + }); + }; + + const propertyDraft = usePropertyDraft({ + displayValue, + parse: (input) => { + const parsedValue = parse(input); + return parsedValue === null ? null : snapValue(parsedValue); + }, + onPreview: (value) => previewValue({ value }), + onCommit: () => editor.timeline.commitPreview(), + }); + + const toggleKeyframe = () => { + if (!isPlayheadWithinElementRange) { + return; + } + + if (keyframeIdAtTime) { + editor.timeline.removeKeyframes({ + keyframes: [ + { + trackId, + elementId, + propertyPath, + keyframeId: keyframeIdAtTime, + }, + ], + }); + return; + } + + editor.timeline.upsertKeyframes({ + keyframes: [ + { + trackId, + elementId, + propertyPath, + time: localTime, + value: snapValue(valueAtPlayhead), + }, + ], + }); + }; + + const commitValue = ({ value }: { value: number }) => { + const nextValue = snapValue(value); + if (shouldUseAnimatedChannel) { + editor.timeline.upsertKeyframes({ + keyframes: [ + { + trackId, + elementId, + propertyPath, + time: localTime, + value: nextValue, + }, + ], + }); + return; + } + + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId, + updates: buildBaseUpdates({ value: nextValue }), + }, + ], + }); + }; + + return { + ...propertyDraft, + hasAnimatedKeyframes, + isKeyframedAtTime, + keyframeIdAtTime, + toggleKeyframe, + commitValue, + }; +} diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts index 335e3fb6..92b5d634 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts @@ -1,148 +1,148 @@ -"use client"; - -import { useEditor } from "@/hooks/use-editor"; -import { - buildGraphicParamPath, - getKeyframeAtTime, - getParamDefaultInterpolation, - getParamValueKind, - hasKeyframesForPath, - upsertPathKeyframe, -} from "@/lib/animation"; -import type { - ElementAnimations, -} from "@/lib/animation/types"; -import type { ParamDefinition } from "@/lib/params"; -import type { TimelineElement } from "@/lib/timeline"; - -export interface KeyframedParamPropertyResult { - hasAnimatedKeyframes: boolean; - isKeyframedAtTime: boolean; - keyframeIdAtTime: string | null; - onPreview: (value: number | string | boolean) => void; - onCommit: () => void; - toggleKeyframe: () => void; -} - -export function useKeyframedParamProperty({ - param, - trackId, - elementId, - animations, - localTime, - isPlayheadWithinElementRange, - resolvedValue, - buildBaseUpdates, -}: { - param: ParamDefinition; - trackId: string; - elementId: string; - animations: ElementAnimations | undefined; - localTime: number; - isPlayheadWithinElementRange: boolean; - resolvedValue: number | string | boolean; - buildBaseUpdates: ({ - value, - }: { - value: number | string | boolean; - }) => Partial; -}): KeyframedParamPropertyResult { - const editor = useEditor(); - const propertyPath = buildGraphicParamPath({ paramKey: param.key }); - const hasAnimatedKeyframes = hasKeyframesForPath({ - animations, - propertyPath, - }); - const keyframeAtTime = isPlayheadWithinElementRange - ? getKeyframeAtTime({ - animations, - propertyPath, - time: localTime, - }) - : null; - const keyframeIdAtTime = keyframeAtTime?.id ?? null; - const isKeyframedAtTime = keyframeAtTime !== null; - const shouldUseAnimatedChannel = - hasAnimatedKeyframes && isPlayheadWithinElementRange; - - const previewValue: KeyframedParamPropertyResult["onPreview"] = (value) => { - if (shouldUseAnimatedChannel) { - editor.timeline.previewElements({ - updates: [ - { - trackId, - elementId, - updates: { - animations: upsertPathKeyframe({ - animations, - propertyPath, - time: localTime, - value, - valueKind: getParamValueKind({ param }), - defaultInterpolation: getParamDefaultInterpolation({ - param, - }), - numericRange: - param.type === "number" - ? { min: param.min, max: param.max, step: param.step } - : undefined, - }), - }, - }, - ], - }); - return; - } - - editor.timeline.previewElements({ - updates: [ - { - trackId, - elementId, - updates: buildBaseUpdates({ value }), - }, - ], - }); - }; - - const toggleKeyframe = () => { - if (!isPlayheadWithinElementRange) { - return; - } - - if (keyframeIdAtTime) { - editor.timeline.removeKeyframes({ - keyframes: [ - { - trackId, - elementId, - propertyPath, - keyframeId: keyframeIdAtTime, - }, - ], - }); - return; - } - - editor.timeline.upsertKeyframes({ - keyframes: [ - { - trackId, - elementId, - propertyPath, - time: localTime, - value: resolvedValue, - }, - ], - }); - }; - - return { - hasAnimatedKeyframes, - isKeyframedAtTime, - keyframeIdAtTime, - onPreview: previewValue, - onCommit: () => editor.timeline.commitPreview(), - toggleKeyframe, - }; -} +"use client"; + +import { useEditor } from "@/hooks/use-editor"; +import { + buildGraphicParamPath, + getKeyframeAtTime, + getParamDefaultInterpolation, + getParamValueKind, + hasKeyframesForPath, + upsertPathKeyframe, +} from "@/lib/animation"; +import type { + ElementAnimations, +} from "@/lib/animation/types"; +import type { ParamDefinition } from "@/lib/params"; +import type { TimelineElement } from "@/lib/timeline"; + +export interface KeyframedParamPropertyResult { + hasAnimatedKeyframes: boolean; + isKeyframedAtTime: boolean; + keyframeIdAtTime: string | null; + onPreview: (value: number | string | boolean) => void; + onCommit: () => void; + toggleKeyframe: () => void; +} + +export function useKeyframedParamProperty({ + param, + trackId, + elementId, + animations, + localTime, + isPlayheadWithinElementRange, + resolvedValue, + buildBaseUpdates, +}: { + param: ParamDefinition; + trackId: string; + elementId: string; + animations: ElementAnimations | undefined; + localTime: number; + isPlayheadWithinElementRange: boolean; + resolvedValue: number | string | boolean; + buildBaseUpdates: ({ + value, + }: { + value: number | string | boolean; + }) => Partial; +}): KeyframedParamPropertyResult { + const editor = useEditor(); + const propertyPath = buildGraphicParamPath({ paramKey: param.key }); + const hasAnimatedKeyframes = hasKeyframesForPath({ + animations, + propertyPath, + }); + const keyframeAtTime = isPlayheadWithinElementRange + ? getKeyframeAtTime({ + animations, + propertyPath, + time: localTime, + }) + : null; + const keyframeIdAtTime = keyframeAtTime?.id ?? null; + const isKeyframedAtTime = keyframeAtTime !== null; + const shouldUseAnimatedChannel = + hasAnimatedKeyframes && isPlayheadWithinElementRange; + + const previewValue: KeyframedParamPropertyResult["onPreview"] = (value) => { + if (shouldUseAnimatedChannel) { + editor.timeline.previewElements({ + updates: [ + { + trackId, + elementId, + updates: { + animations: upsertPathKeyframe({ + animations, + propertyPath, + time: localTime, + value, + valueKind: getParamValueKind({ param }), + defaultInterpolation: getParamDefaultInterpolation({ + param, + }), + numericRange: + param.type === "number" + ? { min: param.min, max: param.max, step: param.step } + : undefined, + }), + }, + }, + ], + }); + return; + } + + editor.timeline.previewElements({ + updates: [ + { + trackId, + elementId, + updates: buildBaseUpdates({ value }), + }, + ], + }); + }; + + const toggleKeyframe = () => { + if (!isPlayheadWithinElementRange) { + return; + } + + if (keyframeIdAtTime) { + editor.timeline.removeKeyframes({ + keyframes: [ + { + trackId, + elementId, + propertyPath, + keyframeId: keyframeIdAtTime, + }, + ], + }); + return; + } + + editor.timeline.upsertKeyframes({ + keyframes: [ + { + trackId, + elementId, + propertyPath, + time: localTime, + value: resolvedValue, + }, + ], + }); + }; + + return { + hasAnimatedKeyframes, + isKeyframedAtTime, + keyframeIdAtTime, + onPreview: previewValue, + onCommit: () => editor.timeline.commitPreview(), + toggleKeyframe, + }; +} diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-vector-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-vector-property.ts index 188f05cb..a342859c 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-vector-property.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-vector-property.ts @@ -1,190 +1,190 @@ -import { useEditor } from "@/hooks/use-editor"; -import { - getKeyframeAtTime, - hasKeyframesForPath, - upsertElementKeyframe, -} from "@/lib/animation"; -import type { - AnimationPropertyPath, - ElementAnimations, - VectorValue, -} from "@/lib/animation/types"; -import type { TimelineElement } from "@/lib/timeline"; -import { snapToStep } from "@/utils/math"; -import { usePropertyDraft } from "./use-property-draft"; - -export function useKeyframedVectorProperty({ - trackId, - elementId, - animations, - propertyPath, - localTime, - isPlayheadWithinElementRange, - resolvedValue, - displayX, - displayY, - parseComponent, - step, - buildBaseUpdates, -}: { - trackId: string; - elementId: string; - animations: ElementAnimations | undefined; - propertyPath: AnimationPropertyPath; - localTime: number; - isPlayheadWithinElementRange: boolean; - resolvedValue: VectorValue; - displayX: string; - displayY: string; - parseComponent: (input: string) => number | null; - step?: number; - buildBaseUpdates: ({ - value, - }: { - value: VectorValue; - }) => Partial; -}) { - const editor = useEditor(); - const snapComponentValue = (value: number) => - step != null ? snapToStep({ value, step }) : value; - - const hasAnimatedKeyframes = hasKeyframesForPath({ - animations, - propertyPath, - }); - const keyframeAtTime = isPlayheadWithinElementRange - ? getKeyframeAtTime({ animations, propertyPath, time: localTime }) - : null; - const keyframeIdAtTime = keyframeAtTime?.id ?? null; - const isKeyframedAtTime = keyframeAtTime !== null; - const shouldUseAnimatedChannel = - hasAnimatedKeyframes && isPlayheadWithinElementRange; - - const previewVector = ({ value }: { value: VectorValue }) => { - const nextValue = { - x: snapComponentValue(value.x), - y: snapComponentValue(value.y), - }; - if (shouldUseAnimatedChannel) { - editor.timeline.previewElements({ - updates: [ - { - trackId, - elementId, - updates: { - animations: upsertElementKeyframe({ - animations, - propertyPath, - time: localTime, - value: nextValue, - }), - }, - }, - ], - }); - return; - } - - editor.timeline.previewElements({ - updates: [{ trackId, elementId, updates: buildBaseUpdates({ value: nextValue }) }], - }); - }; - - const x = usePropertyDraft({ - displayValue: displayX, - parse: (input) => { - const parsedValue = parseComponent(input); - return parsedValue === null ? null : snapComponentValue(parsedValue); - }, - onPreview: (xVal) => - previewVector({ value: { x: xVal, y: resolvedValue.y } }), - onCommit: () => editor.timeline.commitPreview(), - }); - - const y = usePropertyDraft({ - displayValue: displayY, - parse: (input) => { - const parsedValue = parseComponent(input); - return parsedValue === null ? null : snapComponentValue(parsedValue); - }, - onPreview: (yVal) => - previewVector({ value: { x: resolvedValue.x, y: yVal } }), - onCommit: () => editor.timeline.commitPreview(), - }); - - const toggleKeyframe = () => { - if (!isPlayheadWithinElementRange) return; - - if (keyframeIdAtTime) { - editor.timeline.removeKeyframes({ - keyframes: [ - { trackId, elementId, propertyPath, keyframeId: keyframeIdAtTime }, - ], - }); - return; - } - - editor.timeline.upsertKeyframes({ - keyframes: [ - { - trackId, - elementId, - propertyPath, - time: localTime, - value: resolvedValue, - }, - ], - }); - }; - - const commitX = ({ value }: { value: number }) => { - const vector: VectorValue = { - x: snapComponentValue(value), - y: snapComponentValue(resolvedValue.y), - }; - if (shouldUseAnimatedChannel) { - editor.timeline.upsertKeyframes({ - keyframes: [ - { trackId, elementId, propertyPath, time: localTime, value: vector }, - ], - }); - return; - } - editor.timeline.updateElements({ - updates: [ - { trackId, elementId, updates: buildBaseUpdates({ value: vector }) }, - ], - }); - }; - - const commitY = ({ value }: { value: number }) => { - const vector: VectorValue = { - x: snapComponentValue(resolvedValue.x), - y: snapComponentValue(value), - }; - if (shouldUseAnimatedChannel) { - editor.timeline.upsertKeyframes({ - keyframes: [ - { trackId, elementId, propertyPath, time: localTime, value: vector }, - ], - }); - return; - } - editor.timeline.updateElements({ - updates: [ - { trackId, elementId, updates: buildBaseUpdates({ value: vector }) }, - ], - }); - }; - - return { - x, - y, - hasAnimatedKeyframes, - isKeyframedAtTime, - keyframeIdAtTime, - toggleKeyframe, - commitX, - commitY, - }; -} +import { useEditor } from "@/hooks/use-editor"; +import { + getKeyframeAtTime, + hasKeyframesForPath, + upsertElementKeyframe, +} from "@/lib/animation"; +import type { + AnimationPropertyPath, + ElementAnimations, + VectorValue, +} from "@/lib/animation/types"; +import type { TimelineElement } from "@/lib/timeline"; +import { snapToStep } from "@/utils/math"; +import { usePropertyDraft } from "./use-property-draft"; + +export function useKeyframedVectorProperty({ + trackId, + elementId, + animations, + propertyPath, + localTime, + isPlayheadWithinElementRange, + resolvedValue, + displayX, + displayY, + parseComponent, + step, + buildBaseUpdates, +}: { + trackId: string; + elementId: string; + animations: ElementAnimations | undefined; + propertyPath: AnimationPropertyPath; + localTime: number; + isPlayheadWithinElementRange: boolean; + resolvedValue: VectorValue; + displayX: string; + displayY: string; + parseComponent: (input: string) => number | null; + step?: number; + buildBaseUpdates: ({ + value, + }: { + value: VectorValue; + }) => Partial; +}) { + const editor = useEditor(); + const snapComponentValue = (value: number) => + step != null ? snapToStep({ value, step }) : value; + + const hasAnimatedKeyframes = hasKeyframesForPath({ + animations, + propertyPath, + }); + const keyframeAtTime = isPlayheadWithinElementRange + ? getKeyframeAtTime({ animations, propertyPath, time: localTime }) + : null; + const keyframeIdAtTime = keyframeAtTime?.id ?? null; + const isKeyframedAtTime = keyframeAtTime !== null; + const shouldUseAnimatedChannel = + hasAnimatedKeyframes && isPlayheadWithinElementRange; + + const previewVector = ({ value }: { value: VectorValue }) => { + const nextValue = { + x: snapComponentValue(value.x), + y: snapComponentValue(value.y), + }; + if (shouldUseAnimatedChannel) { + editor.timeline.previewElements({ + updates: [ + { + trackId, + elementId, + updates: { + animations: upsertElementKeyframe({ + animations, + propertyPath, + time: localTime, + value: nextValue, + }), + }, + }, + ], + }); + return; + } + + editor.timeline.previewElements({ + updates: [{ trackId, elementId, updates: buildBaseUpdates({ value: nextValue }) }], + }); + }; + + const x = usePropertyDraft({ + displayValue: displayX, + parse: (input) => { + const parsedValue = parseComponent(input); + return parsedValue === null ? null : snapComponentValue(parsedValue); + }, + onPreview: (xVal) => + previewVector({ value: { x: xVal, y: resolvedValue.y } }), + onCommit: () => editor.timeline.commitPreview(), + }); + + const y = usePropertyDraft({ + displayValue: displayY, + parse: (input) => { + const parsedValue = parseComponent(input); + return parsedValue === null ? null : snapComponentValue(parsedValue); + }, + onPreview: (yVal) => + previewVector({ value: { x: resolvedValue.x, y: yVal } }), + onCommit: () => editor.timeline.commitPreview(), + }); + + const toggleKeyframe = () => { + if (!isPlayheadWithinElementRange) return; + + if (keyframeIdAtTime) { + editor.timeline.removeKeyframes({ + keyframes: [ + { trackId, elementId, propertyPath, keyframeId: keyframeIdAtTime }, + ], + }); + return; + } + + editor.timeline.upsertKeyframes({ + keyframes: [ + { + trackId, + elementId, + propertyPath, + time: localTime, + value: resolvedValue, + }, + ], + }); + }; + + const commitX = ({ value }: { value: number }) => { + const vector: VectorValue = { + x: snapComponentValue(value), + y: snapComponentValue(resolvedValue.y), + }; + if (shouldUseAnimatedChannel) { + editor.timeline.upsertKeyframes({ + keyframes: [ + { trackId, elementId, propertyPath, time: localTime, value: vector }, + ], + }); + return; + } + editor.timeline.updateElements({ + updates: [ + { trackId, elementId, updates: buildBaseUpdates({ value: vector }) }, + ], + }); + }; + + const commitY = ({ value }: { value: number }) => { + const vector: VectorValue = { + x: snapComponentValue(resolvedValue.x), + y: snapComponentValue(value), + }; + if (shouldUseAnimatedChannel) { + editor.timeline.upsertKeyframes({ + keyframes: [ + { trackId, elementId, propertyPath, time: localTime, value: vector }, + ], + }); + return; + } + editor.timeline.updateElements({ + updates: [ + { trackId, elementId, updates: buildBaseUpdates({ value: vector }) }, + ], + }); + }; + + return { + x, + y, + hasAnimatedKeyframes, + isKeyframedAtTime, + keyframeIdAtTime, + toggleKeyframe, + commitX, + commitY, + }; +} diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts b/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts index 395666cb..0e7ae6a7 100644 --- a/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts +++ b/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts @@ -1,72 +1,72 @@ -import { useReducer, useRef } from "react"; -import { evaluateMathExpression } from "@/utils/math"; - -function looksLikeExpression({ input }: { input: string }): boolean { - const trimmed = input.trim(); - if (!trimmed) return false; - if (/[+*/]/.test(input)) return true; - const minusIndex = trimmed.indexOf("-"); - return minusIndex > 0; -} - -export function usePropertyDraft({ - displayValue: sourceDisplay, - parse, - onPreview, - onCommit, - supportsExpressions = true, -}: { - displayValue: string; - parse: (input: string) => T | null; - onPreview: (value: T) => void; - onCommit: () => void; - supportsExpressions?: boolean; -}) { - const [, forceRender] = useReducer( - (renderVersion: number) => renderVersion + 1, - 0, - ); - const isEditing = useRef(false); - const draft = useRef(""); - - return { - displayValue: isEditing.current ? draft.current : sourceDisplay, - scrubTo: (value: number) => { - const parsed = parse(String(value)); - if (parsed !== null) onPreview(parsed); - }, - commitScrub: onCommit, - onFocus: () => { - isEditing.current = true; - draft.current = sourceDisplay; - forceRender(); - }, - onChange: ( - event: React.ChangeEvent, - ) => { - draft.current = event.target.value; - forceRender(); - - const parsed = parse(event.target.value); - if (parsed !== null) { - onPreview(parsed); - } - }, - onBlur: () => { - if ( - supportsExpressions && - looksLikeExpression({ input: draft.current }) - ) { - const evaluated = evaluateMathExpression({ input: draft.current }); - if (evaluated !== null) { - const parsed = parse(String(evaluated)); - if (parsed !== null) onPreview(parsed); - } - } - onCommit(); - isEditing.current = false; - draft.current = ""; - forceRender(); - }, - }; -} +import { useReducer, useRef } from "react"; +import { evaluateMathExpression } from "@/utils/math"; + +function looksLikeExpression({ input }: { input: string }): boolean { + const trimmed = input.trim(); + if (!trimmed) return false; + if (/[+*/]/.test(input)) return true; + const minusIndex = trimmed.indexOf("-"); + return minusIndex > 0; +} + +export function usePropertyDraft({ + displayValue: sourceDisplay, + parse, + onPreview, + onCommit, + supportsExpressions = true, +}: { + displayValue: string; + parse: (input: string) => T | null; + onPreview: (value: T) => void; + onCommit: () => void; + supportsExpressions?: boolean; +}) { + const [, forceRender] = useReducer( + (renderVersion: number) => renderVersion + 1, + 0, + ); + const isEditing = useRef(false); + const draft = useRef(""); + + return { + displayValue: isEditing.current ? draft.current : sourceDisplay, + scrubTo: (value: number) => { + const parsed = parse(String(value)); + if (parsed !== null) onPreview(parsed); + }, + commitScrub: onCommit, + onFocus: () => { + isEditing.current = true; + draft.current = sourceDisplay; + forceRender(); + }, + onChange: ( + event: React.ChangeEvent, + ) => { + draft.current = event.target.value; + forceRender(); + + const parsed = parse(event.target.value); + if (parsed !== null) { + onPreview(parsed); + } + }, + onBlur: () => { + if ( + supportsExpressions && + looksLikeExpression({ input: draft.current }) + ) { + const evaluated = evaluateMathExpression({ input: draft.current }); + if (evaluated !== null) { + const parsed = parse(String(evaluated)); + if (parsed !== null) onPreview(parsed); + } + } + onCommit(); + isEditing.current = false; + draft.current = ""; + forceRender(); + }, + }; +} diff --git a/apps/web/src/components/editor/panels/properties/registry.tsx b/apps/web/src/components/editor/panels/properties/registry.tsx index 706faef4..38d0a189 100644 --- a/apps/web/src/components/editor/panels/properties/registry.tsx +++ b/apps/web/src/components/editor/panels/properties/registry.tsx @@ -1,303 +1,303 @@ -import type { ReactNode } from "react"; -import type { - EffectElement, - GraphicElement, - ImageElement, - MaskableElement, - RetimableElement, - StickerElement, - TextElement, - VisualElement, - VideoElement, - AudioElement, - TimelineElement, -} from "@/lib/timeline"; -import type { MediaAsset } from "@/lib/media/types"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { - TextFontIcon, - ArrowExpandIcon, - RainDropIcon, - MusicNote03Icon, - MagicWand05Icon, - DashboardSpeed02Icon, -} from "@hugeicons/core-free-icons"; -import { TransformTab } from "./tabs/transform-tab"; -import { BlendingTab } from "./tabs/blending-tab"; -import { AudioTab } from "./tabs/audio-tab"; -import { TextTab } from "./tabs/text-tab"; -import { ClipEffectsTab, StandaloneEffectTab } from "./tabs/effects-tab"; -import { MasksTab } from "./tabs/masks-tab"; -import { SpeedTab } from "./tabs/speed-tab"; -import { GraphicTab } from "./tabs/graphic-tab"; -import { OcShapesIcon } from "@opencut/ui/icons"; - -export type TabContentProps = { - trackId: string; -}; - -export type PropertiesTabDef = { - id: string; - label: string; - icon: ReactNode; - content: (props: TabContentProps) => ReactNode; -}; - -export type ElementPropertiesConfig = { - defaultTab: string; - tabs: PropertiesTabDef[]; -}; - -function buildTransformTab({ - element, -}: { - element: VisualElement; -}): PropertiesTabDef { - return { - id: "transform", - label: "Transform", - icon: , - content: ({ trackId }) => ( - - ), - }; -} - -function buildBlendingTab({ - element, -}: { - element: VisualElement; -}): PropertiesTabDef { - return { - id: "blending", - label: "Blending", - icon: , - content: ({ trackId }) => ( - - ), - }; -} - -function buildAudioTab({ - element, -}: { - element: AudioElement | VideoElement; -}): PropertiesTabDef { - return { - id: "audio", - label: "Audio", - icon: , - content: ({ trackId }) => , - }; -} - -function buildSpeedTab({ - element, -}: { - element: RetimableElement; -}): PropertiesTabDef { - return { - id: "speed", - label: "Speed", - icon: , - content: ({ trackId }) => , - }; -} - -function buildMasksTab({ - element, -}: { - element: MaskableElement; -}): PropertiesTabDef { - return { - id: "masks", - label: "Masks", - icon: , - content: ({ trackId }) => , - }; -} - -function buildClipEffectsTab({ - element, -}: { - element: VisualElement; -}): PropertiesTabDef { - return { - id: "effects", - label: "Effects", - icon: , - content: ({ trackId }) => ( - - ), - }; -} - -function buildTextTab({ element }: { element: TextElement }): PropertiesTabDef { - return { - id: "text", - label: "Text", - icon: , - content: ({ trackId }) => , - }; -} - -function buildGraphicTab({ - element, -}: { - element: GraphicElement; -}): PropertiesTabDef { - return { - id: "graphic", - label: "Graphic", - icon: , - content: ({ trackId }) => , - }; -} - -function buildStandaloneEffectTab({ - element, -}: { - element: EffectElement; -}): PropertiesTabDef { - return { - id: "effects", - label: "Effects", - icon: , - content: ({ trackId }) => ( - - ), - }; -} - -function getTextConfig({ - element, -}: { - element: TextElement; -}): ElementPropertiesConfig { - return { - defaultTab: "text", - tabs: [ - buildTextTab({ element }), - buildTransformTab({ element }), - buildBlendingTab({ element }), - ], - }; -} - -function getVideoConfig({ - element, - mediaAsset, -}: { - element: VideoElement; - mediaAsset: MediaAsset | undefined; -}): ElementPropertiesConfig { - const showAudioTab = mediaAsset?.hasAudio !== false; - return { - defaultTab: "transform", - tabs: [ - buildTransformTab({ element }), - ...(showAudioTab ? [buildAudioTab({ element })] : []), - buildSpeedTab({ element }), - buildBlendingTab({ element }), - buildMasksTab({ element }), - buildClipEffectsTab({ element }), - ], - }; -} - -function getImageConfig({ - element, -}: { - element: ImageElement; -}): ElementPropertiesConfig { - return { - defaultTab: "transform", - tabs: [ - buildTransformTab({ element }), - buildBlendingTab({ element }), - buildMasksTab({ element }), - buildClipEffectsTab({ element }), - ], - }; -} - -function getStickerConfig({ - element, -}: { - element: StickerElement; -}): ElementPropertiesConfig { - return { - defaultTab: "transform", - tabs: [ - buildTransformTab({ element }), - buildBlendingTab({ element }), - buildClipEffectsTab({ element }), - ], - }; -} - -function getGraphicConfig({ - element, -}: { - element: GraphicElement; -}): ElementPropertiesConfig { - return { - defaultTab: "graphic", - tabs: [ - buildGraphicTab({ element }), - buildTransformTab({ element }), - buildBlendingTab({ element }), - buildMasksTab({ element }), - buildClipEffectsTab({ element }), - ], - }; -} - -function getAudioConfig({ - element, -}: { - element: AudioElement; -}): ElementPropertiesConfig { - return { - defaultTab: "audio", - tabs: [buildAudioTab({ element }), buildSpeedTab({ element })], - }; -} - -function getEffectConfig({ - element, -}: { - element: EffectElement; -}): ElementPropertiesConfig { - return { - defaultTab: "effects", - tabs: [buildStandaloneEffectTab({ element })], - }; -} - -export function getPropertiesConfig({ - element, - mediaAssets, -}: { - element: TimelineElement; - mediaAssets: MediaAsset[]; -}): ElementPropertiesConfig { - switch (element.type) { - case "text": - return getTextConfig({ element }); - case "video": { - const mediaAsset = mediaAssets.find((a) => a.id === element.mediaId); - return getVideoConfig({ element, mediaAsset }); - } - case "image": - return getImageConfig({ element }); - case "sticker": - return getStickerConfig({ element }); - case "graphic": - return getGraphicConfig({ element }); - case "audio": - return getAudioConfig({ element }); - case "effect": - return getEffectConfig({ element }); - } -} +import type { ReactNode } from "react"; +import type { + EffectElement, + GraphicElement, + ImageElement, + MaskableElement, + RetimableElement, + StickerElement, + TextElement, + VisualElement, + VideoElement, + AudioElement, + TimelineElement, +} from "@/lib/timeline"; +import type { MediaAsset } from "@/lib/media/types"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { + TextFontIcon, + ArrowExpandIcon, + RainDropIcon, + MusicNote03Icon, + MagicWand05Icon, + DashboardSpeed02Icon, +} from "@hugeicons/core-free-icons"; +import { TransformTab } from "./tabs/transform-tab"; +import { BlendingTab } from "./tabs/blending-tab"; +import { AudioTab } from "./tabs/audio-tab"; +import { TextTab } from "./tabs/text-tab"; +import { ClipEffectsTab, StandaloneEffectTab } from "./tabs/effects-tab"; +import { MasksTab } from "./tabs/masks-tab"; +import { SpeedTab } from "./tabs/speed-tab"; +import { GraphicTab } from "./tabs/graphic-tab"; +import { OcShapesIcon } from "@opencut/ui/icons"; + +export type TabContentProps = { + trackId: string; +}; + +export type PropertiesTabDef = { + id: string; + label: string; + icon: ReactNode; + content: (props: TabContentProps) => ReactNode; +}; + +export type ElementPropertiesConfig = { + defaultTab: string; + tabs: PropertiesTabDef[]; +}; + +function buildTransformTab({ + element, +}: { + element: VisualElement; +}): PropertiesTabDef { + return { + id: "transform", + label: "Transform", + icon: , + content: ({ trackId }) => ( + + ), + }; +} + +function buildBlendingTab({ + element, +}: { + element: VisualElement; +}): PropertiesTabDef { + return { + id: "blending", + label: "Blending", + icon: , + content: ({ trackId }) => ( + + ), + }; +} + +function buildAudioTab({ + element, +}: { + element: AudioElement | VideoElement; +}): PropertiesTabDef { + return { + id: "audio", + label: "Audio", + icon: , + content: ({ trackId }) => , + }; +} + +function buildSpeedTab({ + element, +}: { + element: RetimableElement; +}): PropertiesTabDef { + return { + id: "speed", + label: "Speed", + icon: , + content: ({ trackId }) => , + }; +} + +function buildMasksTab({ + element, +}: { + element: MaskableElement; +}): PropertiesTabDef { + return { + id: "masks", + label: "Masks", + icon: , + content: ({ trackId }) => , + }; +} + +function buildClipEffectsTab({ + element, +}: { + element: VisualElement; +}): PropertiesTabDef { + return { + id: "effects", + label: "Effects", + icon: , + content: ({ trackId }) => ( + + ), + }; +} + +function buildTextTab({ element }: { element: TextElement }): PropertiesTabDef { + return { + id: "text", + label: "Text", + icon: , + content: ({ trackId }) => , + }; +} + +function buildGraphicTab({ + element, +}: { + element: GraphicElement; +}): PropertiesTabDef { + return { + id: "graphic", + label: "Graphic", + icon: , + content: ({ trackId }) => , + }; +} + +function buildStandaloneEffectTab({ + element, +}: { + element: EffectElement; +}): PropertiesTabDef { + return { + id: "effects", + label: "Effects", + icon: , + content: ({ trackId }) => ( + + ), + }; +} + +function getTextConfig({ + element, +}: { + element: TextElement; +}): ElementPropertiesConfig { + return { + defaultTab: "text", + tabs: [ + buildTextTab({ element }), + buildTransformTab({ element }), + buildBlendingTab({ element }), + ], + }; +} + +function getVideoConfig({ + element, + mediaAsset, +}: { + element: VideoElement; + mediaAsset: MediaAsset | undefined; +}): ElementPropertiesConfig { + const showAudioTab = mediaAsset?.hasAudio !== false; + return { + defaultTab: "transform", + tabs: [ + buildTransformTab({ element }), + ...(showAudioTab ? [buildAudioTab({ element })] : []), + buildSpeedTab({ element }), + buildBlendingTab({ element }), + buildMasksTab({ element }), + buildClipEffectsTab({ element }), + ], + }; +} + +function getImageConfig({ + element, +}: { + element: ImageElement; +}): ElementPropertiesConfig { + return { + defaultTab: "transform", + tabs: [ + buildTransformTab({ element }), + buildBlendingTab({ element }), + buildMasksTab({ element }), + buildClipEffectsTab({ element }), + ], + }; +} + +function getStickerConfig({ + element, +}: { + element: StickerElement; +}): ElementPropertiesConfig { + return { + defaultTab: "transform", + tabs: [ + buildTransformTab({ element }), + buildBlendingTab({ element }), + buildClipEffectsTab({ element }), + ], + }; +} + +function getGraphicConfig({ + element, +}: { + element: GraphicElement; +}): ElementPropertiesConfig { + return { + defaultTab: "graphic", + tabs: [ + buildGraphicTab({ element }), + buildTransformTab({ element }), + buildBlendingTab({ element }), + buildMasksTab({ element }), + buildClipEffectsTab({ element }), + ], + }; +} + +function getAudioConfig({ + element, +}: { + element: AudioElement; +}): ElementPropertiesConfig { + return { + defaultTab: "audio", + tabs: [buildAudioTab({ element }), buildSpeedTab({ element })], + }; +} + +function getEffectConfig({ + element, +}: { + element: EffectElement; +}): ElementPropertiesConfig { + return { + defaultTab: "effects", + tabs: [buildStandaloneEffectTab({ element })], + }; +} + +export function getPropertiesConfig({ + element, + mediaAssets, +}: { + element: TimelineElement; + mediaAssets: MediaAsset[]; +}): ElementPropertiesConfig { + switch (element.type) { + case "text": + return getTextConfig({ element }); + case "video": { + const mediaAsset = mediaAssets.find((a) => a.id === element.mediaId); + return getVideoConfig({ element, mediaAsset }); + } + case "image": + return getImageConfig({ element }); + case "sticker": + return getStickerConfig({ element }); + case "graphic": + return getGraphicConfig({ element }); + case "audio": + return getAudioConfig({ element }); + case "effect": + return getEffectConfig({ element }); + } +} diff --git a/apps/web/src/components/editor/panels/properties/stores/properties-store.ts b/apps/web/src/components/editor/panels/properties/stores/properties-store.ts index 1783e3fc..bd57d4b0 100644 --- a/apps/web/src/components/editor/panels/properties/stores/properties-store.ts +++ b/apps/web/src/components/editor/panels/properties/stores/properties-store.ts @@ -1,18 +1,18 @@ -import { create } from "zustand"; - -interface PropertiesState { - activeTabPerType: Record; - setActiveTab: (elementType: string, tabId: string) => void; - isTransformScaleLocked: boolean; - setTransformScaleLocked: (locked: boolean) => void; -} - -export const usePropertiesStore = create()((set) => ({ - activeTabPerType: {}, - setActiveTab: (elementType, tabId) => - set((state) => ({ - activeTabPerType: { ...state.activeTabPerType, [elementType]: tabId }, - })), - isTransformScaleLocked: false, - setTransformScaleLocked: (locked) => set({ isTransformScaleLocked: locked }), -})); +import { create } from "zustand"; + +interface PropertiesState { + activeTabPerType: Record; + setActiveTab: (elementType: string, tabId: string) => void; + isTransformScaleLocked: boolean; + setTransformScaleLocked: (locked: boolean) => void; +} + +export const usePropertiesStore = create()((set) => ({ + activeTabPerType: {}, + setActiveTab: (elementType, tabId) => + set((state) => ({ + activeTabPerType: { ...state.activeTabPerType, [elementType]: tabId }, + })), + isTransformScaleLocked: false, + setTransformScaleLocked: (locked) => set({ isTransformScaleLocked: locked }), +})); diff --git a/apps/web/src/components/editor/panels/properties/tabs/audio-tab.tsx b/apps/web/src/components/editor/panels/properties/tabs/audio-tab.tsx index 41b6ec6d..a92f7be2 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/audio-tab.tsx +++ b/apps/web/src/components/editor/panels/properties/tabs/audio-tab.tsx @@ -1,109 +1,109 @@ -import { NumberField } from "@/components/ui/number-field"; -import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/lib/timeline/audio-constants"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import { clamp, isNearlyEqual } from "@/utils/math"; -import type { AudioElement, VideoElement } from "@/lib/timeline"; -import { resolveNumberAtTime } from "@/lib/animation"; -import { useElementPlayhead } from "../hooks/use-element-playhead"; -import { useKeyframedNumberProperty } from "../hooks/use-keyframed-number-property"; -import { KeyframeToggle } from "../components/keyframe-toggle"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { VolumeHighIcon } from "@hugeicons/core-free-icons"; -import { - Section, - SectionContent, - SectionField, - SectionFields, - SectionHeader, - SectionTitle, -} from "@/components/section"; - -export function AudioTab({ - element, - trackId, -}: { - element: AudioElement | VideoElement; - trackId: string; -}) { - const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ - startTime: element.startTime, - duration: element.duration, - }); - const resolvedVolume = resolveNumberAtTime({ - baseValue: element.volume ?? DEFAULTS.element.volume, - animations: element.animations, - propertyPath: "volume", - localTime, - }); - const volume = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "volume", - localTime, - isPlayheadWithinElementRange, - displayValue: resolvedVolume.toFixed(1), - parse: (input) => { - const parsed = parseFloat(input); - if (Number.isNaN(parsed)) { - return null; - } - - return clamp({ value: parsed, min: VOLUME_DB_MIN, max: VOLUME_DB_MAX }); - }, - valueAtPlayhead: resolvedVolume, - step: 0.1, - buildBaseUpdates: ({ value }) => ({ - volume: value, - }), - }); - const isDefault = - volume.hasAnimatedKeyframes && isPlayheadWithinElementRange - ? isNearlyEqual({ - leftValue: resolvedVolume, - rightValue: DEFAULTS.element.volume, - }) - : (element.volume ?? DEFAULTS.element.volume) === DEFAULTS.element.volume; - - return ( -
- - Audio - - - - - } - > - } - value={volume.displayValue} - onFocus={volume.onFocus} - onChange={volume.onChange} - onBlur={volume.onBlur} - dragSensitivity="slow" - scrubClamp={{ min: VOLUME_DB_MIN, max: VOLUME_DB_MAX }} - onScrub={volume.scrubTo} - onScrubEnd={volume.commitScrub} - onReset={() => - volume.commitValue({ - value: DEFAULTS.element.volume, - }) - } - isDefault={isDefault} - suffix="dB" - /> - - - -
- ); -} +import { NumberField } from "@/components/ui/number-field"; +import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "@/lib/timeline/audio-constants"; +import { DEFAULTS } from "@/lib/timeline/defaults"; +import { clamp, isNearlyEqual } from "@/utils/math"; +import type { AudioElement, VideoElement } from "@/lib/timeline"; +import { resolveNumberAtTime } from "@/lib/animation"; +import { useElementPlayhead } from "../hooks/use-element-playhead"; +import { useKeyframedNumberProperty } from "../hooks/use-keyframed-number-property"; +import { KeyframeToggle } from "../components/keyframe-toggle"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { VolumeHighIcon } from "@hugeicons/core-free-icons"; +import { + Section, + SectionContent, + SectionField, + SectionFields, + SectionHeader, + SectionTitle, +} from "@/components/section"; + +export function AudioTab({ + element, + trackId, +}: { + element: AudioElement | VideoElement; + trackId: string; +}) { + const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ + startTime: element.startTime, + duration: element.duration, + }); + const resolvedVolume = resolveNumberAtTime({ + baseValue: element.volume ?? DEFAULTS.element.volume, + animations: element.animations, + propertyPath: "volume", + localTime, + }); + const volume = useKeyframedNumberProperty({ + trackId, + elementId: element.id, + animations: element.animations, + propertyPath: "volume", + localTime, + isPlayheadWithinElementRange, + displayValue: resolvedVolume.toFixed(1), + parse: (input) => { + const parsed = parseFloat(input); + if (Number.isNaN(parsed)) { + return null; + } + + return clamp({ value: parsed, min: VOLUME_DB_MIN, max: VOLUME_DB_MAX }); + }, + valueAtPlayhead: resolvedVolume, + step: 0.1, + buildBaseUpdates: ({ value }) => ({ + volume: value, + }), + }); + const isDefault = + volume.hasAnimatedKeyframes && isPlayheadWithinElementRange + ? isNearlyEqual({ + leftValue: resolvedVolume, + rightValue: DEFAULTS.element.volume, + }) + : (element.volume ?? DEFAULTS.element.volume) === DEFAULTS.element.volume; + + return ( +
+ + Audio + + + + + } + > + } + value={volume.displayValue} + onFocus={volume.onFocus} + onChange={volume.onChange} + onBlur={volume.onBlur} + dragSensitivity="slow" + scrubClamp={{ min: VOLUME_DB_MIN, max: VOLUME_DB_MAX }} + onScrub={volume.scrubTo} + onScrubEnd={volume.commitScrub} + onReset={() => + volume.commitValue({ + value: DEFAULTS.element.volume, + }) + } + isDefault={isDefault} + suffix="dB" + /> + + + +
+ ); +} diff --git a/apps/web/src/components/editor/panels/properties/tabs/blending-tab.tsx b/apps/web/src/components/editor/panels/properties/tabs/blending-tab.tsx index c360817c..fdcde148 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/blending-tab.tsx +++ b/apps/web/src/components/editor/panels/properties/tabs/blending-tab.tsx @@ -1,230 +1,230 @@ -import { useEditor } from "@/hooks/use-editor"; -import { clamp } from "@/utils/math"; -import { NumberField } from "@/components/ui/number-field"; -import { OcCheckerboardIcon } from "@opencut/ui/icons"; -import { Fragment, useRef } from "react"; -import { useMenuPreview } from "@/hooks/use-menu-preview"; -import { - Section, - SectionContent, - SectionField, - SectionHeader, - SectionTitle, -} from "@/components/section"; -import { - Select, - SelectContent, - SelectItem, - SelectSeparator, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import type { BlendMode } from "@/lib/rendering"; -import type { ElementType } from "@/lib/timeline"; -import type { ElementAnimations } from "@/lib/animation/types"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { RainDropIcon } from "@hugeicons/core-free-icons"; -import { KeyframeToggle } from "../components/keyframe-toggle"; -import { useKeyframedNumberProperty } from "../hooks/use-keyframed-number-property"; -import { useElementPlayhead } from "../hooks/use-element-playhead"; -import { resolveOpacityAtTime } from "@/lib/animation"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import { isPropertyAtDefault } from "./transform-tab"; - -type BlendingElement = { - id: string; - opacity: number; - type: ElementType; - blendMode?: BlendMode; - startTime: number; - duration: number; - animations?: ElementAnimations; -}; - -const BLEND_MODE_GROUPS = [ - [{ value: "normal", label: "Normal" }], - [ - { value: "darken", label: "Darken" }, - { value: "multiply", label: "Multiply" }, - { value: "color-burn", label: "Color Burn" }, - ], - [ - { value: "lighten", label: "Lighten" }, - { value: "screen", label: "Screen" }, - { value: "plus-lighter", label: "Plus Lighter" }, - { value: "color-dodge", label: "Color Dodge" }, - ], - [ - { value: "overlay", label: "Overlay" }, - { value: "soft-light", label: "Soft Light" }, - { value: "hard-light", label: "Hard Light" }, - ], - [ - { value: "difference", label: "Difference" }, - { value: "exclusion", label: "Exclusion" }, - ], - [ - { value: "hue", label: "Hue" }, - { value: "saturation", label: "Saturation" }, - { value: "color", label: "Color" }, - { value: "luminosity", label: "Luminosity" }, - ], -]; - -export function BlendingTab({ - element, - trackId, -}: { - element: BlendingElement; - trackId: string; -}) { - const editor = useEditor(); - const isPreviewActive = useEditor((e) => e.timeline.isPreviewActive()); - const blendMode = element.blendMode ?? DEFAULTS.element.blendMode; - const committedBlendModeRef = useRef(blendMode); - if (!isPreviewActive) { - committedBlendModeRef.current = blendMode; - } - - const { - onPointerLeave, - onOpenChange: handleBlendModeOpenChange, - markCommitted, - } = useMenuPreview(); - - const previewBlendMode = ({ value }: { value: BlendMode }) => - editor.timeline.previewElements({ - updates: [ - { trackId, elementId: element.id, updates: { blendMode: value } }, - ], - }); - - const commitBlendMode = (value: string) => { - if (editor.timeline.isPreviewActive()) { - editor.timeline.commitPreview(); - } else { - editor.timeline.updateElements({ - updates: [ - { - trackId, - elementId: element.id, - updates: { blendMode: value as BlendMode }, - }, - ], - }); - } - markCommitted(); - }; - - const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ - startTime: element.startTime, - duration: element.duration, - }); - const resolvedOpacity = resolveOpacityAtTime({ - baseOpacity: element.opacity, - animations: element.animations, - localTime, - }); - - const opacity = useKeyframedNumberProperty({ - trackId, - elementId: element.id, - animations: element.animations, - propertyPath: "opacity", - localTime, - isPlayheadWithinElementRange, - displayValue: Math.round(resolvedOpacity * 100).toString(), - parse: (input) => { - const parsed = parseFloat(input); - if (Number.isNaN(parsed)) return null; - return clamp({ value: parsed, min: 0, max: 100 }) / 100; - }, - valueAtPlayhead: resolvedOpacity, - step: 0.01, - buildBaseUpdates: ({ value }) => ({ opacity: value }), - }); - - return ( -
- - Blending - - -
- - } - > - - } - value={opacity.displayValue} - min={0} - max={100} - onFocus={opacity.onFocus} - onChange={opacity.onChange} - onBlur={opacity.onBlur} - onScrub={opacity.scrubTo} - onScrubEnd={opacity.commitScrub} - onReset={() => - opacity.commitValue({ value: DEFAULTS.element.opacity }) - } - isDefault={isPropertyAtDefault({ - hasAnimatedKeyframes: opacity.hasAnimatedKeyframes, - isPlayheadWithinElementRange, - resolvedValue: resolvedOpacity, - staticValue: element.opacity, - defaultValue: DEFAULTS.element.opacity, - })} - dragSensitivity="slow" - /> - - - - -
-
-
- ); -} +import { useEditor } from "@/hooks/use-editor"; +import { clamp } from "@/utils/math"; +import { NumberField } from "@/components/ui/number-field"; +import { OcCheckerboardIcon } from "@opencut/ui/icons"; +import { Fragment, useRef } from "react"; +import { useMenuPreview } from "@/hooks/use-menu-preview"; +import { + Section, + SectionContent, + SectionField, + SectionHeader, + SectionTitle, +} from "@/components/section"; +import { + Select, + SelectContent, + SelectItem, + SelectSeparator, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import type { BlendMode } from "@/lib/rendering"; +import type { ElementType } from "@/lib/timeline"; +import type { ElementAnimations } from "@/lib/animation/types"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { RainDropIcon } from "@hugeicons/core-free-icons"; +import { KeyframeToggle } from "../components/keyframe-toggle"; +import { useKeyframedNumberProperty } from "../hooks/use-keyframed-number-property"; +import { useElementPlayhead } from "../hooks/use-element-playhead"; +import { resolveOpacityAtTime } from "@/lib/animation"; +import { DEFAULTS } from "@/lib/timeline/defaults"; +import { isPropertyAtDefault } from "./transform-tab"; + +type BlendingElement = { + id: string; + opacity: number; + type: ElementType; + blendMode?: BlendMode; + startTime: number; + duration: number; + animations?: ElementAnimations; +}; + +const BLEND_MODE_GROUPS = [ + [{ value: "normal", label: "Normal" }], + [ + { value: "darken", label: "Darken" }, + { value: "multiply", label: "Multiply" }, + { value: "color-burn", label: "Color Burn" }, + ], + [ + { value: "lighten", label: "Lighten" }, + { value: "screen", label: "Screen" }, + { value: "plus-lighter", label: "Plus Lighter" }, + { value: "color-dodge", label: "Color Dodge" }, + ], + [ + { value: "overlay", label: "Overlay" }, + { value: "soft-light", label: "Soft Light" }, + { value: "hard-light", label: "Hard Light" }, + ], + [ + { value: "difference", label: "Difference" }, + { value: "exclusion", label: "Exclusion" }, + ], + [ + { value: "hue", label: "Hue" }, + { value: "saturation", label: "Saturation" }, + { value: "color", label: "Color" }, + { value: "luminosity", label: "Luminosity" }, + ], +]; + +export function BlendingTab({ + element, + trackId, +}: { + element: BlendingElement; + trackId: string; +}) { + const editor = useEditor(); + const isPreviewActive = useEditor((e) => e.timeline.isPreviewActive()); + const blendMode = element.blendMode ?? DEFAULTS.element.blendMode; + const committedBlendModeRef = useRef(blendMode); + if (!isPreviewActive) { + committedBlendModeRef.current = blendMode; + } + + const { + onPointerLeave, + onOpenChange: handleBlendModeOpenChange, + markCommitted, + } = useMenuPreview(); + + const previewBlendMode = ({ value }: { value: BlendMode }) => + editor.timeline.previewElements({ + updates: [ + { trackId, elementId: element.id, updates: { blendMode: value } }, + ], + }); + + const commitBlendMode = (value: string) => { + if (editor.timeline.isPreviewActive()) { + editor.timeline.commitPreview(); + } else { + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { blendMode: value as BlendMode }, + }, + ], + }); + } + markCommitted(); + }; + + const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ + startTime: element.startTime, + duration: element.duration, + }); + const resolvedOpacity = resolveOpacityAtTime({ + baseOpacity: element.opacity, + animations: element.animations, + localTime, + }); + + const opacity = useKeyframedNumberProperty({ + trackId, + elementId: element.id, + animations: element.animations, + propertyPath: "opacity", + localTime, + isPlayheadWithinElementRange, + displayValue: Math.round(resolvedOpacity * 100).toString(), + parse: (input) => { + const parsed = parseFloat(input); + if (Number.isNaN(parsed)) return null; + return clamp({ value: parsed, min: 0, max: 100 }) / 100; + }, + valueAtPlayhead: resolvedOpacity, + step: 0.01, + buildBaseUpdates: ({ value }) => ({ opacity: value }), + }); + + return ( +
+ + Blending + + +
+ + } + > + + } + value={opacity.displayValue} + min={0} + max={100} + onFocus={opacity.onFocus} + onChange={opacity.onChange} + onBlur={opacity.onBlur} + onScrub={opacity.scrubTo} + onScrubEnd={opacity.commitScrub} + onReset={() => + opacity.commitValue({ value: DEFAULTS.element.opacity }) + } + isDefault={isPropertyAtDefault({ + hasAnimatedKeyframes: opacity.hasAnimatedKeyframes, + isPlayheadWithinElementRange, + resolvedValue: resolvedOpacity, + staticValue: element.opacity, + defaultValue: DEFAULTS.element.opacity, + })} + dragSensitivity="slow" + /> + + + + +
+
+
+ ); +} diff --git a/apps/web/src/components/editor/panels/properties/tabs/effects-tab.tsx b/apps/web/src/components/editor/panels/properties/tabs/effects-tab.tsx index 1b94ee2d..759ced60 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/effects-tab.tsx +++ b/apps/web/src/components/editor/panels/properties/tabs/effects-tab.tsx @@ -1,309 +1,309 @@ -"use client"; - -import { useState } from "react"; -import type { ParamValues } from "@/lib/params"; -import type { Effect } from "@/lib/effects/types"; -import type { EffectElement, VisualElement } from "@/lib/timeline"; -import { effectsRegistry } from "@/lib/effects"; -import { useEditor } from "@/hooks/use-editor"; -import { useElementPreview } from "@/hooks/use-element-preview"; -import { - Section, - SectionContent, - SectionHeader, - SectionTitle, - SectionFields, -} from "@/components/section"; -import { PropertyParamField } from "../components/property-param-field"; -import { Button } from "@/components/ui/button"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { - Delete02Icon, - ViewIcon, - ViewOffSlashIcon, - MagicWand05Icon, -} from "@hugeicons/core-free-icons"; -import { cn } from "@/utils/ui"; -import { Separator } from "@/components/ui/separator"; -import { useAssetsPanelStore } from "@/stores/assets-panel-store"; - -export function StandaloneEffectTab({ - element, - trackId, -}: { - element: EffectElement; - trackId: string; -}) { - const { renderElement, previewUpdates, commit } = useElementPreview({ - trackId, - elementId: element.id, - fallback: element, - }); - - const effect: Effect = { - id: element.id, - type: element.effectType, - params: element.params, - enabled: true, - }; - - const previewParam = (key: string) => (value: number | string | boolean) => { - previewUpdates({ - params: { ...(renderElement as EffectElement).params, [key]: value }, - }); - }; - - return ( -
-
- Effect -
- -
- ); -} - -export function ClipEffectsTab({ - element, - trackId, -}: { - element: VisualElement; - trackId: string; -}) { - const [dragIndex, setDragIndex] = useState(null); - const [dropIndex, setDropIndex] = useState(null); - const editor = useEditor(); - const { renderElement, previewUpdates, commit } = useElementPreview({ - trackId, - elementId: element.id, - fallback: element, - }); - - const effects: Effect[] = element.effects ?? []; - - const getRenderParams = ({ effectId }: { effectId: string }): ParamValues => { - return ( - (renderElement as VisualElement).effects?.find((ef) => ef.id === effectId) - ?.params ?? - effects.find((ef) => ef.id === effectId)?.params ?? - {} - ); - }; - - const buildPreviewParam = - (effectId: string) => - (key: string) => - (value: number | string | boolean) => { - const updatedEffects = ( - (renderElement as VisualElement).effects ?? [] - ).map((existing) => - existing.id !== effectId - ? existing - : { ...existing, params: { ...existing.params, [key]: value } }, - ); - previewUpdates({ effects: updatedEffects }); - }; - - const handleDragStart = ({ index }: { index: number }) => setDragIndex(index); - - const handleDragOver = ({ - event, - index, - }: { - event: React.DragEvent; - index: number; - }) => { - event.preventDefault(); - if (index !== dropIndex) setDropIndex(index); - }; - - const handleDrop = ({ toIndex }: { toIndex: number }) => { - if (dragIndex !== null && dragIndex !== toIndex) { - editor.timeline.reorderClipEffects({ - trackId, - elementId: element.id, - fromIndex: dragIndex, - toIndex, - }); - } - setDragIndex(null); - setDropIndex(null); - }; - - const handleDragEnd = () => { - setDragIndex(null); - setDropIndex(null); - }; - - return ( -
-
- Effects -
- {effects.length === 0 ? ( - - ) : ( -
    - {effects.map((effect, index) => { - const resolvedDragIndex = dragIndex ?? -1; - const isDragging = dragIndex === index; - const isDropTarget = - dropIndex === index && dragIndex !== null && dragIndex !== index; - const showTopDropIndicator = - isDropTarget && index < resolvedDragIndex; - const showBottomDropIndicator = - isDropTarget && index > resolvedDragIndex; - - return ( -
  • handleDragStart({ index })} - onDragOver={(event) => handleDragOver({ event, index })} - onDrop={() => handleDrop({ toIndex: index })} - onDragEnd={handleDragEnd} - className={cn( - "group list-none", - isDragging && "opacity-40", - showTopDropIndicator && "border-t-2 border-primary", - showBottomDropIndicator && "border-b-2 border-primary", - )} - > - - editor.timeline.toggleClipEffect({ - trackId, - elementId: element.id, - effectId: effect.id, - }) - } - onRemove={() => - editor.timeline.removeClipEffect({ - trackId, - elementId: element.id, - effectId: effect.id, - }) - } - /> -
  • - ); - })} -
- )} -
- ); -} - -function EmptyView() { - const setActiveTab = useAssetsPanelStore((s) => s.setActiveTab); - - return ( -
- -
-

No effects

-

- Add effects to this layer from the Assets panel. -

-
- -
- ); -} - -function EffectSection({ - effect, - renderParams, - previewParam, - onCommit, - onToggle, - onRemove, -}: { - effect: Effect; - renderParams: ParamValues; - previewParam: (key: string) => (value: number | string | boolean) => void; - onCommit: () => void; - onToggle?: () => void; - onRemove?: () => void; -}) { - const definition = effectsRegistry.get(effect.type); - - return ( -
- - - -
- ) - } - > - - {definition.name} - - - - - {definition.params.map((param) => ( -
-
- -
- -
- ))} -
-
- - ); -} +"use client"; + +import { useState } from "react"; +import type { ParamValues } from "@/lib/params"; +import type { Effect } from "@/lib/effects/types"; +import type { EffectElement, VisualElement } from "@/lib/timeline"; +import { effectsRegistry } from "@/lib/effects"; +import { useEditor } from "@/hooks/use-editor"; +import { useElementPreview } from "@/hooks/use-element-preview"; +import { + Section, + SectionContent, + SectionHeader, + SectionTitle, + SectionFields, +} from "@/components/section"; +import { PropertyParamField } from "../components/property-param-field"; +import { Button } from "@/components/ui/button"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { + Delete02Icon, + ViewIcon, + ViewOffSlashIcon, + MagicWand05Icon, +} from "@hugeicons/core-free-icons"; +import { cn } from "@/utils/ui"; +import { Separator } from "@/components/ui/separator"; +import { useAssetsPanelStore } from "@/stores/assets-panel-store"; + +export function StandaloneEffectTab({ + element, + trackId, +}: { + element: EffectElement; + trackId: string; +}) { + const { renderElement, previewUpdates, commit } = useElementPreview({ + trackId, + elementId: element.id, + fallback: element, + }); + + const effect: Effect = { + id: element.id, + type: element.effectType, + params: element.params, + enabled: true, + }; + + const previewParam = (key: string) => (value: number | string | boolean) => { + previewUpdates({ + params: { ...(renderElement as EffectElement).params, [key]: value }, + }); + }; + + return ( +
+
+ Effect +
+ +
+ ); +} + +export function ClipEffectsTab({ + element, + trackId, +}: { + element: VisualElement; + trackId: string; +}) { + const [dragIndex, setDragIndex] = useState(null); + const [dropIndex, setDropIndex] = useState(null); + const editor = useEditor(); + const { renderElement, previewUpdates, commit } = useElementPreview({ + trackId, + elementId: element.id, + fallback: element, + }); + + const effects: Effect[] = element.effects ?? []; + + const getRenderParams = ({ effectId }: { effectId: string }): ParamValues => { + return ( + (renderElement as VisualElement).effects?.find((ef) => ef.id === effectId) + ?.params ?? + effects.find((ef) => ef.id === effectId)?.params ?? + {} + ); + }; + + const buildPreviewParam = + (effectId: string) => + (key: string) => + (value: number | string | boolean) => { + const updatedEffects = ( + (renderElement as VisualElement).effects ?? [] + ).map((existing) => + existing.id !== effectId + ? existing + : { ...existing, params: { ...existing.params, [key]: value } }, + ); + previewUpdates({ effects: updatedEffects }); + }; + + const handleDragStart = ({ index }: { index: number }) => setDragIndex(index); + + const handleDragOver = ({ + event, + index, + }: { + event: React.DragEvent; + index: number; + }) => { + event.preventDefault(); + if (index !== dropIndex) setDropIndex(index); + }; + + const handleDrop = ({ toIndex }: { toIndex: number }) => { + if (dragIndex !== null && dragIndex !== toIndex) { + editor.timeline.reorderClipEffects({ + trackId, + elementId: element.id, + fromIndex: dragIndex, + toIndex, + }); + } + setDragIndex(null); + setDropIndex(null); + }; + + const handleDragEnd = () => { + setDragIndex(null); + setDropIndex(null); + }; + + return ( +
+
+ Effects +
+ {effects.length === 0 ? ( + + ) : ( +
    + {effects.map((effect, index) => { + const resolvedDragIndex = dragIndex ?? -1; + const isDragging = dragIndex === index; + const isDropTarget = + dropIndex === index && dragIndex !== null && dragIndex !== index; + const showTopDropIndicator = + isDropTarget && index < resolvedDragIndex; + const showBottomDropIndicator = + isDropTarget && index > resolvedDragIndex; + + return ( +
  • handleDragStart({ index })} + onDragOver={(event) => handleDragOver({ event, index })} + onDrop={() => handleDrop({ toIndex: index })} + onDragEnd={handleDragEnd} + className={cn( + "group list-none", + isDragging && "opacity-40", + showTopDropIndicator && "border-t-2 border-primary", + showBottomDropIndicator && "border-b-2 border-primary", + )} + > + + editor.timeline.toggleClipEffect({ + trackId, + elementId: element.id, + effectId: effect.id, + }) + } + onRemove={() => + editor.timeline.removeClipEffect({ + trackId, + elementId: element.id, + effectId: effect.id, + }) + } + /> +
  • + ); + })} +
+ )} +
+ ); +} + +function EmptyView() { + const setActiveTab = useAssetsPanelStore((s) => s.setActiveTab); + + return ( +
+ +
+

No effects

+

+ Add effects to this layer from the Assets panel. +

+
+ +
+ ); +} + +function EffectSection({ + effect, + renderParams, + previewParam, + onCommit, + onToggle, + onRemove, +}: { + effect: Effect; + renderParams: ParamValues; + previewParam: (key: string) => (value: number | string | boolean) => void; + onCommit: () => void; + onToggle?: () => void; + onRemove?: () => void; +}) { + const definition = effectsRegistry.get(effect.type); + + return ( +
+ + + +
+ ) + } + > + + {definition.name} + + + + + {definition.params.map((param) => ( +
+
+ +
+ +
+ ))} +
+
+ + ); +} diff --git a/apps/web/src/components/editor/panels/properties/tabs/graphic-tab.tsx b/apps/web/src/components/editor/panels/properties/tabs/graphic-tab.tsx index 22dae8cc..4593050e 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/graphic-tab.tsx +++ b/apps/web/src/components/editor/panels/properties/tabs/graphic-tab.tsx @@ -1,238 +1,238 @@ -"use client"; - -import { useRef } from "react"; -import { useElementPlayhead } from "../hooks/use-element-playhead"; -import { - useKeyframedParamProperty, - type KeyframedParamPropertyResult, -} from "../hooks/use-keyframed-param-property"; -import { resolveGraphicParamsAtTime } from "@/lib/animation"; -import type { ParamDefinition, ParamValues } from "@/lib/params"; -import type { GraphicElement } from "@/lib/timeline"; -import { graphicsRegistry, registerDefaultGraphics } from "@/lib/graphics"; -import { useElementPreview } from "@/hooks/use-element-preview"; -import { useEditor } from "@/hooks/use-editor"; -import { - Section, - SectionContent, - SectionFields, - SectionHeader, - SectionTitle, -} from "@/components/section"; -import { PropertyParamField } from "../components/property-param-field"; -import { Button } from "@/components/ui/button"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { MinusSignIcon, PlusSignIcon } from "@hugeicons/core-free-icons"; -import { cn } from "@/utils/ui"; - -registerDefaultGraphics(); - -const DEFAULT_STROKE_WIDTH = 2; - -export function GraphicTab({ - element, - trackId, -}: { - element: GraphicElement; - trackId: string; -}) { - const definition = graphicsRegistry.get(element.definitionId); - const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ - startTime: element.startTime, - duration: element.duration, - }); - const { renderElement } = useElementPreview({ - trackId, - elementId: element.id, - fallback: element, - }); - - const liveElement = renderElement as GraphicElement; - const resolvedParams = resolveGraphicParamsAtTime({ - element: liveElement, - localTime, - }); - - const shapeParams = definition.params.filter((p) => p.group !== "stroke"); - const hasStrokeParams = definition.params.some((p) => p.group === "stroke"); - - return ( -
-
- - {definition.name} - - - - {shapeParams.map((param) => ( - - ))} - - -
- {hasStrokeParams && ( - - )} -
- ); -} - -function StrokeSection({ - element, - trackId, -}: { - element: GraphicElement; - trackId: string; -}) { - const editor = useEditor(); - const definition = graphicsRegistry.get(element.definitionId); - const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ - startTime: element.startTime, - duration: element.duration, - }); - const { renderElement } = useElementPreview({ - trackId, - elementId: element.id, - fallback: element, - }); - - const liveElement = renderElement as GraphicElement; - const resolvedParams = resolveGraphicParamsAtTime({ - element: liveElement, - localTime, - }); - const strokeParams = definition.params.filter((p) => p.group === "stroke"); - const lastStrokeWidth = useRef(DEFAULT_STROKE_WIDTH); - const isStrokeEnabled = Number(element.params.strokeWidth ?? 0) > 0; - - const toggleStroke = () => { - if (isStrokeEnabled) { - lastStrokeWidth.current = Number( - element.params.strokeWidth ?? DEFAULT_STROKE_WIDTH, - ); - editor.timeline.updateElements({ - updates: [ - { - trackId, - elementId: element.id, - updates: { params: { ...element.params, strokeWidth: 0 } }, - }, - ], - }); - } else { - editor.timeline.updateElements({ - updates: [ - { - trackId, - elementId: element.id, - updates: { - params: { - ...element.params, - strokeWidth: lastStrokeWidth.current, - }, - }, - }, - ], - }); - } - }; - - return ( -
- { - event.stopPropagation(); - toggleStroke(); - }} - > - - - } - > - Stroke - - - - {strokeParams.map((param) => ( - - ))} - - -
- ); -} - -function AnimatedGraphicParamField({ - param, - trackId, - element, - localTime, - isPlayheadWithinElementRange, - resolvedParams, -}: { - param: ParamDefinition; - trackId: string; - element: GraphicElement; - localTime: number; - isPlayheadWithinElementRange: boolean; - resolvedParams: ParamValues; -}) { - const animatedParam: KeyframedParamPropertyResult = useKeyframedParamProperty({ - param, - trackId, - elementId: element.id, - animations: element.animations, - localTime, - isPlayheadWithinElementRange, - resolvedValue: resolvedParams[param.key] ?? param.default, - buildBaseUpdates: ({ value }) => ({ - params: { - ...element.params, - [param.key]: value, - }, - }), - }); - - return ( - - ); -} +"use client"; + +import { useRef } from "react"; +import { useElementPlayhead } from "../hooks/use-element-playhead"; +import { + useKeyframedParamProperty, + type KeyframedParamPropertyResult, +} from "../hooks/use-keyframed-param-property"; +import { resolveGraphicParamsAtTime } from "@/lib/animation"; +import type { ParamDefinition, ParamValues } from "@/lib/params"; +import type { GraphicElement } from "@/lib/timeline"; +import { graphicsRegistry, registerDefaultGraphics } from "@/lib/graphics"; +import { useElementPreview } from "@/hooks/use-element-preview"; +import { useEditor } from "@/hooks/use-editor"; +import { + Section, + SectionContent, + SectionFields, + SectionHeader, + SectionTitle, +} from "@/components/section"; +import { PropertyParamField } from "../components/property-param-field"; +import { Button } from "@/components/ui/button"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { MinusSignIcon, PlusSignIcon } from "@hugeicons/core-free-icons"; +import { cn } from "@/utils/ui"; + +registerDefaultGraphics(); + +const DEFAULT_STROKE_WIDTH = 2; + +export function GraphicTab({ + element, + trackId, +}: { + element: GraphicElement; + trackId: string; +}) { + const definition = graphicsRegistry.get(element.definitionId); + const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ + startTime: element.startTime, + duration: element.duration, + }); + const { renderElement } = useElementPreview({ + trackId, + elementId: element.id, + fallback: element, + }); + + const liveElement = renderElement as GraphicElement; + const resolvedParams = resolveGraphicParamsAtTime({ + element: liveElement, + localTime, + }); + + const shapeParams = definition.params.filter((p) => p.group !== "stroke"); + const hasStrokeParams = definition.params.some((p) => p.group === "stroke"); + + return ( +
+
+ + {definition.name} + + + + {shapeParams.map((param) => ( + + ))} + + +
+ {hasStrokeParams && ( + + )} +
+ ); +} + +function StrokeSection({ + element, + trackId, +}: { + element: GraphicElement; + trackId: string; +}) { + const editor = useEditor(); + const definition = graphicsRegistry.get(element.definitionId); + const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ + startTime: element.startTime, + duration: element.duration, + }); + const { renderElement } = useElementPreview({ + trackId, + elementId: element.id, + fallback: element, + }); + + const liveElement = renderElement as GraphicElement; + const resolvedParams = resolveGraphicParamsAtTime({ + element: liveElement, + localTime, + }); + const strokeParams = definition.params.filter((p) => p.group === "stroke"); + const lastStrokeWidth = useRef(DEFAULT_STROKE_WIDTH); + const isStrokeEnabled = Number(element.params.strokeWidth ?? 0) > 0; + + const toggleStroke = () => { + if (isStrokeEnabled) { + lastStrokeWidth.current = Number( + element.params.strokeWidth ?? DEFAULT_STROKE_WIDTH, + ); + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { params: { ...element.params, strokeWidth: 0 } }, + }, + ], + }); + } else { + editor.timeline.updateElements({ + updates: [ + { + trackId, + elementId: element.id, + updates: { + params: { + ...element.params, + strokeWidth: lastStrokeWidth.current, + }, + }, + }, + ], + }); + } + }; + + return ( +
+ { + event.stopPropagation(); + toggleStroke(); + }} + > + + + } + > + Stroke + + + + {strokeParams.map((param) => ( + + ))} + + +
+ ); +} + +function AnimatedGraphicParamField({ + param, + trackId, + element, + localTime, + isPlayheadWithinElementRange, + resolvedParams, +}: { + param: ParamDefinition; + trackId: string; + element: GraphicElement; + localTime: number; + isPlayheadWithinElementRange: boolean; + resolvedParams: ParamValues; +}) { + const animatedParam: KeyframedParamPropertyResult = useKeyframedParamProperty({ + param, + trackId, + elementId: element.id, + animations: element.animations, + localTime, + isPlayheadWithinElementRange, + resolvedValue: resolvedParams[param.key] ?? param.default, + buildBaseUpdates: ({ value }) => ({ + params: { + ...element.params, + [param.key]: value, + }, + }), + }); + + return ( + + ); +} diff --git a/apps/web/src/components/editor/panels/properties/tabs/speed-tab.tsx b/apps/web/src/components/editor/panels/properties/tabs/speed-tab.tsx index 44f5dd74..4434890e 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/speed-tab.tsx +++ b/apps/web/src/components/editor/panels/properties/tabs/speed-tab.tsx @@ -1,137 +1,137 @@ -import { useRef } from "react"; -import { useEditor } from "@/hooks/use-editor"; -import { NumberField } from "@/components/ui/number-field"; -import { Switch } from "@/components/ui/switch"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { DashboardSpeed02Icon } from "@hugeicons/core-free-icons"; -import { buildConstantRetime } from "@/lib/retime"; -import { - DEFAULT_RETIME_RATE, - MIN_RETIME_RATE, - MAX_RETIME_RATE, - clampRetimeRate, - canMaintainPitch, -} from "@/constants/retime-constants"; -import type { AudioElement, VideoElement } from "@/lib/timeline"; -import { - Section, - SectionContent, - SectionField, - SectionFields, - SectionHeader, - SectionTitle, -} from "@/components/section"; -import { usePropertyDraft } from "../hooks/use-property-draft"; - -function rateToDisplay({ rate }: { rate: number }): string { - return rate.toFixed(2); -} - -function parseSpeedInput({ input }: { input: string }): number | null { - const parsed = parseFloat(input); - if (Number.isNaN(parsed)) return null; - const rounded = Math.round(parsed * 100) / 100; - return clampRetimeRate({ rate: rounded }); -} - -function buildRetime({ - rate, - maintainPitch, -}: { - rate: number; - maintainPitch: boolean; -}) { - if (rate === DEFAULT_RETIME_RATE && !maintainPitch) return undefined; - return buildConstantRetime({ rate, maintainPitch }); -} - -export function SpeedTab({ - element, - trackId, -}: { - element: AudioElement | VideoElement; - trackId: string; -}) { - const editor = useEditor(); - const rate = clampRetimeRate({ - rate: element.retime?.rate ?? DEFAULT_RETIME_RATE, - }); - const maintainPitchAvailable = canMaintainPitch({ rate }); - const maintainPitch = element.retime?.maintainPitch ?? false; - const pendingRateRef = useRef(rate); - - const applyRetime = ({ - rate: nextRate, - maintainPitch: nextMaintainPitch, - pushHistory = true, - }: { - rate: number; - maintainPitch: boolean; - pushHistory?: boolean; - }) => { - editor.timeline.updateElementRetime({ - trackId, - elementId: element.id, - retime: buildRetime({ rate: nextRate, maintainPitch: nextMaintainPitch }), - pushHistory, - }); - }; - - const speedDraft = usePropertyDraft({ - displayValue: rateToDisplay({ rate }), - parse: (input) => parseSpeedInput({ input }), - onPreview: (nextRate) => { - pendingRateRef.current = nextRate; - applyRetime({ rate: nextRate, maintainPitch, pushHistory: false }); - }, - onCommit: () => { - applyRetime({ rate: pendingRateRef.current, maintainPitch }); - }, - }); - - return ( -
- - Speed - - - - - } - value={speedDraft.displayValue} - suffix="x" - scrubRanges={[ - { from: 0.01, to: 1, pixelsPerUnit: 160 }, - { from: 1, to: 5, pixelsPerUnit: 48 }, - ]} - scrubClamp={{ min: MIN_RETIME_RATE, max: MAX_RETIME_RATE }} - onFocus={() => { - pendingRateRef.current = rate; - speedDraft.onFocus(); - }} - onChange={speedDraft.onChange} - onBlur={speedDraft.onBlur} - onScrub={speedDraft.scrubTo} - onScrubEnd={speedDraft.commitScrub} - onReset={() => - applyRetime({ rate: DEFAULT_RETIME_RATE, maintainPitch }) - } - isDefault={rate === DEFAULT_RETIME_RATE} - /> - -
- Change pitch - - applyRetime({ rate, maintainPitch: !checked }) - } - /> -
-
-
-
- ); -} +import { useRef } from "react"; +import { useEditor } from "@/hooks/use-editor"; +import { NumberField } from "@/components/ui/number-field"; +import { Switch } from "@/components/ui/switch"; +import { HugeiconsIcon } from "@hugeicons/react"; +import { DashboardSpeed02Icon } from "@hugeicons/core-free-icons"; +import { buildConstantRetime } from "@/lib/retime"; +import { + DEFAULT_RETIME_RATE, + MIN_RETIME_RATE, + MAX_RETIME_RATE, + clampRetimeRate, + canMaintainPitch, +} from "@/constants/retime-constants"; +import type { AudioElement, VideoElement } from "@/lib/timeline"; +import { + Section, + SectionContent, + SectionField, + SectionFields, + SectionHeader, + SectionTitle, +} from "@/components/section"; +import { usePropertyDraft } from "../hooks/use-property-draft"; + +function rateToDisplay({ rate }: { rate: number }): string { + return rate.toFixed(2); +} + +function parseSpeedInput({ input }: { input: string }): number | null { + const parsed = parseFloat(input); + if (Number.isNaN(parsed)) return null; + const rounded = Math.round(parsed * 100) / 100; + return clampRetimeRate({ rate: rounded }); +} + +function buildRetime({ + rate, + maintainPitch, +}: { + rate: number; + maintainPitch: boolean; +}) { + if (rate === DEFAULT_RETIME_RATE && !maintainPitch) return undefined; + return buildConstantRetime({ rate, maintainPitch }); +} + +export function SpeedTab({ + element, + trackId, +}: { + element: AudioElement | VideoElement; + trackId: string; +}) { + const editor = useEditor(); + const rate = clampRetimeRate({ + rate: element.retime?.rate ?? DEFAULT_RETIME_RATE, + }); + const maintainPitchAvailable = canMaintainPitch({ rate }); + const maintainPitch = element.retime?.maintainPitch ?? false; + const pendingRateRef = useRef(rate); + + const applyRetime = ({ + rate: nextRate, + maintainPitch: nextMaintainPitch, + pushHistory = true, + }: { + rate: number; + maintainPitch: boolean; + pushHistory?: boolean; + }) => { + editor.timeline.updateElementRetime({ + trackId, + elementId: element.id, + retime: buildRetime({ rate: nextRate, maintainPitch: nextMaintainPitch }), + pushHistory, + }); + }; + + const speedDraft = usePropertyDraft({ + displayValue: rateToDisplay({ rate }), + parse: (input) => parseSpeedInput({ input }), + onPreview: (nextRate) => { + pendingRateRef.current = nextRate; + applyRetime({ rate: nextRate, maintainPitch, pushHistory: false }); + }, + onCommit: () => { + applyRetime({ rate: pendingRateRef.current, maintainPitch }); + }, + }); + + return ( +
+ + Speed + + + + + } + value={speedDraft.displayValue} + suffix="x" + scrubRanges={[ + { from: 0.01, to: 1, pixelsPerUnit: 160 }, + { from: 1, to: 5, pixelsPerUnit: 48 }, + ]} + scrubClamp={{ min: MIN_RETIME_RATE, max: MAX_RETIME_RATE }} + onFocus={() => { + pendingRateRef.current = rate; + speedDraft.onFocus(); + }} + onChange={speedDraft.onChange} + onBlur={speedDraft.onBlur} + onScrub={speedDraft.scrubTo} + onScrubEnd={speedDraft.commitScrub} + onReset={() => + applyRetime({ rate: DEFAULT_RETIME_RATE, maintainPitch }) + } + isDefault={rate === DEFAULT_RETIME_RATE} + /> + +
+ Change pitch + + applyRetime({ rate, maintainPitch: !checked }) + } + /> +
+
+
+
+ ); +} diff --git a/apps/web/src/components/editor/panels/properties/tabs/text-tab.tsx b/apps/web/src/components/editor/panels/properties/tabs/text-tab.tsx index f8e2527a..45831dc0 100644 --- a/apps/web/src/components/editor/panels/properties/tabs/text-tab.tsx +++ b/apps/web/src/components/editor/panels/properties/tabs/text-tab.tsx @@ -1,762 +1,762 @@ -"use client"; - -import { Textarea } from "@/components/ui/textarea"; -import { FontPicker } from "@/components/ui/font-picker"; -import type { TextElement } from "@/lib/timeline"; -import { NumberField } from "@/components/ui/number-field"; -import { useRef } from "react"; -import { - Section, - SectionContent, - SectionField, - SectionFields, - SectionHeader, - SectionTitle, -} from "@/components/section"; -import { ColorPicker } from "@/components/ui/color-picker"; -import { Button } from "@/components/ui/button"; -import { uppercase } from "@/utils/string"; -import { clamp } from "@/utils/math"; -import { useEditor } from "@/hooks/use-editor"; -import { DEFAULT_COLOR } from "@/constants/project-constants"; -import { - CORNER_RADIUS_MAX, - CORNER_RADIUS_MIN, - MAX_FONT_SIZE, - MIN_FONT_SIZE, -} from "@/constants/text-constants"; -import { usePropertyDraft } from "../hooks/use-property-draft"; -import { useKeyframedColorProperty } from "../hooks/use-keyframed-color-property"; -import { useKeyframedNumberProperty } from "../hooks/use-keyframed-number-property"; -import { useElementPlayhead } from "../hooks/use-element-playhead"; -import { KeyframeToggle } from "../components/keyframe-toggle"; -import { isPropertyAtDefault } from "./transform-tab"; -import { resolveColorAtTime, resolveNumberAtTime } from "@/lib/animation"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { - MinusSignIcon, - PlusSignIcon, - TextFontIcon, -} from "@hugeicons/core-free-icons"; -import { OcTextHeightIcon, OcTextWidthIcon } from "@opencut/ui/icons"; -import { DEFAULTS } from "@/lib/timeline/defaults"; -import { cn } from "@/utils/ui"; - -export function TextTab({ - element, - trackId, -}: { - element: TextElement; - trackId: string; -}) { - return ( -
- - - - -
- ); -} - -function ContentSection({ - element, - trackId, -}: { - element: TextElement; - trackId: string; -}) { - const editor = useEditor(); - - const content = usePropertyDraft({ - displayValue: element.content, - parse: (input) => input, - onPreview: (value) => - editor.timeline.previewElements({ - updates: [ - { trackId, elementId: element.id, updates: { content: value } }, - ], - }), - onCommit: () => editor.timeline.commitPreview(), - }); - - return ( -
- - Content - - -