From 0e83ced25b612250d8126778288b7b44f3b91dfc Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 4 Apr 2022 09:42:52 +0300
Subject: refactored how main app layout works

---
 src/components/mobile_nav/mobile_nav.vue | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

(limited to 'src/components/mobile_nav/mobile_nav.vue')

diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index 0f0ea457..229f3006 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -92,8 +92,7 @@
 .MobileNav {
   .mobile-nav {
     display: grid;
-    line-height: 50px;
-    height: 50px;
+    line-height: var(--navbar-height);
     grid-template-rows: 50px;
     grid-template-columns: 2fr auto;
     width: 100%;
-- 
cgit v1.2.3-70-g09d2


From 71863061c691df7f59cf4be283a3e0772d485378 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 4 Apr 2022 19:41:09 +0300
Subject: fixed tons of stuff, at least it looks normalish on desktop

---
 src/App.js                                        |   6 +-
 src/App.scss                                      | 515 +++++++++++-----------
 src/App.vue                                       |  14 +-
 src/components/chat/chat.js                       |  46 +-
 src/components/chat/chat.scss                     |  16 +-
 src/components/chat/chat.vue                      |   6 +-
 src/components/chat/chat_layout_utils.js          |   1 +
 src/components/mobile_nav/mobile_nav.vue          |   2 +-
 src/components/notifications/notifications.vue    |   2 +-
 src/components/password_reset/password_reset.vue  |   3 +
 src/components/registration/registration.vue      |   5 +-
 src/components/settings_modal/settings_modal.scss |  12 +
 src/components/status/status.scss                 |   4 +
 src/components/timeline/timeline.js               |   6 +-
 src/components/timeline/timeline.scss             |   1 -
 src/components/user_card/user_card.vue            |   8 +-
 16 files changed, 320 insertions(+), 327 deletions(-)

(limited to 'src/components/mobile_nav/mobile_nav.vue')

diff --git a/src/App.js b/src/App.js
index c4360af5..4a59f412 100644
--- a/src/App.js
+++ b/src/App.js
@@ -81,11 +81,7 @@ export default {
     },
     isMobileLayout () { return this.$store.state.interface.mobileLayout },
     privateMode () { return this.$store.state.instance.private },
-    sidebarAlign () {
-      return {
-        'order': this.$store.getters.mergedConfig.sidebarRight ? 99 : 0
-      }
-    },
+    reverseLayout () { return this.$store.getters.mergedConfig.sidebarRight },
     ...mapGetters(['mergedConfig'])
   },
   methods: {
diff --git a/src/App.scss b/src/App.scss
index fa2c6a79..7e428407 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -1,7 +1,51 @@
+// stylelint-disable rscss/class-format
 @import './_variables.scss';
 
+html {
+  font-size: 14px;
+  overflow: hidden;
+  max-height: 100vh;
+}
+
 body {
   overflow: hidden;
+  max-height: 100vh;
+  max-width: 100vw;
+  font-family: sans-serif;
+  font-family: var(--interfaceFont, sans-serif);
+  margin: 0;
+  color: $fallback--text;
+  color: var(--text, $fallback--text);
+  overflow-x: hidden;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+
+  &.hidden {
+    display: none;
+  }
+}
+
+a {
+  text-decoration: none;
+  color: $fallback--link;
+  color: var(--link, $fallback--link);
+}
+
+h4 {
+  margin: 0;
+}
+
+nav {
+  z-index: 1000;
+  color: var(--topBarText);
+  background-color: $fallback--fg;
+  background-color: var(--topBar, $fallback--fg);
+  color: $fallback--faint;
+  color: var(--faint, $fallback--faint);
+  box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
+  box-shadow: var(--topBarShadow);
+  box-sizing: border-box;
+  height: var(--navbar-height);
 }
 
 #app-loaded {
@@ -12,14 +56,29 @@ body {
   --navbar-height: 50px;
 }
 
-.navbar {
-  height: var(--navbar-height);
+#content {
+  overscroll-behavior-y: none;
+  overflow-y: auto;
+  position: sticky;
+}
+
+#sidebar {
+  display: grid;
+  grid-template-columns: 100%;
+  row-gap: 1em;
+  grid-area: sidebar;
+  align-content: start;
+}
+
+#main-scroller {
+  grid-area: content;
 }
 
 .app-bg-wrapper {
   position: fixed;
-  z-index: -1;
   height: 100%;
+  top: var(--navbar-height);
+  z-index: -1000;
   left: 0;
   right: -20px;
   background-size: cover;
@@ -29,69 +88,66 @@ body {
   background-position: 50%;
 }
 
-h4 {
-  margin: 0;
-}
-
-#content {
-  overflow-y: auto;
-  position: sticky;
-}
-
 .app-layout {
   position: relative;
   display: grid;
-  grid-template-columns: auto auto;
+  grid-template-columns: auto minmax(auto, 1fr);
+  grid-template-areas: "sidebar content";
   grid-template-rows: 1fr;
   box-sizing: border-box;
   margin: 0 auto;
-  height: 100vh;
+  height: calc(100vh - var(--navbar-height));
   align-content: flex-start;
   flex-wrap: wrap;
   padding: 0 10px 0 10px;
-  grid-template-columns: auto auto;
   justify-content: center;
+
+  &.-reverse {
+    grid-template-columns: minmax(auto, 1fr) auto;
+    grid-template-areas: "content sidebar";
+  }
 }
 
 .underlay {
-  height: 100%;
-  width: 100%;
   grid-column-start: 1;
-  grid-column-end: 3;
+  grid-column-end: span 2;
   grid-row-start: 1;
   grid-row-end: 1;
-  margin: -0.5em;
-  padding: 0.5em;
+  margin: 0 -0.5em;
+  padding: 0 0.5em;
+  pointer-events: none;
+  background-color: rgba(0, 0, 0, 0.15);
+  background-color: var(--underlay, rgba(0, 0, 0, 0.15));
+  z-index: -2000;
 }
 
 .column {
+  box-sizing: border-box;
   max-width: 615px;
   padding-top: 10px;
   grid-row-start: 1;
   grid-row-end: 1;
-
-  &:nth-child(2) {
-    grid-column: 1;
-  }
-
-  &:nth-child(3) {
-    grid-column: 2;
-  }
+  margin: 0 0.5em;
 
   &.-mini {
     max-width: 345px;
   }
 
   &.-scrollable {
+    padding-top: 10px;
     position: sticky;
     top: 0;
-    margin-top: calc(-1 * var(--navbar-padding));
-    max-height: 100vh;
+    max-height: calc(100vh - var(--navbar-height));
     overflow-y: auto;
+    overflow-x: hidden;
+
+    .panel-heading.-sticky {
+      top: -10px;
+    }
   }
 }
 
-body,
+#content,
 .column.-scrollable {
   &::-webkit-scrollbar {
     display: block;
@@ -99,42 +155,10 @@ body,
   }
 }
 
-.underlay {
-  background-color: rgba(0,0,0,0.15);
-  background-color: var(--underlay, rgba(0,0,0,0.15));
-}
-
 .text-center {
   text-align: center;
 }
 
-html {
-  font-size: 14px;
-}
-
-body {
-  overscroll-behavior-y: none;
-  font-family: sans-serif;
-  font-family: var(--interfaceFont, sans-serif);
-  margin: 0;
-  color: $fallback--text;
-  color: var(--text, $fallback--text);
-  max-width: 100vw;
-  overflow-x: hidden;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-
-  &.hidden {
-    display: none;
-  }
-}
-
-a {
-  text-decoration: none;
-  color: $fallback--link;
-  color: var(--link, $fallback--link);
-}
-
 .button-default {
   user-select: none;
   color: $fallback--text;
@@ -166,12 +190,12 @@ a {
   }
 
   &:hover {
-    box-shadow: 0px 0px 4px rgba(255, 255, 255, 0.3);
+    box-shadow: 0 0 4px rgba(255, 255, 255, 0.3);
     box-shadow: var(--buttonHoverShadow);
   }
 
   &:active {
-    box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset;
+    box-shadow: 0 0 4px 0 rgba(255, 255, 255, 0.3), 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset;
     box-shadow: var(--buttonPressedShadow);
     color: $fallback--text;
     color: var(--btnPressedText, $fallback--text);
@@ -204,7 +228,7 @@ a {
     color: var(--btnToggledText, $fallback--text);
     background-color: $fallback--fg;
     background-color: var(--btnToggled, $fallback--fg);
-    box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset;
+    box-shadow: 0 0 4px 0 rgba(255, 255, 255, 0.3), 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset;
     box-shadow: var(--buttonPressedShadow);
 
     svg,
@@ -254,8 +278,9 @@ a {
   }
 }
 
-input, textarea, .input {
-
+input,
+textarea,
+.input {
   &.unstyled {
     border-radius: 0;
     background: none;
@@ -266,7 +291,7 @@ input, textarea, .input {
   border: none;
   border-radius: $fallback--inputRadius;
   border-radius: var(--inputRadius, $fallback--inputRadius);
-  box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 0px 2px 0px rgba(0, 0, 0, 1) inset;
+  box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset, 0 0 2px 0 rgba(0, 0, 0, 1) inset;
   box-shadow: var(--inputShadow);
   background-color: $fallback--fg;
   background-color: var(--input, $fallback--fg);
@@ -282,9 +307,11 @@ input, textarea, .input {
   height: 28px;
   line-height: 16px;
   hyphens: none;
-  padding: 8px .5em;
+  padding: 8px 0.5em;
 
-  &:disabled, &[disabled=disabled], &.disabled {
+  &:disabled,
+  &[disabled=disabled],
+  &.disabled {
     cursor: not-allowed;
     opacity: 0.5;
   }
@@ -299,18 +326,21 @@ input, textarea, .input {
 
   &[type=radio] {
     display: none;
+
     &:checked + label::before {
-      box-shadow: 0px 0px 2px black inset, 0px 0px 0px 4px $fallback--fg inset;
-      box-shadow: var(--inputShadow), 0px 0px 0px 4px var(--fg, $fallback--fg) inset;
+      box-shadow: 0 0 2px black inset, 0 0 0 4px $fallback--fg inset;
+      box-shadow: var(--inputShadow), 0 0 0 4px var(--fg, $fallback--fg) inset;
       background-color: var(--accent, $fallback--link);
     }
+
     &:disabled {
       &,
       & + label,
       & + label::before {
-        opacity: .5;
+        opacity: 0.5;
       }
     }
+
     + label::before {
       flex-shrink: 0;
       display: inline-block;
@@ -319,9 +349,9 @@ input, textarea, .input {
       width: 1.1em;
       height: 1.1em;
       border-radius: 100%; // Radio buttons should always be circle
-      box-shadow: 0px 0px 2px black inset;
+      box-shadow: 0 0 2px black inset;
       box-shadow: var(--inputShadow);
-      margin-right: .5em;
+      margin-right: 0.5em;
       background-color: $fallback--fg;
       background-color: var(--input, $fallback--fg);
       vertical-align: top;
@@ -337,17 +367,20 @@ input, textarea, .input {
 
   &[type=checkbox] {
     display: none;
+
     &:checked + label::before {
       color: $fallback--text;
       color: var(--inputText, $fallback--text);
     }
+
     &:disabled {
       &,
       & + label,
       & + label::before {
-        opacity: .5;
+        opacity: 0.5;
       }
     }
+
     + label::before {
       flex-shrink: 0;
       display: inline-block;
@@ -357,9 +390,9 @@ input, textarea, .input {
       height: 1.1em;
       border-radius: $fallback--checkboxRadius;
       border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
-      box-shadow: 0px 0px 2px black inset;
+      box-shadow: 0 0 2px black inset;
       box-shadow: var(--inputShadow);
-      margin-right: .5em;
+      margin-right: 0.5em;
       background-color: $fallback--fg;
       background-color: var(--input, $fallback--fg);
       vertical-align: top;
@@ -387,6 +420,7 @@ option {
 
 .hide-number-spinner {
   -moz-appearance: textfield;
+
   &[type=number]::-webkit-inner-spin-button,
   &[type=number]::-webkit-outer-spin-button {
     opacity: 0;
@@ -394,7 +428,8 @@ option {
   }
 }
 
-i[class*=icon-], .svg-inline--fa  {
+i[class*=icon-],
+.svg-inline--fa {
   color: $fallback--icon;
   color: var(--icon, $fallback--icon);
 }
@@ -426,10 +461,6 @@ i[class*=icon-], .svg-inline--fa  {
 }
 
 .auto-size {
-  flex: 1
-}
-
-main-router {
   flex: 1;
 }
 
@@ -439,23 +470,21 @@ main-router {
 
   p {
     margin: 0;
-    font-size: 0.8em
+    font-size: 0.8em;
   }
 }
 
 /* Panel */
-
 .panel {
-  display: flex;
   position: relative;
-
+  display: flex;
   flex-direction: column;
-  margin: 0.5em;
-
+  z-index: 0;
   background-color: $fallback--bg;
   background-color: var(--bg, $fallback--bg);
 
-  &::after, & {
+  &::after,
+  & {
     border-radius: $fallback--panelRadius;
     border-radius: var(--panelRadius, $fallback--panelRadius);
   }
@@ -463,16 +492,14 @@ main-router {
   &::after {
     content: '';
     position: absolute;
-
     top: 0;
     bottom: 0;
     left: 0;
     right: 0;
-
-    pointer-events: none;
-
-    box-shadow: 1px 1px 4px rgba(0,0,0,.6);
+    z-index: 2;
+    box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
     box-shadow: var(--panelShadow);
+    pointer-events: none;
   }
 }
 
@@ -484,19 +511,67 @@ main-router {
 }
 
 .panel-heading {
+  position: relative;
+  box-sizing: border-box;
   display: flex;
   flex: none;
   border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
   border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
   background-size: cover;
-  padding: .6em .6em;
+  padding: 0.6em 0.6em;
   text-align: left;
   line-height: 28px;
   color: var(--panelText);
-  background-color: $fallback--fg;
-  background-color: var(--panel, $fallback--fg);
+  background-color: $fallback--bg;
+  background-color: var(--bg, $fallback--bg);
   align-items: baseline;
-  box-shadow: var(--panelHeaderShadow);
+  height: var(--panelHeadingHeight);
+  z-index: -2;
+
+  --panelHeadingHeight: 45px;
+
+  &.-flexible-height {
+    --panelHeadingHeight: auto;
+
+    &::after,
+    &::before {
+      display: none;
+    }
+  }
+
+  &.-sticky {
+    position: sticky;
+    top: 0;
+    z-index: 2;
+  }
+
+  &::after,
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    right: 0;
+    left: 0;
+    pointer-events: none;
+  }
+
+  &::after {
+    background-color: $fallback--fg;
+    background-color: var(--panel, $fallback--fg);
+    z-index: -2;
+    border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
+    border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
+    box-shadow: var(--panelHeaderShadow);
+  }
+
+  &::before {
+    bottom: -20px;
+    z-index: -1;
+    border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
+    border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
+    mask: linear-gradient(to bottom, white var(--panelHeadingHeight), transparent var(--panelHeadingHeight));
+  }
 
   .title {
     flex: 1 0 auto;
@@ -527,7 +602,7 @@ main-router {
     min-height: 0;
     box-sizing: border-box;
     margin: 0;
-    margin-left: .5em;
+    margin-left: 0.5em;
     min-width: 1px;
     align-self: stretch;
   }
@@ -562,7 +637,7 @@ main-router {
   a,
   .-link {
     color: $fallback--link;
-    color: var(--panelLink, $fallback--link)
+    color: var(--panelLink, $fallback--link);
   }
 }
 
@@ -574,7 +649,7 @@ main-router {
 /* TODO Should remove timeline-footer from here when we refactor panels into
  * separate component and utilize slots
  */
-.panel-footer, .timeline-footer {
+.panel-footer {
   display: flex;
   border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
   border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
@@ -605,37 +680,18 @@ main-router {
   margin: 0;
 }
 
-.container > * {
-  min-width: 0px;
-}
-
 .fa {
   color: grey;
 }
 
-nav {
-  z-index: 1000;
-  color: var(--topBarText);
-  background-color: $fallback--fg;
-  background-color: var(--topBar, $fallback--fg);
-  color: $fallback--faint;
-  color: var(--faint, $fallback--faint);
-  box-shadow: 0px 0px 4px rgba(0,0,0,.6);
-  box-shadow: var(--topBarShadow);
-  box-sizing: border-box;
-}
-
-.fade-enter-active, .fade-leave-active {
-  transition: opacity .2s
-}
-.fade-enter-from, .fade-leave-active {
-  opacity: 0
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.2s;
 }
 
-.main {
-  flex-basis: 50%;
-  flex-grow: 1;
-  flex-shrink: 1;
+.fade-enter-from,
+.fade-leave-active {
+  opacity: 0;
 }
 
 .sidebar-bounds {
@@ -653,34 +709,6 @@ nav {
   display: none;
 }
 
-@media all and (min-width: 800px) {
-  .sidebar-bounds {
-    overflow: hidden;
-    max-height: 100vh;
-    width: 345px;
-    position: fixed;
-    margin-top: -10px;
-
-    .sidebar-scroller {
-      height: 96vh;
-      width: 365px;
-      padding-top: 10px;
-      padding-right: 50px;
-      overflow-x: hidden;
-      overflow-y: scroll;
-    }
-
-    .sidebar {
-      width: 345px;
-    }
-  }
-  .sidebar-flexer {
-    max-height: 96vh;
-    flex-shrink: 0;
-    flex-grow: 0;
-  }
-}
-
 .badge {
   box-sizing: border-box;
   display: inline-block;
@@ -764,7 +792,7 @@ nav {
 }
 
 .visibility-notice {
-  padding: .5em;
+  padding: 0.5em;
   border: 1px solid $fallback--faint;
   border: 1px solid var(--faint, $fallback--faint);
   border-radius: $fallback--inputRadius;
@@ -779,7 +807,7 @@ nav {
     position: absolute;
     top: 0;
     right: 0;
-    padding: .5em;
+    padding: 0.5em;
     color: inherit;
   }
 }
@@ -796,30 +824,57 @@ nav {
   }
 }
 
-@keyframes shakeError {
-  0% {
-    transform: translateX(0);
-  }
-  15% {
-    transform: translateX(0.375rem);
-  }
-  30% {
-    transform: translateX(-0.375rem);
-  }
-  45% {
-    transform: translateX(0.375rem);
-  }
-  60% {
-    transform: translateX(-0.375rem);
+.login-hint {
+  text-align: center;
+
+  @media all and (min-width: 801px) {
+    display: none;
   }
-  75% {
-    transform: translateX(0.375rem);
+
+  a {
+    display: inline-block;
+    padding: 1em 0;
+    width: 100%;
   }
-  90% {
-    transform: translateX(-0.375rem);
+}
+
+.btn.button-default {
+  min-height: 28px;
+}
+
+.new-status-notification {
+  position: relative;
+  font-size: 1.1em;
+  z-index: 1;
+  flex: 1;
+}
+
+@media all and (min-width: 800px) {
+  .sidebar-bounds {
+    overflow: hidden;
+    max-height: 100vh;
+    width: 345px;
+    position: fixed;
+    margin-top: -10px;
+
+    .sidebar-scroller {
+      height: 96vh;
+      width: 365px;
+      padding-top: 10px;
+      padding-right: 50px;
+      overflow-x: hidden;
+      overflow-y: scroll;
+    }
+
+    .sidebar {
+      width: 345px;
+    }
   }
-  100% {
-    transform: translateX(0);
+
+  .sidebar-flexer {
+    max-height: 96vh;
+    flex-shrink: 0;
+    flex-grow: 0;
   }
 }
 
@@ -832,14 +887,6 @@ nav {
     display: flex;
   }
 
-  .container {
-    padding: 0;
-  }
-
-  .panel {
-    margin: 0.5em 0 0.5em 0;
-  }
-
   .menu-button {
     display: block;
     margin-right: 0.8em;
@@ -850,41 +897,6 @@ nav {
   }
 }
 
-.setting-list,
-.option-list{
-  list-style-type: none;
-  padding-left: 2em;
-  li {
-    margin-bottom: 0.5em;
-  }
-  .suboptions {
-    margin-top: 0.3em
-  }
-}
-
-.login-hint {
-  text-align: center;
-
-  @media all and (min-width: 801px) {
-    display: none;
-  }
-
-  a {
-    display: inline-block;
-    padding: 1em 0px;
-    width: 100%;
-  }
-}
-
-.btn.button-default {
-  min-height: 28px;
-}
-
-.animate-spin {
-  animation: spin 2s infinite linear;
-  display: inline-block;
-}
-
 @keyframes spin {
   0% {
     transform: rotate(0deg);
@@ -895,45 +907,36 @@ nav {
   }
 }
 
-.new-status-notification {
-  position: relative;
-  font-size: 1.1em;
-  z-index: 1;
-  flex: 1;
-}
+@keyframes shakeError {
+  0% {
+    transform: translateX(0);
+  }
 
-.chat-layout {
-  // Needed for smoother chat navigation in the desktop Safari (otherwise the chat layout "jumps" as the chat opens).
-  overflow: hidden;
-  height: 100%;
+  15% {
+    transform: translateX(0.375rem);
+  }
 
-  // Get rid of scrollbar on body as scrolling happens on different element
-  // Ensures the fixed position of the mobile browser bars on scroll up / down events.
-  // Prevents the mobile browser bars from overlapping or hiding the message posting form.
-  @media all and (max-width: 800px) {
-    body {
-      height: 100%;
-    }
+  30% {
+    transform: translateX(-0.375rem);
+  }
 
-    #app {
-      height: 100%;
-      overflow: hidden;
-      min-height: auto;
-    }
+  45% {
+    transform: translateX(0.375rem);
+  }
 
-    #app_bg_wrapper {
-      overflow: hidden;
-    }
+  60% {
+    transform: translateX(-0.375rem);
+  }
 
-    .main {
-      overflow: hidden;
-      height: 100%;
-    }
+  75% {
+    transform: translateX(0.375rem);
+  }
 
-    #content {
-      padding-top: 0;
-      height: 100%;
-      overflow: visible;
-    }
+  90% {
+    transform: translateX(-0.375rem);
+  }
+
+  100% {
+    transform: translateX(0);
   }
 }
diff --git a/src/App.vue b/src/App.vue
index dc2359f3..71de2a36 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -7,28 +7,28 @@
       id="app_bg_wrapper"
       class="app-bg-wrapper"
     />
-    <MobileNav class="navbar" v-if="isMobileLayout" />
-    <DesktopNav class="navbar" v-else />
-    <div class="app-bg-wrapper app-container-wrapper" />
+    <MobileNav v-if="isMobileLayout" />
+    <DesktopNav v-else />
     <div
       id="content"
       class="app-layout container"
+      :class="{ '-reverse': reverseLayout }"
     >
       <div class="underlay"/>
       <div
+        id="sidebar"
         class="column -scrollable -mini mobile-hidden"
-        :style="sidebarAlign"
       >
         <user-panel />
-        <div v-if="!isMobileLayout">
+        <template v-if="!isMobileLayout">
           <nav-panel />
           <instance-specific-panel v-if="showInstanceSpecificPanel" />
           <features-panel v-if="!currentUser && showFeaturesPanel" />
           <who-to-follow-panel v-if="currentUser && suggestionsEnabled" />
           <notifications v-if="currentUser" />
-        </div>
+        </template>
       </div>
-      <div class="column main">
+      <div id="main-scroller" class="column main">
         <div
           v-if="!currentUser"
           class="login-hint panel panel-default"
diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js
index aef11712..59e884c4 100644
--- a/src/components/chat/chat.js
+++ b/src/components/chat/chat.js
@@ -6,7 +6,7 @@ import PostStatusForm from '../post_status_form/post_status_form.vue'
 import ChatTitle from '../chat_title/chat_title.vue'
 import chatService from '../../services/chat_service/chat_service.js'
 import { promiseInterval } from '../../services/promise_interval/promise_interval.js'
-import { getScrollPosition, getNewTopPosition, isBottomedOut, scrollableContainerHeight, isScrollable } from './chat_layout_utils.js'
+import { getScrollPosition, getNewTopPosition, isBottomedOut } from './chat_layout_utils.js'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
   faChevronDown,
@@ -20,7 +20,7 @@ library.add(
 )
 
 const BOTTOMED_OUT_OFFSET = 10
-const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 150
+const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 10
 const SAFE_RESIZE_TIME_OFFSET = 100
 const MARK_AS_READ_DELAY = 1500
 const MAX_RETRIES = 10
@@ -52,7 +52,6 @@ const Chat = {
     }
 
     this.$nextTick(() => {
-      this.updateScrollableContainerHeight()
       this.handleResize()
     })
     this.setChatLayout()
@@ -132,7 +131,6 @@ const Chat = {
     onFilesDropped () {
       this.$nextTick(() => {
         this.handleResize()
-        this.updateScrollableContainerHeight()
       })
     },
     handleVisibilityChange () {
@@ -154,10 +152,6 @@ const Chat = {
       if (html) {
         html.classList.add('chat-layout')
       }
-
-      this.$nextTick(() => {
-        this.updateScrollableContainerHeight()
-      })
     },
     unsetChatLayout () {
       let html = document.querySelector('html')
@@ -167,17 +161,9 @@ const Chat = {
     },
     handleLayoutChange () {
       this.$nextTick(() => {
-        this.updateScrollableContainerHeight()
         this.scrollDown()
       })
     },
-    // Ensures the proper position of the posting form in the mobile layout (the mobile browser panel does not overlap or hide it)
-    updateScrollableContainerHeight () {
-      const header = this.$refs.header
-      const footer = this.$refs.footer
-      const inner = this.mobileLayout ? window.document.body : this.$refs.inner
-      this.scrollableContainerHeight = scrollableContainerHeight(inner, header, footer) + 'px'
-    },
     // Preserves the scroll position when OSK appears or the posting form changes its height.
     handleResize (opts = {}) {
       const { expand = false, delayed = false } = opts
@@ -190,17 +176,14 @@ const Chat = {
       }
 
       this.$nextTick(() => {
-        this.updateScrollableContainerHeight()
-
         const { offsetHeight = undefined } = this.lastScrollPosition
-        this.lastScrollPosition = getScrollPosition(this.$refs.scrollable)
+        this.lastScrollPosition = getScrollPosition(document.getElementById('content'))
 
         const diff = this.lastScrollPosition.offsetHeight - offsetHeight
         if (diff < 0 || (!this.bottomedOut() && expand)) {
           this.$nextTick(() => {
-            this.updateScrollableContainerHeight()
-            this.$refs.scrollable.scrollTo({
-              top: this.$refs.scrollable.scrollTop - diff,
+            document.getElementById('content').scrollTo({
+              top: document.getElementById('content').scrollTop - diff,
               left: 0
             })
           })
@@ -209,7 +192,7 @@ const Chat = {
     },
     scrollDown (options = {}) {
       const { behavior = 'auto', forceRead = false } = options
-      const scrollable = this.$refs.scrollable
+      const scrollable = document.getElementById('content')
       if (!scrollable) { return }
       this.$nextTick(() => {
         scrollable.scrollTo({ top: scrollable.scrollHeight, left: 0, behavior })
@@ -228,10 +211,10 @@ const Chat = {
       })
     },
     bottomedOut (offset) {
-      return isBottomedOut(this.$refs.scrollable, offset)
+      return isBottomedOut(document.getElementById('content'), offset)
     },
     reachedTop () {
-      const scrollable = this.$refs.scrollable
+      const scrollable = document.getElementById('content')
       return scrollable && scrollable.scrollTop <= 0
     },
     cullOlderCheck () {
@@ -263,7 +246,7 @@ const Chat = {
       }
     }, 200),
     handleScrollUp (positionBeforeLoading) {
-      const positionAfterLoading = getScrollPosition(this.$refs.scrollable)
+      const positionAfterLoading = getScrollPosition(document.getElementById('content'))
       this.$refs.scrollable.scrollTo({
         top: getNewTopPosition(positionBeforeLoading, positionAfterLoading),
         left: 0
@@ -285,22 +268,18 @@ const Chat = {
             chatService.clear(chatMessageService)
           }
 
-          const positionBeforeUpdate = getScrollPosition(this.$refs.scrollable)
+          const positionBeforeUpdate = getScrollPosition(document.getElementById('content'))
           this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => {
             this.$nextTick(() => {
               if (fetchOlderMessages) {
                 this.handleScrollUp(positionBeforeUpdate)
               }
 
-              if (isFirstFetch) {
-                this.updateScrollableContainerHeight()
-              }
-
               // In vertical screens, the first batch of fetched messages may not always take the
               // full height of the scrollable container.
               // If this is the case, we want to fetch the messages until the scrollable container
               // is fully populated so that the user has the ability to scroll up and load the history.
-              if (!isScrollable(this.$refs.scrollable) && messages.length > 0) {
+              if (messages.length > 0) {
                 this.fetchChat({ maxId: this.currentChatMessageService.minId })
               }
             })
@@ -336,9 +315,6 @@ const Chat = {
         this.handleResize()
         // When the posting form size changes because of a media attachment, we need an extra resize
         // to account for the potential delay in the DOM update.
-        setTimeout(() => {
-          this.updateScrollableContainerHeight()
-        }, SAFE_RESIZE_TIME_OFFSET)
         this.scrollDown({ forceRead: true })
       })
     },
diff --git a/src/components/chat/chat.scss b/src/components/chat/chat.scss
index 3a26686c..c1f2dcf2 100644
--- a/src/components/chat/chat.scss
+++ b/src/components/chat/chat.scss
@@ -1,19 +1,12 @@
 .chat-view {
   display: flex;
-  height: calc(100vh - 60px);
-  width: 100%;
-
-  .chat-title {
-    // prevents chat header jumping on when the user avatar loads
-    height: 28px;
-  }
+  height: 100%;
 
   .chat-view-inner {
     height: auto;
     width: 100%;
     overflow: visible;
     display: flex;
-    margin: 0.5em 0.5em 0 0.5em;
   }
 
   .chat-view-body {
@@ -32,11 +25,9 @@
     }
   }
 
-  .scrollable-message-list {
+  .message-list {
     padding: 0 0.8em;
     height: 100%;
-    overflow-y: scroll;
-    overflow-x: hidden;
     display: flex;
     flex-direction: column;
   }
@@ -44,12 +35,13 @@
   .footer {
     position: sticky;
     bottom: 0;
+    background-color: $fallback--bg;
+    background-color: var(--bg, $fallback--bg);
   }
 
   .chat-view-heading {
     align-items: center;
     justify-content: space-between;
-    top: 50px;
     display: flex;
     z-index: 2;
     position: sticky;
diff --git a/src/components/chat/chat.vue b/src/components/chat/chat.vue
index 493c5d5a..3b0129fa 100644
--- a/src/components/chat/chat.vue
+++ b/src/components/chat/chat.vue
@@ -8,7 +8,7 @@
       >
         <div
           ref="header"
-          class="panel-heading chat-view-heading mobile-hidden"
+          class="panel-heading -sticky chat-view-heading mobile-hidden"
         >
           <a
             class="go-back-button"
@@ -27,10 +27,8 @@
           </div>
         </div>
         <div
-          ref="scrollable"
-          class="scrollable-message-list"
+          class="message-list"
           :style="{ height: scrollableContainerHeight }"
-          @scroll="handleScroll"
         >
           <template v-if="!errorLoadingChat">
             <ChatMessage
diff --git a/src/components/chat/chat_layout_utils.js b/src/components/chat/chat_layout_utils.js
index 50a933ac..5c4aa5bf 100644
--- a/src/components/chat/chat_layout_utils.js
+++ b/src/components/chat/chat_layout_utils.js
@@ -1,5 +1,6 @@
 // Captures a scroll position
 export const getScrollPosition = (el) => {
+  console.log(el)
   return {
     scrollTop: el.scrollTop,
     scrollHeight: el.scrollHeight,
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index 229f3006..1c28ef77 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -181,7 +181,7 @@
   .mobile-notifications {
     margin-top: 50px;
     width: 100vw;
-    height: calc(100vh - 50px);
+    height: calc(100vh - var(--navbar-height));
     overflow-x: hidden;
     overflow-y: scroll;
 
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 2ce5d56f..9d8fa059 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -6,7 +6,7 @@
     <div :class="mainClass">
       <div
         v-if="!noHeading"
-        class="panel-heading"
+        class="notifications-heading panel-heading -sticky"
       >
         <div class="title">
           {{ $t('notifications.notifications') }}
diff --git a/src/components/password_reset/password_reset.vue b/src/components/password_reset/password_reset.vue
index 3ffa5425..e0579568 100644
--- a/src/components/password_reset/password_reset.vue
+++ b/src/components/password_reset/password_reset.vue
@@ -91,6 +91,9 @@
     flex-direction: column;
     margin-top: 0.6em;
     max-width: 18rem;
+    > * {
+      min-width: 0;
+    }
   }
 
   .form-group {
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 3d409109..1e735c69 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -271,7 +271,10 @@ $validations-cRed: #f04124;
   .container {
     display: flex;
     flex-direction: row;
-    //margin-bottom: 1em;
+
+    > * {
+      min-width: 0;
+    }
   }
 
   .terms-of-service {
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
index 2f7649a9..f3738a8c 100644
--- a/src/components/settings_modal/settings_modal.scss
+++ b/src/components/settings_modal/settings_modal.scss
@@ -2,6 +2,18 @@
 .settings-modal {
   overflow: hidden;
 
+  .setting-list,
+  .option-list {
+    list-style-type: none;
+    padding-left: 2em;
+    li {
+      margin-bottom: 0.5em;
+    }
+    .suboptions {
+      margin-top: 0.3em
+    }
+  }
+
   &.peek {
     .settings-modal-panel {
       /* Explanation:
diff --git a/src/components/status/status.scss b/src/components/status/status.scss
index 5ed43e0d..e38e2a53 100644
--- a/src/components/status/status.scss
+++ b/src/components/status/status.scss
@@ -42,6 +42,10 @@
     display: flex;
     padding: var(--status-margin, $status-margin);
 
+    > * {
+      min-width: 0;
+    }
+
     &.-repeat {
       padding-top: 0;
     }
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 8ec5d1e5..9056319b 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -64,7 +64,7 @@ const Timeline = {
       if (this.blockingClicks) rootClasses = rootClasses.concat(['-blocked', '_misclick-prevention'])
       return {
         root: rootClasses,
-        header: ['timeline-heading'].concat(!this.embedded ? ['panel-heading'] : []),
+        header: ['timeline-heading'].concat(!this.embedded ? ['panel-heading', '-sticky'] : []),
         body: ['timeline-body'].concat(!this.embedded ? ['panel-body'] : []),
         footer: ['timeline-footer'].concat(!this.embedded ? ['panel-footer'] : [])
       }
@@ -89,7 +89,7 @@ const Timeline = {
     const credentials = store.state.users.currentUser.credentials
     const showImmediately = this.timeline.visibleStatuses.length === 0
 
-    window.addEventListener('scroll', this.handleScroll)
+    document.getElementById('content').addEventListener('scroll', this.handleScroll)
 
     if (store.state.api.fetchers[this.timelineName]) { return false }
 
@@ -111,7 +111,7 @@ const Timeline = {
     setTimeout(this.determineVisibleStatuses, 250)
   },
   unmounted () {
-    window.removeEventListener('scroll', this.handleScroll)
+    document.getElementById('content').removeEventListener('scroll', this.handleScroll)
     window.removeEventListener('keydown', this.handleShortKey)
     if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
     this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
diff --git a/src/components/timeline/timeline.scss b/src/components/timeline/timeline.scss
index 2c5a67e2..c10375d4 100644
--- a/src/components/timeline/timeline.scss
+++ b/src/components/timeline/timeline.scss
@@ -13,7 +13,6 @@
     max-width: 100%;
     flex-wrap: nowrap;
     align-items: center;
-    position: relative;
 
     .loadmore-button {
       flex-shrink: 0;
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 14b4643a..6e044bb6 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -8,7 +8,7 @@
       :style="style"
       class="background-image"
     />
-    <div class="panel-heading">
+    <div class="panel-heading -flexible-height">
       <div class="user-info">
         <div class="container">
           <a
@@ -331,6 +331,7 @@
     border-top-left-radius: calc(var(--panelRadius) - 1px);
     border-top-right-radius: calc(var(--panelRadius) - 1px);
     background-color: var(--profileBg);
+    z-index: -2;
 
     &.hide-bio {
       mask-size: 100% 40px;
@@ -385,11 +386,16 @@
   padding: 0 26px;
 
   .container {
+    min-width: 0;
     padding: 16px 0 6px;
     display: flex;
     align-items: flex-start;
     max-height: 56px;
 
+    > * {
+      min-width: 0;
+    }
+
     .Avatar {
       --_avatarShadowBox: var(--avatarShadow);
       --_avatarShadowFilter: var(--avatarShadowFilter);
-- 
cgit v1.2.3-70-g09d2


From 4a068483ed9b1334780402cbe64dfa3f4a0e8a3a Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 5 Apr 2022 18:38:05 +0300
Subject: wide mode initial implementation + cleanup

---
 src/App.js                               |   8 +-
 src/App.scss                             | 127 +++++++++++++++++--------------
 src/App.vue                              |   6 +-
 src/boot/after_store.js                  |   7 +-
 src/components/about/about.vue           |   2 +-
 src/components/mobile_nav/mobile_nav.vue |   1 -
 src/components/user_card/user_card.vue   |   1 +
 src/modules/interface.js                 |  10 +--
 8 files changed, 89 insertions(+), 73 deletions(-)

(limited to 'src/components/mobile_nav/mobile_nav.vue')

diff --git a/src/App.js b/src/App.js
index 4a59f412..b2ec95d4 100644
--- a/src/App.js
+++ b/src/App.js
@@ -79,7 +79,7 @@ export default {
     hideShoutbox () {
       return this.$store.getters.mergedConfig.hideShoutbox
     },
-    isMobileLayout () { return this.$store.state.interface.mobileLayout },
+    layoutType () { return this.$store.state.interface.layoutType },
     privateMode () { return this.$store.state.instance.private },
     reverseLayout () { return this.$store.getters.mergedConfig.sidebarRight },
     ...mapGetters(['mergedConfig'])
@@ -87,10 +87,12 @@ export default {
   methods: {
     updateMobileState () {
       const mobileLayout = windowWidth() <= 800
+      const wideLayout = windowWidth() >= 1300
       const layoutHeight = windowHeight()
-      const changed = mobileLayout !== this.isMobileLayout
+      const layoutType = wideLayout ? 'wide' : (mobileLayout ? 'mobile' : 'normal')
+      const changed = layoutType !== this.layoutType
       if (changed) {
-        this.$store.dispatch('setMobileLayout', mobileLayout)
+        this.$store.dispatch('setLayoutType', layoutType)
       }
       this.$store.dispatch('setLayoutHeight', layoutHeight)
     }
diff --git a/src/App.scss b/src/App.scss
index c8a7969c..e32a2d36 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -63,11 +63,11 @@ nav {
 }
 
 #sidebar {
-  display: grid;
-  grid-template-columns: 100%;
-  row-gap: 1em;
   grid-area: sidebar;
-  align-content: start;
+
+  @media all and (max-width: 800px) {
+    display: none;
+  }
 }
 
 #main-scroller {
@@ -88,6 +88,19 @@ nav {
   background-position: 50%;
 }
 
+.underlay {
+  grid-column-start: 1;
+  grid-column-end: span 3;
+  grid-row-start: 1;
+  grid-row-end: 1;
+  margin: 0 -0.5em;
+  padding: 0 0.5em;
+  pointer-events: none;
+  background-color: rgba(0, 0, 0, 0.15);
+  background-color: var(--underlay, rgba(0, 0, 0, 0.15));
+  z-index: -2000;
+}
+
 .app-layout {
   position: relative;
   display: grid;
@@ -103,44 +116,66 @@ nav {
   justify-content: center;
 
   --miniColumn: 345px;
-  --maxiColumn: minmax(auto, 615px);
+  --maxiColumn: minmax(345px, 615px);
+
+  .column {
+    display: grid;
+    grid-template-columns: 100%;
+    box-sizing: border-box;
+    padding-top: 10px;
+    grid-row-start: 1;
+    grid-row-end: 1;
+    margin: 0 0.5em;
+    row-gap: 1em;
+    align-content: start;
+
+    &.-scrollable {
+      padding-top: 10px;
+      position: sticky;
+      top: 0;
+      max-height: calc(100vh - var(--navbar-height));
+      overflow-y: auto;
+      overflow-x: hidden;
 
-  &.-reverse {
+      .panel-heading.-sticky {
+        top: -10px;
+      }
+    }
+  }
+
+  .column-inner {
+    display: grid;
+    grid-template-columns: 100%;
+    box-sizing: border-box;
+    row-gap: 1em;
+    align-content: start;
+  }
+
+  &.-reverse:not(.-wide):not(.-mobile) {
     grid-template-columns: var(--maxiColumn) var(--miniColumn);
     grid-template-areas: "content sidebar";
   }
-}
 
-.underlay {
-  grid-column-start: 1;
-  grid-column-end: span 2;
-  grid-row-start: 1;
-  grid-row-end: 1;
-  margin: 0 -0.5em;
-  padding: 0 0.5em;
-  pointer-events: none;
-  background-color: rgba(0, 0, 0, 0.15);
-  background-color: var(--underlay, rgba(0, 0, 0, 0.15));
-  z-index: -2000;
-}
+  &.-wide {
+    grid-template-columns: var(--miniColumn) var(--maxiColumn) var(--miniColumn);
+    grid-template-areas: "sidebar content notifs";
 
-.column {
-  box-sizing: border-box;
-  padding-top: 10px;
-  grid-row-start: 1;
-  grid-row-end: 1;
-  margin: 0 0.5em;
+    &.-reverse {
+      grid-template-areas: "notifs content sidebar";
+    }
+  }
 
-  &.-scrollable {
-    padding-top: 10px;
-    position: sticky;
-    top: 0;
-    max-height: calc(100vh - var(--navbar-height));
-    overflow-y: auto;
-    overflow-x: hidden;
+  &.-mobile {
+    grid-template-columns: 100vw;
+    grid-template-areas: "content";
+    padding: 0;
+
+    .column {
+      margin: 0;
+    }
 
-    .panel-heading.-sticky {
-      top: -10px;
+    .underlay {
+      display: none;
     }
   }
 }
@@ -464,17 +499,6 @@ i[class*=icon-],
   color: grey;
 }
 
-.sidebar-bounds {
-  flex: 0;
-  flex-basis: 35%;
-}
-
-.sidebar-flexer {
-  flex: 1;
-  flex-basis: 345px;
-  width: 365px;
-}
-
 .mobile-shown {
   display: none;
 }
@@ -652,19 +676,6 @@ i[class*=icon-],
   .mobile-hidden {
     display: none;
   }
-
-  .panel-switcher {
-    display: flex;
-  }
-
-  .menu-button {
-    display: block;
-    margin-right: 0.8em;
-  }
-
-  .main {
-    margin-bottom: 7em;
-  }
 }
 
 @keyframes spin {
diff --git a/src/App.vue b/src/App.vue
index 71de2a36..f87d895b 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -7,12 +7,12 @@
       id="app_bg_wrapper"
       class="app-bg-wrapper"
     />
-    <MobileNav v-if="isMobileLayout" />
+    <MobileNav v-if="layoutType === 'mobile'" />
     <DesktopNav v-else />
     <div
       id="content"
       class="app-layout container"
-      :class="{ '-reverse': reverseLayout }"
+      :class="[{ '-reverse': reverseLayout }, '-' + layoutType]"
     >
       <div class="underlay"/>
       <div
@@ -20,7 +20,7 @@
         class="column -scrollable -mini mobile-hidden"
       >
         <user-panel />
-        <template v-if="!isMobileLayout">
+        <template v-if="layoutType !== 'mobile'">
           <nav-panel />
           <instance-specific-panel v-if="showInstanceSpecificPanel" />
           <features-panel v-if="!currentUser && showFeaturesPanel" />
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 76832708..ce96141d 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -332,8 +332,11 @@ const checkOAuthToken = async ({ store }) => {
 }
 
 const afterStoreSetup = async ({ store, i18n }) => {
-  const width = windowWidth()
-  store.dispatch('setMobileLayout', width <= 800)
+  // TODO cleanup copypasta
+  const mobileLayout = windowWidth() <= 800
+  const wideLayout = windowWidth() >= 1300
+  const layoutType = wideLayout ? 'wide' : (mobileLayout ? 'mobile' : 'normal')
+  store.dispatch('setLayoutType', layoutType)
 
   FaviconService.initFaviconService()
 
diff --git a/src/components/about/about.vue b/src/components/about/about.vue
index 518f6184..5d5d6479 100644
--- a/src/components/about/about.vue
+++ b/src/components/about/about.vue
@@ -1,5 +1,5 @@
 <template>
-  <div class="sidebar">
+  <div class="column-inner">
     <instance-specific-panel v-if="showInstanceSpecificPanel" />
     <staff-panel />
     <terms-of-service-panel />
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index 1c28ef77..09680910 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -96,7 +96,6 @@
     grid-template-rows: 50px;
     grid-template-columns: 2fr auto;
     width: 100%;
-    position: fixed;
     box-sizing: border-box;
   }
 
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 6e044bb6..43a660e1 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -289,6 +289,7 @@
 
 .user-card {
   position: relative;
+  z-index: 1;
 
   &:hover {
     --_still-image-img-visibility: visible;
diff --git a/src/modules/interface.js b/src/modules/interface.js
index 17277331..9df72b88 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -13,7 +13,7 @@ const defaultState = {
       window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
     )
   },
-  mobileLayout: false,
+  layoutType: 'normal',
   globalNotices: [],
   layoutHeight: 0,
   lastTimeline: null
@@ -36,8 +36,8 @@ const interfaceMod = {
     setNotificationPermission (state, permission) {
       state.notificationPermission = permission
     },
-    setMobileLayout (state, value) {
-      state.mobileLayout = value
+    setLayoutType (state, value) {
+      state.layoutType = value
     },
     closeSettingsModal (state) {
       state.settingsModalState = 'hidden'
@@ -86,8 +86,8 @@ const interfaceMod = {
     setNotificationPermission ({ commit }, permission) {
       commit('setNotificationPermission', permission)
     },
-    setMobileLayout ({ commit }, value) {
-      commit('setMobileLayout', value)
+    setLayoutType ({ commit }, value) {
+      commit('setLayoutType', value)
     },
     closeSettingsModal ({ commit }) {
       commit('closeSettingsModal')
-- 
cgit v1.2.3-70-g09d2


From 6a319154d94c51f0464e0de31c6f88fbe20defea Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 5 Apr 2022 19:22:15 +0300
Subject: teleport bread

---
 src/App.js                                     |   4 +-
 src/App.scss                                   |  10 ++-
 src/App.vue                                    |   9 +-
 src/components/mobile_nav/mobile_nav.js        |   3 +-
 src/components/mobile_nav/mobile_nav.vue       |   5 +-
 src/components/notifications/notifications.js  |  15 +++-
 src/components/notifications/notifications.vue | 116 +++++++++++++------------
 7 files changed, 88 insertions(+), 74 deletions(-)

(limited to 'src/components/mobile_nav/mobile_nav.vue')

diff --git a/src/App.js b/src/App.js
index b2ec95d4..827692a6 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,6 +1,5 @@
 import UserPanel from './components/user_panel/user_panel.vue'
 import NavPanel from './components/nav_panel/nav_panel.vue'
-import Notifications from './components/notifications/notifications.vue'
 import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
 import FeaturesPanel from './components/features_panel/features_panel.vue'
 import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
@@ -16,13 +15,14 @@ import PostStatusModal from './components/post_status_modal/post_status_modal.vu
 import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
 import { windowWidth, windowHeight } from './services/window_utils/window_utils'
 import { mapGetters } from 'vuex'
+import { defineAsyncComponent } from 'vue'
 
 export default {
   name: 'app',
   components: {
     UserPanel,
     NavPanel,
-    Notifications,
+    Notifications: defineAsyncComponent(() => import('./components/notifications/notifications.vue')),
     InstanceSpecificPanel,
     FeaturesPanel,
     WhoToFollowPanel,
diff --git a/src/App.scss b/src/App.scss
index e32a2d36..1867a54d 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -64,10 +64,10 @@ nav {
 
 #sidebar {
   grid-area: sidebar;
+}
 
-  @media all and (max-width: 800px) {
-    display: none;
-  }
+#notifs-column {
+  grid-area: notifs;
 }
 
 #main-scroller {
@@ -177,6 +177,10 @@ nav {
     .underlay {
       display: none;
     }
+
+    #sidebar {
+      display: none;
+    }
   }
 }
 
diff --git a/src/App.vue b/src/App.vue
index f87d895b..70084538 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -9,23 +9,21 @@
     />
     <MobileNav v-if="layoutType === 'mobile'" />
     <DesktopNav v-else />
+    <notifications v-if="currentUser" />
     <div
       id="content"
       class="app-layout container"
       :class="[{ '-reverse': reverseLayout }, '-' + layoutType]"
     >
       <div class="underlay"/>
-      <div
-        id="sidebar"
-        class="column -scrollable -mini mobile-hidden"
-      >
+      <div id="sidebar" class="column -scrollable">
         <user-panel />
         <template v-if="layoutType !== 'mobile'">
           <nav-panel />
           <instance-specific-panel v-if="showInstanceSpecificPanel" />
           <features-panel v-if="!currentUser && showFeaturesPanel" />
           <who-to-follow-panel v-if="currentUser && suggestionsEnabled" />
-          <notifications v-if="currentUser" />
+          <div id="notifs-sidebar" />
         </template>
       </div>
       <div id="main-scroller" class="column main">
@@ -42,6 +40,7 @@
         </div>
         <router-view />
       </div>
+      <div id="notifs-column" class="column -scrollable"/>
       <media-modal />
     </div>
     <shout-panel
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index 9e736cfb..877d52a9 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -78,7 +78,8 @@ const MobileNav = {
       this.$store.dispatch('logout')
     },
     markNotificationsAsSeen () {
-      this.$refs.notifications.markAsSeen()
+      // this.$refs.notifications.markAsSeen()
+      this.$store.dispatch('markNotificationsAsSeen')
     },
     onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) {
       if (scrollTop + clientHeight >= scrollHeight) {
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index 09680910..6c810bba 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -69,12 +69,9 @@
       </div>
       <div
         class="mobile-notifications"
+        id="mobile-notifications"
         @scroll="onScroll"
       >
-        <Notifications
-          ref="notifications"
-          :no-heading="true"
-        />
       </div>
     </div>
     <SideDrawer
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index c8f1ebcb..2bb8a1d1 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -23,8 +23,6 @@ const Notifications = {
     NotificationFilters
   },
   props: {
-    // Disables display of panel header
-    noHeading: Boolean,
     // Disables panel styles, unread mark, potentially other notification-related actions
     // meant for "Interactions" timeline
     minimalMode: Boolean,
@@ -65,6 +63,19 @@ const Notifications = {
     loading () {
       return this.$store.state.statuses.notifications.loading
     },
+    noHeading () {
+      const { layoutType } = this.$store.state.interface
+      console.log(layoutType)
+      return layoutType === 'mobile'
+    },
+    teleportTarget () {
+      const { layoutType } = this.$store.state.interface
+      const map = {
+        wide: '#notifs-column',
+        mobile: '#mobile-notifications'
+      }
+      return map[layoutType] || '#notifs-sidebar'
+    },
     notificationsToDisplay () {
       return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
     },
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 9d8fa059..f50d3d38 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -1,69 +1,71 @@
 <template>
-  <div
-    :class="{ minimal: minimalMode }"
-    class="Notifications"
-  >
-    <div :class="mainClass">
-      <div
-        v-if="!noHeading"
-        class="notifications-heading panel-heading -sticky"
-      >
-        <div class="title">
-          {{ $t('notifications.notifications') }}
-          <span
-            v-if="unseenCount"
-            class="badge badge-notification unseen-count"
-          >{{ unseenCount }}</span>
-        </div>
-        <button
-          v-if="unseenCount"
-          class="button-default read-button"
-          @click.prevent="markAsSeen"
-        >
-          {{ $t('notifications.read') }}
-        </button>
-        <NotificationFilters />
-      </div>
-      <div class="panel-body">
+  <teleport :to="teleportTarget">
+    <div
+      :class="{ minimal: minimalMode }"
+      class="Notifications"
+    >
+      <div :class="mainClass">
         <div
-          v-for="notification in notificationsToDisplay"
-          :key="notification.id"
-          class="notification"
-          :class="{&quot;unseen&quot;: !minimalMode && !notification.seen}"
+          v-if="!noHeading"
+          class="notifications-heading panel-heading -sticky"
         >
-          <div class="notification-overlay" />
-          <notification :notification="notification" />
+          <div class="title">
+            {{ $t('notifications.notifications') }}
+            <span
+              v-if="unseenCount"
+              class="badge badge-notification unseen-count"
+            >{{ unseenCount }}</span>
+          </div>
+          <button
+            v-if="unseenCount"
+            class="button-default read-button"
+            @click.prevent="markAsSeen"
+          >
+            {{ $t('notifications.read') }}
+          </button>
+          <NotificationFilters />
         </div>
-      </div>
-      <div class="panel-footer notifications-footer">
-        <div
-          v-if="bottomedOut"
-          class="new-status-notification text-center faint"
-        >
-          {{ $t('notifications.no_more_notifications') }}
+        <div class="panel-body">
+          <div
+            v-for="notification in notificationsToDisplay"
+            :key="notification.id"
+            class="notification"
+            :class="{&quot;unseen&quot;: !minimalMode && !notification.seen}"
+          >
+            <div class="notification-overlay" />
+            <notification :notification="notification" />
+          </div>
         </div>
-        <button
-          v-else-if="!loading"
-          class="button-unstyled -link -fullwidth"
-          @click.prevent="fetchOlderNotifications()"
-        >
-          <div class="new-status-notification text-center">
-            {{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
+        <div class="panel-footer notifications-footer">
+          <div
+            v-if="bottomedOut"
+            class="new-status-notification text-center faint"
+          >
+            {{ $t('notifications.no_more_notifications') }}
+          </div>
+          <button
+            v-else-if="!loading"
+            class="button-unstyled -link -fullwidth"
+            @click.prevent="fetchOlderNotifications()"
+          >
+            <div class="new-status-notification text-center">
+              {{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
+            </div>
+          </button>
+          <div
+            v-else
+            class="new-status-notification text-center"
+          >
+            <FAIcon
+              icon="circle-notch"
+              spin
+              size="lg"
+            />
           </div>
-        </button>
-        <div
-          v-else
-          class="new-status-notification text-center"
-        >
-          <FAIcon
-            icon="circle-notch"
-            spin
-            size="lg"
-          />
         </div>
       </div>
     </div>
-  </div>
+  </teleport>
 </template>
 
 <script src="./notifications.js"></script>
-- 
cgit v1.2.3-70-g09d2


From 4750d9bb4c67b5a759a1eb0fde64d0270553e74c Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 10 Apr 2022 14:23:03 +0300
Subject: fix notifs' shadow peeking in when closed on mobile

---
 src/components/mobile_nav/mobile_nav.vue | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

(limited to 'src/components/mobile_nav/mobile_nav.vue')

diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index 6c810bba..4976105d 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -51,7 +51,7 @@
     <div
       v-if="currentUser"
       class="mobile-notifications-drawer"
-      :class="{ 'closed': !notificationsOpen }"
+      :class="{ '-closed': !notificationsOpen }"
       @touchstart.stop="notificationsTouchStart"
       @touchmove.stop="notificationsTouchMove"
     >
@@ -148,8 +148,9 @@
     z-index: 1001;
     -webkit-overflow-scrolling: touch;
 
-    &.closed {
+    &.-closed {
       transform: translateX(100%);
+      box-shadow: none;
     }
   }
 
-- 
cgit v1.2.3-70-g09d2


From 076c52788b46258078ce2d83fdac82d75692c74b Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 20 Apr 2022 20:43:10 +0300
Subject: fixes for mobile

---
 src/components/chat/chat.vue             | 2 +-
 src/components/mobile_nav/mobile_nav.vue | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

(limited to 'src/components/mobile_nav/mobile_nav.vue')

diff --git a/src/components/chat/chat.vue b/src/components/chat/chat.vue
index 31eecbc0..2e7df7bd 100644
--- a/src/components/chat/chat.vue
+++ b/src/components/chat/chat.vue
@@ -7,7 +7,7 @@
       >
         <div
           ref="header"
-          class="panel-heading -sticky chat-view-heading mobile-hidden"
+          class="panel-heading -sticky chat-view-heading"
         >
           <button
             class="button-unstyled go-back-button"
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index 362441bb..d2d48a03 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -5,7 +5,6 @@
     <nav
       id="nav"
       class="mobile-nav"
-      :class="{ 'mobile-hidden': isChat }"
       @click="scrollToTop()"
     >
       <div class="item">
-- 
cgit v1.2.3-70-g09d2

