diff --git a/.gitmodules b/.gitmodules
index 502e5248..18c1f495 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,3 @@
[submodule "web/scss/color"]
path = web/scss/color
- url = https://github.com/lbryio/color
-[submodule "web/components"]
- path = web/components
- url = https://github.com/lbryio/components
+ url = https://github.com/lbryio/color
\ No newline at end of file
diff --git a/web/components b/web/components
deleted file mode 160000
index 43666f86..00000000
--- a/web/components
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 43666f869e69d10a5810136755062aba872f615a
diff --git a/web/components/.editorconfig b/web/components/.editorconfig
new file mode 100644
index 00000000..0f178672
--- /dev/null
+++ b/web/components/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/web/components/.gitignore b/web/components/.gitignore
new file mode 100644
index 00000000..fada4a8d
--- /dev/null
+++ b/web/components/.gitignore
@@ -0,0 +1,8 @@
+# Files
+.DS_Store
+.env
+*.log
+.sass-cache
+
+# Directories
+node_modules
diff --git a/web/components/.npmrc b/web/components/.npmrc
new file mode 100644
index 00000000..43c97e71
--- /dev/null
+++ b/web/components/.npmrc
@@ -0,0 +1 @@
+package-lock=false
diff --git a/web/components/LICENSE b/web/components/LICENSE
new file mode 100644
index 00000000..fee060f1
--- /dev/null
+++ b/web/components/LICENSE
@@ -0,0 +1,14 @@
+BSD 3-Clause License
+
+Copyright © LBRY Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/web/components/README.md b/web/components/README.md
new file mode 100644
index 00000000..ac181289
--- /dev/null
+++ b/web/components/README.md
@@ -0,0 +1,64 @@
+# @lbry/components
+> Styling for shared components across LBRY properties
+
+
+
+### Installation
+
+```bash
+$ npm i @lbry/components sass -D
+```
+
+We recommend using this module with [Dart Sass](https://www.npmjs.com/package/sass) for its' focus on speed and low dependency count.
+
+If you are using `@lbry/components`, you can safely remove [@lbry/color](https://github.com/lbryio/color) if you've already included it separately (this module includes it by default).
+
+
+
+### Demo
+
+[https://lbryio.github.io/components](https://lbryio.github.io/components)
+
+
+
+### Usage
+
+Your main Sass file:
+
+```scss
+@charset "utf-8";
+
+@import "@lbry/components/sass/";
+// ...your other Sass imports
+```
+
+In your watch scripts for Sass files, ensure you load the `node_modules` path in order to `import` this module in your project without silly prefixes like `../../../`. What a _mess_.
+
+Example `package.json` scripts section:
+
+```js
+"scripts": {
+ ...,
+ "sass:dev": "sass --load-path=node_modules --watch app/sass:app/dist --style compressed",
+ "sass:prod": "sass --load-path=node_modules --update app/sass:app/dist --style compressed",
+ ...
+}
+```
+
+They are nearly identical, save for `--watch` and `--update`. Please refer to the [Dart Sass README](https://github.com/sass/dart-sass/blob/master/README.md) for assistance on how to integrate it with your project. The [above example](https://github.com/lbryio/lbry.tech/blob/master/package.json) is taken from the `lbry.tech` repo.
+
+
+
+### Note
+
+To use with Webpack, you have to make use of the tilde character when referencing a file inside your `node_modules` folder. Like so:
+
+```scss
+@import "~@lbry/components/sass/";
+```
+
+
+
+### License
+
+[BSD 3-Clause](LICENSE) Copyright © LBRY Inc.
diff --git a/web/components/dist/index.css b/web/components/dist/index.css
new file mode 100644
index 00000000..484b247b
--- /dev/null
+++ b/web/components/dist/index.css
@@ -0,0 +1 @@
+:root{--spacing-xxs:.2rem;--spacing-xs:.4rem;--spacing-s:.8rem;--spacing-m:1.6rem;--spacing-l:2.4rem;--spacing-xl:3.2rem;--spacing-xxl:6.4rem;--aspect-ratio-bluray:41.6666666667%;--aspect-ratio-panavision:36.3636363636%;--aspect-ratio-sd:75%;--aspect-ratio-standard:56.25%;--lbry-black:#212529;--lbry-white:#fff;--lbry-gray-1:#e2e5e9;--lbry-gray-2:#d8dde1;--lbry-gray-3:#ced4da;--lbry-gray-4:#abb1b7;--lbry-gray-5:#898e93;--lbry-teal-1:#88e8cb;--lbry-teal-2:#60e1ba;--lbry-teal-3:#38d9a9;--lbry-teal-4:#33b58f;--lbry-teal-5:#2f9176;--lbry-cyan-1:#89dfe9;--lbry-cyan-2:#62d4e2;--lbry-cyan-3:#3bc9db;--lbry-cyan-4:#36a8b7;--lbry-cyan-5:#318794;--lbry-blue-1:#85c2f6;--lbry-blue-2:#5caef3;--lbry-blue-3:#339af0;--lbry-blue-4:#2f83c8;--lbry-blue-5:#2c6ba0;--lbry-indigo-1:#acbcfd;--lbry-indigo-2:#90a5fd;--lbry-indigo-3:#748ffc;--lbry-indigo-4:#637ad2;--lbry-indigo-5:#5365a8;--lbry-violet-1:#c1acfc;--lbry-violet-2:#ac91fb;--lbry-violet-3:#9775fa;--lbry-violet-4:#7f65d0;--lbry-violet-5:#6855a6;--lbry-grape-1:#e9adf7;--lbry-grape-2:#e192f5;--lbry-grape-3:#da77f2;--lbry-grape-4:#b567ca;--lbry-grape-5:#9056a2;--lbry-pink-1:#fab5cd;--lbry-pink-2:#f99cbd;--lbry-pink-3:#f783ac;--lbry-pink-4:#cc7092;--lbry-pink-5:#a15d78;--lbry-red-1:#ec8383;--lbry-red-2:#e65a5a;--lbry-red-3:#e03131;--lbry-red-4:#ba2f2f;--lbry-red-5:#942c2e;--lbry-orange-1:#ffbe80;--lbry-orange-2:#ffa855;--lbry-orange-3:#ff922b;--lbry-orange-4:#d37c2b;--lbry-orange-5:#a6662a;--lbry-yellow-1:#ffeca3;--lbry-yellow-2:#ffe685;--lbry-yellow-3:#ffe066;--lbry-yellow-4:#d3bb5a;--lbry-yellow-5:#a6954e;--lbry-lime-1:#cbee93;--lbry-lime-2:#bae96f;--lbry-lime-3:#a9e34b;--lbry-lime-4:#8ebd44;--lbry-lime-5:#73973d;--lbry-green-1:#97e2a3;--lbry-green-2:#74d985;--lbry-green-3:#51cf66;--lbry-green-4:#47ad5a;--lbry-green-5:#3e8b4e;--font-mono:"Fira Code";--font-sans:Inter;--font-serif:Georgia}html{box-sizing:border-box;font-size:12px;text-rendering:optimizeLegibility;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}*,*::before,*::after{margin:0;padding:0;border:none;box-sizing:inherit;outline:0}[disabled]{pointer-events:none;resize:none}[disabled]:hover{background-color:inherit;border-color:inherit;color:inherit;fill:inherit}[readonly]{cursor:not-allowed}[for],[role=button],[type=button],[type=checkbox],[type=file],[type=radio],[type=select],[type=submit]{cursor:pointer}a,area,button,[role=button],input,label,select,summary,textarea{touch-action:manipulation}button,input,select,textarea{background-color:transparent;border-radius:0;font-family:inherit;font-size:inherit;font-weight:inherit;-moz-appearance:none;-webkit-appearance:none}button:disabled,input:disabled,select:disabled,textarea:disabled{border-color:var(--lbry-gray-4);color:var(--lbry-gray-4);cursor:default}h1,h2,h3,h4,h5,h6{font-weight:normal}ol,ul{list-style-position:inside}ol>li,ul>li{list-style-position:inside}ul{list-style-type:none}table{border-spacing:0}dd{width:80%;float:left}dl{width:100%;overflow-x:scroll;overflow-y:hidden}dt{width:20%;float:left}img{width:auto;max-width:100%;height:auto;max-height:100%;vertical-align:middle}a{color:inherit;text-decoration:none}button{background-color:transparent;color:inherit}button:not(:disabled){cursor:pointer}button:disabled{opacity:.3}hr{width:100%;height:1px;background-color:var(--lbry-gray-1)}input{background-color:transparent;color:inherit}input::placeholder{color:inherit;opacity:.2}input::-webkit-search-cancel-button{-webkit-appearance:none}select{outline:none}textarea{width:100%;min-height:var(--spacing-xxl);padding:var(--spacing-s);border:1px solid}textarea:not([disabled]){resize:vertical}@media print{pre,blockquote{border:1px solid var(--lbry-gray-5) !important;page-break-inside:avoid !important}tr,img{page-break-inside:avoid !important}img{max-width:100% !important}@page{margin:.5cm !important}p,h2,h3{orphans:3 !important;widows:3 !important}h2,h3{page-break-after:avoid !important}thead{display:table-header-group !important}*{background-color:transparent !important;background-image:none !important;color:var(--lbry-black) !important;filter:none !important;text-shadow:none !important}p a[href]::after{content:" (" attr(href) ")" !important}p a[href^="javascript:"]::after,p a[href^="#"]::after{content:"" !important}p abbr[title]::after{content:" (" attr(title) ")" !important}p a,p abbr{text-decoration:underline !important;word-wrap:break-word !important}}@keyframes loading-animation{0%{background-position:-500px 0}100%{background-position:500px 0}}@keyframes pulse{0%{opacity:1}50%{opacity:.7}100%{opacity:1}}.badge{border-radius:.2rem;display:inline-block;font-size:.8rem;font-weight:600;letter-spacing:.05rem;line-height:2;padding-right:var(--spacing-xs);padding-left:var(--spacing-xs);vertical-align:top;white-space:nowrap}.badge--cost{background-color:var(--lbry-yellow-2);color:var(--lbry-black)}[data-mode=dark] .badge--cost{background-color:var(--lbry-yellow-3)}.badge--free{background-color:var(--lbry-blue-2)}[data-mode=dark] .badge--free{background-color:var(--lbry-blue-3);color:var(--lbry-black)}.badge--large{font-size:4rem;line-height:1}.badge--nsfw{background-color:var(--lbry-grape-2)}[data-mode=dark] .badge--nsfw{background-color:var(--lbry-grape-3);color:var(--lbry-black)}.badge--primary{background-color:var(--lbry-teal-5);color:var(--lbry-white)}.badge--alert{background-color:var(--lbry-red-2);color:var(--lbry-white)}.button{fill:currentColor;position:relative;white-space:nowrap}.button--alt:hover{text-decoration:underline}.button--constrict{max-width:20vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.button--disabled{opacity:.3}.button--disabled:not([disabled]){pointer-events:none}.button--disabled:not([disabled]):hover{background-color:inherit;color:inherit;fill:inherit}.button--icon{width:5rem;height:5rem;background-color:rgba(33,37,41,.7);background-repeat:no-repeat;background-size:50%;border-radius:50%;color:var(--lbry-white);transition:background-color .2s}.button--icon:hover{background-color:var(--lbry-green-3)}.button--icon.button--play{background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E %3Cg stroke='white' stroke-width='2' fill='white' fill-rule='evenodd' stroke-linejoin='round'%3E %3Cpolygon points='5 21 5 3 21 12'/%3E %3C/g%3E %3C/svg%3E");background-position:calc(50% + 0.1rem) center}.button--icon.button--view{background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E %3Cg stroke='white' stroke-width='2' fill='none' fill-rule='evenodd'%3E %3Cpath d='M2, 12 C2, 12 5, 5 12, 5 C19, 5 22, 12 22, 12 C22, 12 19, 19 12, 19 C5, 19 2, 12 2, 12 Z' stroke-linejoin='round'/%3E %3Ccircle cx='12' cy='12' r='3'/%3E %3Cpath d='M12, 5 L12, 3' stroke-linecap='round'/%3E %3Cpath d='M18, 6.5 L19, 5' stroke-linecap='round'/%3E %3Cpath d='M21, 10 L22.5, 9' stroke-linecap='round'/%3E %3Cpath d='M1.5, 10 L3, 9' stroke-linecap='round' transform='translate(2.250000, 9.500000) scale(1, -1) translate(-2.250000, -9.500000)'/%3E %3Cpath d='M5, 6.5 L6, 5' stroke-linecap='round' transform='translate(5.500000, 5.750000) scale(-1, 1) translate(-5.500000, -5.750000)'/%3E %3C/g%3E %3C/svg%3E");background-position:center calc(50% + 0.1rem)}.button--inverse{padding:var(--spacing-xs) var(--spacing-s);background-color:transparent;border:1px solid var(--lbry-gray-1);border-radius:1rem;color:inherit;transition:background-color .2s}[data-mode=dark] .button--inverse{border-color:rgba(255,255,255,.1)}.button--inverse:hover{background-color:var(--lbry-gray-1)}[data-mode=dark] .button--inverse:hover{background-color:rgba(255,255,255,.1)}.button--link{color:var(--lbry-teal-5);transition:color .2s;word-break:break-all}[data-mode=dark] .button--link{color:var(--lbry-teal-3)}.button--link:hover{color:var(--lbry-teal-3)}[data-mode=dark] .button--link:hover{color:var(--lbry-teal-4)}.button--primary{padding:var(--spacing-xs) var(--spacing-s);align-self:center;background-color:var(--lbry-teal-5);border-radius:1rem;color:var(--lbry-white);transition:background-color .2s}.button--primary:hover{background-color:var(--lbry-teal-3)}[data-mode=dark] .button--primary:hover{background-color:var(--lbry-teal-4)}.button--uppercase{text-transform:uppercase}form:not(:last-child),.form:not(:last-child){margin-bottom:var(--spacing-s)}input,select{height:var(--spacing-l);border:1px solid}checkbox-element,.checkbox-element,radio-element,.radio-element,select{cursor:pointer}[type=email],[type=number],[type=password],[type=text]{padding-right:var(--spacing-s);padding-left:var(--spacing-s);transition:border .2s}[type=submit]{color:var(--lbry-white);padding-right:var(--spacing-m);padding-left:var(--spacing-m);transition:all .2s}[type=submit]:not(:hover){background-color:var(--lbry-black);border-color:var(--lbry-black)}[type=submit]:hover{background-color:var(--lbry-teal-3);border-color:var(--lbry-teal-5)}select{background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23212529'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A");background-position:99% center;background-repeat:no-repeat;background-size:1rem;padding-right:var(--spacing-l);padding-left:var(--spacing-s)}[data-mode=dark] select{background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23ffffff'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A")}fieldset,.fieldset{border-top:1px solid var(--lbry-gray-1);margin-bottom:var(--spacing-m);position:relative}fieldset-group,.fieldset-group{display:flex;flex-direction:row;justify-content:space-between}fieldset-section,.fieldset-section{display:flex;flex-direction:column;margin-bottom:var(--spacing-m)}fieldset-section.full-width,.full-width.fieldset-section{width:100%}fieldset-group fieldset-section,fieldset-group .fieldset-section,.fieldset-group fieldset-section,.fieldset-group .fieldset-section{width:49%}fieldset-section label,.fieldset-section label{color:var(--lbry-gray-4);display:inline-block;font-size:smaller;margin-bottom:var(--spacing-xxs);text-transform:uppercase}fieldset-section [type=email],.fieldset-section [type=email],fieldset-section [type=number],.fieldset-section [type=number],fieldset-section [type=text],.fieldset-section [type=text],fieldset-section select,.fieldset-section select,fieldset-section textarea,.fieldset-section textarea{border-color:var(--lbry-black)}fieldset-section [type=email]:focus,.fieldset-section [type=email]:focus,fieldset-section [type=number]:focus,.fieldset-section [type=number]:focus,fieldset-section [type=text]:focus,.fieldset-section [type=text]:focus,fieldset-section select:focus,.fieldset-section select:focus,fieldset-section textarea:focus,.fieldset-section textarea:focus{border-color:var(--lbry-teal-5)}fieldset-section input,.fieldset-section input,fieldset-section select,.fieldset-section select{width:100%}legend{padding:var(--spacing-xs) var(--spacing-s);border:1px solid var(--lbry-gray-1);margin-bottom:var(--spacing-s);pointer-events:none}input-submit,.input-submit{display:flex}input-submit [type=email],.input-submit [type=email],input-submit [type=text],.input-submit [type=text]{flex:1}input-submit [type=email]:not(:focus),.input-submit [type=email]:not(:focus),input-submit [type=text]:not(:focus),.input-submit [type=text]:not(:focus){border-top-color:var(--lbry-black);border-right-color:transparent;border-bottom-color:var(--lbry-black);border-left-color:var(--lbry-black)}input-submit [type=email]:focus,.input-submit [type=email]:focus,input-submit [type=text]:focus,.input-submit [type=text]:focus{border-top-color:var(--lbry-teal-5);border-right-color:transparent;border-bottom-color:var(--lbry-teal-5);border-left-color:var(--lbry-teal-5)}checkbox-element,.checkbox-element{min-height:2rem;align-items:center;align-self:start;display:inline-flex;flex-direction:row-reverse;margin-right:var(--spacing-s);margin-bottom:var(--spacing-s);position:relative}checkbox-element:hover label,.checkbox-element:hover label{color:var(--lbry-teal-4)}checkbox-element label,.checkbox-element label{left:-2rem;padding-left:var(--spacing-l);position:relative;transition:color .2s;z-index:1}checkbox-element:hover checkbox-toggle,.checkbox-element:hover checkbox-toggle,.checkbox-element:hover .checkbox-toggle,checkbox-element:hover .checkbox-toggle{border-color:var(--lbry-teal-4)}checkbox-element input[type=checkbox],.checkbox-element input[type=checkbox]{width:0;height:0;visibility:hidden}checkbox-element input[type=checkbox]:not(:checked)+label+checkbox-toggle::before,.checkbox-element input[type=checkbox]:not(:checked)+label+checkbox-toggle::before,.checkbox-element input[type=checkbox]:not(:checked)+label+.checkbox-toggle::before,checkbox-element input[type=checkbox]:not(:checked)+label+.checkbox-toggle::before{opacity:0;visibility:hidden}checkbox-element input[type=checkbox]:checked+label,.checkbox-element input[type=checkbox]:checked+label{color:var(--lbry-teal-4)}checkbox-toggle,.checkbox-toggle{width:var(--spacing-m);height:var(--spacing-m);top:0;left:0;border:2px solid;display:block;position:relative;transition:border .2s}checkbox-toggle::before,.checkbox-toggle::before{width:100%;height:100%;top:0;left:0;content:"";display:block;position:absolute}checkbox-toggle::before,.checkbox-toggle::before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2333b58f' stroke-width='2'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E%0A");background-position:center left;background-repeat:no-repeat;background-size:130%;transition:all .2s}radio-element,.radio-element{min-height:2rem;align-items:center;align-self:start;display:inline-flex;flex-direction:row-reverse;margin-right:var(--spacing-s);margin-bottom:var(--spacing-s);position:relative}radio-element:hover label,.radio-element:hover label{color:var(--lbry-teal-4)}radio-element label,.radio-element label{left:-2rem;padding-left:var(--spacing-l);position:relative;transition:color .2s;z-index:1}radio-element:hover radio-toggle,.radio-element:hover radio-toggle,.radio-element:hover .radio-toggle,radio-element:hover .radio-toggle{border-color:var(--lbry-teal-4)}radio-element input[type=radio],.radio-element input[type=radio]{width:0;height:0;visibility:hidden}radio-element input[type=radio]:not(:checked)+label+radio-toggle::before,.radio-element input[type=radio]:not(:checked)+label+radio-toggle::before,.radio-element input[type=radio]:not(:checked)+label+.radio-toggle::before,radio-element input[type=radio]:not(:checked)+label+.radio-toggle::before{background-color:transparent}radio-element input[type=radio]:checked+label,.radio-element input[type=radio]:checked+label{color:var(--lbry-teal-4)}radio-element input[type=radio]:checked+label+radio-toggle::before,.radio-element input[type=radio]:checked+label+radio-toggle::before,.radio-element input[type=radio]:checked+label+.radio-toggle::before,radio-element input[type=radio]:checked+label+.radio-toggle::before{background-color:var(--lbry-teal-4)}radio-toggle,.radio-toggle{width:var(--spacing-m);height:var(--spacing-m);top:0;left:0;border:2px solid;display:block;position:relative;transition:border .2s;border-radius:50%}radio-toggle::before,.radio-toggle::before{width:100%;height:100%;top:0;left:0;content:"";display:block;position:absolute}radio-toggle::before,.radio-toggle::before{border-radius:50%;transform:scale(0.6);transition:background-color .2s}.media-grid{display:grid}.media-grid .media{width:100%;cursor:pointer;vertical-align:top}.media{position:relative}.media--large{display:flex}.media--large .media__info{width:calc(100% - 20rem);margin-left:var(--spacing-s)}.media--large .media__thumb{width:40rem;margin-bottom:var(--spacing-s)}.media--placeholder>*{animation-duration:4s;animation-fill-mode:forwards;animation-iteration-count:infinite;animation-name:loading-animation;animation-timing-function:linear;background-color:transparent;background-image:linear-gradient(to right, var(--lbry-gray-3) 10%, transparent 80%, var(--lbry-gray-3) 100%);background-repeat:repeat;background-size:500px}[data-mode=dark] .media--placeholder>*{background-image:linear-gradient(to right, rgba(255, 255, 255, 0.1) 10%, transparent 80%, rgba(255, 255, 255, 0.1) 100%)}.media--placeholder .media__channel,.media--placeholder .media__date,.media--placeholder .media__title{min-height:1rem}.media--placeholder .media__channel{width:70%}.media--placeholder .media__date{width:30%}.media--placeholder .media__title{width:100%}.media--small{display:flex;justify-content:space-between}.media--small:not(:last-of-type){margin-bottom:var(--spacing-s)}.media--small .media__info{width:50%;padding-left:var(--spacing-s)}.media--small .media__properties{bottom:-1.5rem;left:calc(-100% - 1.5rem);position:absolute}.media--small .media__thumb{width:50%}.media--small .media__title{height:3rem;line-height:1.33}.media--wide{display:flex}.media--wide:not(:last-of-type){margin-bottom:var(--spacing-m)}.media--wide .media__info{width:calc(100% - 20rem);margin-left:var(--spacing-s)}.media--wide .media__properties{bottom:-5.5rem;left:-20rem;position:absolute}.media--wide .media__text{padding-top:var(--spacing-s)}.media--wide .media__thumb{width:20rem}.media--wide .media__title{font-size:1.5rem}.media__actions{display:flex;padding-top:var(--spacing-m);padding-bottom:var(--spacing-m)}.media__info{word-wrap:break-word}[data-mode=dark] .media__info{border-color:rgba(137,142,147,.2)}.media__message{padding:var(--spacing-s);white-space:normal}.media__properties{vertical-align:top}.media__properties>*:not(:last-child){margin-right:var(--spacing-xs)}.media__properties:not(:empty){display:inline-block}.media__subtitle{position:relative}[data-mode=dark] .media__text{color:var(--lbry-white)}.media__thumb::before,.media__thumb::after{content:""}.media__thumb::before{float:left;padding-top:var(--aspect-ratio-standard)}.media__thumb::after{clear:both;display:block}.media__title{font-weight:600;white-space:normal}drawer-navigation{width:100%;height:5rem;display:inline-flex;flex:1;font-size:inherit;justify-content:center;position:relative;z-index:10}drawer-navigation-helper{width:0;height:0;top:3rem;left:-5rem;border-right:8rem solid transparent;border-bottom:5rem solid transparent;border-left:8rem solid transparent;position:absolute}drawer-section{padding-right:1rem;padding-left:1rem}drawer-section:not(:hover):not(.active) drawer-title::after{background-color:transparent}drawer-section:not(:hover):not(.active) drawer-navigation-helper,drawer-section:not(:hover):not(.active) drawer-wrap{display:none}drawer-section:hover,drawer-section.active{z-index:3}drawer-section:hover drawer-title::after,drawer-section.active drawer-title::after{background-color:var(--lbry-teal-4)}drawer-title{height:100%;align-items:center;cursor:default;display:flex;line-height:3;position:relative;z-index:1}drawer-title::after{width:100%;height:1px;bottom:-1px;left:0;content:"";position:absolute;z-index:1}drawer-wrap{width:100%;top:5rem;left:0;background-color:var(--lbry-white);border-top:1px solid var(--lbry-gray-1);padding-top:2rem;padding-bottom:2rem;position:absolute}drawer-wrap::after{width:100vw;height:calc(100vh - 5rem);top:5rem;left:0;background-color:var(--lbry-black);content:"";opacity:.3;pointer-events:none;position:absolute;z-index:-1}drawer-children{display:flex;flex-wrap:wrap;position:relative}drawer-child{padding:var(--spacing-s);border:2px solid;transition:all .2s}drawer-child:not(:hover){border-color:transparent}drawer-child:hover{border-color:var(--lbry-gray-1);padding-left:var(--spacing-m)}drawer-child:hover>a{color:var(--lbry-teal-4)}drawer-child:not([full-width]){width:50%}drawer-child[full-width]{width:100%}drawer-child span{display:flex;padding-top:.25rem;padding-bottom:.25rem}table{width:100%;background-color:var(--lbry-white);position:relative}table thead{cursor:default;font-size:80%;position:relative}table thead tr{position:relative;z-index:1}table tbody{line-height:1.55}table tr:not(:last-of-type) td{border-bottom:1px solid var(--lbry-gray-1)}table th,table td{padding:.5rem 1rem}table th{border-bottom:2px solid var(--lbry-black);letter-spacing:.1rem;text-align:left;text-transform:uppercase}table a{font-weight:bolder}/*# sourceMappingURL=index.css.map */
diff --git a/web/components/dist/index.css.map b/web/components/dist/index.css.map
new file mode 100644
index 00000000..a7c453dd
--- /dev/null
+++ b/web/components/dist/index.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["../sass/init/_variables.scss","../sass/init/_mixins.scss","../sass/init/_color.scss","../sass/init/_reset.scss","../sass/init/_animation.scss","../sass/badge/_index.scss","../sass/button/_index.scss","../sass/form/_index.scss","../sass/media/_index.scss","../sass/navigation/_index.scss","../sass/table/_index.scss"],"names":[],"mappings":"AAAA,MCmJI,cDjJgC,MCiJhC,aDhJ+B,MCgJ/B,YD/I8B,MC+I9B,YD9I8B,OC8I9B,YD7I8B,OC6I9B,aD5I+B,OC4I/B,cD3IgC,OC2IhC,sBDxIwC,eCwIxC,0BDvI4C,eCuI5C,kBDtIoC,ICsIpC,wBDrI0C,OCqI1C,aC7IS,QD6IT,aC/IS,KD+IT,cChHU,QDgHV,cC/GU,QD+GV,cC9GU,QD8GV,cC7GU,QD6GV,cC5GU,QD4GV,cC7FU,QD6FV,cC5FU,QD4FV,cC3FU,QD2FV,cC1FU,QD0FV,cCzFU,QDyFV,cC1EU,QD0EV,cCzEU,QDyEV,cCxEU,QDwEV,cCvEU,QDuEV,cCtEU,QDsEV,cCvDU,QDuDV,cCtDU,QDsDV,cCrDU,QDqDV,cCpDU,QDoDV,cCnDU,QDmDV,gBCpCY,QDoCZ,gBCnCY,QDmCZ,gBClCY,QDkCZ,gBCjCY,QDiCZ,gBChCY,QDgCZ,gBCjBY,QDiBZ,gBChBY,QDgBZ,gBCfY,QDeZ,gBCdY,QDcZ,gBCbY,QDaZ,eCEW,QDFX,eCGW,QDHX,eCIW,QDJX,eCKW,QDLX,eCMW,QDNX,cCqBU,QDrBV,cCsBU,QDtBV,cCuBU,QDvBV,cCwBU,QDxBV,cCyBU,QDzBV,aCwCS,QDxCT,aCyCS,QDzCT,aC0CS,QD1CT,aC2CS,QD3CT,aC4CS,QD5CT,gBC2DY,QD3DZ,gBC4DY,QD5DZ,gBC6DY,QD7DZ,gBC8DY,QD9DZ,gBC+DY,QD/DZ,gBC8EY,QD9EZ,gBC+EY,QD/EZ,gBCgFY,QDhFZ,gBCiFY,QDjFZ,gBCkFY,QDlFZ,cCiGU,QDjGV,cCkGU,QDlGV,cCmGU,QDnGV,cCoGU,QDpGV,cCqGU,QDrGV,eCoHW,QDpHX,eCqHW,QDrHX,eCsHW,QDtHX,eCuHW,QDvHX,eCwHW,QDxHX,YDhD8B,YCgD9B,uCEnJJ,KACE,sBACA,eACA,kCAEA,kCACA,mCAGF,qBAGE,mBAEA,YACA,mBACA,UAGF,WACE,oBACA,YAEA,iBACE,yBACA,qBACA,cACA,aAIJ,WACE,mBAGF,uGAQE,eAGF,gEAUE,0BAGF,6BAIE,6BACA,gBACA,oBACA,kBACA,oBAEA,qBACA,wBAEA,iEAEE,gCACA,yBACA,eAIJ,kBAME,mBAGF,MAEE,2BAEA,YACE,2BAIJ,GACE,qBAGF,MACE,iBAGF,GACE,UACA,WAGF,GACE,WACA,kBACA,kBAGF,GACE,UACA,WAGF,IACE,0BACA,4BACA,sBAGF,EACE,cACA,qBAGF,OACE,6BACA,cAEA,sBACE,eAGF,gBACE,WAIJ,GACE,sBACA,oCAGF,MACE,6BACA,cAEA,mBACE,cACA,WAOF,oCACE,wBAIJ,OACE,aAGF,SACE,WACA,8BACA,yBAEA,iBAEA,yBACE,gBAMJ,aAGE,eAEE,+CACA,mCAGF,OAEE,mCAGF,IACE,0BAGF,MACE,uBAGF,QAGE,qBACA,oBAGF,MAEE,kCAGF,MACE,sCAIF,EACE,wCACA,iCACA,mCACA,uBACA,4BAKE,iBACE,uCAKA,sDACE,sBAMJ,qBACE,wCAIJ,WAEE,qCACA,iCC/PN,6BACE,GACE,6BAGF,KACE,6BAIJ,iBACE,GACE,UAGF,IACE,WAGF,KACE,WCtBJ,OACE,oBACA,qBACA,gBACA,gBACA,sBACA,cACA,gCACA,+BACA,mBACA,mBAGF,aACE,sCACA,wBAEA,8BACE,sCAIJ,aACE,oCAEA,8BACE,oCACA,wBAIJ,cACE,eACA,cAGF,aACE,qCAEA,8BACE,qCACA,wBAIJ,gBACE,oCACA,wBAGF,cACE,mCACA,wBCpDF,QACE,kBACA,kBACA,mBAIA,mBACE,0BAIJ,mBLsBI,UKrBiB,KLwBnB,gBACA,uBACA,mBKvBF,kBACE,WAEA,kCACE,oBAEA,wCACE,yBACA,cACA,aAKN,cACE,WACA,YACA,mCACA,4BACA,oBACA,kBACA,wBACA,gCAGA,oBACE,qCAGF,2BACE,mTACA,8CAGF,2BACE,q1BACA,8CAIJ,iBACE,2CACA,6BACA,oCACA,mBACA,cACA,gCAEA,kCACE,kCAGF,uBACE,oCAEA,wCACE,sCAKN,cACE,yBACA,qBACA,qBAEA,+BACE,yBAGF,oBACE,yBAEA,qCACE,yBAKN,iBACE,2CACA,kBACA,oCACA,mBACA,wBACA,gCAEA,uBACE,oCAEA,wCACE,oCAKN,mBACE,yBC/GA,6CACE,+BAIJ,aAEE,wBACA,iBAGF,uEAGE,eAGF,uDAIE,+BACA,8BACA,sBAGF,cACE,wBACA,+BACA,8BACA,mBAEA,0BACE,mCACA,+BAGF,oBACE,oCACA,gCAIJ,OACE,gcACA,+BACA,4BACA,qBACA,+BACA,8BAEA,wBACE,gcAIJ,mBACE,wCACA,+BACA,kBAGF,+BACE,aACA,mBACA,8BAGF,mCACE,aACA,sBACA,+BAEA,yDACE,WAGF,oIAEE,UAGF,+CACE,yBACA,qBACA,kBACA,iCACA,yBAGF,6RAKE,+BAEA,yVACE,gCAIJ,gGAEE,WAIJ,OACE,2CACA,oCACA,+BACA,oBAGF,2BACE,aAEA,wGAEE,OAEA,wJACE,mCACA,+BACA,sCACA,oCAGF,gIACE,oCACA,+BACA,uCACA,qCAKN,mCN2CE,gBACA,mBACA,iBACA,oBACA,2BACA,8BACA,+BACA,kBAGE,2DACE,yBAIJ,+CACE,WACA,8BACA,kBACA,qBACA,UM3DA,gKAEE,gCAKF,6EACE,iBACA,kBAGE,4UAEE,UACA,kBAKF,yGACE,yBAOV,iCNoCE,+CACA,aAEA,iBACA,cACA,kBACA,sBAEA,iDACE,uBACA,aAEA,WACA,cACA,kBM/CF,iDACE,8NACA,gCACA,4BACA,qBACA,mBAIJ,6BNDE,gBACA,mBACA,iBACA,oBACA,2BACA,8BACA,+BACA,kBAGE,qDACE,yBAIJ,yCACE,WACA,8BACA,kBACA,qBACA,UMfA,wIAEE,gCAKF,iEACE,iBACA,kBAGE,wSAEE,6BAKF,6FACE,yBAGF,gRAEE,oCAOV,2BNZE,+CACA,aAEA,iBACA,cACA,kBACA,sBMQA,kBNNA,2CACE,uBACA,aAEA,WACA,cACA,kBMEF,2CACE,kBACA,qBACA,gCCjOJ,YACE,aAQA,mBACE,WACA,eACA,mBAYJ,OACE,kBAOF,cACE,aAEA,2BACE,yBACA,6BAGF,4BACE,YACA,+BAKF,sBP0EA,sBACA,6BACA,mCACA,iCACA,iCACA,6BACA,6GACA,yBACA,sBAEA,uCACE,yHOjFF,uGAGE,gBAGF,oCACE,UAGF,iCACE,UAGF,kCACE,WAIJ,cACE,aACA,8BAEA,iCACE,+BAGF,2BACE,UACA,8BAGF,iCACE,yCACA,kBAGF,4BACE,UAGF,4BACE,YACA,iBAIJ,aACE,aAEA,gCACE,+BAGF,0BACE,yBACA,6BAGF,gCACE,2BACA,kBAGF,0BACE,6BAGF,2BACE,YAGF,2BACE,iBAQJ,gBACE,aACA,6BACA,gCASF,aACE,qBAEA,8BACE,kCAIJ,gBACE,yBACA,mBAGF,mBACE,mBAEA,sCACE,+BAGF,+BACE,qBAOJ,iBACE,kBAIA,8BACE,wBPdF,2CAEE,WAGF,sBACE,WACA,yCAGF,qBACE,WACA,cOUJ,cACE,gBACA,mBC9LF,kBACE,uBAEA,oBACA,OACA,kBACA,uBACA,kBACA,WAGF,yBACE,iBACA,oBAEA,oCACA,qCACA,mCACA,kBAGF,eACE,mBACA,kBAGE,4DACE,6BAGF,qHAEE,aAIJ,2CAEE,UAEA,mFACE,oCAKN,aACE,YACA,mBACA,eACA,aACA,cACA,kBACA,UAEA,oBACE,sBACA,mBAEA,WACA,kBACA,UAIJ,YACE,WACA,gBAEA,mCACA,wCACA,iBACA,oBACA,kBAEA,mBACE,sCACA,gBAEA,mCACA,WACA,WACA,oBACA,kBACA,WAIJ,gBACE,aACA,eACA,kBAGF,aACE,yBACA,iBACA,mBAEA,yBACE,yBAGF,mBACE,gCACA,8BAEA,qBACE,yBAIJ,+BACE,UAGF,yBACE,WAGF,kBACE,aACA,mBACA,sBC1HJ,MACE,WACA,mCACA,kBAEA,YACE,eACA,cACA,kBAEA,eACE,kBACA,UAIJ,YACE,iBAKE,+BACE,2CAKN,kBAEE,mBAGF,SACE,0CACA,qBACA,gBACA,yBAGF,QACE","file":"index.css"}
\ No newline at end of file
diff --git a/web/components/docs/index.html b/web/components/docs/index.html
new file mode 100644
index 00000000..a3cf1af8
--- /dev/null
+++ b/web/components/docs/index.html
@@ -0,0 +1,401 @@
+
+
+
+
+ Components by LBRY
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Table
+
+
+
+
+ Component
+ Language (Toolset)
+ What Is It
+
+
+
+
+
+ lbrycrd
+ C++
+ A full node for the LBRY blockchain, including a standalone wallet. Used by miners and some applications. Most consumer applications do not bundle lbrycrd directly, and instead bundle lbry-sdk .
+
+
+ lbry-sdk
+ Python (asyncio)
+ A daemon that can be used directly or to develop other applications. Provides convenience APIs , bundles an SPV wallet (torba ), and contains an implementation of the LBRY data network.
+
+
+ torba
+ Python
+ An SPV (Simple Payment Verification) wallet. Bundled with lbry-sdk .
+
+
+ wallet server
+ Protobuf, Python
+ The wallet server used by torba .
+
+
+ schema
+ Protobuf, Python
+ Defines the structure of the metadata stored in the LBRY blockchain.
+
+
+
+
+
+
+ Copyright © 2018-2019, LBRY Inc. | BSD 3-Clause Licensed.
+
+
diff --git a/web/components/package.json b/web/components/package.json
new file mode 100644
index 00000000..31aebb76
--- /dev/null
+++ b/web/components/package.json
@@ -0,0 +1,44 @@
+{
+ "author": {
+ "email": "paul+github@lbry.com",
+ "name": "Paul Anthony Webb"
+ },
+ "bugs": {
+ "url": "https://github.com/lbryio/components/issues"
+ },
+ "description": "Styling for shared components across LBRY properties",
+ "devDependencies": {
+ "@inc/sasslint-config": "^2019.6.22",
+ "husky": "^3.0.1",
+ "npm-run-all": "^4.1.5",
+ "sass": "^1.22.7",
+ "sass-lint": "^1.13.1",
+ "updates": "^8.5.1"
+ },
+ "files": [
+ "dist/style.css",
+ "sass/*"
+ ],
+ "homepage": "https://github.com/lbryio/components#readme",
+ "husky": {
+ "hooks": {
+ "pre-commit": "npm run test:sass && npm run sass:prod && git add -A :/"
+ }
+ },
+ "license": "BSD-3-Clause",
+ "main": "sass/",
+ "name": "@lbry/components",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/lbryio/components.git"
+ },
+ "scripts": {
+ "sass:dev": "sass --load-path=node_modules --watch sass:dist --style compressed",
+ "sass:prod": "sass --load-path=node_modules --update sass:dist --style compressed",
+ "test": "run-s test:*",
+ "test:dependencies": "updates --update ./",
+ "test:sass": "sass-lint --config ./node_modules/@inc/sasslint-config/config.json --verbose",
+ "watch": "npm run sass:dev"
+ },
+ "version": "2.8.0"
+}
diff --git a/web/components/sass/badge/_index.scss b/web/components/sass/badge/_index.scss
new file mode 100644
index 00000000..3fe719b2
--- /dev/null
+++ b/web/components/sass/badge/_index.scss
@@ -0,0 +1,54 @@
+.badge {
+ border-radius: 0.2rem;
+ display: inline-block;
+ font-size: 0.8rem;
+ font-weight: 600;
+ letter-spacing: 0.05rem;
+ line-height: 2;
+ padding-right: var(--spacing-xs);
+ padding-left: var(--spacing-xs);
+ vertical-align: top;
+ white-space: nowrap;
+}
+
+.badge--cost {
+ background-color: var(--lbry-yellow-2);
+ color: var(--lbry-black);
+
+ [data-mode="dark"] & {
+ background-color: var(--lbry-yellow-3);
+ }
+}
+
+.badge--free {
+ background-color: var(--lbry-blue-2);
+
+ [data-mode="dark"] & {
+ background-color: var(--lbry-blue-3);
+ color: var(--lbry-black);
+ }
+}
+
+.badge--large {
+ font-size: 4rem;
+ line-height: 1;
+}
+
+.badge--nsfw {
+ background-color: var(--lbry-grape-2);
+
+ [data-mode="dark"] & {
+ background-color: var(--lbry-grape-3);
+ color: var(--lbry-black);
+ }
+}
+
+.badge--primary {
+ background-color: var(--lbry-teal-5);
+ color: var(--lbry-white);
+}
+
+.badge--alert {
+ background-color: var(--lbry-red-2);
+ color: var(--lbry-white);
+}
diff --git a/web/components/sass/button/_index.scss b/web/components/sass/button/_index.scss
new file mode 100644
index 00000000..7ea12b07
--- /dev/null
+++ b/web/components/sass/button/_index.scss
@@ -0,0 +1,115 @@
+.button {
+ fill: currentColor;
+ position: relative;
+ white-space: nowrap;
+}
+
+.button--alt {
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+.button--constrict {
+ @include constrict(20vw);
+}
+
+.button--disabled {
+ opacity: 0.3;
+
+ &:not([disabled]) {
+ pointer-events: none;
+
+ &:hover {
+ background-color: inherit;
+ color: inherit;
+ fill: inherit;
+ }
+ }
+}
+
+.button--icon {
+ width: 5rem;
+ height: 5rem;
+ background-color: rgba($lbry-black, 0.7);
+ background-repeat: no-repeat;
+ background-size: 50%;
+ border-radius: 50%;
+ color: var(--lbry-white);
+ transition: background-color 0.2s;
+
+
+ &:hover {
+ background-color: var(--lbry-green-3);
+ }
+
+ &.button--play {
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E %3Cg stroke='white' stroke-width='2' fill='white' fill-rule='evenodd' stroke-linejoin='round'%3E %3Cpolygon points='5 21 5 3 21 12'/%3E %3C/g%3E %3C/svg%3E");
+ background-position: calc(50% + 0.1rem) center;
+ }
+
+ &.button--view {
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E %3Cg stroke='white' stroke-width='2' fill='none' fill-rule='evenodd'%3E %3Cpath d='M2, 12 C2, 12 5, 5 12, 5 C19, 5 22, 12 22, 12 C22, 12 19, 19 12, 19 C5, 19 2, 12 2, 12 Z' stroke-linejoin='round'/%3E %3Ccircle cx='12' cy='12' r='3'/%3E %3Cpath d='M12, 5 L12, 3' stroke-linecap='round'/%3E %3Cpath d='M18, 6.5 L19, 5' stroke-linecap='round'/%3E %3Cpath d='M21, 10 L22.5, 9' stroke-linecap='round'/%3E %3Cpath d='M1.5, 10 L3, 9' stroke-linecap='round' transform='translate(2.250000, 9.500000) scale(1, -1) translate(-2.250000, -9.500000)'/%3E %3Cpath d='M5, 6.5 L6, 5' stroke-linecap='round' transform='translate(5.500000, 5.750000) scale(-1, 1) translate(-5.500000, -5.750000)'/%3E %3C/g%3E %3C/svg%3E");
+ background-position: center calc(50% + 0.1rem);
+ }
+}
+
+.button--inverse {
+ padding: var(--spacing-xs) var(--spacing-s); // sass-lint:disable-line shorthand-values
+ background-color: transparent;
+ border: 1px solid var(--lbry-gray-1);
+ border-radius: 1rem;
+ color: inherit;
+ transition: background-color 0.2s;
+
+ [data-mode="dark"] & {
+ border-color: rgba($lbry-white, 0.1);
+ }
+
+ &:hover {
+ background-color: var(--lbry-gray-1);
+
+ [data-mode="dark"] & {
+ background-color: rgba($lbry-white, 0.1);
+ }
+ }
+}
+
+.button--link {
+ color: var(--lbry-teal-5);
+ transition: color 0.2s;
+ word-break: break-all;
+
+ [data-mode="dark"] & {
+ color: var(--lbry-teal-3);
+ }
+
+ &:hover {
+ color: var(--lbry-teal-3);
+
+ [data-mode="dark"] & {
+ color: var(--lbry-teal-4);
+ }
+ }
+}
+
+.button--primary {
+ padding: var(--spacing-xs) var(--spacing-s); // sass-lint:disable-line shorthand-values
+ align-self: center; // fixes buttons next to tall elements inside one with `display: flex`
+ background-color: var(--lbry-teal-5);
+ border-radius: 1rem;
+ color: var(--lbry-white);
+ transition: background-color 0.2s;
+
+ &:hover {
+ background-color: var(--lbry-teal-3);
+
+ [data-mode="dark"] & {
+ background-color: var(--lbry-teal-4);
+ }
+ }
+}
+
+.button--uppercase {
+ text-transform: uppercase;
+}
diff --git a/web/components/sass/form/_index.scss b/web/components/sass/form/_index.scss
new file mode 100644
index 00000000..79bbc690
--- /dev/null
+++ b/web/components/sass/form/_index.scss
@@ -0,0 +1,268 @@
+form {
+ // setting the font size here sizes everything within
+ &:not(:last-child) {
+ margin-bottom: var(--spacing-s);
+ }
+}
+
+input,
+select {
+ height: var(--spacing-l);
+ border: 1px solid;
+}
+
+checkbox-element,
+radio-element,
+select {
+ cursor: pointer;
+}
+
+[type="email"],
+[type="number"],
+[type="password"],
+[type="text"] {
+ padding-right: var(--spacing-s);
+ padding-left: var(--spacing-s);
+ transition: border 0.2s;
+}
+
+[type="submit"] {
+ color: var(--lbry-white);
+ padding-right: var(--spacing-m);
+ padding-left: var(--spacing-m);
+ transition: all 0.2s;
+
+ &:not(:hover) {
+ background-color: var(--lbry-black);
+ border-color: var(--lbry-black);
+ }
+
+ &:hover {
+ background-color: var(--lbry-teal-3);
+ border-color: var(--lbry-teal-5);
+ }
+}
+
+select {
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23212529'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A");
+ background-position: 99% center;
+ background-repeat: no-repeat;
+ background-size: 1rem;
+ padding-right: var(--spacing-l);
+ padding-left: var(--spacing-s);
+
+ [data-mode="dark"] & {
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23ffffff'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A");
+ }
+}
+
+fieldset {
+ border-top: 1px solid var(--lbry-gray-1);
+ margin-bottom: var(--spacing-m);
+ position: relative;
+}
+
+fieldset-group {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+
+fieldset-section {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: var(--spacing-m);
+
+ &.full-width {
+ width: 100%;
+ }
+
+ fieldset-group &,
+ .fieldset-group & {
+ width: 49%;
+ }
+
+ label {
+ color: var(--lbry-gray-4);
+ display: inline-block;
+ font-size: smaller;
+ margin-bottom: var(--spacing-xxs);
+ text-transform: uppercase;
+ }
+
+ [type="email"],
+ [type="number"],
+ [type="text"],
+ select,
+ textarea {
+ border-color: var(--lbry-black);
+
+ &:focus {
+ border-color: var(--lbry-teal-5);
+ }
+ }
+
+ input,
+ select {
+ width: 100%;
+ }
+}
+
+legend {
+ padding: var(--spacing-xs) var(--spacing-s); // sass-lint:disable-line shorthand-values
+ border: 1px solid var(--lbry-gray-1);
+ margin-bottom: var(--spacing-s);
+ pointer-events: none;
+}
+
+input-submit {
+ display: flex;
+
+ [type="email"],
+ [type="text"] {
+ flex: 1;
+
+ &:not(:focus) {
+ border-top-color: var(--lbry-black);
+ border-right-color: transparent;
+ border-bottom-color: var(--lbry-black);
+ border-left-color: var(--lbry-black);
+ }
+
+ &:focus {
+ border-top-color: var(--lbry-teal-5);
+ border-right-color: transparent;
+ border-bottom-color: var(--lbry-teal-5);
+ border-left-color: var(--lbry-teal-5);
+ }
+ }
+}
+
+checkbox-element {
+ @include tick;
+
+ &:hover {
+ checkbox-toggle,
+ .checkbox-toggle {
+ border-color: var(--lbry-teal-4);
+ }
+ }
+
+ input {
+ &[type="checkbox"] {
+ width: 0; height: 0;
+ visibility: hidden;
+
+ &:not(:checked) {
+ + label + checkbox-toggle::before,
+ + label + .checkbox-toggle::before {
+ opacity: 0;
+ visibility: hidden;
+ }
+ }
+
+ &:checked {
+ + label {
+ color: var(--lbry-teal-4);
+ }
+ }
+ }
+ }
+}
+
+checkbox-toggle {
+ @include tick-toggle;
+
+ &::before {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2333b58f' stroke-width='2'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E%0A");
+ background-position: center left;
+ background-repeat: no-repeat;
+ background-size: 130%;
+ transition: all 0.2s;
+ }
+}
+
+radio-element {
+ @include tick;
+
+ &:hover {
+ radio-toggle,
+ .radio-toggle {
+ border-color: var(--lbry-teal-4);
+ }
+ }
+
+ input {
+ &[type="radio"] {
+ width: 0; height: 0;
+ visibility: hidden;
+
+ &:not(:checked) {
+ + label + radio-toggle::before,
+ + label + .radio-toggle::before {
+ background-color: transparent;
+ }
+ }
+
+ &:checked {
+ + label {
+ color: var(--lbry-teal-4);
+ }
+
+ + label + radio-toggle::before,
+ + label + .radio-toggle::before {
+ background-color: var(--lbry-teal-4);
+ }
+ }
+ }
+ }
+}
+
+radio-toggle {
+ @include tick-toggle;
+ border-radius: 50%;
+
+ &::before {
+ border-radius: 50%;
+ transform: scale(0.6);
+ transition: background-color 0.2s;
+ }
+}
+
+// Custom elements are apparently difficult to use in React, so use classes
+
+.checkbox-element {
+ @extend checkbox-element;
+}
+
+.checkbox-toggle {
+ @extend checkbox-toggle;
+}
+
+.fieldset {
+ @extend fieldset;
+}
+
+.fieldset-group {
+ @extend fieldset-group;
+}
+
+.fieldset-section {
+ @extend fieldset-section;
+}
+
+.form {
+ @extend form;
+}
+
+.input-submit {
+ @extend input-submit;
+}
+
+.radio-element {
+ @extend radio-element;
+}
+
+.radio-toggle {
+ @extend radio-toggle;
+}
diff --git a/web/components/sass/index.scss b/web/components/sass/index.scss
new file mode 100644
index 00000000..1b050d41
--- /dev/null
+++ b/web/components/sass/index.scss
@@ -0,0 +1,13 @@
+@charset "utf-8";
+
+@import "init/color";
+@import "init/mixins";
+@import "init/variables";
+@import "init/reset";
+@import "init/animation";
+@import "badge";
+@import "button";
+@import "form";
+@import "media";
+@import "navigation";
+@import "table";
diff --git a/web/components/sass/init/_animation.scss b/web/components/sass/init/_animation.scss
new file mode 100644
index 00000000..718ea300
--- /dev/null
+++ b/web/components/sass/init/_animation.scss
@@ -0,0 +1,25 @@
+// TODO: Make customizable
+// This may have to become a mixin
+@keyframes loading-animation {
+ 0% {
+ background-position: -500px 0;
+ }
+
+ 100% {
+ background-position: 500px 0;
+ }
+}
+
+@keyframes pulse {
+ 0% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0.7;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
diff --git a/web/components/sass/init/_color.scss b/web/components/sass/init/_color.scss
new file mode 100644
index 00000000..ee4673f5
--- /dev/null
+++ b/web/components/sass/init/_color.scss
@@ -0,0 +1,291 @@
+
+// General
+// ───────────────────────────────────
+
+$lbry-white: #fff;
+$lbry-silver: #f1f3f5;
+$lbry-black: #212529;
+
+$base-gray: #ced4da;
+$base-teal: #38d9a9;
+$base-cyan: #3bc9db;
+$base-blue: #339af0;
+$base-indigo: #748ffc;
+$base-violet: #9775fa;
+$base-grape: #da77f2;
+$base-pink: #f783ac;
+$base-red: #e03131;
+$base-orange: #ff922b;
+$base-yellow: #ffe066;
+$base-lime: #a9e34b;
+$base-green: #51cf66;
+
+
+
+// Gray
+// ───────────────────────────────────
+
+$lbry-gray-list: (
+ "1": mix($lbry-white, $base-gray, 40%),
+ "2": mix($lbry-white, $base-gray, 20%),
+ "3": $base-gray,
+ "4": mix($lbry-black, $base-gray, 20%),
+ "5": mix($lbry-black, $base-gray, 40%)
+);
+
+$lbry-gray-1: map-get($lbry-gray-list, "1");
+$lbry-gray-2: map-get($lbry-gray-list, "2");
+$lbry-gray-3: map-get($lbry-gray-list, "3");
+$lbry-gray-4: map-get($lbry-gray-list, "4");
+$lbry-gray-5: map-get($lbry-gray-list, "5");
+
+
+
+// Teal
+// ───────────────────────────────────
+
+$lbry-teal-list: (
+ "1": mix($lbry-white, $base-teal, 40%),
+ "2": mix($lbry-white, $base-teal, 20%),
+ "3": $base-teal,
+ "4": mix($lbry-black, $base-teal, 20%),
+ "5": mix($lbry-black, $base-teal, 40%)
+);
+
+$lbry-teal-1: map-get($lbry-teal-list, "1");
+$lbry-teal-2: map-get($lbry-teal-list, "2");
+$lbry-teal-3: map-get($lbry-teal-list, "3");
+$lbry-teal-4: map-get($lbry-teal-list, "4");
+$lbry-teal-5: map-get($lbry-teal-list, "5");
+
+
+
+// Cyan
+// ───────────────────────────────────
+
+$lbry-cyan-list: (
+ "1": mix($lbry-white, $base-cyan, 40%),
+ "2": mix($lbry-white, $base-cyan, 20%),
+ "3": $base-cyan,
+ "4": mix($lbry-black, $base-cyan, 20%),
+ "5": mix($lbry-black, $base-cyan, 40%)
+);
+
+$lbry-cyan-1: map-get($lbry-cyan-list, "1");
+$lbry-cyan-2: map-get($lbry-cyan-list, "2");
+$lbry-cyan-3: map-get($lbry-cyan-list, "3");
+$lbry-cyan-4: map-get($lbry-cyan-list, "4");
+$lbry-cyan-5: map-get($lbry-cyan-list, "5");
+
+
+
+// Blue
+// ───────────────────────────────────
+
+$lbry-blue-list: (
+ "1": mix($lbry-white, $base-blue, 40%),
+ "2": mix($lbry-white, $base-blue, 20%),
+ "3": $base-blue,
+ "4": mix($lbry-black, $base-blue, 20%),
+ "5": mix($lbry-black, $base-blue, 40%)
+);
+
+$lbry-blue-1: map-get($lbry-blue-list, "1");
+$lbry-blue-2: map-get($lbry-blue-list, "2");
+$lbry-blue-3: map-get($lbry-blue-list, "3");
+$lbry-blue-4: map-get($lbry-blue-list, "4");
+$lbry-blue-5: map-get($lbry-blue-list, "5");
+
+
+
+// Indigo
+// ───────────────────────────────────
+
+$lbry-indigo-list: (
+ "1": mix($lbry-white, $base-indigo, 40%),
+ "2": mix($lbry-white, $base-indigo, 20%),
+ "3": $base-indigo,
+ "4": mix($lbry-black, $base-indigo, 20%),
+ "5": mix($lbry-black, $base-indigo, 40%)
+);
+
+$lbry-indigo-1: map-get($lbry-indigo-list, "1");
+$lbry-indigo-2: map-get($lbry-indigo-list, "2");
+$lbry-indigo-3: map-get($lbry-indigo-list, "3");
+$lbry-indigo-4: map-get($lbry-indigo-list, "4");
+$lbry-indigo-5: map-get($lbry-indigo-list, "5");
+
+
+
+// Violet
+// ───────────────────────────────────
+
+$lbry-violet-list: (
+ "1": mix($lbry-white, $base-violet, 40%),
+ "2": mix($lbry-white, $base-violet, 20%),
+ "3": $base-violet,
+ "4": mix($lbry-black, $base-violet, 20%),
+ "5": mix($lbry-black, $base-violet, 40%)
+);
+
+$lbry-violet-1: map-get($lbry-violet-list, "1");
+$lbry-violet-2: map-get($lbry-violet-list, "2");
+$lbry-violet-3: map-get($lbry-violet-list, "3");
+$lbry-violet-4: map-get($lbry-violet-list, "4");
+$lbry-violet-5: map-get($lbry-violet-list, "5");
+
+
+
+// Grape
+// ───────────────────────────────────
+
+$lbry-grape-list: (
+ "1": mix($lbry-white, $base-grape, 40%),
+ "2": mix($lbry-white, $base-grape, 20%),
+ "3": $base-grape,
+ "4": mix($lbry-black, $base-grape, 20%),
+ "5": mix($lbry-black, $base-grape, 40%)
+);
+
+$lbry-grape-1: map-get($lbry-grape-list, "1");
+$lbry-grape-2: map-get($lbry-grape-list, "2");
+$lbry-grape-3: map-get($lbry-grape-list, "3");
+$lbry-grape-4: map-get($lbry-grape-list, "4");
+$lbry-grape-5: map-get($lbry-grape-list, "5");
+
+
+
+// Pink
+// ───────────────────────────────────
+
+$lbry-pink-list: (
+ "1": mix($lbry-white, $base-pink, 40%),
+ "2": mix($lbry-white, $base-pink, 20%),
+ "3": $base-pink,
+ "4": mix($lbry-black, $base-pink, 20%),
+ "5": mix($lbry-black, $base-pink, 40%)
+);
+
+$lbry-pink-1: map-get($lbry-pink-list, "1");
+$lbry-pink-2: map-get($lbry-pink-list, "2");
+$lbry-pink-3: map-get($lbry-pink-list, "3");
+$lbry-pink-4: map-get($lbry-pink-list, "4");
+$lbry-pink-5: map-get($lbry-pink-list, "5");
+
+
+
+// Red
+// ───────────────────────────────────
+
+$lbry-red-list: (
+ "1": mix($lbry-white, $base-red, 40%),
+ "2": mix($lbry-white, $base-red, 20%),
+ "3": $base-red,
+ "4": mix($lbry-black, $base-red, 20%),
+ "5": mix($lbry-black, $base-red, 40%)
+);
+
+$lbry-red-1: map-get($lbry-red-list, "1");
+$lbry-red-2: map-get($lbry-red-list, "2");
+$lbry-red-3: map-get($lbry-red-list, "3");
+$lbry-red-4: map-get($lbry-red-list, "4");
+$lbry-red-5: map-get($lbry-red-list, "5");
+
+
+
+// Orange
+// ───────────────────────────────────
+
+$lbry-orange-list: (
+ "1": mix($lbry-white, $base-orange, 40%),
+ "2": mix($lbry-white, $base-orange, 20%),
+ "3": $base-orange,
+ "4": mix($lbry-black, $base-orange, 20%),
+ "5": mix($lbry-black, $base-orange, 40%)
+);
+
+$lbry-orange-1: map-get($lbry-orange-list, "1");
+$lbry-orange-2: map-get($lbry-orange-list, "2");
+$lbry-orange-3: map-get($lbry-orange-list, "3");
+$lbry-orange-4: map-get($lbry-orange-list, "4");
+$lbry-orange-5: map-get($lbry-orange-list, "5");
+
+
+
+// Yellow
+// ───────────────────────────────────
+
+$lbry-yellow-list: (
+ "1": mix($lbry-white, $base-yellow, 40%),
+ "2": mix($lbry-white, $base-yellow, 20%),
+ "3": $base-yellow,
+ "4": mix($lbry-black, $base-yellow, 20%),
+ "5": mix($lbry-black, $base-yellow, 40%)
+);
+
+$lbry-yellow-1: map-get($lbry-yellow-list, "1");
+$lbry-yellow-2: map-get($lbry-yellow-list, "2");
+$lbry-yellow-3: map-get($lbry-yellow-list, "3");
+$lbry-yellow-4: map-get($lbry-yellow-list, "4");
+$lbry-yellow-5: map-get($lbry-yellow-list, "5");
+
+
+
+// Lime
+// ───────────────────────────────────
+
+$lbry-lime-list: (
+ "1": mix($lbry-white, $base-lime, 40%),
+ "2": mix($lbry-white, $base-lime, 20%),
+ "3": $base-lime,
+ "4": mix($lbry-black, $base-lime, 20%),
+ "5": mix($lbry-black, $base-lime, 40%)
+);
+
+$lbry-lime-1: map-get($lbry-lime-list, "1");
+$lbry-lime-2: map-get($lbry-lime-list, "2");
+$lbry-lime-3: map-get($lbry-lime-list, "3");
+$lbry-lime-4: map-get($lbry-lime-list, "4");
+$lbry-lime-5: map-get($lbry-lime-list, "5");
+
+
+
+// Green
+// ───────────────────────────────────
+
+$lbry-green-list: (
+ "1": mix($lbry-white, $base-green, 40%),
+ "2": mix($lbry-white, $base-green, 20%),
+ "3": $base-green,
+ "4": mix($lbry-black, $base-green, 20%),
+ "5": mix($lbry-black, $base-green, 40%)
+);
+
+$lbry-green-1: map-get($lbry-green-list, "1");
+$lbry-green-2: map-get($lbry-green-list, "2");
+$lbry-green-3: map-get($lbry-green-list, "3");
+$lbry-green-4: map-get($lbry-green-list, "4");
+$lbry-green-5: map-get($lbry-green-list, "5");
+
+
+
+// Color list
+// ───────────────────────────────────
+
+$lbry-color-spectrum: 9;
+
+$lbry-color-list: (
+ $lbry-gray-list: "gray",
+ $lbry-teal-list: "teal",
+ $lbry-cyan-list: "cyan",
+ $lbry-blue-list: "blue",
+ $lbry-indigo-list: "indigo",
+ $lbry-violet-list: "violet",
+ $lbry-grape-list: "grape",
+ $lbry-pink-list: "pink",
+ $lbry-red-list: "red",
+ $lbry-orange-list: "orange",
+ $lbry-yellow-list: "yellow",
+ $lbry-lime-list: "lime",
+ $lbry-green-list: "green"
+);
diff --git a/web/components/sass/init/_mixins.scss b/web/components/sass/init/_mixins.scss
new file mode 100644
index 00000000..3287cef1
--- /dev/null
+++ b/web/components/sass/init/_mixins.scss
@@ -0,0 +1,242 @@
+@mixin between {
+ display: flex;
+ justify-content: space-between;
+}
+
+@mixin breakpoint-max($breakpoint) {
+ @media (max-width: #{$breakpoint}px) {
+ @content;
+ }
+}
+
+@mixin breakpoint-min($breakpoint) {
+ @media (min-width: #{$breakpoint}px) {
+ @content;
+ }
+}
+
+@mixin center {
+ align-items: center;
+ display: inline-flex;
+ justify-content: center;
+}
+
+@mixin clearfix {
+ clear: both;
+ content: "";
+ display: block;
+}
+
+// (Smart) text truncation
+// Pass in a width to customize how much text is allowed
+// Omit value for basic text truncation
+@mixin constrict($value: null) {
+ @if ($value) {
+ max-width: $value;
+ }
+
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+@mixin create-grid($items-per-row: 4) {
+ grid-template: repeat(1, 1fr) / repeat($items-per-row, 1fr);
+}
+
+// Smart font include
+// Simply pass in the font-weight you want to use and the normal/italicized versions will be added
+// No more weighing down the front-end with references to unused weights
+@mixin font-face($font-weight, $relative-font-path, $font-name) {
+ @font-face {
+ font-family: $font-name;
+ font-style: normal;
+ font-weight: $font-weight;
+ // sass-lint:disable indentation
+ src: url("#{$relative-font-path}/#{$font-weight}.woff2") format("woff2"),
+ url("#{$relative-font-path}/#{$font-weight}.woff") format("woff");
+ // sass-lint:enable indentation
+ }
+
+ @font-face {
+ font-family: $font-name;
+ font-style: italic;
+ font-weight: $font-weight;
+ // sass-lint:disable indentation
+ src: url("#{$relative-font-path}/#{$font-weight}i.woff2") format("woff2"),
+ url("#{$relative-font-path}/#{$font-weight}i.woff") format("woff");
+ // sass-lint:enable indentation
+ }
+}
+
+@mixin font-mono {
+ font-family: "Fira Code", "Courier New", monospace;
+}
+
+@mixin font-sans {
+ font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+}
+
+@mixin font-serif {
+ font-family: Georgia, serif;
+}
+
+@mixin hide-text {
+ border: none;
+ color: transparent;
+ font: 0 / 0 a;
+ text-shadow: none;
+}
+
+// Cross-browser line-clamp support
+@mixin line-clamp(
+ $element-height: 2rem,
+ $row-count: 2,
+ $fade-color: var(--lbry-white),
+ $computed-position: relative
+) {
+ height: $element-height;
+ overflow: hidden;
+ position: $computed-position;
+
+ &::after {
+ width: 50%; height: calc(#{$element-height} / #{$row-count});
+ right: 0; bottom: 0;
+
+ background-image: linear-gradient(to right, rgba($lbry-white, 0), #{$fade-color} 80%);
+ content: "";
+ position: absolute;
+ }
+}
+
+@mixin no-user-select {
+ user-select: none;
+
+ -ms-user-select: none;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+}
+
+// TODO: Make customizable
+// The `background-position` in `loading-animation` is the same width as this `background-size`
+// The same values can be passed to both
+@mixin placeholder {
+ animation-duration: 4s;
+ animation-fill-mode: forwards;
+ animation-iteration-count: infinite;
+ animation-name: loading-animation;
+ animation-timing-function: linear;
+ background-color: transparent;
+ background-image: linear-gradient(to right, var(--lbry-gray-3) 10%, transparent 80%, var(--lbry-gray-3) 100%);
+ background-repeat: repeat;
+ background-size: 500px;
+
+ [data-mode="dark"] & {
+ background-image: linear-gradient(
+ to right,
+ rgba($lbry-white, 0.1) 10%,
+ transparent 80%,
+ rgba($lbry-white, 0.1) 100%
+ );
+ }
+}
+
+// Use CSS variables without upsetting Sass-Lint
+// https://github.com/sasstools/sass-lint/issues/1161#issuecomment-390537190
+@mixin root-prop($prop: null, $value: null) {
+ @if ($prop and $value) {
+ #{$prop}: $value;
+ }
+}
+
+@mixin selection($background-color: var(--lbry-white), $text-color: var(--lbry-black)) {
+ &::selection {
+ background-color: $background-color;
+ color: $text-color;
+ text-shadow: none;
+ }
+
+ &::-moz-selection {
+ background-color: $background-color;
+ color: $text-color;
+ text-shadow: none;
+ }
+}
+
+@mixin thumbnail {
+ &::before,
+ &::after {
+ content: "";
+ }
+
+ &::before {
+ float: left;
+ padding-top: var(--aspect-ratio-standard);
+ }
+
+ &::after {
+ clear: both;
+ display: block;
+ }
+}
+
+@mixin tick {
+ min-height: 2rem;
+ align-items: center;
+ align-self: start;
+ display: inline-flex;
+ flex-direction: row-reverse;
+ margin-right: var(--spacing-s);
+ margin-bottom: var(--spacing-s);
+ position: relative;
+
+ &:hover {
+ label {
+ color: var(--lbry-teal-4);
+ }
+ }
+
+ label {
+ left: -2rem;
+ padding-left: var(--spacing-l);
+ position: relative;
+ transition: color 0.2s;
+ z-index: 1;
+ }
+}
+
+@mixin tick-toggle {
+ width: var(--spacing-m); height: var(--spacing-m);
+ top: 0; left: 0;
+
+ border: 2px solid;
+ display: block;
+ position: relative;
+ transition: border 0.2s;
+
+ &::before {
+ width: 100%; height: 100%;
+ top: 0; left: 0;
+
+ content: "";
+ display: block;
+ position: absolute;
+ }
+}
+
+@mixin underline($text-color: var(--lbry-black), $whitespace-color: var(--lbry-white)) {
+ @include selection($text-color, $whitespace-color);
+
+ background-image: linear-gradient($whitespace-color, $whitespace-color), linear-gradient($whitespace-color, $whitespace-color), linear-gradient($text-color, $text-color);
+ background-position: 0 88%, 100% 88%, 0 88%;
+ background-repeat: no-repeat, no-repeat, repeat-x;
+ background-size: 0.05rem 1px, 0.05rem 1px, 1px 1px;
+ box-decoration-break: clone;
+ display: inline;
+ text-decoration: none;
+ text-shadow: 0.03rem 0 $whitespace-color, -0.03rem 0 $whitespace-color, 0 0.03rem $whitespace-color, 0 -0.03rem $whitespace-color, 0.06rem 0 $whitespace-color, -0.06rem 0 $whitespace-color, 0.09rem 0 $whitespace-color, -0.09rem 0 $whitespace-color, 0.12rem 0 $whitespace-color, -0.12rem 0 $whitespace-color, 0.15rem 0 $whitespace-color, -0.15rem 0 $whitespace-color;
+
+ @-moz-document url-prefix() { // sass-lint:disable-line empty-args
+ background-position: 0 90%, 100% 90%, 0 90%;
+ }
+}
diff --git a/web/components/sass/init/_reset.scss b/web/components/sass/init/_reset.scss
new file mode 100644
index 00000000..c1402b44
--- /dev/null
+++ b/web/components/sass/init/_reset.scss
@@ -0,0 +1,261 @@
+html {
+ box-sizing: border-box;
+ font-size: 12px;
+ text-rendering: optimizeLegibility;
+
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+}
+
+*,
+*::before,
+*::after {
+ margin: 0; padding: 0;
+
+ border: none;
+ box-sizing: inherit;
+ outline: 0;
+}
+
+[disabled] {
+ pointer-events: none;
+ resize: none;
+
+ &:hover {
+ background-color: inherit;
+ border-color: inherit;
+ color: inherit;
+ fill: inherit;
+ }
+}
+
+[readonly] {
+ cursor: not-allowed;
+}
+
+[for],
+[role="button"],
+[type="button"],
+[type="checkbox"],
+[type="file"],
+[type="radio"],
+[type="select"],
+[type="submit"] {
+ cursor: pointer;
+}
+
+a,
+area,
+button,
+[role="button"],
+input,
+label,
+select,
+summary,
+textarea {
+ // Remove touch delay on supported devices
+ touch-action: manipulation;
+}
+
+button,
+input,
+select,
+textarea {
+ background-color: transparent;
+ border-radius: 0;
+ font-family: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+
+ -moz-appearance: none;
+ -webkit-appearance: none;
+
+ &:disabled {
+ // sass-lint:disable-block no-important
+ border-color: var(--lbry-gray-4);
+ color: var(--lbry-gray-4);
+ cursor: default;
+ }
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-weight: normal;
+}
+
+ol,
+ul {
+ list-style-position: inside;
+
+ > li {
+ list-style-position: inside;
+ }
+}
+
+ul {
+ list-style-type: none;
+}
+
+table {
+ border-spacing: 0;
+}
+
+dd {
+ width: 80%;
+ float: left;
+}
+
+dl {
+ width: 100%;
+ overflow-x: scroll;
+ overflow-y: hidden;
+}
+
+dt {
+ width: 20%;
+ float: left;
+}
+
+img {
+ width: auto; max-width: 100%;
+ height: auto; max-height: 100%;
+ vertical-align: middle;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+button {
+ background-color: transparent;
+ color: inherit;
+
+ &:not(:disabled) {
+ cursor: pointer;
+ }
+
+ &:disabled {
+ opacity: 0.3;
+ }
+}
+
+hr {
+ width: 100%; height: 1px;
+ background-color: var(--lbry-gray-1);
+}
+
+input {
+ background-color: transparent;
+ color: inherit;
+
+ &::placeholder {
+ color: inherit;
+ opacity: 0.2;
+ }
+
+ // &:not(:disabled) {
+ // color: inherit;
+ // }
+
+ &::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+ }
+}
+
+select {
+ outline: none;
+}
+
+textarea {
+ width: 100%;
+ min-height: var(--spacing-xxl);
+ padding: var(--spacing-s);
+ // border-color should be added in apps for blur/focus
+ border: 1px solid;
+
+ &:not([disabled]) {
+ resize: vertical;
+ }
+}
+
+
+
+@media print {
+ // sass-lint:disable-block no-important
+ // Intelligent print styles
+ pre,
+ blockquote {
+ border: 1px solid var(--lbry-gray-5) !important;
+ page-break-inside: avoid !important;
+ }
+
+ tr,
+ img {
+ page-break-inside: avoid !important;
+ }
+
+ img {
+ max-width: 100% !important;
+ }
+
+ @page {
+ margin: 0.5cm !important;
+ }
+
+ p,
+ h2,
+ h3 {
+ orphans: 3 !important;
+ widows: 3 !important;
+ }
+
+ h2,
+ h3 {
+ page-break-after: avoid !important;
+ }
+
+ thead {
+ display: table-header-group !important;
+ }
+
+ // Faster, more stable printing
+ * {
+ background-color: transparent !important;
+ background-image: none !important;
+ color: var(--lbry-black) !important;
+ filter: none !important;
+ text-shadow: none !important;
+ }
+
+ p {
+ a {
+ &[href]::after { // Show hypertext data for links and abbreviations
+ content: " (" attr(href) ")" !important;
+ }
+
+ &[href^="javascript:"],
+ &[href^="#"] {
+ &::after {
+ content: "" !important;
+ }
+ }
+ }
+
+ abbr {
+ &[title]::after {
+ content: " (" attr(title) ")" !important;
+ }
+ }
+
+ a,
+ abbr {
+ text-decoration: underline !important;
+ word-wrap: break-word !important;
+ }
+ }
+}
diff --git a/web/components/sass/init/_variables.scss b/web/components/sass/init/_variables.scss
new file mode 100644
index 00000000..66418e3d
--- /dev/null
+++ b/web/components/sass/init/_variables.scss
@@ -0,0 +1,103 @@
+:root {
+ // Spacing
+ @include root-prop(--spacing-xxs, 0.2rem);
+ @include root-prop(--spacing-xs, 0.4rem);
+ @include root-prop(--spacing-s, 0.8rem);
+ @include root-prop(--spacing-m, 1.6rem);
+ @include root-prop(--spacing-l, 2.4rem);
+ @include root-prop(--spacing-xl, 3.2rem);
+ @include root-prop(--spacing-xxl, 6.4rem);
+
+ // Aspect ratio
+ @include root-prop(--aspect-ratio-bluray, 41.6666666667%); // 12:5
+ @include root-prop(--aspect-ratio-panavision, 36.3636363636%); // 11:4
+ @include root-prop(--aspect-ratio-sd, 75%); // 4:3
+ @include root-prop(--aspect-ratio-standard, 56.25%); // 16:9
+
+ // Color
+ @include root-prop(--lbry-black, $lbry-black);
+ @include root-prop(--lbry-white, $lbry-white);
+
+ @include root-prop(--lbry-gray-1, $lbry-gray-1);
+ @include root-prop(--lbry-gray-2, $lbry-gray-2);
+ @include root-prop(--lbry-gray-3, $lbry-gray-3);
+ @include root-prop(--lbry-gray-4, $lbry-gray-4);
+ @include root-prop(--lbry-gray-5, $lbry-gray-5);
+
+ @include root-prop(--lbry-teal-1, $lbry-teal-1);
+ @include root-prop(--lbry-teal-2, $lbry-teal-2);
+ @include root-prop(--lbry-teal-3, $lbry-teal-3);
+ @include root-prop(--lbry-teal-4, $lbry-teal-4);
+ @include root-prop(--lbry-teal-5, $lbry-teal-5);
+
+ @include root-prop(--lbry-cyan-1, $lbry-cyan-1);
+ @include root-prop(--lbry-cyan-2, $lbry-cyan-2);
+ @include root-prop(--lbry-cyan-3, $lbry-cyan-3);
+ @include root-prop(--lbry-cyan-4, $lbry-cyan-4);
+ @include root-prop(--lbry-cyan-5, $lbry-cyan-5);
+
+ @include root-prop(--lbry-blue-1, $lbry-blue-1);
+ @include root-prop(--lbry-blue-2, $lbry-blue-2);
+ @include root-prop(--lbry-blue-3, $lbry-blue-3);
+ @include root-prop(--lbry-blue-4, $lbry-blue-4);
+ @include root-prop(--lbry-blue-5, $lbry-blue-5);
+
+ @include root-prop(--lbry-indigo-1, $lbry-indigo-1);
+ @include root-prop(--lbry-indigo-2, $lbry-indigo-2);
+ @include root-prop(--lbry-indigo-3, $lbry-indigo-3);
+ @include root-prop(--lbry-indigo-4, $lbry-indigo-4);
+ @include root-prop(--lbry-indigo-5, $lbry-indigo-5);
+
+ @include root-prop(--lbry-violet-1, $lbry-violet-1);
+ @include root-prop(--lbry-violet-2, $lbry-violet-2);
+ @include root-prop(--lbry-violet-3, $lbry-violet-3);
+ @include root-prop(--lbry-violet-4, $lbry-violet-4);
+ @include root-prop(--lbry-violet-5, $lbry-violet-5);
+
+ @include root-prop(--lbry-grape-1, $lbry-grape-1);
+ @include root-prop(--lbry-grape-2, $lbry-grape-2);
+ @include root-prop(--lbry-grape-3, $lbry-grape-3);
+ @include root-prop(--lbry-grape-4, $lbry-grape-4);
+ @include root-prop(--lbry-grape-5, $lbry-grape-5);
+
+ @include root-prop(--lbry-pink-1, $lbry-pink-1);
+ @include root-prop(--lbry-pink-2, $lbry-pink-2);
+ @include root-prop(--lbry-pink-3, $lbry-pink-3);
+ @include root-prop(--lbry-pink-4, $lbry-pink-4);
+ @include root-prop(--lbry-pink-5, $lbry-pink-5);
+
+ @include root-prop(--lbry-red-1, $lbry-red-1);
+ @include root-prop(--lbry-red-2, $lbry-red-2);
+ @include root-prop(--lbry-red-3, $lbry-red-3);
+ @include root-prop(--lbry-red-4, $lbry-red-4);
+ @include root-prop(--lbry-red-5, $lbry-red-5);
+
+ @include root-prop(--lbry-orange-1, $lbry-orange-1);
+ @include root-prop(--lbry-orange-2, $lbry-orange-2);
+ @include root-prop(--lbry-orange-3, $lbry-orange-3);
+ @include root-prop(--lbry-orange-4, $lbry-orange-4);
+ @include root-prop(--lbry-orange-5, $lbry-orange-5);
+
+ @include root-prop(--lbry-yellow-1, $lbry-yellow-1);
+ @include root-prop(--lbry-yellow-2, $lbry-yellow-2);
+ @include root-prop(--lbry-yellow-3, $lbry-yellow-3);
+ @include root-prop(--lbry-yellow-4, $lbry-yellow-4);
+ @include root-prop(--lbry-yellow-5, $lbry-yellow-5);
+
+ @include root-prop(--lbry-lime-1, $lbry-lime-1);
+ @include root-prop(--lbry-lime-2, $lbry-lime-2);
+ @include root-prop(--lbry-lime-3, $lbry-lime-3);
+ @include root-prop(--lbry-lime-4, $lbry-lime-4);
+ @include root-prop(--lbry-lime-5, $lbry-lime-5);
+
+ @include root-prop(--lbry-green-1, $lbry-green-1);
+ @include root-prop(--lbry-green-2, $lbry-green-2);
+ @include root-prop(--lbry-green-3, $lbry-green-3);
+ @include root-prop(--lbry-green-4, $lbry-green-4);
+ @include root-prop(--lbry-green-5, $lbry-green-5);
+
+ // Type
+ @include root-prop(--font-mono, "Fira Code");
+ @include root-prop(--font-sans, Inter);
+ @include root-prop(--font-serif, Georgia);
+}
diff --git a/web/components/sass/media/_index.scss b/web/components/sass/media/_index.scss
new file mode 100644
index 00000000..340f24b5
--- /dev/null
+++ b/web/components/sass/media/_index.scss
@@ -0,0 +1,192 @@
+// C O N T A I N E R S
+
+.media-grid {
+ display: grid;
+
+ // set your own grid-gap
+ // example → grid-gap: var(--spacing-s);
+
+ // set your own grid-template
+ // example → @include create-grid(4);
+
+ .media {
+ width: 100%;
+ cursor: pointer;
+ vertical-align: top;
+ }
+}
+
+.media-list { // sass-lint:disable-line no-empty-rulesets
+}
+
+.media-row { // sass-lint:disable-line no-empty-rulesets
+}
+
+
+
+.media {
+ position: relative;
+}
+
+
+
+// M O D I F I E R S
+
+.media--large {
+ display: flex;
+
+ .media__info {
+ width: calc(100% - 20rem);
+ margin-left: var(--spacing-s);
+ }
+
+ .media__thumb {
+ width: 40rem;
+ margin-bottom: var(--spacing-s);
+ }
+}
+
+.media--placeholder {
+ > * {
+ @include placeholder;
+ }
+
+ .media__channel,
+ .media__date,
+ .media__title {
+ min-height: 1rem;
+ }
+
+ .media__channel {
+ width: 70%;
+ }
+
+ .media__date {
+ width: 30%;
+ }
+
+ .media__title {
+ width: 100%;
+ }
+}
+
+.media--small {
+ display: flex;
+ justify-content: space-between;
+
+ &:not(:last-of-type) {
+ margin-bottom: var(--spacing-s);
+ }
+
+ .media__info {
+ width: 50%;
+ padding-left: var(--spacing-s);
+ }
+
+ .media__properties {
+ bottom: -1.5rem; left: calc(-100% - 1.5rem);
+ position: absolute;
+ }
+
+ .media__thumb {
+ width: 50%;
+ }
+
+ .media__title {
+ height: 3rem;
+ line-height: 1.33;
+ }
+}
+
+.media--wide { // .media--search-result
+ display: flex;
+
+ &:not(:last-of-type) {
+ margin-bottom: var(--spacing-m);
+ }
+
+ .media__info {
+ width: calc(100% - 20rem);
+ margin-left: var(--spacing-s);
+ }
+
+ .media__properties {
+ bottom: -5.5rem; left: -20rem;
+ position: absolute;
+ }
+
+ .media__text { // .media__subtext
+ padding-top: var(--spacing-s);
+ }
+
+ .media__thumb {
+ width: 20rem;
+ }
+
+ .media__title {
+ font-size: 1.5rem;
+ }
+}
+
+
+
+// C H I L D R E N
+
+.media__actions {
+ display: flex;
+ padding-top: var(--spacing-m);
+ padding-bottom: var(--spacing-m);
+}
+
+.media__content { // sass-lint:disable-line no-empty-rulesets
+}
+
+.media__date { // sass-lint:disable-line no-empty-rulesets
+}
+
+.media__info {
+ word-wrap: break-word;
+
+ [data-mode="dark"] & {
+ border-color: rgba($lbry-gray-5, 0.2);
+ }
+}
+
+.media__message {
+ padding: var(--spacing-s);
+ white-space: normal;
+}
+
+.media__properties {
+ vertical-align: top;
+
+ > *:not(:last-child) {
+ margin-right: var(--spacing-xs);
+ }
+
+ &:not(:empty) {
+ display: inline-block;
+ }
+}
+
+.media__property { // sass-lint:disable-line no-empty-rulesets
+}
+
+.media__subtitle {
+ position: relative;
+}
+
+.media__text {
+ [data-mode="dark"] & {
+ color: var(--lbry-white);
+ }
+}
+
+.media__thumb {
+ @include thumbnail;
+}
+
+.media__title {
+ font-weight: 600;
+ white-space: normal;
+}
diff --git a/web/components/sass/navigation/_index.scss b/web/components/sass/navigation/_index.scss
new file mode 100644
index 00000000..1a6fac22
--- /dev/null
+++ b/web/components/sass/navigation/_index.scss
@@ -0,0 +1,126 @@
+drawer-navigation {
+ width: 100%; height: 5rem;
+
+ display: inline-flex;
+ flex: 1;
+ font-size: inherit;
+ justify-content: center;
+ position: relative;
+ z-index: 10;
+}
+
+drawer-navigation-helper { // This is to make mouse movement forgiving
+ width: 0; height: 0;
+ top: 3rem; left: -5rem;
+
+ border-right: 8rem solid transparent;
+ border-bottom: 5rem solid transparent;
+ border-left: 8rem solid transparent;
+ position: absolute;
+}
+
+drawer-section {
+ padding-right: 1rem;
+ padding-left: 1rem;
+
+ &:not(:hover):not(.active) {
+ drawer-title::after {
+ background-color: transparent;
+ }
+
+ drawer-navigation-helper,
+ drawer-wrap {
+ display: none;
+ }
+ }
+
+ &:hover,
+ &.active {
+ z-index: 3;
+
+ drawer-title::after {
+ background-color: var(--lbry-teal-4);
+ }
+ }
+}
+
+drawer-title {
+ height: 100%;
+ align-items: center;
+ cursor: default;
+ display: flex;
+ line-height: 3;
+ position: relative;
+ z-index: 1;
+
+ &::after {
+ width: 100%; height: 1px;
+ bottom: -1px; left: 0;
+
+ content: "";
+ position: absolute;
+ z-index: 1;
+ }
+}
+
+drawer-wrap {
+ width: 100%;
+ top: 5rem; left: 0;
+
+ background-color: var(--lbry-white);
+ border-top: 1px solid var(--lbry-gray-1);
+ padding-top: 2rem;
+ padding-bottom: 2rem;
+ position: absolute;
+
+ &::after {
+ width: 100vw; height: calc(100vh - 5rem);
+ top: 5rem; left: 0;
+
+ background-color: var(--lbry-black);
+ content: "";
+ opacity: 0.3;
+ pointer-events: none;
+ position: absolute;
+ z-index: -1;
+ }
+}
+
+drawer-children {
+ display: flex;
+ flex-wrap: wrap;
+ position: relative;
+}
+
+drawer-child {
+ padding: var(--spacing-s);
+ border: 2px solid;
+ transition: all 0.2s;
+
+ &:not(:hover) {
+ border-color: transparent;
+ }
+
+ &:hover {
+ border-color: var(--lbry-gray-1);
+ padding-left: var(--spacing-m);
+
+ > a {
+ color: var(--lbry-teal-4);
+ }
+ }
+
+ &:not([full-width]) {
+ width: 50%;
+ }
+
+ &[full-width] {
+ width: 100%;
+ }
+
+ span {
+ display: flex;
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
+ }
+}
diff --git a/web/components/sass/table/_index.scss b/web/components/sass/table/_index.scss
new file mode 100644
index 00000000..cfbbe250
--- /dev/null
+++ b/web/components/sass/table/_index.scss
@@ -0,0 +1,45 @@
+
+table {
+ width: 100%;
+ background-color: var(--lbry-white);
+ position: relative;
+
+ thead {
+ cursor: default;
+ font-size: 80%;
+ position: relative;
+
+ tr {
+ position: relative;
+ z-index: 1;
+ }
+ }
+
+ tbody {
+ line-height: 1.55;
+ }
+
+ tr {
+ &:not(:last-of-type) {
+ td {
+ border-bottom: 1px solid var(--lbry-gray-1);
+ }
+ }
+ }
+
+ th,
+ td {
+ padding: 0.5rem 1rem;
+ }
+
+ th {
+ border-bottom: 2px solid var(--lbry-black);
+ letter-spacing: 0.1rem;
+ text-align: left;
+ text-transform: uppercase;
+ }
+
+ a {
+ font-weight: bolder;
+ }
+}