This commit is contained in:
dgtlmoon committed 2025-12-29 09:06:47 +01:00
1 parent 55c45dd792
commit 438dd6c4cf
8 files changed
+71 -64

No files matched your search

@@ -42,11 +42,12 @@
<fieldset>
<div class="pure-control-group">
{{ render_field(form.requests.form.time_between_check, class="time-check-widget") }}
<span class="pure-form-message-inline">Default recheck time for all watches, current system minimum is <i>{{min_system_recheck_seconds}}</i> seconds (<a href="https://github.com/dgtlmoon/changedetection.io/wiki/Misc-system-settings#enviroment-variables">more info</a>).</span>
<div id="time-between-check-schedule">
<!-- Start Time and End Time -->
<!-- Start Time and End Time {{ timezone_default_config }} -->
<div id="limit-between-time">
{{ render_time_schedule_form(form.requests, available_timezones, timezone_default_config) }}
{{ render_time_schedule_form(form.requests, available_timezones, timezone_default_config) }}
</div>
</div>
</div>
-6
View File
@@ -1009,12 +1009,6 @@ class globalSettingsApplicationForm(commonSettingsForm):
render_kw={"placeholder": "0.1", "style": "width: 8em;"}
)
comparison_threshold = SelectField(
'Screenshot: Pixel Difference Sensitivity',
choices=SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS,
default=SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT
)
password = SaltyPasswordField()
pager_size = IntegerField('Pager size',
render_kw={"style": "width: 5em;"},
@@ -6,11 +6,13 @@ which is 10-100x faster than SSIM while still detecting meaningful visual change
"""
import os
from pathlib import Path
processor_description = "Visual/Screenshot change detection (Fast)"
processor_name = "image_ssim_diff"
processor_weight = 2 # Lower weight = appears at top, heavier weight = appears lower (bottom)
PROCESSOR_CONFIG_NAME = f"{Path(__file__).parent.name}.json"
# Subprocess timeout settings
# Maximum time to wait for subprocess operations (seconds)
@@ -20,9 +22,11 @@ POLL_TIMEOUT_ABSOLUTE = int(os.getenv('OPENCV_SUBPROCESS_TIMEOUT', '20'))
CROPPED_IMAGE_TEMPLATE_FILENAME = 'cropped_image_template.png'
SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS = [
('75', 'Low sensitivity (only major changes)'),
('30', 'Medium sensitivity (moderate changes)'),
('200', 'Low sensitivity (only major changes)'),
('80', 'Medium sensitivity (moderate changes - recommended)'),
('20', 'High sensitivity (small changes)'),
('0', 'Very high sensitivity (any change)')
]
SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT=0.999
OPENCV_BLUR_SIGMA=float(os.getenv("OPENCV_BLUR_SIGMA", "3.0"))
@@ -10,7 +10,8 @@ import json
import time
from loguru import logger
from changedetectionio.processors.image_ssim_diff import SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT
from changedetectionio.processors.image_ssim_diff import SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT, PROCESSOR_CONFIG_NAME, \
OPENCV_BLUR_SIGMA
# All image operations now use OpenCV via isolated_opencv subprocess handler
# No direct handler imports needed - subprocess isolation handles everything
@@ -81,18 +82,25 @@ def get_asset(asset_name, watch, datastore, request):
img_bytes_from = watch.get_history_snapshot(timestamp=from_version)
img_bytes_to = watch.get_history_snapshot(timestamp=to_version)
# Get pixel difference threshold sensitivity
pixel_difference_threshold_sensitivity = watch.get('comparison_threshold')
if not pixel_difference_threshold_sensitivity or pixel_difference_threshold_sensitivity == '':
pixel_difference_threshold_sensitivity = datastore.data['settings']['application'].get('comparison_threshold', SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT)
# Get pixel difference threshold sensitivity (per-watch > global)
# This controls how different a pixel must be (0-255 scale) to count as "changed"
from changedetectionio import processors
processor_instance = processors.difference_detection_processor(datastore, watch.get('uuid'))
processor_config = processor_instance.get_extra_watch_config(PROCESSOR_CONFIG_NAME)
pixel_difference_threshold_sensitivity = processor_config.get('pixel_difference_threshold_sensitivity')
if not pixel_difference_threshold_sensitivity:
pixel_difference_threshold_sensitivity = datastore.data['settings']['application'].get(
'pixel_difference_threshold_sensitivity', SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT)
try:
pixel_difference_threshold_sensitivity = float(pixel_difference_threshold_sensitivity)
pixel_difference_threshold_sensitivity = int(pixel_difference_threshold_sensitivity)
except (ValueError, TypeError):
pixel_difference_threshold_sensitivity = 30.0
logger.warning(
f"Invalid pixel_difference_threshold_sensitivity value '{pixel_difference_threshold_sensitivity}', using default")
pixel_difference_threshold_sensitivity = SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT
logger.debug(f"Pixel difference threshold sensitivity is {pixel_difference_threshold_sensitivity}")
# Get blur sigma
blur_sigma = float(os.getenv("OPENCV_BLUR_SIGMA", "0.8"))
# Generate diff in isolated subprocess (async-safe)
import asyncio
@@ -105,7 +113,7 @@ def get_asset(asset_name, watch, datastore, request):
img_bytes_from,
img_bytes_to,
pixel_difference_threshold=int(pixel_difference_threshold_sensitivity),
blur_sigma=blur_sigma,
blur_sigma=OPENCV_BLUR_SIGMA,
max_width=MAX_DIFF_WIDTH,
max_height=MAX_DIFF_HEIGHT
)
@@ -330,9 +338,9 @@ def render(watch, datastore, request, url_for, render_template, flash, redirect)
to_version = versions[-1]
# Get pixel difference threshold sensitivity (per-watch > global > env default)
pixel_difference_threshold_sensitivity = watch.get('comparison_threshold')
pixel_difference_threshold_sensitivity = watch.get('pixel_difference_threshold_sensitivity')
if not pixel_difference_threshold_sensitivity or pixel_difference_threshold_sensitivity == '':
pixel_difference_threshold_sensitivity = datastore.data['settings']['application'].get('comparison_threshold', SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT)
pixel_difference_threshold_sensitivity = datastore.data['settings']['application'].get('pixel_difference_threshold_sensitivity', SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT)
# Convert to appropriate type
try:
@@ -342,7 +350,7 @@ def render(watch, datastore, request, url_for, render_template, flash, redirect)
pixel_difference_threshold_sensitivity = 30.0
# Get blur sigma
blur_sigma = float(os.getenv("OPENCV_BLUR_SIGMA", "0.8"))
blur_sigma = OPENCV_BLUR_SIGMA
# Load screenshots from history
try:
@@ -2,7 +2,7 @@
Configuration forms for fast screenshot comparison processor.
"""
from wtforms import SelectField, StringField, FloatField, validators, ValidationError
from wtforms import SelectField, StringField, validators, ValidationError, IntegerField
from changedetectionio.forms import processor_text_json_diff_form
import re
@@ -42,16 +42,16 @@ def validate_selection_mode(form, field):
class processor_settings_form(processor_text_json_diff_form):
"""Form for fast image comparison processor settings."""
min_change_percentage = FloatField(
processor_config_min_change_percentage = IntegerField(
'Minimum Change Percentage',
validators=[
validators.Optional(),
validators.NumberRange(min=0.0, max=100.0, message='Must be between 0 and 100')
validators.NumberRange(min=1, max=100, message='Must be between 0 and 100')
],
render_kw={"placeholder": "Use global default (0.1)"}
)
comparison_threshold = SelectField(
processor_config_pixel_difference_threshold_sensitivity = SelectField(
'Pixel Difference Sensitivity',
choices=[
('', 'Use global default')
@@ -86,14 +86,16 @@ class processor_settings_form(processor_text_json_diff_form):
return 'Screenshot Comparison'
def extra_form_content(self):
"""Render processor-specific form fields."""
"""Render processor-specific form fields.
@NOTE: prepend processor_config_* to the field name so it will save into its own datadir/uuid/image_ssim_diff.json and be read at process time
"""
return '''
{% from '_helpers.html' import render_field %}
<fieldset>
<legend>Screenshot Comparison Settings</legend>
<div class="pure-control-group">
{{ render_field(form.min_change_percentage) }}
{{ render_field(form.processor_config_min_change_percentage) }}
<span class="pure-form-message-inline">
<strong>What percentage of pixels must change to trigger a detection?</strong><br>
For example, <strong>0.1%</strong> means if 0.1% or more of the pixels change, it counts as a change.<br>
@@ -104,7 +106,7 @@ class processor_settings_form(processor_text_json_diff_form):
</div>
<div class="pure-control-group">
{{ render_field(form.comparison_threshold) }}
{{ render_field(form.processor_config_pixel_difference_threshold_sensitivity) }}
<span class="pure-form-message-inline">
<strong>How different must an individual pixel be to count as "changed"?</strong><br>
<strong>Low sensitivity (75)</strong> = Only count pixels that changed significantly (0-255 scale).<br>
@@ -12,7 +12,7 @@ import time
from loguru import logger
from changedetectionio.processors import difference_detection_processor, SCREENSHOT_FORMAT_PNG
from changedetectionio.processors.exceptions import ProcessorException
from . import SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT
from . import SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT, PROCESSOR_CONFIG_NAME, OPENCV_BLUR_SIGMA
# All image operations now use OpenCV via isolated_opencv subprocess handler
# Template matching temporarily disabled pending OpenCV implementation
@@ -55,25 +55,38 @@ class perform_site_check(difference_detection_processor):
else:
logger.debug(f"UUID: {watch.get('uuid')} - Screenshot MD5 changed")
# Check if bounding box is set (for drawn area mode)
# Read from processor-specific config JSON file (named after processor)
crop_region = None
processor_config = self.get_extra_watch_config(PROCESSOR_CONFIG_NAME)
bounding_box = processor_config.get('bounding_box') if processor_config else None
# Get pixel difference threshold sensitivity (per-watch > global)
# This controls how different a pixel must be (0-255 scale) to count as "changed"
pixel_difference_threshold_sensitivity = watch.get('comparison_threshold')
pixel_difference_threshold_sensitivity = processor_config.get('pixel_difference_threshold_sensitivity')
if not pixel_difference_threshold_sensitivity:
pixel_difference_threshold_sensitivity = self.datastore.data['settings']['application'].get('comparison_threshold', SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT)
pixel_difference_threshold_sensitivity = self.datastore.data['settings']['application'].get('pixel_difference_threshold_sensitivity', SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT)
try:
pixel_difference_threshold_sensitivity = int(pixel_difference_threshold_sensitivity)
except (ValueError, TypeError):
logger.warning(f"Invalid pixel_difference_threshold_sensitivity value '{pixel_difference_threshold_sensitivity}', using default")
pixel_difference_threshold_sensitivity = SCREENSHOT_COMPARISON_THRESHOLD_OPTIONS_DEFAULT
# Check if bounding box is set (for drawn area mode)
# Read from processor-specific config JSON file (named after processor)
crop_region = None
# Automatically use the processor name from watch config as filename
processor_name = watch.get('processor', 'default')
config_filename = f'{processor_name}.json'
processor_config = self.get_extra_watch_config(config_filename) if self.get_extra_watch_config(config_filename) else {}
bounding_box = processor_config.get('bounding_box') if processor_config else None
# Get minimum change percentage (per-watch > global > env var default)
# This controls what percentage of pixels must change to trigger a detection
min_change_percentage = processor_config.get('min_change_percentage')
if not min_change_percentage:
min_change_percentage = self.datastore.data['settings']['application'].get('min_change_percentage', 1)
try:
min_change_percentage = int(min_change_percentage)
except (ValueError, TypeError):
logger.warning(f"Invalid min_change_percentage value '{min_change_percentage}', using default 0.1")
min_change_percentage = 1
# Template matching for tracking content movement
template_matching_enabled = processor_config.get('auto_track_region', False) #@@todo disabled for now
@@ -158,20 +171,7 @@ class perform_site_check(difference_detection_processor):
try:
from .image_handler import isolated_opencv as process_screenshot_handler
# Get blur sigma
blur_sigma = float(os.getenv("OPENCV_BLUR_SIGMA", "0.8"))
# Get minimum change percentage (per-watch > global > env var default)
# This controls what percentage of pixels must change to trigger a detection
min_change_percentage = watch.get('min_change_percentage')
if min_change_percentage is None or min_change_percentage == '':
min_change_percentage = self.datastore.data['settings']['application'].get('min_change_percentage', float(os.getenv("OPENCV_MIN_CHANGE_PERCENT", "0.1")))
try:
min_change_percentage = float(min_change_percentage)
except (ValueError, TypeError):
logger.warning(f"Invalid min_change_percentage value '{min_change_percentage}', using default 0.1")
min_change_percentage = 0.1
# stuff in watch doesnt need to be there
logger.debug(f"UUID: {watch.get('uuid')} - Starting isolated subprocess comparison (crop_region={crop_region})")
# Compare using isolated subprocess with OpenCV (async-safe to avoid blocking event loop)
@@ -184,10 +184,10 @@ class perform_site_check(difference_detection_processor):
def run_async_in_thread():
return asyncio.run(
process_screenshot_handler.compare_images_isolated(
previous_screenshot_bytes,
self.screenshot,
img_bytes_from=previous_screenshot_bytes,
img_bytes_to=self.screenshot,
pixel_difference_threshold=pixel_difference_threshold_sensitivity,
blur_sigma=blur_sigma,
blur_sigma=OPENCV_BLUR_SIGMA,
crop_region=crop_region # Pass crop region for isolated cropping
)
)
@@ -215,9 +215,7 @@ class perform_site_check(difference_detection_processor):
raise RuntimeError("Image comparison subprocess returned no result")
changed_detected = change_score > min_change_percentage
logger.debug(f"UUID: {watch.get('uuid')} - Isolated subprocess comparison completed: score={change_score:.2f}%, min_change_threshold={min_change_percentage}%")
logger.info(f"{process_screenshot_handler.IMPLEMENTATION_NAME}: {change_score:.2f}% pixels changed, pixel_diff_threshold_sensitivity: {pixel_difference_threshold_sensitivity:.0f}")
logger.info(f"UUID: {watch.get('uuid')} - {process_screenshot_handler.IMPLEMENTATION_NAME}: {change_score:.2f}% pixels changed, pixel_diff_threshold_sensitivity: {pixel_difference_threshold_sensitivity:.0f} score={change_score:.2f}%, min_change_threshold={min_change_percentage}%")
except Exception as e:
logger.error(f"UUID: {watch.get('uuid')} - Failed to compare screenshots: {e}")
@@ -783,7 +783,7 @@ textarea::placeholder {
display: block;
li {
margin-right: 3px;
margin-right: 1px;
display: inline-block;
color: var(--color-text-tab);
border-top-left-radius: 5px;
@@ -808,7 +808,7 @@ textarea::placeholder {
a {
display: block;
padding: 0.8em;
padding: 0.7em;
color: var(--color-text-tab);
}
}
+1 -1
View File
@@ -1 +1 @@
:root{--color-white: #fff;--color-grey-50: #111;--color-grey-100: #262626;--color-grey-200: #333;--color-grey-300: #444;--color-grey-325: #555;--color-grey-350: #565d64;--color-grey-400: #666;--color-grey-500: #777;--color-grey-600: #999;--color-grey-700: #cbcbcb;--color-grey-750: #ddd;--color-grey-800: #e0e0e0;--color-grey-850: #eee;--color-grey-900: #f2f2f2;--color-black: #000;--color-dark-red: #a00;--color-light-red: #dd0000;--color-background-page: var(--color-grey-100);--color-background-gradient-first: #5ad8f7;--color-background-gradient-second: #2f50af;--color-background-gradient-third: #9150bf;--color-background: var(--color-white);--color-text: var(--color-grey-200);--color-link: #1b98f8;--color-menu-accent: #ed5900;--color-background-code: var(--color-grey-850);--color-error: var(--color-dark-red);--color-error-input: #ffebeb;--color-error-list: var(--color-light-red);--color-table-background: var(--color-background);--color-table-stripe: var(--color-grey-900);--color-text-tab: var(--color-white);--color-background-tab: rgba(255, 255, 255, 0.2);--color-background-tab-hover: rgba(255, 255, 255, 0.5);--color-text-tab-active: #222;--color-api-key: #0078e7;--color-background-button-primary: #0078e7;--color-background-button-green: #42dd53;--color-background-button-red: #dd4242;--color-background-button-success: rgb(28, 184, 65);--color-background-button-error: rgb(202, 60, 60);--color-text-button-error: var(--color-white);--color-background-button-warning: rgb(202, 60, 60);--color-text-button-warning: var(--color-white);--color-background-button-secondary: rgb(66, 184, 221);--color-background-button-cancel: rgb(200, 200, 200);--color-text-button: var(--color-white);--color-background-button-tag: rgb(99, 99, 99);--color-background-snapshot-age: #dfdfdf;--color-error-text-snapshot-age: var(--color-white);--color-error-background-snapshot-age: #ff0000;--color-background-button-tag-active: #9c9c9c;--color-text-messages: var(--color-white);--color-background-messages-message: rgba(255, 255, 255, .2);--color-background-messages-error: rgba(255, 1, 1, .5);--color-background-messages-notice: rgba(255, 255, 255, .5);--color-border-notification: #ccc;--color-background-checkbox-operations: rgba(0, 0, 0, 0.05);--color-warning: #ff3300;--color-border-warning: var(--color-warning);--color-text-legend: var(--color-white);--color-link-new-version: #e07171;--color-last-checked: #bbb;--color-text-footer: #444;--color-border-watch-table-cell: #eee;--color-text-watch-tag-list: rgba(231, 0, 105, 0.4);--color-background-new-watch-form: rgba(0, 0, 0, 0.05);--color-background-new-watch-input: var(--color-white);--color-background-new-watch-input-transparent: rgba(255, 255, 255, 0.1);--color-text-new-watch-input: var(--color-text);--color-border-input: var(--color-grey-500);--color-shadow-input: var(--color-grey-400);--color-background-input: var(--color-white);--color-text-input: var(--color-text);--color-text-input-description: var(--color-grey-500);--color-text-input-placeholder: var(--color-grey-600);--color-background-table-thead: var(--color-grey-800);--color-border-table-cell: var(--color-grey-700);--color-text-menu-heading: var(--color-grey-350);--color-text-menu-link: var(--color-grey-500);--color-background-menu-link-hover: var(--color-grey-850);--color-text-menu-link-hover: var(--color-grey-300);--color-shadow-jump: var(--color-grey-500);--color-icon-github: var(--color-black);--color-icon-github-hover: var(--color-grey-300);--color-watch-table-error: var(--color-dark-red);--color-watch-table-row-text: var(--color-grey-100);--highlight-trigger-text-bg-color: #1b98f8;--highlight-ignored-text-bg-color: var(--color-grey-700);--highlight-blocked-text-bg-color: rgb(202, 60, 60)}html[data-darkmode=true]{--color-link: #59bdfb;--color-text: var(--color-white);--color-background-gradient-first: #3f90a5;--color-background-gradient-second: #1e316c;--color-background-gradient-third: #4d2c64;--color-background-new-watch-input: var(--color-grey-100);--color-background-new-watch-input-transparent: var(--color-grey-100);--color-text-new-watch-input: var(--color-text);--color-background-table-thead: var(--color-grey-200);--color-table-background: var(--color-grey-300);--color-table-stripe: var(--color-grey-325);--color-background: var(--color-grey-300);--color-text-menu-heading: var(--color-grey-850);--color-text-menu-link: var(--color-grey-800);--color-border-table-cell: var(--color-grey-400);--color-text-tab-active: var(--color-text);--color-border-input: var(--color-grey-400);--color-shadow-input: var(--color-grey-50);--color-background-input: var(--color-grey-350);--color-text-input-description: var(--color-grey-600);--color-text-input-placeholder: var(--color-grey-600);--color-text-watch-tag-list: rgba(250, 62, 146, 0.4);--color-background-code: var(--color-grey-200);--color-background-tab: rgba(0, 0, 0, 0.2);--color-background-tab-hover: rgba(0, 0, 0, 0.5);--color-background-snapshot-age: var(--color-grey-200);--color-shadoLine truncated
:root{--color-white: #fff;--color-grey-50: #111;--color-grey-100: #262626;--color-grey-200: #333;--color-grey-300: #444;--color-grey-325: #555;--color-grey-350: #565d64;--color-grey-400: #666;--color-grey-500: #777;--color-grey-600: #999;--color-grey-700: #cbcbcb;--color-grey-750: #ddd;--color-grey-800: #e0e0e0;--color-grey-850: #eee;--color-grey-900: #f2f2f2;--color-black: #000;--color-dark-red: #a00;--color-light-red: #dd0000;--color-background-page: var(--color-grey-100);--color-background-gradient-first: #5ad8f7;--color-background-gradient-second: #2f50af;--color-background-gradient-third: #9150bf;--color-background: var(--color-white);--color-text: var(--color-grey-200);--color-link: #1b98f8;--color-menu-accent: #ed5900;--color-background-code: var(--color-grey-850);--color-error: var(--color-dark-red);--color-error-input: #ffebeb;--color-error-list: var(--color-light-red);--color-table-background: var(--color-background);--color-table-stripe: var(--color-grey-900);--color-text-tab: var(--color-white);--color-background-tab: rgba(255, 255, 255, 0.2);--color-background-tab-hover: rgba(255, 255, 255, 0.5);--color-text-tab-active: #222;--color-api-key: #0078e7;--color-background-button-primary: #0078e7;--color-background-button-green: #42dd53;--color-background-button-red: #dd4242;--color-background-button-success: rgb(28, 184, 65);--color-background-button-error: rgb(202, 60, 60);--color-text-button-error: var(--color-white);--color-background-button-warning: rgb(202, 60, 60);--color-text-button-warning: var(--color-white);--color-background-button-secondary: rgb(66, 184, 221);--color-background-button-cancel: rgb(200, 200, 200);--color-text-button: var(--color-white);--color-background-button-tag: rgb(99, 99, 99);--color-background-snapshot-age: #dfdfdf;--color-error-text-snapshot-age: var(--color-white);--color-error-background-snapshot-age: #ff0000;--color-background-button-tag-active: #9c9c9c;--color-text-messages: var(--color-white);--color-background-messages-message: rgba(255, 255, 255, .2);--color-background-messages-error: rgba(255, 1, 1, .5);--color-background-messages-notice: rgba(255, 255, 255, .5);--color-border-notification: #ccc;--color-background-checkbox-operations: rgba(0, 0, 0, 0.05);--color-warning: #ff3300;--color-border-warning: var(--color-warning);--color-text-legend: var(--color-white);--color-link-new-version: #e07171;--color-last-checked: #bbb;--color-text-footer: #444;--color-border-watch-table-cell: #eee;--color-text-watch-tag-list: rgba(231, 0, 105, 0.4);--color-background-new-watch-form: rgba(0, 0, 0, 0.05);--color-background-new-watch-input: var(--color-white);--color-background-new-watch-input-transparent: rgba(255, 255, 255, 0.1);--color-text-new-watch-input: var(--color-text);--color-border-input: var(--color-grey-500);--color-shadow-input: var(--color-grey-400);--color-background-input: var(--color-white);--color-text-input: var(--color-text);--color-text-input-description: var(--color-grey-500);--color-text-input-placeholder: var(--color-grey-600);--color-background-table-thead: var(--color-grey-800);--color-border-table-cell: var(--color-grey-700);--color-text-menu-heading: var(--color-grey-350);--color-text-menu-link: var(--color-grey-500);--color-background-menu-link-hover: var(--color-grey-850);--color-text-menu-link-hover: var(--color-grey-300);--color-shadow-jump: var(--color-grey-500);--color-icon-github: var(--color-black);--color-icon-github-hover: var(--color-grey-300);--color-watch-table-error: var(--color-dark-red);--color-watch-table-row-text: var(--color-grey-100);--highlight-trigger-text-bg-color: #1b98f8;--highlight-ignored-text-bg-color: var(--color-grey-700);--highlight-blocked-text-bg-color: rgb(202, 60, 60)}html[data-darkmode=true]{--color-link: #59bdfb;--color-text: var(--color-white);--color-background-gradient-first: #3f90a5;--color-background-gradient-second: #1e316c;--color-background-gradient-third: #4d2c64;--color-background-new-watch-input: var(--color-grey-100);--color-background-new-watch-input-transparent: var(--color-grey-100);--color-text-new-watch-input: var(--color-text);--color-background-table-thead: var(--color-grey-200);--color-table-background: var(--color-grey-300);--color-table-stripe: var(--color-grey-325);--color-background: var(--color-grey-300);--color-text-menu-heading: var(--color-grey-850);--color-text-menu-link: var(--color-grey-800);--color-border-table-cell: var(--color-grey-400);--color-text-tab-active: var(--color-text);--color-border-input: var(--color-grey-400);--color-shadow-input: var(--color-grey-50);--color-background-input: var(--color-grey-350);--color-text-input-description: var(--color-grey-600);--color-text-input-placeholder: var(--color-grey-600);--color-text-watch-tag-list: rgba(250, 62, 146, 0.4);--color-background-code: var(--color-grey-200);--color-background-tab: rgba(0, 0, 0, 0.2);--color-background-tab-hover: rgba(0, 0, 0, 0.5);--color-background-snapshot-age: var(--color-grey-200);--color-shadoLine truncated