diff --git a/package.json b/package.json
index ddd058ae..beea8443 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,8 @@
     "@testing-library/jest-dom": "^5.11.4",
     "@testing-library/react": "^11.1.0",
     "@testing-library/user-event": "^12.1.10",
+    "@tippy.js/react": "^3.1.1",
+    "@types/react-router-dom": "^5.1.7",
     "autosize": "^4.0.2",
     "axios": "^0.21.1",
     "body-scroll-lock": "^2.6.4",
@@ -29,6 +31,7 @@
     "react-router-dom": "^5.1.2",
     "react-scripts": "3.4.4",
     "react-sortable-hoc": "^1.11",
+    "react-sticky-box": "^0.9.3",
     "redux": "^4.0.1",
     "redux-persist": "^5.10.0",
     "redux-saga": "^1.1.1",
@@ -71,8 +74,8 @@
     "@types/node": "^11.13.22",
     "@types/ramda": "^0.26.33",
     "@types/react-redux": "^7.1.11",
-    "@types/yup": "^0.29.11",
     "@types/swiper": "^5.4.2",
+    "@types/yup": "^0.29.11",
     "craco-alias": "^2.1.1",
     "craco-fast-refresh": "^1.0.2",
     "prettier": "^1.18.2"
diff --git a/src/components/boris/BorisComments/index.tsx b/src/components/boris/BorisComments/index.tsx
new file mode 100644
index 00000000..b4350c44
--- /dev/null
+++ b/src/components/boris/BorisComments/index.tsx
@@ -0,0 +1,40 @@
+import React, { FC } from 'react';
+import styles from './styles.module.scss';
+import { Group } from '~/components/containers/Group';
+import { NodeCommentForm } from '~/components/node/NodeCommentForm';
+import { NodeNoComments } from '~/components/node/NodeNoComments';
+import { NodeComments } from '~/components/node/NodeComments';
+import { Footer } from '~/components/main/Footer';
+import { Card } from '~/components/containers/Card';
+import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
+import { selectAuthUser } from '~/redux/auth/selectors';
+import { IComment, INode } from '~/redux/types';
+
+interface IProps {
+  isLoadingComments: boolean;
+  commentCount: number;
+  node: INode;
+  comments: IComment[];
+}
+
+const BorisComments: FC<IProps> = ({ isLoadingComments, node, commentCount, comments }) => {
+  const user = useShallowSelect(selectAuthUser);
+
+  return (
+    <>
+      <Group className={styles.grid}>
+        {user.is_user && <NodeCommentForm isBefore nodeId={node.id} />}
+
+        {isLoadingComments ? (
+          <NodeNoComments is_loading count={7} />
+        ) : (
+          <NodeComments comments={comments} count={commentCount} user={user} order="ASC" />
+        )}
+      </Group>
+
+      <Footer />
+    </>
+  );
+};
+
+export { BorisComments };
diff --git a/src/components/boris/BorisComments/styles.module.scss b/src/components/boris/BorisComments/styles.module.scss
new file mode 100644
index 00000000..7662aac0
--- /dev/null
+++ b/src/components/boris/BorisComments/styles.module.scss
@@ -0,0 +1,18 @@
+@import "~/styles/variables.scss";
+
+.content {
+  flex: 4;
+  z-index: 2;
+  border-radius: $radius;
+  padding: $gap;
+  background: $node_bg;
+  box-shadow: inset transparentize(mix($wisegreen, white, 60%), 0.6) 0 1px;
+
+  @include desktop {
+    flex: 2.5;
+  }
+
+  @media(max-width: 1024px) {
+    flex: 2;
+  }
+}
diff --git a/src/components/boris/BorisStatsGit/index.tsx b/src/components/boris/BorisStatsGit/index.tsx
index d81ac721..030699a3 100644
--- a/src/components/boris/BorisStatsGit/index.tsx
+++ b/src/components/boris/BorisStatsGit/index.tsx
@@ -1,4 +1,4 @@
-import React, { FC } from 'react';
+import React, { FC, useMemo } from 'react';
 import { IBorisState } from '~/redux/boris/reducer';
 import styles from './styles.module.scss';
 import { Placeholder } from '~/components/placeholders/Placeholder';
@@ -9,7 +9,17 @@ interface IProps {
 }
 
 const BorisStatsGit: FC<IProps> = ({ stats }) => {
-  if (!stats.git.length) return null;
+  if (!stats.issues.length) return null;
+
+  const open = useMemo(
+    () => stats.issues.filter(el => !el.pull_request && el.state === 'open').slice(0, 5),
+    [stats.issues]
+  );
+
+  const closed = useMemo(
+    () => stats.issues.filter(el => !el.pull_request && el.state === 'closed').slice(0, 5),
+    [stats.issues]
+  );
 
   if (stats.is_loading) {
     return (
@@ -35,12 +45,13 @@ const BorisStatsGit: FC<IProps> = ({ stats }) => {
         <img src="https://jenkins.vault48.org/api/badges/muerwre/vault-golang/status.svg" />
       </div>
 
-      {stats.git
-        .filter(data => data.commit && data.timestamp && data.subject)
-        .slice(0, 5)
-        .map(data => (
-          <BorisStatsGitCard data={data} key={data.commit} />
-        ))}
+      {open.map(data => (
+        <BorisStatsGitCard data={data} key={data.id} />
+      ))}
+
+      {closed.map(data => (
+        <BorisStatsGitCard data={data} key={data.id} />
+      ))}
     </div>
   );
 };
diff --git a/src/components/boris/BorisStatsGitCard/index.tsx b/src/components/boris/BorisStatsGitCard/index.tsx
index f393b710..20f4d469 100644
--- a/src/components/boris/BorisStatsGitCard/index.tsx
+++ b/src/components/boris/BorisStatsGitCard/index.tsx
@@ -1,22 +1,33 @@
-import React, { FC } from 'react';
-import { IStatGitRow } from '~/redux/boris/reducer';
+import React, { FC, useMemo } from 'react';
 import styles from './styles.module.scss';
 import { getPrettyDate } from '~/utils/dom';
+import { IGithubIssue } from '~/redux/boris/types';
+import classNames from 'classnames';
 
 interface IProps {
-  data: Partial<IStatGitRow>;
+  data: IGithubIssue;
 }
 
-const BorisStatsGitCard: FC<IProps> = ({ data: { timestamp, subject } }) => {
-  if (!subject || !timestamp) return null;
+const stateLabels: Record<IGithubIssue['state'], string> = {
+  open: 'Ожидает',
+  closed: 'Сделано',
+};
+
+const BorisStatsGitCard: FC<IProps> = ({ data: { created_at, title, html_url, state } }) => {
+  if (!title || !created_at) return null;
+
+  const date = useMemo(() => getPrettyDate(created_at), [created_at]);
 
   return (
     <div className={styles.wrap}>
       <div className={styles.time}>
-        {getPrettyDate(new Date(parseInt(`${timestamp}000`)).toISOString())}
+        <span className={classNames(styles.icon, styles[state])}>{stateLabels[state]}</span>
+        {date}
       </div>
 
-      <div className={styles.subject}>{subject}</div>
+      <a className={styles.subject} href={html_url} target="_blank">
+        {title}
+      </a>
     </div>
   );
 };
diff --git a/src/components/boris/BorisStatsGitCard/styles.module.scss b/src/components/boris/BorisStatsGitCard/styles.module.scss
index 37bd0b23..eaad031a 100644
--- a/src/components/boris/BorisStatsGitCard/styles.module.scss
+++ b/src/components/boris/BorisStatsGitCard/styles.module.scss
@@ -12,10 +12,28 @@
 .time {
   font: $font_12_regular;
   line-height: 17px;
-  opacity: 0.3;
+  color: transparentize(white, 0.7)
 }
 
 .subject {
   font: $font_14_regular;
   word-break: break-word;
+  text-decoration: none;
+  color: inherit;
+}
+
+.icon {
+  font: $font_10_semibold;
+  margin-right: 5px;
+  border-radius: 2px;
+  padding: 2px 0;
+  text-transform: uppercase;
+
+  &.open {
+    color: $red;
+  }
+
+  &.closed {
+    color: $green;
+  }
 }
diff --git a/src/components/boris/BorisSuperpowers/index.tsx b/src/components/boris/BorisSuperpowers/index.tsx
new file mode 100644
index 00000000..cf2530cb
--- /dev/null
+++ b/src/components/boris/BorisSuperpowers/index.tsx
@@ -0,0 +1,37 @@
+import React, { FC, useCallback } from 'react';
+import styles from './styles.module.scss';
+import { Toggle } from '~/components/input/Toggle';
+
+interface IProps {
+  active?: boolean;
+  onChange?: (val: boolean) => void;
+}
+
+const BorisSuperpowers: FC<IProps> = ({ active, onChange }) => {
+  const onToggle = useCallback(() => {
+    if (!onChange) {
+      return;
+    }
+
+    onChange(!active);
+  }, [onChange, active]);
+
+  return (
+    <div className={styles.wrap}>
+      <div className={styles.toggle}>
+        <Toggle value={active} handler={onChange} color="primary" />
+      </div>
+
+      <div className={styles.left} onClick={onToggle}>
+        <div className={styles.title}>Суперспособности</div>
+        {active ? (
+          <div className={styles.subtitle}>Ты видишь всё, что скрыто</div>
+        ) : (
+          <div className={styles.subtitle}>Включи, чтобы видеть будущее</div>
+        )}
+      </div>
+    </div>
+  );
+};
+
+export { BorisSuperpowers };
diff --git a/src/components/boris/BorisSuperpowers/styles.module.scss b/src/components/boris/BorisSuperpowers/styles.module.scss
new file mode 100644
index 00000000..473572e7
--- /dev/null
+++ b/src/components/boris/BorisSuperpowers/styles.module.scss
@@ -0,0 +1,20 @@
+@import "~/styles/variables";
+
+.wrap {
+  display: grid;
+  grid-template-columns: auto 1fr;
+  column-gap: $gap;
+  align-items: center;
+  cursor: pointer;
+}
+
+.title {
+  font: $font_14_semibold;
+  color: white;
+  text-transform: uppercase;
+}
+
+.subtitle {
+  font: $font_12_regular;
+  color: transparentize(white, 0.5);
+}
diff --git a/src/components/boris/BorisUIDemo/index.tsx b/src/components/boris/BorisUIDemo/index.tsx
new file mode 100644
index 00000000..bba0c86a
--- /dev/null
+++ b/src/components/boris/BorisUIDemo/index.tsx
@@ -0,0 +1,51 @@
+import React, { FC } from 'react';
+import { Card } from '~/components/containers/Card';
+import styles from './styles.module.scss';
+import markdown from '~/styles/common/markdown.module.scss';
+import { Group } from '~/components/containers/Group';
+import { Button } from '~/components/input/Button';
+
+interface IProps {}
+
+const BorisUIDemo: FC<IProps> = () => (
+  <Card className={styles.card}>
+    <div className={markdown.wrapper}>
+      <h1>UI</h1>
+      <p>
+        Простая демонстрация элементов интерфейса. Используется, в основном, как подсказка при
+        разработке
+      </p>
+
+      <h2>Кнопки</h2>
+
+      <h4>Цвета</h4>
+
+      <Group horizontal className={styles.sample}>
+        <Button>Primary</Button>
+        <Button color="secondary">Secondary</Button>
+        <Button color="outline">Outline</Button>
+        <Button color="gray">Gray</Button>
+        <Button color="link">Link</Button>
+      </Group>
+
+      <h4>Размеры</h4>
+
+      <Group horizontal className={styles.sample}>
+        <Button size="micro">Micro</Button>
+        <Button size="mini">Mini</Button>
+        <Button size="normal">Normal</Button>
+        <Button size="big">Big</Button>
+        <Button size="giant">Giant</Button>
+      </Group>
+
+      <h4>Варианты</h4>
+      <Group horizontal className={styles.sample}>
+        <Button iconRight="check">iconRight</Button>
+        <Button iconLeft="send">iconLeft</Button>
+        <Button round>Round</Button>
+      </Group>
+    </div>
+  </Card>
+);
+
+export { BorisUIDemo };
diff --git a/src/components/boris/BorisUIDemo/styles.module.scss b/src/components/boris/BorisUIDemo/styles.module.scss
new file mode 100644
index 00000000..4e04d429
--- /dev/null
+++ b/src/components/boris/BorisUIDemo/styles.module.scss
@@ -0,0 +1,14 @@
+@import "~/styles/variables.scss";
+
+.card {
+  flex: 3;
+  align-self: stretch;
+  position: relative;
+  z-index: 1;
+  padding: 20px 30px;
+  background-color: lighten($content_bg, 4%);
+}
+
+.sample {
+  flex-wrap: wrap;
+}
diff --git a/src/components/boris/Superpower/index.tsx b/src/components/boris/Superpower/index.tsx
new file mode 100644
index 00000000..01d08f75
--- /dev/null
+++ b/src/components/boris/Superpower/index.tsx
@@ -0,0 +1,16 @@
+import React, { FC, memo } from 'react';
+import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
+import { selectAuthIsTester, selectUser } from '~/redux/auth/selectors';
+
+interface IProps {}
+
+const Superpower: FC<IProps> = ({ children }) => {
+  const user = useShallowSelect(selectUser);
+  const is_tester = useShallowSelect(selectAuthIsTester);
+
+  if (!user.is_user || !is_tester) return null;
+
+  return <>{children}</>;
+};
+
+export { Superpower };
diff --git a/src/components/comment/CommentEmbedBlock/index.tsx b/src/components/comment/CommentEmbedBlock/index.tsx
index 77a374d2..e07b7ecf 100644
--- a/src/components/comment/CommentEmbedBlock/index.tsx
+++ b/src/components/comment/CommentEmbedBlock/index.tsx
@@ -30,6 +30,8 @@ const CommentEmbedBlockUnconnected: FC<Props> = memo(
       return (match && match[1]) || '';
     }, [block.content]);
 
+    const url = useMemo(() => `https://youtube.com/watch?v=${id}`, [id]);
+
     const preview = useMemo(() => getYoutubeThumb(block.content), [block.content]);
 
     useEffect(() => {
@@ -47,7 +49,7 @@ const CommentEmbedBlockUnconnected: FC<Props> = memo(
 
     return (
       <div className={styles.embed}>
-        <a href={id[0]} target="_blank" />
+        <a href={url} target="_blank" />
 
         <div className={styles.preview}>
           <div style={{ backgroundImage: `url("${preview}")` }}>
diff --git a/src/components/comment/CommentForm/index.tsx b/src/components/comment/CommentForm/index.tsx
index b644509d..d1a9337e 100644
--- a/src/components/comment/CommentForm/index.tsx
+++ b/src/components/comment/CommentForm/index.tsx
@@ -14,7 +14,7 @@ import { EMPTY_COMMENT } from '~/redux/node/constants';
 import { CommentFormDropzone } from '~/components/comment/CommentFormDropzone';
 import styles from './styles.module.scss';
 import { ERROR_LITERAL } from '~/constants/errors';
-import { Group } from '~/components/containers/Group';
+import { useInputPasteUpload } from '~/utils/hooks/useInputPasteUpload';
 
 interface IProps {
   comment?: IComment;
@@ -47,6 +47,7 @@ const CommentForm: FC<IProps> = ({ comment, nodeId, onCancelEdit }) => {
   }, [formik]);
 
   const error = formik.status || formik.errors.text;
+  useInputPasteUpload(textarea, uploader.uploadFiles);
 
   return (
     <CommentFormDropzone onUpload={uploader.uploadFiles}>
@@ -65,34 +66,40 @@ const CommentForm: FC<IProps> = ({ comment, nodeId, onCancelEdit }) => {
 
             <CommentFormAttaches />
 
-            <Group horizontal className={styles.buttons}>
-              <CommentFormAttachButtons onUpload={uploader.uploadFiles} />
+            <div className={styles.buttons}>
+              <div className={styles.buttons_attach}>
+                <CommentFormAttachButtons onUpload={uploader.uploadFiles} />
+              </div>
 
-              {!!textarea && (
-                <CommentFormFormatButtons
-                  element={textarea}
-                  handler={formik.handleChange('text')}
-                />
-              )}
+              <div className={styles.buttons_format}>
+                {!!textarea && (
+                  <CommentFormFormatButtons
+                    element={textarea}
+                    handler={formik.handleChange('text')}
+                  />
+                )}
+              </div>
 
-              {isLoading && <LoaderCircle size={20} />}
+              <div className={styles.buttons_submit}>
+                {isLoading && <LoaderCircle size={20} />}
 
-              {isEditing && (
-                <Button size="small" color="link" type="button" onClick={onCancelEdit}>
-                  Отмена
+                {isEditing && (
+                  <Button size="small" color="link" type="button" onClick={onCancelEdit}>
+                    Отмена
+                  </Button>
+                )}
+
+                <Button
+                  type="submit"
+                  size="small"
+                  color="gray"
+                  iconRight={!isEditing ? 'enter' : 'check'}
+                  disabled={isLoading}
+                >
+                  {!isEditing ? 'Сказать' : 'Сохранить'}
                 </Button>
-              )}
-
-              <Button
-                type="submit"
-                size="small"
-                color="gray"
-                iconRight={!isEditing ? 'enter' : 'check'}
-                disabled={isLoading}
-              >
-                {!isEditing ? 'Сказать' : 'Сохранить'}
-              </Button>
-            </Group>
+              </div>
+            </div>
           </FileUploaderProvider>
         </FormikProvider>
       </form>
diff --git a/src/components/comment/CommentForm/styles.module.scss b/src/components/comment/CommentForm/styles.module.scss
index 12798bf2..fb629c05 100644
--- a/src/components/comment/CommentForm/styles.module.scss
+++ b/src/components/comment/CommentForm/styles.module.scss
@@ -21,13 +21,42 @@
 
   position: relative;
   z-index: 1;
-  display: flex;
-  flex-direction: row;
+  display: grid;
   background: transparentize(black, 0.8);
   padding: $gap / 2;
   border-radius: 0 0 $radius $radius;
   flex-wrap: wrap;
+  column-gap: $gap;
+  grid-template-columns: auto 1fr auto;
+  grid-template-rows: 1fr;
+  grid-template-areas: "attach format submit";
 
+  @media(max-width: 470px) {
+    padding: $gap;
+    grid-template-columns: 1fr auto;
+    grid-template-rows: 1fr 1fr;
+    grid-template-areas:
+    "attach format"
+    "submit submit";
+    row-gap: $gap;
+  }
+
+  &_attach {
+    grid-area: attach;
+  }
+
+  &_format {
+    grid-area: format;
+  }
+
+  &_submit {
+    grid-area: submit;
+    display: grid;
+    grid-auto-flow: column;
+    align-items: flex-end;
+    justify-content: flex-end;
+    column-gap: $gap / 2;
+  }
 }
 
 .uploads {
diff --git a/src/components/comment/CommentFormFormatButtons/styles.module.scss b/src/components/comment/CommentFormFormatButtons/styles.module.scss
index e63a68c1..d07205e9 100644
--- a/src/components/comment/CommentFormFormatButtons/styles.module.scss
+++ b/src/components/comment/CommentFormFormatButtons/styles.module.scss
@@ -2,11 +2,8 @@
 
 .wrap {
   display: flex;
-  flex-wrap: wrap;
+  flex-wrap: nowrap;
   height: 32px;
   flex: 1;
-
-  @media(max-width: 480px) {
-    display: none;
-  }
+  width: 100%;
 }
diff --git a/src/components/containers/CommentWrapper/styles.module.scss b/src/components/containers/CommentWrapper/styles.module.scss
index c1f36ae5..6587b197 100644
--- a/src/components/containers/CommentWrapper/styles.module.scss
+++ b/src/components/containers/CommentWrapper/styles.module.scss
@@ -33,7 +33,6 @@
   @include tablet {
     :global(.comment-author) {
       display: none !important;
-      color: red;
     }
   }
 }
diff --git a/src/components/containers/Sticky/index.tsx b/src/components/containers/Sticky/index.tsx
index f3e817d5..55288c87 100644
--- a/src/components/containers/Sticky/index.tsx
+++ b/src/components/containers/Sticky/index.tsx
@@ -1,40 +1,15 @@
-import React, { DetailsHTMLAttributes, FC, useEffect, useRef } from 'react';
-import styles from './styles.module.scss';
-import StickySidebar from 'sticky-sidebar';
-import classnames from 'classnames';
-import ResizeSensor from 'resize-sensor';
+import React, { DetailsHTMLAttributes, FC } from 'react';
+import StickyBox from 'react-sticky-box/dist/esnext';
 
-interface IProps extends DetailsHTMLAttributes<HTMLDivElement> {}
-
-(window as any).StickySidebar = StickySidebar;
-(window as any).ResizeSensor = ResizeSensor;
-
-const Sticky: FC<IProps> = ({ children }) => {
-  const ref = useRef(null);
-  const sb = useRef<StickySidebar>(null);
-
-  useEffect(() => {
-    if (!ref.current) return;
-
-    sb.current = new StickySidebar(ref.current, {
-      resizeSensor: true,
-      topSpacing: 72,
-      bottomSpacing: 10,
-    });
-
-    return () => sb.current?.destroy();
-  }, [ref.current, sb.current, children]);
-
-  if (sb) {
-    sb.current?.updateSticky();
-  }
+interface IProps extends DetailsHTMLAttributes<HTMLDivElement> {
+  offsetTop?: number;
+}
 
+const Sticky: FC<IProps> = ({ children, offsetTop = 65 }) => {
   return (
-    <div className={classnames(styles.wrap, 'sidebar_container')}>
-      <div className="sidebar" ref={ref}>
-        <div className={classnames(styles.sticky, 'sidebar__inner')}>{children}</div>
-      </div>
-    </div>
+    <StickyBox offsetTop={offsetTop} offsetBottom={10}>
+      {children}
+    </StickyBox>
   );
 };
 
diff --git a/src/components/containers/Sticky/styles.module.scss b/src/components/containers/Sticky/styles.module.scss
deleted file mode 100644
index eb680535..00000000
--- a/src/components/containers/Sticky/styles.module.scss
+++ /dev/null
@@ -1,17 +0,0 @@
-@import "src/styles/variables";
-
-.wrap {
-  height: 100%;
-  width: 100%;
-  position: relative;
-
-  :global(.sidebar) {
-    will-change: min-height;
-  }
-
-  :global(.sidebar__inner) {
-    transform: translate(0, 0); /* For browsers don't support translate3d. */
-    transform: translate3d(0, 0, 0);
-    will-change: position, transform;
-  }
-}
diff --git a/src/components/dialogs/Tab/index.tsx b/src/components/dialogs/Tab/index.tsx
new file mode 100644
index 00000000..20b49d77
--- /dev/null
+++ b/src/components/dialogs/Tab/index.tsx
@@ -0,0 +1,16 @@
+import React, { FC, MouseEventHandler } from 'react';
+import classNames from 'classnames';
+import styles from './styles.module.scss';
+
+interface IProps {
+  active?: boolean;
+  onClick?: MouseEventHandler<any>;
+}
+
+const Tab: FC<IProps> = ({ active, onClick, children }) => (
+  <div className={classNames(styles.tab, { [styles.active]: active })} onClick={onClick}>
+    {children}
+  </div>
+);
+
+export { Tab };
diff --git a/src/components/dialogs/Tab/styles.module.scss b/src/components/dialogs/Tab/styles.module.scss
new file mode 100644
index 00000000..be3d83e4
--- /dev/null
+++ b/src/components/dialogs/Tab/styles.module.scss
@@ -0,0 +1,20 @@
+@import "src/styles/variables";
+
+.tab {
+  @include outer_shadow();
+
+  padding: $gap;
+  margin-right: $gap;
+  border-radius: $radius $radius 0 0;
+  font: $font_14_semibold;
+  text-transform: uppercase;
+  cursor: pointer;
+  background-color: $content_bg;
+  color: white;
+  text-decoration: none;
+  border: none;
+
+  &.active {
+    background: lighten($content_bg, 4%);
+  }
+}
diff --git a/src/components/dialogs/Tabs/index.tsx b/src/components/dialogs/Tabs/index.tsx
new file mode 100644
index 00000000..f7ca42fd
--- /dev/null
+++ b/src/components/dialogs/Tabs/index.tsx
@@ -0,0 +1,12 @@
+import React, { FC, useCallback } from 'react';
+import styles from './styles.module.scss';
+import classNames from 'classnames';
+import { IAuthState } from '~/redux/auth/types';
+
+interface IProps {}
+
+const Tabs: FC<IProps> = ({ children }) => {
+  return <div className={styles.wrap}>{children}</div>;
+};
+
+export { Tabs };
diff --git a/src/components/dialogs/Tabs/styles.module.scss b/src/components/dialogs/Tabs/styles.module.scss
new file mode 100644
index 00000000..3e915328
--- /dev/null
+++ b/src/components/dialogs/Tabs/styles.module.scss
@@ -0,0 +1,8 @@
+@import "src/styles/variables";
+
+.wrap {
+  display: flex;
+  align-items: flex-start;
+  justify-content: flex-start;
+  padding: 0 $gap / 2;
+}
diff --git a/src/components/editors/EditorImageUploadButton/index.tsx b/src/components/editors/EditorImageUploadButton/index.tsx
index 68504973..f333cf9d 100644
--- a/src/components/editors/EditorImageUploadButton/index.tsx
+++ b/src/components/editors/EditorImageUploadButton/index.tsx
@@ -1,6 +1,5 @@
 import React, { FC } from 'react';
 import { EditorUploadButton } from '~/components/editors/EditorUploadButton';
-import { INode } from '~/redux/types';
 import { UPLOAD_TYPES } from '~/redux/uploads/constants';
 import { IEditorComponentProps } from '~/redux/node/types';
 
diff --git a/src/components/editors/EditorPanel/styles.module.scss b/src/components/editors/EditorPanel/styles.module.scss
index a58338b2..5bda9666 100644
--- a/src/components/editors/EditorPanel/styles.module.scss
+++ b/src/components/editors/EditorPanel/styles.module.scss
@@ -13,11 +13,12 @@
   flex-direction: row;
 
   & > * {
-    margin: 0 $gap;
+    margin: 0 $gap / 2;
 
     &:first-child {
       margin-left: 0;
     }
+
     &:last-child {
       margin-right: 0;
     }
diff --git a/src/components/editors/EditorPublicSwitch/index.tsx b/src/components/editors/EditorPublicSwitch/index.tsx
new file mode 100644
index 00000000..71c1fef2
--- /dev/null
+++ b/src/components/editors/EditorPublicSwitch/index.tsx
@@ -0,0 +1,43 @@
+import React, { FC, useCallback } from 'react';
+import { IEditorComponentProps } from '~/redux/node/types';
+import { Button } from '~/components/input/Button';
+import { Icon } from '~/components/input/Icon';
+import styles from './styles.module.scss';
+import { Superpower } from '~/components/boris/Superpower';
+
+interface IProps extends IEditorComponentProps {}
+
+const EditorPublicSwitch: FC<IProps> = ({ data, setData }) => {
+  const onChange = useCallback(() => setData({ ...data, is_promoted: !data.is_promoted }), [
+    data,
+    setData,
+  ]);
+
+  return (
+    <Superpower>
+      <Button
+        color={data.is_promoted ? 'primary' : 'lab'}
+        type="button"
+        size="giant"
+        label={
+          data.is_promoted
+            ? 'Доступно всем на главной странице'
+            : 'Видно только сотрудникам в лаборатории'
+        }
+        onClick={onChange}
+        className={styles.button}
+        round
+      >
+        {data.is_promoted ? (
+          <Icon icon="waves" size={24} />
+        ) : (
+          <div className={styles.lab_wrapper}>
+            <Icon icon="lab" size={24} />
+          </div>
+        )}
+      </Button>
+    </Superpower>
+  );
+};
+
+export { EditorPublicSwitch };
diff --git a/src/components/editors/EditorPublicSwitch/styles.module.scss b/src/components/editors/EditorPublicSwitch/styles.module.scss
new file mode 100644
index 00000000..95aa2412
--- /dev/null
+++ b/src/components/editors/EditorPublicSwitch/styles.module.scss
@@ -0,0 +1,63 @@
+@import "src/styles/variables";
+
+@keyframes move_1 {
+  0% {
+    transform: scale(0) translate(0, 0);
+    opacity: 0;
+  }
+
+  50% {
+    transform: scale(1)  translate(5px, -5px);
+    opacity: 1;
+  }
+
+  100% {
+    transform: scale(1.2) translate(-5px, -10px);
+    opacity: 0;
+  }
+}
+
+
+@keyframes move_2 {
+  0% {
+    transform: scale(0) translate(0, 0);
+    opacity: 0;
+  }
+
+  50% {
+    transform: scale(1)  translate(-5px, -5px);
+    opacity: 1;
+  }
+
+  100% {
+    transform: scale(1.6) translate(5px, -10px);
+    opacity: 0;
+  }
+}
+
+.button {
+
+}
+
+.lab_wrapper {
+  position: relative;
+  bottom: -2px;
+
+  .button:hover & {
+    &:before,&:after {
+      content: ' ';
+      position: absolute;
+      top: 1px;
+      left: 10px;
+      width: 2px;
+      height: 2px;
+      box-shadow: white 0 0 0 2px;
+      border-radius: 4px;
+      animation: move_1 0.5s infinite linear;
+    }
+
+    &:after {
+      animation: move_2 0.5s -0.25s infinite linear;
+    }
+  }
+}
diff --git a/src/components/editors/EditorUploadButton/index.tsx b/src/components/editors/EditorUploadButton/index.tsx
index 21b7cc50..e77d0804 100644
--- a/src/components/editors/EditorUploadButton/index.tsx
+++ b/src/components/editors/EditorUploadButton/index.tsx
@@ -1,15 +1,15 @@
 import React, { FC, useCallback, useEffect } from 'react';
 import styles from './styles.module.scss';
 import { Icon } from '~/components/input/Icon';
-import { IFileWithUUID, INode, IFile } from '~/redux/types';
+import { IFile, IFileWithUUID } from '~/redux/types';
 import uuid from 'uuid4';
 import { UPLOAD_SUBJECTS, UPLOAD_TARGETS, UPLOAD_TYPES } from '~/redux/uploads/constants';
 import * as UPLOAD_ACTIONS from '~/redux/uploads/actions';
-import { assocPath } from 'ramda';
-import { append } from 'ramda';
+import { append, assocPath } from 'ramda';
 import { selectUploads } from '~/redux/uploads/selectors';
 import { connect } from 'react-redux';
 import { NODE_SETTINGS } from '~/redux/node/constants';
+import { IEditorComponentProps } from '~/redux/node/types';
 
 const mapStateToProps = state => {
   const { statuses, files } = selectUploads(state);
@@ -22,12 +22,7 @@ const mapDispatchToProps = {
 };
 
 type IProps = ReturnType<typeof mapStateToProps> &
-  typeof mapDispatchToProps & {
-    data: INode;
-    setData: (val: INode) => void;
-    temp: string[];
-    setTemp: (val: string[]) => void;
-
+  typeof mapDispatchToProps & IEditorComponentProps & {
     accept?: string;
     icon?: string;
     type?: typeof UPLOAD_TYPES[keyof typeof UPLOAD_TYPES];
@@ -82,18 +77,6 @@ const EditorUploadButtonUnconnected: FC<IProps> = ({
     [data, setData]
   );
 
-  // const onDrop = useCallback(
-  //   (event: React.DragEvent<HTMLDivElement>) => {
-  //     event.preventDefault();
-
-  //     if (!event.dataTransfer || !event.dataTransfer.files || !event.dataTransfer.files.length)
-  //       return;
-
-  //     onUpload(Array.from(event.dataTransfer.files));
-  //   },
-  //   [onUpload]
-  // );
-
   useEffect(() => {
     window.addEventListener('dragover', eventPreventer, false);
     window.addEventListener('drop', eventPreventer, false);
diff --git a/src/components/editors/EditorUploadButton/styles.module.scss b/src/components/editors/EditorUploadButton/styles.module.scss
index 3ba89ad9..3347fd46 100644
--- a/src/components/editors/EditorUploadButton/styles.module.scss
+++ b/src/components/editors/EditorUploadButton/styles.module.scss
@@ -2,17 +2,10 @@
 
 .wrap {
   @include outer_shadow();
+  @include editor_round_button();
 
-  width: $upload_button_height;
-  height: $upload_button_height;
-  border-radius: ($upload_button_height / 2) !important;
-  position: relative;
-  border-radius: $radius;
-  cursor: pointer;
-  // opacity: 0.7;
   transition: opacity 0.5s;
   background: $red_gradient;
-  // box-shadow: $content_bg 0 0 5px 10px;
 
   &:hover {
     opacity: 1;
diff --git a/src/components/flow/FlowRecent/index.tsx b/src/components/flow/FlowRecent/index.tsx
index 79bae2d4..53ac700e 100644
--- a/src/components/flow/FlowRecent/index.tsx
+++ b/src/components/flow/FlowRecent/index.tsx
@@ -11,7 +11,6 @@ const FlowRecent: FC<IProps> = ({ recent, updated }) => {
   return (
     <>
       {updated && updated.map(node => <FlowRecentItem node={node} key={node.id} has_new />)}
-
       {recent && recent.map(node => <FlowRecentItem node={node} key={node.id} />)}
     </>
   );
diff --git a/src/components/input/Button/index.tsx b/src/components/input/Button/index.tsx
index 8c263770..e0f3965d 100644
--- a/src/components/input/Button/index.tsx
+++ b/src/components/input/Button/index.tsx
@@ -1,33 +1,24 @@
 import classnames from 'classnames';
-import React, {
-  ButtonHTMLAttributes,
-  DetailedHTMLProps,
-  FC,
-  createElement,
-  memo,
-  useRef,
-} from 'react';
+import React, { ButtonHTMLAttributes, DetailedHTMLProps, FC, memo, useMemo } from 'react';
 import styles from './styles.module.scss';
 import { Icon } from '~/components/input/Icon';
 import { IIcon } from '~/redux/types';
-import { usePopper } from 'react-popper';
+import Tippy from '@tippy.js/react';
+import 'tippy.js/dist/tippy.css';
 
 type IButtonProps = DetailedHTMLProps<
   ButtonHTMLAttributes<HTMLButtonElement>,
   HTMLButtonElement
 > & {
   size?: 'mini' | 'normal' | 'big' | 'giant' | 'micro' | 'small';
-  color?: 'primary' | 'secondary' | 'outline' | 'link' | 'gray';
+  color?: 'primary' | 'secondary' | 'outline' | 'link' | 'gray' | 'lab';
   iconLeft?: IIcon;
   iconRight?: IIcon;
-  seamless?: boolean;
-  transparent?: boolean;
   title?: string;
-  non_submitting?: boolean;
-  is_loading?: boolean;
   stretchy?: boolean;
   iconOnly?: boolean;
   label?: string;
+  round?: boolean;
 };
 
 const Button: FC<IButtonProps> = memo(
@@ -38,56 +29,36 @@ const Button: FC<IButtonProps> = memo(
     iconLeft,
     iconRight,
     children,
-    seamless = false,
-    transparent = false,
-    non_submitting = false,
-    is_loading,
     title,
     stretchy,
     disabled,
     iconOnly,
     label,
     ref,
+    round,
     ...props
   }) => {
-    const tooltip = useRef<HTMLSpanElement | null>(null);
-    const pop = usePopper(tooltip?.current?.parentElement, tooltip.current, {
-      placement: 'top',
-      modifiers: [
-        {
-          name: 'offset',
-          options: {
-            offset: [0, 5],
-          },
-        },
-      ],
-    });
-
-    return createElement(
-      seamless || non_submitting ? 'div' : 'button',
-      {
-        className: classnames(styles.button, className, styles[size], styles[color], {
-          seamless,
-          transparent,
+    const computedClassName = useMemo(
+      () =>
+        classnames(styles.button, className, styles[size], styles[color], {
           disabled,
-          is_loading,
           stretchy,
           icon: ((iconLeft || iconRight) && !title && !children) || iconOnly,
           has_icon_left: !!iconLeft,
           has_icon_right: !!iconRight,
+          round,
         }),
-        ...props,
-      },
-      [
-        iconLeft && <Icon icon={iconLeft} size={20} key={0} className={styles.icon_left} />,
-        title ? <span>{title}</span> : children || null,
-        iconRight && <Icon icon={iconRight} size={20} key={2} className={styles.icon_right} />,
-        !!label && (
-          <span ref={tooltip} className={styles.tooltip} style={pop.styles.popper} key="tooltip">
-            {label}
-          </span>
-        ),
-      ]
+      [round, disabled, className, stretchy, iconLeft, iconRight, size, color]
+    );
+
+    return (
+      <Tippy content={label || ''} enabled={!!label}>
+        <button className={computedClassName} {...props}>
+          {iconLeft && <Icon icon={iconLeft} size={20} key={0} className={styles.icon_left} />}
+          {!!title ? <span>{title}</span> : children}
+          {iconRight && <Icon icon={iconRight} size={20} key={2} className={styles.icon_right} />}
+        </button>
+      </Tippy>
     );
   }
 );
diff --git a/src/components/input/Button/styles.module.scss b/src/components/input/Button/styles.module.scss
index 676bb62d..0489151c 100644
--- a/src/components/input/Button/styles.module.scss
+++ b/src/components/input/Button/styles.module.scss
@@ -34,17 +34,14 @@
   align-items: center;
   justify-content: center;
 
-  position: relative;
-
   filter: grayscale(0);
 
-  transition: opacity 0.25s, filter 0.25s, box-shadow 0.25s;
+  transition: opacity 0.25s, filter 0.25s, box-shadow 0.25s, background-color 0.5s;
   opacity: 0.8;
 
   @include outer_shadow();
 
   input {
-    color: red;
     position: absolute;
     top: 0;
     left: 0;
@@ -80,30 +77,6 @@
     }
   }
 
-  &:global(.seamless) {
-    background: transparent;
-    color: black;
-    box-shadow: none;
-    fill: black;
-    stroke: black;
-    padding: 0;
-  }
-
-  &:global(.transparent) {
-    background: transparent;
-    color: white;
-    box-shadow: transparentize(black, 0.5) 0 0 4px;
-    padding: 0;
-    fill: black;
-    stroke: black;
-  }
-
-  &:global(.red) {
-    fill: $red;
-    stroke: $red;
-    color: $red;
-  }
-
   &:global(.stretchy) {
     flex: 1;
   }
@@ -112,8 +85,6 @@
   &:global(.grey) {
     background: transparentize(white, 0.9);
     color: white;
-    // background: lighten(white, 0.5);
-    // filter: grayscale(100%);
   }
 
   &:global(.disabled) {
@@ -146,14 +117,6 @@
     padding-right: $gap;
   }
 
-  &.primary {
-    background: $red_gradient;
-  }
-
-  &.secondary {
-    background: $green_gradient;
-  }
-
   &.outline {
     background: transparent;
     box-shadow: inset transparentize(white, 0.8) 0 0 0 2px;
@@ -185,31 +148,60 @@
   font: $font_12_semibold;
   padding: 0 15px;
   border-radius: $radius / 2;
+
+  &:global(.round) {
+    border-radius: 10px;
+  }
 }
+
 .mini {
   height: 28px;
   border-radius: $radius / 2;
+
+  &:global(.round) {
+    border-radius: 14px;
+  }
 }
+
 .small {
   height: 32px;
-  // border-radius: $radius / 2;
 
   svg {
     width: 24px;
     height: 24px;
   }
+
+  &:global(.round) {
+    border-radius: 16px;
+  }
 }
+
 .normal {
   height: 38px;
+
+  &:global(.round) {
+    border-radius: 19px;
+  }
 }
+
 .big {
   height: 40px;
+
+  &:global(.round) {
+    border-radius: 20px;
+  }
 }
+
 .giant {
   height: 50px;
   padding: 0 15px;
   min-width: 50px;
+
+  &:global(.round) {
+    border-radius: 25px;
+  }
 }
+
 .disabled {
   opacity: 0.5;
 }
@@ -226,20 +218,14 @@
   height: 20px;
 }
 
-.tooltip {
-  padding: 5px 10px;
-  background-color: darken($content_bg, 4%);
-  z-index: 2;
-  border-radius: $input_radius;
-  text-transform: none;
-  opacity: 0;
-  pointer-events: none;
-  touch-action: none;
-  transition: opacity 0.1s;
-  border: 1px solid transparentize(white, 0.9);
-
-  .button:hover & {
-    opacity: 1;
-    font: $font_14_semibold;
-  }
+.primary {
+  background: $red;
+}
+
+.secondary {
+  background: $wisegreen;
+}
+
+.lab {
+  background: $blue;
 }
diff --git a/src/components/input/Toggle/index.tsx b/src/components/input/Toggle/index.tsx
new file mode 100644
index 00000000..f36ee358
--- /dev/null
+++ b/src/components/input/Toggle/index.tsx
@@ -0,0 +1,31 @@
+import React, { FC, useCallback } from 'react';
+import styles from './styles.module.scss';
+import classNames from 'classnames';
+
+type ToggleColor = 'primary' | 'secondary' | 'lab' | 'danger';
+
+interface IProps {
+  value?: boolean;
+  handler?: (val: boolean) => void;
+  color?: ToggleColor;
+}
+
+const Toggle: FC<IProps> = ({ value, handler, color = 'primary' }) => {
+  const onClick = useCallback(() => {
+    if (!handler) {
+      return;
+    }
+
+    handler(!value);
+  }, [value, handler]);
+
+  return (
+    <button
+      type="button"
+      className={classNames(styles.toggle, { [styles.active]: value }, styles[color])}
+      onClick={onClick}
+    />
+  );
+};
+
+export { Toggle };
diff --git a/src/components/input/Toggle/styles.module.scss b/src/components/input/Toggle/styles.module.scss
new file mode 100644
index 00000000..00060eab
--- /dev/null
+++ b/src/components/input/Toggle/styles.module.scss
@@ -0,0 +1,51 @@
+@import "~/styles/variables.scss";
+
+.toggle {
+  height: 24px;
+  width: 48px;
+  flex: 0 0 48px;
+  border-radius: 12px;
+  background-color: transparentize(white, 0.9);
+  display: flex;
+  border: none;
+  outline: none;
+  cursor: pointer;
+  position: relative;
+
+  &::after {
+    content: ' ';
+    position: absolute;
+    left: 3px;
+    top: 3px;
+    height: 18px;
+    width: 18px;
+    border-radius: 11px;
+    background-color: darken(white, 50%);
+    transform: translate(0, 0);
+    transition: transform 0.25s, color 0.25s, background-color;
+  }
+
+  &.active {
+
+    &::after {
+      transform: translate(24px, 0);
+      background-color: white;
+    }
+
+    &.primary {
+      background-color: $wisegreen;
+    }
+
+    &.secondary {
+      background-color: transparentize(white, 0.85);
+    }
+
+    &.lab {
+      background-color: $blue;
+    }
+
+    &.danger {
+      background-color: $red;
+    }
+  }
+}
diff --git a/src/components/lab/LabBanner/index.tsx b/src/components/lab/LabBanner/index.tsx
new file mode 100644
index 00000000..df8f60ea
--- /dev/null
+++ b/src/components/lab/LabBanner/index.tsx
@@ -0,0 +1,22 @@
+import React, { FC } from 'react';
+import styles from './styles.module.scss';
+import { Card } from '~/components/containers/Card';
+import { Placeholder } from '~/components/placeholders/Placeholder';
+import { Group } from '~/components/containers/Group';
+
+interface IProps {}
+
+const LabBanner: FC<IProps> = () => (
+  <Card className={styles.wrap}>
+    <Group>
+      <Placeholder height={32} />
+      <Placeholder height={18} width="120px" />
+      <Placeholder height={18} width="200px" />
+      <Placeholder height={18} width="60px" />
+      <Placeholder height={18} width="180px" />
+      <Placeholder height={18} width="230px" />
+    </Group>
+  </Card>
+);
+
+export { LabBanner };
diff --git a/src/components/lab/LabBanner/styles.module.scss b/src/components/lab/LabBanner/styles.module.scss
new file mode 100644
index 00000000..f235ed41
--- /dev/null
+++ b/src/components/lab/LabBanner/styles.module.scss
@@ -0,0 +1,5 @@
+@import "~/styles/variables.scss";
+
+.wrap {
+  background: $red_gradient_alt;
+}
diff --git a/src/components/lab/LabHead/index.tsx b/src/components/lab/LabHead/index.tsx
new file mode 100644
index 00000000..25fb6cfd
--- /dev/null
+++ b/src/components/lab/LabHead/index.tsx
@@ -0,0 +1,32 @@
+import React, { FC } from 'react';
+import { Group } from '~/components/containers/Group';
+import { Card } from '~/components/containers/Card';
+import { Placeholder } from '~/components/placeholders/Placeholder';
+import { Filler } from '~/components/containers/Filler';
+
+interface IProps {}
+
+const LabHead: FC<IProps> = () => (
+  <Card>
+    <Group horizontal>
+      <Group horizontal style={{ flex: '0 0 auto' }}>
+        <Placeholder width="32px" height={32} />
+        <Placeholder width="96px" height={18} />
+      </Group>
+
+      <Group horizontal style={{ flex: '0 0 auto' }}>
+        <Placeholder width="32px" height={32} />
+        <Placeholder width="126px" height={18} />
+      </Group>
+
+      <Group horizontal style={{ flex: '0 0 auto' }}>
+        <Placeholder width="32px" height={32} />
+        <Placeholder width="96px" height={18} />
+      </Group>
+
+      <Filler />
+    </Group>
+  </Card>
+);
+
+export { LabHead };
diff --git a/src/components/lab/LabHero/index.tsx b/src/components/lab/LabHero/index.tsx
new file mode 100644
index 00000000..1a059815
--- /dev/null
+++ b/src/components/lab/LabHero/index.tsx
@@ -0,0 +1,22 @@
+import React, { FC } from 'react';
+import { Placeholder } from '~/components/placeholders/Placeholder';
+import { Group } from '~/components/containers/Group';
+import { Icon } from '~/components/input/Icon';
+import styles from './styles.module.scss';
+
+interface IProps {}
+
+const LabHero: FC<IProps> = () => (
+  <Group horizontal className={styles.wrap1}>
+    <div className={styles.star}>
+      <Icon icon="star_full" size={32} />
+    </div>
+
+    <Group>
+      <Placeholder height={20} />
+      <Placeholder height={12} width="100px" />
+    </Group>
+  </Group>
+);
+
+export { LabHero };
diff --git a/src/components/lab/LabHero/styles.module.scss b/src/components/lab/LabHero/styles.module.scss
new file mode 100644
index 00000000..b1a7e9cc
--- /dev/null
+++ b/src/components/lab/LabHero/styles.module.scss
@@ -0,0 +1,10 @@
+@import "~/styles/variables.scss";
+
+.wrap {
+  margin-bottom: $gap;
+}
+
+.star {
+  fill: #2c2c2c;
+}
+
diff --git a/src/components/lab/LabNode/index.tsx b/src/components/lab/LabNode/index.tsx
new file mode 100644
index 00000000..5f64c55f
--- /dev/null
+++ b/src/components/lab/LabNode/index.tsx
@@ -0,0 +1,31 @@
+import React, { FC } from 'react';
+import { INode } from '~/redux/types';
+import { NodePanelInner } from '~/components/node/NodePanelInner';
+import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks';
+import styles from './styles.module.scss';
+import { Card } from '~/components/containers/Card';
+import { NodePanelLab } from '~/components/node/NodePanelLab';
+
+interface IProps {
+  node: INode;
+}
+
+const LabNode: FC<IProps> = ({ node }) => {
+  const { inline, block, head } = useNodeBlocks(node, false);
+
+  console.log(node.id, { inline, block, head });
+
+  return (
+    <Card seamless className={styles.wrap}>
+      <div className={styles.head}>
+        <NodePanelLab node={node} />
+      </div>
+
+      {head}
+      {block}
+      {inline}
+    </Card>
+  );
+};
+
+export { LabNode };
diff --git a/src/components/lab/LabNode/styles.module.scss b/src/components/lab/LabNode/styles.module.scss
new file mode 100644
index 00000000..0e8e59ab
--- /dev/null
+++ b/src/components/lab/LabNode/styles.module.scss
@@ -0,0 +1,11 @@
+@import "~/styles/variables.scss";
+
+.wrap {
+  min-width: 0;
+}
+
+.head {
+  background-color: transparentize(black, 0.9);
+  border-radius: $radius $radius 0 0;
+}
+
diff --git a/src/components/main/Header/index.tsx b/src/components/main/Header/index.tsx
index 43d7646c..014e12a1 100644
--- a/src/components/main/Header/index.tsx
+++ b/src/components/main/Header/index.tsx
@@ -21,6 +21,7 @@ import * as MODAL_ACTIONS from '~/redux/modal/actions';
 import * as AUTH_ACTIONS from '~/redux/auth/actions';
 import { IState } from '~/redux/store';
 import isBefore from 'date-fns/isBefore';
+import { Superpower } from '~/components/boris/Superpower';
 
 const mapStateToProps = (state: IState) => ({
   user: pick(['username', 'is_user', 'photo', 'last_seen_boris'])(selectUser(state)),
@@ -89,6 +90,15 @@ const HeaderUnconnected: FC<IProps> = memo(
               ФЛОУ
             </Link>
 
+            <Superpower>
+              <Link
+                className={classNames(styles.item, { [styles.is_active]: pathname === URLS.BASE })}
+                to={URLS.LAB}
+              >
+                ЛАБ
+              </Link>
+            </Superpower>
+
             <Link
               className={classNames(styles.item, {
                 [styles.is_active]: pathname === URLS.BORIS,
@@ -122,9 +132,6 @@ const HeaderUnconnected: FC<IProps> = memo(
   }
 );
 
-const Header = connect(
-  mapStateToProps,
-  mapDispatchToProps
-)(HeaderUnconnected);
+const Header = connect(mapStateToProps, mapDispatchToProps)(HeaderUnconnected);
 
 export { Header };
diff --git a/src/components/node/NodeAudioBlock/index.tsx b/src/components/node/NodeAudioBlock/index.tsx
index 9581a16b..c064e831 100644
--- a/src/components/node/NodeAudioBlock/index.tsx
+++ b/src/components/node/NodeAudioBlock/index.tsx
@@ -4,14 +4,12 @@ import { UPLOAD_TYPES } from '~/redux/uploads/constants';
 import { AudioPlayer } from '~/components/media/AudioPlayer';
 import styles from './styles.module.scss';
 import { INodeComponentProps } from '~/redux/node/constants';
+import { useNodeAudios } from '~/utils/hooks/node/useNodeAudios';
 
 interface IProps extends INodeComponentProps {}
 
 const NodeAudioBlock: FC<IProps> = ({ node }) => {
-  const audios = useMemo(
-    () => node.files.filter(file => file && file.type === UPLOAD_TYPES.AUDIO),
-    [node.files]
-  );
+  const audios = useNodeAudios(node);
 
   return (
     <div className={styles.wrap}>
diff --git a/src/components/node/NodeAudioImageBlock/index.tsx b/src/components/node/NodeAudioImageBlock/index.tsx
index ca848b3f..e7834737 100644
--- a/src/components/node/NodeAudioImageBlock/index.tsx
+++ b/src/components/node/NodeAudioImageBlock/index.tsx
@@ -6,14 +6,12 @@ import { path } from 'ramda';
 import { getURL } from '~/utils/dom';
 import { PRESETS } from '~/constants/urls';
 import { INodeComponentProps } from '~/redux/node/constants';
+import { useNodeImages } from '~/utils/hooks/node/useNodeImages';
 
 interface IProps extends INodeComponentProps {}
 
 const NodeAudioImageBlock: FC<IProps> = ({ node }) => {
-  const images = useMemo(
-    () => node.files.filter(file => file && file.type === UPLOAD_TYPES.IMAGE),
-    [node.files]
-  );
+  const images = useNodeImages(node);
 
   if (images.length === 0) return null;
 
diff --git a/src/components/node/NodeBottomBlock/index.tsx b/src/components/node/NodeBottomBlock/index.tsx
index 73a114e6..d06da73c 100644
--- a/src/components/node/NodeBottomBlock/index.tsx
+++ b/src/components/node/NodeBottomBlock/index.tsx
@@ -2,16 +2,16 @@ import React, { FC } from 'react';
 import { NodeDeletedBadge } from '~/components/node/NodeDeletedBadge';
 import { Group } from '~/components/containers/Group';
 import { Padder } from '~/components/containers/Padder';
-import styles from '~/containers/node/NodeLayout/styles.module.scss';
 import { NodeCommentsBlock } from '~/components/node/NodeCommentsBlock';
 import { NodeCommentForm } from '~/components/node/NodeCommentForm';
-import { Sticky } from '~/components/containers/Sticky';
 import { NodeRelatedBlock } from '~/components/node/NodeRelatedBlock';
 import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks';
 import { IComment, INode } from '~/redux/types';
 import { useUser } from '~/utils/hooks/user/userUser';
 import { NodeTagsBlock } from '~/components/node/NodeTagsBlock';
 import { INodeRelated } from '~/redux/node/types';
+import StickyBox from 'react-sticky-box/dist/esnext';
+import styles from './styles.module.scss';
 
 interface IProps {
   node: INode;
@@ -59,12 +59,12 @@ const NodeBottomBlock: FC<IProps> = ({
           </Group>
 
           <div className={styles.panel}>
-            <Sticky>
+            <StickyBox className={styles.sticky} offsetTop={72}>
               <Group style={{ flex: 1, minWidth: 0 }}>
                 <NodeTagsBlock node={node} isLoading={isLoading} />
                 <NodeRelatedBlock isLoading={isLoading} node={node} related={related} />
               </Group>
-            </Sticky>
+            </StickyBox>
           </div>
         </Group>
       </Padder>
diff --git a/src/components/node/NodeBottomBlock/styles.module.scss b/src/components/node/NodeBottomBlock/styles.module.scss
new file mode 100644
index 00000000..a34d0d9b
--- /dev/null
+++ b/src/components/node/NodeBottomBlock/styles.module.scss
@@ -0,0 +1,48 @@
+@import "~/styles/variables.scss";
+
+.sticky {
+  width: 100%;
+}
+
+.content {
+  align-items: stretch !important;
+  @include vertical_at_tablet;
+}
+
+.comments {
+  flex: 3 1;
+  min-width: 0;
+  display: flex;
+  align-items: stretch;
+  justify-content: flex-start;
+  flex-direction: column;
+
+  @media (max-width: 1024px) {
+    flex: 2 1;
+  }
+}
+
+
+.panel {
+  flex: 1 3;
+  display: flex;
+  align-items: flex-start;
+  justify-content: flex-start;
+  padding-left: $gap / 2;
+  min-width: 0;
+  position: relative;
+  z-index: 10;
+
+  @media (max-width: 1024px) {
+    padding-left: 0;
+    padding-top: $comment_height / 2;
+    flex: 1 2;
+  }
+}
+
+.buttons {
+  background: $node_buttons_bg;
+  flex: 1;
+  border-radius: $panel_radius;
+  box-shadow: $comment_shadow;
+}
diff --git a/src/components/node/NodeImageSwiperBlock/index.tsx b/src/components/node/NodeImageSwiperBlock/index.tsx
index c6011813..417ae430 100644
--- a/src/components/node/NodeImageSwiperBlock/index.tsx
+++ b/src/components/node/NodeImageSwiperBlock/index.tsx
@@ -1,12 +1,13 @@
 import React, { FC, useCallback, useEffect, useState } from 'react';
 import { INodeComponentProps } from '~/redux/node/constants';
-import SwiperCore, { A11y, Pagination, SwiperOptions } from 'swiper';
+import SwiperCore, { A11y, Pagination, Navigation, SwiperOptions, Keyboard } from 'swiper';
 import { Swiper, SwiperSlide } from 'swiper/react';
 
 import 'swiper/swiper.scss';
 import 'swiper/components/pagination/pagination.scss';
 import 'swiper/components/scrollbar/scrollbar.scss';
 import 'swiper/components/zoom/zoom.scss';
+import 'swiper/components/navigation/navigation.scss';
 
 import styles from './styles.module.scss';
 import { useNodeImages } from '~/utils/hooks/node/useNodeImages';
@@ -16,13 +17,14 @@ import SwiperClass from 'swiper/types/swiper-class';
 import { modalShowPhotoswipe } from '~/redux/modal/actions';
 import { useDispatch } from 'react-redux';
 
-SwiperCore.use([Pagination, A11y]);
+SwiperCore.use([Navigation, Pagination, A11y]);
 
 interface IProps extends INodeComponentProps {}
 
 const breakpoints: SwiperOptions['breakpoints'] = {
   599: {
     spaceBetween: 20,
+    navigation: true,
   },
 };
 
@@ -43,6 +45,7 @@ const NodeImageSwiperBlock: FC<IProps> = ({ node }) => {
   const resetSwiper = useCallback(() => {
     if (!controlledSwiper) return;
     controlledSwiper.slideTo(0, 0);
+    setTimeout(() => controlledSwiper.slideTo(0, 0), 300);
   }, [controlledSwiper]);
 
   useEffect(() => {
@@ -74,7 +77,12 @@ const NodeImageSwiperBlock: FC<IProps> = ({ node }) => {
         observeParents
         resizeObserver
         watchOverflow
+        updateOnImagesReady
         onInit={resetSwiper}
+        keyboard={{
+          enabled: true,
+          onlyInViewport: false,
+        }}
         zoom
       >
         {images.map(file => (
diff --git a/src/components/node/NodeImageSwiperBlock/styles.module.scss b/src/components/node/NodeImageSwiperBlock/styles.module.scss
index 7766bfe5..6dd0a49b 100644
--- a/src/components/node/NodeImageSwiperBlock/styles.module.scss
+++ b/src/components/node/NodeImageSwiperBlock/styles.module.scss
@@ -20,6 +20,17 @@
   :global(.swiper-container) {
     width: 100vw;
   }
+
+  :global(.swiper-button-next),
+  :global(.swiper-button-prev) {
+    color: white;
+    font-size: 10px;
+
+    &::after {
+      font-size: 32px;
+    }
+  }
+
 }
 
 .slide {
diff --git a/src/components/node/NodePanelInner/styles.module.scss b/src/components/node/NodePanelInner/styles.module.scss
index 58d925fa..c9b9a94b 100644
--- a/src/components/node/NodePanelInner/styles.module.scss
+++ b/src/components/node/NodePanelInner/styles.module.scss
@@ -31,8 +31,6 @@
 
 .wrap {
   display: flex;
-  align-items: center;
-  justify-content: stretch;
   position: relative;
   width: 100%;
   flex-direction: row;
@@ -88,7 +86,7 @@
   @include tablet {
     white-space: nowrap;
     padding-bottom: 0;
-    font: $font_20_semibold;
+    font: $font_16_semibold;
   }
 }
 
diff --git a/src/components/node/NodePanelLab/index.tsx b/src/components/node/NodePanelLab/index.tsx
new file mode 100644
index 00000000..c33714d4
--- /dev/null
+++ b/src/components/node/NodePanelLab/index.tsx
@@ -0,0 +1,19 @@
+import React, { FC } from 'react';
+import { INode } from '~/redux/types';
+import styles from './styles.module.scss';
+import { URLS } from '~/constants/urls';
+import { Link } from 'react-router-dom';
+
+interface IProps {
+  node: INode;
+}
+
+const NodePanelLab: FC<IProps> = ({ node }) => (
+  <div className={styles.wrap}>
+    <div className={styles.title}>
+      <Link to={URLS.NODE_URL(node.id)}>{node.title || '...'}</Link>
+    </div>
+  </div>
+);
+
+export { NodePanelLab };
diff --git a/src/components/node/NodePanelLab/styles.module.scss b/src/components/node/NodePanelLab/styles.module.scss
new file mode 100644
index 00000000..095dafe5
--- /dev/null
+++ b/src/components/node/NodePanelLab/styles.module.scss
@@ -0,0 +1,24 @@
+@import "~/styles/variables.scss";
+
+.wrap {
+  padding: $gap;
+}
+
+.title {
+  text-transform: uppercase;
+  font: $font_24_semibold;
+  overflow: hidden;
+  flex: 1;
+  text-overflow: ellipsis;
+
+  a {
+    text-decoration: none;
+    color: inherit;
+  }
+
+  @include tablet {
+    white-space: nowrap;
+    padding-bottom: 0;
+    font: $font_16_semibold;
+  }
+}
diff --git a/src/components/node/NodeRelatedItem/index.tsx b/src/components/node/NodeRelatedItem/index.tsx
index 88cf1bcd..c3a6903c 100644
--- a/src/components/node/NodeRelatedItem/index.tsx
+++ b/src/components/node/NodeRelatedItem/index.tsx
@@ -12,7 +12,7 @@ type IProps = RouteComponentProps & {
 
 type CellSize = 'small' | 'medium' | 'large';
 
-const getTitleLetters = (title: string): string => {
+const getTitleLetters = (title?: string): string => {
   const words = (title && title.split(' ')) || [];
 
   if (!words.length) return '';
diff --git a/src/constants/api.ts b/src/constants/api.ts
index 6f57d689..c9c4c287 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -50,4 +50,7 @@ export const API = {
     NODES: `/tag/nodes`,
     AUTOCOMPLETE: `/tag/autocomplete`,
   },
+  LAB: {
+    NODES: `/lab/`,
+  },
 };
diff --git a/src/constants/urls.ts b/src/constants/urls.ts
index 67eb4a39..527e072e 100644
--- a/src/constants/urls.ts
+++ b/src/constants/urls.ts
@@ -2,6 +2,7 @@ import { INode } from '~/redux/types';
 
 export const URLS = {
   BASE: '/',
+  LAB: '/lab',
   BORIS: '/boris',
   AUTH: {
     LOGIN: '/auth/login',
diff --git a/src/containers/dialogs/EditorDialog/index.tsx b/src/containers/dialogs/EditorDialog/index.tsx
index a5a42243..564b5dfc 100644
--- a/src/containers/dialogs/EditorDialog/index.tsx
+++ b/src/containers/dialogs/EditorDialog/index.tsx
@@ -95,7 +95,7 @@ const EditorDialogUnconnected: FC<IProps> = ({
           maxLength={256}
         />
 
-        <Button title="Сохранить" iconRight="check" />
+        <Button title="Сохранить" iconRight="check" color={data.is_promoted ? 'primary' : 'lab'} />
       </Group>
     </Padder>
   );
diff --git a/src/containers/dialogs/LoginSocialRegisterDialog/index.tsx b/src/containers/dialogs/LoginSocialRegisterDialog/index.tsx
index 64789120..ff210210 100644
--- a/src/containers/dialogs/LoginSocialRegisterDialog/index.tsx
+++ b/src/containers/dialogs/LoginSocialRegisterDialog/index.tsx
@@ -11,6 +11,7 @@ import { selectAuthRegisterSocial } from '~/redux/auth/selectors';
 import * as AUTH_ACTIONS from '~/redux/auth/actions';
 import { useCloseOnEscape } from '~/utils/hooks';
 import { LoginSocialRegisterButtons } from '~/containers/dialogs/LoginSocialRegisterButtons';
+import { Toggle } from '~/components/input/Toggle';
 
 const mapStateToProps = selectAuthRegisterSocial;
 const mapDispatchToProps = {
@@ -21,6 +22,12 @@ const mapDispatchToProps = {
 
 type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & IDialogProps & {};
 
+const phrase = [
+  'Сушёный кабачок особенно хорош в это время года, знаете ли.',
+  'Бывало, стреляешь по кабачку, или он стреляет в тебя.',
+  'Он всегда рядом, кабачок -- первый сорт! Надежда империи.',
+];
+
 const LoginSocialRegisterDialogUnconnected: FC<Props> = ({
   onRequestClose,
   errors,
@@ -32,6 +39,7 @@ const LoginSocialRegisterDialogUnconnected: FC<Props> = ({
 }) => {
   const [username, setUsername] = useState('');
   const [password, setPassword] = useState('');
+  const [isDryingPants, setIsDryingPants] = useState(false);
 
   const onSubmit = useCallback(
     (event: FormEvent) => {
@@ -56,7 +64,7 @@ const LoginSocialRegisterDialogUnconnected: FC<Props> = ({
   useCloseOnEscape(onRequestClose);
 
   return (
-    <form onSubmit={onSubmit}>
+    <form onSubmit={onSubmit} autoComplete="new-password">
       <BetterScrollDialog
         onClose={onRequestClose}
         width={300}
@@ -73,6 +81,7 @@ const LoginSocialRegisterDialogUnconnected: FC<Props> = ({
                 value={username}
                 title="Юзернэйм"
                 error={errors.username}
+                autoComplete="new-password"
               />
 
               <InputText
@@ -81,12 +90,18 @@ const LoginSocialRegisterDialogUnconnected: FC<Props> = ({
                 title="Пароль"
                 type="password"
                 error={errors.password}
+                autoComplete="new-password"
               />
 
-              <label className={styles.check}>
-                <input type="checkbox" />
+              <div className={styles.check} onClick={() => setIsDryingPants(!isDryingPants)}>
+                <Toggle value={isDryingPants} color="primary" />
                 <span>Это не мои штаны сушатся на радиаторе в третьей лаборатории</span>
-              </label>
+              </div>
+
+              <div className={styles.check} onClick={() => setIsDryingPants(!isDryingPants)}>
+                <Toggle value={!isDryingPants} color="primary" />
+                <span>{phrase[Math.floor(Math.random() * phrase.length)]}</span>
+              </div>
             </Group>
           </div>
         </Padder>
diff --git a/src/containers/lab/LabGrid/index.tsx b/src/containers/lab/LabGrid/index.tsx
new file mode 100644
index 00000000..b6961b5f
--- /dev/null
+++ b/src/containers/lab/LabGrid/index.tsx
@@ -0,0 +1,21 @@
+import React, { FC } from 'react';
+import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
+import styles from './styles.module.scss';
+import { LabNode } from '~/components/lab/LabNode';
+import { selectLabListNodes } from '~/redux/lab/selectors';
+
+interface IProps {}
+
+const LabGrid: FC<IProps> = () => {
+  const nodes = useShallowSelect(selectLabListNodes);
+
+  return (
+    <div className={styles.wrap}>
+      {nodes.map(node => (
+        <LabNode node={node} key={node.id} />
+      ))}
+    </div>
+  );
+};
+
+export { LabGrid };
diff --git a/src/containers/lab/LabGrid/styles.module.scss b/src/containers/lab/LabGrid/styles.module.scss
new file mode 100644
index 00000000..3f42c360
--- /dev/null
+++ b/src/containers/lab/LabGrid/styles.module.scss
@@ -0,0 +1,8 @@
+@import "~/styles/variables.scss";
+
+.wrap {
+  display: grid;
+  grid-auto-flow: row;
+  grid-auto-rows: auto;
+  grid-row-gap: $gap;
+}
diff --git a/src/containers/lab/LabLayout/index.tsx b/src/containers/lab/LabLayout/index.tsx
new file mode 100644
index 00000000..df778d28
--- /dev/null
+++ b/src/containers/lab/LabLayout/index.tsx
@@ -0,0 +1,112 @@
+import React, { FC, useEffect } from 'react';
+import styles from './styles.module.scss';
+import { Card } from '~/components/containers/Card';
+import { Sticky } from '~/components/containers/Sticky';
+import { Container } from '~/containers/main/Container';
+import { LabGrid } from '~/containers/lab/LabGrid';
+import { useDispatch } from 'react-redux';
+import { labGetList } from '~/redux/lab/actions';
+import { Placeholder } from '~/components/placeholders/Placeholder';
+import { Grid } from '~/components/containers/Grid';
+import { Group } from '~/components/containers/Group';
+import { LabHero } from '~/components/lab/LabHero';
+import { LabBanner } from '~/components/lab/LabBanner';
+import { LabHead } from '~/components/lab/LabHead';
+import { Filler } from '~/components/containers/Filler';
+
+interface IProps {}
+
+const LabLayout: FC<IProps> = () => {
+  const dispatch = useDispatch();
+
+  useEffect(() => {
+    dispatch(labGetList());
+  }, [dispatch]);
+
+  return (
+    <div>
+      <Container>
+        <div className={styles.wrap}>
+          <Group className={styles.content}>
+            <LabHead />
+            <LabGrid />
+          </Group>
+
+          <div className={styles.panel}>
+            <Sticky>
+              <Group>
+                <LabBanner />
+
+                <Card>
+                  <Group>
+                    <Placeholder height={36} width="100%" />
+                    <Group horizontal>
+                      <Filler />
+                      <Placeholder height={32} width="120px" />
+                    </Group>
+
+                    <div />
+                    <div />
+
+                    <Placeholder height={14} width="100px" />
+
+                    <div />
+
+                    <div className={styles.tags}>
+                      <Placeholder height={20} width="100px" />
+                      <Placeholder height={20} width="64px" />
+                      <Placeholder height={20} width="100%" />
+                      <Placeholder height={20} width="100px" />
+                      <Placeholder height={20} width="100px" />
+                      <Placeholder height={20} width="64px" />
+                    </div>
+
+                    <div />
+                    <div />
+
+                    <Placeholder height={14} width="180px" />
+
+                    <div />
+
+                    <Group className={styles.heroes}>
+                      <LabHero />
+                      <div />
+                      <LabHero />
+                      <div />
+                      <LabHero />
+                      <div />
+                      <LabHero />
+                      <div />
+                      <LabHero />
+                      <div />
+                      <LabHero />
+                      <div />
+                      <LabHero />
+                    </Group>
+
+                    <div />
+                    <div />
+
+                    <Group>
+                      <Placeholder width="100%" height={100} />
+                      <Placeholder width="120px" height={16} />
+                    </Group>
+
+                    <div />
+
+                    <Group>
+                      <Placeholder width="100%" height={100} />
+                      <Placeholder width="120px" height={16} />
+                    </Group>
+                  </Group>
+                </Card>
+              </Group>
+            </Sticky>
+          </div>
+        </div>
+      </Container>
+    </div>
+  );
+};
+
+export { LabLayout };
diff --git a/src/containers/lab/LabLayout/styles.module.scss b/src/containers/lab/LabLayout/styles.module.scss
new file mode 100644
index 00000000..ee69442c
--- /dev/null
+++ b/src/containers/lab/LabLayout/styles.module.scss
@@ -0,0 +1,20 @@
+@import "~/styles/variables.scss";
+
+.wrap {
+  display: grid;
+  grid-template-columns: 3fr 1fr;
+  column-gap: $gap;
+}
+
+.panel {
+  margin-top: -7px;
+}
+
+.tags {
+  display: flex;
+  flex-wrap: wrap;
+
+  & > * {
+    margin: 0 $gap $gap 0;
+  }
+}
diff --git a/src/containers/main/Container/styles.module.scss b/src/containers/main/Container/styles.module.scss
index 7fe7bafa..f6decbd0 100644
--- a/src/containers/main/Container/styles.module.scss
+++ b/src/containers/main/Container/styles.module.scss
@@ -8,4 +8,8 @@
   @include tablet {
     padding: 0;
   }
+
+  @media (max-width: $content_width + $gap * 4) {
+    padding: 0;
+  }
 }
diff --git a/src/containers/main/MainRouter/index.tsx b/src/containers/main/MainRouter/index.tsx
index bed1579a..ac8988dc 100644
--- a/src/containers/main/MainRouter/index.tsx
+++ b/src/containers/main/MainRouter/index.tsx
@@ -6,10 +6,14 @@ import { BorisLayout } from '~/containers/node/BorisLayout';
 import { ErrorNotFound } from '~/containers/pages/ErrorNotFound';
 import { ProfilePage } from '~/containers/profile/ProfilePage';
 import { Redirect, Route, Switch, useLocation } from 'react-router';
+import { LabLayout } from '~/containers/lab/LabLayout';
+import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
+import { selectAuthUser } from '~/redux/auth/selectors';
 
 interface IProps {}
 
 const MainRouter: FC<IProps> = () => {
+  const { is_user } = useShallowSelect(selectAuthUser);
   const location = useLocation();
 
   return (
@@ -20,6 +24,12 @@ const MainRouter: FC<IProps> = () => {
       <Route path={URLS.ERRORS.NOT_FOUND} component={ErrorNotFound} />
       <Route path={URLS.PROFILE_PAGE(':username')} component={ProfilePage} />
 
+      {is_user && (
+        <>
+          <Route exact path={URLS.LAB} component={LabLayout} />
+        </>
+      )}
+
       <Redirect to="/" />
     </Switch>
   );
diff --git a/src/containers/node/BorisLayout/index.tsx b/src/containers/node/BorisLayout/index.tsx
index 57127226..42a93e70 100644
--- a/src/containers/node/BorisLayout/index.tsx
+++ b/src/containers/node/BorisLayout/index.tsx
@@ -1,25 +1,30 @@
-import React, { FC, useEffect } from 'react';
+import React, { FC, useCallback, useEffect } from 'react';
 import { selectNode, selectNodeComments } from '~/redux/node/selectors';
-import { selectUser } from '~/redux/auth/selectors';
+import { selectAuthIsTester, selectUser } from '~/redux/auth/selectors';
 import { useDispatch } from 'react-redux';
-import { NodeComments } from '~/components/node/NodeComments';
 import styles from './styles.module.scss';
 import { Group } from '~/components/containers/Group';
 import boris from '~/sprites/boris_robot.svg';
-import { NodeNoComments } from '~/components/node/NodeNoComments';
 import { useRandomPhrase } from '~/constants/phrases';
-import { NodeCommentForm } from '~/components/node/NodeCommentForm';
 import isBefore from 'date-fns/isBefore';
-import { Card } from '~/components/containers/Card';
-import { Footer } from '~/components/main/Footer';
-import { Sticky } from '~/components/containers/Sticky';
 import { BorisStats } from '~/components/boris/BorisStats';
 import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
 import { selectBorisStats } from '~/redux/boris/selectors';
-import { authSetUser } from '~/redux/auth/actions';
+import { authSetState, authSetUser } from '~/redux/auth/actions';
 import { nodeLoadNode } from '~/redux/node/actions';
 import { borisLoadStats } from '~/redux/boris/actions';
 import { Container } from '~/containers/main/Container';
+import StickyBox from 'react-sticky-box/dist/esnext';
+import { BorisComments } from '~/components/boris/BorisComments';
+import { URLS } from '~/constants/urls';
+import { Route, Switch, Link } from 'react-router-dom';
+import { BorisUIDemo } from '~/components/boris/BorisUIDemo';
+import { BorisSuperpowers } from '~/components/boris/BorisSuperpowers';
+import { Superpower } from '~/components/boris/Superpower';
+import { Tabs } from '~/components/dialogs/Tabs';
+import { Tab } from '~/components/dialogs/Tab';
+import { useHistory, useLocation } from 'react-router';
+import { Card } from '~/components/containers/Card';
 
 type IProps = {};
 
@@ -30,6 +35,7 @@ const BorisLayout: FC<IProps> = () => {
   const user = useShallowSelect(selectUser);
   const stats = useShallowSelect(selectBorisStats);
   const comments = useShallowSelect(selectNodeComments);
+  const is_tester = useShallowSelect(selectAuthIsTester);
 
   useEffect(() => {
     const last_comment = comments[0];
@@ -55,6 +61,16 @@ const BorisLayout: FC<IProps> = () => {
     dispatch(borisLoadStats());
   }, [dispatch]);
 
+  const setBetaTester = useCallback(
+    (is_tester: boolean) => {
+      dispatch(authSetState({ is_tester }));
+    },
+    [dispatch]
+  );
+
+  const history = useHistory();
+  const location = useLocation();
+
   return (
     <Container>
       <div className={styles.wrap}>
@@ -70,26 +86,40 @@ const BorisLayout: FC<IProps> = () => {
 
         <div className={styles.container}>
           <Card className={styles.content}>
-            <Group className={styles.grid}>
-              {user.is_user && <NodeCommentForm isBefore nodeId={node.current.id} />}
+            <Superpower>
+              <Tabs>
+                <Tab
+                  active={location.pathname === URLS.BORIS}
+                  onClick={() => history.push(URLS.BORIS)}
+                >
+                  Комментарии
+                </Tab>
 
-              {node.is_loading_comments ? (
-                <NodeNoComments is_loading count={7} />
-              ) : (
-                <NodeComments
-                  comments={comments}
-                  count={node.comment_count}
-                  user={user}
-                  order="ASC"
+                <Tab
+                  active={location.pathname === `${URLS.BORIS}/ui`}
+                  onClick={() => history.push(`${URLS.BORIS}/ui`)}
+                >
+                  UI Demo
+                </Tab>
+              </Tabs>
+            </Superpower>
+
+            {
+              <Switch>
+                <Route path={`${URLS.BORIS}/ui`} component={BorisUIDemo} />
+
+                <BorisComments
+                  isLoadingComments={node.is_loading_comments}
+                  commentCount={node.comment_count}
+                  node={node.current}
+                  comments={node.comments}
                 />
-              )}
-            </Group>
-
-            <Footer />
+              </Switch>
+            }
           </Card>
 
           <Group className={styles.stats}>
-            <Sticky>
+            <StickyBox className={styles.sticky} offsetTop={72} offsetBottom={10}>
               <Group className={styles.stats__container}>
                 <div className={styles.stats__about}>
                   <h4>Господи-боженьки, где это я?</h4>
@@ -102,11 +132,15 @@ const BorisLayout: FC<IProps> = () => {
                   <p className="grey">//&nbsp;Такова&nbsp;жизнь.</p>
                 </div>
 
+                <div>
+                  {user.is_user && <BorisSuperpowers active={is_tester} onChange={setBetaTester} />}
+                </div>
+
                 <div className={styles.stats__wrap}>
                   <BorisStats stats={stats} />
                 </div>
               </Group>
-            </Sticky>
+            </StickyBox>
           </Group>
         </div>
       </div>
diff --git a/src/containers/node/BorisLayout/styles.module.scss b/src/containers/node/BorisLayout/styles.module.scss
index f6e3c8ad..c29e2f60 100644
--- a/src/containers/node/BorisLayout/styles.module.scss
+++ b/src/containers/node/BorisLayout/styles.module.scss
@@ -7,22 +7,6 @@
   flex-direction: column;
 }
 
-.content {
-  flex: 4;
-  z-index: 2;
-  border-radius: $radius;
-  padding: 0;
-  background: $node_bg;
-  box-shadow: inset transparentize(mix($wisegreen, white, 60%), 0.6) 0 1px;
-
-  @include desktop {
-    flex: 2.5;
-  }
-
-  @media(max-width: 1024px) {
-    flex: 2;
-  }
-}
 
 .grid {
   padding: $gap;
@@ -36,7 +20,7 @@
   width: 100%;
   height: 100vh;
   overflow: hidden;
-  background: 50% 0% no-repeat url('../../../sprites/boris_bg.svg');
+  background: 50% 0 no-repeat url('../../../sprites/boris_bg.svg');
   background-size: cover;
 }
 
@@ -167,3 +151,9 @@
     }
   }
 }
+
+.content {
+  position: relative;
+  z-index: 1;
+  flex: 3;
+}
diff --git a/src/containers/node/NodeLayout/index.tsx b/src/containers/node/NodeLayout/index.tsx
index 589b9072..35dd2cf2 100644
--- a/src/containers/node/NodeLayout/index.tsx
+++ b/src/containers/node/NodeLayout/index.tsx
@@ -40,7 +40,7 @@ const NodeLayout: FC<IProps> = memo(
     const { head, block } = useNodeBlocks(current, is_loading);
 
     return (
-      <>
+      <div className={styles.wrap}>
         {head}
 
         <Container>
@@ -64,7 +64,7 @@ const NodeLayout: FC<IProps> = memo(
         </Container>
 
         <SidebarRouter prefix="/post:id" />
-      </>
+      </div>
     );
   }
 );
diff --git a/src/containers/node/NodeLayout/styles.module.scss b/src/containers/node/NodeLayout/styles.module.scss
index f799e1a4..0a824c0d 100644
--- a/src/containers/node/NodeLayout/styles.module.scss
+++ b/src/containers/node/NodeLayout/styles.module.scss
@@ -2,6 +2,7 @@
 
 .content {
   align-items: stretch !important;
+
   @include vertical_at_tablet;
 }
 
diff --git a/src/containers/profile/ProfileLayout/index.tsx b/src/containers/profile/ProfileLayout/index.tsx
index 24d623dd..bf1f9072 100644
--- a/src/containers/profile/ProfileLayout/index.tsx
+++ b/src/containers/profile/ProfileLayout/index.tsx
@@ -29,7 +29,9 @@ const ProfileLayoutUnconnected: FC<IProps> = ({ history, nodeSetCoverImage }) =>
   useEffect(() => {
     if (user && user.id && user.cover) {
       nodeSetCoverImage(user.cover);
-      return () => nodeSetCoverImage(null);
+      return () => {
+        nodeSetCoverImage(undefined);
+      };
     }
   }, [user]);
 
diff --git a/src/containers/profile/ProfileTabs/index.tsx b/src/containers/profile/ProfileTabs/index.tsx
index d576d4e2..1f893442 100644
--- a/src/containers/profile/ProfileTabs/index.tsx
+++ b/src/containers/profile/ProfileTabs/index.tsx
@@ -2,6 +2,8 @@ import React, { FC, useCallback } from 'react';
 import styles from './styles.module.scss';
 import classNames from 'classnames';
 import { IAuthState } from '~/redux/auth/types';
+import { Tabs } from '~/components/dialogs/Tabs';
+import { Tab } from '~/components/dialogs/Tab';
 
 interface IProps {
   tab: string;
@@ -20,28 +22,20 @@ const ProfileTabs: FC<IProps> = ({ tab, is_own, setTab }) => {
 
   return (
     <div className={styles.wrap}>
-      <div
-        className={classNames(styles.tab, { [styles.active]: tab === 'profile' })}
-        onClick={changeTab('profile')}
-      >
-        Профиль
-      </div>
-      <div
-        className={classNames(styles.tab, { [styles.active]: tab === 'messages' })}
-        onClick={changeTab('messages')}
-      >
-        Сообщения
-      </div>
-      {is_own && (
-        <>
-          <div
-            className={classNames(styles.tab, { [styles.active]: tab === 'settings' })}
-            onClick={changeTab('settings')}
-          >
+      <Tabs>
+        <Tab active={tab === 'profile'} onClick={changeTab('profile')}>
+          Профиль
+        </Tab>
+
+        <Tab active={tab === 'messages'} onClick={changeTab('messages')}>
+          Сообщения
+        </Tab>
+        {is_own && (
+          <Tab active={tab === 'settings'} onClick={changeTab('settings')}>
             Настройки
-          </div>
-        </>
-      )}
+          </Tab>
+        )}
+      </Tabs>
     </div>
   );
 };
diff --git a/src/containers/profile/ProfileTabs/styles.module.scss b/src/containers/profile/ProfileTabs/styles.module.scss
index 29f68b7b..929f2a53 100644
--- a/src/containers/profile/ProfileTabs/styles.module.scss
+++ b/src/containers/profile/ProfileTabs/styles.module.scss
@@ -1,24 +1,6 @@
 @import "src/styles/variables";
 
 .wrap {
-  display: flex;
-  align-items: flex-start;
-  justify-content: flex-start;
   margin: $gap * 2 0 0 0;
   padding: 0 $gap;
 }
-
-.tab {
-  @include outer_shadow();
-
-  padding: $gap;
-  margin-right: $gap;
-  border-radius: $radius $radius 0 0;
-  font: $font_14_semibold;
-  text-transform: uppercase;
-  cursor: pointer;
-
-  &.active {
-    background: lighten($content_bg, 4%);
-  }
-}
diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts
index 33614334..f0b333d4 100644
--- a/src/react-app-env.d.ts
+++ b/src/react-app-env.d.ts
@@ -3,5 +3,6 @@ declare namespace NodeJS {
   interface ProcessEnv {
     readonly REACT_APP_API_URL: string;
     readonly REACT_APP_REMOTE_CURRENT: string;
+    readonly REACT_APP_LAB_ENABLED: string;
   }
 }
diff --git a/src/redux/auth/actions.ts b/src/redux/auth/actions.ts
index 9c2dd8c9..311414c2 100644
--- a/src/redux/auth/actions.ts
+++ b/src/redux/auth/actions.ts
@@ -20,6 +20,11 @@ export const authSetToken = (token: IAuthState['token']) => ({
   token,
 });
 
+export const authSetState = (payload: Partial<IAuthState>) => ({
+  type: AUTH_USER_ACTIONS.SET_STATE,
+  payload,
+});
+
 export const gotAuthPostMessage = ({ token }: { token: string }) => ({
   type: AUTH_USER_ACTIONS.GOT_AUTH_POST_MESSAGE,
   token,
diff --git a/src/redux/auth/api.ts b/src/redux/auth/api.ts
index 02869974..0224759f 100644
--- a/src/redux/auth/api.ts
+++ b/src/redux/auth/api.ts
@@ -1,6 +1,5 @@
-import { api, cleanResult, errorMiddleware, resultMiddleware } from '~/utils/api';
+import { api, cleanResult } from '~/utils/api';
 import { API } from '~/constants/api';
-import { IResultWithStatus } from '~/redux/types';
 import {
   ApiAttachSocialRequest,
   ApiAttachSocialResult,
diff --git a/src/redux/auth/constants.ts b/src/redux/auth/constants.ts
index d2959fdc..21e6f4ca 100644
--- a/src/redux/auth/constants.ts
+++ b/src/redux/auth/constants.ts
@@ -3,6 +3,7 @@ import { IToken, IUser } from '~/redux/auth/types';
 export const AUTH_USER_ACTIONS = {
   SEND_LOGIN_REQUEST: 'SEND_LOGIN_REQUEST',
   SET_LOGIN_ERROR: 'SET_LOGIN_ERROR',
+  SET_STATE: 'SET_STATE',
   SET_USER: 'SET_USER',
   SET_TOKEN: 'SET_TOKEN',
 
diff --git a/src/redux/auth/handlers.ts b/src/redux/auth/handlers.ts
index 210c919b..736ddf86 100644
--- a/src/redux/auth/handlers.ts
+++ b/src/redux/auth/handlers.ts
@@ -25,6 +25,11 @@ const setUser: ActionHandler<typeof ActionCreators.authSetUser> = (state, { prof
   },
 });
 
+const setState: ActionHandler<typeof ActionCreators.authSetState> = (state, { payload }) => ({
+  ...state,
+  ...payload,
+});
+
 const setToken: ActionHandler<typeof ActionCreators.authSetToken> = (state, { token }) => ({
   ...state,
   token,
@@ -104,6 +109,7 @@ const setRegisterSocialErrors: ActionHandler<typeof ActionCreators.authSetRegist
 export const AUTH_USER_HANDLERS = {
   [AUTH_USER_ACTIONS.SET_LOGIN_ERROR]: setLoginError,
   [AUTH_USER_ACTIONS.SET_USER]: setUser,
+  [AUTH_USER_ACTIONS.SET_STATE]: setState,
   [AUTH_USER_ACTIONS.SET_TOKEN]: setToken,
   [AUTH_USER_ACTIONS.SET_PROFILE]: setProfile,
   [AUTH_USER_ACTIONS.SET_UPDATES]: setUpdates,
diff --git a/src/redux/auth/index.ts b/src/redux/auth/index.ts
index 357cb37e..941f13a3 100644
--- a/src/redux/auth/index.ts
+++ b/src/redux/auth/index.ts
@@ -10,6 +10,7 @@ const HANDLERS = {
 const INITIAL_STATE: IAuthState = {
   token: '',
   user: { ...EMPTY_USER },
+  is_tester: false,
 
   updates: {
     last: '',
diff --git a/src/redux/auth/selectors.ts b/src/redux/auth/selectors.ts
index 6f6ed43b..0b789bef 100644
--- a/src/redux/auth/selectors.ts
+++ b/src/redux/auth/selectors.ts
@@ -2,6 +2,7 @@ import { IState } from '~/redux/store';
 
 export const selectAuth = (state: IState) => state.auth;
 export const selectUser = (state: IState) => state.auth.user;
+export const selectAuthIsTester = (state: IState) => state.auth.is_tester;
 export const selectToken = (state: IState) => state.auth.token;
 export const selectAuthLogin = (state: IState) => state.auth.login;
 export const selectAuthProfile = (state: IState) => state.auth.profile;
diff --git a/src/redux/auth/types.ts b/src/redux/auth/types.ts
index 55d7ae82..eb7aa395 100644
--- a/src/redux/auth/types.ts
+++ b/src/redux/auth/types.ts
@@ -37,6 +37,8 @@ export type IAuthState = Readonly<{
   user: IUser;
   token: string;
 
+  is_tester: boolean;
+
   updates: {
     last: string;
     notifications: INotification[];
diff --git a/src/redux/boris/api.ts b/src/redux/boris/api.ts
index c1bd5a72..d8cc8867 100644
--- a/src/redux/boris/api.ts
+++ b/src/redux/boris/api.ts
@@ -1,10 +1,20 @@
 import git from '~/stats/git.json';
 import { API } from '~/constants/api';
-import { api, resultMiddleware, errorMiddleware, cleanResult } from '~/utils/api';
+import { api, cleanResult } from '~/utils/api';
 import { IBorisState, IStatBackend } from './reducer';
-import { IResultWithStatus } from '../types';
+import axios from 'axios';
+import { IGetGithubIssuesResult } from '~/redux/boris/types';
 
 export const getBorisGitStats = () => Promise.resolve<IBorisState['stats']['git']>(git);
 
 export const getBorisBackendStats = () =>
   api.get<IStatBackend>(API.BORIS.GET_BACKEND_STATS).then(cleanResult);
+
+export const getGithubIssues = () => {
+  return axios
+    .get<IGetGithubIssuesResult>('https://api.github.com/repos/muerwre/vault-frontend/issues', {
+      params: { state: 'all', sort: 'created' },
+    })
+    .then(result => result.data)
+    .catch(() => []);
+};
diff --git a/src/redux/boris/reducer.ts b/src/redux/boris/reducer.ts
index 2032c793..5e182674 100644
--- a/src/redux/boris/reducer.ts
+++ b/src/redux/boris/reducer.ts
@@ -1,5 +1,6 @@
 import { createReducer } from '~/utils/reducer';
 import { BORIS_HANDLERS } from './handlers';
+import { IGithubIssue } from '~/redux/boris/types';
 
 export type IStatGitRow = {
   commit: string;
@@ -31,6 +32,7 @@ export type IStatBackend = {
 export type IBorisState = Readonly<{
   stats: {
     git: Partial<IStatGitRow>[];
+    issues: IGithubIssue[];
     backend?: IStatBackend;
     is_loading: boolean;
   };
@@ -39,6 +41,7 @@ export type IBorisState = Readonly<{
 const BORIS_INITIAL_STATE: IBorisState = {
   stats: {
     git: [],
+    issues: [],
     backend: undefined,
     is_loading: false,
   },
diff --git a/src/redux/boris/sagas.ts b/src/redux/boris/sagas.ts
index a0b1d003..b17e2c16 100644
--- a/src/redux/boris/sagas.ts
+++ b/src/redux/boris/sagas.ts
@@ -1,17 +1,17 @@
-import { takeLatest, put, call } from 'redux-saga/effects';
+import { call, put, takeLatest } from 'redux-saga/effects';
 import { BORIS_ACTIONS } from './constants';
 import { borisSetStats } from './actions';
-import { getBorisGitStats, getBorisBackendStats } from './api';
+import { getBorisBackendStats, getGithubIssues } from './api';
 import { Unwrap } from '../types';
 
 function* loadStats() {
   try {
     yield put(borisSetStats({ is_loading: true }));
 
-    const git: Unwrap<typeof getBorisGitStats> = yield call(getBorisGitStats);
     const backend: Unwrap<typeof getBorisBackendStats> = yield call(getBorisBackendStats);
+    const issues: Unwrap<typeof getGithubIssues> = yield call(getGithubIssues);
 
-    yield put(borisSetStats({ git, backend }));
+    yield put(borisSetStats({ issues, backend }));
   } catch (e) {
     yield put(borisSetStats({ git: [], backend: undefined }));
   } finally {
diff --git a/src/redux/boris/types.ts b/src/redux/boris/types.ts
new file mode 100644
index 00000000..73552b25
--- /dev/null
+++ b/src/redux/boris/types.ts
@@ -0,0 +1,12 @@
+export interface IGithubIssue {
+  id: string;
+  url: string;
+  html_url: string;
+  body: string;
+  title: string;
+  state: 'open' | 'closed';
+  created_at: string;
+  pull_request?: unknown;
+}
+
+export type IGetGithubIssuesResult = IGithubIssue[];
diff --git a/src/redux/lab/actions.ts b/src/redux/lab/actions.ts
new file mode 100644
index 00000000..1e1ff97a
--- /dev/null
+++ b/src/redux/lab/actions.ts
@@ -0,0 +1,12 @@
+import { LAB_ACTIONS } from '~/redux/lab/constants';
+import { ILabState } from '~/redux/lab/types';
+
+export const labGetList = (after?: string) => ({
+  type: LAB_ACTIONS.GET_LIST,
+  after,
+});
+
+export const labSetList = (list: Partial<ILabState['list']>) => ({
+  type: LAB_ACTIONS.SET_LIST,
+  list,
+});
diff --git a/src/redux/lab/api.ts b/src/redux/lab/api.ts
new file mode 100644
index 00000000..5fa97bc0
--- /dev/null
+++ b/src/redux/lab/api.ts
@@ -0,0 +1,8 @@
+import { api, cleanResult } from '~/utils/api';
+import { API } from '~/constants/api';
+import { GetLabNodesRequest, GetLabNodesResult } from '~/redux/lab/types';
+
+export const getLabNodes = ({ after }: GetLabNodesRequest) =>
+  api
+    .get<GetLabNodesResult>(API.LAB.NODES, { params: { after } })
+    .then(cleanResult);
diff --git a/src/redux/lab/constants.ts b/src/redux/lab/constants.ts
new file mode 100644
index 00000000..d2e670da
--- /dev/null
+++ b/src/redux/lab/constants.ts
@@ -0,0 +1,6 @@
+const prefix = 'LAB.';
+
+export const LAB_ACTIONS = {
+  GET_LIST: `${prefix}GET_LIST`,
+  SET_LIST: `${prefix}SET_LIST`,
+};
diff --git a/src/redux/lab/handlers.ts b/src/redux/lab/handlers.ts
new file mode 100644
index 00000000..b09812e2
--- /dev/null
+++ b/src/redux/lab/handlers.ts
@@ -0,0 +1,20 @@
+import { LAB_ACTIONS } from '~/redux/lab/constants';
+import { labSetList } from '~/redux/lab/actions';
+import { ILabState } from '~/redux/lab/types';
+
+type LabHandler<T extends (...args: any) => any> = (
+  state: Readonly<ILabState>,
+  payload: ReturnType<T>
+) => Readonly<ILabState>;
+
+const setList: LabHandler<typeof labSetList> = (state, { list }) => ({
+  ...state,
+  list: {
+    ...state.list,
+    ...list,
+  },
+});
+
+export const LAB_HANDLERS = {
+  [LAB_ACTIONS.SET_LIST]: setList,
+};
diff --git a/src/redux/lab/index.ts b/src/redux/lab/index.ts
new file mode 100644
index 00000000..56879a52
--- /dev/null
+++ b/src/redux/lab/index.ts
@@ -0,0 +1,14 @@
+import { createReducer } from '~/utils/reducer';
+import { LAB_HANDLERS } from '~/redux/lab/handlers';
+import { ILabState } from '~/redux/lab/types';
+
+const INITIAL_STATE: ILabState = {
+  list: {
+    is_loading: false,
+    nodes: [],
+    count: 0,
+    error: '',
+  },
+};
+
+export default createReducer(INITIAL_STATE, LAB_HANDLERS);
diff --git a/src/redux/lab/sagas.ts b/src/redux/lab/sagas.ts
new file mode 100644
index 00000000..5fc48b8b
--- /dev/null
+++ b/src/redux/lab/sagas.ts
@@ -0,0 +1,21 @@
+import { takeLeading, call, put } from 'redux-saga/effects';
+import { labGetList, labSetList } from '~/redux/lab/actions';
+import { LAB_ACTIONS } from '~/redux/lab/constants';
+import { Unwrap } from '~/redux/types';
+import { getLabNodes } from '~/redux/lab/api';
+
+function* getList({ after = '' }: ReturnType<typeof labGetList>) {
+  try {
+    yield put(labSetList({ is_loading: true }));
+    const { nodes, count }: Unwrap<typeof getLabNodes> = yield call(getLabNodes, { after });
+    yield put(labSetList({ nodes, count }));
+  } catch (error) {
+    yield put(labSetList({ error: error.message }));
+  } finally {
+    yield put(labSetList({ is_loading: false }));
+  }
+}
+
+export default function* labSaga() {
+  yield takeLeading(LAB_ACTIONS.GET_LIST, getList);
+}
diff --git a/src/redux/lab/selectors.ts b/src/redux/lab/selectors.ts
new file mode 100644
index 00000000..0854ac25
--- /dev/null
+++ b/src/redux/lab/selectors.ts
@@ -0,0 +1,4 @@
+import { IState } from '~/redux/store';
+
+export const selectLab = (state: IState) => state.lab;
+export const selectLabListNodes = (state: IState) => state.lab.list.nodes;
diff --git a/src/redux/lab/types.ts b/src/redux/lab/types.ts
new file mode 100644
index 00000000..7614807b
--- /dev/null
+++ b/src/redux/lab/types.ts
@@ -0,0 +1,19 @@
+import { IError, INode } from '~/redux/types';
+
+export type ILabState = Readonly<{
+  list: {
+    is_loading: boolean;
+    nodes: INode[];
+    count: number;
+    error: IError;
+  };
+}>;
+
+export type GetLabNodesRequest = {
+  after?: string;
+};
+
+export type GetLabNodesResult = {
+  nodes: INode[];
+  count: number;
+};
diff --git a/src/redux/modal/sagas.ts b/src/redux/modal/sagas.ts
index b6b5ffb4..1f04582f 100644
--- a/src/redux/modal/sagas.ts
+++ b/src/redux/modal/sagas.ts
@@ -10,13 +10,23 @@ function* onPathChange({
   },
 }: LocationChangeAction) {
   if (pathname.match(/^\/~([\wа-яА-Я]+)/)) {
-    const [, username] = pathname.match(/^\/~([\wа-яА-Я]+)/);
-    return yield put(authOpenProfile(username));
+    const match = pathname.match(/^\/~([\wа-яА-Я]+)/);
+
+    if (!match || !match.length || !match[1]) {
+      return;
+    }
+
+    return yield put(authOpenProfile(match[1]));
   }
 
   if (pathname.match(/^\/restore\/([\w\-]+)/)) {
-    const [, code] = pathname.match(/^\/restore\/([\w\-]+)/);
-    return yield put(authShowRestoreModal(code));
+    const match = pathname.match(/^\/restore\/([\w\-]+)/);
+
+    if (!match || !match.length || !match[1]) {
+      return;
+    }
+
+    return yield put(authShowRestoreModal(match[1]));
   }
 }
 
diff --git a/src/redux/node/constants.ts b/src/redux/node/constants.ts
index b7664ba8..8cc79869 100644
--- a/src/redux/node/constants.ts
+++ b/src/redux/node/constants.ts
@@ -13,6 +13,7 @@ import { EditorAudioUploadButton } from '~/components/editors/EditorAudioUploadB
 import { EditorUploadCoverButton } from '~/components/editors/EditorUploadCoverButton';
 import { IEditorComponentProps, NodeEditorProps } from '~/redux/node/types';
 import { EditorFiller } from '~/components/editors/EditorFiller';
+import { EditorPublicSwitch } from '~/components/editors/EditorPublicSwitch';
 import { NodeImageSwiperBlock } from '~/components/node/NodeImageSwiperBlock';
 
 const prefix = 'NODE.';
@@ -59,6 +60,8 @@ export const EMPTY_NODE: INode = {
 
   blocks: [],
   tags: [],
+  is_public: true,
+  is_promoted: true,
 
   flow: {
     display: 'single',
@@ -112,14 +115,20 @@ export const NODE_EDITORS: Record<
 };
 
 export const NODE_PANEL_COMPONENTS: Record<string, FC<IEditorComponentProps>[]> = {
-  [NODE_TYPES.TEXT]: [EditorFiller, EditorUploadCoverButton],
-  [NODE_TYPES.VIDEO]: [EditorFiller, EditorUploadCoverButton],
-  [NODE_TYPES.IMAGE]: [EditorImageUploadButton, EditorFiller, EditorUploadCoverButton],
+  [NODE_TYPES.TEXT]: [EditorFiller, EditorUploadCoverButton, EditorPublicSwitch],
+  [NODE_TYPES.VIDEO]: [EditorFiller, EditorUploadCoverButton, EditorPublicSwitch],
+  [NODE_TYPES.IMAGE]: [
+    EditorImageUploadButton,
+    EditorFiller,
+    EditorUploadCoverButton,
+    EditorPublicSwitch,
+  ],
   [NODE_TYPES.AUDIO]: [
     EditorAudioUploadButton,
     EditorImageUploadButton,
     EditorFiller,
     EditorUploadCoverButton,
+    EditorPublicSwitch,
   ],
 };
 
diff --git a/src/redux/store.ts b/src/redux/store.ts
index eb9c60ff..6b19ebe9 100644
--- a/src/redux/store.ts
+++ b/src/redux/store.ts
@@ -17,6 +17,10 @@ import nodeSaga from '~/redux/node/sagas';
 import flow, { IFlowState } from '~/redux/flow/reducer';
 import flowSaga from '~/redux/flow/sagas';
 
+import lab from '~/redux/lab';
+import labSaga from '~/redux/lab/sagas';
+import { ILabState } from '~/redux/lab/types';
+
 import uploads, { IUploadState } from '~/redux/uploads/reducer';
 import uploadSaga from '~/redux/uploads/sagas';
 
@@ -42,7 +46,7 @@ import { assocPath } from 'ramda';
 
 const authPersistConfig: PersistConfig = {
   key: 'auth',
-  whitelist: ['token', 'user', 'updates'],
+  whitelist: ['token', 'user', 'updates', 'is_tester'],
   storage,
 };
 
@@ -69,13 +73,16 @@ export interface IState {
   boris: IBorisState;
   messages: IMessagesState;
   tag: ITagState;
+  lab: ILabState;
 }
 
 export const sagaMiddleware = createSagaMiddleware();
 export const history = createBrowserHistory();
 
 const composeEnhancers =
-  typeof window === 'object' && (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
+  typeof window === 'object' &&
+  (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
+  process.env.NODE_ENV === 'development'
     ? (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
     : compose;
 
@@ -91,6 +98,7 @@ export const store = createStore(
     player: persistReducer(playerPersistConfig, player),
     messages,
     tag: tag,
+    lab: lab,
   }),
   composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware))
 );
@@ -108,6 +116,7 @@ export function configureStore(): {
   sagaMiddleware.run(borisSaga);
   sagaMiddleware.run(messagesSaga);
   sagaMiddleware.run(tagSaga);
+  sagaMiddleware.run(labSaga);
 
   window.addEventListener('message', message => {
     if (message && message.data && message.data.type === 'oauth_login' && message.data.token)
diff --git a/src/redux/tag/sagas.ts b/src/redux/tag/sagas.ts
index eb1c3f16..90c5cf1f 100644
--- a/src/redux/tag/sagas.ts
+++ b/src/redux/tag/sagas.ts
@@ -11,7 +11,7 @@ import { apiGetTagSuggestions, apiGetNodesOfTag } from '~/redux/tag/api';
 import { Unwrap } from '~/redux/types';
 
 function* loadTagNodes({ tag }: ReturnType<typeof tagLoadNodes>) {
-  yield put(tagSetNodes({ isLoading: true, list: [] }));
+  yield put(tagSetNodes({ isLoading: true }));
 
   try {
     const { list }: ReturnType<typeof selectTagNodes> = yield select(selectTagNodes);
diff --git a/src/redux/types.ts b/src/redux/types.ts
index 8dd92998..f78309a3 100644
--- a/src/redux/types.ts
+++ b/src/redux/types.ts
@@ -124,6 +124,8 @@ export interface INode {
   description?: string;
   is_liked?: boolean;
   is_heroic?: boolean;
+  is_promoted?: boolean;
+  is_public?: boolean;
   like_count?: number;
 
   flow: {
diff --git a/src/sprites/Sprites.tsx b/src/sprites/Sprites.tsx
index dbd5b6e2..f822d726 100644
--- a/src/sprites/Sprites.tsx
+++ b/src/sprites/Sprites.tsx
@@ -255,6 +255,16 @@ const Sprites: FC<{}> = () => (
       <path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z" />
     </g>
 
+    <g id="waves" stroke="none">
+      <path fill="none" d="M0 0h24v24H0V0z" />
+      <path d="M17 16.99c-1.35 0-2.2.42-2.95.8-.65.33-1.18.6-2.05.6-.9 0-1.4-.25-2.05-.6-.75-.38-1.57-.8-2.95-.8s-2.2.42-2.95.8c-.65.33-1.17.6-2.05.6v1.95c1.35 0 2.2-.42 2.95-.8.65-.33 1.17-.6 2.05-.6s1.4.25 2.05.6c.75.38 1.57.8 2.95.8s2.2-.42 2.95-.8c.65-.33 1.18-.6 2.05-.6.9 0 1.4.25 2.05.6.75.38 1.58.8 2.95.8v-1.95c-.9 0-1.4-.25-2.05-.6-.75-.38-1.6-.8-2.95-.8zm0-4.45c-1.35 0-2.2.43-2.95.8-.65.32-1.18.6-2.05.6-.9 0-1.4-.25-2.05-.6-.75-.38-1.57-.8-2.95-.8s-2.2.43-2.95.8c-.65.32-1.17.6-2.05.6v1.95c1.35 0 2.2-.43 2.95-.8.65-.35 1.15-.6 2.05-.6s1.4.25 2.05.6c.75.38 1.57.8 2.95.8s2.2-.43 2.95-.8c.65-.35 1.15-.6 2.05-.6s1.4.25 2.05.6c.75.38 1.58.8 2.95.8v-1.95c-.9 0-1.4-.25-2.05-.6-.75-.38-1.6-.8-2.95-.8zm2.95-8.08c-.75-.38-1.58-.8-2.95-.8s-2.2.42-2.95.8c-.65.32-1.18.6-2.05.6-.9 0-1.4-.25-2.05-.6-.75-.37-1.57-.8-2.95-.8s-2.2.42-2.95.8c-.65.33-1.17.6-2.05.6v1.93c1.35 0 2.2-.43 2.95-.8.65-.33 1.17-.6 2.05-.6s1.4.25 2.05.6c.75.38 1.57.8 2.95.8s2.2-.43 2.95-.8c.65-.32 1.18-.6 2.05-.6.9 0 1.4.25 2.05.6.75.38 1.58.8 2.95.8V5.04c-.9 0-1.4-.25-2.05-.58zM17 8.09c-1.35 0-2.2.43-2.95.8-.65.35-1.15.6-2.05.6s-1.4-.25-2.05-.6c-.75-.38-1.57-.8-2.95-.8s-2.2.43-2.95.8c-.65.35-1.15.6-2.05.6v1.95c1.35 0 2.2-.43 2.95-.8.65-.32 1.18-.6 2.05-.6s1.4.25 2.05.6c.75.38 1.57.8 2.95.8s2.2-.43 2.95-.8c.65-.32 1.18-.6 2.05-.6.9 0 1.4.25 2.05.6.75.38 1.58.8 2.95.8V9.49c-.9 0-1.4-.25-2.05-.6-.75-.38-1.6-.8-2.95-.8z" />
+    </g>
+
+    <g id="lab" stroke="none">
+      <path fill="none" d="M0 0h24v24H0V0z" />
+      <path d="M13,11.33L18,18H6l5-6.67V6h2 M15.96,4H8.04C7.62,4,7.39,4.48,7.65,4.81L9,6.5v4.17L3.2,18.4C2.71,19.06,3.18,20,4,20h16 c0.82,0,1.29-0.94,0.8-1.6L15,10.67V6.5l1.35-1.69C16.61,4.48,16.38,4,15.96,4L15.96,4z" />
+    </g>
+
     <g id="search">
       <path fill="none" d="M0 0h24v24H0V0z" stroke="none" />
       <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
diff --git a/src/styles/_colors.scss b/src/styles/_colors.scss
index 0319f9ab..3a291541 100644
--- a/src/styles/_colors.scss
+++ b/src/styles/_colors.scss
@@ -3,7 +3,7 @@
 $red: #ff3344;
 $yellow: #ffd60f;
 $dark_blue: #3c75ff;
-$blue: #3ca1ff;
+$blue: #582cd0;
 $green: #00d2b9;
 //$green: #00503c;
 $olive: #8bc12a;
diff --git a/src/styles/common/markdown.module.scss b/src/styles/common/markdown.module.scss
index 9cfcb7e1..82cb22a5 100644
--- a/src/styles/common/markdown.module.scss
+++ b/src/styles/common/markdown.module.scss
@@ -55,6 +55,10 @@ $margin: 1em;
 
   p {
     margin-bottom: $margin;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
   }
 
   h5, h4, h3, h2, h1 {
diff --git a/src/styles/variables.scss b/src/styles/variables.scss
index 8577202c..a518e5d4 100644
--- a/src/styles/variables.scss
+++ b/src/styles/variables.scss
@@ -209,3 +209,13 @@ $sidebar_border: transparentize(white, 0.95);
   background: transparentize($content_bg, 0.4);
   box-shadow: transparentize(white, 0.95) -1px 0;
 }
+
+@mixin editor_round_button {
+  width: $upload_button_height;
+  height: $upload_button_height;
+  border-radius: ($upload_button_height / 2) !important;
+  flex: 0 0 $upload_button_height;
+  position: relative;
+  border-radius: $radius;
+  cursor: pointer;
+}
diff --git a/src/utils/hooks/node/useNodeAudios.ts b/src/utils/hooks/node/useNodeAudios.ts
index 7ece487f..5dc043a1 100644
--- a/src/utils/hooks/node/useNodeAudios.ts
+++ b/src/utils/hooks/node/useNodeAudios.ts
@@ -3,6 +3,10 @@ import { useMemo } from 'react';
 import { UPLOAD_TYPES } from '~/redux/uploads/constants';
 
 export const useNodeAudios = (node: INode) => {
+  if (!node?.files) {
+    return [];
+  }
+
   return useMemo(() => node.files.filter(file => file && file.type === UPLOAD_TYPES.AUDIO), [
     node.files,
   ]);
diff --git a/src/utils/hooks/node/useNodeImages.ts b/src/utils/hooks/node/useNodeImages.ts
index 4f6b71d5..375d6198 100644
--- a/src/utils/hooks/node/useNodeImages.ts
+++ b/src/utils/hooks/node/useNodeImages.ts
@@ -3,6 +3,10 @@ import { useMemo } from 'react';
 import { UPLOAD_TYPES } from '~/redux/uploads/constants';
 
 export const useNodeImages = (node: INode) => {
+  if (!node?.files) {
+    return [];
+  }
+
   return useMemo(() => node.files.filter(file => file && file.type === UPLOAD_TYPES.IMAGE), [
     node.files,
   ]);
diff --git a/src/utils/hooks/useInputPasteUpload.ts b/src/utils/hooks/useInputPasteUpload.ts
new file mode 100644
index 00000000..dff574a1
--- /dev/null
+++ b/src/utils/hooks/useInputPasteUpload.ts
@@ -0,0 +1,24 @@
+import { useCallback, useEffect } from 'react';
+import { getImageFromPaste } from '~/utils/uploader';
+
+// useInputPasteUpload attaches event listener to input, that calls onUpload if user pasted any image
+export const useInputPasteUpload = (
+  input: HTMLTextAreaElement | HTMLInputElement | undefined,
+  onUpload: (files: File[]) => void
+) => {
+  const onPaste = useCallback(async event => {
+    const image = await getImageFromPaste(event);
+
+    if (!image) return;
+
+    onUpload([image]);
+  }, []);
+
+  useEffect(() => {
+    if (!input) return;
+
+    input.addEventListener('paste', onPaste);
+
+    return () => input.removeEventListener('paste', onPaste);
+  }, [input, onPaste]);
+};
diff --git a/src/utils/uploader.ts b/src/utils/uploader.ts
index c1ad5941..3ade4cf8 100644
--- a/src/utils/uploader.ts
+++ b/src/utils/uploader.ts
@@ -74,3 +74,37 @@ export const fakeUploader = ({
 export const getFileType = (file: File): keyof typeof UPLOAD_TYPES | undefined =>
   (file.type && Object.keys(FILE_MIMES).find(mime => FILE_MIMES[mime].includes(file.type))) ||
   undefined;
+
+// getImageFromPaste returns any images from paste event
+export const getImageFromPaste = (event: ClipboardEvent): Promise<File | undefined> => {
+  const items = event.clipboardData?.items;
+
+  return new Promise(resolve => {
+    for (let index in items) {
+      const item = items[index];
+
+      if (item.kind === 'file' && item.type.match(/^image\//)) {
+        const blob = item.getAsFile();
+        const reader = new FileReader();
+        const type = item.type;
+
+        reader.onload = function(e) {
+          if (!e.target?.result) {
+            return;
+          }
+
+          resolve(
+            new File([e.target?.result], 'paste.png', {
+              type,
+              lastModified: new Date().getTime(),
+            })
+          );
+        };
+
+        reader.readAsArrayBuffer(blob);
+      }
+    }
+
+    // resolve(undefined);
+  });
+};
diff --git a/yarn.lock b/yarn.lock
index f0c085db..ff033f49 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1109,6 +1109,13 @@
   dependencies:
     regenerator-runtime "^0.13.4"
 
+"@babel/runtime@^7.1.5":
+  version "7.13.10"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
+  integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
+  dependencies:
+    regenerator-runtime "^0.13.4"
+
 "@babel/runtime@^7.10.5":
   version "7.13.7"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.7.tgz#d494e39d198ee9ca04f4dcb76d25d9d7a1dc961a"
@@ -1587,6 +1594,14 @@
   dependencies:
     "@babel/runtime" "^7.10.2"
 
+"@tippy.js/react@^3.1.1":
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/@tippy.js/react/-/react-3.1.1.tgz#027e4595e55f31430741fe8e0d92aaddfbe47efd"
+  integrity sha512-KF45vW/jKh/nBXk/2zzTFslv/T46zOMkIoDJ56ymZ+M00yHttk58J5wZ29oqGqDIUnobWSZD+cFpbR4u/UUvgw==
+  dependencies:
+    prop-types "^15.6.2"
+    tippy.js "^5.1.1"
+
 "@types/aria-query@^4.2.0":
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0"
@@ -1643,6 +1658,11 @@
     "@types/minimatch" "*"
     "@types/node" "*"
 
+"@types/history@*":
+  version "4.7.8"
+  resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
+  integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
+
 "@types/hoist-non-react-statics@^3.3.0":
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
@@ -1748,6 +1768,23 @@
     hoist-non-react-statics "^3.3.0"
     redux "^4.0.0"
 
+"@types/react-router-dom@^5.1.7":
+  version "5.1.7"
+  resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.7.tgz#a126d9ea76079ffbbdb0d9225073eb5797ab7271"
+  integrity sha512-D5mHD6TbdV/DNHYsnwBTv+y73ei+mMjrkGrla86HthE4/PVvL1J94Bu3qABU+COXzpL23T1EZapVVpwHuBXiUg==
+  dependencies:
+    "@types/history" "*"
+    "@types/react" "*"
+    "@types/react-router" "*"
+
+"@types/react-router@*":
+  version "5.1.12"
+  resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.12.tgz#0f300e09468e7aed86e18241c90238c18c377e51"
+  integrity sha512-0bhXQwHYfMeJlCh7mGhc0VJTRm0Gk+Z8T00aiP4702mDUuLs9SMhnd2DitpjWFjdOecx2UXtICK14H9iMnziGA==
+  dependencies:
+    "@types/history" "*"
+    "@types/react" "*"
+
 "@types/react@*":
   version "16.9.56"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.56.tgz#ea25847b53c5bec064933095fc366b1462e2adf0"
@@ -2637,10 +2674,10 @@ bluebird@^3.5.5:
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
   integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
 
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
-  version "4.11.9"
-  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
-  integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
+  version "4.12.0"
+  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
+  integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
 
 bn.js@^5.0.0, bn.js@^5.1.1:
   version "5.1.3"
@@ -2716,7 +2753,7 @@ braces@~3.0.2:
   dependencies:
     fill-range "^7.0.1"
 
-brorand@^1.0.1:
+brorand@^1.0.1, brorand@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
   integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
@@ -4200,17 +4237,17 @@ electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.591:
   integrity sha512-ctRyXD9y0mZu8pgeNwBUhLP3Guyr5YuqkfLKYmpTwYx7o9JtCEJme9JVX4xBXPr5ZNvr/iBXUvHLFEVJQThATg==
 
 elliptic@^6.5.3:
-  version "6.5.3"
-  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
-  integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
+  version "6.5.4"
+  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
+  integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
   dependencies:
-    bn.js "^4.4.0"
-    brorand "^1.0.1"
+    bn.js "^4.11.9"
+    brorand "^1.1.0"
     hash.js "^1.0.0"
-    hmac-drbg "^1.0.0"
-    inherits "^2.0.1"
-    minimalistic-assert "^1.0.0"
-    minimalistic-crypto-utils "^1.0.0"
+    hmac-drbg "^1.0.1"
+    inherits "^2.0.4"
+    minimalistic-assert "^1.0.1"
+    minimalistic-crypto-utils "^1.0.1"
 
 emoji-regex@^7.0.1, emoji-regex@^7.0.2:
   version "7.0.3"
@@ -5469,7 +5506,7 @@ history@^4.9.0:
     tiny-warning "^1.0.0"
     value-equal "^1.0.1"
 
-hmac-drbg@^1.0.0:
+hmac-drbg@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
   integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
@@ -7366,7 +7403,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
   resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
   integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
 
-minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
+minimalistic-crypto-utils@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
@@ -8323,6 +8360,11 @@ pnp-webpack-plugin@1.6.4:
   dependencies:
     ts-pnp "^1.1.6"
 
+popper.js@^1.16.0:
+  version "1.16.1"
+  resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
+  integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
+
 portfinder@^1.0.26:
   version "1.0.28"
   resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
@@ -9454,6 +9496,14 @@ react-sortable-hoc@^1.11:
     invariant "^2.2.4"
     prop-types "^15.5.7"
 
+react-sticky-box@^0.9.3:
+  version "0.9.3"
+  resolved "https://registry.yarnpkg.com/react-sticky-box/-/react-sticky-box-0.9.3.tgz#8450d4cef8e4fdd7b0351520365bc98c97da11af"
+  integrity sha512-Y/qO7vTqAvXuRR6G6ZCW4fX2Bz0GZRwiiLTVeZN5CVz9wzs37ev0Xj3KSKF/PzF0jifwATivI4t24qXG8rSz4Q==
+  dependencies:
+    "@babel/runtime" "^7.1.5"
+    resize-observer-polyfill "^1.5.1"
+
 react@^17.0.1:
   version "17.0.1"
   resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"
@@ -9780,6 +9830,11 @@ requires-port@^1.0.0:
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
   integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
 
+resize-observer-polyfill@^1.5.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
+  integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
+
 resize-sensor@^0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/resize-sensor/-/resize-sensor-0.0.6.tgz#75147dcb273de6832760e461d2e28de6dcf88c45"
@@ -10952,6 +11007,13 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
   resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
   integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
 
+tippy.js@^5.1.1:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-5.2.1.tgz#e08d7332c103a15e427124d710d881fca82365d6"
+  integrity sha512-66UT6JRVn3dXNCORE+0UvUK3JZqV/VhLlU6HTDm3FmrweUUFUxUGvT8tUQ7ycMp+uhuLAwQw6dBabyC+iKf/MA==
+  dependencies:
+    popper.js "^1.16.0"
+
 tmp@^0.0.33:
   version "0.0.33"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"