├── static ├── .gitkeep ├── CNAME └── media │ ├── logo.png │ ├── designed.svg │ └── designed-dark.svg ├── themes └── .gitkeep ├── content ├── _index.md ├── community │ ├── _index.md │ ├── rules.md │ └── ai.md ├── resources.md ├── about.md ├── crates.toml └── resources.toml ├── .gitignore ├── templates ├── crates │ ├── list.html │ └── single.html ├── articles │ ├── index.html │ └── page.html ├── topics │ ├── list.html │ └── single.html ├── contributors │ ├── list.html │ └── single.html ├── page.html ├── resources.html ├── macros.html ├── index.html ├── rss.xml └── layout.html ├── sass ├── _colors.scss └── main.scss ├── .github └── workflows │ └── zola.yml ├── config.toml └── README.md /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/CNAME: -------------------------------------------------------------------------------- 1 | rust.audio -------------------------------------------------------------------------------- /content/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | +++ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | public 2 | .DS_Store -------------------------------------------------------------------------------- /content/community/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | +++ 3 | -------------------------------------------------------------------------------- /templates/crates/list.html: -------------------------------------------------------------------------------- 1 | {% extends "topics/list.html" %} -------------------------------------------------------------------------------- /content/resources.md: -------------------------------------------------------------------------------- 1 | +++ 2 | template = "resources.html" 3 | +++ 4 | -------------------------------------------------------------------------------- /static/media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustAudio/website/HEAD/static/media/logo.png -------------------------------------------------------------------------------- /templates/articles/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import "macros.html" as m %} 3 | 4 | {% block content %} 5 | {% endblock content %} -------------------------------------------------------------------------------- /templates/topics/list.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import "macros.html" as m %} 3 | 4 | {% block content %} 5 | {% endblock content %} -------------------------------------------------------------------------------- /templates/contributors/list.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import "macros.html" as m %} 3 | 4 | {% block content %} 5 | {% endblock content %} -------------------------------------------------------------------------------- /templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import "macros.html" as m %} 3 | {% block content %} 4 |
5 | {{ page.content | safe }} 6 |
7 | {% endblock content %} 8 | -------------------------------------------------------------------------------- /content/about.md: -------------------------------------------------------------------------------- 1 | +++ 2 | +++ 3 | 4 | This website is a collection of resources dedicated to the development of audio applications in the Rust programming language. These topics vary from general DSP, to synthesis, to plugins and associated crates. 5 | -------------------------------------------------------------------------------- /sass/_colors.scss: -------------------------------------------------------------------------------- 1 | $border: #e1d8dd; 2 | $background: #ffffff; 3 | $background-low: #f3f3f3; 4 | $primary: #09184f; 5 | $accent: #0529ab; 6 | $contrast: darken($primary, 10); 7 | $contrast-low: lighten(desaturate($primary, 70), 20); 8 | $shadow: 0px 0px 3px 0px #1110121a; -------------------------------------------------------------------------------- /templates/topics/single.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import "macros.html" as m %} 3 | 4 | {% block content %} 5 |
6 |

{{ term.name }}

7 |
8 | {{ m::show_taxonomy_pages(term=term) }} 9 |
10 |
11 | {% endblock content %} -------------------------------------------------------------------------------- /.github/workflows/zola.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | jobs: 7 | build_and_deploy: 8 | runs-on: ubuntu-latest 9 | if: github.ref == 'refs/heads/main' 10 | steps: 11 | - name: "Checkout" 12 | uses: actions/checkout@main 13 | - name: "Build and deploy" 14 | uses: shalzz/zola-deploy-action@master 15 | env: 16 | PAGES_BRANCH: gh-pages 17 | BUILD_DIR: . 18 | TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /templates/resources.html: -------------------------------------------------------------------------------- 1 | {% extends "page.html" %} 2 | {% import "macros.html" as m %} 3 | 4 | {% block content %} 5 |
6 | 7 | {% set resources = load_data(path="content/resources.toml") %} 8 | {% for group, resources in resources.resources | group_by(attribute="category") %} 9 |

{{group | capitalize}}

10 | 17 | {% endfor %} 18 | {% endblock content %} 19 | -------------------------------------------------------------------------------- /templates/contributors/single.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import "macros.html" as m %} 3 | 4 | {% block content %} 5 |
6 | {# get associated contributors page #} 7 | {% set contributor = get_page(path="contributors/" ~ term.name ~ ".md") %} 8 |

{{ contributor.title }}

9 | {{ contributor.content | safe }} 10 | 11 | {% if contributor.extra.github %} 12 | GitHub 13 | {% endif %} 14 | 15 | {# display pages #} 16 |
17 | {{ m::show_taxonomy_pages(term=term) }} 18 |
19 |
20 | {% endblock content %} -------------------------------------------------------------------------------- /templates/macros.html: -------------------------------------------------------------------------------- 1 | {# An easy way to keep consistent links that work with the fixed header #} 2 | {% macro hash_link(name) %} 3 |
4 | {% endmacro %} 5 | 6 | {# Tachyons can be somewhat verbose, so a Button element isn't a bad choice. #} 7 | {% macro button(href="#", title="Button", icon=false, color="blue") %} 8 | 10 | 11 | {{ title }} 12 | 13 | {% if icon %} 14 | 15 | {{ icon }} 16 | 17 | {% endif %} 18 | 19 | {% endmacro %} 20 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # The URL the site will be built for 2 | base_url = "https://rust.audio" 3 | 4 | # Whether to automatically compile all Sass files in the sass directory 5 | compile_sass = true 6 | generate_feeds = true 7 | 8 | # Whether to do syntax highlighting 9 | # Theme can be customised by setting the `highlight_theme` variable to a theme supported by Gutenberg 10 | # 11 | # Whether to build a search index to be used later on by a JavaScript library 12 | build_search_index = true 13 | 14 | title = "Rust Audio" 15 | 16 | taxonomies = [ 17 | ] 18 | 19 | [markdown] 20 | highlight_code = true 21 | highlight_theme = "ir-white" 22 | 23 | [extra] 24 | logo = "/media/logo.png" 25 | discourse_url = "https://rust-audio.discourse.group/" 26 | repos_url = "https://api.github.com/orgs/RustAudio/repos" 27 | discord_server_id = "590254806208217089" 28 | discord_invite = "https://discord.gg/8qW6q2k" 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust Audio 2 | 3 | This repository contains the code powering https://rust.audio. This website contains information on audio development with the Rust programming language. 4 | 5 | ## Developing & contributing 6 | 7 | Contributions are welcome. This repository uses the [Zola static site generator](https://github.com/getzola/zola). Please check [here](https://www.getzola.org/documentation/getting-started/overview/) for basic Zola documentation. 8 | 9 | ### Conventions 10 | 11 | When possible, try to content conventions from other items on the site. Most important things should be documented in the `toml` or `md` file. Don't worry too much about getting everything right! 12 | 13 | ### Site Layout 14 | 15 | - About rust.audio is in `content/about.md` 16 | - Formats describing e.g. `VST` vs `CLAP` are in `content/formats.toml`. 17 | - Audio resource links are in `resources.toml`. 18 | 19 | -------------------------------------------------------------------------------- /templates/articles/page.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import "macros.html" as m %} 3 | 4 | {% block content %} 5 |
6 |

{{ page.title }}

7 | {{ page.content | safe }} 8 |
9 | 10 | {% if config.extra.discourse_url %} 11 |
12 |
13 |
14 | 27 | {% endif %} 28 | 29 | {% endblock content %} -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import "macros.html" as m %} 3 | 4 | {% block content %} 5 | 6 | {# About #} 7 |
8 |

Rust Audio

9 | {# Get about page content from the `about.md` file in content #} 10 | {% set about = get_page(path="about.md") %} 11 | {{ about.content | safe }} 12 |
13 | 14 |
15 |

Community

16 |
17 |

Join Rust Audio on Discord to keep up with audio development within the Rust ecosystem, get help with 18 | DSP, collaborate on projects, and everything in-between. 19 |

20 |
21 | 22 | 24 | 25 |
26 | 27 | {% endblock content %} 28 | -------------------------------------------------------------------------------- /templates/rss.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ config.title }} 4 | {{ config.base_url }} 5 | {{ config.description }} 6 | Gutenberg 7 | {{ config.default_language }} 8 | 9 | {{ last_build_date | date(format="%a, %d %b %Y %H:%M:%S %z") }} 10 | {% set articles = get_section(path="articles/_index.md") %} 11 | {% for page in articles.pages %} 12 | 13 | {{ page.title }} 14 | {{ page.date | date(format="%a, %d %b %Y %H:%M:%S %z") }} 15 | {{ page.permalink }} 16 | {{ page.permalink }} 17 | {% if page.summary %}{{ page.summary }}{% else %}{{ page.content }}{% endif %} 18 | 19 | {% endfor %} 20 | 21 | -------------------------------------------------------------------------------- /templates/crates/single.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% import "macros.html" as m %} 3 | 4 | {% block content %} 5 |
6 | {#% set info = load_data(url="https://crates.io/api/v1/crates/" ~ term.name, format="json") %#} 7 | {% set info = load_data(url="https://crates.io/api/v1/crates/winapi", format="json") %} 8 |

{{ term.name }}

9 | {# assign a shorter variable to info.crate for sanity #} 10 | {% set c = info.crate %} 11 |
12 | {% if c.description %} 13 |

{{c.description}}

14 | {% endif %} 15 | 16 | {% if c.updated_at %} 17 |

Updated at {{c.updated_at | date(format="%Y-%m-%d %H:%M%Z")}}

18 | {% endif %} 19 | 20 | {% if c.documentation %} 21 |

22 | 23 | Documentation 24 | 25 |

26 | {% endif %} 27 |
28 | 29 |
30 | {{ m::show_taxonomy_pages(term=term) }} 31 |
32 |
33 | {% endblock content %} -------------------------------------------------------------------------------- /content/community/rules.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "CoC & Rules" 3 | +++ 4 | 5 | # Community rules 6 | 7 | ## Expected Behavior 8 | 9 | - Exercise consideration and respect in your speech and actions. 10 | - Attempt collaboration before conflict. 11 | - Refrain from demeaning, discriminatory, or harassing behavior and speech. 12 | 13 | ## Unacceptable Behavior 14 | 15 | - Unacceptable behaviors include: intimidating, harassing, abusive, discriminatory, derogatory or demeaning speech or actions by any participant in our community. 16 | - Harassment includes: harmful or prejudicial verbal or written comments related to gender, sexual orientation, race, religion, disability; inappropriate use of nudity and/or sexual images; inappropriate depictions of violence; deliberate intimidation or stalking; and unwelcome sexual attention. 17 | 18 | ## Consequences of Unacceptable Behavior 19 | 20 | - Unacceptable behavior from any community member will not be tolerated. Anyone asked to stop unacceptable behavior is expected to comply immediately. 21 | - If a community member engages in unacceptable behavior, the moderators may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning. 22 | 23 | More detail on expected behavior in the Rust Audio community can be found in the Berlin Code of Conduct: [https://berlincodeofconduct.org/](https://berlincodeofconduct.org/) [(Mirror)](https://web.archive.org/web/20251004120400/https://berlincodeofconduct.org/en) 24 | 25 | We appreciate your efforts to keep this a safe and welcoming community, and again, welcome! 26 | -------------------------------------------------------------------------------- /content/community/ai.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "AI policy" 3 | +++ 4 | 5 | # AI policy 6 | 7 | ## Code contributions 8 | 9 | Generative AI is a burden to maintainers due to the advent of low-quality pull requests that this technology enables, as well as the copyright issues it poses. To protect our contributors and community members, pull-requests that are suspected to be generated by AI will be closed, and their authors may be barred from further contribution. What is and isn't AI generated is notoriously difficult to strictly define, so moderators will approach these situations on a case-by-case basis. 10 | 11 | As a general guideline, here are some examples of **acceptable AI use:** 12 | 13 | 1. Generating personal-use documentation to understand a repository. Ensure you have permission from the repository owner. 14 | 2. Translation services for communication. We recognize these are valuable to members who do not speak English as a native language. 15 | 3. Using AI services to check your own written code, to ask questions about your own written code, or to help spot errors in your own written code. 16 | 17 | **Unacceptable AI use includes (non-exhaustive):** 18 | 19 | 1. AI generation of entire function blocks 20 | 2. "Vibe coding" entire pull requests 21 | 3. Using AI to generate comments or pull request descriptions (except for translation). 22 | 23 | ## Discord 24 | 25 | Our AI policy extends to our Discord community. The Rust Audio community features a channel to showcase and share your audio-related projects in Rust. We ask that all posters respect the following rules. Posts that break these rules will be removed promptly. 26 | 27 | 1. AI/"Vibe coded" projects are **not allowed**. 28 | 2. Using AI generated imagery or art in a post is **not allowed**, even if it is placeholder. 29 | 3. Generated AI music (generating a song based on models of other musicians), is **not allowed** anywhere in the Rust Audio community. 30 | -------------------------------------------------------------------------------- /content/crates.toml: -------------------------------------------------------------------------------- 1 | [[crates]] 2 | key = "fundsp" 3 | source = "crates" 4 | tags = ["DSP"] 5 | 6 | [[crates]] 7 | key = "cpal" 8 | source = "crates" 9 | tags = ["devices"] 10 | 11 | [[crates]] 12 | key = "coremidi" 13 | source = "crates" 14 | tags = ["MIDI"] 15 | 16 | [[crates]] 17 | key = "wrl/baseplug" 18 | source = "github" 19 | tags = ["frameworks","VST2"] 20 | 21 | [[crates]] 22 | key = "RustAudio/baseview" 23 | source = "github" 24 | tags = ["windowing","GUI"] 25 | 26 | [[crates]] 27 | key = "robbert-vdh/nih-plug" 28 | source = "github" 29 | tags = ["frameworks","VST3","CLAP"] 30 | 31 | [[crates]] 32 | key = "glowcoil/clap-sys" 33 | source = "github" 34 | tags = ["bindings","CLAP"] 35 | 36 | [[crates]] 37 | key = "RustyDAW/rusty-daw-io" 38 | source = "github" 39 | tags = ["devices"] 40 | 41 | [[crates]] 42 | key = "RustyDAW/creek" 43 | source = "github" 44 | tags = ["streaming"] 45 | 46 | [[crates]] 47 | key = "MeadowlarkDAW/Meadowlark" 48 | source = "github" 49 | tags = ["DAWs"] 50 | 51 | [[crates]] 52 | key = "WeirdConstructor/HexoSynth" 53 | source = "github" 54 | tags = ["synthesizer", "plugin"] 55 | 56 | [[crates]] 57 | key = "vizia/vizia" 58 | source = "github" 59 | tags = ["GUI", "frameworks"] 60 | 61 | [[crates]] 62 | key = "chaosprint/glicol" 63 | source = "github" 64 | tags = ["languages", "DSP"] 65 | 66 | [[crates]] 67 | key = "rodio" 68 | source = "crates" 69 | tags = ["playback"] 70 | 71 | [[crates]] 72 | key = "kira" 73 | source = "crates" 74 | tags = ["games", "playback"] 75 | 76 | [[crates]] 77 | key = "awedio" 78 | source = "crates" 79 | tags = ["playback", "embedded"] 80 | 81 | [[crates]] 82 | key = "rubato" 83 | source = "crates" 84 | tags = ["resampling", "DSP"] 85 | 86 | [[crates]] 87 | key = "realfft" 88 | source = "crates" 89 | tags = ["FFT", "DSP"] 90 | 91 | [[crates]] 92 | key = "jack" 93 | source = "crates" 94 | tags = ["devices"] 95 | 96 | [[crates]] 97 | key = "alisomay/libpd-rs" 98 | source = "github" 99 | tags = ["languages", "DSP"] 100 | 101 | [[crates]] 102 | key = "rustfft" 103 | source = "crates" 104 | tags = ["FFT", "DSP"] 105 | 106 | [[crates]] 107 | key = "hound" 108 | source = "crates" 109 | tags = ["streaming", "frameworks"] 110 | 111 | -------------------------------------------------------------------------------- /content/resources.toml: -------------------------------------------------------------------------------- 1 | # Resources format 2 | # 3 | # ``` 4 | # [[resources]] 5 | # 6 | # A descriptive but short name for the resource 7 | # name = "My resource name" 8 | # 9 | # # Link directly to the resource named 10 | # url = "http://www.example.com/dsp.html" 11 | # 12 | # # Avoid creating new categories unless completely necessary 13 | # category = "general" 14 | # ``` 15 | 16 | [[resources]] 17 | name = "The Scientist and Engineer's Guide to Digital Signal Processing" 18 | url = "http://www.dspguide.com/" 19 | category = "general" 20 | 21 | [[resources]] 22 | name = "MIT OpenCourseWare: 6.003 Signals and Systems 1" 23 | url = "https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-003-signals-and-systems-fall-2011/" 24 | category = "general" 25 | 26 | [[resources]] 27 | name = "MIT OpenCourseWare: 6.007 Signals and Systems 2" 28 | url = "https://ocw.mit.edu/resources/res-6-007-signals-and-systems-spring-2011/" 29 | category = "general" 30 | 31 | [[resources]] 32 | name = "Audio doghouse" 33 | url = "https://www.objc.io/issues/24-audio/audio-dog-house/" 34 | category = "general" 35 | 36 | [[resources]] 37 | name = "Alias-Free Digital Synthesis of Classic Analog Waveforms (whitepaper)" 38 | url = "https://ccrma.stanford.edu/~stilti/papers/blit.pdf" 39 | category = "bandlimited synthesis" 40 | 41 | [[resources]] 42 | name = "Stefan Stenzel - The amazing usefulness of band limited impulse trains" 43 | url = "https://www.youtube.com/watch?v=lpM4Tawq-XU" 44 | category = "bandlimited synthesis" 45 | 46 | [[resources]] 47 | name = "Interval Tree" 48 | url = "https://en.wikipedia.org/wiki/Interval_tree" 49 | category = "structures and algorithms" 50 | 51 | [[resources]] 52 | name = "Finger Tree" 53 | url = "https://en.wikipedia.org/wiki/Finger_tree" 54 | category = "structures and algorithms" 55 | 56 | [[resources]] 57 | name = "Radix Tree" 58 | url = "https://en.wikipedia.org/wiki/Radix_tree" 59 | category = "structures and algorithms" 60 | 61 | [[resources]] 62 | name = "Persistent Vector" 63 | url = "https://hypirion.com/musings/understanding-persistent-vector-pt-1) (Clojure)" 64 | category = "structures and algorithms" 65 | 66 | [[resources]] 67 | name = "Immutable data structures" 68 | url = "https://www.youtube.com/watch?v=ZsryQp0UAC8) (C++)" 69 | category = "structures and algorithms" 70 | 71 | [[resources]] 72 | name = "Xiph.org digital show-and-tell: Exploring sample rate and bit depth" 73 | url = "https://xiph.org/video/vid2.shtml" 74 | category = "misc" 75 | -------------------------------------------------------------------------------- /templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ config.title }} 8 | 9 | 13 | 17 | {# In case we need some specific script or something #} {% block 18 | extra_head %} {% endblock extra_head %} 19 | 20 | 21 | 22 | 54 | 55 | 56 | {% block content %}{% endblock content %} {% block extra_body %}{% 57 | endblock extra_body %} {% block footer %}{% endblock footer %} 58 | 59 | 60 | -------------------------------------------------------------------------------- /static/media/designed.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/media/designed-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sass/main.scss: -------------------------------------------------------------------------------- 1 | @import "colors"; 2 | 3 | .animated-border { 4 | height: 4px; 5 | background: linear-gradient( 6 | 90deg, 7 | #562d6a 0%, 8 | #ab3d6a 25%, 9 | #562d6a 50%, 10 | #ab3d6a 75%, 11 | #562d6a 100% 12 | ); 13 | background-size: 200% 200%; 14 | animation: BorderAnimation 2s linear infinite; 15 | opacity: 0.3; 16 | } 17 | 18 | @keyframes BorderAnimation { 19 | 0% { 20 | background-position: 0% 0%; 21 | } 22 | 23 | 100% { 24 | background-position: 100% 0%; 25 | } 26 | } 27 | 28 | #slideout-menu { 29 | touch-action: none; 30 | display: fixed; 31 | bottom: 100000px; 32 | } 33 | 34 | nav { 35 | display: flex; 36 | align-items: center; 37 | gap: 1rem; 38 | img { 39 | height: 40px; 40 | } 41 | padding: 1rem; 42 | h3 { 43 | margin: 0; 44 | } 45 | a { 46 | display: flex; 47 | align-items: center; 48 | gap: 1rem; 49 | } 50 | } 51 | 52 | p { 53 | margin-block-start: 0; 54 | } 55 | 56 | body { 57 | font-size: 18px; 58 | line-height: 1.5; 59 | font-family: Arial, Helvetica, sans-serif; 60 | background: $background; 61 | margin: 0; 62 | margin-bottom: 8em; 63 | a { 64 | text-decoration: none; 65 | } 66 | } 67 | 68 | .button { 69 | background: $primary; 70 | font-weight: 700; 71 | padding: 0.5em 1em; 72 | border-radius: 1000px; 73 | color: $background; 74 | display: inline-block; 75 | &:hover { 76 | background: lighten($primary, 5); 77 | } 78 | &:active { 79 | background: lighten($primary, 10); 80 | } 81 | } 82 | 83 | .filters { 84 | margin-bottom: 1em; 85 | } 86 | 87 | .tags { 88 | text-transform: capitalize; 89 | display: flex; 90 | flex-wrap: wrap; 91 | gap: 0.3em; 92 | align-items: flex-start; 93 | } 94 | 95 | .tag { 96 | background: $background-low; 97 | font-weight: 600; 98 | padding: 0.1em 0.6em; 99 | border-radius: 1000px; 100 | color: $contrast-low; 101 | display: inline-block; 102 | border: 1px solid $border; 103 | font-size: 0.8rem; 104 | vertical-align: baseline; 105 | i { 106 | height: 0; 107 | svg { 108 | width: 16px; 109 | } 110 | } 111 | cursor: pointer; 112 | &:hover { 113 | background: darken($background-low, 5); 114 | } 115 | &:active { 116 | background: darken($background-low, 10); 117 | } 118 | } 119 | 120 | .tag.toggled { 121 | background: $accent; 122 | border: 1px solid rgba(255, 255, 255, 0.5); 123 | color: $background; 124 | } 125 | 126 | i { 127 | vertical-align: middle; 128 | svg { 129 | width: 24px; 130 | } 131 | } 132 | 133 | .container { 134 | max-width: 800px; 135 | width: 100%; 136 | margin: 0 auto; 137 | padding: 0 1rem; 138 | box-sizing: border-box; 139 | } 140 | 141 | .container-full { 142 | width: 100%; 143 | box-sizing: border-box; 144 | padding: 0 1rem; 145 | } 146 | 147 | .hide-small { 148 | @media screen and (max-width: 700px) { 149 | display: none; 150 | } 151 | } 152 | 153 | hr { 154 | border: 1px solid $border; 155 | margin: 3em 0; 156 | } 157 | 158 | h1, 159 | h2, 160 | h3, 161 | h4, 162 | h5, 163 | h6, 164 | pre { 165 | margin: 0.2em 0; 166 | } 167 | 168 | h1 { 169 | font-size: 3rem; 170 | } 171 | h2 { 172 | font-size: 2.5rem; 173 | } 174 | h3 { 175 | font-size: 2rem; 176 | } 177 | h4 { 178 | font-size: 1.5rem; 179 | } 180 | h5 { 181 | font-size: 1rem; 182 | } 183 | h6 { 184 | font-size: 0.75rem; 185 | } 186 | 187 | article { 188 | h1 { 189 | font-size: 2.5rem; 190 | } 191 | h2 { 192 | font-size: 2rem; 193 | } 194 | h3 { 195 | font-size: 1.5rem; 196 | } 197 | h4 { 198 | font-size: 1.2rem; 199 | } 200 | h5 { 201 | font-size: 0.8rem; 202 | } 203 | h6 { 204 | font-size: 0.6rem; 205 | } 206 | } 207 | 208 | article { 209 | pre { 210 | white-space: pre-wrap; 211 | word-break: break-word; 212 | } 213 | 214 | > blockquote { 215 | &:before { 216 | content: "◈"; 217 | vertical-align: -0.1em; 218 | } 219 | p { 220 | display: inline; 221 | } 222 | } 223 | } 224 | 225 | p, 226 | ul, 227 | li { 228 | a { 229 | text-decoration: none; 230 | font-weight: normal; 231 | } 232 | } 233 | 234 | section { 235 | margin: 4em 0; 236 | } 237 | 238 | .crates { 239 | display: grid; 240 | grid-template-columns: 1fr 1fr 1fr 1fr; 241 | @media screen and (max-width: 960px) { 242 | grid-template-columns: 1fr 1fr 1fr; 243 | } 244 | @media screen and (max-width: 720px) { 245 | grid-template-columns: 1fr 1fr; 246 | } 247 | @media screen and (max-width: 500px) { 248 | grid-template-columns: 1fr; 249 | } 250 | h4 { 251 | margin: 0; 252 | } 253 | .crate { 254 | border-top: 1px $border solid; 255 | display: flex; 256 | flex-direction: column; 257 | padding: 1em; 258 | gap: 0.4em 1em; 259 | .details { 260 | display: flex; 261 | gap: 0.5em; 262 | align-items: center; 263 | } 264 | p, 265 | pre { 266 | margin: 0; 267 | font-size: 0.8em; 268 | line-height: 1.4; 269 | } 270 | } 271 | } 272 | 273 | // Nav menu 274 | 275 | .dropdown__title { 276 | background-color: transparent; 277 | border: none; 278 | font-family: inherit; 279 | } 280 | 281 | nav { 282 | background-color: $background-low; 283 | padding: 0 1rem; 284 | position: sticky; 285 | top: 0; 286 | display: grid; 287 | place-items: center; 288 | 289 | > ul { 290 | grid-auto-flow: column; 291 | 292 | > li { 293 | margin: 0 0.5rem; 294 | 295 | a, 296 | .dropdown__title { 297 | text-decoration: none; 298 | text-align: start; 299 | display: inline-block; 300 | color: $primary; 301 | font-size: 1.125rem; 302 | &:focus { 303 | outline: none; 304 | } 305 | } 306 | 307 | > a, 308 | .dropdown__title { 309 | padding: 1rem 0.5rem; 310 | border-top: 3px solid transparent; 311 | transition: 0.3s border-color; 312 | &:hover, 313 | &:focus { 314 | background-color: rgba($contrast-low, 0.1); 315 | border-top-color: $accent; 316 | } 317 | } 318 | } 319 | } 320 | 321 | ul { 322 | list-style: none; 323 | margin: 0; 324 | padding: 0; 325 | display: grid; 326 | 327 | li { 328 | padding: 0; 329 | } 330 | } 331 | } 332 | 333 | .dropdown { 334 | position: relative; 335 | 336 | .dropdown__title { 337 | display: inline-flex; 338 | align-items: center; 339 | 340 | &:after { 341 | content: ""; 342 | border: 0.35rem solid transparent; 343 | border-top-color: rgba($contrast-low, 0.4); 344 | margin-left: 0.25em; 345 | transform: translateY(0.15em); 346 | } 347 | } 348 | 349 | .dropdown__menu { 350 | position: absolute; 351 | min-width: max-content; 352 | left: 50%; 353 | top: calc(100% - 0.25rem); 354 | transition: 0.3s opacity; 355 | transform: rotateX(-90deg) translateX(-50%); 356 | transform-origin: top center; 357 | visibility: hidden; 358 | opacity: 0.3; 359 | padding: 0.5em 0; 360 | background-color: $background; 361 | border-radius: 3px; 362 | box-shadow: 0 0.15em 0.25em rgba($contrast, 0.08); 363 | 364 | a { 365 | color: $primary; 366 | display: block; 367 | padding: 0.5em 1em; 368 | opacity: 0; 369 | transition: 0.3s background-color; 370 | 371 | &:hover { 372 | background-color: rgba($contrast-low, 0.15); 373 | } 374 | 375 | &:focus { 376 | background-color: rgba($contrast-low, 0.25); 377 | } 378 | } 379 | } 380 | 381 | &:after { 382 | content: ""; 383 | border: 0.5rem solid transparent; 384 | border-bottom-color: #fff; 385 | position: absolute; 386 | top: calc(100% - 1.25rem); 387 | left: 50%; 388 | transform: translateX(-50%); 389 | opacity: 0; 390 | will-change: opacity; 391 | } 392 | 393 | &:hover, 394 | &:focus-within { 395 | .dropdown__title { 396 | border-top-color: $accent; 397 | } 398 | 399 | .dropdown__menu { 400 | opacity: 1; 401 | transform: rotateX(0) translateX(-50%); 402 | visibility: visible; 403 | 404 | a { 405 | opacity: 1; 406 | } 407 | } 408 | 409 | &:after { 410 | opacity: 1; 411 | } 412 | } 413 | } 414 | --------------------------------------------------------------------------------