Use sass instead of pure css

This commit is contained in:
Zed 2019-09-13 10:44:21 +02:00
parent 08239a3fae
commit de828bdd79
21 changed files with 1452 additions and 6 deletions

37
src/sass/general.scss Normal file
View File

@ -0,0 +1,37 @@
@import '_variables';
@import '_mixins';
.panel-container {
margin: auto;
font-size: 130%;
}
.error-panel {
@include center-panel($error_red);
}
.search-panel > form {
@include center-panel($darkest-grey);
button {
background: #303030;
color: $fg_color;
border: 0;
border-radius: 3px;
cursor: pointer;
font-weight: bold;
width: 30px;
height: 30px;
}
input {
font-size: 16px;
width: 100%;
background: $bg_elements;
color: $fg_color;
border: 0;
border-radius: 4px;
padding: 4px;
margin-right: 8px;
}
}

View File

@ -0,0 +1,60 @@
@import '_variables';
@mixin panel($width, $max-width) {
max-width: $max-width;
margin: 0 auto;
float: none;
border-radius: 0;
position: relative;
width: $width;
}
@mixin play-button {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1;
&:hover {
.overlay-circle {
border-color: $accent;
}
.overlay-triangle {
border-color: transparent transparent transparent $accent;
}
}
}
@mixin breakable {
overflow: hidden;
overflow-wrap: break-word;
}
@mixin ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@mixin center-panel($bg) {
padding: 12px;
border-radius: 4px;
display: flex;
background: $bg;
box-shadow: 0 0 15px $shadow_dark;
margin: auto;
margin-top: -50px;
}
@mixin input-colors {
&:hover {
border-color: $accent;
}
&:active {
border-color: $accent_light;
}
}

View File

@ -0,0 +1,36 @@
// colors
$bg_color: #0F0F0F;
$fg_color: #F8F8F2;
$fg_faded: #F8F8F2CF;
$fg_dark: #9d9da0;
$bg_panel: #161616;
$bg_elements: #121212;
$bg_overlays: #1F1F1F;
$grey: #888889;
$dark_grey: #404040;
$darker_grey: #282828;
$darkest_grey: #222222;
$border_grey: #3E3E35;
$accent: #FF6C60;
$accent_light: #FFACA0;
$accent_dark: #8A3731;
$accent_border: #FF6C6091;
$play_button_red: #D8574D;
$more_replies_dots: #AD433B;
$error_red: #420A05;
$verified_blue: #1DA1F2;
$shadow: rgba(0,0,0,.6);
$shadow_dark: rgba(0,0,0,.2);
//fonts
$font_0: Helvetica Neue;
$font_1: Helvetica;
$font_2: Arial;
$font_3: sans-serif;
$font_4: fontello;

113
src/sass/index.scss Normal file
View File

@ -0,0 +1,113 @@
@import '_variables';
@import 'tweet/_base';
@import 'profile/_base';
@import 'general';
@import 'navbar';
@import 'inputs';
@import 'timeline';
body {
background-color: $bg_color;
color: $fg_color;
font-family: $font_0, $font_1, $font_2, $font_3;
font-size: 14px;
line-height: 1.3;
margin: 0;
}
* {
outline: unset;
margin: 0;
text-decoration: none;
}
h1 {
display: inline;
}
h2, h3 {
font-weight: normal;
}
p {
margin: 14px 0;
}
a {
color: $accent;
&:hover {
text-decoration: underline;
}
}
fieldset {
border: 0;
padding: 0;
margin-top: -0.6em;
}
legend {
width: 100%;
padding: .6em 0 .3em 0;
border: 0;
font-size: 16px;
border-bottom: 1px solid $border_grey;
margin-bottom: 8px;
}
ul {
padding-left: 1.3em;
}
ul.about-list {
margin-bottom: 14px;
}
.container {
display: flex;
flex-wrap: wrap;
}
#content {
box-sizing: border-box;
padding-top: 50px;
margin: auto;
min-height: 100vh;
}
.icon-container {
display: inline;
}
.overlay-panel {
max-width: 600px;
width: 100%;
margin: 0 auto;
margin-top: 10px;
background-color: $bg_overlays;
padding: 10px 15px;
align-self: start;
}
.verified-icon {
color: $fg_color;
background-color: $verified_blue;
border-radius: 50%;
flex-shrink: 0;
margin: 2px 0 3px 3px;
padding-top: 2px;
height: 12px;
width: 14px;
font-size: 8px;
display: inline-block;
text-align: center;
vertical-align: middle;
}
@media(max-width: 600px) {
.preferences-container {
max-width: 95vw;
}
}

114
src/sass/inputs.scss Normal file
View File

@ -0,0 +1,114 @@
@import '_variables';
@import '_mixins';
button {
@include input-colors;
background-color: $bg_elements;
color: $fg_color;
border: 1px solid $accent_border;
padding: 3px 6px;
font-size: 14px;
cursor: pointer;
float: right;
}
input[type="text"] {
@include input-colors;
background-color: $bg_elements;
padding: 1px 4px;
color: $fg_color;
border: 1px solid $accent_border;
border-radius: 0;
font-size: 14px;
}
.icon-button button {
color: $accent;
text-decoration: none;
border: none;
float: none;
padding: unset;
padding-left: 4px;
&:hover {
color: $accent_light;
}
}
.checkbox {
position: absolute;
top: 1px;
right: 0;
height: 17px;
width: 17px;
background-color: $bg_elements;
border: 1px solid $accent_border;
&:after {
content: "";
position: absolute;
display: none;
}
}
.checkbox-container {
display: block;
position: relative;
margin-bottom: 5px;
cursor: pointer;
user-select: none;
padding-right: 22px;
input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
&:checked ~ .checkbox:after {
display: block;
}
}
&:hover input ~ .checkbox {
border-color: $accent;
}
&:active input ~ .checkbox {
border-color: $accent_light;
}
.checkbox:after {
left: 2px;
bottom: 0;
font-size: 13px;
font-family: $font_4;
content: '\e803';
}
}
.preferences {
button {
margin: 6px 0 3px 0;
}
label {
padding-right: 135px;
}
input[type="text"] {
position: absolute;
right: 0;
max-width: 120px;
}
.pref-input {
position: relative;
margin-bottom: 6px;
}
.pref-reset {
float: left;
}
}

63
src/sass/navbar.scss Normal file
View File

@ -0,0 +1,63 @@
@import '_variables';
nav {
z-index: 1000;
background-color: $bg_overlays;
box-shadow: 0 0 4px $shadow;
}
.nav-bar {
padding: 0;
width: 100%;
align-items: center;
position: fixed;
height: 50px;
.inner-nav {
margin: auto;
box-sizing: border-box;
padding: 0 10px;
display: flex;
align-items: center;
flex-basis: 920px;
height: 50px;
}
}
.site-name {
font-weight: 600;
&:hover {
color: $accent_light;
text-decoration: unset;
}
}
.site-logo {
display: block;
width: 35px;
height: 35px;
}
.item {
display: flex;
flex: 1;
line-height: 50px;
height: 50px;
overflow: hidden;
flex-wrap: wrap;
&.right {
text-align: right;
justify-content: flex-end;
}
&.right a {
padding-left: 4px;
&:hover {
color: $accent_light;
text-decoration: unset;
}
}
}

View File

@ -0,0 +1,61 @@
@import '_variables';
@import '_mixins';
@import 'card';
@import 'photo-rail';
.profile-timeline, .profile-tabs {
@include panel(auto, 900px);
}
.profile-tabs {
> .timeline-tab {
width: 68% !important;
}
}
.profile-banner {
padding-bottom: 4px;
a {
display: inherit;
line-height: 0;
}
img {
width: 100%;
}
}
.profile-banner-color {
width: 100%;
padding-bottom: 25%;
}
.profile-tab {
padding: 0 4px 0 0;
box-sizing: border-box;
display: inline-block;
font-size: 14px;
text-align: left;
vertical-align: top;
max-width: 32%;
top: 50px;
}
@media(max-width: 600px) {
.profile-tabs {
width: 100vw;
.timeline-tab {
width: 100% !important;
}
}
.profile-tab {
width: 100%;
max-width: unset;
position: initial !important;
padding: 0;
}
}

120
src/sass/profile/card.scss Normal file
View File

@ -0,0 +1,120 @@
@import '_variables';
@import '_mixins';
.profile-card {
flex-wrap: wrap;
background: $bg_panel;
padding: 12px;
display: flex;
}
.profile-card-info {
@include breakable;
width: 100%;
}
.profile-card-tabs-name {
@include breakable;
max-width: 100%;
}
.profile-card-username {
@include breakable;
color: $fg_color;
font-size: 14px;
display: block;
}
.profile-card-fullname {
@include breakable;
color: $fg_color;
font-size: 16px;
font-weight: bold;
text-shadow: none;
max-width: 100%;
}
.profile-card-avatar {
display: block;
width: 100%;
padding-bottom: 6px;
margin-right: 4px;
img {
display: block;
width: calc(100% - 8px);
height: 100%;
margin: 0;
border: 4px solid $darker_grey;
background: $bg_color;
}
}
.profile-card-extra {
display: contents;
flex: 100%;
margin-top: 7px;
.profile-bio {
@include breakable;
width: 100%;
margin: 4px -6px 6px 0;
p {
margin: 0;
}
}
.profile-joindate, .profile-location, profile-website {
color: $fg_faded;
margin: 2px 0;
width: 100%;
}
}
.profile-card-extra-links {
margin-top: 8px;
font-size: 14px;
width: 100%;
}
.profile-statlist {
display: flex;
flex-wrap: wrap;
padding: 0;
width: 100%;
justify-content: space-between;
li {
display: table-cell;
text-align: center;
}
}
.profile-stat-header {
font-weight: bold;
}
.profile-stat-num {
display: block;
}
@media(max-width: 600px) {
.profile-card-info {
display: flex;
}
.profile-card-tabs-name {
@include breakable;
}
.profile-card-avatar {
height: 60px;
width: unset;
img {
border-width: 2px;
width: unset;
}
}
}

View File

@ -0,0 +1,84 @@
@import '_variables';
.photo-rail {
&-card {
float: left;
background: $bg_panel;
border-radius: 0 0 4px 4px;
width: 100%;
margin: 5px 0;
}
&-header {
padding: 5px 12px 0;
}
&-header-mobile {
padding: 5px 12px 0;
display: none;
}
&-label {
width: 100%;
float: unset;
color: $accent;
display: flex;
justify-content: space-between;
}
&-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 3px 3px;
padding: 5px 12px 12px;
a {
position: relative;
border-radius: 5px;
&:before {
content: "";
display: block;
padding-top: 100%;
}
}
img {
height: 100%;
width: 100%;
border-radius: 4px;
object-fit: cover;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
}
}
#photo-rail-toggle {
display: none;
&:checked ~ .photo-rail-grid {
max-height: 600px;
padding-bottom: 12px;
}
}
@media(max-width: 600px) {
.photo-rail-header {
display: none;
}
.photo-rail-header-mobile {
display: block;
}
.photo-rail-grid {
max-height: 0;
padding-bottom: 0;
overflow: hidden;
transition: max-height 0.4s;
}
}

139
src/sass/timeline.scss Normal file
View File

@ -0,0 +1,139 @@
@import '_variables';
#posts {
background-color: $bg_panel;
}
.timeline-tab {
float: right;
padding: 0;
box-sizing: border-box;
display: inline-block;
font-size: 14px;
text-align: left;
vertical-align: top;
}
.multi-timeline {
max-width: 600px;
width: 100%;
margin: 0 auto;
.timeline-tab {
width: 100%;
}
}
.multi-header {
background-color: $bg_panel;
text-align: center;
padding: 10px;
display: block;
font-weight: bold;
margin-bottom: 5px;
}
.tab {
align-items: center;
display: flex;
flex-wrap: wrap;
list-style: none;
margin: 0 0 5px 0;
background-color: $bg_panel;
padding: 0;
}
.tab-item {
flex: 1 1 0;
text-align: center;
margin-top: 0;
a {
border-bottom: .1rem solid transparent;
color: inherit;
display: block;
padding: 8px 0;
text-decoration: none;
font-weight: bold;
&:hover {
text-decoration: none;
}
&.active {
border-bottom-color: $accent;
color: $accent;
}
}
&.active a {
border-bottom-color: $accent;
color: $accent;
}
}
.timeline-tweet {
border-bottom: 1px solid $border_grey;
}
.timeline-footer {
background-color: $bg_panel;
padding: 6px 0;
}
.timeline-header {
background-color: $bg_panel;
padding: 6px 0;
}
.timeline-protected {
text-align: center;
p {
margin: 8px 0;
}
h2 {
color: $accent;
font-size: 20px;
font-weight: 600;
}
}
.timeline-none {
color: $accent;
font-size: 20px;
font-weight: 600;
text-align: center;
}
.timeline-end {
background-color: $bg_panel;
color: $accent;
font-size: 16px;
font-weight: 600;
text-align: center;
}
.show-more {
background-color: $bg_panel;
text-align: center;
padding: .75em 0;
display: block;
&.status-el {
border-bottom: 1px solid $border_grey;
}
a {
background-color: $darkest_grey;
display: inline-block;
height: 2em;
padding: 0 2em;
line-height: 2em;
&:hover {
background-color: $darker_grey;
}
}
}

133
src/sass/tweet/_base.scss Normal file
View File

@ -0,0 +1,133 @@
@import '_variables';
@import '_mixins';
@import 'thread';
@import 'media';
@import 'video';
@import 'card';
@import 'poll';
@import 'quote';
.status-el {
overflow-wrap: break-word;
border-left-width: 0;
min-width: 0;
padding: .75em;
display: flex;
.status-content {
font-family: $font_3;
line-height: 1.4em;
}
}
.status-body {
flex: 1;
min-width: 0;
margin-left: 58px;
}
.tweet-header {
padding: 0;
vertical-align: bottom;
flex-basis: 100%;
margin-bottom: .2em;
a {
display: inline-block;
word-break: break-all;
max-width: 100%;
}
}
.tweet-name-row {
padding: 0;
display: flex;
justify-content: space-between;
}
.fullname-and-username {
display: flex;
min-width: 0;
}
.fullname {
@include ellipsis;
flex-shrink: 2;
max-width: 80%;
font-size: 14px;
font-weight: 700;
color: $fg_color;
}
.username {
@include ellipsis;
min-width: 1.6em;
margin-left: .4em;
word-wrap: normal;
}
.tweet-date {
display: flex;
flex-shrink: 0;
margin-left: 4px;
}
.tweet-avatar {
display: contents !important;
img {
float: left;
margin-top: 3px;
margin-left: -58px;
position: absolute;
width: 48px;
height: 48px;
border-radius: 50%;
}
}
.replying-to {
color: $fg_dark;
margin: -2px 0 4px;
}
.retweet, .pinned, .tweet-stats {
align-content: center;
color: $grey;
display: flex;
flex-shrink: 0;
flex-wrap: wrap;
font-size: 14px;
font-weight: 600;
line-height: 22px;
span {
@include ellipsis;
}
}
.retweet {
margin-top: -5px !important;
}
.tweet-stats {
margin-bottom: -3px;
}
.tweet-stat {
padding-top: 5px;
padding-right: 8px;
}
.show-thread {
display: block;
}
.unavailable-box {
width: 100%;
height: 100%;
padding: 12px;
border: solid 1px $dark_grey;
border-radius: 10px;
background-color: $bg_color;
}

112
src/sass/tweet/card.scss Normal file
View File

@ -0,0 +1,112 @@
@import '_variables';
@import '_mixins';
.card {
margin: 5px 0;
}
.card-container {
border-radius: 10px;
border-width: 1px;
border-style: solid;
border-color: $dark_grey;
background-color: $bg_elements;
overflow: hidden;
color: inherit;
display: flex;
text-decoration: none !important;
&:hover {
border-color: $grey;
}
.attachments {
border-radius: 0;
}
}
.card-content {
padding: 0.5em;
}
.card-title {
@include ellipsis;
font-weight: bold;
font-size: 1.15em;
}
.card-description {
margin: 0.3em 0;
}
.card-destination {
@include ellipsis;
color: $grey;
display: block;
}
.card-content-container {
color: unset;
overflow: auto;
&:hover {
text-decoration: none;
}
}
.card-image-container {
width: 98px;
flex-shrink: 0;
position: relative;
overflow: hidden;
&:before {
content: "";
display: block;
padding-top: 100%;
}
}
.card-image {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: $fg_color;
img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
}
.card-overlay {
@include play-button;
opacity: 0.8;
display: flex;
justify-content: center;
align-items: center;
}
.large {
.card-container {
display: block;
}
.card-image-container {
width: unset;
&:before {
display: none;
}
}
.card-image {
position: unset;
border-style: solid;
border-color: $dark_grey;
border-width: 0;
border-bottom-width: 1px;
}
}

102
src/sass/tweet/media.scss Normal file
View File

@ -0,0 +1,102 @@
@import '_variables';
.gallery-row {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
overflow: hidden;
flex-grow: 1;
max-height: 379.5px;
max-width: 533px;
.still-image {
width: 100%;
}
}
.attachments {
margin-top: .35em;
display: flex;
flex-direction: row;
width: 100%;
max-height: 600px;
border-radius: 7px;
overflow: hidden;
flex-flow: column;
background-color: $bg_color;
align-items: center;
.image-attachment {
width: 100%;
}
}
.attachment {
position: relative;
line-height: 0;
overflow: hidden;
margin: 0 .25em 0 0;
flex-grow: 1;
box-sizing: border-box;
min-width: 2em;
&:last-child {
margin: 0;
max-height: 530px;
}
}
.still-image {
max-height: 379.5px;
max-width: 533px;
justify-content: center;
img {
object-fit: cover;
max-width: 100%;
max-height: 379.5px;
flex-basis: 300px;
}
}
.image {
display: inline-block;
}
.single-image {
display: inline-block;
width: unset;
}
.overlay-circle {
border-radius: 50%;
background-color: $dark_grey;
width: 40px;
height: 40px;
align-items: center;
display: flex;
border-width: 5px;
border-color: $play_button_red;
border-style: solid;
}
.overlay-triangle {
width: 0;
height: 0;
border-style: solid;
border-width: 12px 0 12px 17px;
border-color: transparent transparent transparent $play_button_red;
margin-left: 14px;
}
.media-gif {
display: inline-block;
background-color: unset;
}
.media-body {
flex: 1;
padding: 0;
white-space: pre-wrap;
}

39
src/sass/tweet/poll.scss Normal file
View File

@ -0,0 +1,39 @@
@import '_variables';
.poll-meter {
overflow: hidden;
position: relative;
margin: 6px 0;
height: 26px;
background: $bg_color;
border-radius: 5px;
display: flex;
align-items: center;
}
.poll-choice-bar {
height: 100%;
position: absolute;
background: $dark_grey;
}
.poll-choice-value {
position: relative;
font-weight: bold;
margin-left: 5px;
margin-right: 6px;
min-width: 30px;
text-align: right;
}
.poll-choice-option {
position: relative;
}
.poll-info {
color: $grey;
}
.leader .poll-choice-bar {
background: $accent_dark;
}

102
src/sass/tweet/quote.scss Normal file
View File

@ -0,0 +1,102 @@
@import '_variables';
.quote {
margin-top: 10px;
border: solid 1px $dark_grey;
border-radius: 10px;
background-color: $bg_elements;
overflow: auto;
padding: 6px;
position: relative;
&:hover {
border-color: $grey;
}
&.unavailable:hover {
border-color: $dark_grey;
}
}
.unavailable-quote {
padding: 6px;
}
.quote-link {
height: 100%;
width: 100%;
left: 0;
top: 0;
position: absolute;
}
.quote-text {
overflow: hidden;
white-space: pre-wrap;
word-wrap: break-word;
}
.quote-media-container {
display: flex;
flex-direction: column;
align-items: center;
overflow: hidden;
max-height: 102px;
width: 102px;
float: left;
margin-right: 7px;
border-radius: 7px;
position: relative;
}
.quote-media {
display: flex;
justify-content: center;
pointer-events: none;
img {
width: 100%;
height: 100%;
align-self: center;
}
}
.quote-badge {
left: 0;
bottom: 0;
position: absolute;
z-index: 1;
align-self: flex-end;
}
.quote-badge-text {
margin: 4px;
background: $shadow;
border-radius: 4px;
color: #fffffff0;
padding: 1px 3px;
font-size: 12px;
font-weight: bold;
}
.quote-sensitive {
background: $darker_grey;
width: 102px;
height: 102px;
border-radius: 12px;
display: flex;
justify-content: center;
align-items: center;
}
.quote-sensitive-icon {
font-size: 40px;
color: $grey;
}
@media(max-width: 600px) {
.quote-media-container {
width: 70px;
max-height: 70px;
}
}

View File

@ -0,0 +1,73 @@
@import '_variables';
@import '_mixins';
.conversation {
@include panel(100%, 600px);
background-color: $bg_color !important;
}
.main-thread {
margin-bottom: 20px;
background-color: $bg_panel;
}
.main-tweet .status-content {
font-size: 20px;
}
.reply {
background-color: $bg_panel;
margin-bottom: 10px;
}
.thread-line {
.status-el::before {
background: $accent_dark;
content: '';
position: relative;
min-width: 3px;
width: 3px;
left: 26px;
border-radius: 2px;
margin-left: -3px;
margin-bottom: 37px;
top: 56px;
}
.unavailable::before {
top: 48px;
margin-bottom: 28px;
}
.more-replies::before {
content: '...';
background: unset;
color: $more_replies_dots;
font-weight: bold;
font-size: 20px;
line-height: 0.25em;
left: 1.2em;
width: 5px;
top: 2px;
margin-bottom: 0;
margin-left: -5px;
}
}
.thread-last .status-el::before {
background: unset;
min-width: unset;
width: 0;
margin: 0;
}
.more-replies {
padding-top: 0.3em !important;
}
.more-replies-text {
@include ellipsis;
display: block;
margin-left: 58px;
padding: 7px 0;
}

58
src/sass/tweet/video.scss Normal file
View File

@ -0,0 +1,58 @@
@import '_variables';
@import '_mixins';
video {
height: 100%;
width: 100%;
}
.gallery-video {
display: flex;
overflow: hidden;
}
.video-container {
max-height: 530px;
img {
height: 100%;
width: 100%;
}
}
.video-overlay {
@include play-button;
background-color: $shadow;
p {
position: relative;
z-index: 0;
text-align: center;
top: calc(50% - 20px);
font-size: 20px;
line-height: 1.3;
margin: 0 20px;
}
div {
position: relative;
z-index: 0;
top: calc(50% - 20px);
margin: 0 auto;
width: 40px;
height: 40px;
}
form {
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
display: flex;
}
button {
padding: 5px 8px;
font-size: 16px;
}
}

View File

@ -1,7 +1,7 @@
import karax/[karaxdsl, vdom]
proc renderAbout*(): VNode =
buildHtml(tdiv(class="about-page")):
buildHtml(tdiv(class="overlay-panel")):
h1: text "About"
p:
text "Nitter is a free and open source alternative Twitter front-end focused on privacy. "

View File

@ -56,14 +56,14 @@ proc renderMain*(body: VNode; prefs: Prefs; title="Nitter"; titleText=""; desc="
result = doctype & $node
proc renderSearch*(): VNode =
buildHtml(tdiv(class="panel")):
buildHtml(tdiv(class="panel-container")):
tdiv(class="search-panel"):
form(`method`="post", action="/search"):
input(`type`="text", name="query", autofocus="", placeholder="Enter usernames...")
button(`type`="submit"): icon "search"
proc renderError*(error: string): VNode =
buildHtml(tdiv(class="panel")):
buildHtml(tdiv(class="panel-container")):
tdiv(class="error-panel"):
span: text error

View File

@ -53,7 +53,7 @@ macro renderPrefs*(): untyped =
result[2].add stmt
proc renderPreferences*(prefs: Prefs; path: string): VNode =
buildHtml(tdiv(class="preferences-container")):
buildHtml(tdiv(class="overlay-panel")):
fieldset(class="preferences"):
form(`method`="post", action="/saveprefs"):
refererField path

View File

@ -41,12 +41,12 @@ proc renderOlder(timeline: Timeline; username: string): VNode =
proc renderNoMore(): VNode =
buildHtml(tdiv(class="timeline-footer")):
h2(class="timeline-end", style={textAlign: "center"}):
h2(class="timeline-end"):
text "No more tweets."
proc renderNoneFound(): VNode =
buildHtml(tdiv(class="timeline-header")):
h2(class="timeline-none", style={textAlign: "center"}):
h2(class="timeline-none"):
text "No tweets found."
proc renderProtected(username: string): VNode =