├── .gitattributes ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app.py ├── deepgram.toml ├── requirements.txt ├── sample.env └── static ├── assets ├── dg_favicon.ico ├── logo-6ad0fabf.png └── preview-starter.png ├── index.html ├── script.js └── style.css /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-detectable=false 2 | *.css linguist-detectable=false 3 | *.js linguist-detectable=false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # environment artifacts 2 | .venv 3 | .env 4 | venv/ 5 | venv.bak/ 6 | .vscode/ 7 | .DS_Store 8 | Pipfile 9 | Pipfile.lock 10 | 11 | # python artifacts 12 | __pycache__ 13 | *.egg-info 14 | dist/ 15 | .mypy_cache/ 16 | .pytest_cache/ 17 | 18 | # build 19 | build/ 20 | poetry.lock -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | devrel@deepgram.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | . 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | . Translations are available at 128 | . 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Want to contribute to this project? We ❤️ it! 4 | 5 | Here are a few types of contributions that we would be interested in hearing about. 6 | 7 | * Bug fixes 8 | * If you find a bug, please first report it using Github Issues. 9 | * Issues that have already been identified as a bug will be labeled `bug`. 10 | * If you'd like to submit a fix for a bug, send a Pull Request from your own fork and mention the Issue number. 11 | * Include a test that isolates the bug and verifies that it was fixed. 12 | * New Features 13 | * If you'd like to accomplish something in the extension that it doesn't already do, describe the problem in a new Github Issue. 14 | * Issues that have been identified as a feature request will be labeled `enhancement`. 15 | * If you'd like to implement the new feature, please wait for feedback from the project maintainers before spending 16 | too much time writing the code. In some cases, `enhancement`s may not align well with the project objectives at 17 | the time. 18 | * Tests, Documentation, Miscellaneous 19 | * If you think the test coverage could be improved, the documentation could be clearer, you've got an alternative 20 | implementation of something that may have more advantages, or any other change we would still be glad hear about 21 | it. 22 | * If its a trivial change, go ahead and send a Pull Request with the changes you have in mind 23 | * If not, open a Github Issue to discuss the idea first. 24 | * Snippets 25 | * To add snippets: 26 | * Add a directory in the `snippets` folder with the name of the language. 27 | * Add one or more files in the language directory with snippets. 28 | * Update the `package.json` to include the snippets you added. 29 | 30 | We also welcome anyone to work on any existing issues with the `good first issue` tag. 31 | 32 | ## Requirements 33 | 34 | For a contribution to be accepted: 35 | 36 | * The test suite must be complete and pass 37 | * Code must follow existing styling conventions 38 | * Commit messages must be descriptive. Related issues should be mentioned by number. 39 | 40 | If the contribution doesn't meet these criteria, a maintainer will discuss it with you on the Issue. You can still 41 | continue to add more commits to the branch you have sent the Pull Request from. 42 | 43 | ## How To 44 | 45 | 1. Fork this repository on GitHub. 46 | 1. Clone/fetch your fork to your local development machine. 47 | 1. Create a new branch (e.g. `issue-12`, `feat.add_foo`, etc) and check it out. 48 | 1. Make your changes and commit them. (Did the tests pass? No linting errors?) 49 | 1. Push your new branch to your fork. (e.g. `git push myname issue-12`) 50 | 1. Open a Pull Request from your new branch to the original fork's `main` branch. 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Deepgram Devs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask Text-to-Speech Starter 2 | 3 | [![Discord](https://dcbadge.vercel.app/api/server/xWRaCDBtW4?style=flat)](https://discord.gg/xWRaCDBtW4) 4 | 5 | This example app demonstrates how to use the Deepgram Text-to-Speech API with Python using Flask, a micro web framework for Python. 6 | 7 | A preview of the app 8 | 9 | ## What is Deepgram? 10 | 11 | [Deepgram’s](https://deepgram.com/) voice AI platform provides APIs for speech-to-text, text-to-speech, and full speech-to-speech voice agents. Over 200,000+ developers use Deepgram to build voice AI products and features. 12 | 13 | ## Sign-up to Deepgram 14 | 15 | Before you start, it's essential to generate a Deepgram API key to use in this project. [Sign-up now for Deepgram and create an API key](https://console.deepgram.com/signup?jump=keys). 16 | 17 | ## Quickstart 18 | 19 | ### Manual 20 | 21 | Follow these steps to get started with this starter application. 22 | 23 | #### Clone the repository 24 | 25 | Go to GitHub and [clone the repository](https://github.com/deepgram-devs/text-to-speech-starter-python). 26 | 27 | #### Install dependencies 28 | 29 | Install the project dependencies. 30 | 31 | ```bash 32 | pip install -r requirements.txt 33 | ``` 34 | 35 | #### Edit the config file 36 | 37 | Copy the code from `sample.env` and create a new file called `.env`. Paste in the code and enter your API key you generated in the [Deepgram console](https://console.deepgram.com/). 38 | 39 | ```js 40 | DEEPGRAM_API_KEY=%api_key% 41 | ``` 42 | 43 | #### Select branch 44 | 45 | The `main` branch demonstrates a basic implementation, where text is sent to the API and an audio file response with synthesized text-to-speech is returned. 46 | 47 | Checkout the other branches to see added functionality: 48 | 49 | - [output streaming](https://github.com/deepgram-starters/text-to-speech-starter-python/tree/output-streaming): Demonstrates how to take advantage of Deepgram's output streaming feature. This example streams the audio response to the client as it is being generated. 50 | 51 | ```bash 52 | git checkout output-streaming 53 | ``` 54 | 55 | #### Run the application 56 | 57 | Once running, you can access the application in your browser at 58 | 59 | ```bash 60 | python app.py 61 | ``` 62 | 63 | ## Issue Reporting 64 | 65 | If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Security Policy](./SECURITY.md) details the procedure for contacting Deepgram. 66 | 67 | ## Getting Help 68 | 69 | We love to hear from you so if you have questions, comments or find a bug in the project, let us know! You can either: 70 | 71 | - [Open an issue in this repository](https://github.com/deepgram-starters/live-node-starter/issues/new) 72 | - [Join the Deepgram Github Discussions Community](https://github.com/orgs/deepgram/discussions) 73 | - [Join the Deepgram Discord Community](https://discord.gg/xWRaCDBtW4) 74 | 75 | ## Author 76 | 77 | [Deepgram](https://deepgram.com) 78 | 79 | ## License 80 | 81 | This project is licensed under the MIT license. See the [LICENSE](./LICENSE) file for more info. 82 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, jsonify 2 | from dotenv import load_dotenv 3 | import os 4 | 5 | from deepgram import DeepgramClient, SpeakOptions 6 | 7 | load_dotenv() 8 | 9 | app = Flask(__name__, static_folder="./static", static_url_path="/static") 10 | 11 | def synthesize_audio(text, model): 12 | try: 13 | deepgram = DeepgramClient(os.environ.get("DEEPGRAM_API_KEY")) 14 | options = SpeakOptions(model=model) 15 | audio_folder = os.path.join(app.static_folder, 'audio') 16 | if not os.path.exists(audio_folder): 17 | os.makedirs(audio_folder) 18 | filename = os.path.join(app.static_folder, audio_folder, "output.mp3") 19 | deepgram.speak.v("1").save(filename, {"text":text}, options) 20 | return filename 21 | except Exception as e: 22 | raise ValueError(f"Speech synthesis failed: {str(e)}") 23 | 24 | @app.route("/", methods=["GET"]) 25 | def serve_index(): 26 | return app.send_static_file("index.html") 27 | 28 | @app.route("/api", methods=["POST"]) 29 | def synthesize_speech(): 30 | try: 31 | data = request.get_json() 32 | text = data.get('text') 33 | model = data.get('model') 34 | 35 | if not text: 36 | raise ValueError("Text is required in the request") 37 | 38 | audio_file = synthesize_audio(text, model) 39 | audio_url = f"{request.url_root}static/audio/{os.path.basename(audio_file)}" 40 | 41 | return jsonify({"success": True, "audioUrl": audio_url}) 42 | 43 | except ValueError as ve: 44 | return jsonify({"success": False, "error": str(ve)}) 45 | 46 | except Exception as e: 47 | return jsonify({"success": False, "error": "Internal server error"}), 500 48 | 49 | if __name__ == "__main__": 50 | app.run(debug=True) 51 | -------------------------------------------------------------------------------- /deepgram.toml: -------------------------------------------------------------------------------- 1 | [meta] 2 | title = "Flask Text-to-Speech Starter" 3 | description = "Get started using Deepgram's Text-to-Speech with this Flask demo app" 4 | author = "Deepgram DX Team (https://developers.deepgram.com)" 5 | useCase = "TTS" 6 | language = "Python" 7 | framework = "Flask" 8 | 9 | [build] 10 | command = "pip install -r requirements.txt" 11 | 12 | [config] 13 | sample = "sample.env" 14 | output = ".env" 15 | 16 | [post-build] 17 | message = "Run `python app.py` to get up and running." 18 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==3.0.2 2 | python-dotenv==1.0.1 3 | deepgram-sdk==3.2.2 -------------------------------------------------------------------------------- /sample.env: -------------------------------------------------------------------------------- 1 | DEEPGRAM_API_KEY=%api_key% -------------------------------------------------------------------------------- /static/assets/dg_favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepgram-starters/flask-text-to-speech/c82bda3ca99f955445affda8e463362420a7e95a/static/assets/dg_favicon.ico -------------------------------------------------------------------------------- /static/assets/logo-6ad0fabf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepgram-starters/flask-text-to-speech/c82bda3ca99f955445affda8e463362420a7e95a/static/assets/logo-6ad0fabf.png -------------------------------------------------------------------------------- /static/assets/preview-starter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepgram-starters/flask-text-to-speech/c82bda3ca99f955445affda8e463362420a7e95a/static/assets/preview-starter.png -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Deepgram Test 6 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 | 19 | 20 |

Text-to-Speech

21 |
22 |
23 |
24 | 25 | 40 | 44 |
45 |
46 | 49 |
50 |
51 |
52 |

Be sure to check out:

53 |
    54 |
  • 55 | The 56 | output-streaming 60 | branch to learn how to play the audio back as soon as the first 61 | byte is returned from Deepgram. 62 |
  • 63 |
  • 64 | The TTS Getting Started guides in the 65 | Deepgram documentation. 70 |
  • 71 |
72 |
73 |
74 |
75 |
76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /static/script.js: -------------------------------------------------------------------------------- 1 | const PLAY_STATES = { 2 | NO_AUDIO: "no_audio", 3 | LOADING: "loading", 4 | PLAYING: "playing", 5 | }; 6 | 7 | let playState = PLAY_STATES.NO_AUDIO; 8 | let audio; 9 | const textArea = document.getElementById("text-input"); 10 | const errorMessage = document.querySelector("#error-message"); 11 | 12 | // Function to update the play button based on the current state 13 | function updatePlayButton() { 14 | const playButton = document.getElementById("play-button"); 15 | const icon = playButton.querySelector(".button-icon"); 16 | 17 | switch (playState) { 18 | case PLAY_STATES.NO_AUDIO: 19 | icon.className = "button-icon fa-solid fa-play"; 20 | break; 21 | case PLAY_STATES.LOADING: 22 | icon.className = "button-icon fa-solid fa-circle-notch "; 23 | break; 24 | case PLAY_STATES.PLAYING: 25 | icon.className = "button-icon fa-solid fa-stop"; 26 | break; 27 | default: 28 | break; 29 | } 30 | } 31 | 32 | // Function to play audio 33 | function playAudio(audioUrl) { 34 | if (audio) { 35 | stopAudio(); 36 | } 37 | currentAudioUrl = audioUrl + "?t=" + new Date().getTime(); // Add cache-busting query parameter 38 | audio = new Audio(currentAudioUrl); 39 | 40 | audio.play(); 41 | 42 | audio.addEventListener("ended", () => { 43 | console.log("Audio finished playing"); 44 | stopAudio(); 45 | }); 46 | } 47 | 48 | // Function to stop audio 49 | function stopAudio() { 50 | if (audio) { 51 | audio.pause(); 52 | audio.currentTime = 0; 53 | playState = PLAY_STATES.NO_AUDIO; 54 | updatePlayButton(); 55 | } 56 | } 57 | 58 | // Function to handle the click event on the play button 59 | function playButtonClick() { 60 | switch (playState) { 61 | case PLAY_STATES.NO_AUDIO: 62 | sendData(); 63 | break; 64 | case PLAY_STATES.PLAYING: 65 | stopAudio(); 66 | playState = PLAY_STATES.NO_AUDIO; 67 | updatePlayButton(); 68 | break; 69 | default: 70 | break; 71 | } 72 | } 73 | 74 | textArea.addEventListener("input", () => { 75 | errorMessage.innerHTML = ""; 76 | }); 77 | 78 | // Function to send data to backend 79 | function sendData() { 80 | const modelSelect = document.getElementById("models"); 81 | const selectedModel = modelSelect.options[modelSelect.selectedIndex].value; 82 | const textInput = document.getElementById("text-input").value; 83 | if (!textInput) { 84 | errorMessage.innerHTML = "ERROR: Please add text!"; 85 | } else { 86 | playState = PLAY_STATES.LOADING; 87 | updatePlayButton(); 88 | 89 | const data = { 90 | model: selectedModel, 91 | text: textInput, 92 | }; 93 | fetch("http://127.0.0.1:5000/api", { 94 | method: "POST", 95 | headers: { 96 | "Content-Type": "application/json", 97 | }, 98 | body: JSON.stringify(data), 99 | }) 100 | .then((response) => { 101 | if (!response.ok) { 102 | throw new Error("Network response was not ok"); 103 | } 104 | return response.json(); 105 | }) 106 | .then((data) => { 107 | console.log("Response received from server:", data); 108 | playAudio(data.audioUrl); 109 | playState = PLAY_STATES.PLAYING; 110 | updatePlayButton(); 111 | }) 112 | .catch((error) => { 113 | console.error("There was a problem with your fetch operation:", error); 114 | playState = PLAY_STATES.NO_AUDIO; 115 | updatePlayButton(); 116 | }); 117 | } 118 | } 119 | 120 | // Event listener for the click event on the play button 121 | document 122 | .getElementById("play-button") 123 | .addEventListener("click", playButtonClick); 124 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | /* Import fonts */ 2 | @import url("https://fonts.googleapis.com/css2?family=Arimo:wght@400;600;700"); 3 | @import url("https://fonts.googleapis.com/css2?family=Inter"); 4 | 5 | /* Global styles */ 6 | html { 7 | background: #101014; 8 | color: #fff; 9 | font-family: Inter, sans-serif; 10 | } 11 | 12 | /* Main content */ 13 | main { 14 | margin-left: auto; 15 | margin-right: auto; 16 | display: flex; 17 | flex-direction: column; 18 | min-height: 100vh; 19 | max-width: 1400px; 20 | gap: 1rem; 21 | padding: 1.5rem; 22 | } 23 | 24 | @media (min-width: 640px) { 25 | main { 26 | padding: 2rem; 27 | } 28 | } 29 | 30 | /* Grid container */ 31 | .grid-container { 32 | display: grid; 33 | gap: 1rem; 34 | } 35 | 36 | @media (min-width: 768px) { 37 | .grid-container { 38 | grid-template-columns: repeat(2, minmax(0, 1fr)); 39 | } 40 | } 41 | 42 | /* Text-to-speech section */ 43 | .tts-section { 44 | display: flex; 45 | flex-direction: column; 46 | gap: 1rem; 47 | } 48 | 49 | /* Button container */ 50 | .button-container { 51 | display: flex; 52 | justify-content: space-between; 53 | } 54 | 55 | /* Button icon */ 56 | .button-icon { 57 | color: #00dda2; 58 | font-size: 1.5rem; 59 | } 60 | 61 | /* Spinner animation */ 62 | .fa-circle-notch { 63 | animation: spin 1s ease-in-out infinite; 64 | -webkit-animation: spin 1s ease-in-out infinite; 65 | } 66 | 67 | @keyframes spin { 68 | to { 69 | -webkit-transform: rotate(360deg); 70 | } 71 | } 72 | @-webkit-keyframes spin { 73 | to { 74 | -webkit-transform: rotate(360deg); 75 | } 76 | } 77 | 78 | /* Button styling */ 79 | button { 80 | background: linear-gradient(#000, #000) padding-box, 81 | linear-gradient(90deg, #201cff -91.5%, #13ef95 80.05%) border-box; 82 | height: 48px; 83 | width: 113px; 84 | border: 1px solid transparent; 85 | border-radius: 4px; 86 | cursor: pointer; 87 | } 88 | 89 | /* Error message */ 90 | #error-message { 91 | color: rgb(255, 74, 93); 92 | font-weight: 800; 93 | } 94 | 95 | /* Dropdown select */ 96 | select { 97 | height: 54px; 98 | border-radius: 4px; 99 | border-width: 1px; 100 | border-color: rgba(44, 44, 51, 1); 101 | background-color: rgba(16, 16, 20, 1); 102 | padding: 1rem; 103 | font-family: Fira Code, monospace; 104 | box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); 105 | color: white; 106 | } 107 | 108 | /* Label */ 109 | label { 110 | font-size: 14px; 111 | } 112 | 113 | /* Textarea */ 114 | textarea { 115 | color: white; 116 | display: flex; 117 | min-height: 256px; 118 | border-radius: 4px; 119 | border-width: 1px; 120 | border-color: rgba(44, 44, 51, 1); 121 | background-color: rgba(16, 16, 20, 1); 122 | padding: 1rem; 123 | font-family: Fira Code, monospace; 124 | box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); 125 | resize: none; 126 | } 127 | 128 | /* Deepgram logo */ 129 | .dg-logo { 130 | padding-top: 0.75rem; 131 | width: 10rem; 132 | } 133 | 134 | /* Title */ 135 | .title { 136 | display: flex; 137 | gap: 0.5rem; 138 | align-items: flex-start; 139 | margin-bottom: 4rem; 140 | } 141 | 142 | @media (min-width: 640px) { 143 | .title { 144 | flex-direction: row; 145 | } 146 | } 147 | 148 | h1 { 149 | color: transparent; 150 | background-clip: text; 151 | background-image: linear-gradient(90deg, #201cff -91.5%, #13ef95 120.05%); 152 | font-size: 1.5rem; 153 | margin: 0; 154 | font-weight: 800; 155 | font-family: Arimo; 156 | } 157 | 158 | @media (min-width: 640px) { 159 | h1 { 160 | font-size: 3.5rem; 161 | } 162 | } 163 | 164 | h2 { 165 | font: 700 2rem / 3.75rem Arimo; 166 | letter-spacing: -0.02em; 167 | } 168 | 169 | /* Information section */ 170 | .information-section { 171 | margin-left: 0; 172 | } 173 | 174 | @media (min-width: 768px) { 175 | .information-section { 176 | margin-left: 120px; 177 | color: rgb(237, 237, 242); 178 | } 179 | } 180 | 181 | /* List */ 182 | ul { 183 | font-family: "Arimo"; 184 | display: flex; 185 | padding-left: 0px; 186 | gap: 16px; 187 | flex-direction: column; 188 | } 189 | 190 | li { 191 | font-size: 20px; 192 | line-height: 1.75rem; 193 | } 194 | 195 | li::marker { 196 | color: #13ef95; 197 | } 198 | 199 | /* Links */ 200 | a { 201 | color: #13ef95; 202 | text-decoration: none; 203 | } 204 | --------------------------------------------------------------------------------