diff --git a/pylib/anki/utils.py b/pylib/anki/utils.py
index 9566a027c..2a6fcc361 100644
--- a/pylib/anki/utils.py
+++ b/pylib/anki/utils.py
@@ -226,11 +226,12 @@ def entsToTxt(html: str) -> str:
return reEnts.sub(fixup, html)
+# legacy function
def bodyClass(col, card) -> str:
- bodyclass = "card card%d" % (card.ord + 1)
- if col.conf.get("nightMode"):
- bodyclass += " nightMode"
- return bodyclass
+ from aqt.theme import theme_manager
+
+ print("bodyClass() deprecated")
+ return theme_manager.body_classes_for_card_ord(card.ord)
# IDs
diff --git a/qt/Makefile b/qt/Makefile
index 8079c4763..9f66d58b6 100644
--- a/qt/Makefile
+++ b/qt/Makefile
@@ -5,7 +5,7 @@ MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
.SUFFIXES:
-BLACKARGS := -t py36 aqt tests setup.py tools/*.py --exclude='aqt/forms|buildinfo'
+BLACKARGS := -t py36 aqt tests setup.py tools/*.py --exclude='aqt/forms|buildinfo|colors'
ISORTARGS := aqt tests setup.py
$(shell mkdir -p .build ../dist)
@@ -33,6 +33,7 @@ TSDEPS := $(wildcard ts/src/*.ts) $(wildcard ts/scss/*.scss)
.build/js: $(TSDEPS)
(cd ts && npm i && npm run build)
+ python ./tools/extract_scss_colors.py
@touch $@
.build/hooks: tools/genhooks_gui.py ../pylib/tools/hookslib.py
diff --git a/qt/aqt/.gitignore b/qt/aqt/.gitignore
index c1b440396..b56d01f0a 100644
--- a/qt/aqt/.gitignore
+++ b/qt/aqt/.gitignore
@@ -1 +1,2 @@
buildinfo.py
+colors.py
diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py
index 3808f137e..7063480ff 100644
--- a/qt/aqt/browser.py
+++ b/qt/aqt/browser.py
@@ -20,19 +20,12 @@ from anki.consts import *
from anki.lang import _, ngettext
from anki.models import NoteType
from anki.notes import Note
-from anki.utils import (
- bodyClass,
- fmtTimeSpan,
- htmlToTextLine,
- ids2str,
- intTime,
- isMac,
- isWin,
-)
+from anki.utils import fmtTimeSpan, htmlToTextLine, ids2str, intTime, isMac, isWin
from aqt import AnkiQt, gui_hooks
from aqt.editor import Editor
from aqt.qt import *
from aqt.sound import av_player
+from aqt.theme import theme_manager
from aqt.utils import (
MenuList,
SubMenu,
@@ -366,16 +359,6 @@ class DataModel(QAbstractTableModel):
# Line painter
######################################################################
-COLOUR_SUSPENDED = "#FFFFB2"
-COLOUR_MARKED = "#ccc"
-
-flagColours = {
- 1: "#ffaaaa",
- 2: "#ffb347",
- 3: "#82E0AA",
- 4: "#85C1E9",
-}
-
class StatusDelegate(QItemDelegate):
def __init__(self, browser, model):
@@ -399,13 +382,13 @@ class StatusDelegate(QItemDelegate):
col = None
if c.userFlag() > 0:
- col = flagColours[c.userFlag()]
+ col = theme_manager.qcolor(f"flag{c.userFlag()}-bg")
elif c.note().hasTag("Marked"):
- col = COLOUR_MARKED
+ col = theme_manager.qcolor("marked-bg")
elif c.queue == -1:
- col = COLOUR_SUSPENDED
+ col = theme_manager.qcolor("suspended-bg")
if col:
- brush = QBrush(QColor(col))
+ brush = QBrush(col)
painter.save()
painter.fillRect(option.rect, brush)
painter.restore()
@@ -450,7 +433,6 @@ class SidebarModel(QAbstractItemModel):
def __init__(self, root: SidebarItem) -> None:
super().__init__()
self.root = root
- self.iconCache: Dict[str, QIcon] = {}
# Qt API
######################################################################
@@ -510,18 +492,11 @@ class SidebarModel(QAbstractItemModel):
elif role == Qt.ToolTipRole:
return QVariant(item.tooltip)
else:
- return QVariant(self.iconFromRef(item.icon))
+ return QVariant(theme_manager.icon_from_resources(item.icon))
# Helpers
######################################################################
- def iconFromRef(self, iconRef: str) -> QIcon:
- icon = self.iconCache.get(iconRef)
- if icon is None:
- icon = QIcon(iconRef)
- self.iconCache[iconRef] = icon
- return icon
-
def expandWhereNeccessary(self, tree: QTreeView) -> None:
for row, child in enumerate(self.root.children):
if child.expanded:
@@ -821,9 +796,11 @@ class Browser(QMainWindow):
self.form.tableView.selectionModel()
self.form.tableView.setItemDelegate(StatusDelegate(self, self.model))
self.form.tableView.selectionModel().selectionChanged.connect(self.onRowChanged)
- self.form.tableView.setStyleSheet(
- "QTableView{ selection-background-color: rgba(127, 127, 127, 50); }"
- )
+ if not theme_manager.night_mode:
+ self.form.tableView.setStyleSheet(
+ "QTableView{ selection-background-color: rgba(150, 150, 150, 50); "
+ "selection-color: black; }"
+ )
self.singleCard = False
def setupEditor(self):
@@ -1709,7 +1686,7 @@ where id in %s"""
txt = c.a()
txt = re.sub(r"\[\[type:[^]]+\]\]", "", txt)
- bodyclass = bodyClass(self.mw.col, c)
+ bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
if self.mw.reviewer.autoplay(c):
# if we're showing both sides at once, play question audio first
diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py
index aa4ef8253..5ab4bafe2 100644
--- a/qt/aqt/clayout.py
+++ b/qt/aqt/clayout.py
@@ -9,10 +9,11 @@ import re
import aqt
from anki.consts import *
from anki.lang import _, ngettext
-from anki.utils import bodyClass, isMac, isWin, joinFields
+from anki.utils import isMac, isWin, joinFields
from aqt import gui_hooks
from aqt.qt import *
from aqt.sound import av_player
+from aqt.theme import theme_manager
from aqt.utils import (
askUser,
downArrow,
@@ -336,7 +337,7 @@ Please create a new card type first."""
c = self.card
ti = self.maybeTextInput
- bodyclass = bodyClass(self.mw.col, c)
+ bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
q = ti(mungeQA(self.mw.col, c.q(reload=True)))
q = gui_hooks.card_will_show(q, c, "clayoutQuestion")
diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py
index a152d75f0..8b81d4d2f 100644
--- a/qt/aqt/deckbrowser.py
+++ b/qt/aqt/deckbrowser.py
@@ -209,16 +209,16 @@ where id > ?""",
name,
)
# due counts
- def nonzeroColour(cnt, colour):
+ def nonzeroColour(cnt, klass):
if not cnt:
- colour = "#e0e0e0"
+ klass = "zero-count"
if cnt >= 1000:
cnt = "1000+"
- return "%s" % (colour, cnt)
+ return f'{cnt}'
buf += "
%s | %s | " % (
- nonzeroColour(due, "#007700"),
- nonzeroColour(new, "#000099"),
+ nonzeroColour(due, "review-count"),
+ nonzeroColour(new, "new-count"),
)
# options
buf += (
diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py
index b7795bf7f..cfd6d0545 100644
--- a/qt/aqt/editor.py
+++ b/qt/aqt/editor.py
@@ -442,10 +442,10 @@ class Editor:
self.web.evalWithCallback("saveNow(%d)" % keepFocus, lambda res: callback())
def checkValid(self):
- cols = ["#fff"] * len(self.note.fields)
+ cols = [""] * len(self.note.fields)
err = self.note.dupeOrEmpty()
if err == 2:
- cols[0] = "#fcc"
+ cols[0] = "dupe"
self.web.eval("showDupes();")
else:
self.web.eval("hideDupes();")
diff --git a/qt/aqt/main.py b/qt/aqt/main.py
index a4e2824d2..27e1c0255 100644
--- a/qt/aqt/main.py
+++ b/qt/aqt/main.py
@@ -5,7 +5,6 @@
import faulthandler
import gc
import os
-import platform
import re
import signal
import time
@@ -37,6 +36,7 @@ from aqt.profiles import ProfileManager as ProfileManagerType
from aqt.qt import *
from aqt.qt import sip
from aqt.taskman import TaskManager
+from aqt.theme import theme_manager
from aqt.utils import (
askUser,
checkInvalidFilename,
@@ -58,6 +58,7 @@ class AnkiQt(QMainWindow):
col: _Collection
pm: ProfileManagerType
web: aqt.webview.AnkiWebView
+ bottomWeb: aqt.webview.AnkiWebView
def __init__(
self,
@@ -111,9 +112,9 @@ class AnkiQt(QMainWindow):
self.setupMediaServer()
self.setupSound()
self.setupSpellCheck()
+ self.setupStyle()
self.setupMainWindow()
self.setupSystemSpecific()
- self.setupStyle()
self.setupMenus()
self.setupProgress()
self.setupErrorHandler()
@@ -850,33 +851,8 @@ title="%s" %s>%s""" % (
return True
def setupStyle(self) -> None:
- buf = ""
-
- if isWin and platform.release() == "10":
- # add missing bottom border to menubar
- buf += """
-QMenuBar {
- border-bottom: 1px solid #aaa;
- background: white;
-}
-"""
- # qt bug? setting the above changes the browser sidebar
- # to white as well, so set it back
- buf += """
-QTreeWidget {
- background: #eee;
-}
- """
-
- # allow addons to modify the styling
- buf = gui_hooks.style_did_init(buf)
-
- # allow users to extend styling
- p = os.path.join(aqt.mw.pm.base, "style.css")
- if os.path.exists(p):
- buf += open(p).read()
-
- self.app.setStyleSheet(buf)
+ theme_manager.night_mode = self.pm.night_mode()
+ theme_manager.apply_style(self.app)
# Key handling
##########################################################################
diff --git a/qt/aqt/overview.py b/qt/aqt/overview.py
index e5a9c9891..3a75eb8a7 100644
--- a/qt/aqt/overview.py
+++ b/qt/aqt/overview.py
@@ -189,9 +189,9 @@ to their original deck."""
-%s: | %s |
-%s: | %s |
-%s: | %s |
+%s: | %s |
+%s: | %s |
+%s: | %s |
|
%s |
""" % (
diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py
index 669c828ad..53714d911 100644
--- a/qt/aqt/preferences.py
+++ b/qt/aqt/preferences.py
@@ -85,7 +85,6 @@ class Preferences(QDialog):
f.timeLimit.setValue(qc["timeLim"] / 60.0)
f.showEstimates.setChecked(qc["estTimes"])
f.showProgress.setChecked(qc["dueCounts"])
- f.nightMode.setChecked(qc.get("nightMode", False))
f.newSpread.addItems(list(c.newCardSchedulingLabels().values()))
f.newSpread.setCurrentIndex(qc["newSpread"])
f.useCurrent.setCurrentIndex(int(not qc.get("addToCur", True)))
@@ -113,7 +112,6 @@ class Preferences(QDialog):
qc["dueCounts"] = f.showProgress.isChecked()
qc["estTimes"] = f.showEstimates.isChecked()
qc["newSpread"] = f.newSpread.currentIndex()
- qc["nightMode"] = f.nightMode.isChecked()
qc["timeLim"] = f.timeLimit.value() * 60
qc["collapseTime"] = f.lrnCutoff.value() * 60
qc["addToCur"] = not f.useCurrent.currentIndex()
@@ -227,12 +225,22 @@ Not currently enabled; click the sync button in the main window to enable."""
self.form.uiScale.setValue(self.mw.pm.uiScale() * 100)
self.form.pasteInvert.setChecked(self.prof.get("pasteInvert", False))
self.form.showPlayButtons.setChecked(self.prof.get("showPlayButtons", True))
+ self.form.nightMode.setChecked(self.mw.pm.night_mode())
def updateOptions(self):
+ restart_required = False
+
self.prof["pastePNG"] = self.form.pastePNG.isChecked()
self.prof["pasteInvert"] = self.form.pasteInvert.isChecked()
newScale = self.form.uiScale.value() / 100
if newScale != self.mw.pm.uiScale():
self.mw.pm.setUiScale(newScale)
- showInfo(_("Changes will take effect when you restart Anki."))
+ restart_required = True
self.prof["showPlayButtons"] = self.form.showPlayButtons.isChecked()
+
+ if self.mw.pm.night_mode() != self.form.nightMode.isChecked():
+ self.mw.pm.set_night_mode(not self.mw.pm.night_mode())
+ restart_required = True
+
+ if restart_required:
+ showInfo(_("Changes will take effect when you restart Anki."))
diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py
index 092e1cabe..51ee3c8de 100644
--- a/qt/aqt/profiles.py
+++ b/qt/aqt/profiles.py
@@ -495,3 +495,9 @@ please see:
def set_last_addon_update_check(self, secs):
self.meta["last_addon_update_check"] = secs
+
+ def night_mode(self) -> bool:
+ return self.meta.get("night_mode", False)
+
+ def set_night_mode(self, on: bool) -> None:
+ self.meta["night_mode"] = on
diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py
index ebaef9038..c7c7371c5 100644
--- a/qt/aqt/reviewer.py
+++ b/qt/aqt/reviewer.py
@@ -14,10 +14,11 @@ from anki import hooks
from anki.cards import Card
from anki.lang import _, ngettext
from anki.sound import AVTag
-from anki.utils import bodyClass, stripHTML
+from anki.utils import stripHTML
from aqt import AnkiQt, gui_hooks
from aqt.qt import *
from aqt.sound import av_player, getAudio
+from aqt.theme import theme_manager
from aqt.toolbar import BottomBar
from aqt.utils import (
askUserDialog,
@@ -199,7 +200,7 @@ The front of this card is empty. Please run Tools>Empty Cards."""
q = self._mungeQA(q)
q = gui_hooks.card_will_show(q, c, "reviewQuestion")
- bodyclass = bodyClass(self.mw.col, c)
+ bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
self.web.eval("_showQuestion(%s,'%s');" % (json.dumps(q), bodyclass))
self._drawFlag()
@@ -600,9 +601,9 @@ time = %(time)d;
idx = self.mw.col.sched.countIdx(self.card)
counts[idx] = "%s" % (counts[idx])
space = " + "
- ctxt = '%s' % counts[0]
- ctxt += space + '%s' % counts[1]
- ctxt += space + '%s' % counts[2]
+ ctxt = "%s" % counts[0]
+ ctxt += space + "%s" % counts[1]
+ ctxt += space + "%s" % counts[2]
return ctxt
def _defaultEase(self):
diff --git a/qt/aqt/theme.py b/qt/aqt/theme.py
new file mode 100644
index 000000000..3d0e4e9f9
--- /dev/null
+++ b/qt/aqt/theme.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+# Copyright: Ankitects Pty Ltd and contributors
+# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import platform
+from typing import Dict
+
+from aqt import QApplication, gui_hooks, isWin
+from aqt.colors import colors
+from aqt.qt import QColor, QIcon, QPalette, QPixmap, QStyleFactory, Qt
+
+
+class ThemeManager:
+ night_mode = True
+
+ _icon_cache: Dict[str, QIcon] = {}
+ _icon_size = 128
+
+ def icon_from_resources(self, path: str) -> QIcon:
+ "Fetch icon from Qt resources, and invert if in night mode."
+ icon = self._icon_cache.get(path)
+ if icon:
+ return icon
+
+ icon = QIcon(path)
+
+ if self.night_mode:
+ img = icon.pixmap(self._icon_size, self._icon_size).toImage()
+ img.invertPixels()
+ icon = QIcon(QPixmap(img))
+
+ return self._icon_cache.setdefault(path, icon)
+
+ def body_class(self) -> str:
+ "Returns '' in normal mode, 'nightMode' in night mode."
+ return self.night_mode and "nightMode" or ""
+
+ def body_classes_for_card_ord(self, card_ord: int) -> str:
+ "Returns body classes used when showing a card."
+ return f"card card{card_ord+1} {self.body_class()}"
+
+ def str_color(self, key: str) -> str:
+ """Get a color defined in _vars.scss
+
+ If the colour is called '$day-frame-bg', key should be
+ 'frame-bg'.
+
+ Returns the color as a string hex code or color name."""
+ prefix = self.night_mode and "night-" or "day-"
+ c = colors.get(prefix + key)
+ if c is None:
+ raise Exception("no such color:", key)
+ return c
+
+ def qcolor(self, key: str) -> QColor:
+ """Get a color defined in _vars.scss as a QColor."""
+ return QColor(self.str_color(key))
+
+ def apply_style(self, app: QApplication) -> None:
+ self._apply_palette(app)
+ self._apply_style(app)
+
+ def _apply_style(self, app: QApplication) -> None:
+ buf = ""
+
+ if isWin and platform.release() == "10" and not self.night_mode:
+ # add missing bottom border to menubar
+ buf += """
+QMenuBar {
+ border-bottom: 1px solid #aaa;
+ background: white;
+}
+"""
+ # qt bug? setting the above changes the browser sidebar
+ # to white as well, so set it back
+ buf += """
+QTreeWidget {
+ background: #eee;
+}
+ """
+
+ # allow addons to modify the styling
+ buf = gui_hooks.style_did_init(buf)
+
+ app.setStyleSheet(buf)
+
+ def _apply_palette(self, app: QApplication) -> None:
+ if not self.night_mode:
+ return
+
+ app.setStyle(QStyleFactory.create("fusion")) # type: ignore
+
+ palette = QPalette()
+
+ text_fg = self.qcolor("text-fg")
+ palette.setColor(QPalette.WindowText, text_fg)
+ palette.setColor(QPalette.ToolTipBase, text_fg)
+ palette.setColor(QPalette.ToolTipText, text_fg)
+ palette.setColor(QPalette.Text, text_fg)
+ palette.setColor(QPalette.ButtonText, text_fg)
+
+ hlbg = self.qcolor("highlight-bg")
+ hlbg.setAlpha(64)
+ palette.setColor(QPalette.HighlightedText, self.qcolor("highlight-fg"))
+ palette.setColor(QPalette.Highlight, hlbg)
+
+ window_bg = self.qcolor("window-bg")
+ palette.setColor(QPalette.Window, window_bg)
+ palette.setColor(QPalette.AlternateBase, window_bg)
+ palette.setColor(QPalette.Button, window_bg)
+
+ palette.setColor(QPalette.Base, self.qcolor("frame-bg"))
+
+ disabled_color = self.qcolor("disabled")
+ palette.setColor(QPalette.Disabled, QPalette.Text, disabled_color)
+ palette.setColor(QPalette.Disabled, QPalette.ButtonText, disabled_color)
+ palette.setColor(QPalette.Disabled, QPalette.HighlightedText, disabled_color)
+
+ palette.setColor(QPalette.Link, self.qcolor("link"))
+
+ palette.setColor(QPalette.BrightText, Qt.red)
+
+ app.setPalette(palette)
+
+
+theme_manager = ThemeManager()
diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py
index f40ed8ce8..a095c9c41 100644
--- a/qt/aqt/webview.py
+++ b/qt/aqt/webview.py
@@ -10,6 +10,7 @@ from anki.lang import _
from anki.utils import isLin, isMac, isWin
from aqt import gui_hooks
from aqt.qt import *
+from aqt.theme import theme_manager
from aqt.utils import openLink
# Page for debug messages
@@ -308,6 +309,8 @@ div[contenteditable="true"]:focus {
head = mw.baseHTML() + head + csstxt + jstxt
+ body_class = theme_manager.body_class()
+
html = """
@@ -321,7 +324,7 @@ body {{ zoom: {}; background: {}; {} }}
{}
-{}
+{}
""".format(
self.title,
self.zoomFactor(),
@@ -329,6 +332,7 @@ body {{ zoom: {}; background: {}; {} }}
fontspec,
widgetspec,
head,
+ body_class,
body,
)
# print(html)
diff --git a/qt/designer/preferences.ui b/qt/designer/preferences.ui
index 2f638ceb3..87e80b9bb 100644
--- a/qt/designer/preferences.ui
+++ b/qt/designer/preferences.ui
@@ -7,7 +7,7 @@
0
0
422
- 579
+ 586
@@ -111,7 +111,7 @@
-
- Show cards as white on black (night mode)
+ Night mode
diff --git a/qt/tools/extract_scss_colors.py b/qt/tools/extract_scss_colors.py
new file mode 100644
index 000000000..6202689f3
--- /dev/null
+++ b/qt/tools/extract_scss_colors.py
@@ -0,0 +1,23 @@
+import re
+
+import json
+
+colors = {}
+
+for line in open("ts/scss/_vars.scss"):
+ line = line.strip()
+ if not line:
+ continue
+ m = re.match(r"^\$(.+): (.+);$", line)
+ if not m:
+ print("failed to match", line)
+ continue
+
+ var = m.group(1)
+ val = m.group(2)
+
+ colors[var] = val
+
+with open("aqt/colors.py", "w") as buf:
+ buf.write("# this file is auto-generated from _vars.scss\n")
+ buf.write("colors = " + json.dumps(colors))
diff --git a/qt/ts/scss/_buttons.scss b/qt/ts/scss/_buttons.scss
new file mode 100644
index 000000000..a8c58d154
--- /dev/null
+++ b/qt/ts/scss/_buttons.scss
@@ -0,0 +1,27 @@
+@use 'vars';
+
+.nightMode {
+ button {
+ -webkit-appearance: none;
+ color: vars.$night-text-fg;
+
+ /* match the fusion button gradient */
+ background: linear-gradient(0deg,
+ vars.$fusion-button-gradient-start 0%,
+ vars.$fusion-button-gradient-end 100%);
+ box-shadow: 0 0 3px vars.$fusion-button-outline;
+ border: 1px solid vars.$night-faint-border;
+
+ border-radius: 3px;
+ height: 24px;
+ padding: 5px;
+ padding-left: 10px;
+ padding-right: 10px;
+ padding-bottom: 18px;
+ }
+
+ button:hover {
+ background: vars.$fusion-button-hover-bg;
+ }
+}
+
diff --git a/qt/ts/scss/_card_counts.scss b/qt/ts/scss/_card_counts.scss
new file mode 100644
index 000000000..e72e764ad
--- /dev/null
+++ b/qt/ts/scss/_card_counts.scss
@@ -0,0 +1,28 @@
+@use 'vars';
+
+.review-count {
+ color: vars.$day-review-count;
+}
+
+.new-count {
+ color: vars.$day-new-count;
+}
+
+.learn-count {
+ color: vars.$day-learn-count;
+}
+
+.nightMode {
+ .review-count {
+ color: vars.$night-review-count;
+ }
+
+ .new-count {
+ color: vars.$night-new-count;
+ }
+
+ .learn-count {
+ color: vars.$night-learn-count;
+ }
+}
+
diff --git a/qt/ts/scss/_vars.scss b/qt/ts/scss/_vars.scss
new file mode 100644
index 000000000..9aa60ab36
--- /dev/null
+++ b/qt/ts/scss/_vars.scss
@@ -0,0 +1,46 @@
+$day-text-fg: black;
+$day-window-bg: white;
+$day-frame-bg: white;
+$day-border: #aaa;
+$day-faint-border: #e7e7e7;
+$day-link: #00a;
+$day-review-count: #0a0;
+$day-new-count: #00a;
+$day-learn-count: #C35617;
+$day-zero-count: #aaa;
+$day-slightly-grey-text: #333;
+$day-highlight-bg: #77ccff;
+$day-highlight-fg: black;
+$day-disabled: #777;
+$day-flag1-bg: #ffaaaa;
+$day-flag2-bg: #ffb347;
+$day-flag3-bg: #82E0AA;
+$day-flag4-bg: #85C1E9;
+$day-suspended-bg: #FFFFB2;
+$day-marked-bg: #cce;
+
+$night-text-fg: white;
+$night-window-bg: #2f2f31;
+$night-frame-bg: #3a3a3a;
+$night-border: #777;
+$night-faint-border: #444;
+$night-link: #aaf;
+$night-review-count: #7CFC00;
+$night-new-count: #77ccff;
+$night-learn-count: #FF935B;
+$night-zero-count: #777;
+$night-slightly-grey-text: #ccc;
+$night-highlight-bg: #77ccff;
+$night-highlight-fg: white;
+$night-disabled: #777;
+$night-flag1-bg: #aa5555;
+$night-flag2-bg: #aa6337;
+$night-flag3-bg: #33a055;
+$night-flag4-bg: #3581a9;
+$night-suspended-bg: #aaaa33;
+$night-marked-bg: #77c;
+
+$fusion-button-gradient-start: #363636;
+$fusion-button-gradient-end: #404040;
+$fusion-button-outline: #000;
+$fusion-button-hover-bg: #454545;
diff --git a/qt/ts/scss/deckbrowser.scss b/qt/ts/scss/deckbrowser.scss
index 57887de2b..13b186a89 100644
--- a/qt/ts/scss/deckbrowser.scss
+++ b/qt/ts/scss/deckbrowser.scss
@@ -1,8 +1,11 @@
/* Copyright: Ankitects Pty Ltd and contributors
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
+@use 'vars';
+@use 'card_counts';
+
a.deck {
- color: #000;
+ color: vars.$day-text-fg;
text-decoration: none;
min-width: 5em;
display: inline-block;
@@ -13,7 +16,7 @@ a.deck:hover {
}
tr.deck td {
- border-bottom: 1px solid #e7e7e7;
+ border-bottom: 1px solid vars.$day-faint-border;
}
tr.top-level-drag-row td {
@@ -25,7 +28,7 @@ td {
}
tr.drag-hover td {
- border-bottom: 1px solid #aaa;
+ border-bottom: 1px solid vars.$day-border;
}
body {
@@ -34,7 +37,7 @@ body {
}
.current {
- background-color: #e7e7e7;
+ background-color: vars.$day-faint-border;
}
.decktd {
@@ -51,14 +54,14 @@ body {
}
.collapse {
- color: #000;
+ color: vars.$day-text-fg;
text-decoration: none;
display: inline-block;
width: 1em;
}
.filtered {
- color: #00a !important;
+ color: vars.$day-link !important;
}
.gears {
@@ -67,3 +70,29 @@ body {
opacity: .5;
padding-top: 0.2em;
}
+
+.nightMode {
+ a.deck {
+ color: vars.$night-text-fg;
+ }
+
+ tr.deck td {
+ border-bottom-color: vars.$night-faint-border;
+ }
+
+ tr.drag-hover td {
+ border-bottom-color: vars.$night-border;
+ }
+
+ .current {
+ background-color: vars.$night-faint-border;
+ }
+
+ .collapse {
+ color: vars.$night-text-fg;
+ }
+
+ .gears {
+ filter: invert(180);
+ }
+}
\ No newline at end of file
diff --git a/qt/ts/scss/editor.scss b/qt/ts/scss/editor.scss
index 6a51a19d6..a21d40f8b 100644
--- a/qt/ts/scss/editor.scss
+++ b/qt/ts/scss/editor.scss
@@ -1,10 +1,12 @@
/* Copyright: Ankitects Pty Ltd and contributors
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
+@use 'vars';
+
.field {
- border: 1px solid #aaa;
- background: #fff;
- color: #000;
+ border: 1px solid vars.$day-border;
+ background: vars.$day-frame-bg;
+ color: vars.$day-text-fg;
padding: 5px;
overflow-wrap: break-word;
}
@@ -49,9 +51,10 @@ body {
color-stop(100%, #77f));
}
-.linkb {
+button.linkb {
-webkit-appearance: none;
border: 0;
+ box-shadow: none;
padding: 0px 2px;
background: transparent;
}
@@ -68,3 +71,27 @@ body {
#fields {
margin-top: 35px;
}
+
+.dupe {
+ background: #fcc;
+}
+
+.nightMode {
+ .field {
+ border-color: vars.$night-border;
+ background: vars.$night-frame-bg;
+ color: vars.$night-text-fg;
+ }
+
+ button.linkb > img {
+ filter: invert(180);
+ }
+
+ .dupe {
+ background: #a00;
+ }
+
+ #dupes a {
+ color: vars.$night-link;
+ }
+}
\ No newline at end of file
diff --git a/qt/ts/scss/overview.scss b/qt/ts/scss/overview.scss
index d11b18e5f..82c3f1557 100644
--- a/qt/ts/scss/overview.scss
+++ b/qt/ts/scss/overview.scss
@@ -1,6 +1,9 @@
/* Copyright: Ankitects Pty Ltd and contributors
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
+@use 'vars';
+@use 'card_counts';
+
.smallLink {
font-size: 10px;
}
@@ -11,7 +14,7 @@ h3 {
.descfont {
padding: 1em;
- color: #333;
+ color: vars.$day-slightly-grey-text;
}
.description {
@@ -31,3 +34,9 @@ h3 {
.dyn {
text-align: center;
}
+
+.nightMode {
+ .descfont {
+ color: vars.$night-slightly-grey-text;
+ }
+}
diff --git a/qt/ts/scss/reviewer-bottom.scss b/qt/ts/scss/reviewer-bottom.scss
index 29c1c94fc..95810fc2c 100644
--- a/qt/ts/scss/reviewer-bottom.scss
+++ b/qt/ts/scss/reviewer-bottom.scss
@@ -1,8 +1,11 @@
/* Copyright: Ankitects Pty Ltd and contributors
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
+@use 'vars';
+@use 'card_counts';
+
body {
- margin: 0px;
+ margin: 0;
padding: 0;
}
@@ -48,9 +51,15 @@ button {
}
#outer {
- border-top: 1px solid #aaa;
+ border-top: 1px solid vars.$day-border;
}
#innertable {
padding: 3px;
}
+
+.nightMode {
+ #outer {
+ border-top-color: vars.$night-border;
+ }
+}
diff --git a/qt/ts/scss/toolbar.scss b/qt/ts/scss/toolbar.scss
index 8c842b564..970a81535 100644
--- a/qt/ts/scss/toolbar.scss
+++ b/qt/ts/scss/toolbar.scss
@@ -1,10 +1,12 @@
/* Copyright: Ankitects Pty Ltd and contributors
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
+@use 'vars';
+
#header {
padding: 3px;
font-weight: bold;
- border-bottom: 1px solid #aaa;
+ border-bottom: 1px solid vars.$day-border;
}
.tdcenter {
@@ -26,11 +28,7 @@ body {
padding-right: 12px;
padding-left: 12px;
text-decoration: none;
- color: #000;
-}
-
-.nightMode .hitem {
- color: white;
+ color: vars.$day-text-fg;
}
.hitem:hover {
@@ -38,3 +36,13 @@ body {
}
.hitem:focus { outline: 0; }
+
+.nightMode {
+ .hitem {
+ color: vars.$night-text-fg;
+ }
+
+ #header {
+ border-bottom-color: vars.$night-border;
+ }
+}
\ No newline at end of file
diff --git a/qt/ts/scss/webview.scss b/qt/ts/scss/webview.scss
index b824b591a..b729411fc 100644
--- a/qt/ts/scss/webview.scss
+++ b/qt/ts/scss/webview.scss
@@ -1,6 +1,9 @@
/* Copyright: Ankitects Pty Ltd and contributors
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
+@use 'vars';
+@use 'buttons';
+
body {
margin: 2em;
}
@@ -10,6 +13,6 @@ h1 {
}
body.nightMode {
- color: white;
- background: #2f2f31;
+ color: vars.$night-text-fg;
+ background: vars.$night-window-bg;
}
diff --git a/qt/ts/src/editor.ts b/qt/ts/src/editor.ts
index 8efe4bf23..3336eef8e 100644
--- a/qt/ts/src/editor.ts
+++ b/qt/ts/src/editor.ts
@@ -352,7 +352,11 @@ function setFields(fields) {
function setBackgrounds(cols) {
for (let i = 0; i < cols.length; i++) {
- $("#f" + i).css("background", cols[i]);
+ if (cols[i] == "dupe") {
+ $("#f" + i).addClass("dupe");
+ } else {
+ $("#f" + i).removeClass("dupe");
+ }
}
}