You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

970 lines
24 KiB

// Copyright (c) 2018 Tildes contributors <code@tildes.net>
// SPDX-License-Identifier: AGPL-3.0-or-later
// This file should only contain rules that need to be affected by all the
// different themes, defined inside the `use-theme` mixin below.
// Note that all rules inside the mixin will be included in the compiled CSS
// once for each theme, so they should be kept as minimal as possible.
// Each theme should be defined in its own SCSS file, and consist of a SCSS map
// and a unique `body.theme-<name>` selector.
// The `use-theme` mixin is called inside the body.theme-<name> block and takes
// the theme's map as its only argument, applying each defined color available
// in the map. If a color variable is left undefined in the theme's map, it
// will fall back to the default value from `$theme-base` instead.
@mixin use-theme($theme) {
$theme: init-theme($theme);
color: map-get($theme, "foreground-primary");
background-color: map-get($theme, "background-secondary");
// set $is-light as a bool for whether $background-color seems light or dark
$is-light: lightness(map-get($theme, "background-primary")) > 50;
a {
color: map-get($theme, "link");
&:hover {
color: map-get($theme, "link-hover");
}
&:visited {
color: map-get($theme, "link-visited");
}
code {
color: map-get($theme, "link");
&:hover {
text-decoration: underline;
}
}
&:visited code {
color: map-get($theme, "link-visited");
}
}
a.link-user,
a.link-group {
&:visited {
color: map-get($theme, "link");
}
}
a.logged-in-user-alert {
color: map-get($theme, "alert");
&:visited {
color: map-get($theme, "alert");
}
}
@include syntax-highlighting($theme);
blockquote {
border-color: map-get($theme, "foreground-highlight");
background-color: map-get($theme, "background-secondary");
}
code,
pre {
color: map-get($theme, "foreground-highlight");
background-color: map-get($theme, "background-secondary");
}
fieldset {
border-color: map-get($theme, "border");
}
figure {
border-color: map-get($theme, "border");
}
hr {
border-color: map-get($theme, "border");
}
main {
background-color: map-get($theme, "background-primary");
}
section {
border-color: map-get($theme, "border");
}
tbody tr:nth-of-type(2n + 1) {
background-color: map-get($theme, "background-secondary");
}
td {
border-color: map-get($theme, "border");
}
th {
border-color: map-get($theme, "foreground-highlight");
}
.form-autocomplete {
.menu {
background-color: map-get($theme, "background-secondary");
}
}
.btn {
color: map-get($theme, "button");
background-color: transparent;
border-color: map-get($theme, "button");
&:hover {
background-color: rgba(map-get($theme, "button"), 0.2);
}
}
.btn-light {
color: map-get($theme, "foreground-secondary");
border-color: map-get($theme, "border");
&:hover {
color: map-get($theme, "link");
}
}
.btn.btn-link {
color: map-get($theme, "link");
background-color: transparent;
border-color: transparent;
&:hover {
color: map-get($theme, "link");
}
}
.btn.btn-primary {
$is-button-bg-light: lightness(map-get($theme, "button")) > 50;
@if ($is-button-bg-light) {
color: #000;
} @else {
color: #fff;
}
background-color: map-get($theme, "button");
border-color: map-get($theme, "button");
&:hover {
background-color: darken(map-get($theme, "button"), 10%);
border-color: darken(map-get($theme, "button"), 10%);
}
&:visited {
@if ($is-button-bg-light) {
color: #000;
} @else {
color: #fff;
}
}
}
.btn-used {
color: map-get($theme, "button-used");
border-color: darken(map-get($theme, "button-used"), 3%);
&:hover {
background-color: darken(map-get($theme, "button-used"), 3%);
border-color: darken(map-get($theme, "button-used"), 8%);
color: #fff;
}
}
.btn-post-action {
color: map-get($theme, "foreground-secondary");
&:hover {
color: map-get($theme, "foreground-extreme");
}
}
.btn-post-action-used {
color: map-get($theme, "button-used");
}
.btn-comment-label-exemplary {
@include labelbutton(map-get($theme, "comment-label-exemplary"));
}
.btn-comment-label-joke {
@include labelbutton(map-get($theme, "comment-label-joke"));
}
.btn-comment-label-noise {
@include labelbutton(map-get($theme, "comment-label-noise"));
}
.btn-comment-label-offtopic {
@include labelbutton(map-get($theme, "comment-label-offtopic"));
}
.btn-comment-label-malice {
@include labelbutton(map-get($theme, "comment-label-malice"));
}
.chip {
background-color: map-get($theme, "background-secondary");
color: map-get($theme, "foreground-highlight");
&.active {
background-color: map-get($theme, "button");
$active-color: #fff;
$is-active-bg-light: lightness(map-get($theme, "button")) > 50;
@if ($is-active-bg-light) {
$active-color: #000;
}
color: $active-color;
.btn {
color: $active-color;
}
}
&.error {
background-color: map-get($theme, "error");
$error-color: #fff;
$is-error-bg-light: perceived-brightness(map-get($theme, "error")) > 50;
@if ($is-error-bg-light) {
$error-color: #000;
}
color: $error-color;
.btn {
color: $error-color;
}
}
}
.comment-branch-counter {
color: map-get($theme, "foreground-secondary");
}
.comment-nav-link,
.comment-nav-link:visited {
color: map-get($theme, "foreground-secondary");
}
.comment-removed-warning {
color: map-get($theme, "warning");
}
.comment-votes {
color: map-get($theme, "foreground-secondary");
}
.label-comment-exemplary {
@include theme-special-label(map-get($theme, "comment-label-exemplary"), $is-light);
}
.label-comment-joke {
@include theme-special-label(map-get($theme, "comment-label-joke"), $is-light);
}
.label-comment-noise {
@include theme-special-label(map-get($theme, "comment-label-noise"), $is-light);
}
.label-comment-offtopic {
@include theme-special-label(map-get($theme, "comment-label-offtopic"), $is-light);
}
.label-comment-malice {
@include theme-special-label(map-get($theme, "comment-label-malice"), $is-light);
}
%collapsed-theme {
.comment-header {
background-color: map-get($theme, "background-primary");
}
}
.is-comment-collapsed:not(:target) {
@extend %collapsed-theme;
}
.is-comment-collapsed-individual:not(:target) {
> .comment-itself {
@extend %collapsed-theme;
}
}
.comment {
border-color: map-get($theme, "border");
&[data-comment-depth="0"] {
border-color: map-get($theme, "border");
}
}
.comment-header {
color: map-get($theme, "foreground-highlight");
background-color: map-get($theme, "background-secondary");
}
.comment:target > .comment-itself {
border-left-color: map-get($theme, "stripe-target");
}
.divider {
border-color: map-get($theme, "border");
&[data-content]::after {
color: map-get($theme, "foreground-primary");
background-color: map-get($theme, "background-primary");
}
}
.empty-subtitle {
color: map-get($theme, "foreground-secondary");
}
.form-autocomplete .form-autocomplete-input .form-input {
border-color: transparent;
}
.form-input {
color: map-get($theme, "foreground-primary");
background-color: map-get($theme, "background-input");
}
.form-input:not(:focus) {
border-color: map-get($theme, "border");
}
// error colors for :invalid inputs, using same approach as Spectre
.form-input:not(:placeholder-shown):invalid {
border-color: map-get($theme, "error");
&:focus {
box-shadow: 0 0 0 1px map-get($theme, "error");
}
+ .form-input-hint {
color: map-get($theme, "error");
}
}
.form-markdown-preview {
border-color: map-get($theme, "border");
}
.form-select {
border-color: map-get($theme, "border");
&:not([multiple]):not([size]) {
background-color: map-get($theme, "background-input");
}
}
.form-status-error {
color: map-get($theme, "error");
}
.input-group-addon {
background-color: map-get($theme, "background-secondary");
color: map-get($theme, "foreground-highlight");
border-color: map-get($theme, "border");
}
.label-topic-tag {
color: map-get($theme, "foreground-middle");
a,
a:hover,
a:visited {
color: map-get($theme, "foreground-middle");
}
}
.label-topic-tag-nsfw,
.label-topic-tag[class*="label-topic-tag-nsfw-"] {
@include theme-special-label(map-get($theme, "topic-tag-nsfw"), $is-light);
}
.label-topic-tag-spoiler,
.label-topic-tag[class*="label-topic-tag-spoiler-"] {
@include theme-special-label(map-get($theme, "topic-tag-spoiler"), $is-light);
}
.link-no-visited-color:visited {
color: map-get($theme, "link");
}
.logged-in-user-username,
.logged-in-user-username:visited {
color: map-get($theme, "foreground-primary");
}
.message {
border-color: map-get($theme, "border");
header {
color: map-get($theme, "foreground-highlight");
background-color: map-get($theme, "background-secondary");
}
}
.nav .nav-item {
a {
color: map-get($theme, "link");
&:hover {
color: map-get($theme, "link-hover");
}
}
&.active a {
color: map-get($theme, "link");
}
}
.sidebar-controls {
background-color: map-get($theme, "background-secondary");
}
#sidebar {
background-color: map-get($theme, "background-primary");
}
#site-footer a:visited {
color: map-get($theme, "link");
}
.site-header-context,
.site-header-context:visited {
color: map-get($theme, "foreground-primary");
}
.site-header-logo,
.site-header-logo:visited {
color: map-get($theme, "foreground-highlight");
}
.site-header-sidebar-button.badge[data-badge]::after {
background-color: map-get($theme, "alert");
}
.tab {
border-color: map-get($theme, "border");
}
.tab .tab-item {
a {
color: map-get($theme, "foreground-primary");
}
&.active a,
&.active button {
color: map-get($theme, "link");
border-bottom-color: map-get($theme, "link");
}
}
.text-secondary {
color: map-get($theme, "foreground-secondary");
}
.text-warning {
color: map-get($theme, "warning");
}
.text-wiki {
h1,
h2,
h3,
h4,
h5,
h6 {
a {
color: map-get($theme, "foreground-highlight");
}
}
}
.toast {
color: map-get($theme, "foreground-highlight");
border-color: map-get($theme, "border");
background-color: map-get($theme, "background-secondary");
}
// Toasts should have colored border + text for dark themes, instead of a
// brightly colored background
@if ($is-light == false) {
.toast-warning {
border-color: map-get($theme, "warning");
color: map-get($theme, "warning");
background-color: transparent;
}
} @else {
.toast-warning {
background-color: rgba(map-get($theme, "warning"), 0.9);
border-color: map-get($theme, "warning");
@if (perceived-brightness(map-get($theme, "warning")) > 50) {
color: #000;
} @else {
color: #fff;
}
}
}
.topic {
border-color: map-get($theme, "border");
}
.topic-listing {
> li:nth-of-type(2n) {
color:
mix(
map-get($theme, "foreground-primary"),
map-get($theme, "foreground-highlight")
);
background-color:
mix(
map-get($theme, "background-primary"),
map-get($theme, "background-secondary")
);
}
}
.topic-content-metadata {
color: map-get($theme, "foreground-secondary");
}
.topic-full-byline {
color: map-get($theme, "foreground-secondary");
}
.topic-icon {
border-color: map-get($theme, "link");
}
.topic-info {
color: map-get($theme, "foreground-middle");
}
.topic-info-comments-new {
color: map-get($theme, "alert");
}
.topic-log-entry-time {
color: map-get($theme, "foreground-secondary");
}
.topic-text-excerpt {
color: map-get($theme, "foreground-secondary");
summary::after {
color: map-get($theme, "foreground-secondary");
}
&[open] {
color: map-get($theme, "foreground-primary");
}
}
.topic-voting.btn-used {
border-color: transparent;
&:hover {
background-color: darken(map-get($theme, "button"), 3%);
border-color: darken(map-get($theme, "button"), 8%);
}
}
.is-comment-deleted,
.is-comment-removed {
color: map-get($theme, "foreground-secondary");
}
.is-comment-mine > .comment-itself {
border-left-color: map-get($theme, "stripe-mine");
}
.is-comment-new {
> .comment-itself {
border-left-color: map-get($theme, "alert");
}
.comment-text {
color: map-get($theme, "foreground-highlight");
}
}
.is-comment-exemplary {
> .comment-itself {
border-left-color: map-get($theme, "comment-label-exemplary");
}
}
.is-message-mine,
.is-topic-mine {
border-left-color: map-get($theme, "stripe-mine");
}
.is-topic-official {
border-left-color: map-get($theme, "alert");
h1 {
a,
a:visited {
color: map-get($theme, "alert");
}
}
}
}
@mixin theme-special-label($color, $is-light) {
@if $is-light {
background-color: $color;
color: #fff;
a,
a:hover,
a:visited {
color: #fff;
}
} @else {
background-color: transparent;
color: $color;
border: 1px solid $color;
a,
a:hover,
a:visited {
color: $color;
}
}
}
@mixin labelbutton($color) {
color: $color;
border-color: $color;
&:hover {
color: $color;
}
&.btn-used:hover {
background-color: $color;
color: #fff;
}
}
@mixin syntax-highlighting($theme) {
.highlight {
.syntax-c {
color: map-get($theme, "syntax-comment");
} // Comment
.syntax-err {
color: map-get($theme, "foreground");
} // Error
.syntax-g {
color: map-get($theme, "foreground");
} // Generic
.syntax-k {
color: map-get($theme, "syntax-keyword");
} // Keyword
.syntax-l {
color: map-get($theme, "foreground");
} // Literal
.syntax-n {
color: map-get($theme, "foreground");
} // Name
.syntax-o {
color: map-get($theme, "syntax-comment");
} // Operator
.syntax-x {
color: map-get($theme, "syntax-constant");
} // Other
.syntax-p {
color: map-get($theme, "foreground");
} // Punctuation
.syntax-cm {
color: map-get($theme, "syntax-comment");
} // Comment.Multiline
.syntax-cp {
color: map-get($theme, "syntax-comment");
} // Comment.Preproc
.syntax-c1 {
color: map-get($theme, "syntax-comment");
} // Comment.Single
.syntax-cs {
color: map-get($theme, "syntax-comment");
} // Comment.Special
.syntax-gd {
color: map-get($theme, "syntax-comment");
} // Generic.Deleted
.syntax-ge {
color: map-get($theme, "foreground");
font-style: italic;
} // Generic.Emph
.syntax-gr {
color: map-get($theme, "syntax-constant");
} // Generic.Error
.syntax-gh {
color: map-get($theme, "syntax-constant");
} // Generic.Heading
.syntax-gi {
color: map-get($theme, "syntax-comment");
} // Generic.Inserted
.syntax-go {
color: map-get($theme, "foreground");
} // Generic.Output
.syntax-gp {
color: map-get($theme, "foreground");
} // Generic.Prompt
.syntax-gs {
color: map-get($theme, "foreground");
font-weight: bold;
} // Generic.Strong
.syntax-gu {
color: map-get($theme, "syntax-constant");
} // Generic.Subheading
.syntax-gt {
color: map-get($theme, "foreground");
} // Generic.Traceback
.syntax-kc {
color: map-get($theme, "syntax-constant");
} // Keyword.Constant
.syntax-kd {
color: map-get($theme, "syntax-keyword");
} // Keyword.Declaration
.syntax-kn {
color: map-get($theme, "syntax-comment");
} // Keyword.Namespace
.syntax-kp {
color: map-get($theme, "syntax-comment");
} // Keyword.Pseudo
.syntax-kr {
color: map-get($theme, "syntax-keyword");
} // Keyword.Reserved
.syntax-kt {
color: map-get($theme, "syntax-keyword");
} // Keyword.Type
.syntax-ld {
color: map-get($theme, "foreground");
} // Literal.Date
.syntax-m {
color: map-get($theme, "syntax-comment");
} // Literal.Number
.syntax-s {
color: map-get($theme, "syntax-comment");
} // Literal.String
.syntax-na {
color: map-get($theme, "foreground");
} // Name.Attribute
.syntax-nb {
color: map-get($theme, "syntax-builtin");
} // Name.Builtin
.syntax-nc {
color: map-get($theme, "syntax-keyword");
} // Name.Class
.syntax-no {
color: map-get($theme, "syntax-constant");
} // Name.Constant
.syntax-nd {
color: map-get($theme, "syntax-keyword");
} // Name.Decorator
.syntax-ni {
color: map-get($theme, "syntax-builtin");
} // Name.Entity
.syntax-ne {
color: map-get($theme, "syntax-builtin");
} // Name.Exception
.syntax-nf {
color: map-get($theme, "syntax-builtin");
} // Name.Function
.syntax-nl {
color: map-get($theme, "foreground");
} // Name.Label
.syntax-nn {
color: map-get($theme, "foreground");
} // Name.Namespace
.syntax-nx {
color: map-get($theme, "foreground");
} // Name.Other
.syntax-py {
color: map-get($theme, "foreground");
} // Name.Property
.syntax-nt {
color: map-get($theme, "syntax-keyword");
} // Name.Tag
.syntax-nv {
color: map-get($theme, "syntax-keyword");
} // Name.Variable
.syntax-ow {
color: map-get($theme, "syntax-comment");
} // Operator.Word
.syntax-w {
color: map-get($theme, "foreground");
} // Text.Whitespace
.syntax-mf {
color: map-get($theme, "syntax-literal");
} // Literal.Number.Float
.syntax-mh {
color: map-get($theme, "syntax-literal");
} // Literal.Number.Hex
.syntax-mi {
color: map-get($theme, "syntax-literal");
} // Literal.Number.Integer
.syntax-mo {
color: map-get($theme, "syntax-literal");
} // Literal.Number.Oct
.syntax-sb {
color: map-get($theme, "syntax-string");
} // Literal.String.Backtick
.syntax-sc {
color: map-get($theme, "syntax-string");
} // Literal.String.Char
.syntax-sd {
color: map-get($theme, "syntax-comment");
} // Literal.String.Doc
.syntax-s2 {
color: map-get($theme, "syntax-string");
} // Literal.String.Double
.syntax-se {
color: map-get($theme, "syntax-constant");
} // Literal.String.Escape
.syntax-sh {
color: map-get($theme, "syntax-comment");
} // Literal.String.Heredoc
.syntax-si {
color: map-get($theme, "syntax-string");
} // Literal.String.Interpol
.syntax-sx {
color: map-get($theme, "syntax-string");
} // Literal.String.Other
.syntax-sr {
color: map-get($theme, "syntax-constant");
} // Literal.String.Regex
.syntax-s1 {
color: map-get($theme, "syntax-string");
} // Literal.String.Single
.syntax-ss {
color: map-get($theme, "syntax-string");
} // Literal.String.Symbol
.syntax-bp {
color: map-get($theme, "syntax-keyword");
} // Name.Builtin.Pseudo
.syntax-vc {
color: map-get($theme, "syntax-keyword");
} // Name.Variable.Class
.syntax-vg {
color: map-get($theme, "syntax-keyword");
} // Name.Variable.Global
.syntax-vi {
color: map-get($theme, "syntax-keyword");
} // Name.Variable.Instance
.syntax-il {
color: map-get($theme, "syntax-comment");
} // Literal.Number.Integer.Long
}
}
@function map-get-fallback($map, $preferred-key, $fallback-key) {
// map-get that will fall back to a second key if the first isn't set
@if (map-has-key($map, $preferred-key)) {
@return map-get($map, $preferred-key);
}
@return map-get($map, $fallback-key);
}
@function init-theme($theme) {
// check to make sure the theme has all of the essential colors set
$essential-keys:
"alert"
"background-primary"
"background-secondary"
"comment-label-exemplary"
"comment-label-joke"
"comment-label-noise"
"comment-label-offtopic"
"comment-label-malice"
"error"
"foreground-primary"
"foreground-secondary"
"link"
"link-visited"
"warning";
@each $key in $essential-keys {
@if (not map-has-key($theme, $key)) {
@error "Missing essential key in theme: #{$key}";
}
}
// set $is-light as a bool for whether background-primary seems light or dark
$is-light: lightness(map-get($theme, "background-primary")) > 50;
// colors that simply fall back to another if not defined
$background-input: map-get-fallback($theme, "background-input", "background-primary");
$border: map-get-fallback($theme, "border", "foreground-secondary");
$button: map-get-fallback($theme, "button", "link");
$button-used: map-get-fallback($theme, "button-used", "link-visited");
// stylelint-disable-next-line
$foreground-highlight:
map-get-fallback($theme, "foreground-highlight", "foreground-primary");
$stripe-mine: map-get-fallback($theme, "stripe-mine", "link-visited");
$stripe-target: map-get-fallback($theme, "stripe-target", "warning");
$syntax-builtin: map-get-fallback($theme, "syntax-builtin", "foreground-primary");
$syntax-comment: map-get-fallback($theme, "syntax-comment", "foreground-primary");
$syntax-constant: map-get-fallback($theme, "syntax-constant", "foreground-primary");
$syntax-keyword: map-get-fallback($theme, "syntax-keyword", "foreground-primary");
$syntax-literal: map-get-fallback($theme, "syntax-literal", "foreground-primary");
$syntax-string: map-get-fallback($theme, "syntax-string", "foreground-primary");
$topic-tag-nsfw: map-get-fallback($theme, "topic-tag-nsfw", "error");
$topic-tag-spoiler: map-get-fallback($theme, "topic-tag-spoiler", "warning");
// foreground-extreme: if not defined, use white on a dark bg and black on a light one
$foreground-extreme: map-get($theme, "foreground-extreme");
$foreground-extreme: if($is-light, #000, #fff) !default;
// foreground-middle: if not defined, mix foreground-primary and foreground-secondary
$foreground-middle: map-get($theme, "foreground-middle");
$foreground-middle:
mix(
map-get($theme, "foreground-primary"),
map-get($theme, "foreground-secondary")
) !default;
// link-hover: if not defined, darken the link color slightly
$link-hover: map-get($theme, "link-hover");
$link-hover: darken(map-get($theme, "link"), 5%) !default;
@return map-merge($theme, (
"background-input": $background-input,
"border": $border,
"button": $button,
"button-used": $button-used,
"foreground-extreme": $foreground-extreme,
"foreground-highlight": $foreground-highlight,
"foreground-middle": $foreground-middle,
"link-hover": $link-hover,
"stripe-mine": $stripe-mine,
"stripe-target": $stripe-target,
"syntax-builtin": $syntax-builtin,
"syntax-comment": $syntax-comment,
"syntax-constant": $syntax-constant,
"syntax-keyword": $syntax-keyword,
"syntax-literal": $syntax-literal,
"syntax-string": $syntax-string,
"topic-tag-nsfw": $topic-tag-nsfw,
"topic-tag-spoiler": $topic-tag-spoiler,
));
}