stacker.news/styles/globals.scss
soxa 9092d90797
Enhancements to live comments (#2269)
* check new comments every 10 seconds

* enhance: clear newComments on child comments when we show a topLevel new comment; cleanup: resolvers, logs

* handle comments of comments, new structure to clear newComments on childs

* use original recursive comments data structure

* correct comment structure after deduplication

* faster newComments query deduplication, don't need to know how many comments are there

* cleanup: comments on newComments fetches and dedupes

* cleanup, use correct function declarations

* stop polling after 30 minutes, pause polling if user is not on the page

* ActionTooltip indicating that the user is in a live comment section

* handleVisibilityChange to control polling by visibility

* paused polling styling, check activity on 1 minute intervals and visibility change, light cleanup

* user can resume polling without refreshing the page

* better naming, straightforward dedupeComment on newComment arrival

* cleanup: better naming, get latest comment creation, correct order of comment injection

* cleanup: refactor live comments related functions to use-live-comments.js

* refactor: clearer naming, optimized polling and date retrieval logic, use of constants, general cleanup

* ui: place ShowNewComments in the bottom-right corner of nested comments

* fix: make updateQuery sort-aware to correctly inject the comment in the correct Item query

* cleanup: better naming; fix: usecallback on live comments component; fix leak on useEffect because of missing sort
atomic apollo cache manipulations; manage top sort not being present in item query cache
queue nested comments without a parent, retry on the next poll
fix commit messages

* fix: don't show unpaid comments; cleanup: compact cache merge/dedupe, queue comments via state

* fix: read new comments fragments to inject fresh new comments, fixing dropped comments;

ui: show amount of new comments

refactor: correct function positioning;

cleanup: useless logs

* enhance: queuedComments Ref, cache-and-network fetch policy; freshNewComments readFragment fallback to received comment

* cleanup: detailed comments and better ShowNewComment text

* fix: while showing new comments, also update ncomments for UI and pagination

* refactor: ShowNewComments is its own component; cleanup: proven useless dedupe on ShowNewComments, count nested ncomments from fresh new comments

* enhance: direct latest comment createdAt calc with reduce

* cleanup queue on unmount

* feat: live comments indicator for bottomed-out replies, ncomments updates; fix: nested comment structures

- new comments indicator for bottomed-out replies
- ncomments sync for parent and its ancestors
- limited comments fragment for comments that don't have CommentsRecursive
- reduce cache complexity by removing useless roundtrips

ux: live comments indicator on bottomedOut replies

fix: dedupe newComments before displaying ShowNewComments to avoid false positives

enhance: store ids of new comments in the cache, instead of carrying full comments that would get discarded anyway

hotfix: newComments deduplication ID mismatch, filter null comments from freshNewComments

fix: ncomments not updating for all comment levels; refactor: share Reply update ancestors' ncomments function with ShowNewComments

cleanup: better naming to indicate the total number of comments including nested comments

fix: increment parent comment ncomments

cleanup: Items that will have comments will always have a structure where item.comments is true

cleanup: reduce code complexity checking the nested comment update result instead of preventively reading the fragment

cleanup: avoid double-updating ncomments on parent

fix: don't use CommentsRecursive for bottomed-out comments

cleanup: better fragment naming; add TODO for absolute bottom comments

* enhance: give the possibility to show all new comments of a thread, even nested

* enhance: change favicon on new comments; warn: prop-drilling

* refactor: merge ShowAllNewComments with ShowNewComments, better usage of props

* hotfix: isThread should be recognized when an item has 2 items in its path

* fix regression: topLevel comments not showing

* fix: avoid trying to show new comments even after the depth limit; todo: two recursive counts might be too much

* favicon-new-comment, fix favicon showing also when there aren't new comments

* enhance: highlight new comments when shown; nit-fixes and cleanups

fixes:
- sync local commentsViewedAt on comment injection, to avoid double outline on item re-visit
- avoid double highlighting when client-side visiting an item and injecting a new comment

cleanups:
- move ShowNewComments functions to dedicated lib/comments.js
- bust auto-show enhancement due to bad useEffect usage

todos:
- two recursive counts might be too much

* cleanup: move cache manipulation functions, comments for comments.js

- lib/comments.js explanations for its functions
- itemUpdateQuery, commentUpdateFragment, getLatestCommentCreatedAt on comments.js
- format too many imports from comments.js

todo:
- we're not deduping comments for isThread, which forces us at this state, to dedupe twice

* enhance: highlight new comment with injected field, recursive injection in every case but top level; cleanups

cleanups:
- better separation of concerns for lib/comments.js
- don't show new comment count, avoiding useless complexity
- simpler topLevel/nested logic
- add comments

* backport live comments logic enhancements

use-live-comments:
- remove useless dedupe against already present comments
- check newComments.comments length to tell if there are new comments
- code reordering

show-new-comments:
- show all new comments recursively for nested comments
- get always the newest comments to inject also their own child new comments
- update local storage commentsViewedAt on comment injection
- respect depth on comment injection

comments.js
- apollo cache manipulations now live here

* hotfix: handle undefined item.comments.comments on dedupe

* hotfix: fix lint after merge

* hotfix: limited fragment for recursive comment collection; protect from null fragments; add missing deps to memoization

* merge: missing memo deps, limited fragment for non-recursive comments; fix: don't highlight injected comments with classic outline; cleanup: comments

* docs: clarify ncomments updates

* cleanup: remove unused export

* count and show only the direct new comments and recursively their children

enhance: dedupe against existing comments only in the component
enhance: recursive count/injection share the same logic

* fix regression on top level counting

* hotfix: introduce readNestedCommentsFragment in lib/comments.js

* fix: count also existing comments of a new comment; cleanup: use readCommentFragment also for prepareComments; reduce freshNewComments usage

* add support for comments at the deepest level

fixes:
- client-side navigation re-fetched all new comments because 'after' was cached, now the latest new comment time persists in sessionStorage

enhancements:
- use CommentWithNewMinimal fragment fallback for comments at the deepest level
- tweak ReplyOnAnotherPage to show also how many direct new comments are there

cleanup:
- queue management is not needed anymore, therefore it has been removed

* cleanup: remove logs

* revert counting on ReplyOnAnotherPage, TODO for enhancements PR

* move ShowNewComments to CommentsHeader for top level comments

* fix: update commentsViewedAfterComment to support ncomments

* fix typo, lint

* cleanup: remove old CSS

* enhance: inject topLevel and its children new comments, simplify injection logic

- top-level and nested comment handling share the same recursion logic
- ShowNewComments references the item object for every type of comments
— note: item from item-full.js is passed to comments.js
- depth now starts at 0 to support top level comments
- injection and counting now reach the deepest level, updating also the deepest comment

* cleanup: remove unused topLevel prop

* fix: deepest comments don't have CommentsRecursive structure, don't access it on injection

* move top level ShowNewComments above CommentsHeader; preserve space to avoid vertical layout shifting

* cleanup: remove unused item on CommentsHeader

* enhance: scroll and load new comments via a floating button using IntersectionObserver API; fix merge: restore injected field for outlining

* style: transparent and animated floating button, new comment dot color aligned to new comments accent color

* cleanup: less redundancy between the two types of buttons; enhance: show the floating button only if we're past the element, not only if it's not visible

* enhance: outline newly injected comments using root item's lastCommentAt

* cleanup: remove transparency of floating comments button, remove other traces of the injected field

* adapt and restore showing all new comments of a thread

* fix: respect deepest comments structure on injection, adjust depth limit; cleanup: consistent naming

* fix: avoid double outlines because of all conditions being met

* cleanup: remove favicon, dedicate space for useVisibility, correct comments

* ux: show all new comments of a thread only if its children have them

* mark injected comments in the cache for reliable outlining

* cleanup: clearer structure, more explaining

* optimize: better closure usage, remove duplicate code, immutable payloads

- ncomments count logic shared with injection and counting
- don't re-create and persist closures for every injection, rather temporarily on injection
- access item hierarchy once, avoid creating new arrays
- don't create and mutate payloads, rather know what to return

fixes:
- fix wrong parameters on traverseNewComments recursion

* cleanup: further clarifications

* safer rootLastCommentAt usage for injected comments outlining

* hotfix: ignore nDirectComments server updates when the item being updated has pending newComments, fixes CommentEdit consequences

* simpler show all new comments text for thread comments, regardless of how many

* fix: reference the correct Item for newComments reading, during nDirectComments apollo merge

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
2025-07-23 11:57:36 -05:00

1127 lines
25 KiB
SCSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

$primary: #FADA5E;
$secondary: #F6911D;
$danger: #c03221;
$warning: $secondary;
$info: #007cbe;
$success: #5c8001;
$twitter: #1da1f2;
$boost: #8c25f4;
$light: #f8f9fa;
$dark: #212529;
$nostr: #8d45dd;
$theme-colors: (
"primary" : #FADA5E,
"secondary" : #F6911D,
"danger" : #c03221,
"warning" : $secondary,
"info" : #007cbe,
"success" : #5c8001,
"twitter" : #1da1f2,
"boost" : #8c25f4,
"light": #f8f9fa,
"dark": #212529,
"grey" : #e9ecef,
"grey-medium" : #d2d2d2,
"grey-darkmode": #8c8c8c,
"nostr": #8d45dd
);
$body-bg: #fcfcff;
$body-bg-dark: #121214;
$body-color: #212529;
$body-color-dark: #f0f0f0;
$border-radius: .4rem;
$enable-transitions: false;
$enable-gradients: false;
$enable-shadows: false;
$btn-transition: none;
$form-feedback-icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='12' height='12'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='M2 9h3v12H2a1 1 0 0 1-1-1V10a1 1 0 0 1 1-1zm5.293-1.293l6.4-6.4a.5.5 0 0 1 .654-.047l.853.64a1.5 1.5 0 0 1 .553 1.57L14.6 8H21a2 2 0 0 1 2 2v2.104a2 2 0 0 1-.15.762l-3.095 7.515a1 1 0 0 1-.925.619H8a1 1 0 0 1-1-1V8.414a1 1 0 0 1 .293-.707z' fill='rgba(92, 128, 1, 1)'/%3E%3C/svg%3E");
$form-feedback-icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='12' height='12'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='M22 15h-3V3h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-5.293 1.293l-6.4 6.4a.5.5 0 0 1-.654.047L8.8 22.1a1.5 1.5 0 0 1-.553-1.57L9.4 16H3a2 2 0 0 1-2-2v-2.104a2 2 0 0 1 .15-.762L4.246 3.62A1 1 0 0 1 5.17 3H16a1 1 0 0 1 1 1v11.586a1 1 0 0 1-.293.707z' fill='rgba(192,50,33,1)'/%3E%3C/svg%3E");
$line-height-base: 1.75;
$input-placeholder-color: #6c757d;
$input-btn-padding-y: .42rem;
$input-btn-padding-x: .84rem;
$btn-padding-y: .42rem;
$btn-padding-x: 1.1rem;
$btn-font-weight: bold;
$btn-focus-width: 0;
$btn-border-width: 0;
$btn-focus-box-shadow: none;
$form-invalid-border-color: #c03221;
$form-invalid-border-color-dark: #c03221;
$form-invalid-color: #c03221;
$form-invalid-color-dark: #c03221;
$alert-border-width: 0;
$close-text-shadow: none;
$close-color: inherit;
$alert-border-radius: #{33% 2%} / #{11% 74%};
$link-color: #007cbe;
$link-decoration: none;
$font-size-base: .93rem;
$enable-responsive-font-sizes: true;
$link-hover-decoration: none;
$dropdown-border-color: #ced4da;
$dropdown-link-active-color: inherit;
$dropdown-link-hover-bg: transparent;
$dropdown-link-active-bg: transparent;
$dropdown-link-color: rgba(0, 0, 0, 0.7);
$dropdown-link-hover-color: rgba(0, 0, 0, 0.9);
$dropdown-item-padding-x: 1.5rem;
$modal-inner-padding: 2rem;
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 900px,
) !default;
$nav-link-padding-y: .1rem;
$nav-tabs-link-active-bg: #fff;
$nav-tabs-link-hover-border-color: transparent;
$nav-tabs-link-active-border-color: #ced4da #ced4da $nav-tabs-link-active-bg;
$form-check-input-checked-color: var(--bs-primary);
$form-check-input-checked-bg-color: var(--bs-primary);
$popover-bg: var(--bs-body-bg);
$form-check-input-checked-color: #000;
$tooltip-bg: #5c8001;
$form-select-indicator-color: #808080;
$form-select-indicator: url("data:image/svg+xml, %3Csvg fill='#{$form-select-indicator-color}' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 15.0006L7.75732 10.758L9.17154 9.34375L12 12.1722L14.8284 9.34375L16.2426 10.758L12 15.0006Z'%3E%3C/path%3E%3C/svg%3E%0A");
$form-select-indicator-dark: url("data:image/svg+xml, %3Csvg fill='#{$form-select-indicator-color}' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 15.0006L7.75732 10.758L9.17154 9.34375L12 12.1722L14.8284 9.34375L16.2426 10.758L12 15.0006Z'%3E%3C/path%3E%3C/svg%3E%0A");
$form-select-bg-position: right .25rem center;
$form-select-bg-size: 1.5rem;
$popover-body-padding-y: .5rem;
$popover-max-width: 320px !default;
$popover-border-color: var(--theme-borderColor);
$grid-gutter-width: 2rem;
$toast-spacing: .5rem;
$btn-close-bg: none;
$accordion-padding-y: 0rem;
$accordion-padding-x: .75rem;
// $accordion-color: var(--#{$prefix}body-color) !default;
// $accordion-bg: var(--#{$prefix}body-bg) !default;
// $accordion-border-width: var(--#{$prefix}border-width) !default;
// $accordion-border-color: var(--#{$prefix}border-color) !default;
// $accordion-border-radius: var(--#{$prefix}border-radius) !default;
// $accordion-inner-border-radius: subtract($accordion-border-radius, $accordion-border-width) !default;
// $accordion-body-padding-y: $accordion-padding-y !default;
// $accordion-body-padding-x: $accordion-padding-x !default;
// $accordion-button-padding-y: $accordion-padding-y !default;
// $accordion-button-padding-x: $accordion-padding-x !default;
// $accordion-button-color: var(--#{$prefix}body-color) !default;
// $accordion-button-bg: var(--#{$prefix}accordion-bg) !default;
// $accordion-transition: $btn-transition, border-radius .15s ease !default;
// $accordion-button-focus-border-color: $input-focus-border-color !default;
// $accordion-button-focus-box-shadow: $btn-focus-box-shadow !default;
$accordion-icon-width: 2rem;
$accordion-icon-color: $form-select-indicator-color;
$accordion-icon-active-color: $form-select-indicator-color;
$accordion-icon-color-dark: $form-select-indicator-color;
$accordion-icon-active-color-dark: $form-select-indicator-color;
$accordion-button-active-bg: var(--theme-clickToContextColor);
$accordion-button-active-color: var(--bs-body-color);
$accordion-button-icon: url("data:image/svg+xml, %3Csvg fill='#{$form-select-indicator-color}' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 15.0006L7.75732 10.758L9.17154 9.34375L12 12.1722L14.8284 9.34375L16.2426 10.758L12 15.0006Z'%3E%3C/path%3E%3C/svg%3E%0A");
$accordion-button-active-icon: $accordion-button-icon;
$accordion-button-icon-dark: $accordion-button-icon;
$accordion-button-active-icon-dark: $accordion-button-icon;
$zindex-sticky: 900;
:root, [data-bs-theme=light] {
--theme-navLink: rgba(0, 0, 0, 0.55);
--theme-navLinkFocus: rgba(0, 0, 0, 0.7);
--theme-navLinkActive: rgba(0, 0, 0, 0.9);
--theme-borderColor: #ced4da;
--theme-inputBg: #ffffff;
--theme-inputDisabledBg: #e9ecef;
--theme-dropdownItemColor: rgba(0, 0, 0, 0.7);
--theme-dropdownItemColorHover: rgba(0, 0, 0, 0.9);
--theme-commentBg: rgba(0, 0, 0, 0.03);
--theme-clickToContextColor: rgba(0, 0, 0, 0.07);
--theme-brandColor: rgba(0, 0, 0, 0.9);
--theme-grey: #707070;
--theme-link: #007cbe;
--theme-toolbarActive: rgba(0, 0, 0, 0.10);
--theme-toolbarHover: rgba(0, 0, 0, 0.20);
--theme-quoteBar: rgb(206, 208, 212);
--theme-linkHover: #004a72;
--theme-linkVisited: #53758;
--theme-note-reply: rgba(0, 0, 0, 0.04);
--theme-note-fresh: rgba(0, 124, 190, 0.5);
}
[data-bs-theme=dark] {
color-scheme: dark;
--theme-inputBg: #121211;
--theme-inputDisabledBg: #2d2d2d;
--theme-navLink: rgba(255, 255, 255, 0.55);
--theme-navLinkFocus: rgba(255, 255, 255, 0.75);
--theme-navLinkActive: rgba(255, 255, 255, 0.9);
--theme-borderColor: rgba(255, 255, 255, 0.5);
--theme-dropdownItemColor: rgba(255, 255, 255, 0.7);
--theme-dropdownItemColorHover: rgba(255, 255, 255, 0.9);
--theme-commentBg: rgba(255, 255, 255, 0.025);
--theme-clickToContextColor: rgba(255, 255, 255, 0.1);
--theme-brandColor: var(--bs-primary);
--theme-grey: #969696;
--theme-link: #2e99d1;
--theme-toolbarActive: rgba(255, 255, 255, 0.10);
--theme-toolbarHover: rgba(255, 255, 255, 0.20);
--theme-toolbar: #3e3f3f;
--theme-quoteBar: rgb(158, 159, 163);
--theme-quoteColor: rgb(141, 144, 150);
--theme-linkHover: #007cbe;
--theme-linkVisited: #56798E;
--theme-note-reply: rgba(255, 255, 255, 0.05);
--theme-note-fresh: rgba(0, 124, 190, 0.75);
}
@import '../node_modules/bootstrap/scss/bootstrap.scss';
@media screen and (min-width: 899px) {
.table-sm th,
.table-sm td {
padding: .3rem .75rem;
}
.standalone {
// undo the container padding
margin-left: -16px;
}
}
.squircle {
clip-path: url(#squircleClip);
}
.btn-outline-grey {
--bs-btn-color: var(--theme-grey);
--bs-btn-border-color: var(--theme-grey);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: var(--theme-grey);
--bs-btn-hover-border-color: var(--theme-grey);
--bs-btn-focus-shadow-rgb: 233, 236, 239;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: var(--theme-grey);
--bs-btn-active-border-color: var(--theme-grey);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: var(--theme-grey);
--bs-btn-disabled-bg: transparent;
--bs-btn-disabled-border-color: var(--theme-grey);
--bs-gradient: none;
}
* {
scroll-margin-top: 60px;
}
.ms-xs {
margin-left: 0.125rem;
}
.text-monospace {
font-family: monospace;
}
#nprogress .bar {
background: var(--bs-primary) !important;
height: 2px !important;
}
.toast-body {
padding: 0.75rem 1.25rem !important;
}
#nprogress .peg {
box-shadow: 0 0 10px var(--bs-primary), 0 0 5px var(--bs-primary) !important;
}
.standalone {
display: none;
}
.w-fit-content {
width: fit-content;
}
.line-height-sm {
line-height: 1.25;
}
.line-height-md {
line-height: 1.5;
}
@media (display-mode: standalone) {
.standalone {
display: flex
}
}
.justify-self-center {
justify-self: center;
}
.pulse {
animation-name: pulse;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
animation-duration: 0.66s;
animation-direction: alternate;
}
@keyframes pulse {
0% {
opacity: 42%;
}
}
svg {
fill: var(--bs-body-color);
}
.fill-primary {
fill: var(--bs-primary);
}
.text-primary svg {
fill: var(--bs-primary);
}
.line-height-1 {
line-height: 1;
}
.lexical blockquote>* {
margin-bottom: 0 !important;
}
.modal.fullscreen {
padding-right: 0;
background-color: color-mix(in srgb, var(--theme-inputBg) 66%, transparent);
}
.modal-dialog.fullscreen {
margin: 0;
max-width: 100vw;
max-height: 100vh;
max-height: 100svh;
background-color: transparent;
}
.modal-content.fullscreen {
border: none;
background-color: transparent;
}
.modal-body.fullscreen {
width: 100vw;
height: calc(100svh - 6.8rem);
padding: 0;
background-color: transparent;
}
.modal-close.fullscreen {
padding: 1.25rem;
}
.modal-overflow.fullscreen {
padding: 1.25rem;
margin-top: -10px;
}
.modal-btn {
cursor: pointer;
display: flex;
padding-top: 1rem;
align-items: center;
}
.modal-back {
margin-right: auto;
padding-left: 1.5rem;
}
.modal-close {
margin-left: auto;
padding-right: 1.5rem;
font-family: "lightning";
font-size: 160%;
line-height: 1rem;
}
.modal-close:hover {
opacity: 0.7;
}
.modal-close+.modal-body {
padding-top: 0.5rem;
}
.text-muted {
color: var(--theme-grey) !important;
}
ol,
ul,
dl {
padding-inline-start: 2rem;
}
mark {
background-color: #fada5e5e;
padding: 0 0.2rem;
color: var(--bs-body-color);
}
.table-sm th,
.table-sm td {
line-height: 1.2rem;
}
.twitter-tweet {
margin-top: 0 !important;
margin-bottom: 0 !important;
padding-right: 12px;
}
.twitter-tweet iframe {
width: 100% !important;
}
.btn-twitter, .btn-twitter:hover, .btn-twitter:active {
color: #ffffff !important;
}
.btn-nostr, .btn-nostr:hover, .btn-nostr:active {
fill: #ffffff !important;
stroke: #ffffff !important;
stroke-miterlimit: 10;
stroke-width: 6px;
}
.btn-outline-grey-darkmode:hover, .btn-outline-grey-darkmode:active {
color: #ffffff !important;
}
.table {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
}
.table th,
.table td,
.table thead th {
border-color: var(--theme-borderColor);
}
.table-hover tbody tr:hover {
color: var(--bs-body-color);
background-color: var(--theme-clickToContextColor);
}
html, body {
background: var(--bs-body-bg) !important;
color: var(--bs-body-color) !important;
min-height: 100vh;
min-height: 100svh;
}
a {
color: var(--theme-link);
}
a:hover {
color: var(--theme-linkHover);
}
.nav-tabs .nav-link.active,
.nav-tabs .nav-item.show .nav-link {
color: inherit;
background-color: var(--theme-inputBg);
border-color: var(--theme-borderColor);
border-bottom-color: var(--theme-inputBg);
}
select,
div[contenteditable],
.form-control {
background-color: var(--theme-inputBg);
color: var(--bs-body-color);
border-color: var(--theme-borderColor);
}
.form-group {
margin-bottom: 1rem;
}
select.form-select {
background-color: var(--theme-clickToContextColor);
color: var(--theme-dropdownItemColor);
font-weight: bold;
border: none;
display: inline-block;
width: 100%;
height: calc(1.5em + 0.5rem + 2px);
padding: 0.25rem 1.84rem 0.25rem 0.5rem;
font-size: .7875rem;
vertical-align: middle;
border-radius: 0.4rem;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
select.form-select option {
// fix dropdown items not visible in darkmode on some browsers
color: rgba(0, 0, 0, 0.9);
}
select:focus {
border-color: none !important;
box-shadow: none !important;
outline: none !important;
}
div[contenteditable]:focus,
.form-control:focus {
background-color: var(--theme-inputBg);
color: var(--bs-body-color);
outline: 0;
box-shadow: 0 0 0 0.2rem rgb(250 218 94 / 25%);
}
div[contenteditable]:disabled,
.form-control:disabled,
.form-control[readonly] {
background-color: var(--theme-inputDisabledBg);
border-color: var(--theme-borderColor);
color: var(--theme-borderColor);
opacity: 1;
}
.clickToContext {
border-radius: .4rem;
padding: 0;
cursor: pointer;
}
.py-half {
padding-top: .125rem;
padding-bottom: .125rem;
}
.clickToContext:hover {
background-color: var(--theme-clickToContextColor);
}
.btn-primary svg {
fill: #212529;
}
.btn-grey svg {
fill: #212529;
}
.btn-grey-medium svg {
fill: #212529;
}
.modal-content {
background-color: var(--theme-inputBg);
border-color: var(--theme-borderColor);
}
.modal-body:has(video) {
width: 100%;
}
.nav-link:not(.text-success, .text-warning) {
color: var(--theme-navLink) !important;
}
.nav-link:not(.text-success, .text-warning):hover,
.nav-link:not(.text-success, .text-warning):focus {
color: var(--theme-navLinkFocus) !important;
}
.nav-dropdown-toggle .btn-link {
color: var(--theme-navLink) !important;
}
.nav-dropdown-toggle .btn-link[aria-expanded="true"] {
color: var(--theme-navLinkFocus) !important;
}
.dropdown-menu {
background-color: var(--theme-inputBg);
border: 1px solid var(--theme-borderColor);
max-width: 90vw;
overflow: auto;
}
.dropdown-item {
color: var(--theme-dropdownItemColor) !important;
}
.dropdown-item:hover {
color: var(--theme-dropdownItemColorHover) !important;
}
.dropdown-item.active {
color: var(--theme-brandColor) !important;
text-shadow: 0 0 10px var(--bs-primary);
}
.dropdown-divider {
border-top: 1px solid var(--theme-borderColor);
}
.theme {
cursor: pointer;
fill: var(--theme-dropdownItemColor);
}
.theme:hover {
fill: var(--theme-dropdownItemColorHover);
}
#__next {
display: flex;
flex-direction: column;
min-height: 100vh;
min-height: 100svh;
}
#__next > main {
z-index: 0;
}
#__next > footer {
z-index: 0;
}
footer {
margin-top: auto;
display: flex;
width: 100%;
align-items: center;
justify-content: center;
text-align: center;
}
.text-boost-glow {
color: #8c25f4;
filter: drop-shadow(0 0 7px #8c25f4);
}
.text-twitter svg {
fill: #1da1f2 !important;
}
.text-muted svg {
fill: var(--theme-grey);
}
.pointer {
cursor: pointer;
}
@media screen and (max-width: 767px) {
input,
textarea,
.form-control,
.form-control:focus,
div[contenteditable],
div[contenteditable]:focus,
.input-group-text {
font-size: 1rem !important;
}
}
.input-group-text {
background-color: var(--theme-clickToContextColor);
border-color: var(--theme-borderColor);
color: var(--bs-body-color);
}
textarea.form-control,
div[contenteditable] {
line-height: 1rem;
}
.nav-tabs {
border-bottom: 0;
}
.nav-tabs .nav-link {
font-size: 80%;
color: #6c757d;
z-index: 100;
position: relative;
border-bottom: 0;
}
.nav-tabs .nav-link.active {
height: 100%;
}
.form-label {
font-size: 92%;
font-weight: bold;
}
.form-label .text-muted {
font-weight: normal;
}
.alert-dismissible .btn-close, .offcanvas-header .btn-close {
font-weight: 300;
font-family: "lightning";
font-size: 150%;
line-height: 1;
}
header .navbar:not(:last-child) {
padding-bottom: 0;
}
header .navbar:not(:first-child) {
padding-top: 0;
}
.popover-header.alert-dismissible .btn-close, .offcanvas-header .btn-close {
padding: .5rem !important;
}
.popover {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border-color: var(--theme-borderColor);
}
.popover .arrow::before {
border-top-color: var(--theme-borderColor);
}
.popover .arrow::after {
border-top-color: var(--bs-body-bg);
}
.alert-dismissible .btn-close::after, .offcanvas-header .btn-close::after {
content: 'X';
}
.nav-item {
font-weight: 500;
}
.nav-dropdown-toggle .dropdown-toggle::after {
display: inline-block;
}
.nav-dropdown-toggle .dropdown .btn {
padding: 0;
}
.nav-dropdown-toggle .dropdown .dropdown-toggle {
padding-left: .75rem;
padding-right: .5rem;
}
.nav-dropdown-toggle .nav-link {
padding-right: 0 !important;
font-weight: 500;
}
.dropdown-toggle::after {
display: none;
}
.dropdown-item {
font-weight: 500;
}
.dropdown-item.active {
text-shadow: 0 0 10px var(--bs-primary);
}
div[contenteditable]:focus,
.form-control:focus {
border-color: var(--bs-primary);
}
.btn-secondary,
.btn-secondary:hover,
.btn-secondary:active,
.btn-secondary:disabled {
color: #ffffff !important;
}
.btn-twitter svg {
fill: #ffffff;
}
.btn-nostr svg {
fill: #fff;
stroke: #fff;
stroke-miterlimit: 10;
stroke-width: 6px;
}
.btn-dark svg {
fill: #ffffff;
}
.nav-link.active {
font-weight: bold;
}
.nav-link.active:not(.text-success, .text-warning) {
color: var(--theme-navLinkActive) !important;
}
.nav-link svg {
fill: var(--theme-navLink);
}
.nav-link.active svg {
fill: var(--theme-navLinkActive);
}
.fill-grey, .fill-muted {
fill: var(--theme-grey);
}
.fill-nostr {
fill: var(--bs-nostr);
}
.fill-lgrey {
fill: #a5a5a5;
}
.fill-secondary {
fill: var(--bs-secondary);
}
.fill-boost {
fill: var(--bs-boost);
}
.fill-white {
fill: white;
}
.fill-dark {
fill: #212529;
}
.fill-success {
fill: var(--bs-success);
}
.fill-info {
fill: var(--bs-info);
}
.fill-danger {
fill: var(--bs-danger) !important;
}
.fill-theme-color {
fill: var(--bs-body-color);
}
.fill-warning {
fill: var(--bs-warning-text-emphasis);
}
.text-underline {
text-decoration: underline;
}
@keyframes flash {
from {
filter: brightness(1);
}
2% {
filter: brightness(2.3);
}
4% {
filter: brightness(1.4);
}
8% {
filter: brightness(3);
}
16% {
filter: brightness(1);
}
to {
filter: brightness(1);
}
}
.clouds {
background-image: url('/clouds.jpeg') !important;
background-color: var(--theme-grey);
background-repeat: repeat;
background-origin: content-box;
background-size: cover;
background-attachment: fixed;
opacity: .2;
}
.flash-it {
animation: flash 2s linear 1;
}
@keyframes outline {
from {
box-shadow: inset 0 0 1px 1px var(--bs-info);
}
90% {
box-shadow: inset 0 0 1px 1px var(--bs-info);
}
to {
box-shadow: inset 0 0 0px 0px var(--bs-info);
}
}
.outline-it {
animation: outline 3s linear 1;
}
.outline-new-comment {
box-shadow: inset 0 0 1px 1px rgba(0, 123, 190, 0.25);
}
.outline-new-injected-comment {
box-shadow: inset 0 0 1px 1px rgba(0, 123, 190, 0.25);
}
.outline-new-comment.outline-new-comment-unset {
box-shadow: none;
}
.outline-new-injected-comment.outline-new-comment-unset {
box-shadow: none;
}
.outline-new-comment .outline-new-comment {
box-shadow: none;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
.spin {
animation: spin 2s linear infinite;
}
.static {
background: url('/giphy.gif');
background-color: var(--theme-grey);
background-repeat: repeat;
background-origin: content-box;
background-size: cover;
background-attachment: fixed;
opacity: .1;
}
@keyframes flipX {
from {
transform: rotateX(180deg);
}
to {
transform: rotateX(-180deg);
}
}
.flipX {
animation: flipX 2s linear infinite;
}
.topLevel {
--grid-gap: 0.75rem;
}
@keyframes flipY {
from {
transform: rotateY(0deg);
}
to {
transform: rotateY(360deg);
}
}
.flipY {
animation: flipY 4s linear infinite;
}
@media (hover:none),
(hover:on-demand) {
.tooltip {
visibility: hidden;
pointer-events: none;
}
}
.tooltip-inner {
padding: 0.1rem 0.3rem;
color: #fff;
}
.popover {
.popover-header {
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
}
.popover-body {
color: var(--bs-body-color);
}
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
.react-datepicker-wrapper {
display: inline-block;
padding: 0;
border: 0;
margin-bottom: 0.5rem;
}
// To satisfy assumptions of the date picker component.
.react-datepicker__navigation-icon {
line-height: normal;
}
.react-datepicker__navigation-icon::before, .react-datepicker__navigation-icon::after {
box-sizing: content-box;
}
.react-datepicker {
background-color: var(--theme-inputBg) !important;
}
.react-datepicker__header {
background-color: var(--theme-inputDisabledBg) !important;
}
.react-datepicker__day,
.react-datepicker__current-month,
.react-datepicker__day-name {
color: var(--bs-body-color) !important;
}
.react-datepicker__day--outside-month {
color: var(--theme-inputDisabledBg) !important;
}
.react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::before, .react-datepicker-popper[data-placement^=bottom] .react-datepicker__triangle::after {
border-top: none;
border-bottom-color: var(--theme-inputDisabledBg) !important;
}
.react-datepicker__day--selected, .react-datepicker__day--in-selecting-range, .react-datepicker__day--in-range,
.react-datepicker__month-text--selected,
.react-datepicker__month-text--in-selecting-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--selected,
.react-datepicker__quarter-text--in-selecting-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--selected,
.react-datepicker__year-text--in-selecting-range,
.react-datepicker__year-text--in-range {
border-radius: 0.4rem !important;
background-color: var(--bs-info) !important;
color: var(--theme-inputDisabledBg) !important;
}
.react-datepicker__day--selected:hover, .react-datepicker__day--in-selecting-range:hover, .react-datepicker__day--in-range:hover,
.react-datepicker__month-text--selected:hover,
.react-datepicker__month-text--in-selecting-range:hover,
.react-datepicker__month-text--in-range:hover,
.react-datepicker__quarter-text--selected:hover,
.react-datepicker__quarter-text--in-selecting-range:hover,
.react-datepicker__quarter-text--in-range:hover,
.react-datepicker__year-text--selected:hover,
.react-datepicker__year-text--in-selecting-range:hover,
.react-datepicker__year-text--in-range:hover {
background-color: var(--bs-info) !important;
}
.react-datepicker__day--keyboard-selected,
.react-datepicker__month-text--keyboard-selected,
.react-datepicker__quarter-text--keyboard-selected,
.react-datepicker__year-text--keyboard-selected {
border-radius: 0.4rem !important;
background-color: var(--theme-inputBg) !important;
color: var(--bs-body-color) !important;
}
.react-datepicker__day:hover, .react-datepicker__month-text:hover,
.react-datepicker__quarter-text:hover, .react-datepicker__year-text:hover,
.react-datepicker__day--keyboard-selected:hover,
.react-datepicker__month-text--keyboard-selected:hover,
.react-datepicker__quarter-text--keyboard-selected:hover,
.react-datepicker__year-text--keyboard-selected:hover {
background-color: var(--theme-clickToContextColor) !important;
}
.react-datepicker__day--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range),
.react-datepicker__month-text--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range),
.react-datepicker__quarter-text--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range),
.react-datepicker__year-text--in-selecting-range:not(.react-datepicker__day--in-range,
.react-datepicker__month-text--in-range,
.react-datepicker__quarter-text--in-range,
.react-datepicker__year-text--in-range) {
background-color: var(--bs-info) !important;
}
.react-datepicker__close-icon {
padding: 0rem 0.75rem !important;
align-items: center !important;
height: calc(100% - 3px) !important;
&::after {
content: "×" !important;
color: var(--theme-grey) !important;
font-size: 20px !important;
background: none !important;
}
}