text:
![](media/banner.png)

A curated list of awesome themes, plugins and more for [Obsidian](https://obsidian.md/).

---

# Table of contents

- [What is Obsidian?](#what-is-obsidian)
- [Resources](#resources)
- [Plugins](#plugins)
- [External Tools](#external-tools)
- [Templates](#templates)
- [CSS Snippets](#css-snippets)
- [Themes](#themes)
- [For Developers](#for-developers)

# What is Obsidian?

- [Official Obsidian Website](https://obsidian.md)
- [Reviews, Pros and Cons of Obsidian at Slant.co](https://www.slant.co/options/37045/~obsidian-md-review)
- [Obsidian at Markdown Guide](https://www.markdownguide.org/tools/obsidian/)
- [Obsidian Beginner Guide at SitePoint](https://www.sitepoint.com/obsidian-beginner-guide/)
- [Obsidian Official Roadmap](https://trello.com/b/Psqfqp7I/obsidian-roadmap)

# Resources

- [Obsidian Help](https://help.obsidian.md/): a open-source Obsidian's official help vault.
- [Obsidian Roundup](https://obsidianroundup.org/): a weekly roundup of Obsidian.md tips, news & resources via Eleanor Konik.
- [Obsidian Garden](https://obsidian.garden): a open-source guide to creating your own 🌳 Digital Garden with Obsidian.
- [Official Getting Started Guide](https://dynalist.io/d/CQ4V16tFhIJIfy-rgDK7r1Bp) (may be a little outdated)
- [🎬 Obsidian Office Hours](https://www.youtube.com/channel/UCJKoNPnBdCSloJ2wlKnV2Fw)
- [🎬 Obsidian Community Talks](https://www.youtube.com/channel/UCxNSTq2kmupdR6LD400FpvA)
- [🎬 Linking Your Thinking (Nick Milo)](https://www.youtube.com/channel/UC85D7ERwhke7wVqskV_DZUA)
- [🎬 Bryan Jenks](https://www.youtube.com/channel/UCfhSB16X9MXhzSFe_H7XbHg)
- [🎬 Productivity Guru](https://www.youtube.com/playlist?list=PLvmlaxyxtsWz428CtIo_Ia8Bhbo8Uvq2f)
- [🎬 Santi Younger](https://www.youtube.com/playlist?list=PL_7j1BHf-xmj3Jr2h3lI6SXlvIaIjcmmo)
- [🎬 Effective Remote Work](https://www.youtube.com/playlist?list=PLrI2d6gSaO9BCd8HjgkSY1yd50nyfxYpN)
- [🇨🇳 AllinBon](https://www.zhihu.com/people/kio-mis/pins): a Chinese alternative to Obsidian Roundup

# Plugins

| Name | Description | Credits |
| :--: | :---------- | ------: |
| [Obsidian Day Planner](https://github.com/lynchjames/obsidian-day-planner) | Plugin for day planning and managing pomodoro timers from a task list | [lynchjames](https://github.com/lynchjames) |
| [Flashcards](https://github.com/reuseman/flashcards-obsidian) | Simple and intuitive integration for Anki | [reuseman](https://github.com/reuseman) |
| [Shell commands](https://github.com/Taitava/obsidian-shellcommands) | You can predefine system commands that you want to run frequently, and assign hotkeys for them. For example open external applications. Automatic execution is also supported. | [Jarkko Linnanvirta](https://github.com/Taitava) |

# External Tools

- [Converters](#converters)
- [Browser extensions](#browser-extensions)
- [Publishing](#publishing)
- [Other](#other)

## Converters

| Name | Description | Credits |
| :--: | :---------- | ------: |
| [Notion to Obsidian converter](https://github.com/connertennery/Notion-to-Obsidian-Converter) | Simple script to convert exported Notion notes to Obsidian. | [Conner Tennery](https://github.com/connertennery) |
| [Yarle - Yet Another Rope Ladder from Evernote](https://github.com/akosbalasko/yarle) | Node.js app to convert exported Evernote notes to Markdown (Obsidian) | [Akos Balasko](https://github.com/akosbalasko) |
| [keep-it-markdown](https://github.com/djsudduth/keep-it-markdown) | Convert Google Keep notes dynamically to markdown for Obsidian and Notion using the unofficial Keep API. | [Don Sudduth](https://github.com/djsudduth) |
| [gooseberry](https://github.com/out-of-cheese-error/gooseberry) | A command line utility to generate a knowledge base from Hypothesis annotations. | [out-of-cheese-error](https://github.com/out-of-cheese-error) |
| [BibleGateway to Obsidian](https://github.com/selfire1/BibleGateway-to-Obsidian) | Fetch the text from Bible Gateway and save it as formatted markdown file. Each chapter is saved as one file and navigation between files as well as a book-file is automatically created. All of the chapter files of a book are saved in its numbered folder. | [selfire1](https://github.com/selfire1) |
| [DayOne JSON to MD](https://github.com/ze-kel/DayOne-JSON-to-MD) | Simple app that converts jsons (packed with media files into zip) exports from Day One to Markdown. | [Kirill Kleymenov](https://github.com/ze-kel) |
| [Annotation tools](https://github.com/collignon/annotation-tools) | connecting hypothesis and obsidian.md | [Nicolas Collignon](https://github.com/collignon) |
| [A thousand notes](https://github.com/davidedc/A-thousand-notes) | Scripts for cleaning, validating and maintaining the markdown notes exported from Evernote. | [Davide Della Casa](https://github.com/davidedc) |
| [PKMigrator](https://github.com/AnweshGangula/PKMigrator) | Tools to migrate between various Personal Knowledge Management Utilities, including Remnote, Obsidian, Roam and Org-roam. | [Anwesh Gangula](https://github.com/AnweshGangula) |
| [Goodsidian](https://github.com/selfire1/goodsidian) | Goodsidian takes updates to your shelves on Goodreads and formats them to a note in Obsidian. | [selfire1](https://github.com/selfire1) |

---

## Browser extensions

| Name | Description | Credits |
| :--: | :---------- | ------: |
| [Markdownload](https://github.com/deathau/markdown-clipper) | A Firefox and Google Chrome extension to clip websites and download them into a readable markdown file. | [deathau](https://github.com/deathau) |
| [Copy Selection as Markdown](https://github.com/0x6b/copy-selection-as-markdown) | Firefox add-on to copy a selection or link as formatted Markdown | [0x6b](https://github.com/0x6b) |
| [Obsidian Clipper](https://github.com/jplattel/obsidian-clipper) | An unofficial Obsidian clipper for Chrome, that allows you to easily clip a selection to a note in Obsidian | [Joost Plattel](https://github.com/jplattel) |
| [Obsidian Bookmark](https://github.com/Liamballin/ObsidianBookmark) | A Chrome extension and nodejs server to allow web clipping to Obsidian. | [Liamballin](https://github.com/Liamballin) |

---

## Publishing

| Name | Description | Credits |
| :--: | :---------- | ------: |
| [mdzk](https://github.com/mdzk-rs/mdzk) | Zettelkasten system and command line static publishing tool. Aims at full support for Obsidian Markdown. | [mdzk-rs](https://github.com/mdzk-rs) |
| [publish](https://github.com/obsidian-userland/publish) | An open-source Obsidian Publish alternative | [obsidian-userland](https://github.com/obsidian-userland)
| [quartz](https://github.com/jackyzha0/quartz) | Host your own second brain and digital garden | [jackyzha0](https://github.com/jackyzha0) |
| [obyde](https://github.com/khalednassar/obyde) | A minimal tool to convert a "standardly" configured Obsidian vault to a Jekyll or Hugo blog. | [Khaled M. Nassar](https://github.com/khalednassar) |
| [writeup-converter](https://github.com/Twigonometry/writeup-converter) | Script for grabbing markdown files and Obsidian attachments from one folder and copying them to another. Also contains a 'website formatter' that uses regex to parse markdown headers and links and reformat them to create Jekyll-friendly links and contents tables. | [Mac Goodwin](https://github.com/Twigonometry) |
| [pelican-obsidian](https://github.com/jonathan-s/pelican-obsidian) | Makes it possible to bridge work in obsidian to pelican seamlessly. | [Jonathan Sundqvist](https://github.com/jonathan-s) |
| [yet-another-free-publish-alternative](https://github.com/Mara-Li/yet-another-free-publish-alternative) | A Jekyll digital garden template, optimized for integration with Obsidian. It aims to enhance discoverability and help you build a personal knowledge base that can scale with time. | [Mara Li](https://github.com/Mara-Li) |
| [Perlite](https://github.com/secure-77/Perlite) | A webbased markdown viewer optimized for Obsidian | [secure-77](https://github.com/secure-77)

---

## Other

| Name | Description | Credits |
| :--: | :---------- | ------: |
| [Obsidian For Business](https://github.com/tallguyjenks/Obsidian-For-Business) | A combination of a template vault with initial structure and some Microsoft Office VBA Macros to facilitate a powerful, extensible, and flexible plain text workflow using Microsoft Office and Obsidian For Business. | [Bryan Jenks](https://github.com/tallguyjenks) |
| [Sourcegraph knowledge bases extension](https://github.com/bobheadxi/sourcegraph-knowledge-bases) | Browse Markdown knowledge bases (e.g. Obsidian vaults or Foam repositories) in Sourcegraph. | [Robert Lin](https://github.com/bobheadxi) |
| [Obweb](https://github.com/chenyukang/obweb/) | Web applcation to view and edit files in an Obsidian vault. Optimized for mobile devices. | [Yukang Chen](https://github.com/chenyukang) |

---

# Templates

## Vault templates

| Name | Description | Credits |
| :--: | :---------- | ------: |
| [Linking Your Thinking Kit](https://forum.obsidian.md/t/lyt-kit-now-downloadable/390) | This starter kit is designed to get you immediately hands-on with ways to manage your digital notes. Experiment. Learn by doing. See what methods work for you. | [Nick Milo](https://forum.obsidian.md/u/nickmilo) |
| [PARA Starter Kit](https://forum.obsidian.md/t/para-starter-kit/223) | It’s a full vault to explore around and some of the methodology for P.A.R.A. and how to get started for yourself. | [cotemaxime](https://forum.obsidian.md/u/cotemaxime/summary) |
| [Obsidian Starter Templates](https://github.com/masonlr/obsidian-starter-templates) | Obsidian Starter Templates gathers examples from the Obsidian community. Its broader aims are to uncover and communicate how others use backlinks and various markdown tricks and why these techniques work. | [masonlr](https://github.com/masonlr) |
| [Obsidian JG Method](https://github.com/joshwingreene/Obsidian-JG-Method) | A starter kit that follows how to use Obsidian to manage goals, tasks, notes, and software development knowledge base. | [joshwingreene](https://github.com/joshwingreene) |
| [SlRvb’s Journaling Setup](https://publish.obsidian.md/slrvb/50+Nebula/30-N+Nebula/N+-+Bullet+Journal+Setup) | This is a deep dive into how SlRvb go about journaling the things that happen in his life. | [SlRvb](https://forum.obsidian.md/u/SlRvb/summary) |
| [SlRvb’s MediaDB Setup](https://publish.obsidian.md/slrvb/50+Nebula/30-N+Nebula/N+-+MediaDB+Vault+Setup) | Setup for a database vault that stores entertainment media. | [SlRvb](https://forum.obsidian.md/u/SlRvb/summary) |

## Note templates

| Name | Description | Credits |
| :--: | :---------- | ------: |
| [Obsidian Templates](https://github.com/chetachiezikeuzor/Obsidian-Templates) | Few templates, such as admonitions, chess plays, clipboard and others | [Chetachi Ezikeuzor](https://github.com/chetachiezikeuzor) |
| [Template Showcase for Templater](https://github.com/SilentVoid13/Templater/discussions/categories/templates-showcase) | Templates for [Templater](https://github.com/SilentVoid13/Templater) made by its community. | Templater Community |

# CSS Snippets

Small tweaks to add to your `vault/.obsidian/snippets` folder.

- [Collapsing Sidebar](#collapsing-sidebar)
- [Bullet Point Relationship Lines](#bullet-point-relationship-lines)
- [Auto-fading UI](#auto-fading-ui)
- [Smaller Scrollbar](#smaller-scrollbar)
- [Subtler Folding Gutter Arrows](#subtler-folding-gutter-arrows)
- [Hyphenation and Justification](#hyphenation-and-justification)
- [Enlarge Image on Hover](#enlarge-image-on-hover)
- [Nicer Checkboxes](#nicer-checkboxes)
- [Stylish Block Quotes](#stylish-block-quotes)
- [Bigger Link Popup Preview](#bigger-link-popup-preview)
- [Custom Icons Differing Files and Folders](#custom-icons-differing-files-and-folders)
- [Custom Icons for Specific Folders](#custom-icons-for-specific-folders)
- [Tag Pills](#tag-pills)
- [Custom Icons for Frontmatter Tags](#custom-icons-for-frontmatter-tags)
- [Outliner For The Outline and File Explorer](#outliner-for-the-outline-and-file-explorer)
- [Better Bullet Points in Edit Mode](#better-bullet-points-in-edit-mode)
- [Image Cards](#image-cards)
- [Media Grid](#media-grid)
- [Custom-folder-files-tree](#Custom-folder-files-tree)

Not enough? There are many other places related to snippets:

- [#custom-css at Obsidian Forum](https://forum.obsidian.md/tag/custom-css/l/top)
- [CSS snippets in Obsidian Help](https://help.obsidian.md/How+to/Add+custom+styles#Use+Themes+and+or+CSS+snippets)
- [Other GitHub repositories](https://github.com/search?q=obsidian-snippets)

## [Collapsing Sidebar](code/css-snippets/collapsing-sidebar.css)

by [@Kmaasrud](https://github.com/kmaasrud)

[📁 collapsing-sidebar.css](code/css-snippets/collapsing-sidebar.css)

---

## [Bullet Point Relationship Lines](code/css-snippets/bullet-point-relationship-lines.css)

by [@deathau](https://github.com/deathau)

Relationship lines that shows hierarchy, like in code editors.

![](media/css-snippets/bullet-point-relationship-lines-1.png)
[📁 bullet-point-relationship-lines.css](code/css-snippets/bullet-point-relationship-lines.css)

---

## [Auto-fading UI](code/css-snippets/autofading-ui.css)

by [@Rumen Dimitrov](https://forum.obsidian.md/u/rsdimitrov)

Fades note's header controls and status bar items when not hovered.

![](media/css-snippets/autofading-ui-1.gif)
[📁 autofading-ui.css](code/css-snippets/autofading-ui.css)

---

## [Smaller Scrollbar](code/css-snippets/smaller-scrollbar.css)

by [@Rumen Dimitrov](https://forum.obsidian.md/u/rsdimitrov)

![](media/css-snippets/smaller-scrollbar-1.png)

[📁 smaller-scrollbar.css](code/css-snippets/smaller-scrollbar.css)

---

## [Subtler Folding Gutter Arrows](code/css-snippets/subtler-folding-gutter-arrows.css)

by [@Rumen Dimitrov](https://forum.obsidian.md/u/rsdimitrov)

Increases the size of the folding gutter arrows and decreases their opacity until hovered.

![](media/css-snippets/subtler-folding-gutter-arrows-1.png)

[📁 subtler-folding-gutter-arrows.css](code/css-snippets/subtler-folding-gutter-arrows.css)

---

## [Hyphenation and Justification](code/css-snippets/hyphenation-and-justification.css)

by [@Boyd](https://forum.obsidian.md/u/boyd/summary)

Justifies the whole text and breaks words into two lines if needed.

![](media/css-snippets/hyphenation-and-justification-1.png)

[📁 hyphenation-and-justification.css](code/css-snippets/hyphenation-and-justification.css)

---

## [Enlarge Image on Hover](code/css-snippets/enlarge-image-on-hover.css)

by [@den](https://forum.obsidian.md/u/den/summary)

![](media/css-snippets/enlarge-image-on-hover-1.gif)

[📁 enlarge-image-on-hover.css](code/css-snippets/enlarge-image-on-hover.css)

---

## [Nicer Checkboxes](code/css-snippets/nicer-checkboxes.css)

by [@kepano](https://forum.obsidian.md/u/kepano/summary) & [@rsdimitrov](https://forum.obsidian.md/u/rsdimitrov)

To toggle a checkbox, place your cursor right after it, press left arrow once and then press backspace.

![](media/css-snippets/nicer-checkboxes-1.gif)

[📁 nicer-checkboxes.css](code/css-snippets/nicer-checkboxes.css)

---

## [Stylish Block Quotes](code/css-snippets/stylish-blockquotes.css)

by [@Thery](https://forum.obsidian.md/u/Thery/summary)

![](media/css-snippets/stylish-blockquotes-1.png)

[📁 stylish-blockquotes.css](code/css-snippets/stylish-blockquotes.css)

---

## [Bigger Link Popup Preview](code/css-snippets/bigger-link-popup-preview.css)

by [@Cannibalox](https://github.com/cannibalox) & [@konhi](https://github.com/konhi)

![](media/css-snippets/bigger-link-popup-preview-1.png)

[📁 bigger-link-popup-preview.css](code/css-snippets/bigger-link-popup-preview.css)

---

## [Custom Icons Differing Files and Folders](code/css-snippets/custom-icons-differing-files-and-folders.css)

by [@deathau](https://github.com/deathau)

Adds custom icons for files and folders. Please read the comments in the `.css` file.

![](media/css-snippets/custom-icons-differing-files-and-folders-1.png)

[📁 custom-icons-differing-files-and-folders.css](code/css-snippets/custom-icons-differing-files-and-folders.css)

[📁 IcoMoon.io Free Version](https://github.com/Keyamoon/IcoMoon-Free/archive/master.zip)

---

## [Custom Icons for Specific Folders](code/css-snippets/custom-icons-for-specific-folders.css)

by [@ScottKillen](https://github.com/ScottKillen)

Adds custom icons to **specific** folders.

![](media/css-snippets/custom-icons-for-specific-folders-1.png)

[📁 custom-icons-for-specific-folders.css](code/css-snippets/custom-icons-for-specific-folders.css)

---

## [Tag Pills](code/css-snippets/tag-pills.css)

by [@uzerper](https://forum.obsidian.md/u/uzerper)

![](media/css-snippets/tag-pills-1.png)

[📁 tag-pills.css](code/css-snippets/tag-pills.css)

---

## [Custom Icons for Frontmatter Tags](code/css-snippets/custom-icons-for-frontmatter-tags.css)

by [@ScottKillen](https://github.com/ScottKillen)

Adds custom icons to **specific** tags in the frontmatter section.
Compatible with [Tag Pills](#tag-pills) and most themes, including [minimal](https://forum.obsidian.md/t/minimal-theme/3659).

![](media/css-snippets/custom-icons-for-frontmatter-tags-1.png)

[📁 custom-icons-for-frontmatter-tags.css](code/css-snippets/custom-icons-for-frontmatter-tags.css)

---

## [Outliner For The Outline and File Explorer](code/css-snippets/outliner-for-the-outline-and-file-explorer.css)

by [@Shamama](https://forum.obsidian.md/u/Shamama), [@wonton](https://forum.obsidian.md/u/wonton/summary) & [@konhi](https://github.com/konhi)

![](media/css-snippets/outliner-for-the-outline-and-file-explorer-1.png)

[📁 outliner-for-the-outline-and-file-explorer.css](code/css-snippets/outliner-for-the-outline-and-file-explorer.css)

---

## [Better Bullet Points in Edit Mode](code/css-snippets/better-bullet-points-in-edit-mode.css)

by [@Piotr](https://forum.obsidian.md/u/Piotr) & [@konhi](https://github.com/konhi)

![](media/css-snippets/better-bullet-points-in-edit-mode-1.gif)

[📁 better-bullet-points-in-edit-mode.css](code/css-snippets/better-bullet-points-in-edit-mode.css)

---

## [Image Cards](code/css-snippets/image-cards.css)

by [@TClark1011](https://github.com/TClark1011)

Give your images a shadow and rounded corners.

![](media/css-snippets/image-cards-1.png)

[📁 image-cards.css](code/css-snippets/image-cards.css)

---

## [Media Grid](code/css-snippets/media-grid.css)

by [@zremboldt](https://github.com/zremboldt)

Add more meaning and visual interest to your documents by easily grouping sets of images, videos, or even audio tracks into grid layouts.

IMPORTANT
For information on requirements and how to use this snippet, see its [original repo](https://github.com/zremboldt/obsidian-media-grid).

![](media/css-snippets/media-grid.png)

[📁 media-grid.css](code/css-snippets/media-grid.css)

---

## [Custom-folder-files-tree](code/css-snippets/custom-folder-files-tree.css)

![preview](media/css-snippets/custom-folder-files-tree.gif)

[📁 custom-folder-files-tree.css](code/css-snippets/custom-folder-files-tree.css)

---

# Themes

Most themes should be available through the `Appearance` => `Community themes` in Obsidian's settings. You can find more info at [Obsidian Help/Add custom styles](https://help.obsidian.md/How+to/Add+custom+styles).

| Repository | Screenshot |
| :-----------: | :-----------: |
| [kognise/obsidian-atom](https://github.com/kognise/obsidian-atom) | ![Atom](https://raw.githubusercontent.com/kognise/obsidian-atom/master/screenshot-hybrid.png "") |
| [cotemaxime/obsidian-amethyst](https://github.com/cotemaxime/obsidian-amethyst) | ![Amethyst](https://raw.githubusercontent.com/cotemaxime/obsidian-amethyst/master/screenshot.png "") |
| [insanum/obsidian_gruvbox](https://github.com/insanum/obsidian_gruvbox) | ![Obsidian gruvbox](https://raw.githubusercontent.com/insanum/obsidian_gruvbox/master/dark.png "") |
| [insanum/obsidian_nord](https://github.com/insanum/obsidian_nord) | ![Obsidian Nord](https://raw.githubusercontent.com/insanum/obsidian_nord/master/screen.png "") |
| [jarodise/Dracula-for-Obsidian.md](https://github.com/jarodise/Dracula-for-Obsidian.md) | ![Dracula for Obsidian](https://raw.githubusercontent.com/jarodise/Dracula-for-Obsidian.md/master/screencap.jpg "") |
| [chad-bennett/warmth-obsidian-theme](https://github.com/chad-bennett/warmth-obsidian-theme) | ![Warmth](https://raw.githubusercontent.com/chad-bennett/warmth-obsidian-theme/master/warmth.jpg "") |
| [dogwaddle/obsidian-gastown-theme.md](https://github.com/dogwaddle/obsidian-gastown-theme.md) | ![Gastown](https://raw.githubusercontent.com/dogwaddle/obsidian-gastown-theme.md/master/ObsidianOne.png "") |
| [deathau/80s-Neon-for-Obsidian.md](https://github.com/deathau/80s-Neon-for-Obsidian.md) | ![80s Neon](https://raw.githubusercontent.com/deathau/80s-Neon-for-Obsidian.md/master/screenshot.jpg "") |
| [deathau/Base2Tone-For-Obsidian.md](https://github.com/deathau/Base2Tone-For-Obsidian.md) | ![Base2Tone](https://raw.githubusercontent.com/deathau/Base2Tone-For-Obsidian.md/master/colours.gif "") |
| [deathau/Notation-for-Obsidian](https://github.com/deathau/Notation-for-Obsidian) | ![Notation](https://raw.githubusercontent.com/deathau/Notation-for-Obsidian/master/screenshot.jpg "") |
| [deathau/obsidian-christmas-theme](https://github.com/deathau/obsidian-christmas-theme) | ![Christmas](https://raw.githubusercontent.com/deathau/obsidian-christmas-theme/master/screenshot.png "") |
| [Slowbad/obsidian-solarized](https://github.com/Slowbad/obsidian-solarized) | ![Solarized](https://raw.githubusercontent.com/Slowbad/obsidian-solarized/master/screenshot.png "") |
| [obsidian-ezs/obsidian-comfort-color-dark](https://github.com/obsidian-ezs/obsidian-comfort-color-dark) | ![Comfort color dark](https://raw.githubusercontent.com/obsidian-ezs/obsidian-comfort-color-dark/master/screencap.png "") |
| [obsidian-ezs/obsidian-ursa](https://github.com/obsidian-ezs/obsidian-ursa) | ![Ursa](https://raw.githubusercontent.com/obsidian-ezs/obsidian-ursa/master/light-theme_full.png "") |
| [nickmilo/Cybertron](https://github.com/nickmilo/Cybertron) | ![Cybertron](https://raw.githubusercontent.com/nickmilo/Cybertron/master/Cybertron.png "") |
| [kartik-karz/moonlight-obsidian](https://github.com/kartik-karz/moonlight-obsidian) | ![Moonlight](https://raw.githubusercontent.com/kartik-karz/moonlight-obsidian/master/moonlight-theme.png "") |
| [seanwcom/Red-Graphite-for-Obsidian](https://github.com/seanwcom/Red-Graphite-for-Obsidian) | ![Red Graphite](https://raw.githubusercontent.com/seanwcom/Red-Graphite-for-Obsidian/master/screenshot01.png "") |
| [kartik-karz/subtlegold-obsidian](https://github.com/kartik-karz/subtlegold-obsidian) | ![Subtlegold](https://raw.githubusercontent.com/kartik-karz/subtlegold-obsidian/master/subtlegold-theme.png "") |
| [sainadh-d/obsidian-boom](https://github.com/sainadh-d/obsidian-boom) | ![Obsidian Boom](https://raw.githubusercontent.com/sainadh-d/obsidian-boom/master/roam-1.png "") |
| [pgalliford/Obsidian-theme-Incredible-Hulk](https://github.com/pgalliford/Obsidian-theme-Incredible-Hulk) | ![Hulk](https://raw.githubusercontent.com/pgalliford/Obsidian-theme-Incredible-Hulk/master/Screen%20Shot.png "") |
| [GuangluWu/obsidian-pisum](https://github.com/GuangluWu/obsidian-pisum) | ![Pisum](https://raw.githubusercontent.com/GuangluWu/obsidian-pisum/master/fullpower.png "") |
| [elliotboyd/obsidian-traffic-lights](https://github.com/elliotboyd/obsidian-traffic-lights) | ![Traffic Lights](https://raw.githubusercontent.com/elliotboyd/obsidian-traffic-lights/master/dark.png "") |
| [mediapathic/obsidian-arsmagna-theme](https://github.com/mediapathic/obsidian-arsmagna-theme) | ![Ars Magna](https://raw.githubusercontent.com/mediapathic/obsidian-arsmagna-theme/master/arsmagna.png "") |
| [cannibalox/Obsdn-dark-rmx](https://github.com/cannibalox/Obsdn-dark-rmx) | ![Obsdn-Dark-Rmx](https://raw.githubusercontent.com/cannibalox/Obsdn-dark-rmx/master/Obsdn-Dark-Rmx.png "") |
| [kepano/obsidian-minimal](https://github.com/kepano/obsidian-minimal) | ![Minimal](https://raw.githubusercontent.com/kepano/obsidian-minimal/master/dark-simple.png "") |
| [rcvd/obsidian_ia](https://github.com/rcvd/obsidian_ia) | ![obsidian_ia](https://raw.githubusercontent.com/rcvd/obsidian_ia/master/light.png "") |
| [bcdavasconcelos/Obsidian-Charcoal](https://github.com/bcdavasconcelos/Obsidian-Charcoal) | ![Charcoal](https://raw.githubusercontent.com/bcdavasconcelos/Obsidian-Charcoal/master/charcoal.png "") |
| [bcdavasconcelos/Obsidian-Panic_Mode](https://github.com/bcdavasconcelos/Obsidian-Panic_Mode) | ![Panic Mode](https://raw.githubusercontent.com/bcdavasconcelos/Obsidian-Panic_Mode/master/panic.png "") |
| [bcdavasconcelos/Obsidian-Graphite](https://github.com/bcdavasconcelos/Obsidian-Graphite) | ![Dark Graphite](https://raw.githubusercontent.com/bcdavasconcelos/Obsidian-Graphite/master/graphite.png "") |
| [bcdavasconcelos/Obsidian-Ayu](https://github.com/bcdavasconcelos/Obsidian-Ayu) | ![Ayu](https://raw.githubusercontent.com/bcdavasconcelos/Obsidian-Ayu/master/ayu2.png "") |
| [bcdavasconcelos/Obsidian-Ayu_Mirage](https://github.com/bcdavasconcelos/Obsidian-Ayu_Mirage) | ![Ayu Mirage](https://raw.githubusercontent.com/bcdavasconcelos/Obsidian-Ayu_Mirage/master/ayu1.png "") |
| [bcdavasconcelos/Obsidian-GDCT](https://github.com/bcdavasconcelos/Obsidian-GDCT) | ![GDCT](https://raw.githubusercontent.com/bcdavasconcelos/Obsidian-GDCT/master/gdct.png "") |
| [bcdavasconcelos/Obsidian-GDCT_Dark](https://github.com/bcdavasconcelos/Obsidian-GDCT_Dark) | ![GDCT Dark](https://raw.githubusercontent.com/bcdavasconcelos/Obsidian-GDCT_Dark/master/gdct.png "") |
| [DubininDmitry/Obuntu-theme-for-Obsidian](https://github.com/DubininDmitry/Obuntu-theme-for-Obsidian) | ![Obuntu](https://raw.githubusercontent.com/DubininDmitry/Obuntu-theme-for-Obsidian/master/screenshot.jpg "") |
| [cannibalox/ono-sendai_obsdn](https://github.com/cannibalox/ono-sendai_obsdn) | ![Ono Sendai](https://raw.githubusercontent.com/cannibalox/ono-sendai_obsdn/master/ono-sendai_obsdn_00.png "") |
| [whyt-byte/Blue-Topaz_Obsidian-css](https://github.com/whyt-byte/Blue-Topaz_Obsidian-css) | ![Blue Topaz](https://raw.githubusercontent.com/whyt-byte/Blue-Topaz_Obsidian-css/master/preview_Blue%20Topaz.png "") |
| [santiyounger/Reverie-Obsidian-Theme](https://github.com/santiyounger/Reverie-Obsidian-Theme) | ![Reverie](https://raw.githubusercontent.com/santiyounger/Reverie-Obsidian-Theme/master/img/reverie-2020-09-14-dark.png "") |
| [ryjjin/Obsidian-Dark-Graphite-Pie-theme](https://github.com/ryjjin/Obsidian-Dark-Graphite-Pie-theme) | ![Dark Graphite Pie](https://raw.githubusercontent.com/ryjjin/Obsidian-Dark-Graphite-Pie-theme/master/Dark%20Graphite%20Pie%20theme%200.9.4.png "") |
| [bennyxguo/Obsidian-Obsidianite](https://github.com/bennyxguo/Obsidian-Obsidianite) | ![Obsidianite](https://raw.githubusercontent.com/bennyxguo/Obsidian-Obsidianite/master/images/demo1.png "") |
| [ishgunacar/gitsidian](https://github.com/ishgunacar/gitsidian) | ![Gitsidian](https://raw.githubusercontent.com/ishgunacar/gitsidian/master/showcase.png "") |
| [jamiebrynes7/clair-de-lune-obsidian-theme](https://github.com/jamiebrynes7/clair-de-lune-obsidian-theme) | ![Clair de Lune](https://raw.githubusercontent.com/jamiebrynes7/clair-de-lune-obsidian-theme/master/assets/screenshot.png "") |
| [manassadasivuni/obsidian-molecule](https://github.com/manassadasivuni/obsidian-molecule) | ![Molecule](https://raw.githubusercontent.com/manassadasivuni/obsidian-molecule/master/images/Screenshot1.png "") |
| [sparklau/comfort-smooth](https://github.com/sparklau/comfort-smooth) | ![Comfort Smooth](https://raw.githubusercontent.com/sparklau/comfort-smooth/master/comfort-smooth.png "") |
| [dxcore35/Suddha-theme](https://github.com/dxcore35/Suddha-theme) | ![Suddha](https://raw.githubusercontent.com/dxcore35/Suddha-theme/master/Images/Preview1.jpg "") |
| [mgmeyers/obsidian-california-coast-theme](https://github.com/mgmeyers/obsidian-california-coast-theme) | ![California Coast](https://raw.githubusercontent.com/mgmeyers/obsidian-california-coast-theme/master/screenshots/04.png "") |
| [radekkozak/discordian](https://github.com/radekkozak/discordian) | ![Discordian](https://raw.githubusercontent.com/radekkozak/discordian/master/media/screenshots/discordian-full-mode.png "") |
| [chad-bennett/al-dente-obsidian-theme](https://github.com/chad-bennett/al-dente-obsidian-theme) | ![Al Dente](https://raw.githubusercontent.com/chad-bennett/al-dente-obsidian-theme/master/aldente-screenshot.png "") |
| [santiyounger/Wasp-Obsidian-Theme](https://github.com/santiyounger/Wasp-Obsidian-Theme) | ![Wasp](https://raw.githubusercontent.com/santiyounger/Wasp-Obsidian-Theme/master/img/wasp-dark.png "") |
| [davidgolding/obsidian-big-sur-aesthetic](https://github.com/davidgolding/obsidian-big-sur-aesthetic) | ![Big Sur Aesthetic](https://raw.githubusercontent.com/davidgolding/obsidian-big-sur-aesthetic/master/screenshot-hybrid.png "") |
| [lukauskas/obsidian-highlighter-theme](https://github.com/lukauskas/obsidian-highlighter-theme) | ![Higlighter](https://raw.githubusercontent.com/lukauskas/obsidian-highlighter-theme/master/screenshots/screenshot-themes-panel.png "") |
| [SlRvb/Obsidian--ITS-Theme](https://github.com/SlRvb/Obsidian--ITS-Theme) | ![ITS Theme](https://raw.githubusercontent.com/SlRvb/Obsidian--ITS-Theme/master/ITS.png "") |
| [Braweria/Spectrum](https://github.com/Braweria/Spectrum) | ![Spectrum](https://raw.githubusercontent.com/Braweria/Spectrum/master/SpectrumPreview.png "") |
| [hipstersmoothie/hipstersmoothie-obsidian-theme](https://github.com/hipstersmoothie/hipstersmoothie-obsidian-theme) | ![Hipstersmoothie](https://raw.githubusercontent.com/hipstersmoothie/hipstersmoothie-obsidian-theme/master/hipstersmoothie-obsidian-theme.png "") |
| [hungsu/vileplume-obsidian](https://github.com/hungsu/vileplume-obsidian) | ![Vileplume](https://raw.githubusercontent.com/hungsu/vileplume-obsidian/master/Vileplume.png "") |
| [auroral-ui/aurora-obsidian-md](https://github.com/auroral-ui/aurora-obsidian-md) | ![Aurora](https://raw.githubusercontent.com/auroral-ui/aurora-obsidian-md/master/screenshots/screenshot-1.png "") |
| [izumin5210/obsidian-iceberg](https://github.com/izumin5210/obsidian-iceberg) | ![Iceberg](https://raw.githubusercontent.com/izumin5210/obsidian-iceberg/master/screenshot.png "") |
| [bencodezen/obsidian-night-owl-theme](https://github.com/bencodezen/obsidian-night-owl-theme) | ![Night Owl](https://raw.githubusercontent.com/bencodezen/obsidian-night-owl-theme/master/obsidian-night-owl-theme.png "") |
| [chetachiezikeuzor/Yin-and-Yang-Theme](https://github.com/chetachiezikeuzor/Yin-and-Yang-Theme) | ![Yin and Yang](https://raw.githubusercontent.com/chetachiezikeuzor/Yin-and-Yang-Theme/master/assets/screenshot.png "") |
| [shaggyfeng/obsidian-Golden-Topaz-theme](https://github.com/shaggyfeng/obsidian-Golden-Topaz-theme) | ![Golden Topaz](https://raw.githubusercontent.com/shaggyfeng/obsidian-Golden-Topaz-theme/master/screenshot.png "") |
| [shaggyfeng/obsidian-Pink-topaz-theme](https://github.com/shaggyfeng/obsidian-Pink-topaz-theme) | ![Pink Topaz](https://raw.githubusercontent.com/shaggyfeng/obsidian-Pink-topaz-theme/master/screenshot.png "") |
| [eleanorkonik/-palatinate](https://github.com/eleanorkonik/-palatinate) | ![Palatinate](https://raw.githubusercontent.com/eleanorkonik/-palatinate/master/palatinate.png "") |
| [sergey900553/obsidian_githublike_theme](https://github.com/sergey900553/obsidian_githublike_theme) | ![Dark Moss](https://raw.githubusercontent.com/sergey900553/obsidian_githublike_theme/master/screenshot.png "") |
| [Wittionary/mammoth-obsidian-theme](https://github.com/Wittionary/mammoth-obsidian-theme) | ![Mammoth](https://raw.githubusercontent.com/Wittionary/mammoth-obsidian-theme/master/screenshots/fullscreen-formatting-examples.png "") |
| [luke-rmaki/rmaki-obsidian](https://github.com/luke-rmaki/rmaki-obsidian) | ![Rmaki](https://raw.githubusercontent.com/luke-rmaki/rmaki-obsidian/master/screenshot.png "") |
| [ArtexJay/Obsidian-CyberGlow](https://github.com/ArtexJay/Obsidian-CyberGlow) | ![Cyber Glow](https://raw.githubusercontent.com/ArtexJay/Obsidian-CyberGlow/master/Screenshot.png "") |
| [johackim/obsidian-darkyan](https://github.com/johackim/obsidian-darkyan) | ![Darkyan](https://raw.githubusercontent.com/johackim/obsidian-darkyan/master/screenshot.png "") |
| [mrglitchbyte/obsidian_everforest](https://github.com/mrglitchbyte/obsidian_everforest) | ![Everforest](https://raw.githubusercontent.com/mrglitchbyte/obsidian_everforest/master/dark_v2.png "") |
| [ZaherAlMajed/Illusion-Theme.md](https://github.com/ZaherAlMajed/Illusion-Theme.md) | ![Illusion](https://raw.githubusercontent.com/ZaherAlMajed/Illusion-Theme.md/master/screenshot.png "") |
| [tylernguyen/obsidian-horizon](https://github.com/tylernguyen/obsidian-horizon) | ![Horizon](https://raw.githubusercontent.com/tylernguyen/obsidian-horizon/master/assets/preview.png "") |
| [RuslanGagushin/Tokyo-Night-Obsidian-Theme](https://github.com/RuslanGagushin/Tokyo-Night-Obsidian-Theme) | ![Tokyo Night](https://raw.githubusercontent.com/RuslanGagushin/Tokyo-Night-Obsidian-Theme/master/screenshot.png "") |
| [nikbrunner/obsidian-deep-work-theme](https://github.com/nikbrunner/obsidian-deep-work-theme) | ![Deep Work](https://raw.githubusercontent.com/nikbrunner/obsidian-deep-work-theme/master/screenshot.png "") |
| [vanadium23/obsidian-blackbird-theme](https://github.com/vanadium23/obsidian-blackbird-theme) | ![Blackbird](https://raw.githubusercontent.com/vanadium23/obsidian-blackbird-theme/master/images/example.png "") |
| [kinmury/GoldenCoffeeTheme](https://github.com/kinmury/GoldenCoffeeTheme) | ![Golden Coffee](https://raw.githubusercontent.com/kinmury/GoldenCoffeeTheme/master/Screenshots/Showcase.png "") |
| [Chrismettal/Obsidian-Behave-dark](https://github.com/Chrismettal/Obsidian-Behave-dark) | ![Behave dark](https://raw.githubusercontent.com/Chrismettal/Obsidian-Behave-dark/master/Screenshot.png "") |
| [SMUsamaShah/Obsidian-Win98-Edition](https://github.com/SMUsamaShah/Obsidian-Win98-Edition) | ![Obsidian Windows 98 Edition](https://raw.githubusercontent.com/SMUsamaShah/Obsidian-Win98-Edition/master/screenshots/main.png "") |
| [dogwaddle/lizardmen-zettelkasten](https://github.com/dogwaddle/lizardmen-zettelkasten) | ![Lizardmen Zettelkasten](https://raw.githubusercontent.com/dogwaddle/lizardmen-zettelkasten/master/screenshot.png "") |
| [firinael/obsidian-cidreira-coast-theme](https://github.com/firinael/obsidian-cidreira-coast-theme) | ![Cidreira Coast](https://raw.githubusercontent.com/firinael/obsidian-cidreira-coast-theme/master/screenshot.png "") |
| [chrisgrieser/shimmering-focus](https://github.com/chrisgrieser/shimmering-focus) | ![Shimmering Focus](https://raw.githubusercontent.com/chrisgrieser/shimmering-focus/master/dual-theme-screenshot.png "") |

# For Developers

## Creating Themes

- [#custom-css at Obsidian Forum](https://forum.obsidian.md/tag/custom-css)
  - [Getting comfortable with Obsidian CSS](https://forum.obsidian.md/t/getting-comfortable-with-obsidian-css/133)
  - [Common Selectors for Custom CSS](https://forum.obsidian.md/t/common-selectors-for-custom-css/1984)
  - [CSS Themes Showcase](https://forum.obsidian.md/t/meta-post-css-themes-showcase/76)
- [🎬 Create a Custom Theme in Obsidian](https://www.youtube.com/watch?v=lyaEnxgow4E)
- [ReggieNotes' about Obsidian CSS Themes](https://publish.obsidian.md/reggienotes/Quickstart+CSS+Guide/010+Obsidian+CSS+Themes)
- [obsidian-style-settings:](https://github.com/mgmeyers/obsidian-style-settings)  allows snippet, theme, and plugin CSS files to define a set of configuration options. It then allows users to see all the tweakable settings in one settings pane.

## Creating Plugins

- [Developers & API at Obsidian Forum](https://forum.obsidian.md/c/developers-api/14)
  - [How to get started with developing a custom plugin](https://forum.obsidian.md/t/how-to-get-started-with-developing-a-custom-plugin/8157)
  - [Using GitHub actions to release plugins](https://forum.obsidian.md/t/using-github-actions-to-release-plugins/7877)
  - [Hot Reload](https://forum.obsidian.md/t/plugin-release-for-developers-hot-reload-the-plugin-s-youre-developing/12185)
  - [Plugins mini FAQ](https://forum.obsidian.md/t/plugins-mini-faq/7737)
  - [List of available icons](https://forum.obsidian.md/t/list-of-available-icons-for-component-seticon/16332/4)
  - [Alpha Plugin List](https://forum.obsidian.md/t/meta-alpha-plugin-list-0-9-7/7736)
- [Obsidian at GitHub](https://github.com/obsidianmd)
  - [Obsidian API](https://github.com/obsidianmd/obsidian-api)
  - [Obsidian Sample Plugin](https://github.com/obsidianmd/obsidian-sample-plugin)
- [obsidian-tools:](https://github.com/obsidian-tools/obsidian-tools) an unofficial collection of tools that helps devs build plugins for obsidian.
- [obsidian-rust-plugin:](https://github.com/trashhalo/obsidian-rust-plugin) boilerplate needed to write obsidian plugins in rust!
- [obsidian-api-docs:](https://github.com/HEmile/obsidian-api-docs/blob/main/docs/00_Home.md) community-provided documentation of the Obsidian API.
- [obsidian-dev-tools:](https://github.com/KjellConnelly/obsidian-dev-tools)  allows for a modified console (useful for debugging on mobile), and viewing all Obsidian icons/strings.
- [obsidian-daily-notes-interface:](https://github.com/liamcain/obsidian-daily-notes-interface) a collection of utility functions for working with dates and daily notes in Obsidian plugins.
- [obsidian-calendar-ui:](https://github.com/liamcain/obsidian-calendar-ui) provides an out-of-the-box calendar view for Obsidian plugins.
- [obsidian-plugin-template](https://github.com/THeK3nger/obsidian-plugin-template): a plugin template customized with a nicer directory structure and some additional tool (like featuring automatic releases with Github's Actions).
- [obsidian-vault-parser](https://github.com/coffee-cup/obsidian-vault-parser): vault parser for the Obsidian note taking app.
- [Liam Cain's Obsidian API FAQ](https://liamca.in/Obsidian/API+FAQ/index)
- [#obsidian-md at GitHub](https://github.com/topics/obsidian-md)
- [Svelte (sometimes used by plugin developers to create components)](https://svelte.dev/)
- [What tools and libraries are used in Obsidian?](https://konhi.me/obsidian-stack.html)


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://forum.obsidian.md/u/uzerper */
/* source: https://forum.obsidian.md/t/meta-post-common-css-hacks/1978/72 */

.tag:not(.token) {
  background-color: var(--text-accent);
  border: none;
  color: white;
  font-size: 11px;
  padding: 1px 8px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  margin: 0px 0px;
  cursor: pointer;
  border-radius: 14px;
}
.tag:not(.token):hover {
  color: white;
  background-color: var(--text-accent-hover);
}
.tag[href^="#obsidian"] {
  background-color: #4d3ca6;
}
.tag[href^="#important"] {
  background-color: red;
}
.tag[href^="#complete"] {
  background-color: green;
}
.tag[href^="#inprogress"] {
  background-color: orange;
}

repo_name: kmaasrud/awesome-obsidian

repo_language: CSS

mime_type: text/plain


text:
/* ====== Custom icons for frontmatter tags ======== */

/* Add tag icon to the right of all frontmatter tags by default */
.frontmatter-section-tags .tag:not(.token)::after {
  content: '🔖';
}

/* Remove the tag icon from specific tags */
.frontmatter-section-tags .tag[href^='#Article']::after,
.frontmatter-section-tags .tag[href^='#DailyNote']::after,
.frontmatter-section-tags .tag[href^='#Definition']::after,
.frontmatter-section-tags .tag[href^='#Note']::after,
.frontmatter-section-tags .tag[href^='#Person']::after,
.frontmatter-section-tags .tag[href^='#Sermon']::after,
.frontmatter-section-tags .tag[href^='#Task']::after {
  content: '';
}

/* Add icon to the left of specific tags */
.frontmatter-section-tags .tag[href^='#Article']::before {
  content: '📰';
}

.frontmatter-section-tags .tag[href^='#DailyNote']::before,
.frontmatter-section-tags .tag[href^='#Note']::before {
  content: '📝';
}

.frontmatter-section-tags .tag[href^='#Definition']::before {
  content: '📖';
}

.frontmatter-section-tags .tag[href^='#Person']::before {
  content: '🧑';
}

.frontmatter-section-tags .tag[href^='#Sermon']::before {
  content: '✝';
}


text:
/* Custom icons for specific folders */

/* https://forum.obsidian.md/t/add-emojis-to-folder-titles-in-explorer-without-renaming-folders/22702 */

div[data-path="_Inbox"] .nav-folder-title-content::before
{
  content: "📥";
}

div[data-path="Attachments"] .nav-folder-title-content::before
{
  content: "📋";
}

div[data-path="Bible Resources"] .nav-folder-title-content::before
{
  content: "⚔";
}

div[data-path="Notes"] .nav-folder-title-content::before
{
  content: "📝";
}

div[data-path="Notes/Daily"] .nav-folder-title-content::before
{
  content: "📆";
}


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://forum.obsidian.md/u/Thery/summary */
/* source: https://forum.obsidian.md/t/meta-post-common-css-hacks/1978/39 */

blockquote:before {
  font: 14px/20px italic Times, serif;
  content: "“";
  font-size: 3em;
  line-height: 0.1em;
  vertical-align: -0.4em;
}
blockquote p {
  display: inline;
}

/* Remove blockquote left margin */
blockquote {
  margin-inline-start: 0;
}


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://github.com/deathau */
/* source: https://forum.obsidian.md/t/meta-post-common-css-hacks/1978/2 */

.cm-hmd-list-indent .cm-tab,
ul ul {
  position: relative;
}
.cm-hmd-list-indent .cm-tab::before,
ul ul::before {
  content: "";
  border-left: 1px solid rgba(0, 122, 255, 0.25);
  position: absolute;
}
.cm-hmd-list-indent .cm-tab::before {
  left: 0;
  top: -5px;
  bottom: -4px;
}
ul ul::before {
  left: -11px;
  top: 0;
  bottom: 0;
}


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://github.com/kmaasrud */
/* source: https://github.com/kmaasrud/awesome-obsidian */

.workspace {
  position: relative;
}

.side-dock-ribbon.mod-left.is-collapsed,
.side-dock-ribbon.mod-right.is-collapsed {
  height: 100%;
}

.side-dock-ribbon.mod-left.is-collapsed:not(:hover),
.side-dock-ribbon.mod-right.is-collapsed:not(:hover) {
  position: absolute;
  opacity: 0;
}

.side-dock-ribbon.mod-left.is-collapsed:not(:hover) {
  left: 0;
}

.side-dock-ribbon.mod-right.is-collapsed:not(:hover) {
  right: 0;
}


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://forum.obsidian.md/u/rsdimitrov */
/* source: https://forum.obsidian.md/t/optimize-obsidian-ui-for-a-more-seamless-experience/155/5 */

/* smaller scrollbar */
.CodeMirror-vscrollbar,
.CodeMirror-hscrollbar,
::-webkit-scrollbar {
  width: 3px;
}


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://github.com/TClark1011 */
/* source: https://github.com/kmaasrud/awesome-obsidian */

img {
  border-radius: 4px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
  background-color: var(
    --background-secondary
  ); /* Background color so PNGs with transparent backgrounds don't look weird */
}


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://forum.obsidian.md/u/kepano/summary, https://forum.obsidian.md/u/rsdimitrov */
/* source: https://forum.obsidian.md/t/meta-post-common-css-hacks/1978/34 */

/* Round checkbxes in preview and editor */
input[type="checkbox"],
.cm-formatting-task {
  -webkit-appearance: none;
  appearance: none;
  border-radius: 50%;
  border: 1px solid var(--text-faint);
  padding: 0;
  vertical-align: middle;
}

.cm-s-obsidian span.cm-formatting-task {
  color: transparent;
  width: 1.25em !important;
  height: 1.25em;
  display: inline-block;
}

input[type="checkbox"]:focus {
  outline: 0;
}
input[type="checkbox"]:checked,
.cm-formatting-task.cm-property {
  background-color: var(--text-accent-hover);
  border: 1px solid var(--text-accent-hover);
  background-position: center;
  background-size: 70%;
  background-repeat: no-repeat;
  background-image: url('data:image/svg+xml; utf8, <svg width="12px" height="10px" viewBox="0 0 12 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g transform="translate(-4.000000, -6.000000)" fill="%23ffffff"><path d="M8.1043257,14.0367999 L4.52468714,10.5420499 C4.32525014,10.3497722 4.32525014,10.0368095 4.52468714,9.8424863 L5.24777413,9.1439454 C5.44721114,8.95166768 5.77142411,8.95166768 5.97086112,9.1439454 L8.46638057,11.5903727 L14.0291389,6.1442083 C14.2285759,5.95193057 14.5527889,5.95193057 14.7522259,6.1442083 L15.4753129,6.84377194 C15.6747499,7.03604967 15.6747499,7.35003511 15.4753129,7.54129009 L8.82741268,14.0367999 C8.62797568,14.2290777 8.3037627,14.2290777 8.1043257,14.0367999"></path></g></g></svg>');
}


text:
/* Custom icons for specific folders or files */

:root {
  --active-file-bg-color: #005C00;
  --active-file-fg-color: #ffffff;
}

/* folder begin */
.nav-folder-title {
  cursor: pointer !important;
}
.nav-file-title-content, .nav-folder-title-content {
  display: flex !important;
  align-items: center;
}
.nav-folder-title-content::before {
  content: '';
  background: url("https://media.macosicons.com/parse/files/macOSicons/af21153d07a2e92bde7b2ad155055489_low_res_1619092574091.png") no-repeat center;
  background-size: 100%;
  width: 20px;
  height: 20px;
  display: inline-block;
  margin-right: 5px;
}
/* root folder */
div[data-path="/"] .nav-folder-title-content::before
{
  content: '';
}
/* 所有的附件目录, 我自定义的附件目录为笔记所在目录的名为 attachments 的子目录, 匹配以 attachments 结尾 */
/* All attachment directories, and my custom `attachments` directory is the subdirectory named `attachments` of the note directory and ends with the matching `attachments` */
div[data-path$="attachments"] .nav-folder-title-content::before
{
  background: url("https://media.macosicons.com/parse/files/macOSicons/325fcaee7fcf0ce428cddec2a53675c1_low_res_Folder___Favourite_Images.png") no-repeat center;
  background-size: 100%;
}
div[data-path="编程"] .nav-folder-title-content::before
{
  background: url("https://media.macosicons.com/parse/files/macOSicons/c84880ed4b36bcaf78df9580aa069a43_low_res_Programming_Folder.png") no-repeat center;
  background-size: 100%;
}
div[data-path="编程/工具"] .nav-folder-title-content::before
{
  background: url("https://media.macosicons.com/parse/files/macOSicons/9d1f67b889d73ed9813c83199e039354_low_res_Developer_Folder.png") no-repeat center;
  background-size: 100%;
}
div[data-path="编程/Docker"] .nav-folder-title-content::before
{
  background: url("https://media.macosicons.com/parse/files/macOSicons/f024d0e956f6663fb48b55ac11673f54_low_res_Docker.png") no-repeat center;
  background-size: 100%;
}
div[data-path="编程/MacOS"] .nav-folder-title-content::before
{
  background: url("https://media.macosicons.com/parse/files/macOSicons/4ad8d9253a2541e49b6d86d5fe0f6829_low_res_Apple.png") no-repeat center;
  background-size: 100%;
}
div[data-path="编程/MySQL"] .nav-folder-title-content::before
{
  background: url("https://media.macosicons.com/parse/files/macOSicons/dfb95bab582395d9e5fa89284bb24e34_MySQL_Workbench.png") no-repeat center;
  background-size: 100%;
}
div[data-path="编程/RabbitMQ"] .nav-folder-title-content::before
{
  background: url("https://media.macosicons.com/parse/files/macOSicons/28c81b354a89b7ebeb599a849f5291a2_low_res_Matrix_Follow_the_White_Rabbit_Folder.png") no-repeat center;
  background-size: 100%;
}
div[data-path="编程/Spring"] .nav-folder-title-content::before
{
  background: url("https://media.macosicons.com/parse/files/macOSicons/d89876a20a9ce438cc7f437a7704a6a2_low_res_Spring_Tool_Suite_4.png") no-repeat center;
  background-size: 100%;
}
div[data-path="编程/Linux"] .nav-folder-title-content::before
{
  background: url("https://media.macosicons.com/parse/files/macOSicons/6604c58f3ffcd4648f6c1bf1a956818b_low_res_Linux.png") no-repeat center;
  background-size: 100%;
}
div[data-path="编程/Jenkins"] .nav-folder-title-content::before
{
  background: url("https://media.macosicons.com/parse/files/macOSicons/c5e3a3b55e2e23b9dc839305fe53dbf1_low_res_Jenkins.png") no-repeat center;
  background-size: 100%;
}
div[data-path="编程/Java"] .nav-folder-title-content::before
{
  background: url("https://media.macosicons.com/parse/files/macOSicons/f4ae1a3f1d2143a2e2ec0b94ea374ace_low_res_Java.png") no-repeat center;
  background-size: 100%;
}
div[data-path="编程/GitLab"] .nav-folder-title-content::before
{
  background: url("https://media.macosicons.com/parse/files/macOSicons/d7033bb681a9f5ae6f3b0479573b0352_low_res_Gitlab.png") no-repeat center;
  background-size: 100%;
}
/* folder end */

/* file begin */
.nav-file-title {
  cursor: pointer !important;
}
.nav-file-title-content::before {
  content: '';
  background: url("https://media.macosicons.com/parse/files/macOSicons/c66c64dea9d84b6dcb21836d5fa451e5_low_res_Google_Docs.png") no-repeat center;
  background-size: 100%;
  width: 20px;
  height: 20px;
  display: inline-block;
  margin-right: 5px;
}
/* All attachment hidden file icon */
.nav-file-title[data-path*="attachments"] .nav-file-title-content::before {
  content: '';
  background: none !important;
  width: 0px;
  height: 0px;
  display: inline-block;
  margin-right: 0px;
}
body:not(.is-grabbing) .nav-file-title.is-active:hover, body:not(.is-grabbing) .nav-folder-title.is-active:hover, .nav-file-title.is-active, .nav-folder-title.is-active {
  background: var(--active-file-bg-color) !important;
  color: var(--active-file-fg-color) !important;
}
/* file end */


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://forum.obsidian.md/u/Shamama & https://forum.obsidian.md/u/wonton/summary & https://github.com/konhi */
/* source: https://forum.obsidian.md/t/meta-post-common-css-hacks/1978/112 */

.outline .tree-item-children {
  margin-left: 20px;
  border-left: 1px solid var(--background-modifier-border);
  border-radius: 4px;
  transition: all 0.5s ease-in-out;
}
.outline .tree-item-children:hover {
  margin:0 !important;
  border-left-color: var(--background-modifier-border);
}
.nav-folder-children .nav-folder-children {
  margin-left: 20px;
  padding-left: 0;
  border-left: 1px solid var(--background-modifier-border);
  border-radius: 4px;
  transition: all 0.5s ease-in-out;
}
.nav-folder-children .nav-folder-children:hover {
  margin:0 !important;
  border-left-color: var(--background-modifier-border);
}


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://github.com/cannibalox & https://github.com/konhi */
/* source: https://forum.obsidian.md/t/meta-post-common-css-hacks/1978/82 */

.popover.hover-popover {
  transform: scale(0.8); /* makes the content smaller */
  max-height: 800px; /* was 300 */
  min-height: 100px;
  width: 500px; /* was 400 */
}

.popover.hover-popover .markdown-embed {
  height: 800px;
}


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://forum.obsidian.md/u/rsdimitrov */
/* source: https://forum.obsidian.md/t/meta-post-common-css-hacks/1978/10 */

/* Make subtler folding gutter arrows */
.CodeMirror-foldgutter-folded:after,
.CodeMirror-foldgutter-open:after {
  opacity: 0.5;
  font-size: 60%;
}

.CodeMirror-foldgutter-folded:hover:after,
.CodeMirror-foldgutter-open:hover:after {
  opacity: 1;
}

.CodeMirror-foldgutter-folded:after {
  content: "\25BA";
}

.CodeMirror-foldgutter-open:after {
  content: "\25BC";
}


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://forum.obsidian.md/u/den/summary */
/* source: https://forum.obsidian.md/t/meta-post-common-css-hacks/1978/29 */

.markdown-preview-view img {
  display: block;
  margin-top: 20pt;
  margin-bottom: 20pt;
  margin-left: auto;
  margin-right: auto;
  width: 50%; /* experiment with values */
  transition: transform 0.25s ease;
}

.markdown-preview-view img:hover {
  -webkit-transform: scale(1.8); /* experiment with values */
  transform: scale(2);
}


text:
/*
IMPORTANT
For information on requirements and how to use this snippet, see its original repo:
https://github.com/zremboldt/obsidian-media-grid

This snippet requires Contextual Typography 2.2.1+
https://github.com/mgmeyers/obsidian-contextual-typography
*/

:root {
    --content-base-width: 45rem;
  }

  /* ----------------------------------------- */
  /* Handle overall content width */
  /* ----------------------------------------- */

  /* Wrapper with padding keeps content from bumping the edges */
  .markdown-preview-view.is-readable-line-width {
    padding-inline: 30px;
  }

  /* Container controlling overall max content width */
  .markdown-preview-view.is-readable-line-width .markdown-preview-section {
    width: 100%;
    max-width: var(--content-max-width, 45rem); /* Second value is fallback in case user isn't using Style settings */
  }

  /* All children of overall container are divs */
  .markdown-preview-view.is-readable-line-width .markdown-preview-section > div {
    margin-inline: auto;
    width: min(var(--content-base-width), 100%);
  }

  /* Specific children of overall container that contain media */
  .markdown-preview-view.is-readable-line-width .markdown-preview-section div[data-is-embed] {
    width: 100%;
  }

  /* ----------------------------------------- */
  /* Make media items display in rows */
  /* ----------------------------------------- */

  /* p tags wrap each row */
  .markdown-preview-view.is-readable-line-width .markdown-preview-section div[data-is-embed] > p {
    display:grid;
    grid-template-columns:repeat(auto-fit, minmax(0, 1fr));
    grid-gap: var(--media-grid-gap, 7px);
    margin: 0;
  }

  /* Remove breaks from the document flow */
  .markdown-preview-view.is-readable-line-width .markdown-preview-section div[data-is-embed] > p br {
    display: none;
  }

  /* Media items sourced from your vault are wrapped in a span – media linked from the web is not wrapped */
  .markdown-preview-view.is-readable-line-width .markdown-preview-section div[data-is-embed] > p > span {
    display: flex;
  }

  /* Apply this to all img/video tags in a row whether they're wrapped in a span or not */
  .markdown-preview-view.is-readable-line-width .markdown-preview-section div[data-is-embed] > p img,
  .markdown-preview-view.is-readable-line-width .markdown-preview-section div[data-is-embed] > p video {
    flex: 1;
    align-self: stretch;
    object-fit: cover;
    border-radius: var(--media-border-radius, 4px);
  }

  /* Create gaps between rows */
  .markdown-preview-view.is-readable-line-width .markdown-preview-section div[data-is-embed] + div[data-is-embed] {
    margin-top: var(--media-grid-gap, 7px);
  }

  /* ----------------------------------------- */
  /* Style settings */
  /* ----------------------------------------- */

  /* @settings
  name: Media Grid Snippet
  id: media-grid-snippet
  settings:
    -
        id: content-max-width
        title: Grid width
        description: Set the max width of your grid layouts.
        type: variable-select
        allowEmpty: false
        default: var(--content-base-width)
        options:
            -
                label: Base width
                value: var(--content-base-width)
            -
                label: Small
                value: calc(var(--content-base-width) + 5%)
            -
                label: Medium
                value: calc(var(--content-base-width) + 10%)
            -
                label: Large
                value: calc(var(--content-base-width) + 20%)
            -
                label: Full width
                value: 100%
    -
        id: media-grid-gap
        title: Grid gap
        description: Set the gap size between items in your grid layouts.
        type: variable-number-slider
        format: px
        default: 7
        min: 0
        max: 80
        step: 1
    -
        id: media-border-radius
        title: Border radius
        description: Set the border radius of your images and videos.
        type: variable-number-slider
        format: px
        default: 4
        min: 0
        max: 40
        step: 1
  */


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://github.com/deathau */
/* source: https://discord.com/channels/686053708261228577/702656734631821413/755293685046050896 */

/* Emoji */
/*.nav-file-title-content::before { content: '🗒 '; }
.nav-folder-title-content::before { content: '📂 '; }*/

/* Flat font */
/* Requires: https://icomoon.io/#icons-icomoon/liga-font */
.nav-folder-children .nav-file-title-content:first-child::before {
  content: "\e924  ";
  font-family: "IcoMoon-Free";
}
.nav-folder-children .nav-folder-title-content::before {
  content: "\e930  ";
  font-family: "IcoMoon-Free";
}


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://forum.obsidian.md/u/Piotr & https://github.com/konhi */
/* source: https://forum.obsidian.md/t/clutter-free-edit-mode/6791/30 */

div:not(.CodeMirror-activeline) > .CodeMirror-line span.cm-formatting-list-ul {
  color: transparent;
  position: relative;
}

div:not(.CodeMirror-activeline)
  > .CodeMirror-line
  span.cm-formatting-list-ul:after {
  content: "•";
  position: absolute;
  top: -6px;
  left: -1px;
  color: var(--text-normal);
  font-family: sans-serif;
  font-size: 20px;
}


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://forum.obsidian.md/u/rsdimitrov */
/* source: https://forum.obsidian.md/t/optimize-obsidian-ui-for-a-more-seamless-experience/155/5 */

/* change opacity to 0 if you want controls to fade completely */
/* auto fades note header controlls */
.view-header:not(:hover) .view-actions {
  opacity: 0.1;
  transition: opacity 0.25s ease-in-out;
}

/* auto fades status bar items */
.status-bar:not(:hover) .status-bar-item {
  opacity: 0.25;
  transition: opacity 0.25s ease-in-out;
}


text:
/* get more snippets at https://github.com/kmaasrud/awesome-obsidian */
/* author: https://forum.obsidian.md/u/boyd/summary */
/* source: https://forum.obsidian.md/t/meta-post-common-css-hacks/1978/25 */

.cm-s-obsidian,
.markdown-preview-view {
  text-align: justify;
  hyphens: auto;
}


text:
# YouTube Music Hotkeys

> Browser extension that adds keyboard control to the web version of Google Podcasts or YouTube Music

## Background

I wanted to control background playback of YouTube Music (named Google Music at the time) without switching tabs.
That is all :-)

## Install

| Firefox                                                                                | Chrome / Chromium                                                                                |
| -------------                                                                                | -------------                                                                                |
| [![Get the add-on](https://ipfs.io/ipfs/QmWNa64XjA78QvK3zG2593bSMizkDXXcubDHjnRDYUivqt)](https://addons.mozilla.org/en-US/firefox/addon/google-music-hotkeys-webext/) | [![](https://ipfs.io/ipfs/QmXeTTMAxJVSZLqNcVzBdsAZKhWUpP7w7QAZ8f3Bnmk3Mj)](https://chrome.google.com/webstore/detail/google-music-hotkeys/fgjkdpncbpnlhbdbmelbhmapblgaamkl) |

## Features

- works in Firefox and Chromium-based browsers
- keyboard-based playback control
    - toggle playback
      > default: Ctrl+Shift+8, visual hint: *
      > windows: Alt+Shift+8
    - previous song
      > default: Ctrl+Shift+9, visual hint: (
      > windows: Alt+Shift+9
    - next song:
      > default: Ctrl+Shift+0, visual hint: )
      > windows: Alt+Shift+0
- **[option to customize default shortcuts](#how-to-customize-shortcuts)**
- works with YouTube Music but also supports Google Podcasts (next/previous jump back/forward within current episode)
- toolbar button
    - regular click toggles playback
    - context-click shows menu with other playback controls
- open and pin YouTube Music if it is not open yet
- try to build a new playlist when one hasn't been loaded yet
- support for autoplay of bookmarks with `&autoplay=true` parameter

## How To Customize Shortcuts

### Firefox

1. Open `about:addons` → Google / YouTube Music Hotkeys_ → _Preferences_
   - ..or just right-click the Browser Action:
     > ![Right-click on Browser Action](https://user-images.githubusercontent.com/157609/39958925-24e00498-560a-11e8-937e-45bc8fbf43eb.png)
3. Customize key bindings to your liking:
   - Supported values are listed [here](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/commands#Shortcut_values). Changes are saved automatically.
     > ![screenshot with old school shortcuts](https://user-images.githubusercontent.com/157609/39966689-5b7e9dce-56b0-11e8-886c-a98c2c3fef0e.png)
   - The shortcut may be specified as one of the following media keys:
     MediaPlayPause, MediaPrevTrack, MediaNextTrack
     > ![screenshot with media key](https://user-images.githubusercontent.com/157609/39966694-6abf5b02-56b0-11e8-9ad4-ec1cde02bc29.png)
   - Keep in mind that some shortcuts can be intercepted by regular desktop apps running on your system.
     For example, on MS Windows Ctrl+Shift+0 is hijacked by system itself – see [issue #3](https://github.com/lidel/google-music-hotkeys/issues/3).
     If something does not work, try a different shortcut.
   - Customization works in _Firefox 60 or later_. If you are using an older version please see [issue #1](https://github.com/lidel/google-music-hotkeys/issues/1) before reporting a new bug.
   - Firefox does not provide API for setting _Global_ shortcuts (that work when browser window is not focused) yet: [Bug 1411795 - Add global keyboard shortcut support to commands API](https://bugzilla.mozilla.org/show_bug.cgi?id=1411795).
4. That is all!

### Chromium

Chromium-based browsers provide global UI for managing shortcuts of all extensions.

1. Open `chrome://extensions/configureCommands`
2. Customize key bindings to your liking
3. You can optionally switch shortcut scope to _Global_:
   > ![peek 2017-11-28 19-44](https://user-images.githubusercontent.com/157609/33337860-a03f29f6-d474-11e7-88b9-748739b20725.gif)
    _Global_ shortcuts should work even when Chrome isn’t ‘in focus’.
    Keep in mind that _Global_ shortcuts can interfere with regular desktop app shortcuts.
4. That is all!

## Development

```
npm ci
npm run build
npm run firefox
```

## License

`Play_music_triangle.png` from https://commons.wikimedia.org/wiki/File:Play_music_triangle.svg

[webextensions-lib-shortcut-customize-ui](https://github.com/piroor/webextensions-lib-shortcut-customize-ui) is [licensed under MIT](https://github.com/piroor/webextensions-lib-shortcut-customize-ui/blob/master/LICENSE)

The add-on itself is released under [CC0](LICENSE): to the extent possible under law, the author has waived all copyright and related or neighboring rights to this work, effectively placing it in the public domain.

repo_name: lidel/google-music-hotkeys

repo_language: JavaScript

mime_type: text/html


text:
'use strict'
/* eslint-env browser, webextensions */

// check if URL includes autoplay token
if (decodeURIComponent(window.location.href).includes('autoplay=true')) {
  console.log('[Google Music Hotkeys] Autoplay token detected')
  const autoplay = setInterval(async () => {
    const playbutton = document.getElementById('player-bar-play-pause')
    // press button only if  there is no playback
    if (playbutton && playbutton.title === 'Play') {
      clearInterval(autoplay)
      await browser.runtime.sendMessage({ command: 'toggle-playback' })
      console.log('[Google Music Hotkeys] Toggled playback')
    }
  }, 1000)
}


text:
/* global browser, ShortcutCustomizeUI */
async function buildShortcutCustomizeUI () {
  const browserInfo = await browser.runtime.getBrowserInfo()
  const runtimeBrowserName = browserInfo ? browserInfo.name : 'unknown'
  const runtimeIsFirefox = !!runtimeBrowserName.match('Firefox')
  const shortcutsSection = document.getElementById('shortcuts')
  if (runtimeIsFirefox) {
    shortcutsSection.classList.add('firefox')
    // Firefox does not provide native UI for customizing shortcuts, just an API
    // so we use a lib that builds UI in userland
    ShortcutCustomizeUI.build().then(list => {
      // remove old UI, if any
      while (shortcutsSection.firstChild) {
        shortcutsSection.removeChild(shortcutsSection.firstChild)
      }
      shortcutsSection.insertAdjacentHTML('afterbegin', '
Customize Bindings
')
      shortcutsSection.appendChild(list)
      shortcutsSection.insertAdjacentHTML('beforeend', '
List of supported shortcut keys names can be found here.
')
    })
  }
}

document.addEventListener('DOMContentLoaded', buildShortcutCustomizeUI)


text:
'use strict'
/* eslint-env browser, webextensions */

const googleMusicPlayerUrl = 'https://play.google.com/music/listen*'
const youtubeMusicPlayerUrl = 'https://music.youtube.com/*'
const gpodcastMusicPlayerUrl = 'https://podcasts.google.com/*'
const togglePlaybackCommand = 'toggle-playback'
const previousSongCommand = 'previous-song'
const nextSongCommand = 'next-song'
const backupSuffix = '-backup'

async function openPlayer () {
  await browser.tabs.create({
    pinned: true,
    url: youtubeMusicPlayerUrl.replace('*', '')
  })
}

function scriptFor (command, scriptThatClicksOn) {
  switch (command) {
    case togglePlaybackCommand:
    case togglePlaybackCommand + backupSuffix:
      return scriptThatClicksOn('play-pause')
    case previousSongCommand:
    case previousSongCommand + backupSuffix:
      return scriptThatClicksOn('rewind')
    case nextSongCommand:
    case nextSongCommand + backupSuffix:
      return scriptThatClicksOn('forward')
  }
}

function googleMusicScriptThatClicksOn (actionName) { // TODO: remove them GM is dead
  const script = function () {
    // Google Music (to be removed)
    // ============================
    const buttons = document.getElementsByTagName('paper-icon-button')
    let disabledMainPlayButton = false
    for (let i = 0; i < buttons.length; i++) {
      if (buttons[i].getAttribute('data-id') === 'kitty') {
        if (buttons[i].style['pointer-events'] === 'none') {
          disabledMainPlayButton = true
        } else {
          buttons[i].click()
        }
        break
      }
    }
    const tryFallbackButtons = function () {
      // try I'm Feeling Lucky button (main page)
      let buttons = document.getElementsByTagName('gpm-ifl-button')
      if (buttons.length > 0) {
        console.log('[Google Music Hotkeys] Playing Feeling Lucky Radio')
        buttons[0].click()
        return
      }
      // try first 'play' button (eg. on album listing page)
      buttons = document.getElementsByTagName('button')
      for (let i = 0; i < buttons.length; i++) {
        if (buttons[i].getAttribute('data-id') === 'play') {
          console.log('[Google Music Hotkeys] Playing first playable item')
          buttons[i].click()
          return
        }
      }
      // TODO: try 'play all' button (eg. on album listing page)
      // try standalone play button (eg. on a radio page)
      const playButton = document.getElementById('playButton')
      if (playButton) {
        console.log('[Google Music Hotkeys] Playing current context')
        playButton.click()
        return
      }
      // try context's shuffle button (eg. on the main library page)
      buttons = document.getElementsByTagName('paper-button')
      for (let i = 0; i < buttons.length; i++) {
        if (buttons[i].getAttribute('data-id') === 'shuffle-my-library') {
          console.log('[Google Music Hotkeys] Playing shuffled library')
          buttons[i].click()
          return
        }
      }
    }
    if (disabledMainPlayButton) {
      // https://github.com/lidel/google-music-hotkeys/issues/2
      console.log('[Google Music Hotkeys] trying to load a new playlist..')
      tryFallbackButtons()
    }
  }
  return '(' + script.toString().replace('kitty', actionName) + ')()'
}

function youTubeMusicScriptThatClicksOn (actionName) {
  const script = function () {
    // TODO: revisit when YouTube Music adds 'feeling lucky' on page without player
    const findButton = (actionName) => {
      const qs = (q) => document.querySelector(q)
      switch (actionName) {
        case 'rewind':
          return qs('.ytmusic-player-bar.previous-button') || qs('.previous-button')
        case 'forward':
          return qs('.ytmusic-player-bar.next-button') || qs('.next-button')
        case 'play-pause':
          return qs('.ytmusic-player-bar.play-pause-button') || qs('.play-pause-button') || document.getElementById('play-button')
      }
    }
    const button = findButton('kitty')
    if (button) {
      button.click()
    } else {
      console.log('[YouTube Music Hotkeys] unable to find the play button, please report a bug at https://github.com/lidel/google-music-hotkeys/issues/new')
    }
  }
  return '(' + script.toString().replace('kitty', actionName) + ')()'
}

function podcastsScriptThatClicksOn (actionName) {
  const script = function (actionName) {
    const getElementFromAria = function (...strings) {
      const selector = strings.map(str => `div[aria-label="${str}"]`).join(', ')
      return document.querySelector(selector)
    }

    try {
      const ariaIdsPerLang = {
        es: { play: 'Reproducir', pause: 'Pausar', rewind: 'Retroceder 10 segundos', forward: 'Avanzar 30 segundos' },
        en: { play: 'Play', pause: 'Pause', rewind: 'Rewind 10 seconds', forward: 'Fast forward 30 seconds' }
      }
      const localizedAria = ariaIdsPerLang[document.querySelector('html').lang]
      switch (actionName) {
        case 'rewind':
          return getElementFromAria(localizedAria.rewind).click()
        case 'forward':
          return getElementFromAria(localizedAria.forward).click()
        case 'play-pause':
          return getElementFromAria(localizedAria.play, localizedAria.pause).click()
      }
    } catch (err) {
      console.debug('[Google Podcasts hotkeys] unable to find button via aria labels, falling back to operating on audio node', err)
      const audioNode = document.querySelector('audio')

      switch (actionName) {
        case 'rewind':
          audioNode.currentTime -= 10
          return
        case 'forward':
          audioNode.currentTime += 30
          return
        case 'play-pause':
          if (audioNode.paused) { return audioNode.play() } else { return audioNode.pause() }
      }
    }
  }
  return '(' + script.toString() + ')("' + actionName + '")'
}

async function executeGoogleMusicCommand (command) {
  console.log('[Google Music Hotkeys] executing command: ', command)
  const gmTabs = await browser.tabs.query({ url: googleMusicPlayerUrl })
  const ymTabs = await browser.tabs.query({ url: youtubeMusicPlayerUrl })
  const gpTabs = await browser.tabs.query({ url: gpodcastMusicPlayerUrl })

  if (gmTabs.length === 0 && ymTabs.length === 0 && gpTabs.length === 0) {
    openPlayer()
    return
  }
  for (const tab of gmTabs) { // TODO: remove them GM is dead
    browser.tabs.executeScript(tab.id, {
      runAt: 'document_start',
      code: scriptFor(command, googleMusicScriptThatClicksOn)
    })
  }
  for (const tab of ymTabs) {
    browser.tabs.executeScript(tab.id, {
      runAt: 'document_start',
      code: scriptFor(command, youTubeMusicScriptThatClicksOn)
    })
  }
  for (const tab of gpTabs) {
    browser.tabs.executeScript(tab.id, {
      runAt: 'document_start',
      code: scriptFor(command, podcastsScriptThatClicksOn)
    })
  }
}

async function onRuntimeMessage (request, sender) {
  console.log('[Google Music Hotkeys] onRuntimeMessage', request)
  await browser.tabs.executeScript(sender.tab.id, {
    runAt: 'document_start',
    code: scriptFor(request.command)
  })
}

// listen for keyboard hotkeys
browser.commands.onCommand.addListener(executeGoogleMusicCommand)

// listen for messages from content script
setTimeout(() => {
  // this needs to be run bit later due to a bug in Chrome with current browser polyfill:
  // Uncaught Error: runtime namespace is null or undefined
  browser.runtime.onMessage.addListener(onRuntimeMessage)
}, 250)

// regular click on browser action toggles playback
browser.browserAction.onClicked.addListener(() => executeGoogleMusicCommand('toggle-playback'))

// context-click on browser action displays more options
browser.contextMenus.create({
  id: 'toggle-playback-menu-item',
  title: 'Toggle Playback',
  contexts: ['browser_action'],
  onclick: () => executeGoogleMusicCommand(togglePlaybackCommand)
})
browser.contextMenus.create({
  id: 'previous-song-menu-item',
  title: 'Previous Song',
  contexts: ['browser_action'],
  onclick: () => executeGoogleMusicCommand(previousSongCommand)
})
browser.contextMenus.create({
  id: 'next-song-menu-item',
  title: 'Next Song',
  contexts: ['browser_action'],
  onclick: () => executeGoogleMusicCommand(nextSongCommand)
})
browser.contextMenus.create({
  id: 'open-preferences-menu-item',
  title: 'Customize Shortcuts',
  contexts: ['browser_action'],
  onclick: () => {
    browser.runtime.openOptionsPage()
      .catch((err) => {
        console.error('runtime.openOptionsPage() failed, opening options page in tab instead.', err)
        // fallback for weird browsers ;-)
        browser.tabs.create({ url: browser.extension.getURL('options.html') })
      })
  }
})


text:
<!DOCTYPE html>

    <meta charset="utf-8">

How To Customize Shortcuts in Chrome

Open chrome://extensions/configureCommands (needs to be copied&pasted manually to location bar)

Customize key bindings to your liking

You can optionally switch shortcut scope to Global:


text:
# Community Participation Guidelines

This repository is governed by Mozilla's code of conduct and etiquette guidelines.
For more details, please read the
[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).

## How to Report
For more information on how to report violations of the Community Participation Guidelines, please read our [How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/) page.

<!--
## Project Specific Etiquette

In some cases, there will be additional project etiquette i.e.: (https://bugzilla.mozilla.org/page.cgi?id=etiquette.html).
Please update for your project.
-->

repo_name: mdn/pwa-examples


text:
# PWA Examples

Examples for progressive web apps.

In this repo, we currently have:

* [CycleTracker](): A basic app for tracking menstrual cycles. The app's HTML includes a form to add a period cycle start and end dates. The JavaScript app functionality sorts the dates and saves thems to local storage. It also displays the dates retrieved from local storage below the form. The app includes a manifest file with three icons, color scheme, etc. The app also includes a service worker to handle asset caching.

* [a2hs](a2hs): An example set up to show how Add to home screen (A2HS) works. [See it live here](https://mdn.github.io/pwa-examples/a2hs/). This includes an icon and [manifest file](a2hs/manifest.webmanifest) for allowing the app to be added to home screen, and a [simple service worker](a2hs/sw.js) for making the site work offline.

* [js13kpwa](js13kpwa): A list of A-Frame entries submitted to the js13kGames 2017 competition, used as an example for the MDN articles about Progressive Web Apps. The js13kPWA have the App Shell structure, works offline with the Service Worker, is installable thanks to the Manifest file and Add to Homescreen feature, and is re-engageable by using Notifications and Push. [See it live here](https://mdn.github.io/pwa-examples/js13kpwa/).


text:
name: "AlloAllo"

on:
  issues:
    types:
      - opened
  pull_request_target:
    branches:
      - main
    types:
      - opened
      - closed

jobs:
  allo-allo:
    uses: mdn/workflows/.github/workflows/allo-allo.yml@main
    with:
      target-repo: "mdn/pwa-examples"
      issue-welcome: >
        It looks like this is your first issue. Welcome! 👋
        One of the project maintainers will be with you as soon as possible. We
        appreciate your patience. To safeguard the health of the project, please
        take a moment to read our [code of conduct](../blob/main/CODE_OF_CONDUCT.md).
      pr-welcome: >
        It looks like this is your first pull request. 🎉
        Thank you for your contribution! One of the project maintainers will triage
        and assign the pull request for review. We appreciate your patience. To
        safeguard the health of the project, please take a moment to read our
        [code of conduct](../blob/main/CODE_OF_CONDUCT.md).
      pr-merged: >
        Congratulations on your first merged pull request. 🎉 Thank you for your contribution!
        Did you know we have a [project board](https://github.com/orgs/mdn/projects/25) with high-impact contribution opportunities?
        We look forward to your next contribution.


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Cycle Tracker PWA</title>

CycleTracker Period tracker PWA progress

      Tutorial:
      CycleTracker: Creating your first PWA

Tutorial demos:

HTML and CSS

Secure connection (no files)

        JavaScript functionality

Manifest file tutorial

Service worker tutorial

Tutorial files:

        HTML and CSS

Secure connection (no files)

        JavaScript functionality

        Manifest file tutorial

        Service worker tutorial


text:
// JS file for cycleTracker app

// -------------
// Variable declarations
// -------------
const newPeriodFormEl = document.getElementsByTagName("form")[0];
const startDateInputEl = document.getElementById("start-date");
const endDateInputEl = document.getElementById("end-date");
const pastPeriodContainer = document.getElementById("past-periods");

// Storage key is an app-wide constant
const STORAGE_KEY = "period-tracker";

// -------------
// Event Handlers
// -------------
newPeriodFormEl.addEventListener("submit", (event) => {
  event.preventDefault();
  const startDate = startDateInputEl.value;
  const endDate = endDateInputEl.value;
  if (checkDatesInvalid(startDate, endDate)) {
    return;
  }
  storeNewPeriod(startDate, endDate);
  renderPastPeriods();
  newPeriodFormEl.reset();
});

// -------------
// Functionality
// -------------

// 1. Form validation
function checkDatesInvalid(startDate, endDate) {
  if (!startDate || !endDate || startDate > endDate) {
    newPeriodFormEl.reset();
    return true;
  }
  return false;
}

// 2. Get, add, sort, and store data
function storeNewPeriod(startDate, endDate) {
  const periods = getAllStoredPeriods();
  periods.push({ startDate, endDate });
  periods.sort((a, b) => {
    return new Date(b.startDate) - new Date(a.startDate);
  });
  window.localStorage.setItem(STORAGE_KEY, JSON.stringify(periods));
}

// 3. Get and parse data
function getAllStoredPeriods() {
  const data = window.localStorage.getItem(STORAGE_KEY);
  const periods = data ? JSON.parse(data) : [];
  return periods;
}

// 4. Display data
function renderPastPeriods() {
  const pastPeriodHeader = document.createElement("h2");
  const pastPeriodList = document.createElement("ul");
  const periods = getAllStoredPeriods();
  if (periods.length === 0) {
    return;
  }
  pastPeriodContainer.innerHTML = "";
  pastPeriodHeader.textContent = "Past periods";
  periods.forEach((period) => {
    const periodEl = document.createElement("li");
    periodEl.textContent = `From ${formatDate(
      period.startDate
    )} to ${formatDate(period.endDate)}`;
    pastPeriodList.appendChild(periodEl);
  });

  pastPeriodContainer.appendChild(pastPeriodHeader);
  pastPeriodContainer.appendChild(pastPeriodList);
}

// 5. format dates for display
function formatDate(dateString) {
  const date = new Date(dateString);
  return date.toLocaleDateString("en-US", { timeZone: "UTC" });
}

// -------------
// Call render on page load
// -------------

renderPastPeriods();


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Cycle Tracker</title>
    <link rel="stylesheet" href="style.css" />
    <link rel="manifest" href="cycletracker.json" />
    <link rel="icon" href="icons/circle.svg" />

Period tracker

    <form id="new-period">
      <fieldset>
        <legend>Enter your period start and end date</legend>

          Start date
          <input type="date" id="start-date" required />

          End date
          <input type="date" id="end-date" required />

      </fieldset>

        <button type="submit">Add Period</button>

    </form>


text:
/* CSS for cycleTracker App */

body {
  margin: 1vh 1vw;
  background-color: #efe;
}
ul,
fieldset,
legend {
  border: 1px solid;
  background-color: #fff;
}
ul {
  padding: 0;
  font-family: monospace;
}
li,
legend {
  list-style-type: none;
  padding: 0.2em 0.5em;
  background-color: #cfc;
}
li:nth-of-type(even) {
  background-color: inherit;
}


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Cycle Tracker</title>
    <link rel="stylesheet" href="style.css" />

Period tracker

    <form id="new-period">
      <fieldset>
        <legend>Enter your period start and end date</legend>

          Start date
          <input type="date" id="start-date" required />

          End date
          <input type="date" id="end-date" required />

      </fieldset>

        <button type="submit">Add Period</button>

    </form>


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Cycle Tracker</title>
    <link rel="stylesheet" href="style.css" />

Period tracker

    <form id="new-period">
      <fieldset>
        <legend>Enter your period start and end date</legend>

          Start date
          <input type="date" id="start-date" required />

          End date
          <input type="date" id="end-date" required />

      </fieldset>

        <button type="submit">Add Period</button>

    </form>

Past periods

From 01/01/2024 to 01/06/2024

From 01/29/2024 to 02/04/2024


text:
body {
  margin: 1vh 1vw;
  background-color: #efe;
}
ul,
fieldset,
legend {
  border: 1px solid;
  background-color: #fff;
}
ul {
  padding: 0;
  font-family: monospace;
}
li,
legend {
  list-style-type: none;
  padding: 0.2em 0.5em;
  background-color: #cfc;
}
li:nth-of-type(even) {
  background-color: inherit;
}


text:
// The version of the cache.
const VERSION = "v1";

// The name of the cache
const CACHE_NAME = `period-tracker-${VERSION}`;

// The static resources that the app needs to function.
const APP_STATIC_RESOURCES = [
  "/",
  "/index.html",
  "/app.js",
  "/styles.css",
  "/icons/wheel.svg",
];

// On install, cache the static resources
self.addEventListener("install", (event) => {
  event.waitUntil(
    (async () => {
      const cache = await caches.open(CACHE_NAME);
      cache.addAll(APP_STATIC_RESOURCES);
    })()
  );
});

// delete old caches on activate
self.addEventListener("activate", (event) => {
  event.waitUntil(
    (async () => {
      const names = await caches.keys();
      await Promise.all(
        names.map((name) => {
          if (name !== CACHE_NAME) {
            return caches.delete(name);
          }
        })
      );
      await clients.claim();
    })()
  );
});

// On fetch, intercept server requests
// and respond with cached responses instead of going to network
self.addEventListener("fetch", (event) => {
  // As a single page app, direct app to always go to cached home page.
  if (event.request.mode === "navigate") {
    event.respondWith(caches.match("/"));
    return;
  }

  // For all other requests, go to the cache first, and then the network.
  event.respondWith(
    (async () => {
      const cache = await caches.open(CACHE_NAME);
      const cachedResponse = await cache.match(event.request);
      if (cachedResponse) {
        // Return the cached response if it's available.
        return cachedResponse;
      } else {
        // If resource isn't in the cache, return a 404.
        return new Response(null, { status: 404 });
      }
    })()
  );
});


text:
// Generating content based on the template
const template = `

#POS. NAME

Author: AUTHOR

Twitter: @TWITTER

Website: WEBSITE

GitHub: GITHUB

More: js13kgames.com/entries/SLUG

`;
let content = '';
for (let i = 0; i < games.length; i++) {
  let entry = template.replace(/POS/g, (i + 1))
    .replace(/SLUG/g, games[i].slug)
    .replace(/NAME/g, games[i].name)
    .replace(/AUTHOR/g, games[i].author)
    .replace(/TWITTER/g, games[i].twitter)
    .replace(/WEBSITE/g, games[i].website)
    .replace(/GITHUB/g, games[i].github);
  entry = entry.replace('', '-');
  content += entry;
}
document.getElementById('content').innerHTML = content;

// Registering Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/pwa-examples/js13kpwa/sw.js');
}

// Requesting permission for Notifications after clicking on the button
const button = document.getElementById('notifications');
button.addEventListener('click', () => {
  Notification.requestPermission().then((result) => {
    if (result === 'granted') {
      randomNotification();
    }
  });
});

// Setting up random Notification
function randomNotification() {
  const randomItem = Math.floor(Math.random() * games.length);
  const notifTitle = games[randomItem].name;
  const notifBody = `Created by ${games[randomItem].author}.`;
  const notifImg = `data/img/${games[randomItem].slug}.jpg`;
  const options = {
    body: notifBody,
    icon: notifImg,
  };
  new Notification(notifTitle, options);
  setTimeout(randomNotification, 30000);
}

// Progressive loading images
const imagesToLoad = document.querySelectorAll('img[data-src]');
const loadImages = (image) => {
  image.setAttribute('src', image.getAttribute('data-src'));
  image.onload = () => {
    image.removeAttribute('data-src');
  };
};
if ('IntersectionObserver' in window) {
  const observer = new IntersectionObserver((items) => {
    items.forEach((item) => {
      if (item.isIntersecting) {
        loadImages(item.target);
        observer.unobserve(item.target);
      }
    });
  });
  imagesToLoad.forEach((img) => {
    observer.observe(img);
  });
} else {
  imagesToLoad.forEach((img) => {
    loadImages(img);
  });
}


text:
<!DOCTYPE html>

	<meta charset="utf-8">
	<title>js13kGames A-Frame entries</title>
	<meta name="description" content="A list of A-Frame entries submitted to the js13kGames 2017 competition, used as an example for the MDN articles about Progressive Web Apps.">
	<meta name="author" content="end3r">
	<meta name="theme-color" content="#B12A34">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<meta property="og:image" content="icons/icon-512.png">
	<link rel="shortcut icon" href="favicon.ico">
	<link rel="stylesheet" href="style.css">
	<link rel="manifest" href="js13kpwa.webmanifest">

js13kGames A-Frame entries

List of games submitted to the A-Frame category in the js13kGames 2017 competition. You can fork js13kPWA on GitHub to check its source code.

	<button id="notifications">Request dummy notifications</button>

© js13kGames 2012-2018, created and maintained by Andrzej Mazur from Enclave Games.


text:
@font-face {
	font-family: 'Graduate';
	font-style: normal;
	font-weight: 400;
	src: local('Graduate'),
		local('Graduate-Regular'),
		url(fonts/graduate.eot) format('embedded-opentype'),
		url(fonts/graduate.ttf) format('truetype'),
		url(fonts/graduate.woff) format('woff');
}
* {
	margin: 0;
	padding: 0;
}
html {
	font-size: 62.5%;
}
body {
	background: #efefef;
	font: normal 1.6em Helvetica, Arial, sans-serif;
	line-height: 1.6;
	color: #333;
	position: relative;
}
h1, h3 {
	font-family: "Graduate", "Times New Roman", Verdana;
	font-weight: normal;
	margin-top: 0;
	letter-spacing: -0.05em;
}
h1 {
	font-size: 2em;
	line-height: 1.2em;
	padding: 0.5em 0.3em 0.2em 0.3em;
	text-align: center;
	color: #6c6b6b;
}
h1:before {
	content: " • ";
}
h1:after {
	content: " • ";
}
h3 {
	font-size: 1.5em;
	line-height: 1em;
	padding-top: 0.2em;
	margin-bottom: 0.8em;
}
a {
	text-decoration: none;
	color: #b12a34;
}
header {
	background: #2d2d2d url(img/bg.png) repeat;
	border-top: 0.2em solid #a52730;
	border-bottom: 0.2em solid #fff;
}
header p {
	width: 90%;
	margin: 1em auto;
	position: relative;
}
header img {
	display: block;
	width: 50%;
	min-width: 150px;
	max-width: 295px;
}
.description {
	text-align: center;
	padding: 1em;
}
button {
	display: block;
	margin: 0 auto 2em;
}
main {
	width: 100%;
	background: #fafafa;
}
main a:hover {
	text-decoration: none;
	border-bottom: 1px solid #b12a34;
}
main p {
	padding-bottom: 1.5em;
}
main ul {
	padding: 0.2em;
}
main li {
	line-height: 1.4em;
	margin-left: 1em;
}
main img {
	float: right;
	width: 30%;
	min-width: 80px;
	max-width: 160px;
	padding-bottom: 1em;
}
article {
	border-top: 0.1em solid #acabab;
	padding: 1em;
}
article img[data-src] {
	filter: blur(0.2em);
}
article img {
	filter: blur(0em);
	transition: filter 0.5s;
}
footer {
	background: #2d2d2d url(img/bg.png) repeat;
	border-top: 0.2em solid #fff;
	border-bottom: 0.2em solid #a52730;
}
footer p {
	width: 90%;
	margin: 0 auto;
	color: #e2e2e2;
	font-size: 0.7em;
	padding: 2em 0;
	position: relative;
	text-align: center;
	line-height: 1.2;
}
footer a {
	color: #fff;
	font-weight: bold;
}


text:
self.importScripts('data/games.js');

// Files to cache
const cacheName = 'js13kPWA-v1';
const appShellFiles = [
  '/pwa-examples/js13kpwa/',
  '/pwa-examples/js13kpwa/index.html',
  '/pwa-examples/js13kpwa/app.js',
  '/pwa-examples/js13kpwa/style.css',
  '/pwa-examples/js13kpwa/fonts/graduate.eot',
  '/pwa-examples/js13kpwa/fonts/graduate.ttf',
  '/pwa-examples/js13kpwa/fonts/graduate.woff',
  '/pwa-examples/js13kpwa/favicon.ico',
  '/pwa-examples/js13kpwa/img/js13kgames.png',
  '/pwa-examples/js13kpwa/img/bg.png',
  '/pwa-examples/js13kpwa/icons/icon-32.png',
  '/pwa-examples/js13kpwa/icons/icon-64.png',
  '/pwa-examples/js13kpwa/icons/icon-96.png',
  '/pwa-examples/js13kpwa/icons/icon-128.png',
  '/pwa-examples/js13kpwa/icons/icon-168.png',
  '/pwa-examples/js13kpwa/icons/icon-192.png',
  '/pwa-examples/js13kpwa/icons/icon-256.png',
  '/pwa-examples/js13kpwa/icons/icon-512.png',
];
const gamesImages = [];
for (let i = 0; i < games.length; i++) {
  gamesImages.push(`data/img/${games[i].slug}.jpg`);
}
const contentToCache = appShellFiles.concat(gamesImages);

// Installing Service Worker
self.addEventListener('install', (e) => {
  console.log('[Service Worker] Install');
  e.waitUntil((async () => {
    const cache = await caches.open(cacheName);
    console.log('[Service Worker] Caching all: app shell and content');
    await cache.addAll(contentToCache);
  })());
});

// Fetching content using Service Worker
self.addEventListener('fetch', (e) => {
    // Cache http and https only, skip unsupported chrome-extension:// and file://...
    if (!(
       e.request.url.startsWith('http:') || e.request.url.startsWith('https:')
    )) {
        return;
    }

  e.respondWith((async () => {
    const r = await caches.match(e.request);
    console.log(`[Service Worker] Fetching resource: ${e.request.url}`);
    if (r) return r;
    const response = await fetch(e.request);
    const cache = await caches.open(cacheName);
    console.log(`[Service Worker] Caching new resource: ${e.request.url}`);
    cache.put(e.request, response.clone());
    return response;
  })());
});


text:
var games = [
  {
    slug: 'lost-in-cyberspace',
    name: 'Lost in Cyberspace',
    author: 'Zosia and Bartek',
    twitter: 'bartaz',
    website: '',
    github: 'github.com/bartaz/lost-in-cyberspace',
  },
  {
    slug: 'vernissage',
    name: 'Vernissage',
    author: 'Platane',
    twitter: 'platane_',
    website: 'github.com/Platane',
    github: 'github.com/Platane/js13k-2017',
  },
  {
    slug: 'coconutty',
    name: 'Coconutty',
    author: 'Mary Knize',
    twitter: 'captainpainway',
    website: 'maryknize.com',
    github: 'github.com/captainpainway/coconutty',
  },
  {
    slug: 'lost-pacman',
    name: 'Lost Pacman',
    author: 'MarcGuinea',
    twitter: 'MarcGuineaCasas',
    website: 'marcguinea.com',
    github: 'github.com/mguinea/lost-pacman',
  },
  {
    slug: 'polyhedron-runner',
    name: 'Polyhedron Runner',
    author: 'Alex Swan',
    twitter: 'BoldBigflank',
    website: 'bold-it.com',
    github: 'github.com/BoldBigflank/js13k-polyhedron',
  },
  {
    slug: 'she-is-my-universe',
    name: 'She is my universe',
    author: 'Madmarcel',
    twitter: 'madmarcel',
    website: '',
    github: 'github.com/madmarcel/js13k2017',
  },
  {
    slug: 'spacewrecked',
    name: 'Spacewrecked',
    author: 'Sorskoot',
    twitter: 'Sorskoot',
    website: 'timmykokke.com',
    github: 'github.com/sorskoot/js13kgames_2017_Lost',
  },
  {
    slug: 'shifted-dimensions',
    name: 'Shifted Dimensions',
    author: 'Nylki',
    twitter: 'nylk',
    website: 'github.com/nylki',
    github: 'github.com/nylki/shifted-dimensions',
  },
  {
    slug: 'wandering-moon',
    name: 'Wandering Moon',
    author: 'Jack Greenberg',
    twitter: 'thprgrmmrjck',
    website: '',
    github: 'github.com/theProgrammerJack/js13k2017',
  },
  {
    slug: 'lost-in-guam',
    name: 'Lost in Guam',
    author: 'Kenneth Banico',
    twitter: 'kjdesigns671',
    website: '',
    github: 'github.com/kbanico/lost-in-guam-vr-game',
  },
  {
    slug: 'balloon-problems',
    name: 'Balloon Problems',
    author: 'Fasility',
    twitter: 'Fasility_VR',
    website: 'fasility.com',
    github: 'github.com/flowerio/balloon-problems',
  },
  {
    slug: 'lost-in-my-mind',
    name: 'Lost in my mind',
    author: 'Lasagne Games',
    twitter: 'Lazyeels',
    website: '',
    github: 'github.com/lazyeels/js13kb',
  },
  {
    slug: 'lost-in-the-forest-dungeon',
    name: 'Lost In The Forest Dungeon',
    author: 'Luke',
    twitter: 'cannl',
    website: 'lc-apps.co.uk',
    github: 'github.com/lcapps-luke/js13k-lost',
  },
  {
    slug: 'galacticdiamond',
    name: 'GalacticDiamond',
    author: 'Mitruska',
    twitter: 'mitruska_',
    website: '',
    github: 'github.com/mitruch/GalacticDiamond-JS13KGames-2017',
  },
  {
    slug: 'cat-meow',
    name: 'Cat Meow',
    author: 'Lislis',
    twitter: '',
    website: '',
    github: 'github.com/lislis/cat-meow',
  },
  {
    slug: 'metamorphosis',
    name: 'Metamorphosis',
    author: 'Steff and Tanyuan',
    twitter: '',
    website: '',
    github: 'github.com/tanyuan/metamorphosis',
  },
  {
    slug: 'a-snake',
    name: 'A-Snake',
    author: 'Nick Frazier',
    twitter: 'nrf',
    website: 'nickfrazier.com',
    github: 'github.com/fraziern/vrsnake',
  },
  {
    slug: 'wherewhat',
    name: 'Where? What?',
    author: '..Katu..',
    twitter: '',
    website: '',
    github: 'github.com/katubrd/LostVR',
  },
  {
    slug: 'dont-let-your-dreams-be-memes',
    name: 'Don\'t let your dreams be memes',
    author: 'Mark Vasilkov',
    twitter: 'mvasilkov',
    website: 'mvasilkov.ovh',
    github: 'github.com/mvasilkov/aframe13k',
  },
  {
    slug: 'fly-south',
    name: 'Fly South',
    author: 'Christian Paul (jaller94)',
    twitter: '',
    website: 'chrpaul.de',
    github: 'github.com/jaller94/fly-south',
  },
  {
    slug: 'prisonri0t',
    name: 'PrisonRi0t',
    author: 'Sondor',
    twitter: '',
    website: '',
    github: 'github.com/gabboraron/prison',
  },
  {
    slug: 'debriss',
    name: 'DebrISS',
    author: 'Kovolmany',
    twitter: '',
    website: '',
    github: 'github.com/gabboraron/iss',
  },
  {
    slug: 'vr-racing',
    name: 'VR Racing',
    author: 'Vedansh Bhartia and Kartikey Pandey',
    twitter: '',
    website: '',
    github: 'github.com/vedanshbhartia/vr_racing',
  },
  {
    slug: 'a-box-invaders',
    name: 'A-box Invaders',
    author: 'Felipe Do E. Santo',
    twitter: 'felipez3r0',
    website: 'hardcodigo.com.br',
    github: 'github.com/felipez3r0/a-box-invaders',
  },
  {
    slug: 'world-lost',
    name: 'World Lost',
    author: 'Ms. K. Bhuvana Meenakshi',
    twitter: 'bhuvanakotees1',
    website: 'bhuvanameenakshik.wixsite.com/bhuvanameenakshi',
    github: 'github.com/bhuvanameenakshi/World_Lost',
  },
  {
    slug: 'give-space',
    name: 'Give Space',
    author: 'Ram',
    twitter: 'ram_gurumukhi',
    website: 'gurumukhi.wordpress.com',
    github: 'github.com/gurumukhi/13kGiveSpaceGame',
  },
  {
    slug: 'lost-in-metaverse',
    name: 'Lost in Metaverse',
    author: 'Karan Ganesan',
    twitter: 'karanganesan',
    website: 'linkedin.com/in/karanganesan',
    github: 'github.com/karanganesan/Lost_in_Metaverse',
  },
  {
    slug: 'emma-3d',
    name: 'Emma-3D',
    author: 'Prateek Roushan',
    twitter: '',
    website: '',
    github: 'github.com/coderprateek/Emma-3D',
  },
];


text:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>A2HS demo</title>
    <link href="style.css" rel="stylesheet">
    <script src="index.js" defer></script>
    <link rel="manifest" href="manifest.webmanifest">
  </head>
  <body>
    <img src="images/fox1.jpg" alt="a fox picture">
    <button class="add-button">Add to home screen</button>
  </body>
</html>


text:
const images = ['fox1', 'fox2', 'fox3', 'fox4'];
const imgElem = document.querySelector('img');

function randomValueFromArray(array) {
  const randomNo = Math.floor(Math.random() * array.length);
  return array[randomNo];
}

setInterval(() => {
  const randomChoice = randomValueFromArray(images);
  imgElem.src = `images/${randomChoice}.jpg`;
}, 2000);

// Register service worker to control making site work offline

if ('serviceWorker' in navigator) {
  navigator.serviceWorker
    .register('/pwa-examples/a2hs/sw.js')
    .then(() => { console.log('Service Worker Registered'); });
}

// Code to handle install prompt on desktop

let deferredPrompt;
const addBtn = document.querySelector('.add-button');

window.addEventListener('beforeinstallprompt', (e) => {
  // Prevent Chrome 67 and earlier from automatically showing the prompt
  e.preventDefault();
  // Stash the event so it can be triggered later.
  deferredPrompt = e;
  // Update UI to notify the user they can add to home screen
  addBtn.style.display = 'block';

  addBtn.addEventListener('click', () => {
    // hide our user interface that shows our A2HS button
    addBtn.style.display = 'none';
    // Show the prompt
    deferredPrompt.prompt();
    // Wait for the user to respond to the prompt
    deferredPrompt.userChoice.then((choiceResult) => {
      if (choiceResult.outcome === 'accepted') {
        console.log('User accepted the A2HS prompt');
      } else {
        console.log('User dismissed the A2HS prompt');
      }
      deferredPrompt = null;
    });
  });
});


text:
html {
  height: 100%;
  background: black;
}

body {
  height: inherit;
  margin: 0;
}

img {
  display: block;
  width: 100%;
}

.add-button {
  position: absolute;
  top: 1px;
  left: 1px;
  display: none;
}

@media (orientation: landscape) {
  img {
    height: 100%;
    object-fit: cover;
  }
}